use std::time::{Duration, Instant};
use crate::media::AdaptiveBitrate;
const DEFAULT_MIN_INTERVAL: Duration = Duration::from_millis(200);
const DEFAULT_REL_THRESHOLD: f32 = 0.05;
pub struct BitrateBridge {
last_applied_bps: Option<u32>,
last_applied_at: Option<Instant>,
min_interval: Duration,
rel_threshold: f32,
}
impl BitrateBridge {
pub fn new() -> Self {
Self {
last_applied_bps: None,
last_applied_at: None,
min_interval: DEFAULT_MIN_INTERVAL,
rel_threshold: DEFAULT_REL_THRESHOLD,
}
}
pub fn poll(
&mut self,
target_bps: u64,
codec: &mut dyn AdaptiveBitrate,
now: Instant,
) -> Result<bool, String> {
let new_bps: u32 = target_bps.min(u32::MAX as u64) as u32;
let should_apply = match self.last_applied_bps {
None => true,
Some(0) => true,
Some(last) => {
let abs_delta = new_bps.abs_diff(last) as f32;
let rel_delta = abs_delta / last as f32;
let elapsed = self
.last_applied_at
.map(|t| now.duration_since(t))
.unwrap_or(Duration::ZERO);
rel_delta >= self.rel_threshold && elapsed >= self.min_interval
}
};
if !should_apply {
return Ok(false);
}
codec.set_target_bitrate_bps(new_bps)?;
self.last_applied_bps = Some(new_bps);
self.last_applied_at = Some(now);
Ok(true)
}
}
impl Default for BitrateBridge {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockAdaptive {
last_set: Option<u32>,
force_err: bool,
}
impl MockAdaptive {
fn new() -> Self {
Self {
last_set: None,
force_err: false,
}
}
}
impl AdaptiveBitrate for MockAdaptive {
fn set_target_bitrate_bps(&mut self, bps: u32) -> Result<(), String> {
if self.force_err {
return Err("forced".to_string());
}
self.last_set = Some(bps);
Ok(())
}
}
#[test]
fn applies_first_update() {
let mut bridge = BitrateBridge::new();
let mut mock = MockAdaptive::new();
let t0 = Instant::now();
let applied = bridge.poll(100_000, &mut mock, t0).unwrap();
assert!(applied);
assert_eq!(mock.last_set, Some(100_000));
}
#[test]
fn suppresses_subthreshold_change() {
let mut bridge = BitrateBridge::new();
let mut mock = MockAdaptive::new();
let t0 = Instant::now();
bridge.poll(100_000, &mut mock, t0).unwrap();
let applied = bridge
.poll(102_000, &mut mock, t0 + Duration::from_secs(1))
.unwrap();
assert!(!applied);
assert_eq!(mock.last_set, Some(100_000));
}
#[test]
fn applies_supra_threshold_change() {
let mut bridge = BitrateBridge::new();
let mut mock = MockAdaptive::new();
let t0 = Instant::now();
bridge.poll(100_000, &mut mock, t0).unwrap();
let applied = bridge
.poll(110_000, &mut mock, t0 + Duration::from_secs(1))
.unwrap();
assert!(applied);
assert_eq!(mock.last_set, Some(110_000));
}
#[test]
fn respects_min_interval() {
let mut bridge = BitrateBridge::new();
let mut mock = MockAdaptive::new();
let t0 = Instant::now();
bridge.poll(100_000, &mut mock, t0).unwrap();
let too_soon = bridge
.poll(200_000, &mut mock, t0 + Duration::from_millis(50))
.unwrap();
assert!(!too_soon);
assert_eq!(mock.last_set, Some(100_000));
let ok_now = bridge
.poll(200_000, &mut mock, t0 + Duration::from_millis(250))
.unwrap();
assert!(ok_now);
assert_eq!(mock.last_set, Some(200_000));
}
#[test]
fn monotonic_through_repeated_updates() {
let mut bridge = BitrateBridge::new();
let mut mock = MockAdaptive::new();
let t0 = Instant::now();
let values = [120_000_u64, 80_000, 90_000, 150_000];
for (i, v) in values.iter().enumerate() {
let now = t0 + Duration::from_secs(i as u64);
let applied = bridge.poll(*v, &mut mock, now).unwrap();
assert!(applied, "expected apply at step {i} (value={v})");
}
assert_eq!(mock.last_set, Some(150_000));
}
#[test]
fn surfaces_codec_error() {
let mut bridge = BitrateBridge::new();
let mut mock = MockAdaptive {
last_set: None,
force_err: true,
};
let t0 = Instant::now();
let err = bridge.poll(100_000, &mut mock, t0).unwrap_err();
assert_eq!(err, "forced");
assert_eq!(mock.last_set, None);
mock.force_err = false;
let applied = bridge
.poll(100_000, &mut mock, t0 + Duration::from_secs(1))
.unwrap();
assert!(applied);
assert_eq!(mock.last_set, Some(100_000));
}
#[test]
fn defensive_zero_baseline_applies() {
let mut bridge = BitrateBridge::new();
let mut mock = MockAdaptive::new();
let t0 = Instant::now();
let applied = bridge.poll(0, &mut mock, t0).unwrap();
assert!(applied);
assert_eq!(mock.last_set, Some(0));
let applied = bridge
.poll(50_000, &mut mock, t0 + Duration::from_millis(10))
.unwrap();
assert!(applied);
assert_eq!(mock.last_set, Some(50_000));
}
#[test]
fn slow_drift_eventually_applies() {
let mut bridge = BitrateBridge::new();
let mut mock = MockAdaptive::new();
let t0 = Instant::now();
bridge.poll(100_000, &mut mock, t0).unwrap();
let drifts = [
(102_000_u64, Duration::from_millis(250), false),
(104_040, Duration::from_millis(500), false),
(106_120, Duration::from_millis(750), true),
(108_242, Duration::from_millis(1000), false),
];
for (value, dt, expect_applied) in drifts {
let applied = bridge.poll(value, &mut mock, t0 + dt).unwrap();
assert_eq!(
applied, expect_applied,
"value={value} dt={dt:?} expected_applied={expect_applied}"
);
}
assert_eq!(mock.last_set, Some(106_120));
}
}