1use std::collections::VecDeque;
21use std::sync::Arc;
22use std::task::Poll;
23use std::time::{Duration, SystemTime};
24
25use bytes::Bytes;
26use hang::timeline::Range;
27
28use super::Rendition;
29use crate::Result;
30
31pub(crate) struct Producer {
37 state: kio::Producer<State>,
38}
39
40struct State {
41 rows: VecDeque<Row>,
43 sequence: u64,
45 ended: bool,
47}
48
49#[derive(Default)]
55pub(crate) struct Discontinuities {
56 sequence: u64,
57 last_end: Option<Duration>,
59 broken: bool,
61}
62
63impl Discontinuities {
64 pub fn stamp(&mut self, pts: Duration, end: Duration) -> u64 {
66 let jumped = self
68 .last_end
69 .is_some_and(|last| pts.saturating_sub(last).max(last.saturating_sub(pts)) > Duration::from_millis(1));
70 if self.broken || jumped {
71 self.sequence += 1;
72 }
73 self.broken = false;
74 self.last_end = Some(end);
75 self.sequence
76 }
77
78 pub fn interrupt(&mut self) {
80 self.broken = true;
81 }
82}
83
84#[derive(Clone, Debug, PartialEq)]
86pub(crate) struct Row {
87 pub index: u64,
89 pub segment: u64,
91 pub ranges: Vec<Range>,
94 pub duration: Duration,
96 pub pts: moq_net::Timestamp,
98 pub end: Duration,
101 pub discontinuity: u64,
104}
105
106#[cfg_attr(not(feature = "server"), allow(dead_code))]
108pub(crate) struct Window {
109 pub sequence: u64,
112 pub segments: Vec<Row>,
114 pub ended: bool,
116}
117
118enum Next {
120 Ready(Row),
122 Ended,
124 Pending,
126}
127
128impl State {
129 #[cfg_attr(not(feature = "server"), allow(dead_code))]
131 fn window(&self) -> Window {
132 Window {
133 sequence: self.sequence,
134 segments: self.rows.iter().cloned().collect(),
135 ended: self.ended,
136 }
137 }
138
139 fn next_after(&self, after: Option<u64>) -> Next {
145 let next = self.rows.iter().find(|r| match after {
146 Some(after) => r.segment > after,
147 None => true,
148 });
149 match next {
150 Some(row) => Next::Ready(row.clone()),
151 None if self.ended => Next::Ended,
152 None => Next::Pending,
153 }
154 }
155}
156
157impl Producer {
158 pub fn new() -> Self {
160 Self {
161 state: kio::Producer::new(State {
162 rows: VecDeque::new(),
163 sequence: 0,
164 ended: false,
165 }),
166 }
167 }
168
169 pub fn push(&self, row: Row, window: Duration) {
171 let Ok(mut state) = self.state.write() else {
172 return;
173 };
174
175 if let Some(back) = state.rows.back() {
176 if Duration::from(row.pts) < Duration::from(back.pts) || row.segment <= back.segment {
179 tracing::warn!("timeline jumped backwards; resetting the playlist window");
180 state.rows.clear();
181 }
182 }
183 if state.rows.is_empty() {
184 state.sequence = row.segment;
185 }
186
187 state.rows.push_back(row);
188
189 while state.rows.len() >= 2 {
191 let span = state.rows.back().unwrap().end.saturating_sub(state.rows[1].pts.into());
192 if span < window {
193 break;
194 }
195 state.rows.pop_front();
196 }
197 state.sequence = state.rows.front().unwrap().segment;
198 }
199
200 pub fn pop(&self, range: std::ops::Range<u64>) {
202 if let Ok(mut state) = self.state.write() {
203 let after = state
204 .rows
205 .iter()
206 .filter(|row| range.contains(&row.index))
207 .map(|row| row.segment.saturating_add(1))
208 .max();
209 state.rows.retain(|row| !range.contains(&row.index));
210 state.sequence = state
211 .rows
212 .front()
213 .map(|row| row.segment)
214 .or(after)
215 .unwrap_or(state.sequence);
216 }
217 }
218
219 pub fn clear(&self) {
221 if let Ok(mut state) = self.state.write() {
222 state.rows.clear();
223 }
224 }
225
226 pub fn end(&self) {
229 if let Ok(mut state) = self.state.write() {
230 state.ended = true;
231 }
232 }
233
234 pub fn close(&self) {
238 let _ = self.state.close();
239 }
240
241 #[cfg_attr(not(feature = "server"), allow(dead_code))]
243 pub fn window(&self) -> Window {
244 self.state.read().window()
245 }
246
247 pub fn segment_ranges(&self, segment: u64) -> Option<Vec<Range>> {
250 let state = self.state.read();
251 let row = state.rows.iter().find(|r| r.segment == segment)?;
252 Some(row.ranges.clone())
253 }
254
255 #[cfg_attr(not(feature = "server"), allow(dead_code))]
260 pub fn segment_number_at(&self, time: u64, timescale: moq_net::Timescale) -> Option<u64> {
261 let state = self.state.read();
262 state
263 .rows
264 .iter()
265 .find(|row| row.pts.as_scale(timescale) == time as u128)
266 .map(|row| row.segment)
267 }
268
269 pub fn latest_keyframe_group(&self) -> Option<u64> {
272 let state = self.state.read();
273 state
274 .rows
275 .iter()
276 .rev()
277 .flat_map(|row| row.ranges.iter().rev())
278 .find(|range| range.keyframe)
279 .map(|range| range.start)
280 }
281
282 #[cfg_attr(not(feature = "server"), allow(dead_code))]
285 pub fn is_playable(&self) -> bool {
286 let state = self.state.read();
287 state.ended || !state.rows.is_empty()
288 }
289
290 #[cfg_attr(not(feature = "server"), allow(dead_code))]
292 pub fn poll_playable(&self, waiter: &kio::Waiter) -> Poll<()> {
293 let poll = self.state.poll_ref(waiter, |state| {
294 if state.ended || !state.rows.is_empty() {
295 Poll::Ready(())
296 } else {
297 Poll::Pending
298 }
299 });
300 match poll {
301 Poll::Ready(_) => Poll::Ready(()),
303 Poll::Pending => Poll::Pending,
304 }
305 }
306
307 pub fn subscribe(&self, rendition: Arc<Rendition>) -> Consumer {
309 Consumer {
310 state: self.state.consume(),
311 rendition,
312 position: Position::default(),
313 feed: None,
314 }
315 }
316}
317
318pub struct Segment {
320 pub segment: u64,
323 pub media: Bytes,
325 pub duration: Duration,
327 pub program_date_time: Option<SystemTime>,
329 pub discontinuity: bool,
334}
335
336pub struct Consumer {
345 state: kio::Consumer<State>,
346 rendition: Arc<Rendition>,
347 position: Position,
348 feed: Option<Feed>,
349}
350
351struct Feed {
353 binding: Arc<moq_mux::Binding>,
355 state: FeedState,
356}
357
358enum FeedState {
359 Binding,
361 Subscribing(moq_net::track::Subscribing),
362 Live(Box<moq_net::track::Subscriber>),
363 Failed,
365}
366
367#[derive(Default)]
370struct Position {
371 after: Option<u64>,
373 discontinuity: Option<u64>,
375}
376
377impl Position {
378 fn skip(&mut self, row: &Row) {
381 self.after = Some(row.segment);
382 self.discontinuity.get_or_insert(row.discontinuity);
383 }
384
385 fn emit(&mut self, row: &Row) -> bool {
387 self.after = Some(row.segment);
388 let previous = self.discontinuity.replace(row.discontinuity);
389 previous.is_some_and(|previous| previous != row.discontinuity)
390 }
391}
392
393impl Consumer {
394 pub async fn init(&self) -> Result<Option<Bytes>> {
397 self.rendition.init().await
398 }
399
400 pub async fn next(&mut self) -> Result<Option<Segment>> {
407 loop {
408 let Some(row) = kio::wait(|waiter| self.poll_next(waiter)).await else {
409 return Ok(None);
410 };
411 match self.rendition.segment(row.segment).await? {
412 Some(media) => {
413 return Ok(Some(Segment {
414 segment: row.segment,
415 media,
416 duration: row.duration,
417 program_date_time: self.rendition.wall_clock(row.pts),
418 discontinuity: self.position.emit(&row),
419 }));
420 }
421 None => self.position.skip(&row),
422 }
423 }
424 }
425
426 fn poll_next(&mut self, waiter: &kio::Waiter) -> Poll<Option<Row>> {
427 self.poll_feed(waiter);
428 let poll = self
429 .state
430 .poll(waiter, |state| match state.next_after(self.position.after) {
431 Next::Ready(row) => Poll::Ready(Some(row)),
432 Next::Ended => Poll::Ready(None),
433 Next::Pending => Poll::Pending,
434 });
435 match poll {
436 Poll::Ready(Ok(found)) => Poll::Ready(found),
437 Poll::Ready(Err(_)) => Poll::Ready(None),
439 Poll::Pending => Poll::Pending,
440 }
441 }
442
443 fn poll_feed(&mut self, waiter: &kio::Waiter) {
445 let binding = self.rendition.binding();
446 let feed = match &mut self.feed {
447 Some(feed) if Arc::ptr_eq(&feed.binding, &binding) => feed,
448 feed => feed.insert(Feed {
449 binding,
450 state: FeedState::Binding,
451 }),
452 };
453 loop {
454 feed.state = match &mut feed.state {
455 FeedState::Binding => match self.rendition.poll_subscribe(&feed.binding, waiter) {
456 Poll::Ready(Some(subscribing)) => FeedState::Subscribing(subscribing),
457 Poll::Ready(None) => FeedState::Failed,
458 Poll::Pending => return,
459 },
460 FeedState::Subscribing(subscribing) => match subscribing.poll_ok(waiter) {
461 Poll::Ready(Ok(subscriber)) => FeedState::Live(Box::new(subscriber)),
462 Poll::Ready(Err(_)) => FeedState::Failed,
463 Poll::Pending => return,
464 },
465 FeedState::Live(subscriber) => {
466 while let Poll::Ready(Ok(Some(_))) = subscriber.poll_recv_group(waiter) {}
469 return;
470 }
471 FeedState::Failed => return,
472 };
473 }
474 }
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480
481 fn row(segment: u64, group: u64, pts_ms: u64, duration_ms: u64) -> Row {
482 let pts = moq_net::Timestamp::from_millis(pts_ms).unwrap();
483 Row {
484 index: segment,
485 segment,
486 ranges: vec![Range::new(group, group)],
487 duration: Duration::from_millis(duration_ms),
488 pts,
489 end: Duration::from(pts) + Duration::from_millis(duration_ms),
490 discontinuity: 0,
491 }
492 }
493
494 #[test]
495 fn every_row_is_listed() {
496 let live = Producer::new();
497 live.push(row(0, 0, 0, 2_000), Duration::from_secs(30));
498 live.push(row(1, 1, 2_000, 2_000), Duration::from_secs(30));
499
500 let window = live.window();
501 assert_eq!(window.sequence, 0);
502 assert!(!window.ended);
503 assert_eq!(window.segments.len(), 2, "rows are complete segments; all are listed");
504 assert_eq!(window.segments[0].segment, 0);
505 assert_eq!(window.segments[0].duration, Duration::from_secs(2));
506
507 live.end();
508 assert!(live.window().ended);
509 }
510
511 #[test]
512 fn window_evicts_and_advances_sequence() {
513 let live = Producer::new();
514 let window = Duration::from_secs(4);
515 for i in 0..6u64 {
516 live.push(row(i, i, i * 2_000, 2_000), window);
517 }
518
519 let snapshot = live.window();
520 assert!(snapshot.sequence > 0);
523 let span: Duration = snapshot.segments.iter().map(|s| s.duration).sum();
524 assert!(span >= Duration::from_secs(4));
525 assert_eq!(snapshot.segments.first().unwrap().segment, snapshot.sequence);
526 }
527
528 #[test]
529 fn source_window_pop_removes_playlist_rows() {
530 let live = Producer::new();
531 let window = Duration::from_secs(30);
532 for i in 0..4u64 {
533 let mut row = row(i, i, i * 2_000, 2_000);
534 row.index = i + 10;
535 live.push(row, window);
536 }
537
538 live.pop(10..12);
539 let snapshot = live.window();
540 assert_eq!(snapshot.sequence, 2);
541 assert_eq!(
542 snapshot.segments.iter().map(|row| row.segment).collect::<Vec<_>>(),
543 vec![2, 3]
544 );
545
546 live.pop(12..14);
547 let snapshot = live.window();
548 assert_eq!(snapshot.sequence, 4);
549 assert!(snapshot.segments.is_empty());
550 }
551
552 #[test]
553 fn a_skipped_source_range_clears_rows_before_the_next_segment() {
554 let live = Producer::new();
555 live.push(row(4, 4, 8_000, 2_000), Duration::from_secs(10));
556 live.clear();
557 live.push(row(10, 10, 20_000, 2_000), Duration::from_secs(10));
558
559 let snapshot = live.window();
560 assert_eq!(snapshot.sequence, 10);
561 assert_eq!(
562 snapshot.segments.iter().map(|row| row.segment).collect::<Vec<_>>(),
563 vec![10]
564 );
565 }
566
567 fn secs(s: u64) -> Duration {
568 Duration::from_secs(s)
569 }
570
571 fn stamped(segment: u64, discontinuity: u64) -> Row {
572 Row {
573 discontinuity,
574 ..row(segment, segment, segment * 2_000, 2_000)
575 }
576 }
577
578 #[test]
579 fn skipped_rows_do_not_hide_or_invent_breaks() {
580 let mut position = Position::default();
582 assert!(!position.emit(&stamped(0, 0)), "a clean start");
583 position.skip(&stamped(1, 0));
584 assert!(!position.emit(&stamped(2, 0)));
585
586 let mut position = Position::default();
588 position.skip(&stamped(0, 0));
589 assert!(position.emit(&stamped(1, 1)));
590 }
591
592 #[test]
593 fn a_continuous_timeline_keeps_one_discontinuity() {
594 let mut timeline = Discontinuities::default();
597 assert_eq!(timeline.stamp(secs(0), secs(2)), 0);
598 assert_eq!(timeline.stamp(secs(2), secs(4)), 0);
599 assert_eq!(timeline.stamp(secs(4), secs(6)), 0);
600 }
601
602 #[test]
603 fn a_content_time_jump_starts_a_new_discontinuity() {
604 let mut timeline = Discontinuities::default();
605 assert_eq!(timeline.stamp(secs(0), secs(2)), 0);
606 assert_eq!(timeline.stamp(secs(10), secs(12)), 1);
607 assert_eq!(timeline.stamp(secs(12), secs(14)), 1);
608 }
609
610 #[test]
611 fn skipped_records_start_a_new_discontinuity() {
612 let mut timeline = Discontinuities::default();
613 assert_eq!(timeline.stamp(secs(0), secs(2)), 0);
614 timeline.interrupt();
615 assert_eq!(
616 timeline.stamp(secs(2), secs(4)),
617 1,
618 "skipped records break the timeline even when the next one lines up"
619 );
620 }
621
622 #[test]
623 fn segment_ranges_and_gaps() {
624 let live = Producer::new();
625 let window = Duration::from_secs(30);
626 live.push(row(0, 0, 0, 1_000), window);
627 live.push(
629 Row {
630 index: 1,
631 segment: 1,
632 ranges: Vec::new(),
633 duration: Duration::from_secs(1),
634 pts: moq_net::Timestamp::from_millis(1_000).unwrap(),
635 end: Duration::from_millis(2_000),
636 discontinuity: 0,
637 },
638 window,
639 );
640 live.push(row(2, 100, 2_000, 1_000), window);
641
642 assert_eq!(live.segment_ranges(0), Some(vec![Range::new(0, 0)]));
643 assert_eq!(live.segment_ranges(1), Some(vec![]), "a gap is present but empty");
644 assert_eq!(live.segment_ranges(7), None, "unknown segments miss");
645
646 assert_eq!(live.latest_keyframe_group(), Some(100));
648 }
649
650 #[test]
651 fn backwards_jump_resets_the_window() {
652 let live = Producer::new();
653 let window = Duration::from_secs(30);
654 live.push(row(0, 0, 10_000, 2_000), window);
655 live.push(row(1, 1, 12_000, 2_000), window);
656 live.push(row(2, 2, 1_000, 2_000), window); let snapshot = live.window();
659 assert_eq!(snapshot.segments.len(), 1, "the window restarted at the new row");
660 assert_eq!(snapshot.segments[0].segment, 2);
661
662 live.push(row(0, 0, 2_000, 2_000), window);
664 assert_eq!(live.window().segments.len(), 1);
665 }
666
667 #[test]
668 fn next_after_walks_segments() {
669 let live = Producer::new();
670 let window = Duration::from_secs(30);
671 live.push(row(0, 0, 0, 2_000), window);
672 live.push(row(1, 1, 2_000, 2_000), window);
673
674 let Next::Ready(first) = live.state.read().next_after(None) else {
675 panic!("expected a segment");
676 };
677 assert_eq!(first.segment, 0);
678 let Next::Ready(second) = live.state.read().next_after(Some(0)) else {
679 panic!("expected a segment");
680 };
681 assert_eq!(second.segment, 1);
682 assert!(
683 matches!(live.state.read().next_after(Some(1)), Next::Pending),
684 "nothing further while live"
685 );
686
687 live.end();
688 assert!(matches!(live.state.read().next_after(Some(1)), Next::Ended));
689 }
690}