use std::collections::{BTreeMap, VecDeque};
use std::sync::{Arc, Mutex, Weak};
use std::task::Poll;
use std::time::{Duration, SystemTime};
use moq_mux::catalog::hang::Catalog;
use moq_mux::timeline::Entry;
use super::Upstream;
use super::rendition::{Kind, Rendition};
use super::segments::Discontinuities;
type Key = (Kind, String);
#[non_exhaustive]
pub enum Event {
Added(Arc<Rendition>),
Removed {
kind: Kind,
name: String,
},
}
struct Feed {
targets: Vec<Weak<Rendition>>,
history: VecDeque<(u64, Entry, u64)>,
discontinuities: Discontinuities,
ended: bool,
closed: bool,
anchor: Option<SystemTime>,
generation: Option<Arc<str>>,
}
#[derive(Clone)]
pub(crate) struct Producer {
state: kio::Producer<BTreeMap<Key, Arc<Rendition>>>,
fanout: Fanout,
}
#[derive(Clone)]
pub(crate) struct Fanout {
feed: Arc<Mutex<Feed>>,
window: Duration,
}
impl Producer {
pub fn new(window: Duration) -> Self {
Self {
state: kio::Producer::new(BTreeMap::new()),
fanout: Fanout {
feed: Arc::new(Mutex::new(Feed {
targets: Vec::new(),
history: VecDeque::new(),
discontinuities: Discontinuities::default(),
ended: false,
closed: false,
anchor: None,
generation: None,
})),
window,
},
}
}
pub fn fanout(&self) -> Fanout {
self.fanout.clone()
}
pub fn window(&self) -> Duration {
self.fanout.window
}
pub fn anchor(&self) -> Option<SystemTime> {
self.fanout.feed.lock().unwrap().anchor
}
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 any_held_externally(&self) -> bool {
self.state.read().values().any(|r| Arc::strong_count(r) > 1)
}
pub fn clear(&self) {
if let Ok(mut current) = self.state.write() {
current.clear();
}
}
pub fn close(&self) {
let _ = self.state.close();
}
}
impl Fanout {
pub fn push(&self, index: u64, entry: Entry) {
let mut feed = self.feed.lock().unwrap();
if let Some((_, back, _)) = feed.history.back()
&& (Duration::from(entry.pts) < Duration::from(back.pts) || entry.segment <= back.segment)
{
feed.history.clear();
feed.anchor = None;
}
if feed.anchor.is_none() {
let end = Duration::from(entry.pts) + entry.duration;
feed.anchor = Some(SystemTime::now().checked_sub(end).unwrap_or(SystemTime::UNIX_EPOCH));
}
let pts = Duration::from(entry.pts);
let discontinuity = feed.discontinuities.stamp(pts, pts + entry.duration);
feed.history.push_back((index, entry.clone(), discontinuity));
while feed.history.len() >= 2 {
let newest = &feed.history.back().unwrap().1;
let span =
(Duration::from(newest.pts) + newest.duration).saturating_sub(Duration::from(feed.history[1].1.pts));
if span < self.window {
break;
}
feed.history.pop_front();
}
let window = self.window;
feed.targets.retain(|target| {
let Some(rendition) = target.upgrade() else {
return false;
};
rendition.push(index, &entry, discontinuity, window);
true
});
}
pub fn pop(&self, range: std::ops::Range<u64>) {
let mut feed = self.feed.lock().unwrap();
feed.history.retain(|(index, _, _)| !range.contains(index));
feed.targets.retain(|target| {
let Some(rendition) = target.upgrade() else {
return false;
};
rendition.pop(range.clone());
true
});
}
pub fn set_generation(&self, generation: Option<Arc<str>>) {
let mut feed = self.feed.lock().unwrap();
if feed.generation == generation {
return;
}
let restart = feed.generation.is_some();
feed.generation = generation.clone();
if restart {
feed.history.clear();
feed.discontinuities.interrupt();
feed.anchor = None;
}
feed.targets.retain(|target| {
let Some(rendition) = target.upgrade() else {
return false;
};
if restart {
rendition.restart(generation.clone());
} else {
rendition.label(generation.clone());
}
true
});
}
pub fn skip(&self) {
let mut feed = self.feed.lock().unwrap();
feed.history.clear();
feed.discontinuities.interrupt();
feed.targets.retain(|target| {
let Some(rendition) = target.upgrade() else {
return false;
};
rendition.clear();
true
});
}
pub fn end_windows(&self) {
let mut feed = self.feed.lock().unwrap();
feed.ended = true;
feed.targets.retain(|target| {
let Some(rendition) = target.upgrade() else {
return false;
};
rendition.end();
true
});
}
pub fn close_windows(&self) {
let mut feed = self.feed.lock().unwrap();
feed.closed = true;
feed.targets.retain(|target| {
let Some(rendition) = target.upgrade() else {
return false;
};
rendition.close();
true
});
}
}
impl Producer {
pub async fn ready(&self) {
let _ = kio::wait(|waiter| {
self.state.poll_ref(waiter, |current| {
if current
.values()
.any(|rendition| rendition.poll_playable(waiter).is_ready())
{
Poll::Ready(())
} else {
Poll::Pending
}
})
})
.await;
}
fn register(&self, rendition: &Arc<Rendition>) {
let mut feed = self.fanout.feed.lock().unwrap();
rendition.label(feed.generation.clone());
for (index, entry, discontinuity) in &feed.history {
rendition.push(*index, entry, *discontinuity, self.fanout.window);
}
if feed.ended {
rendition.end();
}
if feed.closed {
rendition.close();
}
feed.targets.push(Arc::downgrade(rendition));
}
pub fn sync(&self, upstream: &Upstream, catalog: &Catalog) {
let Some(archive) = catalog.archive.clone() else {
if !catalog.video.renditions.is_empty() || !catalog.audio.renditions.is_empty() {
tracing::warn!("catalog advertises no archive; its renditions can't be served as HLS");
}
return;
};
let section = archive;
let clock = catalog.clock;
let Ok(mut current) = self.state.write() else {
return;
};
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 let Some(rendition) = current.get(&key) {
rendition.refresh(video.bitrate, video.framerate);
continue;
}
let rendition = match Rendition::video(name.clone(), video, upstream, section.clone(), clock) {
Ok(rendition) => Arc::new(rendition),
Err(err) => {
tracing::warn!(rendition = name, %err, "ignoring unservable video rendition");
continue;
}
};
self.register(&rendition);
current.insert(key, rendition);
}
for (name, audio) in &catalog.audio.renditions {
let key = (Kind::Audio, name.clone());
if let Some(rendition) = current.get(&key) {
rendition.refresh(audio.bitrate, None);
continue;
}
let rendition = match Rendition::audio(name.clone(), audio, upstream, section.clone(), clock) {
Ok(rendition) => Arc::new(rendition),
Err(err) => {
tracing::warn!(rendition = name, %err, "ignoring unservable audio rendition");
continue;
}
};
self.register(&rendition);
current.insert(key, rendition);
}
}
}
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
}