use std::sync::Arc;
use hang::catalog::{AudioConfig, VideoConfig};
use moq_mux::catalog::{self, CatalogFormat, Stream};
use moq_mux::container::fmp4::Export;
use moq_mux::select;
use tokio::sync::watch;
use super::Config;
use super::store::SegmentStore;
use crate::Result;
const DEFAULT_VIDEO_BITRATE: u64 = 2_000_000;
const DEFAULT_AUDIO_BITRATE: u64 = 128_000;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Kind {
Video,
Audio,
}
pub struct Rendition {
pub name: String,
pub kind: Kind,
pub bandwidth: u64,
pub width: Option<u32>,
pub height: Option<u32>,
pub codec: String,
pub store: Arc<SegmentStore>,
}
impl Rendition {
pub fn video(
name: String,
config: &VideoConfig,
broadcast: moq_net::BroadcastConsumer,
cfg: &Config,
paused: watch::Receiver<bool>,
) -> Self {
let store = Arc::new(SegmentStore::new(Kind::Video, cfg));
spawn_pump(broadcast, name.clone(), Kind::Video, store.clone(), cfg.clone(), paused);
Self {
name,
kind: Kind::Video,
bandwidth: config.bitrate.unwrap_or(DEFAULT_VIDEO_BITRATE),
width: config.coded_width,
height: config.coded_height,
codec: config.codec.to_string(),
store,
}
}
pub fn audio(
name: String,
config: &AudioConfig,
broadcast: moq_net::BroadcastConsumer,
cfg: &Config,
paused: watch::Receiver<bool>,
) -> Self {
let store = Arc::new(SegmentStore::new(Kind::Audio, cfg));
spawn_pump(broadcast, name.clone(), Kind::Audio, store.clone(), cfg.clone(), paused);
Self {
name,
kind: Kind::Audio,
bandwidth: config.bitrate.unwrap_or(DEFAULT_AUDIO_BITRATE),
width: None,
height: None,
codec: config.codec.to_string(),
store,
}
}
}
fn spawn_pump(
broadcast: moq_net::BroadcastConsumer,
name: String,
kind: Kind,
store: Arc<SegmentStore>,
cfg: Config,
paused: watch::Receiver<bool>,
) {
tokio::spawn(async move {
if let Err(err) = run_pump(broadcast, &name, kind, &store, &cfg, paused).await {
tracing::warn!(%name, ?kind, %err, "hls rendition pump ended with error");
}
store.finish();
});
}
async fn run_pump(
broadcast: moq_net::BroadcastConsumer,
name: &str,
kind: Kind,
store: &SegmentStore,
cfg: &Config,
mut paused: watch::Receiver<bool>,
) -> Result<()> {
let consumer = catalog::Consumer::<()>::new(&broadcast, CatalogFormat::Hang)?;
let selection = match kind {
Kind::Video => select::Broadcast::default().video(select::Video::default().name(name)),
Kind::Audio => select::Broadcast::default().audio(select::Audio::default().name(name)),
};
let filtered = consumer.select(selection);
let closed = broadcast.clone();
let mut export = Export::new(broadcast, filtered)
.with_fragment_duration(cfg.part_target)
.with_latency(cfg.latency);
let mut resumed = false;
loop {
while *paused.borrow_and_update() {
resumed = true;
tokio::select! {
res = paused.changed() => {
if res.is_err() {
return Ok(()); }
}
_ = kio::wait(|w| closed.poll_closed(w)) => return Ok(()),
}
}
if resumed {
store.mark_discontinuity();
resumed = false;
}
match export.next_fragment().await? {
Some(fragment) => store.push(fragment),
None => break,
}
}
Ok(())
}