use std::time::{Duration, Instant};
use hang::catalog::stalled::{Detector, Sample};
use crate::catalog::VideoHint;
type Track = crate::container::Producer<crate::catalog::hang::Container, hang::catalog::VideoConfig>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Reorder {
pub depth: u32,
pub period: Option<(u64, u64)>,
}
pub(crate) struct Catalog {
hint: VideoHint,
last: Option<hang::catalog::VideoConfig>,
stalled: Detector,
last_source: Option<Instant>,
lag: Duration,
}
impl Catalog {
pub(crate) fn new(hint: VideoHint) -> Self {
Self {
hint,
last: None,
stalled: Detector::new(),
last_source: None,
lag: Duration::ZERO,
}
}
pub(crate) fn initial_config(&self) -> Option<hang::catalog::VideoConfig> {
self.hint.to_config()
}
pub(crate) fn configured(&self) -> bool {
self.last.is_some()
}
pub(crate) fn publish(&mut self, track: &mut Track, mut config: hang::catalog::VideoConfig) -> crate::Result<()> {
self.hint.apply(&mut config);
config.stalled = self.stalled.flag();
if self.last.as_ref() == Some(&config) {
return Ok(());
}
tracing::debug!(name = ?track.name(), ?config, "starting track");
track.set(config.clone())?;
self.last = Some(config);
Ok(())
}
pub(crate) fn on_frame(&mut self, track: &mut Track, demand: bool) -> crate::Result<()> {
self.last_source = Some(Instant::now());
self.publish_stalled(track, demand, true, self.lag)
}
pub(crate) fn tick(&mut self, track: &mut Track, demand: bool) -> crate::Result<()> {
self.publish_stalled(track, demand, false, Duration::ZERO)
}
pub(crate) fn observe_lag(&mut self, track: &mut Track, demand: bool, lag: Duration) -> crate::Result<()> {
self.lag = lag;
self.publish_stalled(track, demand, false, lag)
}
pub(crate) fn idle(&mut self, track: &mut Track) -> crate::Result<()> {
self.last_source = None;
self.lag = Duration::ZERO;
self.publish_stalled(track, false, false, Duration::ZERO)
}
fn publish_stalled(
&mut self,
track: &mut Track,
demand: bool,
frame: bool,
extra_lag: Duration,
) -> crate::Result<()> {
if demand {
self.last_source.get_or_insert_with(Instant::now);
} else {
self.last_source = None;
}
let media_lag = extra_lag;
let quiet = self
.last_source
.map(|at| Instant::now().saturating_duration_since(at))
.unwrap_or(Duration::ZERO);
let interval = hang::catalog::stalled::interval_from_fps(self.last.as_ref().and_then(|c| c.framerate));
if !self.stalled.observe(Sample {
frame,
media_lag,
quiet,
interval,
demand,
idle: !demand,
}) {
return Ok(());
}
let flag = self.stalled.flag();
if let Some(last) = self.last.as_mut() {
last.stalled = flag;
}
let mut config = track.modify()?;
config.stalled = flag;
config.commit()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn quiet_startup_tracks_demand() {
let mut broadcast = moq_net::broadcast::Info::new().produce();
let catalog = crate::catalog::Producer::new(&mut broadcast, crate::catalog::Config::default()).unwrap();
let reserved = catalog.reserve();
let net = broadcast
.create_track("video", hang::container::track_info(hang::catalog::PRIORITY.video))
.unwrap();
let mut rendition = reserved
.video(
net,
crate::catalog::hang::Container::Legacy(crate::container::Kind::Video),
None,
)
.unwrap();
let mut state = Catalog::new(VideoHint::default());
state
.publish(
&mut rendition,
hang::catalog::VideoConfig::new(hang::catalog::VideoCodec::VP8),
)
.unwrap();
state.tick(&mut rendition, true).unwrap();
let start = state
.last_source
.expect("demand starts the quiet clock before the first frame");
state.last_source = Some(start - Duration::from_millis(200));
state.tick(&mut rendition, true).unwrap();
assert_eq!(catalog.snapshot().video.renditions["video"].stalled, Some(true));
state.tick(&mut rendition, false).unwrap();
assert!(state.last_source.is_none());
assert_eq!(catalog.snapshot().video.renditions["video"].stalled, None);
state.tick(&mut rendition, true).unwrap();
assert_eq!(catalog.snapshot().video.renditions["video"].stalled, None);
state
.observe_lag(&mut rendition, true, Duration::from_millis(200))
.unwrap();
state.on_frame(&mut rendition, true).unwrap();
assert_eq!(catalog.snapshot().video.renditions["video"].stalled, Some(true));
state.observe_lag(&mut rendition, true, Duration::ZERO).unwrap();
for _ in 0..2 {
state.on_frame(&mut rendition, true).unwrap();
for _ in 0..10 {
state.tick(&mut rendition, true).unwrap();
}
assert_eq!(catalog.snapshot().video.renditions["video"].stalled, Some(true));
}
state.on_frame(&mut rendition, true).unwrap();
assert_eq!(catalog.snapshot().video.renditions["video"].stalled, None);
}
}