use std::time::Duration;
use super::streaming::AbrVariant;
#[derive(Debug, Clone)]
pub struct BbaParams {
pub buffer_capacity: Duration,
pub reservoir: Duration,
pub cushion: Duration,
}
impl Default for BbaParams {
fn default() -> Self {
Self {
buffer_capacity: Duration::from_secs(30),
reservoir: Duration::from_secs(10),
cushion: Duration::from_secs(20),
}
}
}
impl BbaParams {
#[must_use]
pub fn low_latency() -> Self {
Self {
buffer_capacity: Duration::from_secs(10),
reservoir: Duration::from_secs(2),
cushion: Duration::from_secs(8),
}
}
#[must_use]
pub fn cushion_upper(&self) -> Duration {
self.reservoir.saturating_add(self.cushion)
}
#[must_use]
pub fn normalise(&self, buffer_level: Duration) -> f64 {
let cap = self.buffer_capacity.as_secs_f64();
if cap <= 0.0 {
return 1.0; }
(buffer_level.as_secs_f64() / cap).clamp(0.0, 1.0)
}
}
#[must_use]
pub fn select_variant(
buffer_level: Duration,
params: &BbaParams,
variants: &[AbrVariant],
) -> usize {
match variants.len() {
0 => return 0,
1 => return 0,
_ => {}
}
let cap_secs = params.buffer_capacity.as_secs_f64();
if cap_secs <= 0.0 {
return variants.len() - 1;
}
let norm = (buffer_level.as_secs_f64() / cap_secs).clamp(0.0, 1.0);
let reservoir_norm = params.reservoir.as_secs_f64() / cap_secs;
let cushion_upper_norm = params.cushion_upper().as_secs_f64() / cap_secs;
if norm <= reservoir_norm {
return 0;
}
if norm >= cushion_upper_norm {
return variants.len() - 1;
}
let denom = cushion_upper_norm - reservoir_norm;
let cushion_pos = if denom > 0.0 {
((norm - reservoir_norm) / denom).clamp(0.0, 1.0)
} else {
0.0
};
let max_idx = variants.len() - 1;
let idx = (cushion_pos * max_idx as f64).round() as usize;
idx.min(max_idx)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_variant(bandwidth: u64) -> AbrVariant {
AbrVariant {
bandwidth,
width: 0,
height: 0,
codecs: String::new(),
uri: String::new(),
name: String::new(),
frame_rate: None,
hdcp_level: None,
}
}
fn variants_3() -> Vec<AbrVariant> {
vec![
make_variant(500_000),
make_variant(1_500_000),
make_variant(4_000_000),
]
}
fn params() -> BbaParams {
BbaParams::default() }
#[test]
fn reservoir_selects_lowest() {
assert_eq!(
select_variant(Duration::from_secs(5), ¶ms(), &variants_3()),
0
);
}
#[test]
fn zero_buffer_selects_lowest() {
assert_eq!(select_variant(Duration::ZERO, ¶ms(), &variants_3()), 0);
}
#[test]
fn exact_reservoir_boundary_selects_lowest() {
assert_eq!(
select_variant(Duration::from_secs(10), ¶ms(), &variants_3()),
0
);
}
#[test]
fn above_cushion_selects_highest() {
assert_eq!(
select_variant(Duration::from_secs(30), ¶ms(), &variants_3()),
2
);
}
#[test]
fn overflow_buffer_selects_highest() {
assert_eq!(
select_variant(Duration::from_secs(60), ¶ms(), &variants_3()),
2
);
}
#[test]
fn cushion_midpoint_selects_middle() {
assert_eq!(
select_variant(Duration::from_secs(20), ¶ms(), &variants_3()),
1
);
}
#[test]
fn cushion_lower_quarter_selects_lowest() {
let buf = Duration::from_millis(12_500);
let idx = select_variant(buf, ¶ms(), &variants_3());
assert!(
idx <= 1,
"lower cushion should not jump to highest: got {idx}"
);
}
#[test]
fn cushion_upper_quarter_selects_high() {
let buf = Duration::from_millis(27_500);
let idx = select_variant(buf, ¶ms(), &variants_3());
assert!(idx >= 1, "upper cushion should not stay lowest: got {idx}");
}
#[test]
fn single_variant_always_returns_zero() {
let v = vec![make_variant(1_000_000)];
for secs in [0u64, 5, 15, 30, 60] {
assert_eq!(
select_variant(Duration::from_secs(secs), ¶ms(), &v),
0,
"single variant at {secs}s should be 0"
);
}
}
#[test]
fn empty_variants_returns_zero() {
assert_eq!(select_variant(Duration::from_secs(15), ¶ms(), &[]), 0);
}
#[test]
fn cushion_upper_is_reservoir_plus_cushion() {
let p = params();
assert_eq!(p.cushion_upper(), Duration::from_secs(30));
}
#[test]
fn normalise_clamps_below_zero() {
let p = params();
assert!((p.normalise(Duration::ZERO) - 0.0).abs() < f64::EPSILON);
}
#[test]
fn normalise_clamps_above_capacity() {
let p = params();
assert!((p.normalise(Duration::from_secs(100)) - 1.0).abs() < f64::EPSILON);
}
#[test]
fn low_latency_params_have_smaller_buffers() {
let ll = BbaParams::low_latency();
let def = BbaParams::default();
assert!(ll.buffer_capacity < def.buffer_capacity);
assert!(ll.reservoir < def.reservoir);
assert!(ll.cushion < def.cushion);
}
#[test]
fn zero_capacity_selects_highest() {
let p = BbaParams {
buffer_capacity: Duration::ZERO,
reservoir: Duration::from_secs(5),
cushion: Duration::from_secs(10),
};
assert_eq!(select_variant(Duration::from_secs(5), &p, &variants_3()), 2);
}
}