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