mod master;
mod playlist;
mod rendition;
pub mod store;
use std::collections::BTreeMap;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use moq_mux::catalog::hang::Catalog;
use moq_mux::catalog::{self, CatalogFormat, Stream};
use tokio::sync::watch;
pub use playlist::render_media;
pub use rendition::{Kind, Rendition};
const CATALOG_RETRY: Duration = Duration::from_millis(250);
#[derive(Clone, Debug)]
pub struct Config {
pub part_target: Duration,
pub window: Duration,
pub latency: Duration,
pub audio_segment_target: Duration,
}
impl Default for Config {
fn default() -> Self {
Self {
part_target: Duration::from_millis(500),
window: Duration::from_secs(16),
latency: Duration::from_secs(10),
audio_segment_target: Duration::from_secs(2),
}
}
}
pub struct Broadcaster {
broadcast: moq_net::BroadcastConsumer,
renditions: Mutex<BTreeMap<String, Arc<Rendition>>>,
ready: watch::Sender<usize>,
paused: watch::Sender<bool>,
}
impl Broadcaster {
pub fn new(broadcast: moq_net::BroadcastConsumer, config: Config) -> Arc<Self> {
let (ready, _) = watch::channel(0);
let (paused, _) = watch::channel(false);
let broadcaster = Arc::new(Self {
broadcast: broadcast.clone(),
renditions: Mutex::new(BTreeMap::new()),
ready,
paused,
});
tokio::spawn(watch_catalog(broadcast, config, broadcaster.clone()));
broadcaster
}
pub fn set_paused(&self, paused: bool) {
let _ = self.paused.send(paused);
}
pub fn is_paused(&self) -> bool {
*self.paused.borrow()
}
pub(crate) fn is_closed(&self) -> bool {
self.broadcast.is_closed()
}
pub(crate) async fn closed(&self) {
self.broadcast.closed().await;
}
pub fn rendition(&self, name: &str) -> Option<Arc<Rendition>> {
self.renditions.lock().unwrap().get(name).cloned()
}
pub async fn wait_ready(&self, timeout: Duration) {
let mut rx = self.ready.subscribe();
if *rx.borrow() > 0 {
return;
}
let _ = tokio::time::timeout(timeout, async {
while rx.changed().await.is_ok() {
if *rx.borrow() > 0 {
break;
}
}
})
.await;
}
pub fn master_playlist(&self) -> String {
let renditions = self.renditions.lock().unwrap();
let mut video = Vec::new();
let mut audio = Vec::new();
for rendition in renditions.values() {
match rendition.kind {
Kind::Video => video.push(master::VideoVariant {
name: rendition.name.clone(),
bandwidth: rendition.bandwidth,
width: rendition.width,
height: rendition.height,
codec: rendition.codec.clone(),
}),
Kind::Audio => audio.push(master::AudioVariant {
name: rendition.name.clone(),
bandwidth: rendition.bandwidth,
codec: rendition.codec.clone(),
}),
}
}
master::render_master(&video, &audio)
}
fn sync(&self, broadcast: &moq_net::BroadcastConsumer, config: &Config, catalog: &Catalog) {
let mut renditions = self.renditions.lock().unwrap();
for (name, video) in &catalog.video.renditions {
renditions.entry(name.clone()).or_insert_with(|| {
Arc::new(Rendition::video(
name.clone(),
video,
broadcast.clone(),
config,
self.paused.subscribe(),
))
});
}
for (name, audio) in &catalog.audio.renditions {
renditions.entry(name.clone()).or_insert_with(|| {
Arc::new(Rendition::audio(
name.clone(),
audio,
broadcast.clone(),
config,
self.paused.subscribe(),
))
});
}
let _ = self.ready.send(renditions.len());
}
}
async fn watch_catalog(broadcast: moq_net::BroadcastConsumer, config: Config, broadcaster: Arc<Broadcaster>) {
let mut consumer = loop {
match catalog::Consumer::<()>::new(&broadcast, CatalogFormat::Hang) {
Ok(consumer) => break consumer,
Err(err) => {
tracing::warn!(%err, "failed to subscribe to broadcast catalog, retrying");
tokio::select! {
_ = tokio::time::sleep(CATALOG_RETRY) => {}
_ = kio::wait(|waiter| broadcast.poll_closed(waiter)) => return,
}
}
}
};
loop {
match kio::wait(|waiter| consumer.poll_next(waiter)).await {
Ok(Some(catalog)) => broadcaster.sync(&broadcast, &config, &catalog),
Ok(None) => break,
Err(err) => {
tracing::warn!(%err, "broadcast catalog stream ended with error");
break;
}
}
}
}