Skip to content

Commit

Permalink
New test for handling of MPD.Location element
Browse files Browse the repository at this point in the history
  • Loading branch information
emarsden committed Sep 9, 2023
1 parent 6e92281 commit 149b3e5
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 7 deletions.
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ publish = true
edition = "2021"

[dependencies]
base64 = "0.21.2"
base64 = "0.21.3"
base64-serde = "0.7.0"
serde = { version = "1.0.188", features = ["derive"] }
serde_with = { version = "3.3.0", features = ["base64"] }
quick-xml = { version = "0.30.0", features = ["serialize", "overlapped-lists"] }
chrono = { version = "0.4.28", features = ["serde"] }
chrono = { version = "0.4.30", features = ["serde"] }
num-traits = "0.2.16"
iso8601 = "0.6.1"
regex = "1.9.4"
regex = "1.9.5"
thiserror = "1.0.48"
fs-err = "2.9.0"
log = "0.4.20"
url = { version = "2.4.1", optional = true }
data-url = { version = "0.3.0", optional = true }
reqwest = { version = "0.11.20", default-features = false, optional = true }
tokio = { version = "1.32.0", features = ["rt-multi-thread", "time"] }
tokio = { version = "1.32.0", features = ["rt-multi-thread", "time", "macros"] }
backoff = { version = "0.4.0", features = ["tokio"], optional = true }
governor = { version = "0.6.0", optional = true }
xmltree = { version = "0.10.3", optional = true }
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1935,6 +1935,7 @@ mod tests {
assert!(parse_xs_duration("foobles").is_err());
assert!(parse_xs_duration("P").is_err());
assert!(parse_xs_duration("PW").is_err());
assert!(parse_xs_duration("PT-4.5S").is_err());
assert!(parse_xs_duration("1Y2M3DT4H5M6S").is_err()); // missing initial P
assert_eq!(parse_xs_duration("PT3H11M53S").ok(), Some(Duration::new(11513, 0)));
assert_eq!(parse_xs_duration("PT42M30S").ok(), Some(Duration::new(2550, 0)));
Expand Down
7 changes: 4 additions & 3 deletions tests/fetching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ fn check_file_size_approx(p: &PathBuf, expected: u64) {
#[cfg(not(feature = "libav"))]
async fn test_dl_mp4() {
let mpd_url = "https://cloudflarestream.com/31c9291ab41fac05471db4e73aa11717/manifest/video.mpd";
let out = env::temp_dir().join("itec-elephants-dream.mp4");
let out = env::temp_dir().join("cf.mp4");
DashDownloader::new(mpd_url)
.worst_quality()
.download_to(out.clone()).await
Expand All @@ -52,9 +52,10 @@ async fn test_dl_mp4() {
#[cfg(not(feature = "libav"))]
async fn test_dl_mkv() {
let mpd_url = "https://cloudflarestream.com/31c9291ab41fac05471db4e73aa11717/manifest/video.mpd";
let out = env::temp_dir().join("itec-elephants-dream.mkv");
let out = env::temp_dir().join("cf.mkv");
DashDownloader::new(mpd_url)
.worst_quality()
.verbose(3)
.download_to(out.clone()).await
.unwrap();
let format = FileFormat::from_file(out.clone()).unwrap();
Expand All @@ -66,7 +67,7 @@ async fn test_dl_mkv() {
#[cfg(not(feature = "libav"))]
async fn test_dl_webm() {
let mpd_url = "https://cloudflarestream.com/31c9291ab41fac05471db4e73aa11717/manifest/video.mpd";
let out = env::temp_dir().join("itec-elephants-dream.webm");
let out = env::temp_dir().join("cf.webm");
DashDownloader::new(mpd_url)
.worst_quality()
.download_to(out.clone()).await
Expand Down
160 changes: 160 additions & 0 deletions tests/mpd_location.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Testing that the MPD.Location element is handled correctly.
//
//
// To run this test while enabling printing to stdout/stderr
//
// cargo test --test mpd_location -- --show-output


use fs_err as fs;
use std::env;
use std::process::Command;
use std::time::Duration;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use axum::{routing::get, Router};
use axum::extract::State;
use axum::response::{Response, IntoResponse};
use axum::http::{header, StatusCode};
use axum::body::{Full, Bytes};
use dash_mpd::{MPD, Period, AdaptationSet, Representation, SegmentTemplate, Location};
use dash_mpd::fetch::DashDownloader;
use anyhow::{Context, Result};


#[derive(Debug, Default)]
struct AppState {
counter: AtomicUsize,
}

impl AppState {
fn new() -> AppState {
AppState { counter: AtomicUsize::new(0) }
}
}


#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_mpd_location() -> Result<()> {
let segment_template1 = SegmentTemplate {
initialization: Some(format!("/media/init.mp4")),
..Default::default()
};
let rep1 = Representation {
id: Some("1".to_string()),
mimeType: Some("video/mp4".to_string()),
codecs: Some("avc1.640028".to_string()),
width: Some(1920),
height: Some(800),
bandwidth: Some(1980081),
SegmentTemplate: Some(segment_template1),
..Default::default()
};
let adap = AdaptationSet {
id: Some("1".to_string()),
contentType: Some("video".to_string()),
representations: vec!(rep1),
..Default::default()
};
let period = Period {
id: Some("p1".to_string()),
duration: Some(Duration::new(5, 0)),
adaptations: vec!(adap),
..Default::default()
};
let elsewhere = Location { url: "http://localhost:6666/relocated.mpd".to_string() };
let orig_mpd = MPD {
mpdtype: Some("static".to_string()),
locations: vec!(elsewhere),
periods: vec!(),
..Default::default()
};
let relocated_mpd = MPD {
mpdtype: Some("static".to_string()),
periods: vec!(period),
..Default::default()
};
let xml1 = quick_xml::se::to_string(&orig_mpd)?;
let xml2 = quick_xml::se::to_string(&relocated_mpd)?;

// State shared between the request handlers. We are simply maintaining a counter of the number
// of requests made, to check that each XLink reference has been resolved.
let shared_state = Arc::new(AppState::new());


// Useful ffmpeg recipes: https://github.com/videojs/http-streaming/blob/main/docs/creating-content.md
// ffmpeg -y -f lavfi -i testsrc=size=10x10:rate=1 -vf hue=s=0 -t 1 -metadata title=foobles1 tiny.mp4
async fn send_segment(State(state): State<Arc<AppState>>) -> Response<Full<Bytes>> {
state.counter.fetch_add(1, Ordering::SeqCst);
let tmp = env::temp_dir().join("segment.mp4");
let ffmpeg = Command::new("ffmpeg")
.args(["-f", "lavfi",
"-y", // overwrite output file if it exists
"-i", "testsrc=size=10x10:rate=1",
"-vf", "hue=s=0",
"-t", "1",
tmp.to_str().unwrap()])
.output()
.expect("spawning ffmpeg");
assert!(ffmpeg.status.success());
let bytes = fs::read(tmp).unwrap();
Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "video/mp4")
.body(Full::from(bytes))
.unwrap()
}

async fn send_status(State(state): State<Arc<AppState>>) -> impl IntoResponse {
([(header::CONTENT_TYPE, "text/plain")], format!("{}", state.counter.load(Ordering::Relaxed)))
}

let app = Router::new()
.route("/mpd", get(
|| async { ([(header::CONTENT_TYPE, "application/dash+xml")], xml1) }))
.route("/relocated.mpd", get(
|| async { ([(header::CONTENT_TYPE, "application/dash+xml")], xml2) }))
.route("/media/:id", get(send_segment))
.route("/status", get(send_status))
.with_state(shared_state);
let backend = async move {
axum::Server::bind(&"127.0.0.1:6666".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap()
};
tokio::spawn(backend);
tokio::time::sleep(Duration::from_millis(500)).await;
// Check that the initial value of our request counter is zero.
let client = reqwest::Client::builder()
.timeout(Duration::new(10, 0))
.build()
.context("creating HTTP client")?;
let txt = client.get("http://localhost:6666/status")
.send().await?
.error_for_status()?
.text().await
.context("fetching status")?;
assert!(txt.eq("0"));

let mpd_url = "http://localhost:6666/mpd";
let r = env::temp_dir().join("relocated.mp4");
DashDownloader::new(mpd_url)
.best_quality()
.verbosity(3)
.with_http_client(client.clone())
.download_to(r.clone()).await
.unwrap();

// Check the total number of requested media segments corresponds to what we expect. We expect
// two requests for the init.mp4 segment because we are running in verbose mode, and the init
// segment is fetched once just to extract and print the PSSH.
let txt = client.get("http://localhost:6666/status")
.send().await?
.error_for_status()?
.text().await
.context("fetching status")?;
assert!(txt.eq("2"), "Expecting status=2, got {}", txt);

Ok(())
}

0 comments on commit 149b3e5

Please sign in to comment.