use moq_mux::catalog;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Kind {
Video,
Audio,
}
#[derive(Default)]
struct Half {
playing: bool,
read: Option<u64>,
}
#[derive(Default)]
pub(super) struct Playback {
video: Half,
audio: Half,
latest: Option<catalog::hang::Catalog>,
snapshots: u64,
pub(super) played: bool,
pub(super) catalog_ended: bool,
}
impl Playback {
fn half(&self, kind: Kind) -> &Half {
match kind {
Kind::Video => &self.video,
Kind::Audio => &self.audio,
}
}
fn half_mut(&mut self, kind: Kind) -> &mut Half {
match kind {
Kind::Video => &mut self.video,
Kind::Audio => &mut self.audio,
}
}
pub(super) fn received(&mut self, snapshot: catalog::hang::Catalog) {
self.snapshots += 1;
self.latest = Some(snapshot);
}
pub(super) fn started(&mut self, kind: Kind) {
self.played = true;
self.half_mut(kind).playing = true;
}
pub(super) fn playing(&self, kind: Kind) -> bool {
self.half(kind).playing
}
pub(super) fn ended(&mut self, kind: Option<Kind>) {
if let Some(kind) = kind {
self.half_mut(kind).playing = false;
}
}
pub(super) fn wants(&self, kind: Kind) -> bool {
let half = self.half(kind);
!half.playing && half.read != Some(self.snapshots)
}
pub(super) fn read(&mut self, kind: Kind) {
let snapshots = self.snapshots;
self.half_mut(kind).read = Some(snapshots);
}
pub(super) fn pending(&self) -> Option<&catalog::hang::Catalog> {
let snapshot = self.latest.as_ref()?;
(self.wants(Kind::Video) || self.wants(Kind::Audio)).then_some(snapshot)
}
pub(super) fn following(&self) -> bool {
!self.catalog_ended
}
pub(super) fn done(&self) -> bool {
self.catalog_ended && !self.video.playing && !self.audio.playing && self.pending().is_none()
}
}
pub(super) fn joined<T>(
result: Result<(Kind, anyhow::Result<T>), tokio::task::JoinError>,
) -> anyhow::Result<Option<(Kind, T)>> {
match result {
Ok((kind, result)) => result.map(|left| Some((kind, left))),
Err(err) if err.is_cancelled() => Ok(None),
Err(err) => Err(err.into()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_finished_track_re_arms_its_half() {
let mut playback = Playback::default();
playback.received(Default::default());
playback.started(Kind::Video);
playback.read(Kind::Video);
playback.started(Kind::Audio);
playback.read(Kind::Audio);
playback.received(Default::default());
assert!(playback.pending().is_none(), "nothing to start while both halves play");
playback.ended(Some(Kind::Video));
assert!(!playback.done(), "playback ended on a retired rendition");
assert!(playback.audio.playing, "audio ended with the video rendition");
assert!(playback.pending().is_some(), "the retirement snapshot was dropped");
assert!(playback.wants(Kind::Video), "the replacement was never looked for");
assert!(!playback.wants(Kind::Audio), "audio is still playing its own rendition");
playback.read(Kind::Video);
playback.started(Kind::Video);
assert!(playback.pending().is_none());
assert!(!playback.done());
}
#[test]
fn a_read_snapshot_is_not_read_twice() {
let mut playback = Playback::default();
playback.received(Default::default());
playback.read(Kind::Video);
playback.started(Kind::Video);
playback.read(Kind::Audio);
playback.ended(Some(Kind::Video));
assert!(playback.pending().is_none(), "the player would resubscribe in a loop");
playback.received(Default::default());
assert!(playback.pending().is_some(), "a fresh snapshot must be read");
}
#[test]
fn playback_ends_with_the_catalog() {
let mut playback = Playback::default();
playback.started(Kind::Video);
playback.catalog_ended = true;
assert!(!playback.done(), "playback ended while video was still playing");
playback.ended(Some(Kind::Video));
assert!(playback.done());
}
#[test]
fn a_final_snapshot_outlives_the_catalog() {
let mut playback = Playback::default();
playback.received(Default::default());
playback.started(Kind::Video);
playback.read(Kind::Video);
playback.received(Default::default());
playback.catalog_ended = true;
playback.ended(Some(Kind::Video));
assert!(playback.pending().is_some(), "the final snapshot was never offered");
assert!(!playback.done(), "playback ended with a replacement still unread");
playback.read(Kind::Video);
playback.read(Kind::Audio);
assert!(playback.done());
}
}