pub mod dash;
pub mod ll_dash;
pub mod llhls;
use std::sync::Arc;
use axum::Router;
use crate::store::MediaStore;
#[non_exhaustive]
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub enum OutputKind {
#[serde(rename = "llhls")]
LlHls,
#[serde(rename = "dash")]
Dash,
#[serde(rename = "ll_dash")]
LlDash,
#[serde(rename = "custom")]
Custom {
type_tag: String,
#[serde(default)]
params: serde_json::Value,
},
}
impl OutputKind {
pub fn name(&self) -> &str {
match self {
OutputKind::LlHls => "llhls",
OutputKind::Dash => "dash",
OutputKind::LlDash => "ll_dash",
OutputKind::Custom { type_tag, .. } => type_tag,
}
}
pub fn build(&self) -> Arc<dyn Output> {
self.build_with_playlist_name(llhls::DEFAULT_PLAYLIST_NAME)
}
pub fn build_with_playlist_name(&self, playlist_name: &str) -> Arc<dyn Output> {
match self {
OutputKind::LlHls => Arc::new(llhls::LlHlsOutput::new(playlist_name)),
OutputKind::Dash => Arc::new(dash::DashOutput),
OutputKind::LlDash => Arc::new(ll_dash::LlDashOutput),
OutputKind::Custom { .. } => unreachable!(
"OutputKind::Custom cannot be built without a SchemeRegistry — \
crate::origin::serve_with_registry resolves it via \
`registry.output(type_tag)` instead of this method"
),
}
}
}
broadcast_common::impl_spec_display!(OutputKind);
pub trait Output: Send + Sync + 'static {
fn kind(&self) -> OutputKind;
fn manifest_routes(&self, store: Arc<MediaStore>) -> Router;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn output_kind_name_and_display_agree() {
for (kind, label) in [
(OutputKind::LlHls, "llhls"),
(OutputKind::Dash, "dash"),
(OutputKind::LlDash, "ll_dash"),
] {
assert_eq!(kind.name(), label);
assert_eq!(kind.to_string(), label);
}
}
#[test]
fn output_kind_serde_round_trips() {
for kind in [OutputKind::LlHls, OutputKind::Dash, OutputKind::LlDash] {
let json = serde_json::to_string(&kind).unwrap();
let back: OutputKind = serde_json::from_str(&json).unwrap();
assert_eq!(back.name(), kind.name());
}
assert_eq!(
serde_json::to_string(&OutputKind::LlHls).unwrap(),
"\"llhls\""
);
}
#[test]
fn output_kind_build_matches_kind() {
assert!(matches!(
OutputKind::LlHls.build().kind(),
OutputKind::LlHls
));
assert!(matches!(OutputKind::Dash.build().kind(), OutputKind::Dash));
assert!(matches!(
OutputKind::LlDash.build().kind(),
OutputKind::LlDash
));
}
#[test]
fn output_kind_custom_deserializes_with_type_tag_and_params() {
let json = r#"{ "custom": { "type_tag": "webrtc", "params": { "k": "v" } } }"#;
let kind: OutputKind = serde_json::from_str(json).unwrap();
match &kind {
OutputKind::Custom { type_tag, params } => {
assert_eq!(type_tag, "webrtc");
assert_eq!(params.get("k").and_then(|v| v.as_str()), Some("v"));
}
other => panic!("expected OutputKind::Custom, got {other:?}"),
}
assert_eq!(kind.name(), "webrtc");
}
#[test]
#[should_panic(expected = "SchemeRegistry")]
fn output_kind_custom_build_panics() {
let kind = OutputKind::Custom {
type_tag: "webrtc".into(),
params: serde_json::Value::Null,
};
let _ = kind.build();
}
}