use std::collections::BTreeMap;
use std::sync::Arc;
use std::task::Poll;
use moq_mux::catalog::hang::Catalog;
use super::rendition::{Kind, Rendition};
use super::{Config, Upstream};
type Key = (Kind, String);
#[non_exhaustive]
pub enum Event {
Added(Arc<Rendition>),
Removed {
kind: Kind,
name: String,
},
}
#[derive(Clone)]
pub(crate) struct Producer {
state: kio::Producer<BTreeMap<Key, Arc<Rendition>>>,
}
impl Producer {
pub fn new() -> Self {
Self {
state: kio::Producer::new(BTreeMap::new()),
}
}
pub fn subscribe(&self) -> Consumer {
Consumer {
state: self.state.consume(),
seen: BTreeMap::new(),
}
}
pub fn get(&self, kind: Kind, name: &str) -> Option<Arc<Rendition>> {
self.state.read().get(&(kind, name.to_string())).cloned()
}
pub fn snapshot(&self) -> Vec<Arc<Rendition>> {
self.state.read().values().cloned().collect()
}
#[cfg_attr(not(feature = "server"), allow(dead_code))]
pub fn is_empty(&self) -> bool {
self.state.read().is_empty()
}
pub fn clear(&self) {
if let Ok(mut current) = self.state.write() {
current.clear();
}
}
pub fn close(&self) {
let _ = self.state.close();
}
pub async fn ready(&self) {
let _ = kio::wait(|waiter| {
self.state.poll_ref(waiter, |current| {
if current.is_empty() {
Poll::Pending
} else {
Poll::Ready(())
}
})
})
.await;
}
pub fn sync(&self, upstream: &Upstream, config: &Config, catalog: &Catalog) {
let Ok(mut current) = self.state.write() else {
return;
};
let mut catalog = catalog.clone();
catalog.video.renditions.retain(|name, config| {
let valid = upstream.source.resolve_reference(config.broadcast.as_ref()).is_some();
if !valid {
tracing::warn!(
rendition = name,
"ignoring video rendition whose broadcast escapes above the root"
);
}
valid
});
catalog.audio.renditions.retain(|name, config| {
let valid = upstream.source.resolve_reference(config.broadcast.as_ref()).is_some();
if !valid {
tracing::warn!(
rendition = name,
"ignoring audio rendition whose broadcast escapes above the root"
);
}
valid
});
let catalog = &catalog;
let stale: Vec<Key> = current
.iter()
.filter(|((kind, name), rendition)| match kind {
Kind::Video => !catalog
.video
.renditions
.get(name)
.is_some_and(|config| rendition.matches_video(config)),
Kind::Audio => !catalog
.audio
.renditions
.get(name)
.is_some_and(|config| rendition.matches_audio(config)),
})
.map(|(key, _)| key.clone())
.collect();
for key in stale {
if let Some(rendition) = current.remove(&key) {
rendition.close();
}
}
for (name, video) in &catalog.video.renditions {
let key = (Kind::Video, name.clone());
if current.contains_key(&key) {
continue;
}
match Rendition::video(name.clone(), video, upstream, config.window) {
Some(rendition) => {
current.insert(key, Arc::new(rendition));
}
None => tracing::warn!(%name, "skipping video rendition without a timeline track"),
}
}
for (name, audio) in &catalog.audio.renditions {
let key = (Kind::Audio, name.clone());
if current.contains_key(&key) {
continue;
}
match Rendition::audio(name.clone(), audio, upstream, config.window) {
Some(rendition) => {
current.insert(key, Arc::new(rendition));
}
None => tracing::warn!(%name, "skipping audio rendition without a timeline track"),
}
}
}
}
pub struct Consumer {
state: kio::Consumer<BTreeMap<Key, Arc<Rendition>>>,
seen: BTreeMap<Key, Arc<Rendition>>,
}
impl Consumer {
pub async fn next(&mut self) -> Option<Event> {
let event = kio::wait(|waiter| self.poll_next(waiter)).await?;
match &event {
Event::Added(rendition) => {
self.seen
.insert((rendition.kind, rendition.name.clone()), rendition.clone());
}
Event::Removed { kind, name } => {
self.seen.remove(&(*kind, name.clone()));
}
}
Some(event)
}
fn poll_next(&self, waiter: &kio::Waiter) -> Poll<Option<Event>> {
match self.state.poll(waiter, |current| next_event(current, &self.seen)) {
Poll::Ready(Ok(event)) => Poll::Ready(Some(event)),
Poll::Ready(Err(_)) => Poll::Ready(None),
Poll::Pending => Poll::Pending,
}
}
}
fn next_event(current: &BTreeMap<Key, Arc<Rendition>>, seen: &BTreeMap<Key, Arc<Rendition>>) -> Poll<Event> {
for (key, rendition) in seen {
match current.get(key) {
Some(current) if Arc::ptr_eq(current, rendition) => {}
_ => {
return Poll::Ready(Event::Removed {
kind: key.0,
name: key.1.clone(),
});
}
}
}
for (key, rendition) in current {
if !seen.contains_key(key) {
return Poll::Ready(Event::Added(rendition.clone()));
}
}
Poll::Pending
}