mod hls;
mod dash;
pub use hls::HlsParser;
pub use dash::DashParser;
use crate::{Result, Rendition, Segment};
use async_trait::async_trait;
use url::Url;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ManifestType {
Hls,
Dash,
}
#[derive(Debug, Clone)]
pub struct Manifest {
pub manifest_type: ManifestType,
pub renditions: Vec<Rendition>,
pub is_live: bool,
pub duration: Option<std::time::Duration>,
pub target_duration: std::time::Duration,
pub base_url: Url,
}
#[async_trait]
pub trait ManifestParser: Send + Sync {
async fn parse(&self, url: &Url) -> Result<Manifest>;
async fn parse_variant(&self, url: &Url) -> Result<Vec<Segment>>;
async fn get_latest_segments(&self, url: &Url, last_sequence: u64) -> Result<Vec<Segment>>;
}
pub fn detect_manifest_type(url: &Url, content: Option<&str>) -> ManifestType {
let path = url.path().to_lowercase();
if path.ends_with(".m3u8") || path.ends_with(".m3u") {
return ManifestType::Hls;
}
if path.ends_with(".mpd") {
return ManifestType::Dash;
}
if let Some(content) = content {
if content.contains("#EXTM3U") {
return ManifestType::Hls;
}
if content.contains("<MPD") || content.contains("urn:mpeg:dash") {
return ManifestType::Dash;
}
}
ManifestType::Hls
}
pub fn create_parser(url: &Url) -> Box<dyn ManifestParser> {
match detect_manifest_type(url, None) {
ManifestType::Hls => Box::new(HlsParser::new()),
ManifestType::Dash => Box::new(DashParser::new()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_hls() {
let url = Url::parse("https://example.com/master.m3u8").unwrap();
assert_eq!(detect_manifest_type(&url, None), ManifestType::Hls);
}
#[test]
fn test_detect_dash() {
let url = Url::parse("https://example.com/manifest.mpd").unwrap();
assert_eq!(detect_manifest_type(&url, None), ManifestType::Dash);
}
}