smooth-runtime 0.1.0

Sans-IO Smooth Streaming (MS-SSTR) engine: a pull client (manifest fetch/refresh, stream/quality selection, chunk enumeration, fragment URLs, init/tfxd handling, retry/backoff, live join) and a live origin that renders the client Manifest and serves fragments from a media-plane Trunk. No I/O, no clock reads.
Documentation
use smooth_runtime::{Output, SmoothConfig, SmoothError};
use transmux::smooth_fragment::{FragmentState, fragment_state};

use crate::common::{Host, Reply, fixture, is_ended, is_fatal};

const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
const VIDEO_FRAGMENTS: usize = 3;

fn fnv1a(bytes: &[u8]) -> u64 {
    bytes.iter().fold(FNV_OFFSET_BASIS, |h, b| {
        (h ^ u64::from(*b)).wrapping_mul(FNV_PRIME)
    })
}

fn trace_without_track(host: &Host) -> Vec<String> {
    host.samples()
        .iter()
        .map(|(_, s)| {
            format!(
                "dts={:?} pts={:?} dur={:?} sync={} len={} fnv={:016x}",
                s.dts,
                s.pts,
                s.duration,
                s.flags.is_sync,
                s.data.len(),
                fnv1a(&s.data)
            )
        })
        .collect()
}

fn golden_without_track(name: &str) -> Vec<String> {
    let text = std::fs::read_to_string(test_fixtures::path(&format!("golden/smooth/{name}")))
        .expect("golden");
    text.lines()
        .filter_map(|l| l.strip_prefix("sample "))
        .map(|l| l.split_once(' ').expect("fields").1.to_string())
        .collect()
}

#[test]
fn av_plays_every_sample_of_both_tracks_in_the_recorded_order() {
    let mut host = Host::new(SmoothConfig::default());
    host.serve_dir("av");
    host.run_until(|o| o.iter().any(is_ended));
    assert_eq!(
        trace_without_track(&host),
        golden_without_track("pull-broadcast-client-av.txt")
    );
}

#[test]
fn av_announces_one_track_per_stream_numbered_from_one() {
    let mut host = Host::new(SmoothConfig::default());
    host.serve_dir("av");
    host.run_until(|o| o.iter().any(is_ended));
    let tracks = host
        .outputs
        .iter()
        .find_map(|o| match o {
            Output::Tracks(t) => Some(t.iter().map(|s| s.track_id.value()).collect::<Vec<_>>()),
            _ => None,
        })
        .expect("tracks");
    assert_eq!(tracks, [1, 2]);
    let per_track = |id: u32| {
        host.samples()
            .iter()
            .filter(|(t, _)| t.value() == id)
            .count()
    };
    assert!(per_track(1) > 0 && per_track(2) > 0);
    assert_eq!(per_track(1) + per_track(2), host.samples().len());
}

#[test]
fn timestamps_are_rebased_onto_the_fragment_start_not_restarted_per_fragment() {
    let mut host = Host::new(SmoothConfig::default());
    host.serve_dir("av-low");
    host.run_until(|o| o.iter().any(is_fatal));
    let video: Vec<i64> = host
        .samples()
        .iter()
        .map(|(_, s)| s.dts.expect("dts"))
        .collect();
    assert!(
        video.windows(2).all(|w| w[0] < w[1]),
        "dts strictly increasing across fragments"
    );
    assert!(
        video.iter().any(|&d| d >= 20_000_000),
        "third fragment starts at 2 s"
    );
}

#[test]
fn av_low_ends_fatally_when_the_fourth_chunk_has_no_fragment() {
    let mut host = Host::new(SmoothConfig::default());
    host.serve_dir("av-low");
    host.run_until(|o| o.iter().any(is_fatal));
    assert!(host.has(|o| matches!(
        o,
        Output::Fatal(SmoothError::Fragment { status: Some(404) })
    )));
    assert_eq!(
        host.samples().len(),
        golden_without_track("pull-broadcast-client-av-low.txt").len()
    );
    assert!(!host.has(is_ended));
}

#[test]
fn a_manifest_with_a_protection_element_is_fatal_and_fetches_no_fragment() {
    let text = String::from_utf8(fixture("av/Manifest")).expect("utf8");
    let text = text.replace("<StreamIndex", "<Protection/>\n<StreamIndex");
    let mut host = Host::new(SmoothConfig::default());
    host.route("Manifest", vec![Reply::Body(text.into_bytes())]);
    host.run();
    assert!(host.has(|o| matches!(o, Output::Fatal(SmoothError::ManifestProtected))));
    assert_eq!(host.requested.len(), 1);
}

#[test]
fn a_piff_encrypted_fragment_is_fatal() {
    let probe = fixture("av/QualityLevels(64528)/Fragments(video=0)");
    assert_eq!(fragment_state(&probe), FragmentState::Clear);
    let encrypted = test_fixtures::read("mp4/piff-sample-encrypted.mp4");
    assert_eq!(fragment_state(&encrypted), FragmentState::Encrypted);
    let mut host = Host::new(SmoothConfig::default());
    host.serve_dir("av");
    host.route(
        "QualityLevels(64528)/Fragments(video=0)",
        vec![Reply::Body(encrypted)],
    );
    host.run();
    assert!(host.has(|o| matches!(o, Output::Fatal(SmoothError::FragmentEncrypted))));
    assert_eq!(host.samples().len(), 0);
}

#[test]
fn only_the_first_video_quality_is_offered_and_chosen_by_default() {
    let mut host = Host::new(SmoothConfig::default());
    host.serve_dir("av");
    host.run_until(|o| o.iter().any(is_ended));
    let (qualities, chosen) = host
        .outputs
        .iter()
        .find_map(|o| match o {
            Output::Qualities { qualities, chosen } => Some((qualities.clone(), *chosen)),
            _ => None,
        })
        .expect("qualities");
    assert_eq!(chosen, 0);
    assert_eq!(qualities[0].bitrate, 64_528);
    assert_eq!(qualities[0].resolution, Some((320, 240)));
    assert!(
        qualities[0]
            .codecs
            .as_deref()
            .is_some_and(|c| c.starts_with("avc1"))
    );
    let _ = VIDEO_FRAGMENTS;
}