use std::task::Poll;
use std::time::SystemTime;
use hang::catalog::Timeline;
use hang::timeline::{Record, RecordExt, track_name};
use crate::container::Timestamp;
pub const DEFAULT_GRANULARITY: Timestamp = Timestamp::from_secs_unchecked(1);
pub struct Producer<E: RecordExt = ()> {
inner: moq_json::stream::Producer<Record<E>>,
track: String,
timescale: u32,
granularity: Timestamp,
wall: Option<u64>,
}
impl<E: RecordExt> Producer<E> {
pub fn new(broadcast: &mut moq_net::BroadcastProducer, name: &str) -> Result<Self, moq_net::Error> {
let track = track_name(name);
let net = broadcast.create_track(moq_net::Track::new(&track))?;
let config = moq_json::stream::ProducerConfig::default().with_compression(true);
Ok(Self {
inner: moq_json::stream::Producer::new(net, config),
track,
timescale: Timeline::default_timescale(),
granularity: DEFAULT_GRANULARITY,
wall: None,
})
}
pub fn with_granularity(mut self, granularity: Timestamp) -> Self {
self.granularity = granularity;
self
}
pub fn section(&self) -> Timeline {
let mut section = Timeline::new(&self.track);
section.timescale = self.timescale;
section.wall = self.wall;
section
}
pub fn set_wall(&mut self, pts: Timestamp, wall: SystemTime) {
let unix_millis = wall
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_millis();
let scale = self.timescale as u128;
let pts_units = pts.as_scale(self.timescale as u64);
let moq_millis = unix_millis.saturating_sub(hang::catalog::MOQ_EPOCH_UNIX_MILLIS as u128);
let moq_units = moq_millis * scale / 1000;
self.wall = Some(moq_units.saturating_sub(pts_units) as u64);
}
pub(crate) fn recorder(&self) -> Recorder<E> {
Recorder {
inner: self.inner.clone(),
timescale: self.timescale,
granularity: self.granularity,
last: None,
}
}
pub fn finish(&mut self) -> Result<(), moq_net::Error> {
match self.inner.finish() {
Ok(()) => Ok(()),
Err(moq_json::Error::Net(err)) => Err(err),
Err(err) => unreachable!("timeline finish failed to encode: {err}"),
}
}
}
pub(crate) struct Recorder<E: RecordExt = ()> {
inner: moq_json::stream::Producer<Record<E>>,
timescale: u32,
granularity: Timestamp,
last: Option<Timestamp>,
}
impl<E: RecordExt> Recorder<E> {
pub(crate) fn record(&mut self, sequence: u64, pts: Timestamp) -> Result<(), moq_net::Error> {
if let Some(last) = self.last
&& pts.as_micros() < last.as_micros() + self.granularity.as_micros()
{
return Ok(());
}
self.last = Some(pts);
let record = Record::new(sequence, pts.as_scale(self.timescale as u64) as u64);
match self.inner.append(&record) {
Ok(()) => Ok(()),
Err(moq_json::Error::Net(err)) => Err(err),
Err(err) => unreachable!("timeline record failed to encode: {err}"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Entry<E: RecordExt = ()> {
pub group: u64,
pub pts: Timestamp,
pub ext: E,
}
pub struct Consumer<E: RecordExt = ()> {
inner: moq_json::stream::Consumer<Record<E>>,
timescale: u32,
}
impl<E: RecordExt> Consumer<E> {
pub fn subscribe(broadcast: &moq_net::BroadcastConsumer, section: &Timeline) -> Result<Self, moq_net::Error> {
let track = broadcast.subscribe_track(&moq_net::Track::new(§ion.track))?;
let config = moq_json::stream::ConsumerConfig::default().with_compression(true);
Ok(Self {
inner: moq_json::stream::Consumer::new(track, config),
timescale: section.timescale,
})
}
fn decode(&self, record: Record<E>) -> Entry<E> {
Entry {
group: record.group,
pts: Timestamp::from_scale_unchecked(record.pts, self.timescale as u64),
ext: record.ext,
}
}
pub async fn next(&mut self) -> Result<Option<Entry<E>>, moq_json::Error> {
match self.inner.next().await? {
Some(record) => Ok(Some(self.decode(record))),
None => Ok(None),
}
}
pub fn poll_next(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Entry<E>>, moq_json::Error>> {
match self.inner.poll_next(waiter)? {
Poll::Ready(Some(record)) => Poll::Ready(Ok(Some(self.decode(record)))),
Poll::Ready(None) => Poll::Ready(Ok(None)),
Poll::Pending => Poll::Pending,
}
}
}
#[cfg(test)]
mod test {
use std::time::Duration;
use super::*;
fn entry(group: u64, pts_ms: u64) -> Entry {
Entry {
group,
pts: Timestamp::from_millis(pts_ms).unwrap(),
ext: (),
}
}
fn drain(broadcast: &moq_net::BroadcastProducer, producer: &Producer) -> Vec<Entry> {
let mut consumer = Consumer::subscribe(&broadcast.consume(), &producer.section()).unwrap();
let waiter = kio::Waiter::noop();
let mut out = Vec::new();
while let Poll::Ready(Ok(Some(entry))) = consumer.poll_next(&waiter) {
out.push(entry);
}
out
}
fn frame(timestamp_us: u64, keyframe: bool) -> crate::container::Frame {
crate::container::Frame {
timestamp: Timestamp::from_micros(timestamp_us).unwrap(),
payload: bytes::Bytes::from_static(&[0xDE, 0xAD]),
keyframe,
duration: None,
}
}
#[test]
fn records_group_opens_in_milliseconds() {
let mut broadcast = moq_net::Broadcast::new().produce();
let mut timeline = Producer::new(&mut broadcast, "video0").unwrap();
assert_eq!(timeline.track, "video0.timeline.z");
let track = broadcast.create_track(moq_net::Track::new("video0")).unwrap();
let mut media = crate::container::Producer::new(track, crate::catalog::hang::Container::Legacy)
.with_recorder(timeline.recorder());
media.write(frame(0, true)).unwrap(); media.write(frame(2_000_000, false)).unwrap(); media.write(frame(4_000_000, true)).unwrap(); media.finish().unwrap();
timeline.finish().unwrap();
assert_eq!(drain(&broadcast, &timeline), vec![entry(0, 0), entry(1, 4_000)]);
}
#[test]
fn granularity_throttles_records() {
let mut broadcast = moq_net::Broadcast::new().produce();
let mut timeline = Producer::new(&mut broadcast, "audio0").unwrap();
let mut recorder = timeline.recorder();
for (seq, ms) in [(0u64, 0u64), (1, 300), (2, 600), (3, 900), (4, 1200)] {
recorder.record(seq, Timestamp::from_millis(ms).unwrap()).unwrap();
}
drop(recorder);
timeline.finish().unwrap();
assert_eq!(drain(&broadcast, &timeline), vec![entry(0, 0), entry(4, 1200)]);
}
#[test]
fn section_advertises_track_and_wall() {
let mut broadcast = moq_net::Broadcast::new().produce();
let mut timeline = Producer::<()>::new(&mut broadcast, "audio0").unwrap();
let section = timeline.section();
assert_eq!(section.track, "audio0.timeline.z");
assert_eq!(section.timescale, 1000);
assert_eq!(section.wall, None);
let moq = hang::catalog::MOQ_EPOCH_UNIX_MILLIS;
let observed = SystemTime::UNIX_EPOCH + Duration::from_millis(1_751_846_400_000);
timeline.set_wall(Timestamp::from_micros(0).unwrap(), observed);
assert_eq!(timeline.section().wall, Some(1_751_846_400_000 - moq));
timeline.set_wall(Timestamp::from_micros(2_000_000).unwrap(), observed);
assert_eq!(timeline.section().wall, Some(1_751_846_400_000 - moq - 2_000));
}
#[test]
fn consumer_decodes_pts_from_the_section() {
let mut broadcast = moq_net::Broadcast::new().produce();
let mut timeline = Producer::new(&mut broadcast, "video0").unwrap();
timeline
.recorder()
.record(3, Timestamp::from_micros(7_000).unwrap())
.unwrap();
timeline.finish().unwrap();
let entries = drain(&broadcast, &timeline);
assert_eq!(entries, vec![entry(3, 7)]);
assert_eq!(entries[0].pts, Timestamp::from_micros(7_000).unwrap());
}
}