use std::time::{Duration, Instant};
use moq_net::Timestamp;
#[derive(Default)]
pub struct Pacer {
lead: Duration,
delay: Duration,
anchor: Option<(Instant, u128)>,
slack: Duration,
}
impl Pacer {
pub fn with_lead(mut self, lead: Duration) -> Self {
self.lead = lead;
self
}
pub fn with_delay(mut self, delay: Duration) -> Self {
self.delay = delay;
self
}
pub fn pace(&mut self, ts: Timestamp, now: Instant) -> Instant {
let nanos = ts.as_nanos();
let (anchor, base) = *self.anchor.get_or_insert((now, nanos));
match send_at(anchor, base, nanos) {
Some(at) if at.saturating_duration_since(now) <= self.lead => {
let at = self.absorb(at, now);
self.delayed(at)
}
_ => self.hurry(ts, now),
}
}
pub fn due(&self, ts: Timestamp) -> Option<Instant> {
let (anchor, base) = self.anchor?;
Some(self.delayed(send_at(anchor, base, ts.as_nanos())?))
}
fn delayed(&self, at: Instant) -> Instant {
at.checked_add(self.delay).unwrap_or(at)
}
fn absorb(&mut self, at: Instant, now: Instant) -> Instant {
let behind = now.saturating_duration_since(at);
let shift = behind.min(self.lead.saturating_sub(self.slack));
if shift.is_zero() {
return at;
}
self.slack += shift;
if let Some((anchor, _)) = self.anchor.as_mut() {
*anchor += shift;
}
at + shift
}
pub fn slack(&self) -> Duration {
self.slack
}
pub fn hurry(&mut self, ts: Timestamp, now: Instant) -> Instant {
self.anchor = Some((now, ts.as_nanos()));
self.slack = Duration::ZERO;
self.delayed(now)
}
}
fn send_at(anchor: Instant, base: u128, nanos: u128) -> Option<Instant> {
if nanos >= base {
anchor.checked_add(duration(nanos - base))
} else {
Some(anchor.checked_sub(duration(base - nanos)).unwrap_or(anchor))
}
}
fn duration(nanos: u128) -> Duration {
Duration::from_nanos(nanos.try_into().unwrap_or(u64::MAX))
}
#[cfg(test)]
mod tests {
use super::*;
fn ms(m: u64) -> Timestamp {
Timestamp::from_micros(m * 1_000).unwrap()
}
#[test]
fn re_anchors_to_live_edge() {
let start = Instant::now();
let mut pacer = Pacer::default();
assert_eq!(pacer.pace(ms(1_400), start), start, "the first frame anchors at now");
let now = start + Duration::from_millis(8);
assert_eq!(pacer.pace(ms(4_132), now), now, "the live edge paces to now");
let jittered = pacer.pace(ms(4_165), now + Duration::from_millis(80));
assert_eq!(
jittered,
now + Duration::from_millis(33),
"a late frame keeps its media instant, not its arrival instant"
);
let reordered = pacer.pace(ms(4_099), now + Duration::from_millis(100));
assert_eq!(
reordered,
now - Duration::from_millis(33),
"a reordered frame can pace before the anchor"
);
}
#[test]
fn paces_across_timescales() {
let start = Instant::now();
let mut pacer = Pacer::default().with_lead(Duration::from_millis(500));
assert_eq!(pacer.pace(ms(0), start), start);
let media = Timestamp::from_scale(3_600, 90_000).unwrap();
assert_eq!(pacer.pace(media, start), start + Duration::from_millis(40));
assert_eq!(pacer.pace(ms(50), start), start + Duration::from_millis(50));
}
#[test]
fn huge_lead_does_not_overflow() {
let start = Instant::now();
let mut pacer = Pacer::default().with_lead(Duration::MAX);
assert_eq!(pacer.pace(ms(0), start), start);
assert_eq!(pacer.pace(ms(40), start), start + Duration::from_millis(40));
}
#[test]
fn absorbs_a_standing_delivery_lag() {
let start = Instant::now();
let mut pacer = Pacer::default().with_lead(Duration::from_millis(500));
assert_eq!(pacer.pace(ms(0), start), start, "the first frame anchors at now");
let now = start + Duration::from_millis(80);
assert_eq!(pacer.pace(ms(40), now), now, "a late frame still goes out at once");
assert_eq!(
pacer.pace(ms(80), now),
now + Duration::from_millis(40),
"the discovered lag becomes the sink's margin"
);
}
#[test]
fn absorbed_lag_is_capped_by_the_lead() {
let start = Instant::now();
let mut pacer = Pacer::default().with_lead(Duration::from_millis(50));
assert_eq!(pacer.pace(ms(0), start), start);
let now = start + Duration::from_millis(240);
assert_eq!(pacer.pace(ms(40), now), start + Duration::from_millis(90));
assert_eq!(
pacer.pace(ms(80), now),
start + Duration::from_millis(130),
"the anchor moved by the cap, not by the shortfall"
);
}
#[test]
fn zero_lead_absorbs_nothing() {
let start = Instant::now();
let mut pacer = Pacer::default();
assert_eq!(pacer.pace(ms(0), start), start);
let now = start + Duration::from_millis(80);
assert_eq!(pacer.pace(ms(40), now), start + Duration::from_millis(40));
assert_eq!(pacer.pace(ms(80), now), start + Duration::from_millis(80));
}
#[test]
fn hurry_makes_the_frame_the_live_edge() {
let start = Instant::now();
let mut pacer = Pacer::default().with_lead(Duration::from_millis(500));
assert_eq!(pacer.pace(ms(0), start), start);
let now = start + Duration::from_millis(100);
assert_eq!(pacer.hurry(ms(800), now), now);
assert_eq!(pacer.pace(ms(840), now), now + Duration::from_millis(40));
}
#[test]
fn the_delay_is_outside_the_lead_comparison() {
let start = Instant::now();
let mut pacer = Pacer::default().with_delay(Duration::from_millis(100));
assert_eq!(pacer.pace(ms(0), start), start + Duration::from_millis(100));
let now = start + Duration::from_millis(40);
assert_eq!(pacer.pace(ms(40), now), start + Duration::from_millis(140));
let now = start + Duration::from_millis(80);
assert_eq!(pacer.pace(ms(80), now), start + Duration::from_millis(180));
}
#[test]
fn an_earlier_arrival_re_anchors_and_keeps_the_delay() {
let start = Instant::now();
let mut pacer = Pacer::default().with_delay(Duration::from_millis(100));
assert_eq!(pacer.pace(ms(0), start), start + Duration::from_millis(100));
let now = start + Duration::from_millis(20);
assert_eq!(pacer.pace(ms(40), now), now + Duration::from_millis(100));
let late = now + Duration::from_millis(70);
assert_eq!(
pacer.pace(ms(80), late),
now + Duration::from_millis(140),
"a late arrival must not re-anchor"
);
}
#[test]
fn hurry_keeps_the_delay() {
let start = Instant::now();
let mut pacer = Pacer::default().with_delay(Duration::from_millis(100));
assert_eq!(pacer.hurry(ms(800), start), start + Duration::from_millis(100));
let now = start + Duration::from_millis(40);
assert_eq!(pacer.pace(ms(840), now), start + Duration::from_millis(140));
}
#[test]
fn due_reads_the_anchor_without_moving_it() {
let start = Instant::now();
let mut pacer = Pacer::default().with_delay(Duration::from_millis(100));
assert_eq!(pacer.due(ms(0)), None, "no anchor until the first frame");
pacer.pace(ms(0), start);
assert_eq!(pacer.due(ms(40)), Some(start + Duration::from_millis(140)));
assert_eq!(pacer.due(ms(40)), Some(start + Duration::from_millis(140)));
let now = start + Duration::from_millis(10);
pacer.hurry(ms(20), now);
assert_eq!(pacer.due(ms(40)), Some(now + Duration::from_millis(120)));
}
#[test]
fn lead_schedules_bursts_into_the_future() {
let start = Instant::now();
let mut pacer = Pacer::default().with_lead(Duration::from_millis(500));
assert_eq!(pacer.pace(ms(0), start), start);
assert_eq!(pacer.pace(ms(40), start), start + Duration::from_millis(40));
assert_eq!(pacer.pace(ms(500), start), start + Duration::from_millis(500));
assert_eq!(pacer.pace(ms(1_200), start), start);
assert_eq!(pacer.pace(ms(1_240), start), start + Duration::from_millis(40));
}
}