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>> {
loop {
if let Some(mut frame) = self.pending.pop_front() {
frame.keyframe = frame.keyframe || self.index == 0;
self.index += 1;
return Poll::Ready(Ok(Some(frame)));
}
let Some(frames) = ready!(self.format.poll_read(&mut self.group, waiter))? else {
return Poll::Ready(Ok(None));
};
self.pending.extend(frames);
}
}
}
#[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 mut 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);
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);
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 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::try_from(&cmaf).unwrap();
let mut 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_latency(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::try_from(&cmaf).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());
}
}