1use std::marker::PhantomData;
18use std::sync::{Arc, Mutex};
19use std::task::Poll;
20
21use bytes::Bytes;
22use moq_flate::{Decoder, Encoder};
23use serde::Serialize;
24use serde::de::DeserializeOwned;
25
26use crate::Result;
27
28#[derive(Debug, Clone, Default)]
33#[non_exhaustive]
34pub struct ProducerConfig {
35 pub compression: bool,
41}
42
43impl ProducerConfig {
44 pub fn with_compression(mut self, compression: bool) -> Self {
46 self.compression = compression;
47 self
48 }
49}
50
51#[derive(Debug, Clone, Default)]
56#[non_exhaustive]
57pub struct ConsumerConfig {
58 pub compression: bool,
61}
62
63impl ConsumerConfig {
64 pub fn with_compression(mut self, compression: bool) -> Self {
66 self.compression = compression;
67 self
68 }
69}
70
71pub struct Producer<T> {
76 inner: Arc<Mutex<Inner>>,
77 _marker: PhantomData<fn(T)>,
78}
79
80impl<T> Clone for Producer<T> {
81 fn clone(&self) -> Self {
82 Self {
83 inner: self.inner.clone(),
84 _marker: PhantomData,
85 }
86 }
87}
88
89impl<T> Producer<T> {
90 pub fn consume(&self) -> moq_net::TrackConsumer {
92 self.inner.lock().unwrap().track.consume()
93 }
94}
95
96impl<T: Serialize> Producer<T> {
97 pub fn new(track: moq_net::TrackProducer, config: ProducerConfig) -> Self {
99 Self {
100 inner: Arc::new(Mutex::new(Inner {
101 track,
102 group: None,
103 encoder: None,
104 config,
105 })),
106 _marker: PhantomData,
107 }
108 }
109
110 pub fn append(&mut self, value: &T) -> Result<()> {
112 self.inner.lock().unwrap().append(value)
113 }
114
115 pub fn finish(&mut self) -> Result<()> {
117 self.inner.lock().unwrap().finish()
118 }
119}
120
121struct Inner {
123 track: moq_net::TrackProducer,
124 group: Option<moq_net::GroupProducer>,
126 encoder: Option<Encoder>,
128 config: ProducerConfig,
129}
130
131impl Inner {
132 fn append<T: Serialize>(&mut self, value: &T) -> Result<()> {
133 let payload = Bytes::from(serde_json::to_vec(value)?);
134
135 if self.group.is_none() {
136 self.group = Some(self.track.append_group()?);
137 self.encoder = self.config.compression.then(Encoder::new);
138 }
139
140 let slice = match self.encoder.as_mut() {
141 Some(encoder) => encoder.frame(&payload),
142 None => payload,
143 };
144 self.group.as_mut().expect("a group is open").write_frame(slice)?;
145 Ok(())
146 }
147
148 fn finish(&mut self) -> Result<()> {
149 if let Some(mut group) = self.group.take() {
150 group.finish()?;
151 }
152 self.track.finish()?;
153 Ok(())
154 }
155}
156
157pub struct Consumer<T> {
161 track: moq_net::TrackConsumer,
162 group: Option<moq_net::GroupConsumer>,
163 compressed: bool,
164 decoder: Option<Decoder>,
166 _marker: PhantomData<fn() -> T>,
167}
168
169impl<T: DeserializeOwned> Consumer<T> {
170 pub fn new(track: moq_net::TrackConsumer, config: ConsumerConfig) -> Self {
175 Self {
176 track,
177 group: None,
178 compressed: config.compression,
179 decoder: None,
180 _marker: PhantomData,
181 }
182 }
183
184 pub async fn next(&mut self) -> Result<Option<T>>
186 where
187 T: Unpin,
188 {
189 kio::wait(|waiter| self.poll_next(waiter)).await
190 }
191
192 pub fn poll_next(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<T>>> {
194 loop {
195 let Some(group) = &mut self.group else {
196 match self.track.poll_next_group(waiter)? {
197 Poll::Ready(Some(group)) => {
198 self.decoder = self.compressed.then(Decoder::new);
199 self.group = Some(group);
200 continue;
201 }
202 Poll::Ready(None) => return Poll::Ready(Ok(None)),
203 Poll::Pending => return Poll::Pending,
204 }
205 };
206
207 match group.poll_read_frame(waiter)? {
208 Poll::Ready(Some(frame)) => {
209 let plain = match self.decoder.as_mut() {
210 Some(decoder) => decoder.frame(&frame)?,
211 None => frame,
212 };
213 return Poll::Ready(Ok(Some(serde_json::from_slice(&plain)?)));
214 }
215 Poll::Ready(None) => {
216 self.group = None;
219 self.decoder = None;
220 }
221 Poll::Pending => return Poll::Pending,
222 }
223 }
224 }
225}
226
227#[cfg(test)]
228mod test {
229 use super::*;
230 use serde_json::{Value, json};
231
232 fn producer(config: ProducerConfig) -> (Producer<Value>, moq_net::TrackConsumer) {
233 let track = moq_net::Track::new("test").produce();
234 let consumer = track.consume();
235 (Producer::new(track, config), consumer)
236 }
237
238 fn compressed() -> ProducerConfig {
239 ProducerConfig { compression: true }
240 }
241
242 fn consumer(track: moq_net::TrackConsumer, compression: bool) -> Consumer<Value> {
243 Consumer::new(track, ConsumerConfig { compression })
244 }
245
246 fn drain(mut consumer: Consumer<Value>) -> Vec<Value> {
248 let waiter = kio::Waiter::noop();
249 let mut out = Vec::new();
250 while let Poll::Ready(Ok(Some(value))) = consumer.poll_next(&waiter) {
251 out.push(value);
252 }
253 out
254 }
255
256 #[test]
257 fn plaintext_roundtrip_in_order() {
258 let (mut producer, track) = producer(ProducerConfig::default());
259 for n in 0..5 {
260 producer.append(&json!({ "n": n })).unwrap();
261 }
262 producer.finish().unwrap();
263
264 let records = drain(consumer(track, false));
265 assert_eq!(records, (0..5).map(|n| json!({ "n": n })).collect::<Vec<_>>());
266 }
267
268 #[test]
269 fn compressed_roundtrip_in_order() {
270 let (mut producer, track) = producer(compressed());
271 for n in 0..20 {
272 producer.append(&json!({ "group": n, "pts": n * 2_000 })).unwrap();
273 }
274 producer.finish().unwrap();
275
276 let records = drain(consumer(track, true));
277 assert_eq!(records.len(), 20);
278 assert_eq!(records[7], json!({ "group": 7, "pts": 14_000 }));
279 }
280
281 #[test]
282 fn all_records_ride_one_group() {
283 let (mut producer, track) = producer(compressed());
284 for n in 0..50 {
285 producer.append(&json!({ "n": n })).unwrap();
286 }
287 producer.finish().unwrap();
288
289 assert_eq!(track.latest(), Some(0));
291 assert_eq!(drain(consumer(track, true)).len(), 50);
292 }
293
294 #[test]
295 fn live_consumer_sees_each_record() {
296 let (mut producer, track) = producer(compressed());
297 let mut consumer = consumer(track, true);
298 let waiter = kio::Waiter::noop();
299
300 for n in 0..3 {
301 producer.append(&json!({ "n": n })).unwrap();
302 match consumer.poll_next(&waiter) {
303 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "n": n })),
304 other => panic!("expected record, got {other:?}"),
305 }
306 }
307 assert!(matches!(consumer.poll_next(&waiter), Poll::Pending));
308 producer.finish().unwrap();
309 }
310
311 #[test]
312 fn shared_window_shrinks_repetitive_records() {
313 let (mut producer, mut track) = producer(compressed());
314 for n in 0..8 {
315 producer.append(&json!({ "group": n, "pts": n * 2_000 })).unwrap();
316 }
317 producer.finish().unwrap();
318
319 let waiter = kio::Waiter::noop();
320 let Poll::Ready(Ok(Some(mut group))) = track.poll_next_group(&waiter) else {
321 panic!("expected a group");
322 };
323 let mut sizes = Vec::new();
324 while let Poll::Ready(Ok(Some(frame))) = group.poll_read_frame(&waiter) {
325 sizes.push(frame.len());
326 }
327 assert_eq!(sizes.len(), 8);
328 let raw = serde_json::to_vec(&json!({ "group": 7, "pts": 14_000 })).unwrap().len();
329 assert!(
330 *sizes.last().unwrap() < raw / 2,
331 "windowed record {} should be far below its raw size {raw}",
332 sizes.last().unwrap()
333 );
334 }
335
336 #[test]
337 fn embedded_newlines_survive() {
338 let (mut producer, track) = producer(compressed());
341 let value = json!({ "s": "line1\nline2\ttab", "u": "a\u{000a}b" });
342 for _ in 0..4 {
343 producer.append(&value).unwrap();
344 }
345 producer.finish().unwrap();
346
347 let records = drain(consumer(track, true));
348 assert_eq!(records, vec![value.clone(), value.clone(), value.clone(), value]);
349 }
350}