use std::fmt::Write as _;
#[derive(Debug, Clone)]
pub(crate) struct MasterVariant {
pub(crate) bandwidth: u64,
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) uri: String,
pub(crate) codecs: Option<String>,
}
pub(crate) fn generate_master_playlist(variants: &[MasterVariant]) -> String {
let mut ordered: Vec<&MasterVariant> = variants.iter().collect();
ordered.sort_by_key(|v| v.bandwidth);
let mut out = String::from("#EXTM3U\n#EXT-X-VERSION:3\n");
for v in ordered {
let _ = write!(
out,
"#EXT-X-STREAM-INF:BANDWIDTH={},RESOLUTION={}x{}",
v.bandwidth, v.width, v.height
);
if let Some(codecs) = &v.codecs {
let _ = write!(out, ",CODECS=\"{codecs}\"");
}
out.push('\n');
out.push_str(&v.uri);
out.push('\n');
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn variant(bandwidth: u64, width: u32, height: u32, uri: &str) -> MasterVariant {
MasterVariant {
bandwidth,
width,
height,
uri: uri.to_string(),
codecs: None,
}
}
#[test]
fn header_is_version_3() {
let text = generate_master_playlist(&[variant(800_000, 640, 360, "360p/index.m3u8")]);
assert!(text.starts_with("#EXTM3U\n#EXT-X-VERSION:3\n"));
}
#[test]
fn stream_inf_has_bandwidth_and_resolution_then_uri() {
let text = generate_master_playlist(&[variant(1_500_000, 1280, 720, "720p/index.m3u8")]);
assert!(text.contains("#EXT-X-STREAM-INF:BANDWIDTH=1500000,RESOLUTION=1280x720\n"));
assert!(text.contains("RESOLUTION=1280x720\n720p/index.m3u8\n"));
assert!(!text.contains("CODECS"));
}
#[test]
fn variants_sorted_ascending_by_bandwidth() {
let variants = vec![
variant(5_000_000, 1920, 1080, "1080p/index.m3u8"),
variant(800_000, 640, 360, "360p/index.m3u8"),
variant(1_500_000, 1280, 720, "720p/index.m3u8"),
];
let text = generate_master_playlist(&variants);
let pos_360 = text.find("360p/index.m3u8").unwrap();
let pos_720 = text.find("720p/index.m3u8").unwrap();
let pos_1080 = text.find("1080p/index.m3u8").unwrap();
assert!(pos_360 < pos_720, "360p must precede 720p");
assert!(pos_720 < pos_1080, "720p must precede 1080p");
let first_inf = text.find("#EXT-X-STREAM-INF").unwrap();
assert!(text[first_inf..].starts_with("#EXT-X-STREAM-INF:BANDWIDTH=800000"));
}
#[test]
fn codecs_attribute_is_quoted_when_present() {
let mut v = variant(1_500_000, 1280, 720, "720p/index.m3u8");
v.codecs = Some("avc1.640028,mp4a.40.2".to_string());
let text = generate_master_playlist(&[v]);
assert!(text.contains(",CODECS=\"avc1.640028,mp4a.40.2\"\n"));
}
#[test]
fn empty_ladder_yields_header_only() {
let text = generate_master_playlist(&[]);
assert_eq!(text, "#EXTM3U\n#EXT-X-VERSION:3\n");
}
}