1use std::collections::{BTreeMap, BTreeSet, VecDeque};
53use std::ops::Deref;
54use std::sync::{Arc, Mutex};
55use std::task::Poll;
56use std::time::Duration;
57
58use hang::catalog::Archive;
59use hang::timeline::{DEFAULT_NAME, Range, Record, RecordExt};
60
61use moq_net::{Timescale, Timestamp};
62
63pub const DEFAULT_DURATION_MIN: Duration = Duration::from_secs(1);
65
66const CHECKPOINT_RECORDS: usize = 256;
68
69#[derive(Clone, Debug)]
71#[non_exhaustive]
72pub struct Config {
73 pub duration_min: Duration,
84
85 pub duration_max: Option<Duration>,
94}
95
96impl Default for Config {
97 fn default() -> Self {
98 Self {
99 duration_min: DEFAULT_DURATION_MIN,
100 duration_max: None,
101 }
102 }
103}
104
105#[derive(Default)]
107struct TrackState {
108 pending: VecDeque<(u64, Timestamp, bool)>,
110 frontier: Option<Timestamp>,
114 closed: bool,
116 pacing: bool,
118}
119
120impl TrackState {
121 fn candidate(&self, threshold: u128) -> Option<Timestamp> {
124 self.pending
125 .iter()
126 .map(|&(_, pts, _)| pts)
127 .find(|pts| pts.as_micros() >= threshold)
128 }
129}
130
131struct State {
133 config: Config,
134 ready: VecDeque<Record>,
136 committing: BTreeSet<u64>,
138 committed: BTreeMap<u64, Record>,
140 start: Option<Timestamp>,
142 cuts: VecDeque<Timestamp>,
144 manual: bool,
148 next_segment: u64,
150 tracks: BTreeMap<String, TrackState>,
152 reservers: usize,
155 overrun: Option<(u64, Duration)>,
158 finished: bool,
160 deferred: bool,
162 timescale: Timescale,
164}
165
166impl State {
167 fn requeue(&mut self, record: Record) {
169 self.committing.remove(&record.segment);
170 let index = self
171 .ready
172 .iter()
173 .position(|ready| ready.segment > record.segment)
174 .unwrap_or(self.ready.len());
175 self.ready.insert(index, record);
176 }
177
178 fn report(&mut self, name: &str, sequence: u64, pts: Timestamp, keyframe: bool) {
180 if self.finished {
181 return;
182 }
183 let Some(track) = self.tracks.get_mut(name) else {
184 return;
185 };
186 track.pending.push_back((sequence, pts, keyframe));
187 self.advance(name, pts);
188 }
189
190 fn report_end(&mut self, name: &str, pts: Timestamp) {
193 if self.finished {
194 return;
195 }
196 if self.tracks.contains_key(name) {
197 self.advance(name, pts);
198 }
199 }
200
201 fn advance(&mut self, name: &str, pts: Timestamp) {
203 if let Some(track) = self.tracks.get_mut(name)
204 && track.frontier.is_none_or(|f| pts.as_micros() > f.as_micros())
205 {
206 track.frontier = Some(pts);
207 }
208
209 if self.start.is_none() && self.tracks.get(name).is_some_and(|t| t.pacing) {
214 self.start = Some(pts);
215 }
216
217 self.pump(false);
218 }
219
220 fn close(&mut self, name: &str) {
222 if self.finished {
223 return;
224 }
225 if let Some(track) = self.tracks.get_mut(name) {
226 track.closed = true;
227 }
228 self.pump(false);
229 }
230
231 fn pump(&mut self, finished: bool) {
236 if self.finished || self.tracks.is_empty() || self.reservers > 0 || self.overrun.is_some() {
240 return;
241 }
242
243 while self.close_segment(finished) {
244 if self.overrun.is_some() {
247 break;
248 }
249 }
250 }
251
252 fn close_segment(&mut self, finished: bool) -> bool {
254 let Some(start) = self.start else {
255 return false;
256 };
257
258 while self.cuts.front().is_some_and(|c| c.as_micros() <= start.as_micros()) {
262 self.cuts.pop_front();
263 }
264
265 let Some((end, cut)) = self.boundary(start, finished) else {
266 return false;
267 };
268
269 let complete = finished
274 || self
275 .tracks
276 .values()
277 .all(|t| !t.pacing || t.closed || t.frontier.is_some_and(|f| f.as_micros() >= end.as_micros()));
278 if !complete {
279 return false;
280 }
281
282 self.flush_segment(start, Some(end));
283 if cut {
284 self.cuts.pop_front();
285 }
286 self.start = Some(end);
287 true
288 }
289
290 fn boundary(&self, start: Timestamp, finished: bool) -> Option<(Timestamp, bool)> {
294 if let Some(&cut) = self.cuts.front()
295 && cut.as_micros() > start.as_micros()
296 {
297 return Some((cut, true));
298 }
299
300 if self.manual {
303 return None;
304 }
305
306 let threshold = start.as_micros() + self.config.duration_min.as_micros();
307
308 let mut end: Option<Timestamp> = None;
309 for track in self.tracks.values().filter(|t| t.pacing) {
310 match track.candidate(threshold) {
311 Some(pts) => {
316 if end.is_none_or(|e| pts.as_micros() > e.as_micros()) {
317 end = Some(pts);
318 }
319 }
320 None if finished || track.closed => continue,
324 None => return None,
325 }
326 }
327
328 end.map(|end| (end, false))
329 }
330
331 fn flush_segment(&mut self, start: Timestamp, end: Option<Timestamp>) {
334 let pts = start.as_scale(self.timescale) as u64;
335 let duration = match end {
336 Some(end) => (end.as_scale(self.timescale) as u64).saturating_sub(pts),
337 None => self
342 .tracks
343 .values()
344 .filter(|t| t.pacing)
345 .filter_map(|t| t.frontier)
346 .map(|f| f.as_scale(self.timescale) as u64)
347 .max()
348 .unwrap_or(pts)
349 .saturating_sub(pts),
350 };
351
352 if let Some(max) = self.config.duration_max
357 && duration > self.units(max)
358 {
359 let observed = Duration::from_nanos(duration * (1_000_000_000 / self.timescale.as_u64()));
360 tracing::error!(
361 segment = self.next_segment,
362 duration = ?observed,
363 duration_max = ?max,
364 "segment exceeded the declared duration_max; dropping the timeline track"
365 );
366 self.overrun = Some((self.next_segment, observed));
367 return;
368 }
369
370 let mut record = Record::new(self.next_segment, pts, duration);
371 self.next_segment += 1;
372
373 for (name, track) in &mut self.tracks {
374 let mut ranges: Vec<Range> = Vec::new();
375 while let Some(&(sequence, group_pts, keyframe)) = track.pending.front() {
376 if track.pacing && end.is_some_and(|end| group_pts.as_micros() >= end.as_micros()) {
380 break;
381 }
382 track.pending.pop_front();
383 match ranges.last_mut() {
384 Some(last) if last.end + 1 == sequence => last.end = sequence,
387 _ => {
388 let mut range = Range::new(sequence, sequence);
389 range.keyframe = keyframe;
390 ranges.push(range);
391 }
392 }
393 }
394 if !ranges.is_empty() {
395 record.tracks.insert(name.clone(), ranges);
396 }
397 }
398
399 self.ready.push_back(record);
400 }
401
402 fn units(&self, duration: Duration) -> u64 {
404 (duration.as_micros() * self.timescale.as_u64() as u128).div_ceil(1_000_000) as u64
405 }
406
407 fn finish(&mut self) {
409 if self.finished {
410 return;
411 }
412
413 self.reservers = 0;
415 self.pump(true);
416
417 if self.overrun.is_none()
419 && let Some(start) = self.start.take()
420 && self.tracks.values().any(|t| !t.pending.is_empty())
421 {
422 self.flush_segment(start, None);
423 }
424
425 self.finished = true;
426 }
427
428 fn failure(&self) -> Option<crate::Error> {
430 let (segment, duration) = self.overrun?;
431 Some(crate::Error::TimelineOverrun {
432 segment,
433 duration,
434 duration_max: self.config.duration_max.unwrap_or_default(),
435 })
436 }
437
438 fn section(&self) -> Archive {
443 let mut section = Archive::new(DEFAULT_NAME);
444 section.timescale = self.timescale.as_u64() as u32;
445 section.duration_max = self.config.duration_max.map(|max| self.units(max));
446 section
447 }
448}
449
450#[derive(Clone)]
457pub struct Segmenter {
458 state: Arc<Mutex<State>>,
459}
460
461impl Segmenter {
462 pub fn new(config: Config) -> Self {
464 #[allow(clippy::arc_with_non_send_sync)]
468 Self {
469 state: Arc::new(Mutex::new(State {
470 config,
471 ready: VecDeque::new(),
472 committing: BTreeSet::new(),
473 committed: BTreeMap::new(),
474 start: None,
475 cuts: VecDeque::new(),
476 manual: false,
477 next_segment: 0,
478 tracks: BTreeMap::new(),
479 reservers: 0,
480 overrun: None,
481 finished: false,
482 deferred: false,
483 timescale: Timescale::MILLI,
484 })),
485 }
486 }
487
488 pub fn track(&self, name: &str) -> Recorder {
490 self.enroll(name, false, None)
491 }
492
493 pub fn pacing_track(&self, name: &str) -> Recorder {
495 self.enroll(name, true, None)
496 }
497
498 fn enroll(&self, name: &str, pacing: bool, output: Option<Arc<Mutex<Output>>>) -> Recorder {
499 let mut state = self.state.lock().unwrap();
500 let output = if state.finished {
501 None
502 } else {
503 state.tracks.insert(
504 name.to_string(),
505 TrackState {
506 pacing,
507 ..Default::default()
508 },
509 );
510 output
511 };
512 drop(state);
513
514 Recorder {
515 segmenter: self.clone(),
516 output,
517 name: name.to_string(),
518 }
519 }
520
521 pub fn cut(&self, pts: Timestamp) -> crate::Result<()> {
523 let mut state = self.state.lock().unwrap();
524 if let Some(err) = state.failure() {
525 return Err(err);
526 }
527 if state.finished {
528 return Err(moq_net::Error::Closed.into());
529 }
530
531 state.manual = true;
532 let floor = state
533 .cuts
534 .back()
535 .copied()
536 .or(state.start)
537 .map(|since| since.as_micros() + state.config.duration_min.as_micros());
538 if floor.is_none_or(|floor| pts.as_micros() >= floor) {
539 state.cuts.push_back(pts);
540 state.pump(false);
541 }
542
543 Ok(())
544 }
545
546 pub fn reserve(&self) -> Reserved {
548 let mut state = self.state.lock().unwrap();
549 if !state.finished {
550 state.reservers += 1;
551 }
552 Reserved {
553 segmenter: self.clone(),
554 output: None,
555 }
556 }
557
558 pub fn section(&self) -> Archive {
560 self.state.lock().unwrap().section()
561 }
562
563 pub fn finish(self) -> Drain {
565 let mut state = self.state.lock().unwrap();
566 state.finish();
567 drop(state);
568 Drain { segmenter: self }
569 }
570
571 pub fn next(&self) -> Option<Record> {
573 self.state.lock().unwrap().ready.pop_front()
574 }
575}
576
577pub struct Drain {
579 segmenter: Segmenter,
580}
581
582impl Drain {
583 pub fn result(&self) -> crate::Result<()> {
585 result(&self.segmenter)
586 }
587
588 pub fn next(&self) -> Option<Record> {
590 self.segmenter.next()
591 }
592}
593
594#[derive(Clone)]
599pub struct Deferred {
600 segmenter: Segmenter,
601}
602
603impl Deferred {
604 pub fn track(&self, name: &str) -> Recorder {
606 self.segmenter.track(name)
607 }
608
609 pub fn pacing_track(&self, name: &str) -> Recorder {
611 self.segmenter.pacing_track(name)
612 }
613
614 pub fn cut(&self, pts: Timestamp) -> crate::Result<()> {
616 self.segmenter.cut(pts)
617 }
618
619 pub fn reserve(&self) -> Reserved {
621 self.segmenter.reserve()
622 }
623
624 pub fn section(&self) -> Archive {
626 self.segmenter.section()
627 }
628
629 pub fn finish(self) -> DeferredDrain {
631 let drain = self.segmenter.finish();
632 DeferredDrain {
633 segmenter: drain.segmenter,
634 }
635 }
636
637 pub fn next(&self) -> Option<Pending> {
639 pending(&self.segmenter)
640 }
641}
642
643pub struct DeferredDrain {
645 segmenter: Segmenter,
646}
647
648impl DeferredDrain {
649 pub fn result(&self) -> crate::Result<()> {
651 result(&self.segmenter)
652 }
653
654 pub fn next(&self) -> Option<Pending> {
656 pending(&self.segmenter)
657 }
658}
659
660fn result(segmenter: &Segmenter) -> crate::Result<()> {
661 match segmenter.state.lock().unwrap().failure() {
662 Some(err) => Err(err),
663 None => Ok(()),
664 }
665}
666
667fn pending(segmenter: &Segmenter) -> Option<Pending> {
668 let mut state = segmenter.state.lock().unwrap();
669 let record = state.ready.pop_front()?;
670 state.committing.insert(record.segment);
671 drop(state);
672 Some(Pending {
673 segmenter: segmenter.clone(),
674 record: Some(record),
675 })
676}
677
678pub struct Pending {
683 segmenter: Segmenter,
684 record: Option<Record>,
685}
686
687impl Pending {
688 pub fn gap(mut self) -> Self {
693 self.record
694 .as_mut()
695 .expect("a pending record is present until commit")
696 .tracks
697 .clear();
698 self
699 }
700}
701
702impl Deref for Pending {
703 type Target = Record;
704
705 fn deref(&self) -> &Self::Target {
706 self.record.as_ref().expect("a pending record is present until commit")
707 }
708}
709
710impl Drop for Pending {
711 fn drop(&mut self) {
712 let Some(record) = self.record.take() else {
713 return;
714 };
715 let mut state = self.segmenter.state.lock().unwrap();
716 state.requeue(record);
717 }
718}
719
720struct Output {
721 broadcast: moq_net::broadcast::Producer,
722 sink: Option<moq_json::window::Producer<Record>>,
723 closed: bool,
724}
725
726impl Output {
727 fn prepare(&mut self) -> crate::Result<()> {
728 if self.sink.is_none() && !self.closed {
729 let info = moq_net::track::Info::default().with_priority(hang::catalog::PRIORITY.catalog);
730 let net = self.broadcast.create_track(DEFAULT_NAME, info)?;
731 let config = moq_json::window::ProducerConfig::default()
732 .with_compression(true)
733 .with_checkpoint_records(CHECKPOINT_RECORDS);
734 self.sink = Some(moq_json::window::Producer::new(net, config));
735 }
736 Ok(())
737 }
738
739 fn push(&mut self, record: &Record) -> crate::Result<()> {
740 self.prepare()?;
741 let Some(sink) = self.sink.as_mut() else {
742 return Err(moq_net::Error::Closed.into());
743 };
744 match sink.push(record) {
745 Ok(()) => Ok(()),
746 Err(moq_json::Error::Net(err)) => Err(err.into()),
747 Err(err) => Err(err.into()),
748 }
749 }
750
751 fn pop(&mut self, count: u64) -> crate::Result<()> {
752 let Some(sink) = self.sink.as_mut() else {
753 return Ok(());
754 };
755 match sink.pop(count) {
756 Ok(()) => Ok(()),
757 Err(moq_json::Error::Net(err)) => Err(err.into()),
758 Err(err) => Err(err.into()),
759 }
760 }
761
762 fn finish(&mut self) -> crate::Result<()> {
763 self.closed = true;
764 let Some(sink) = self.sink.as_mut() else {
765 return Ok(());
766 };
767 match sink.finish() {
770 Ok(()) => Ok(()),
771 Err(moq_json::Error::Net(err)) => Err(err.into()),
772 Err(err) => Err(err.into()),
773 }
774 }
775}
776
777#[derive(Clone)]
782pub struct Producer {
783 segmenter: Segmenter,
784 output: Arc<Mutex<Output>>,
785}
786
787impl Producer {
788 pub fn new(broadcast: &moq_net::broadcast::Producer, config: Config) -> Self {
790 #[allow(clippy::arc_with_non_send_sync)]
792 let output = Arc::new(Mutex::new(Output {
793 broadcast: broadcast.clone(),
794 sink: None,
795 closed: false,
796 }));
797 Self {
798 segmenter: Segmenter::new(config),
799 output,
800 }
801 }
802
803 pub fn track(&self, name: &str) -> Recorder {
817 self.segmenter.enroll(name, false, Some(self.output.clone()))
818 }
819
820 pub fn pacing_track(&self, name: &str) -> crate::Result<Recorder> {
834 self.output.lock().unwrap().prepare()?;
835 Ok(self.segmenter.enroll(name, true, Some(self.output.clone())))
836 }
837
838 pub fn cut(&self, pts: Timestamp) -> crate::Result<()> {
853 self.segmenter.cut(pts)?;
854 self.publish_ready();
855 Ok(())
856 }
857
858 pub fn reserve(&self) -> Reserved {
873 let mut state = self.segmenter.state.lock().unwrap();
874 let output = if state.finished {
875 None
876 } else {
877 state.reservers += 1;
878 Some(self.output.clone())
879 };
880 Reserved {
881 segmenter: self.segmenter.clone(),
882 output,
883 }
884 }
885
886 pub fn section(&self) -> Archive {
888 self.segmenter.section()
889 }
890
891 pub fn deferred(&self) -> crate::Result<Deferred> {
898 let mut output = self.output.lock().unwrap();
901 let mut state = self.segmenter.state.lock().unwrap();
902 if state.finished {
903 return Err(moq_net::Error::Closed.into());
904 }
905 output.prepare()?;
906 state.deferred = true;
907 Ok(Deferred {
908 segmenter: self.segmenter.clone(),
909 })
910 }
911
912 pub fn drain(&self) -> crate::Result<DeferredDrain> {
917 let state = self.segmenter.state.lock().unwrap();
918 if !state.deferred || !state.finished {
919 return Err(crate::Error::TimelineDeferredPending);
920 }
921 drop(state);
922 Ok(DeferredDrain {
923 segmenter: self.segmenter.clone(),
924 })
925 }
926
927 pub fn push(&self, mut pending: Pending) -> crate::Result<()> {
929 if !Arc::ptr_eq(&self.segmenter.state, &pending.segmenter.state) {
930 return Err(crate::Error::TimelineDeferredRecord(pending.segment));
931 }
932
933 let mut output = self.output.lock().unwrap();
934 let mut state = self.segmenter.state.lock().unwrap();
935 if !state.deferred || !state.committing.contains(&pending.segment) {
936 return Err(crate::Error::TimelineDeferredRecord(pending.segment));
937 }
938
939 let record = pending.record.take().expect("a pending record is present until commit");
940 state.committing.remove(&record.segment);
941 state.committed.insert(record.segment, record);
942
943 while let Some((&segment, record)) = state.committed.first_key_value() {
944 let earlier_ready = state.ready.front().is_some_and(|record| record.segment < segment);
945 let earlier_committing = state.committing.first().is_some_and(|pending| *pending < segment);
946 if earlier_ready || earlier_committing {
947 break;
948 }
949
950 let record = record.clone();
951 if let Err(err) = output.push(&record) {
952 let record = state.committed.remove(&segment).unwrap();
953 state.requeue(record);
954 return Err(err);
955 }
956 state.committed.remove(&segment);
957 }
958
959 Ok(())
960 }
961
962 pub fn pop(&self, count: u64) -> crate::Result<()> {
964 self.output.lock().unwrap().pop(count)
965 }
966
967 fn publish_ready(&self) {
968 let mut output = self.output.lock().unwrap();
972 if self.segmenter.state.lock().unwrap().deferred {
973 return;
974 }
975 while let Some(record) = self.segmenter.next() {
976 if let Err(err) = output.push(&record) {
977 tracing::warn!(%err, "timeline publish failed; dropping the timeline track");
978 let _ = output.finish();
979 return;
980 }
981 }
982 if self.segmenter.state.lock().unwrap().failure().is_some() {
983 let _ = output.finish();
984 }
985 }
986
987 pub fn finish(&mut self) -> crate::Result<()> {
996 let mut output = self.output.lock().unwrap();
1000 let mut state = self.segmenter.state.lock().unwrap();
1001 if state.deferred {
1002 if !state.finished || !state.ready.is_empty() || !state.committing.is_empty() || !state.committed.is_empty()
1003 {
1004 return Err(crate::Error::TimelineDeferredPending);
1005 }
1006 if let Some(err) = state.failure() {
1007 drop(state);
1008 let _ = output.finish();
1009 return Err(err);
1010 }
1011 drop(state);
1012 return output.finish();
1013 }
1014
1015 state.finish();
1016 if let Some(err) = state.failure() {
1017 drop(state);
1018 let _ = output.finish();
1019 return Err(err);
1020 }
1021 while let Some(record) = state.ready.pop_front() {
1022 if let Err(err) = output.push(&record) {
1023 drop(state);
1024 let _ = output.finish();
1025 return Err(err);
1026 }
1027 }
1028 drop(state);
1029 output.finish()
1030 }
1031}
1032
1033pub struct Reserved {
1038 segmenter: Segmenter,
1039 output: Option<Arc<Mutex<Output>>>,
1040}
1041
1042impl Clone for Reserved {
1043 fn clone(&self) -> Self {
1044 let mut state = self.segmenter.state.lock().unwrap();
1045 if !state.finished {
1046 state.reservers += 1;
1047 }
1048 Self {
1049 segmenter: self.segmenter.clone(),
1050 output: self.output.clone(),
1051 }
1052 }
1053}
1054
1055impl Drop for Reserved {
1056 fn drop(&mut self) {
1057 let mut state = self.segmenter.state.lock().unwrap();
1058 state.reservers = state.reservers.saturating_sub(1);
1059 state.pump(false);
1060 drop(state);
1061 if let Some(output) = &self.output {
1062 Producer {
1063 segmenter: self.segmenter.clone(),
1064 output: output.clone(),
1065 }
1066 .publish_ready();
1067 }
1068 }
1069}
1070
1071pub struct Recorder {
1077 segmenter: Segmenter,
1078 output: Option<Arc<Mutex<Output>>>,
1079 name: String,
1080}
1081
1082impl Recorder {
1083 pub fn record(&mut self, sequence: u64, pts: Timestamp, keyframe: bool) {
1089 self.segmenter
1090 .state
1091 .lock()
1092 .unwrap()
1093 .report(&self.name, sequence, pts, keyframe);
1094 self.publish_ready();
1095 }
1096
1097 pub fn end(&mut self, pts: Timestamp) {
1104 self.segmenter.state.lock().unwrap().report_end(&self.name, pts);
1105 self.publish_ready();
1106 }
1107
1108 fn publish_ready(&self) {
1109 if let Some(output) = &self.output {
1110 Producer {
1111 segmenter: self.segmenter.clone(),
1112 output: output.clone(),
1113 }
1114 .publish_ready();
1115 }
1116 }
1117}
1118
1119impl Drop for Recorder {
1120 fn drop(&mut self) {
1121 self.segmenter.state.lock().unwrap().close(&self.name);
1122 self.publish_ready();
1123 }
1124}
1125
1126#[derive(Debug, Clone, PartialEq)]
1129pub struct Entry<E: RecordExt = ()> {
1130 pub segment: u64,
1132
1133 pub pts: Timestamp,
1135
1136 pub duration: Duration,
1139
1140 pub tracks: BTreeMap<String, Vec<Range>>,
1143
1144 pub ext: E,
1146}
1147
1148#[derive(Debug, Clone, PartialEq)]
1150#[non_exhaustive]
1151pub enum Event<E: RecordExt = ()> {
1152 Push {
1154 index: u64,
1156 entry: Entry<E>,
1158 },
1159 Pop(std::ops::Range<u64>),
1161 Skip(std::ops::Range<u64>),
1163}
1164
1165pub struct Consumer<E: RecordExt = ()> {
1169 inner: moq_json::window::Consumer<Record<E>>,
1170 timescale: Timescale,
1171}
1172
1173impl<E: RecordExt> Consumer<E> {
1174 pub async fn subscribe(broadcast: &moq_net::broadcast::Consumer, section: &Archive) -> crate::Result<Self> {
1180 let track = broadcast.track(§ion.track)?.subscribe(None).await?;
1181
1182 let config = moq_json::window::ConsumerConfig::default().with_compression(true);
1183
1184 Ok(Self {
1185 inner: moq_json::window::Consumer::new(track, config),
1186 timescale: Timescale::new(section.timescale as u64)
1187 .map_err(|_| crate::Error::InvalidTimescale(section.timescale))?,
1188 })
1189 }
1190
1191 fn decode(&self, record: Record<E>) -> crate::Result<Entry<E>> {
1196 let scale = self.timescale.as_u64() as u128;
1197 let nanos = (record.duration as u128) * 1_000_000_000 / scale;
1198 Ok(Entry {
1199 segment: record.segment,
1200 pts: Timestamp::new(record.pts, self.timescale)?,
1201 duration: Duration::from_nanos(nanos as u64),
1202 tracks: record.tracks,
1203 ext: record.ext,
1204 })
1205 }
1206
1207 fn decode_event(&self, event: moq_json::window::Event<Record<E>>) -> crate::Result<Event<E>> {
1208 Ok(match event {
1209 moq_json::window::Event::Push { index, value } => Event::Push {
1210 index,
1211 entry: self.decode(value)?,
1212 },
1213 moq_json::window::Event::Pop(range) => Event::Pop(range),
1214 moq_json::window::Event::Skip(range) => Event::Skip(range),
1215 _ => unreachable!("unknown timeline window event"),
1216 })
1217 }
1218
1219 pub async fn next(&mut self) -> crate::Result<Option<Event<E>>> {
1221 match self.inner.next().await? {
1222 Some(event) => Ok(Some(self.decode_event(event)?)),
1223 None => Ok(None),
1224 }
1225 }
1226
1227 pub fn poll_next(&mut self, waiter: &kio::Waiter) -> Poll<crate::Result<Option<Event<E>>>> {
1229 match self.inner.poll_next(waiter)? {
1230 Poll::Ready(Some(event)) => Poll::Ready(self.decode_event(event).map(Some)),
1231 Poll::Ready(None) => Poll::Ready(Ok(None)),
1232 Poll::Pending => Poll::Pending,
1233 }
1234 }
1235}
1236
1237#[cfg(test)]
1238mod test {
1239 use super::*;
1240
1241 fn ms(v: u64) -> Timestamp {
1242 Timestamp::from_millis(v).unwrap()
1243 }
1244
1245 fn entry(segment: u64, pts_ms: u64, duration_ms: u64, tracks: &[(&str, &[(u64, u64)])]) -> Entry {
1247 Entry {
1248 segment,
1249 pts: ms(pts_ms),
1250 duration: Duration::from_millis(duration_ms),
1251 tracks: tracks
1252 .iter()
1253 .map(|(name, ranges)| {
1254 (
1255 name.to_string(),
1256 ranges.iter().map(|&(start, end)| Range::new(start, end)).collect(),
1257 )
1258 })
1259 .collect(),
1260 ext: (),
1261 }
1262 }
1263
1264 async fn drain(broadcast: &moq_net::broadcast::Producer, producer: &Producer) -> Vec<Entry> {
1266 let mut consumer = Consumer::subscribe(&broadcast.consume(), &producer.section())
1267 .await
1268 .unwrap();
1269 drain_consumer(&mut consumer)
1270 }
1271
1272 fn drain_consumer(consumer: &mut Consumer<()>) -> Vec<Entry> {
1273 let waiter = kio::Waiter::noop();
1274 let mut out = Vec::new();
1275 while let Poll::Ready(Ok(Some(event))) = consumer.poll_next(&waiter) {
1276 if let Event::Push { entry, .. } = event {
1277 out.push(entry);
1278 }
1279 }
1280 out
1281 }
1282
1283 fn setup() -> (moq_net::broadcast::Producer, Producer) {
1284 setup_with(Config::default())
1285 }
1286
1287 fn setup_with(config: Config) -> (moq_net::broadcast::Producer, Producer) {
1288 let broadcast = moq_net::broadcast::Info::new().produce();
1289 let timeline = Producer::new(&broadcast, config);
1290 (broadcast, timeline)
1291 }
1292
1293 #[tokio::test]
1294 async fn deferred_records_are_invisible_until_committed() {
1295 let (broadcast, timeline) = setup();
1296 let segmenter = timeline.deferred().unwrap();
1297 let mut video = segmenter.pacing_track("video0");
1298 let mut consumer = Consumer::<()>::subscribe(&broadcast.consume(), &timeline.section())
1299 .await
1300 .unwrap();
1301 let waiter = kio::Waiter::noop();
1302
1303 video.record(0, ms(0), true);
1304 video.record(1, ms(2_000), true);
1305 let record = segmenter.next().expect("the complete segment is ready for storage");
1306 assert!(matches!(consumer.poll_next(&waiter), Poll::Pending));
1307
1308 timeline.push(record).unwrap();
1309 let Poll::Ready(Ok(Some(Event::Push { index, entry: actual }))) = consumer.poll_next(&waiter) else {
1310 panic!("the committed segment was not visible");
1311 };
1312 assert_eq!(index, 0);
1313 assert_eq!(actual, entry(0, 0, 2_000, &[("video0", &[(0, 0)])]));
1314
1315 timeline.pop(1).unwrap();
1316 assert!(matches!(
1317 consumer.poll_next(&waiter),
1318 Poll::Ready(Ok(Some(Event::Pop(range)))) if range == (0..1)
1319 ));
1320
1321 video.end(ms(4_000));
1322 drop(video);
1323 segmenter.finish().result().unwrap();
1324 }
1325
1326 #[tokio::test]
1327 async fn deferred_disables_existing_auto_publishing_recorders() {
1328 let (broadcast, timeline) = setup();
1329 let mut video = timeline.pacing_track("video0").unwrap();
1330 let segmenter = timeline.deferred().unwrap();
1331 let mut consumer = Consumer::<()>::subscribe(&broadcast.consume(), &timeline.section())
1332 .await
1333 .unwrap();
1334 let waiter = kio::Waiter::noop();
1335
1336 video.record(0, ms(0), true);
1337 video.record(1, ms(2_000), true);
1338 let record = segmenter.next().expect("the complete segment is ready for storage");
1339 assert!(matches!(consumer.poll_next(&waiter), Poll::Pending));
1340
1341 timeline.push(record).unwrap();
1342 assert!(matches!(
1343 consumer.poll_next(&waiter),
1344 Poll::Ready(Ok(Some(Event::Push { index: 0, .. })))
1345 ));
1346 }
1347
1348 #[test]
1349 fn dropping_a_pending_record_requeues_it() {
1350 let (_broadcast, timeline) = setup();
1351 let segmenter = timeline.deferred().unwrap();
1352 let mut video = segmenter.pacing_track("video0");
1353 video.record(0, ms(0), true);
1354 video.record(1, ms(2_000), true);
1355
1356 let pending = segmenter.next().expect("the complete segment is ready for storage");
1357 assert_eq!(pending.segment, 0);
1358 drop(pending);
1359
1360 let retry = segmenter.next().expect("the abandoned segment is ready to retry");
1361 assert_eq!(retry.segment, 0);
1362 timeline.push(retry).unwrap();
1363 }
1364
1365 #[test]
1366 fn ordinary_producer_rejects_a_deferred_record() {
1367 let (_ordinary_broadcast, ordinary) = setup();
1368 let (_deferred_broadcast, deferred) = setup();
1369 let segmenter = deferred.deferred().unwrap();
1370 let mut video = segmenter.pacing_track("video0");
1371 video.record(0, ms(0), true);
1372 video.record(1, ms(2_000), true);
1373
1374 let pending = segmenter.next().expect("the complete segment is ready for storage");
1375 assert!(matches!(
1376 ordinary.push(pending),
1377 Err(crate::Error::TimelineDeferredRecord(0))
1378 ));
1379 assert_eq!(
1380 segmenter
1381 .next()
1382 .expect("the rejected segment returns to its producer")
1383 .segment,
1384 0
1385 );
1386 }
1387
1388 #[test]
1389 fn deferred_mode_is_rejected_after_finishing_starts() {
1390 let (_broadcast, mut timeline) = setup();
1391 timeline.finish().unwrap();
1392 assert!(matches!(
1393 timeline.deferred(),
1394 Err(crate::Error::Moq(moq_net::Error::Closed))
1395 ));
1396 }
1397
1398 #[tokio::test]
1399 async fn deferred_records_publish_in_segment_order_before_finish() {
1400 let (broadcast, mut timeline) = setup();
1401 let segmenter = timeline.deferred().unwrap();
1402 let mut video = segmenter.pacing_track("video0");
1403 video.record(0, ms(0), true);
1404 video.record(1, ms(2_000), true);
1405 video.end(ms(4_000));
1406 drop(video);
1407 let mut consumer = Consumer::<()>::subscribe(&broadcast.consume(), &timeline.section())
1408 .await
1409 .unwrap();
1410 let waiter = kio::Waiter::noop();
1411
1412 assert!(matches!(timeline.finish(), Err(crate::Error::TimelineDeferredPending)));
1413 let drain = segmenter.finish();
1414 drain.result().unwrap();
1415 drop(drain);
1416 let drain = timeline.drain().expect("a cancelled terminal drain is recoverable");
1417 assert!(matches!(timeline.finish(), Err(crate::Error::TimelineDeferredPending)));
1418 let first = drain.next().expect("the first record is ready for storage").gap();
1421 let second = drain.next().expect("the final record is ready for storage");
1422 assert!(matches!(timeline.finish(), Err(crate::Error::TimelineDeferredPending)));
1423
1424 timeline.push(second).unwrap();
1425 assert!(matches!(consumer.poll_next(&waiter), Poll::Pending));
1426 assert!(matches!(timeline.finish(), Err(crate::Error::TimelineDeferredPending)));
1427
1428 timeline.push(first).unwrap();
1429 timeline.finish().unwrap();
1430
1431 assert_eq!(
1432 drain_consumer(&mut consumer),
1433 vec![
1434 entry(0, 0, 2_000, &[]),
1435 entry(1, 2_000, 2_000, &[("video0", &[(1, 1)])]),
1436 ]
1437 );
1438 }
1439
1440 #[test]
1441 fn segmenter_finish_is_terminal() {
1442 let segmenter = Segmenter::new(Config::default());
1443 let late_segmenter = segmenter.clone();
1444 let mut video = segmenter.pacing_track("video0");
1445 video.record(0, ms(0), true);
1446 video.end(ms(2_000));
1447
1448 let drain = segmenter.finish();
1449 drain.result().unwrap();
1450 assert!(drain.next().is_some(), "the terminal flush keeps its final record");
1451
1452 video.record(1, ms(2_000), true);
1453 video.end(ms(4_000));
1454 let mut late = late_segmenter.pacing_track("late");
1455 late.record(0, ms(2_000), true);
1456 late.end(ms(4_000));
1457
1458 assert!(drain.next().is_none(), "surviving recorders cannot reopen the timeline");
1459 assert!(matches!(
1460 late_segmenter.cut(ms(4_000)),
1461 Err(crate::Error::Moq(moq_net::Error::Closed))
1462 ));
1463 }
1464
1465 #[tokio::test]
1467 async fn the_coarsest_track_paces_the_segments() {
1468 let (broadcast, mut timeline) = setup();
1469 let mut video = timeline.pacing_track("video0").unwrap();
1470 let mut audio = timeline.pacing_track("audio0").unwrap();
1471
1472 video.record(0, ms(0), true);
1474 for (seq, t) in [(0u64, 0u64), (1, 500), (2, 1_000), (3, 1_500)] {
1475 audio.record(seq, ms(t), true);
1476 }
1477 video.record(1, ms(2_000), true);
1478 for (seq, t) in [(4u64, 2_000u64), (5, 2_500), (6, 3_000), (7, 3_500)] {
1479 audio.record(seq, ms(t), true);
1480 }
1481 video.record(2, ms(4_000), true);
1482 audio.record(8, ms(4_000), true);
1483 drop(video);
1484 drop(audio);
1485 timeline.finish().unwrap();
1486
1487 assert_eq!(
1490 drain(&broadcast, &timeline).await,
1491 vec![
1492 entry(0, 0, 2_000, &[("video0", &[(0, 0)]), ("audio0", &[(0, 3)])]),
1493 entry(1, 2_000, 2_000, &[("video0", &[(1, 1)]), ("audio0", &[(4, 7)])]),
1494 entry(2, 4_000, 0, &[("video0", &[(2, 2)]), ("audio0", &[(8, 8)])]),
1495 ]
1496 );
1497 }
1498
1499 #[tokio::test]
1502 async fn a_gop_longer_than_the_minimum_is_one_segment() {
1503 let (broadcast, mut timeline) = setup();
1504 let mut video = timeline.pacing_track("video0").unwrap();
1505
1506 video.record(0, ms(0), true);
1507 video.record(1, ms(30_000), true);
1508 video.end(ms(60_000));
1509 drop(video);
1510 timeline.finish().unwrap();
1511
1512 assert_eq!(
1513 drain(&broadcast, &timeline).await,
1514 vec![
1515 entry(0, 0, 30_000, &[("video0", &[(0, 0)])]),
1516 entry(1, 30_000, 30_000, &[("video0", &[(1, 1)])]),
1517 ]
1518 );
1519 }
1520
1521 #[tokio::test]
1523 async fn short_groups_pack_up_to_the_minimum() {
1524 let (broadcast, mut timeline) = setup_with(Config {
1525 duration_min: Duration::from_millis(1_500),
1526 ..Default::default()
1527 });
1528 let mut audio = timeline.pacing_track("audio0").unwrap();
1529
1530 for seq in 0..8u64 {
1531 audio.record(seq, ms(seq * 500), true);
1532 }
1533 drop(audio);
1534 timeline.finish().unwrap();
1535
1536 let entries = drain(&broadcast, &timeline).await;
1537 assert_eq!(entries[0], entry(0, 0, 1_500, &[("audio0", &[(0, 2)])]));
1539 assert_eq!(entries[1], entry(1, 1_500, 1_500, &[("audio0", &[(3, 5)])]));
1540 }
1541
1542 #[tokio::test]
1545 async fn a_segment_waits_for_every_track() {
1546 let (broadcast, mut timeline) = setup();
1547 let mut video = timeline.pacing_track("video0").unwrap();
1548 let mut audio = timeline.pacing_track("audio0").unwrap();
1549
1550 video.record(0, ms(0), true);
1551 audio.record(0, ms(0), true);
1552 video.record(1, ms(2_000), true);
1553 assert!(drain(&broadcast, &timeline).await.is_empty());
1555
1556 audio.record(1, ms(2_000), true);
1557 let entries = drain(&broadcast, &timeline).await;
1558 assert_eq!(
1559 entries,
1560 vec![entry(0, 0, 2_000, &[("video0", &[(0, 0)]), ("audio0", &[(0, 0)])])]
1561 );
1562 drop(video);
1563 drop(audio);
1564 timeline.finish().unwrap();
1565 }
1566
1567 #[tokio::test]
1569 async fn explicit_cuts_override_the_pacing() {
1570 let (broadcast, mut timeline) = setup();
1571 let mut video = timeline.pacing_track("video0").unwrap();
1572
1573 timeline.cut(ms(3_000)).unwrap();
1575 timeline.cut(ms(6_000)).unwrap();
1576 for seq in 0..7u64 {
1577 video.record(seq, ms(seq * 1_000), true);
1578 }
1579 drop(video);
1580 timeline.finish().unwrap();
1581
1582 let entries = drain(&broadcast, &timeline).await;
1583 assert_eq!(entries[0], entry(0, 0, 3_000, &[("video0", &[(0, 2)])]));
1584 assert_eq!(entries[1], entry(1, 3_000, 3_000, &[("video0", &[(3, 5)])]));
1585 }
1586
1587 #[tokio::test]
1590 async fn a_cut_below_the_minimum_is_ignored() {
1591 let (broadcast, mut timeline) = setup();
1592 let mut video = timeline.pacing_track("video0").unwrap();
1593
1594 video.record(0, ms(0), true);
1595 timeline.cut(ms(2_000)).unwrap();
1596 timeline.cut(ms(2_000)).unwrap();
1598 timeline.cut(ms(2_500)).unwrap();
1599 timeline.cut(ms(4_000)).unwrap();
1600 video.record(1, ms(2_000), true);
1601 video.record(2, ms(4_000), true);
1602 drop(video);
1603 timeline.finish().unwrap();
1604
1605 let entries = drain(&broadcast, &timeline).await;
1606 assert_eq!(entries.len(), 3, "the duplicate and the 500ms cut are both dropped");
1607 assert_eq!(entries[0], entry(0, 0, 2_000, &[("video0", &[(0, 0)])]));
1608 assert_eq!(entries[1], entry(1, 2_000, 2_000, &[("video0", &[(1, 1)])]));
1609 }
1610
1611 #[tokio::test]
1615 async fn exceeding_the_declared_maximum_fails_the_timeline() {
1616 let (broadcast, mut timeline) = setup_with(Config {
1617 duration_min: Duration::from_secs(1),
1618 duration_max: Some(Duration::from_secs(3)),
1619 ..Default::default()
1620 });
1621 let mut video = timeline.pacing_track("video0").unwrap();
1622
1623 let mut consumer = {
1624 video.record(0, ms(0), true);
1625 video.record(1, ms(2_000), true);
1626 Consumer::<()>::subscribe(&broadcast.consume(), &timeline.section())
1627 .await
1628 .unwrap()
1629 };
1630
1631 video.record(2, ms(6_000), true);
1633 drop(video);
1634
1635 let err = timeline.finish().unwrap_err();
1636 assert!(
1637 matches!(err, crate::Error::TimelineOverrun { segment: 1, .. }),
1638 "unexpected error: {err}"
1639 );
1640
1641 let waiter = kio::Waiter::noop();
1644 let mut entries = Vec::new();
1645 while let Poll::Ready(Ok(Some(event))) = consumer.poll_next(&waiter) {
1646 if let Event::Push { entry, .. } = event {
1647 entries.push(entry);
1648 }
1649 }
1650 assert_eq!(entries, vec![entry(0, 0, 2_000, &[("video0", &[(0, 0)])])]);
1651 }
1652
1653 #[test]
1654 fn a_deferred_overrun_is_reported_when_the_output_finishes() {
1655 let (_broadcast, mut timeline) = setup_with(Config {
1656 duration_min: Duration::from_secs(1),
1657 duration_max: Some(Duration::from_secs(3)),
1658 ..Default::default()
1659 });
1660 let deferred = timeline.deferred().unwrap();
1661 let mut video = deferred.pacing_track("video0");
1662
1663 video.record(0, ms(0), true);
1664 video.record(1, ms(2_000), true);
1665
1666 video.record(2, ms(6_000), true);
1667 drop(video);
1668 let drain = deferred.finish();
1669 assert!(matches!(
1670 drain.result(),
1671 Err(crate::Error::TimelineOverrun { segment: 1, .. })
1672 ));
1673 timeline.push(drain.next().unwrap()).unwrap();
1675 assert!(matches!(
1676 timeline.finish(),
1677 Err(crate::Error::TimelineOverrun { segment: 1, .. })
1678 ));
1679 }
1680
1681 #[tokio::test]
1682 async fn an_undeclared_maximum_is_omitted_from_the_catalog() {
1683 let (_broadcast, timeline) = setup();
1684 assert_eq!(timeline.section().duration_max, None);
1685
1686 let (_broadcast, timeline) = setup_with(Config {
1687 duration_max: Some(Duration::from_millis(2_500)),
1688 ..Default::default()
1689 });
1690 assert_eq!(timeline.section().duration_max, Some(2_500));
1691 }
1692
1693 #[tokio::test]
1696 async fn the_final_segment_runs_to_the_reported_end() {
1697 let (broadcast, mut timeline) = setup();
1698 let mut video = timeline.pacing_track("video0").unwrap();
1699
1700 video.record(0, ms(0), true);
1701 video.record(1, ms(2_000), true);
1702 video.end(ms(4_000));
1704 drop(video);
1705 timeline.finish().unwrap();
1706
1707 assert_eq!(
1708 drain(&broadcast, &timeline).await,
1709 vec![
1710 entry(0, 0, 2_000, &[("video0", &[(0, 0)])]),
1711 entry(1, 2_000, 2_000, &[("video0", &[(1, 1)])]),
1712 ]
1713 );
1714 }
1715
1716 #[tokio::test]
1720 async fn a_reservation_defers_flushing_until_every_track_enrolls() {
1721 let (broadcast, mut timeline) = setup();
1722 let reserved = timeline.reserve();
1723
1724 let mut first = timeline.pacing_track("video0").unwrap();
1727 for (seq, t) in [(0u64, 0u64), (1, 2_000), (2, 4_000)] {
1728 first.record(seq, ms(t), true);
1729 }
1730
1731 let mut second = timeline.pacing_track("video1").unwrap();
1732 for (seq, t) in [(0u64, 0u64), (1, 2_000), (2, 4_000)] {
1733 second.record(seq, ms(t), true);
1734 }
1735
1736 drop(reserved);
1737 drop(first);
1738 drop(second);
1739 timeline.finish().unwrap();
1740
1741 let entries = drain(&broadcast, &timeline).await;
1745 assert_eq!(
1746 entries[0],
1747 entry(0, 0, 2_000, &[("video0", &[(0, 0)]), ("video1", &[(0, 0)])])
1748 );
1749 assert_eq!(
1750 entries[1],
1751 entry(1, 2_000, 2_000, &[("video0", &[(1, 1)]), ("video1", &[(1, 1)])])
1752 );
1753 }
1754
1755 #[tokio::test]
1757 async fn groups_before_the_first_boundary_join_the_first_segment() {
1758 let (broadcast, mut timeline) = setup();
1759 let mut video = timeline.pacing_track("video0").unwrap();
1760 let mut audio = timeline.pacing_track("audio0").unwrap();
1761
1762 audio.record(0, ms(0), true);
1765 video.record(0, ms(30), true);
1766 for (seq, t) in [(1u64, 500u64), (2, 1_000), (3, 1_500), (4, 2_000)] {
1767 audio.record(seq, ms(t), true);
1768 }
1769 video.record(1, ms(2_030), true);
1770 audio.record(5, ms(2_500), true);
1771 drop(video);
1772 drop(audio);
1773 timeline.finish().unwrap();
1774
1775 let entries = drain(&broadcast, &timeline).await;
1776 assert_eq!(
1777 entries[0],
1778 entry(0, 0, 2_030, &[("video0", &[(0, 0)]), ("audio0", &[(0, 4)])]),
1779 );
1780 }
1781
1782 #[tokio::test]
1783 async fn sequence_gaps_split_ranges() {
1784 let (broadcast, mut timeline) = setup();
1785 let mut video = timeline.pacing_track("video0").unwrap();
1786 let mut audio = timeline.pacing_track("audio0").unwrap();
1787
1788 video.record(0, ms(0), true);
1790 audio.record(0, ms(0), true);
1791 audio.record(1, ms(300), true);
1792 audio.record(5, ms(1_500), true);
1793 video.record(1, ms(2_000), true);
1794 audio.record(6, ms(2_100), true);
1795 drop(video);
1796 drop(audio);
1797 timeline.finish().unwrap();
1798
1799 let entries = drain(&broadcast, &timeline).await;
1800 assert_eq!(
1801 entries[0],
1802 entry(0, 0, 2_000, &[("video0", &[(0, 0)]), ("audio0", &[(0, 1), (5, 5)])]),
1803 );
1804 }
1805
1806 #[tokio::test]
1810 async fn a_closed_track_leaves_a_whole_segment_gap() {
1811 let (broadcast, mut timeline) = setup();
1812 let mut video = timeline.pacing_track("video0").unwrap();
1813 let mut audio = timeline.pacing_track("audio0").unwrap();
1814
1815 video.record(0, ms(0), true);
1816 audio.record(0, ms(0), true);
1817 drop(audio);
1818 video.record(1, ms(2_000), true);
1819 video.record(2, ms(4_000), true);
1820 drop(video);
1821 timeline.finish().unwrap();
1822
1823 let entries = drain(&broadcast, &timeline).await;
1824 assert_eq!(entries[1], entry(1, 2_000, 2_000, &[("video0", &[(1, 1)])]));
1825 }
1826
1827 #[tokio::test]
1828 async fn a_non_keyframe_range_start_is_flagged() {
1829 let (broadcast, mut timeline) = setup();
1830 let mut video = timeline.pacing_track("video0").unwrap();
1831
1832 video.record(0, ms(0), true);
1833 video.record(1, ms(2_000), false);
1835 video.record(2, ms(4_000), true);
1836 drop(video);
1837 timeline.finish().unwrap();
1838
1839 let entries = drain(&broadcast, &timeline).await;
1840 let range = entries[1].tracks["video0"][0];
1841 assert!(!range.keyframe, "a range whose first group isn't an IDR says so");
1842 }
1843
1844 #[tokio::test]
1847 async fn a_closed_track_stops_gating() {
1848 let (broadcast, mut timeline) = setup();
1849 let mut video = timeline.pacing_track("video0").unwrap();
1850 let mut audio = timeline.pacing_track("audio0").unwrap();
1851
1852 video.record(0, ms(0), true);
1853 audio.record(0, ms(0), true);
1854 video.record(1, ms(2_000), true);
1855 assert!(drain(&broadcast, &timeline).await.is_empty(), "audio still gates");
1856
1857 drop(audio);
1858 let entries = drain(&broadcast, &timeline).await;
1859 assert_eq!(
1860 entries,
1861 vec![entry(0, 0, 2_000, &[("video0", &[(0, 0)]), ("audio0", &[(0, 0)])])]
1862 );
1863 drop(video);
1864 timeline.finish().unwrap();
1865 }
1866
1867 #[tokio::test]
1869 async fn the_track_is_created_on_first_enrollment() {
1870 let broadcast = moq_net::broadcast::Info::new().produce();
1871 let timeline = Producer::new(&broadcast, Config::default());
1872 assert!(
1873 broadcast.create_track(DEFAULT_NAME, None).is_ok(),
1874 "nothing took the name yet"
1875 );
1876
1877 let broadcast = moq_net::broadcast::Info::new().produce();
1878 let timeline2 = Producer::new(&broadcast, Config::default());
1879 let _recorder = timeline2.pacing_track("video0").unwrap();
1880 assert!(
1881 broadcast.create_track(DEFAULT_NAME, None).is_err(),
1882 "the timeline took the name once a track enrolled"
1883 );
1884 drop(timeline);
1885 }
1886
1887 #[tokio::test]
1888 async fn section_advertises_track_without_wall() {
1889 let (_broadcast, timeline) = setup();
1892 let section = timeline.section();
1893 assert_eq!(section.track, DEFAULT_NAME);
1894 assert_eq!(section.timescale, 1000);
1895 assert_eq!(section.duration_max, None);
1896 }
1897
1898 #[tokio::test]
1901 async fn reservations_nest() {
1902 let (broadcast, mut timeline) = setup();
1903 let mut video = timeline.pacing_track("video0").unwrap();
1904
1905 let outer = timeline.reserve();
1906 let inner = outer.clone();
1907
1908 for (seq, t) in [(0u64, 0u64), (1, 2_000), (2, 4_000)] {
1909 video.record(seq, ms(t), true);
1910 }
1911
1912 drop(inner);
1913 assert!(
1914 drain(&broadcast, &timeline).await.is_empty(),
1915 "the other clone still gates"
1916 );
1917
1918 drop(outer);
1919 assert_eq!(
1920 drain(&broadcast, &timeline).await.len(),
1921 2,
1922 "the last clone dropped flushes"
1923 );
1924
1925 drop(video);
1926 timeline.finish().unwrap();
1927 }
1928
1929 #[tokio::test]
1931 async fn finish_overrides_an_outstanding_reservation() {
1932 let (broadcast, mut timeline) = setup();
1933 let mut video = timeline.pacing_track("video0").unwrap();
1934 let reserved = timeline.reserve();
1935
1936 video.record(0, ms(0), true);
1937 video.record(1, ms(2_000), true);
1938 video.end(ms(4_000));
1939 drop(video);
1940
1941 timeline.finish().unwrap();
1942 assert_eq!(
1943 drain(&broadcast, &timeline).await,
1944 vec![
1945 entry(0, 0, 2_000, &[("video0", &[(0, 0)])]),
1946 entry(1, 2_000, 2_000, &[("video0", &[(1, 1)])]),
1947 ]
1948 );
1949
1950 drop(reserved);
1952 }
1953
1954 #[tokio::test]
1955 async fn rejects_an_invalid_timescale() {
1956 let (broadcast, timeline) = setup();
1957 let _recorder = timeline.pacing_track("video0").unwrap();
1958 let mut section = timeline.section();
1959 section.timescale = 0;
1960 let err = Consumer::<()>::subscribe(&broadcast.consume(), §ion).await;
1961 assert!(matches!(err, Err(crate::Error::InvalidTimescale(0))));
1962 }
1963
1964 #[tokio::test]
1967 async fn a_cut_on_the_first_group_does_not_poison_later_cuts() {
1968 let (broadcast, mut timeline) = setup();
1969 let mut video = timeline.pacing_track("video0").unwrap();
1970
1971 timeline.cut(ms(0)).unwrap();
1974 video.record(0, ms(0), true);
1975 for seq in 1..10u64 {
1976 if seq % 3 == 0 {
1977 timeline.cut(ms(seq * 1_000)).unwrap();
1978 }
1979 video.record(seq, ms(seq * 1_000), true);
1980 }
1981 drop(video);
1982 timeline.finish().unwrap();
1983
1984 let entries = drain(&broadcast, &timeline).await;
1985 assert_eq!(
1986 entries[0],
1987 entry(0, 0, 3_000, &[("video0", &[(0, 2)])]),
1988 "the source's 3s boundaries should be reproduced, not duration_min pacing"
1989 );
1990 }
1991
1992 #[tokio::test]
1996 async fn a_non_pacing_track_neither_paces_nor_gates() {
1997 let (broadcast, mut timeline) = setup();
1998 let mut video = timeline.pacing_track("video0").unwrap();
1999 let mut catalog = timeline.track("catalog.json");
2000
2001 catalog.record(0, ms(0), true);
2002 for (seq, t) in [(0u64, 0u64), (1, 2_000), (2, 4_000)] {
2003 video.record(seq, ms(t), true);
2004 }
2005 video.end(ms(6_000));
2006 timeline.finish().unwrap();
2007
2008 let entries = drain(&broadcast, &timeline).await;
2009 assert_eq!(
2010 entries,
2011 vec![
2012 entry(0, 0, 2_000, &[("catalog.json", &[(0, 0)]), ("video0", &[(0, 0)])]),
2013 entry(1, 2_000, 2_000, &[("video0", &[(1, 1)])]),
2014 entry(2, 4_000, 2_000, &[("video0", &[(2, 2)])]),
2015 ],
2016 "the silent catalog should neither hold records back nor move a boundary"
2017 );
2018 }
2019
2020 #[tokio::test]
2024 async fn a_late_non_pacing_group_lands_in_the_next_segment() {
2025 let (broadcast, mut timeline) = setup();
2026 let mut video = timeline.pacing_track("video0").unwrap();
2027 let mut catalog = timeline.track("catalog.json");
2028
2029 video.record(0, ms(0), true);
2030 video.record(1, ms(2_000), true);
2032
2033 catalog.record(0, ms(1_000), true);
2035
2036 video.record(2, ms(4_000), true);
2037 video.end(ms(6_000));
2038 timeline.finish().unwrap();
2039
2040 let entries = drain(&broadcast, &timeline).await;
2041 assert_eq!(entries[0], entry(0, 0, 2_000, &[("video0", &[(0, 0)])]));
2042 assert_eq!(
2043 entries[1],
2044 entry(1, 2_000, 2_000, &[("catalog.json", &[(0, 0)]), ("video0", &[(1, 1)])]),
2045 "the late group belongs to the segment that was open when it arrived"
2046 );
2047 }
2048
2049 #[tokio::test]
2053 async fn a_non_pacing_track_uses_arrival_and_does_not_extend_the_tail() {
2054 let (broadcast, mut timeline) = setup();
2055 let mut video = timeline.pacing_track("video0").unwrap();
2056 let mut catalog = timeline.track("catalog.json");
2057
2058 video.record(0, ms(0), true);
2059 catalog.record(0, ms(10_000), true);
2060 video.record(1, ms(2_000), true);
2061 video.end(ms(4_000));
2062 timeline.finish().unwrap();
2063
2064 assert_eq!(
2065 drain(&broadcast, &timeline).await,
2066 vec![
2067 entry(0, 0, 2_000, &[("catalog.json", &[(0, 0)]), ("video0", &[(0, 0)])]),
2068 entry(1, 2_000, 2_000, &[("video0", &[(1, 1)])]),
2069 ]
2070 );
2071 }
2072
2073 #[tokio::test]
2074 async fn the_built_in_catalog_is_recorded_without_pacing() {
2075 let mut broadcast = moq_net::broadcast::Info::new().produce();
2076 let mut catalog = crate::catalog::Producer::new(&mut broadcast, crate::catalog::Config::default()).unwrap();
2077 let timeline = catalog.timeline();
2078 let mut video = catalog.enroll("video0").unwrap();
2079
2080 video.record(0, ms(0), true);
2081 video.record(1, ms(2_000), true);
2082 video.end(ms(4_000));
2083 drop(video);
2084 catalog.finish().unwrap();
2085
2086 assert_eq!(
2087 drain(&broadcast, &timeline).await,
2088 vec![
2089 entry(0, 0, 2_000, &[("catalog.json", &[(0, 0)]), ("video0", &[(0, 0)])]),
2090 entry(1, 2_000, 2_000, &[("video0", &[(1, 1)])]),
2091 ]
2092 );
2093 }
2094
2095 #[tokio::test]
2098 async fn non_pacing_tracks_alone_publish_no_timeline() {
2099 let (broadcast, mut timeline) = setup();
2100 let mut catalog = timeline.track("catalog.json");
2101
2102 catalog.record(0, ms(0), true);
2103 timeline.finish().unwrap();
2104
2105 assert!(
2106 broadcast.create_track(DEFAULT_NAME, None).is_ok(),
2107 "the timeline track should never have been created"
2108 );
2109 }
2110}