use std::collections::VecDeque;
use std::sync::Mutex;
use bytes::{Bytes, BytesMut};
use moq_mux::container::fmp4::Fragment;
use tokio::sync::watch;
use super::{Config, Kind};
#[derive(Clone)]
struct Part {
data: Bytes,
duration: f64,
independent: bool,
}
struct Segment {
sequence: u64,
parts: Vec<Part>,
duration: f64,
complete: bool,
discontinuity: bool,
}
pub struct PartMeta {
pub duration: f64,
pub independent: bool,
}
pub struct SegmentMeta {
pub sequence: u64,
pub parts: Vec<PartMeta>,
pub duration: f64,
pub complete: bool,
pub discontinuity: bool,
}
pub struct Snapshot {
pub init_ready: bool,
pub part_target: f64,
pub target_duration: u64,
pub media_sequence: u64,
pub discontinuity_sequence: u64,
pub next_sequence: u64,
pub segments: Vec<SegmentMeta>,
pub finished: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Version {
pub last_sequence: u64,
pub last_parts: usize,
pub media_sequence: u64,
pub finished: bool,
}
struct Inner {
init: Option<Bytes>,
segments: VecDeque<Segment>,
next_sequence: u64,
discontinuity_sequence: u64,
target_duration: u64,
finished: bool,
discontinuity_pending: bool,
}
pub struct SegmentStore {
inner: Mutex<Inner>,
notify: watch::Sender<Version>,
kind: Kind,
part_target: f64,
audio_segment_target: f64,
window: f64,
}
impl SegmentStore {
pub fn new(kind: Kind, config: &Config) -> Self {
let (notify, _) = watch::channel(Version::default());
let target_duration = config.part_target.as_secs_f64().ceil().max(1.0) as u64;
Self {
inner: Mutex::new(Inner {
init: None,
segments: VecDeque::new(),
next_sequence: 0,
discontinuity_sequence: 0,
target_duration,
finished: false,
discontinuity_pending: false,
}),
notify,
kind,
part_target: config.part_target.as_secs_f64(),
audio_segment_target: config.audio_segment_target.as_secs_f64(),
window: config.window.as_secs_f64(),
}
}
pub fn push(&self, fragment: Fragment) {
if fragment.init {
self.inner.lock().unwrap().init = Some(fragment.data);
self.bump();
return;
}
{
let mut inner = self.inner.lock().unwrap();
let discontinuity = inner.discontinuity_pending;
let need_new = discontinuity
|| match inner.segments.back() {
None => true,
Some(cur) => {
if self.kind == Kind::Video {
fragment.independent
} else {
cur.duration >= self.audio_segment_target
}
}
};
if need_new {
if let Some(duration) = inner.segments.back_mut().map(|cur| {
cur.complete = true;
cur.duration
}) {
latch_target_duration(duration, &mut inner.target_duration);
}
let sequence = inner.next_sequence;
inner.next_sequence += 1;
inner.segments.push_back(Segment {
sequence,
parts: Vec::new(),
duration: 0.0,
complete: false,
discontinuity,
});
inner.discontinuity_pending = false;
}
let cur = inner.segments.back_mut().expect("segment present after need_new");
cur.duration += fragment.duration;
cur.parts.push(Part {
data: fragment.data,
duration: fragment.duration,
independent: fragment.independent,
});
while inner.segments.len() > 1 {
let total: f64 = inner.segments.iter().map(|s| s.duration).sum();
let oldest = inner.segments.front().expect("segments non-empty").duration;
if total - oldest >= self.window {
let evicted = inner.segments.pop_front().expect("segments non-empty");
if evicted.discontinuity {
inner.discontinuity_sequence = inner.discontinuity_sequence.saturating_add(1);
}
} else {
break;
}
}
}
self.bump();
}
pub fn mark_discontinuity(&self) {
self.inner.lock().unwrap().discontinuity_pending = true;
}
pub fn finish(&self) {
{
let mut inner = self.inner.lock().unwrap();
if let Some(duration) = inner.segments.back_mut().map(|cur| {
cur.complete = true;
cur.duration
}) {
latch_target_duration(duration, &mut inner.target_duration);
}
inner.finished = true;
}
self.bump();
}
fn bump(&self) {
let version = self.version();
let _ = self.notify.send(version);
}
pub fn version(&self) -> Version {
let inner = self.inner.lock().unwrap();
let media_sequence = inner
.segments
.front()
.map(|s| s.sequence)
.unwrap_or(inner.next_sequence);
match inner.segments.back() {
Some(last) => Version {
last_sequence: last.sequence,
last_parts: last.parts.len(),
media_sequence,
finished: inner.finished,
},
None => Version {
last_sequence: inner.next_sequence,
last_parts: 0,
media_sequence,
finished: inner.finished,
},
}
}
pub fn subscribe(&self) -> watch::Receiver<Version> {
self.notify.subscribe()
}
pub fn init(&self) -> Option<Bytes> {
self.inner.lock().unwrap().init.clone()
}
pub fn part(&self, sequence: u64, index: usize) -> Option<Bytes> {
let inner = self.inner.lock().unwrap();
let segment = inner.segments.iter().find(|s| s.sequence == sequence)?;
segment.parts.get(index).map(|p| p.data.clone())
}
pub fn segment(&self, sequence: u64) -> Option<Bytes> {
let inner = self.inner.lock().unwrap();
let segment = inner.segments.iter().find(|s| s.sequence == sequence)?;
let mut buf = BytesMut::new();
for part in &segment.parts {
buf.extend_from_slice(&part.data);
}
Some(buf.freeze())
}
pub fn satisfies(&self, sequence: u64, part: usize) -> bool {
let inner = self.inner.lock().unwrap();
if inner.finished {
return true;
}
let media_sequence = inner.segments.front().map(|s| s.sequence).unwrap_or(0);
if sequence < media_sequence {
return true; }
match inner.segments.iter().find(|s| s.sequence == sequence) {
Some(segment) => segment.parts.len() > part || segment.complete,
None => false,
}
}
pub fn snapshot(&self) -> Snapshot {
let inner = self.inner.lock().unwrap();
let media_sequence = inner
.segments
.front()
.map(|s| s.sequence)
.unwrap_or(inner.next_sequence);
let segments = inner
.segments
.iter()
.map(|s| SegmentMeta {
sequence: s.sequence,
parts: s
.parts
.iter()
.map(|p| PartMeta {
duration: p.duration,
independent: p.independent,
})
.collect(),
duration: s.duration,
complete: s.complete,
discontinuity: s.discontinuity,
})
.collect();
Snapshot {
init_ready: inner.init.is_some(),
part_target: self.part_target,
target_duration: inner.target_duration,
media_sequence,
discontinuity_sequence: inner.discontinuity_sequence,
next_sequence: inner.next_sequence,
segments,
finished: inner.finished,
}
}
}
fn latch_target_duration(duration: f64, target_duration: &mut u64) {
*target_duration = (*target_duration).max(duration.ceil().max(1.0) as u64);
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use bytes::Bytes;
use super::*;
fn config(window: Duration) -> Config {
Config {
part_target: Duration::from_millis(500),
window,
latency: Duration::from_secs(10),
audio_segment_target: Duration::from_secs(2),
}
}
fn fragment(duration: f64) -> Fragment {
Fragment {
data: Bytes::new(),
init: false,
independent: true,
duration,
}
}
#[test]
fn evicting_discontinuous_segment_advances_sequence() {
let cfg = config(Duration::from_secs(2));
let store = SegmentStore::new(Kind::Video, &cfg);
store.push(fragment(1.0));
store.mark_discontinuity();
store.push(fragment(1.0));
store.push(fragment(1.0));
let snapshot = store.snapshot();
assert_eq!(snapshot.media_sequence, 1);
assert_eq!(snapshot.discontinuity_sequence, 0);
assert!(snapshot.segments[0].discontinuity);
store.push(fragment(1.0));
let snapshot = store.snapshot();
assert_eq!(snapshot.media_sequence, 2);
assert_eq!(snapshot.discontinuity_sequence, 1);
assert!(snapshot.segments.iter().all(|s| !s.discontinuity));
}
#[test]
fn target_duration_never_decreases_after_eviction() {
let cfg = config(Duration::from_secs(2));
let store = SegmentStore::new(Kind::Video, &cfg);
store.push(fragment(6.0));
store.push(fragment(1.0));
let snapshot = store.snapshot();
assert_eq!(snapshot.target_duration, 6);
assert_eq!(snapshot.media_sequence, 0);
store.push(fragment(1.0));
store.push(fragment(1.0));
let snapshot = store.snapshot();
assert_eq!(snapshot.media_sequence, 2);
assert_eq!(snapshot.target_duration, 6);
}
}