use super::{Container, Frame, Timestamp};
pub struct Producer<C: Container> {
inner: moq_net::TrackProducer,
container: C,
group: Option<moq_net::GroupProducer>,
buffer: Vec<Frame>,
latency: std::time::Duration,
pending_sequence: Option<u64>,
recorder: Option<crate::timeline::Recorder>,
}
impl<C: Container> Producer<C> {
pub fn new(track: moq_net::TrackProducer, container: C) -> Self {
Self {
inner: track,
container,
group: None,
buffer: Vec::new(),
latency: std::time::Duration::ZERO,
pending_sequence: None,
recorder: None,
}
}
pub fn with_latency(mut self, latency: std::time::Duration) -> Self {
self.latency = latency;
self
}
pub(crate) fn with_recorder(mut self, recorder: crate::timeline::Recorder) -> Self {
self.recorder = Some(recorder);
self
}
pub fn track(&self) -> &moq_net::TrackProducer {
&self.inner
}
pub fn write(&mut self, frame: Frame) -> Result<(), C::Error> {
if frame.keyframe {
self.finish_group_at(Some(frame.timestamp))?;
}
if self.group.is_none() {
if !frame.keyframe {
return Err(super::MissingKeyframe.into());
}
let group = match self.pending_sequence.take() {
Some(sequence) => self.inner.create_group(moq_net::Group { sequence })?,
None => self.inner.append_group()?,
};
let timeline_err = match self.recorder.as_mut() {
Some(recorder) => recorder.record(group.sequence, frame.timestamp).err(),
None => None,
};
if let Some(err) = timeline_err {
tracing::warn!(?err, "timeline recording failed; dropping the timeline for this track");
self.recorder = None;
}
self.group = Some(group);
}
if self.latency.is_zero() {
let group = self.group.as_mut().unwrap();
self.container.write(group, &[frame])?;
} else {
self.buffer.push(frame);
if self.buffer.len() >= 2 {
let mut iter = self.buffer.iter().map(|f| std::time::Duration::from(f.timestamp));
let first = iter.next().unwrap();
let (min, max) = iter.fold((first, first), |(min, max), d| (min.min(d), max.max(d)));
if max.saturating_sub(min) >= self.latency {
self.flush(None)?;
}
}
}
Ok(())
}
pub fn finish_group(&mut self) -> Result<(), C::Error> {
self.finish_group_at(None)
}
fn finish_group_at(&mut self, next: Option<Timestamp>) -> Result<(), C::Error> {
self.flush(next)?;
if let Some(mut group) = self.group.take() {
group.finish()?;
}
Ok(())
}
pub fn seek(&mut self, sequence: u64) -> Result<(), C::Error> {
self.finish_group()?;
self.pending_sequence = Some(sequence);
Ok(())
}
fn flush(&mut self, next: Option<Timestamp>) -> Result<(), C::Error> {
if self.buffer.is_empty() {
return Ok(());
}
for i in 0..self.buffer.len() {
if self.buffer[i].duration.is_some() {
continue;
}
let boundary = self.buffer.get(i + 1).map(|f| f.timestamp).or(next);
if let Some(boundary) = boundary
&& let Ok(duration) = boundary.checked_sub(self.buffer[i].timestamp)
{
self.buffer[i].duration = Some(duration);
}
}
let group = match &mut self.group {
Some(group) => group,
None => return Ok(()),
};
self.container.write(group, &self.buffer)?;
self.buffer.clear();
Ok(())
}
pub fn finish(&mut self) -> Result<(), C::Error> {
self.finish_group()?;
self.inner.finish()?;
Ok(())
}
pub fn consume(&self) -> moq_net::TrackConsumer {
self.inner.consume()
}
}
impl<C: Container> std::ops::Deref for Producer<C> {
type Target = moq_net::TrackProducer;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[cfg(test)]
mod tests {
use bytes::Bytes;
use super::*;
use crate::catalog::hang::Container;
use crate::container::Timestamp;
fn track_producer(name: impl Into<String>) -> moq_net::TrackProducer {
moq_net::Broadcast::new()
.produce()
.create_track(moq_net::Track::new(name))
.unwrap()
}
fn frame(timestamp_us: u64, keyframe: bool) -> Frame {
Frame {
timestamp: Timestamp::from_micros(timestamp_us).unwrap(),
payload: Bytes::from_static(&[0xDE, 0xAD]),
keyframe,
duration: None,
}
}
async fn collect_groups(mut consumer: moq_net::TrackConsumer) -> Vec<usize> {
let mut groups = Vec::new();
while let Some(mut group) = consumer.recv_group().await.unwrap() {
let mut count = 0;
while group.next_frame().await.unwrap().is_some() {
count += 1;
}
groups.push(count);
}
groups
}
#[tokio::test]
async fn keyframe_closes_group_immediately() {
let track = track_producer("test");
let consumer = track.consume();
let mut producer = Producer::new(track, Container::Legacy);
producer.write(frame(0, true)).unwrap(); producer.write(frame(10_000, false)).unwrap();
producer.write(frame(20_000, true)).unwrap(); producer.write(frame(30_000, false)).unwrap();
producer.finish().unwrap();
assert_eq!(collect_groups(consumer).await, vec![2, 2]);
}
#[tokio::test]
async fn finish_group_closes_immediately() {
let track = track_producer("test");
let consumer = track.consume();
let mut producer = Producer::new(track, Container::Legacy);
producer.write(frame(0, true)).unwrap();
producer.write(frame(10_000, false)).unwrap();
producer.finish_group().unwrap();
producer.write(frame(20_000, true)).unwrap();
producer.finish().unwrap();
assert_eq!(collect_groups(consumer).await, vec![2, 1]);
}
#[test]
fn first_frame_must_be_keyframe() {
let track = track_producer("test");
let mut producer = Producer::new(track, Container::Legacy);
let err = producer.write(frame(0, false)).unwrap_err();
assert!(matches!(err, crate::Error::MissingKeyframe(_)));
}
async fn collect_sequences(mut consumer: moq_net::TrackConsumer) -> Vec<u64> {
let mut sequences = Vec::new();
while let Some(group) = consumer.recv_group().await.unwrap() {
sequences.push(group.sequence);
}
sequences
}
#[tokio::test]
async fn seek_uses_explicit_sequence() {
let track = track_producer("test");
let consumer = track.consume();
let mut producer = Producer::new(track, Container::Legacy);
producer.write(frame(0, true)).unwrap(); producer.seek(42).unwrap();
producer.write(frame(10_000, true)).unwrap(); producer.finish().unwrap();
assert_eq!(collect_sequences(consumer).await, vec![0, 42]);
}
#[tokio::test]
async fn seek_clears_pending_after_use() {
let track = track_producer("test");
let consumer = track.consume();
let mut producer = Producer::new(track, Container::Legacy);
producer.seek(5).unwrap();
producer.write(frame(0, true)).unwrap(); producer.write(frame(10_000, true)).unwrap(); producer.finish().unwrap();
assert_eq!(collect_sequences(consumer).await, vec![5, 6]);
}
#[derive(Clone, Default)]
struct Recording(std::rc::Rc<std::cell::RefCell<Vec<Vec<Frame>>>>);
impl super::Container for Recording {
type Error = crate::Error;
fn write(&self, _group: &mut moq_net::GroupProducer, frames: &[Frame]) -> Result<(), Self::Error> {
self.0.borrow_mut().push(frames.to_vec());
Ok(())
}
fn poll_read(
&self,
_group: &mut moq_net::GroupConsumer,
_waiter: &kio::Waiter,
) -> std::task::Poll<Result<crate::container::Read, Self::Error>> {
unreachable!("Recording is write-only")
}
}
#[tokio::test]
async fn keyframe_backfills_batched_durations() {
let track = track_producer("test");
let recording = Recording::default();
let mut producer = Producer::new(track, recording.clone()).with_latency(std::time::Duration::from_secs(10));
producer.write(frame(0, true)).unwrap(); producer.write(frame(33_000, false)).unwrap(); producer.write(frame(66_000, true)).unwrap(); producer.finish().unwrap();
let writes = recording.0.borrow();
let group0 = &writes[0];
assert_eq!(group0.len(), 2);
assert_eq!(group0[0].duration, Some(Timestamp::from_micros(33_000).unwrap()));
assert_eq!(group0[1].duration, Some(Timestamp::from_micros(33_000).unwrap()));
}
}