use std::collections::VecDeque;
use std::sync::Arc;
use std::task::Poll;
use std::time::{Duration, SystemTime};
use bytes::Bytes;
use hang::timeline::Range;
use super::Rendition;
use crate::Result;
pub(crate) struct Producer {
state: kio::Producer<State>,
}
struct State {
rows: VecDeque<Row>,
sequence: u64,
ended: bool,
}
#[derive(Default)]
pub(crate) struct Discontinuities {
sequence: u64,
last_end: Option<Duration>,
broken: bool,
}
impl Discontinuities {
pub fn stamp(&mut self, pts: Duration, end: Duration) -> u64 {
let jumped = self
.last_end
.is_some_and(|last| pts.saturating_sub(last).max(last.saturating_sub(pts)) > Duration::from_millis(1));
if self.broken || jumped {
self.sequence += 1;
}
self.broken = false;
self.last_end = Some(end);
self.sequence
}
pub fn interrupt(&mut self) {
self.broken = true;
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Row {
pub index: u64,
pub segment: u64,
pub ranges: Vec<Range>,
pub duration: Duration,
pub pts: moq_net::Timestamp,
pub end: Duration,
pub discontinuity: u64,
}
#[cfg_attr(not(feature = "server"), allow(dead_code))]
pub(crate) struct Window {
pub sequence: u64,
pub segments: Vec<Row>,
pub ended: bool,
}
enum Next {
Ready(Row),
Ended,
Pending,
}
impl State {
#[cfg_attr(not(feature = "server"), allow(dead_code))]
fn window(&self) -> Window {
Window {
sequence: self.sequence,
segments: self.rows.iter().cloned().collect(),
ended: self.ended,
}
}
fn next_after(&self, after: Option<u64>) -> Next {
let next = self.rows.iter().find(|r| match after {
Some(after) => r.segment > after,
None => true,
});
match next {
Some(row) => Next::Ready(row.clone()),
None if self.ended => Next::Ended,
None => Next::Pending,
}
}
}
impl Producer {
pub fn new() -> Self {
Self {
state: kio::Producer::new(State {
rows: VecDeque::new(),
sequence: 0,
ended: false,
}),
}
}
pub fn push(&self, row: Row, window: Duration) {
let Ok(mut state) = self.state.write() else {
return;
};
if let Some(back) = state.rows.back() {
if Duration::from(row.pts) < Duration::from(back.pts) || row.segment <= back.segment {
tracing::warn!("timeline jumped backwards; resetting the playlist window");
state.rows.clear();
}
}
if state.rows.is_empty() {
state.sequence = row.segment;
}
state.rows.push_back(row);
while state.rows.len() >= 2 {
let span = state.rows.back().unwrap().end.saturating_sub(state.rows[1].pts.into());
if span < window {
break;
}
state.rows.pop_front();
}
state.sequence = state.rows.front().unwrap().segment;
}
pub fn pop(&self, range: std::ops::Range<u64>) {
if let Ok(mut state) = self.state.write() {
let after = state
.rows
.iter()
.filter(|row| range.contains(&row.index))
.map(|row| row.segment.saturating_add(1))
.max();
state.rows.retain(|row| !range.contains(&row.index));
state.sequence = state
.rows
.front()
.map(|row| row.segment)
.or(after)
.unwrap_or(state.sequence);
}
}
pub fn clear(&self) {
if let Ok(mut state) = self.state.write() {
state.rows.clear();
}
}
pub fn end(&self) {
if let Ok(mut state) = self.state.write() {
state.ended = true;
}
}
pub fn close(&self) {
let _ = self.state.close();
}
#[cfg_attr(not(feature = "server"), allow(dead_code))]
pub fn window(&self) -> Window {
self.state.read().window()
}
pub fn segment_ranges(&self, segment: u64) -> Option<Vec<Range>> {
let state = self.state.read();
let row = state.rows.iter().find(|r| r.segment == segment)?;
Some(row.ranges.clone())
}
#[cfg_attr(not(feature = "server"), allow(dead_code))]
pub fn segment_number_at(&self, time: u64, timescale: moq_net::Timescale) -> Option<u64> {
let state = self.state.read();
state
.rows
.iter()
.find(|row| row.pts.as_scale(timescale) == time as u128)
.map(|row| row.segment)
}
pub fn latest_keyframe_group(&self) -> Option<u64> {
let state = self.state.read();
state
.rows
.iter()
.rev()
.flat_map(|row| row.ranges.iter().rev())
.find(|range| range.keyframe)
.map(|range| range.start)
}
#[cfg_attr(not(feature = "server"), allow(dead_code))]
pub fn is_playable(&self) -> bool {
let state = self.state.read();
state.ended || !state.rows.is_empty()
}
#[cfg_attr(not(feature = "server"), allow(dead_code))]
pub fn poll_playable(&self, waiter: &kio::Waiter) -> Poll<()> {
let poll = self.state.poll_ref(waiter, |state| {
if state.ended || !state.rows.is_empty() {
Poll::Ready(())
} else {
Poll::Pending
}
});
match poll {
Poll::Ready(_) => Poll::Ready(()),
Poll::Pending => Poll::Pending,
}
}
pub fn subscribe(&self, rendition: Arc<Rendition>) -> Consumer {
Consumer {
state: self.state.consume(),
rendition,
position: Position::default(),
feed: None,
}
}
}
pub struct Segment {
pub segment: u64,
pub media: Bytes,
pub duration: Duration,
pub program_date_time: Option<SystemTime>,
pub discontinuity: bool,
}
pub struct Consumer {
state: kio::Consumer<State>,
rendition: Arc<Rendition>,
position: Position,
feed: Option<Feed>,
}
struct Feed {
binding: Arc<moq_mux::Binding>,
state: FeedState,
}
enum FeedState {
Binding,
Subscribing(moq_net::track::Subscribing),
Live(Box<moq_net::track::Subscriber>),
Failed,
}
#[derive(Default)]
struct Position {
after: Option<u64>,
discontinuity: Option<u64>,
}
impl Position {
fn skip(&mut self, row: &Row) {
self.after = Some(row.segment);
self.discontinuity.get_or_insert(row.discontinuity);
}
fn emit(&mut self, row: &Row) -> bool {
self.after = Some(row.segment);
let previous = self.discontinuity.replace(row.discontinuity);
previous.is_some_and(|previous| previous != row.discontinuity)
}
}
impl Consumer {
pub async fn init(&self) -> Result<Option<Bytes>> {
self.rendition.init().await
}
pub async fn next(&mut self) -> Result<Option<Segment>> {
loop {
let Some(row) = kio::wait(|waiter| self.poll_next(waiter)).await else {
return Ok(None);
};
match self.rendition.segment(row.segment).await? {
Some(media) => {
return Ok(Some(Segment {
segment: row.segment,
media,
duration: row.duration,
program_date_time: self.rendition.wall_clock(row.pts),
discontinuity: self.position.emit(&row),
}));
}
None => self.position.skip(&row),
}
}
}
fn poll_next(&mut self, waiter: &kio::Waiter) -> Poll<Option<Row>> {
self.poll_feed(waiter);
let poll = self
.state
.poll(waiter, |state| match state.next_after(self.position.after) {
Next::Ready(row) => Poll::Ready(Some(row)),
Next::Ended => Poll::Ready(None),
Next::Pending => Poll::Pending,
});
match poll {
Poll::Ready(Ok(found)) => Poll::Ready(found),
Poll::Ready(Err(_)) => Poll::Ready(None),
Poll::Pending => Poll::Pending,
}
}
fn poll_feed(&mut self, waiter: &kio::Waiter) {
let binding = self.rendition.binding();
let feed = match &mut self.feed {
Some(feed) if Arc::ptr_eq(&feed.binding, &binding) => feed,
feed => feed.insert(Feed {
binding,
state: FeedState::Binding,
}),
};
loop {
feed.state = match &mut feed.state {
FeedState::Binding => match self.rendition.poll_subscribe(&feed.binding, waiter) {
Poll::Ready(Some(subscribing)) => FeedState::Subscribing(subscribing),
Poll::Ready(None) => FeedState::Failed,
Poll::Pending => return,
},
FeedState::Subscribing(subscribing) => match subscribing.poll_ok(waiter) {
Poll::Ready(Ok(subscriber)) => FeedState::Live(Box::new(subscriber)),
Poll::Ready(Err(_)) => FeedState::Failed,
Poll::Pending => return,
},
FeedState::Live(subscriber) => {
while let Poll::Ready(Ok(Some(_))) = subscriber.poll_recv_group(waiter) {}
return;
}
FeedState::Failed => return,
};
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn row(segment: u64, group: u64, pts_ms: u64, duration_ms: u64) -> Row {
let pts = moq_net::Timestamp::from_millis(pts_ms).unwrap();
Row {
index: segment,
segment,
ranges: vec![Range::new(group, group)],
duration: Duration::from_millis(duration_ms),
pts,
end: Duration::from(pts) + Duration::from_millis(duration_ms),
discontinuity: 0,
}
}
#[test]
fn every_row_is_listed() {
let live = Producer::new();
live.push(row(0, 0, 0, 2_000), Duration::from_secs(30));
live.push(row(1, 1, 2_000, 2_000), Duration::from_secs(30));
let window = live.window();
assert_eq!(window.sequence, 0);
assert!(!window.ended);
assert_eq!(window.segments.len(), 2, "rows are complete segments; all are listed");
assert_eq!(window.segments[0].segment, 0);
assert_eq!(window.segments[0].duration, Duration::from_secs(2));
live.end();
assert!(live.window().ended);
}
#[test]
fn window_evicts_and_advances_sequence() {
let live = Producer::new();
let window = Duration::from_secs(4);
for i in 0..6u64 {
live.push(row(i, i, i * 2_000, 2_000), window);
}
let snapshot = live.window();
assert!(snapshot.sequence > 0);
let span: Duration = snapshot.segments.iter().map(|s| s.duration).sum();
assert!(span >= Duration::from_secs(4));
assert_eq!(snapshot.segments.first().unwrap().segment, snapshot.sequence);
}
#[test]
fn source_window_pop_removes_playlist_rows() {
let live = Producer::new();
let window = Duration::from_secs(30);
for i in 0..4u64 {
let mut row = row(i, i, i * 2_000, 2_000);
row.index = i + 10;
live.push(row, window);
}
live.pop(10..12);
let snapshot = live.window();
assert_eq!(snapshot.sequence, 2);
assert_eq!(
snapshot.segments.iter().map(|row| row.segment).collect::<Vec<_>>(),
vec![2, 3]
);
live.pop(12..14);
let snapshot = live.window();
assert_eq!(snapshot.sequence, 4);
assert!(snapshot.segments.is_empty());
}
#[test]
fn a_skipped_source_range_clears_rows_before_the_next_segment() {
let live = Producer::new();
live.push(row(4, 4, 8_000, 2_000), Duration::from_secs(10));
live.clear();
live.push(row(10, 10, 20_000, 2_000), Duration::from_secs(10));
let snapshot = live.window();
assert_eq!(snapshot.sequence, 10);
assert_eq!(
snapshot.segments.iter().map(|row| row.segment).collect::<Vec<_>>(),
vec![10]
);
}
fn secs(s: u64) -> Duration {
Duration::from_secs(s)
}
fn stamped(segment: u64, discontinuity: u64) -> Row {
Row {
discontinuity,
..row(segment, segment, segment * 2_000, 2_000)
}
}
#[test]
fn skipped_rows_do_not_hide_or_invent_breaks() {
let mut position = Position::default();
assert!(!position.emit(&stamped(0, 0)), "a clean start");
position.skip(&stamped(1, 0));
assert!(!position.emit(&stamped(2, 0)));
let mut position = Position::default();
position.skip(&stamped(0, 0));
assert!(position.emit(&stamped(1, 1)));
}
#[test]
fn a_continuous_timeline_keeps_one_discontinuity() {
let mut timeline = Discontinuities::default();
assert_eq!(timeline.stamp(secs(0), secs(2)), 0);
assert_eq!(timeline.stamp(secs(2), secs(4)), 0);
assert_eq!(timeline.stamp(secs(4), secs(6)), 0);
}
#[test]
fn a_content_time_jump_starts_a_new_discontinuity() {
let mut timeline = Discontinuities::default();
assert_eq!(timeline.stamp(secs(0), secs(2)), 0);
assert_eq!(timeline.stamp(secs(10), secs(12)), 1);
assert_eq!(timeline.stamp(secs(12), secs(14)), 1);
}
#[test]
fn skipped_records_start_a_new_discontinuity() {
let mut timeline = Discontinuities::default();
assert_eq!(timeline.stamp(secs(0), secs(2)), 0);
timeline.interrupt();
assert_eq!(
timeline.stamp(secs(2), secs(4)),
1,
"skipped records break the timeline even when the next one lines up"
);
}
#[test]
fn segment_ranges_and_gaps() {
let live = Producer::new();
let window = Duration::from_secs(30);
live.push(row(0, 0, 0, 1_000), window);
live.push(
Row {
index: 1,
segment: 1,
ranges: Vec::new(),
duration: Duration::from_secs(1),
pts: moq_net::Timestamp::from_millis(1_000).unwrap(),
end: Duration::from_millis(2_000),
discontinuity: 0,
},
window,
);
live.push(row(2, 100, 2_000, 1_000), window);
assert_eq!(live.segment_ranges(0), Some(vec![Range::new(0, 0)]));
assert_eq!(live.segment_ranges(1), Some(vec![]), "a gap is present but empty");
assert_eq!(live.segment_ranges(7), None, "unknown segments miss");
assert_eq!(live.latest_keyframe_group(), Some(100));
}
#[test]
fn backwards_jump_resets_the_window() {
let live = Producer::new();
let window = Duration::from_secs(30);
live.push(row(0, 0, 10_000, 2_000), window);
live.push(row(1, 1, 12_000, 2_000), window);
live.push(row(2, 2, 1_000, 2_000), window);
let snapshot = live.window();
assert_eq!(snapshot.segments.len(), 1, "the window restarted at the new row");
assert_eq!(snapshot.segments[0].segment, 2);
live.push(row(0, 0, 2_000, 2_000), window);
assert_eq!(live.window().segments.len(), 1);
}
#[test]
fn next_after_walks_segments() {
let live = Producer::new();
let window = Duration::from_secs(30);
live.push(row(0, 0, 0, 2_000), window);
live.push(row(1, 1, 2_000, 2_000), window);
let Next::Ready(first) = live.state.read().next_after(None) else {
panic!("expected a segment");
};
assert_eq!(first.segment, 0);
let Next::Ready(second) = live.state.read().next_after(Some(0)) else {
panic!("expected a segment");
};
assert_eq!(second.segment, 1);
assert!(
matches!(live.state.read().next_after(Some(1)), Next::Pending),
"nothing further while live"
);
live.end();
assert!(matches!(live.state.read().next_after(Some(1)), Next::Ended));
}
}