dash-runtime 0.4.0

Sans-IO MPEG-DASH (ISO/IEC 23009-1): MPD model and parser, segment addressing, UTC clock and live-edge arithmetic, and a client for static and dynamic presentations. No I/O, no clock reads.
Documentation
//! Every committed DASH MPD parses, and the parse is pinned against values read
//! out of the real files.

use std::path::{Path, PathBuf};

use dash_runtime::mpd::{Mpd, PresentationType};

fn collect(dir: &Path, out: &mut Vec<PathBuf>) {
    for entry in std::fs::read_dir(dir).expect("read fixtures dir") {
        let path = entry.expect("dir entry").path();
        if path.is_dir() {
            collect(&path, out);
        } else if path.extension().is_some_and(|e| e == "mpd") {
            out.push(path);
        }
    }
}

fn all_mpds() -> Vec<PathBuf> {
    let mut out = Vec::new();
    collect(&test_fixtures::path("dash"), &mut out);
    out.sort();
    out
}

#[test]
fn every_committed_mpd_parses() {
    let files = all_mpds();
    assert!(
        files.len() >= 24,
        "expected the full P0 set, found {}",
        files.len()
    );
    for f in files {
        let bytes = std::fs::read(&f).unwrap();
        Mpd::parse(&bytes).unwrap_or_else(|e| panic!("{}: {e}", f.display()));
    }
}

#[test]
fn livesim2_dynamic_mpd_values() {
    let bytes = test_fixtures::read("dash/livesim2/segtimeline_time_a.mpd");
    let mpd = Mpd::parse(&bytes).unwrap();
    assert_eq!(mpd.kind, PresentationType::Dynamic);
    assert_eq!(mpd.availability_start_time_us, Some(0));
    assert_eq!(mpd.publish_time_us, Some(1_791_934_510_000_000));
    assert_eq!(mpd.periods.len(), 1);
    assert_eq!(mpd.periods[0].adaptation_sets.len(), 2);
    assert_eq!(mpd.utc_timings.len(), 1);
}

const ISO_LIST_EXAMPLE: &str = "specs/iso_iec_23009-1_dash_2026_ed6_schema/example_G29-2.mpd";

#[test]
fn list_mpd_parses_its_imported_mpds() {
    let Some(root) = test_fixtures::private_root() else {
        eprintln!("SKIPPED list_mpd_parses_its_imported_mpds: private checkout absent");
        return;
    };
    let path = root.join(ISO_LIST_EXAMPLE);
    assert!(
        path.is_file(),
        "private checkout present but {} is missing",
        path.display()
    );
    let mpd = Mpd::parse(&std::fs::read(&path).expect("read")).expect("parse");
    assert_eq!(mpd.kind, PresentationType::List);
    assert_eq!(mpd.periods.len(), 3);
    let urls: Vec<&str> = mpd
        .periods
        .iter()
        .map(|p| p.imported_mpd.as_ref().expect("ImportedMPD").url.as_str())
        .collect();
    assert_eq!(urls, ["ad0.mpd", "ad1.mpd", "ad2.mpd"]);
    assert_eq!(
        mpd.periods[0]
            .imported_mpd
            .as_ref()
            .expect("ImportedMPD")
            .earliest_resolution_time_offset,
        Some(0.0)
    );
}

const INHERITING_TIMELINE: &str = "specs/iso_iec_23009-1_dash_2026_ed6_schema/example_G22.mpd";

#[test]
fn a_representation_inherits_the_adaptation_sets_timeline() {
    let Some(root) = test_fixtures::private_root() else {
        eprintln!(
            "SKIPPED a_representation_inherits_the_adaptation_sets_timeline: private checkout absent"
        );
        return;
    };
    let path = root.join(INHERITING_TIMELINE);
    assert!(
        path.is_file(),
        "private checkout present but {} is missing",
        path.display()
    );
    let mpd = Mpd::parse(&std::fs::read(&path).expect("read")).expect("parse");
    let set = &mpd.periods.first().expect("period").adaptation_sets[0];
    let declared = set
        .segment
        .template
        .as_ref()
        .expect("AdaptationSet SegmentTemplate");
    assert_eq!(
        declared
            .multi
            .timeline
            .as_ref()
            .expect("timeline")
            .entries
            .len(),
        3
    );
    let bare = set
        .representations
        .iter()
        .find(|r| {
            r.segment
                .template
                .as_ref()
                .is_some_and(|t| t.multi.timeline.is_none())
        })
        .expect("a Representation with a SegmentTemplate of its own");
    let info = dash_runtime::mpd::inherit::resolve_segment_info(&mpd.periods[0], set, bare)
        .expect("resolve");
    let dash_runtime::mpd::inherit::SegmentInfo::Template(info) = info else {
        panic!("expected a SegmentTemplate chain");
    };
    assert_eq!(
        info.multi
            .timeline
            .expect("inherited timeline")
            .entries
            .len(),
        3
    );
    assert_eq!(info.multi.timescale, 90_000);
}