use std::collections::VecDeque;
use std::task::{Poll, ready};
use super::{Container, Frame};
pub struct GroupConsumer<F: Container> {
group: moq_net::group::Consumer,
format: F,
pending: VecDeque<Frame>,
index: u64,
}
impl<F: Container> GroupConsumer<F> {
pub fn new(group: moq_net::group::Consumer, format: F) -> Self {
Self {
group,
format,
pending: VecDeque::new(),
index: 0,
}
}
pub fn sequence(&self) -> u64 {
self.group.sequence
}
pub async fn read(&mut self) -> Result<Option<Frame>, F::Error> {
kio::wait(|waiter| self.poll_read(waiter)).await
}
pub fn poll_read(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Frame>, F::Error>> {
while self.pending.front().is_none_or(|frame| {
self.format.kind() == super::Kind::Video && frame.duration.is_none() && self.pending.len() < 2
}) {
match ready!(self.format.poll_read(&mut self.group, waiter)?) {
Some(frames) => {
for frame in frames {
if let Some(bound) = self.format.end(&frame) {
if self.format.kind() == super::Kind::Video
&& let Some(last) = self.pending.back_mut()
{
super::close_duration(last, bound);
}
} else {
self.pending.push_back(frame);
}
}
}
None => return Poll::Ready(Ok(self.pop_media())),
}
}
Poll::Ready(Ok(self.pop_media()))
}
fn pop_media(&mut self) -> Option<Frame> {
let mut frame = self.pending.pop_front()?;
frame.keyframe = frame.keyframe || self.index == 0;
self.index += 1;
Some(frame)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::catalog::hang::Container as Hang;
fn frame(timestamp_us: u64, payload: &'static [u8], keyframe: bool) -> Frame {
Frame {
timestamp: moq_net::Timestamp::from_micros(timestamp_us).unwrap(),
payload: bytes::Bytes::from_static(payload),
keyframe,
duration: None,
}
}
#[tokio::test]
async fn reads_a_group_to_completion() {
let broadcast = moq_net::broadcast::Info::new().produce();
let track = broadcast.create_track("media", None).unwrap();
let consumer = broadcast.consume();
let mut media = crate::container::Producer::new(track, Hang::Legacy(crate::container::Kind::Data));
media.write(frame(1_000_000, b"keyframe", true)).unwrap();
media.write(frame(1_020_000, b"delta", false)).unwrap();
media.finish().unwrap();
let group = consumer.track("media").unwrap().fetch_group(0, None).await.unwrap();
let mut group = GroupConsumer::new(group, Hang::Legacy(crate::container::Kind::Data));
assert_eq!(group.sequence(), 0);
let first = group.read().await.unwrap().unwrap();
assert_eq!(first.payload, b"keyframe".as_slice());
assert!(first.keyframe);
let second = group.read().await.unwrap().unwrap();
assert_eq!(second.payload, b"delta".as_slice());
assert!(!second.keyframe);
assert!(group.read().await.unwrap().is_none());
}
#[tokio::test]
async fn empty_data_frames_survive_subscription_and_fetch() {
for config in [hang::catalog::Container::Legacy, hang::catalog::Container::Loc] {
let broadcast = moq_net::broadcast::Info::new().produce();
let track = broadcast.create_track("data", hang::container::track_info(0)).unwrap();
let subscription = track.subscribe(moq_net::track::Subscription::default());
let consumer = broadcast.consume();
let mut producer =
crate::container::Producer::new(track, Hang::new(&config, crate::container::Kind::Data).unwrap());
producer.write(frame(0, b"", true)).unwrap();
producer.write(frame(10_000, b"data", false)).unwrap();
producer.finish().unwrap();
let mut live = crate::container::Consumer::new(
subscription,
Hang::new(&config, crate::container::Kind::Data).unwrap(),
);
let first = live.read().await.unwrap().unwrap();
assert!(first.payload.is_empty());
assert!(first.keyframe);
assert_eq!(live.read().await.unwrap().unwrap().payload, b"data".as_slice());
assert!(live.read().await.unwrap().is_none());
let group = consumer.track("data").unwrap().fetch_group(0, None).await.unwrap();
let mut fetched = GroupConsumer::new(group, Hang::new(&config, crate::container::Kind::Data).unwrap());
assert!(fetched.read().await.unwrap().unwrap().payload.is_empty());
assert_eq!(fetched.read().await.unwrap().unwrap().payload, b"data".as_slice());
assert!(fetched.read().await.unwrap().is_none());
}
}
#[tokio::test]
async fn a_duration_marker_times_the_last_frame() {
let broadcast = moq_net::broadcast::Info::new().produce();
let track = broadcast.create_track("media", None).unwrap();
let consumer = broadcast.consume();
let mut media = crate::container::Producer::new(track, Hang::Legacy(crate::container::Kind::Video));
media.write(frame(1_000_000, b"keyframe", true)).unwrap();
media.write(frame(1_020_000, b"delta", false)).unwrap();
media
.cut(Some(moq_net::Timestamp::from_micros(1_053_000).unwrap()))
.unwrap();
media.finish().unwrap();
let group = consumer.track("media").unwrap().fetch_group(0, None).await.unwrap();
let mut group = GroupConsumer::new(group, Hang::Legacy(crate::container::Kind::Video));
let first = group.read().await.unwrap().unwrap();
assert_eq!(first.payload, b"keyframe".as_slice());
assert_eq!(first.duration, None);
let second = group.read().await.unwrap().unwrap();
assert_eq!(second.payload, b"delta".as_slice());
assert_eq!(second.duration, Some(moq_net::Timestamp::from_micros(33_000).unwrap()));
assert!(group.read().await.unwrap().is_none());
}
#[tokio::test]
async fn hands_back_a_cmaf_batch_one_frame_at_a_time() {
let mut config = hang::catalog::VideoConfig::new(hang::catalog::VideoCodec::VP8);
config.coded_width = Some(320);
config.coded_height = Some(240);
let muxer = crate::container::fmp4::Muxer::video(&config).unwrap();
let init = muxer.init().unwrap().expect("VP8 init should be available");
let cmaf = hang::catalog::Container::Cmaf { init };
let format = Hang::new(&cmaf, crate::container::Kind::Video).unwrap();
let broadcast = moq_net::broadcast::Info::new().produce();
let track = broadcast.create_track("video", None).unwrap();
let consumer = broadcast.consume();
let mut media = crate::container::Producer::new(track, format).with_buffer(std::time::Duration::from_secs(1));
for (timestamp_us, payload, keyframe) in [
(2_000_000, b"keyframe".as_slice(), true),
(2_020_000, b"delta".as_slice(), false),
] {
media
.write(Frame {
timestamp: moq_net::Timestamp::from_micros(timestamp_us).unwrap(),
payload: bytes::Bytes::from_static(payload),
keyframe,
duration: Some(moq_net::Timestamp::from_micros(20_000).unwrap()),
})
.unwrap();
}
media.finish().unwrap();
let group = consumer.track("video").unwrap().fetch_group(0, None).await.unwrap();
let mut group = GroupConsumer::new(group, Hang::new(&cmaf, crate::container::Kind::Video).unwrap());
let first = group.read().await.unwrap().unwrap();
assert_eq!(first.payload, b"keyframe".as_slice());
assert!(first.keyframe);
let second = group.read().await.unwrap().unwrap();
assert_eq!(second.payload, b"delta".as_slice());
assert!(!second.keyframe);
assert!(group.read().await.unwrap().is_none());
}
}