use crate::common;
use common::{load, oracle_epct, oracle_tfdt};
use dash_runtime::address::base_index;
use dash_runtime::address::{AddressError, PlanError, PlanOptions, plan_representation};
use dash_runtime::mpd::inherit::SegmentInfo;
use dash_runtime::mpd::inherit::resolve_segment_info;
use dash_runtime::selection::RepRef;
const SECOND_US: i128 = 1_000_000;
fn plans_of(
rel: &str,
) -> (
common::Loaded,
Vec<(RepRef, dash_runtime::address::PlannedRepresentation)>,
) {
let l = load(rel);
let plans = l.reps().into_iter().map(|r| (r, l.plan(r))).collect();
(l, plans)
}
fn exists(
l: &common::Loaded,
p: &dash_runtime::address::PlannedRepresentation,
reference: &str,
) -> bool {
l.file_of(p, reference).is_file()
}
#[test]
fn ffmpeg_timeline_manifest_matches_disk() {
let (l, plans) = plans_of("dash/manifest.mpd");
assert_eq!(plans.len(), 2);
let (_, video) = &plans[0];
let (_, audio) = &plans[1];
assert_eq!(video.plan.len(), Some(3));
assert_eq!(audio.plan.len(), Some(4));
for (p, prefix) in [(video, "chunk-stream0-"), (audio, "chunk-stream1-")] {
for (i, seg) in p.plan.iter().enumerate() {
assert_eq!(seg.number, i as u64 + 1);
let file = l.file_of(p, &seg.reference);
assert!(file.is_file(), "{}", file.display());
assert_eq!(
file.file_name().unwrap().to_str().unwrap(),
format!("{prefix}{:05}.m4s", seg.number)
);
}
let init = p.plan.init().expect("initialization");
assert!(exists(&l, p, &init.reference));
}
let first = video.plan.get(0).unwrap().unwrap();
assert_eq!(first.time_ticks, 2070);
assert_eq!(first.reference, "chunk-stream0-00001.m4s");
assert_eq!(
video.plan.get(1).unwrap().unwrap().time_ticks,
2070 + 90_000
);
assert_eq!(first.start_us, 2070 * SECOND_US / 90_000);
let durations: Vec<u64> = audio.plan.iter().map(|s| s.duration_ticks).collect();
assert_eq!(durations, vec![41_984, 44_032, 45_056, 3_072]);
}
#[test]
fn ffmpeg_quirk_time_is_the_sidx_value_not_tfdt() {
let (l, plans) = plans_of("dash/timeline-ffmpeg-quirk/manifest.mpd");
let tfdt = oracle_tfdt("dash/timeline-ffmpeg-quirk");
let epct = oracle_epct("dash/timeline-ffmpeg-quirk");
let (_, video) = &plans[0];
let (_, audio) = &plans[1];
assert_eq!((video.plan.len(), audio.plan.len()), (Some(3), Some(4)));
for (p, video_stream) in [(video, true), (audio, false)] {
for seg in p.plan.iter() {
let name = l
.file_of(p, &seg.reference)
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_owned();
assert_eq!(
epct[&name], seg.time_ticks,
"{name}: $Time$ is the sidx earliest_presentation_time"
);
let offset = seg.time_ticks - tfdt[&name][0];
assert_eq!(offset, if video_stream { 2090 } else { 0 }, "{name}");
}
}
}
#[test]
fn gpac_timeline_video_matches_tfdt_and_audio_quirk_is_kept() {
let (l, plans) = plans_of("dash/timeline-gpac/timeline.mpd");
let tfdt = oracle_tfdt("dash/timeline-gpac");
let (_, video) = &plans[0];
let (_, audio) = &plans[1];
assert_eq!(video.plan.len(), Some(6));
assert_eq!(audio.plan.len(), Some(6));
for seg in video.plan.iter() {
let file = l.file_of(video, &seg.reference);
assert!(file.is_file(), "{}", file.display());
let name = file.file_name().unwrap().to_str().unwrap().to_owned();
assert_eq!(
tfdt[&name][0], seg.time_ticks,
"{name}: video timeline equals tfdt"
);
}
let mut mismatches = 0;
for seg in audio.plan.iter() {
let file = l.file_of(audio, &seg.reference);
assert!(file.is_file(), "{}", file.display());
let name = file.file_name().unwrap().to_str().unwrap().to_owned();
if tfdt[&name][0] != seg.time_ticks {
mismatches += 1;
}
}
assert_eq!(
mismatches, 5,
"the real audio timeline disagrees with the media after segment 1"
);
let init = video.plan.init().unwrap();
assert_eq!(init.reference, "seg-1-.mp4");
assert!(exists(&l, video, &init.reference));
}
#[test]
fn segment_list_matches_disk() {
let (l, plans) = plans_of("dash/segmentlist/list.mpd");
assert_eq!(plans.len(), 2);
for (n, (_, p)) in plans.iter().enumerate() {
assert!(matches!(p.info, SegmentInfo::List(_)));
assert_eq!(p.plan.len(), Some(6));
for (i, seg) in p.plan.iter().enumerate() {
assert_eq!(seg.number, i as u64 + 1);
assert_eq!(seg.reference, format!("seg-{n}-{:03}.m4s", i + 1));
assert!(exists(&l, p, &seg.reference));
assert_eq!(seg.start_us, i as i128 * 2 * SECOND_US);
assert_eq!(seg.duration_us, 2 * SECOND_US);
}
let init = p.plan.init().unwrap();
assert_eq!(init.reference, format!("init-{n}.m4s"));
assert!(exists(&l, p, &init.reference));
}
}
#[test]
fn on_demand_sidx_ranges_tile_the_real_file() {
for dir in ["dash/ondemand-sidx", "dash/ondemand-gpac-pto-quirk"] {
let l = load(&format!("{dir}/ondemand.mpd"));
for at in l.reps() {
let first = plan_representation(&l.mpd, at, &PlanOptions::default());
assert!(matches!(
first,
Err(PlanError::Address(AddressError::IndexRequired))
));
let (period, set, rep) = at.resolve(&l.mpd).unwrap();
let info = resolve_segment_info(period, set, rep).unwrap();
let SegmentInfo::Base(base) = &info else {
panic!("expected SegmentBase")
};
let index_range = base.index_range.expect("indexRange");
let bytes = l.base_file_bytes(at);
let (a, b) = (
index_range.first as usize,
index_range.last.unwrap() as usize,
);
let sidx = base_index::parse_index(&bytes[a..=b]).unwrap();
let anchor = base_index::anchor_after(&index_range).unwrap();
let planned = plan_representation(
&l.mpd,
at,
&PlanOptions::default().with_index(&sidx, anchor),
)
.unwrap();
assert_eq!(planned.plan.len(), Some(6), "{dir}");
let mut expected_next = None;
for seg in planned.plan.iter() {
let r = seg.byte_range.unwrap();
let (first, last) = (r.first as usize, r.last.unwrap() as usize);
assert_eq!(
&bytes[first + 4..first + 8],
b"moof",
"{dir}: range starts at a moof"
);
if let Some(next) = expected_next {
assert_eq!(first, next, "{dir}: ranges are contiguous");
}
expected_next = Some(last + 1);
}
assert_eq!(
expected_next,
Some(bytes.len()),
"{dir}: ranges end at the end of the file"
);
let init = planned.plan.init().unwrap();
let range = init.byte_range.unwrap();
assert_eq!(&bytes[4..8], b"ftyp");
assert_eq!(range.first, 0);
assert_eq!(&bytes[a + 4..a + 8], b"sidx");
}
}
}
#[test]
fn ondemand_pto_quirk_does_not_overflow() {
let l = load("dash/ondemand-gpac-pto-quirk/ondemand.mpd");
let at = RepRef::at(0, 0, 0);
let (period, set, rep) = at.resolve(&l.mpd).unwrap();
let info = resolve_segment_info(period, set, rep).unwrap();
let SegmentInfo::Base(b) = &info else {
panic!()
};
assert_eq!(b.presentation_time_offset, Some(5_354_157_010_056_287_232));
let bytes = l.base_file_bytes(at);
let r = b.index_range.unwrap();
let sidx =
base_index::parse_index(&bytes[r.first as usize..=r.last.unwrap() as usize]).unwrap();
let planned = plan_representation(
&l.mpd,
at,
&PlanOptions::default().with_index(&sidx, base_index::anchor_after(&r).unwrap()),
)
.unwrap();
let first = planned.plan.get(0).unwrap().unwrap();
assert_eq!(first.time_ticks, 512);
assert!(
first.start_us < 0,
"a nonsense PTO yields a hugely negative start, never a wrap"
);
}
#[test]
fn multiperiod_restart_plans_each_period_and_keeps_the_tail_quirk() {
let (l, plans) = plans_of("dash/multiperiod-restart/multiperiod.mpd");
assert_eq!(plans.len(), 4);
let (_, p1_video) = &plans[0];
assert_eq!(p1_video.plan.len(), Some(4));
let tail = p1_video.plan.get(3).unwrap().unwrap();
assert!(tail.duration_us < 14_000, "tail is {} us", tail.duration_us);
assert!(exists(
&l,
p1_video,
&p1_video.plan.get(2).unwrap().unwrap().reference
));
assert!(
!exists(&l, p1_video, &tail.reference),
"the 4th segment is not on disk"
);
for (_, p) in &plans[2..] {
assert_eq!(p.period.start_us, Some(6 * SECOND_US));
assert_eq!(p.plan.len(), Some(3));
for seg in p.plan.iter() {
assert!(exists(&l, p, &seg.reference));
}
let first = p.plan.get(0).unwrap();
assert_eq!(first.unwrap().start_us, 0);
assert_eq!(p.plan.period_start_us(), 6 * SECOND_US);
}
}
#[test]
fn multiperiod_continuous_numbers_and_pto() {
let (l, plans) = plans_of("dash/multiperiod-continuous/multiperiod.mpd");
let tfdt = oracle_tfdt("dash/timeline-gpac");
assert_eq!(plans.len(), 2);
let (_, a) = &plans[0];
let (_, b) = &plans[1];
assert_eq!(a.plan.len(), Some(3));
assert_eq!(b.plan.len(), Some(3));
let numbers = |p: &dash_runtime::address::PlannedRepresentation| -> Vec<u64> {
p.plan.iter().map(|s| s.number).collect()
};
assert_eq!(numbers(a), vec![1, 2, 3]);
assert_eq!(numbers(b), vec![4, 5, 6]);
for (p, base_time) in [(a, 0u64), (b, 76_800)] {
for (i, seg) in p.plan.iter().enumerate() {
let file = l.file_of(p, &seg.reference);
assert!(file.is_file(), "{}", file.display());
let name = file.file_name().unwrap().to_str().unwrap().to_owned();
assert_eq!(tfdt[&name][0], seg.time_ticks, "{name}");
assert_eq!(seg.time_ticks, base_time + i as u64 * 25_600);
}
}
assert_eq!(b.plan.get(0).unwrap().unwrap().start_us, 0);
assert_eq!(b.plan.get(1).unwrap().unwrap().start_us, 2 * SECOND_US);
assert_eq!(b.plan.period_start_us(), 6 * SECOND_US);
}
#[test]
fn hierarchical_sidx_is_a_typed_unsupported_error() {
use dash_runtime::error::Unsupported;
let l = load("dash/ondemand-sidx/ondemand.mpd");
let at = RepRef::at(0, 0, 0);
let (p, s, r) = at.resolve(&l.mpd).unwrap();
let SegmentInfo::Base(base) = resolve_segment_info(p, s, r).unwrap() else {
panic!()
};
let range = base.index_range.unwrap();
let bytes = l.base_file_bytes(at);
let mut sidx =
base_index::parse_index(&bytes[range.first as usize..=range.last.unwrap() as usize])
.unwrap();
sidx.references[1].reference_type = 1;
let anchor = base_index::anchor_after(&range).unwrap();
assert!(matches!(
plan_representation(
&l.mpd,
at,
&PlanOptions::default().with_index(&sidx, anchor)
),
Err(PlanError::Address(AddressError::Unsupported(
Unsupported::HierarchicalSidx
)))
));
}