use std::time::Duration; use tokio::time::Instant; use super::UpstreamMediaKind; use crate::calibration::{FACTORY_MJPEG_AUDIO_OFFSET_US, FACTORY_MJPEG_VIDEO_OFFSET_US}; pub(super) fn upstream_timing_trace_enabled() -> bool { std::env::var("LESAVKA_UPSTREAM_TIMING_TRACE") .ok() .map(|value| { let trimmed = value.trim(); !(trimmed.eq_ignore_ascii_case("0") || trimmed.eq_ignore_ascii_case("false") || trimmed.eq_ignore_ascii_case("no") || trimmed.eq_ignore_ascii_case("off")) }) .unwrap_or(false) } pub(super) fn upstream_playout_delay() -> Duration { let delay_ms = std::env::var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS") .ok() .and_then(|value| value.trim().parse::().ok()) .unwrap_or(350); Duration::from_millis(delay_ms) } pub(super) fn upstream_max_live_lag() -> Duration { let lag_ms = std::env::var("LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS") .ok() .and_then(|value| value.trim().parse::().ok()) .unwrap_or(1_000); Duration::from_millis(lag_ms.max(1)) } pub(super) fn upstream_startup_timeout() -> Duration { let timeout_ms = std::env::var("LESAVKA_UPSTREAM_STARTUP_TIMEOUT_MS") .ok() .and_then(|value| value.trim().parse::().ok()) .unwrap_or(60_000); Duration::from_millis(timeout_ms.max(1)) } pub(super) fn upstream_require_paired_startup() -> bool { std::env::var("LESAVKA_UPSTREAM_REQUIRE_PAIRED_STARTUP") .ok() .map(|value| { let trimmed = value.trim(); !(trimmed.eq_ignore_ascii_case("0") || trimmed.eq_ignore_ascii_case("false") || trimmed.eq_ignore_ascii_case("no") || trimmed.eq_ignore_ascii_case("off")) }) .unwrap_or(true) } pub(super) fn upstream_playout_offset_us(kind: UpstreamMediaKind) -> i64 { let name = match kind { UpstreamMediaKind::Camera => "LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US", UpstreamMediaKind::Microphone => "LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US", }; let default_offset_us = match kind { UpstreamMediaKind::Camera => FACTORY_MJPEG_VIDEO_OFFSET_US, // 0.17 keeps shipped offsets small: the planner owns freshness and // pairing, while calibration only handles sub-frame trim. UpstreamMediaKind::Microphone => FACTORY_MJPEG_AUDIO_OFFSET_US, }; std::env::var(name) .ok() .and_then(|value| value.trim().parse::().ok()) .unwrap_or(default_offset_us) } pub(super) fn upstream_pairing_master_slack() -> Duration { let slack_us = std::env::var("LESAVKA_UPSTREAM_PAIR_SLACK_US") .ok() .and_then(|value| value.trim().parse::().ok()) .unwrap_or(80_000); Duration::from_micros(slack_us) } pub(super) fn upstream_reanchor_late_threshold(playout_delay: Duration) -> Duration { if let Some(override_ms) = std::env::var("LESAVKA_UPSTREAM_REANCHOR_LATE_MS") .ok() .and_then(|value| value.trim().parse::().ok()) { return Duration::from_millis(override_ms); } let default_ms = (playout_delay.as_millis().min(u64::MAX as u128) as u64) .saturating_div(2) .max(100); Duration::from_millis(default_ms) } pub(super) fn upstream_camera_startup_grace_us() -> u64 { std::env::var("LESAVKA_UPSTREAM_CAMERA_STARTUP_GRACE_MS") .ok() .and_then(|value| value.trim().parse::().ok()) .unwrap_or(if cfg!(test) { 0 } else { 250 }) .saturating_mul(1_000) } pub(super) fn apply_playout_offset(base: Instant, offset_us: i64) -> Instant { if offset_us >= 0 { base + Duration::from_micros(offset_us as u64) } else { base.checked_sub(Duration::from_micros(offset_us.unsigned_abs())) .unwrap_or(base) } }