lesavka/client/src/app/audio_recovery_config.rs

127 lines
4.2 KiB
Rust

#[cfg(not(coverage))]
fn audio_usb_auto_recover_enabled() -> bool {
std::env::var("LESAVKA_AUDIO_AUTO_RECOVER_USB")
.map(|raw| {
!matches!(
raw.trim().to_ascii_lowercase().as_str(),
"0" | "false" | "no" | "off"
)
})
.unwrap_or(false)
}
#[cfg(not(coverage))]
fn audio_usb_recover_after() -> u32 {
std::env::var("LESAVKA_AUDIO_AUTO_RECOVER_AFTER")
.ok()
.and_then(|raw| raw.parse::<u32>().ok())
.filter(|value| *value > 0)
.unwrap_or(3)
}
#[cfg(not(coverage))]
fn audio_usb_recover_cooldown() -> Duration {
let millis = std::env::var("LESAVKA_AUDIO_AUTO_RECOVER_COOLDOWN_MS")
.ok()
.and_then(|raw| raw.parse::<u64>().ok())
.unwrap_or(60_000);
Duration::from_millis(millis)
}
#[cfg(not(coverage))]
fn is_recoverable_remote_audio_error(message: &str) -> bool {
message.contains("remote speaker capture produced no audio samples")
|| message.contains("remote speaker capture stalled")
|| message.contains("remote speaker capture cadence is too low")
}
#[cfg(not(coverage))]
#[derive(Debug, Default)]
struct AudioFailureLogLimiter {
last_warn_at: Option<Instant>,
suppressed_repeats: u64,
last_message: String,
}
#[cfg(not(coverage))]
/// Rate-limit repeated remote audio failures so operators see state changes, not log floods.
impl AudioFailureLogLimiter {
/// Emit the first failure promptly, then aggregate identical repeats.
fn record(&mut self, context: &'static str, message: &str) {
let same_message = self.last_message == message;
let should_warn = !same_message
|| self
.last_warn_at
.map(|last| last.elapsed() >= AUDIO_FAILURE_WARN_INTERVAL)
.unwrap_or(true);
if should_warn {
tracing::warn!(
context,
suppressed_repeats = self.suppressed_repeats,
"❌🔊 audio stream unhealthy: {message}"
);
self.last_warn_at = Some(Instant::now());
self.suppressed_repeats = 0;
self.last_message.clear();
self.last_message.push_str(message);
} else {
self.suppressed_repeats = self.suppressed_repeats.saturating_add(1);
tracing::debug!(
context,
suppressed_repeats = self.suppressed_repeats,
"audio stream repeated unhealthy state suppressed from WARN noise: {message}"
);
}
}
}
#[cfg(not(coverage))]
const AUDIO_FAILURE_WARN_INTERVAL: Duration = Duration::from_secs(30);
pub(crate) fn keyboard_stream_report(
report: Result<KeyboardReport, BroadcastStreamRecvError>,
remote_capture_enabled: bool,
remote_capture_was_enabled: &mut bool,
) -> Option<KeyboardReport> {
if !remote_capture_enabled {
let emit_reset = *remote_capture_was_enabled;
*remote_capture_was_enabled = false;
return emit_reset.then_some(KeyboardReport { data: vec![0; 8] });
}
*remote_capture_was_enabled = true;
match report {
Ok(report) => Some(report),
Err(BroadcastStreamRecvError::Lagged(skipped)) => {
warn!(
skipped,
"⌨️ live keyboard stream lagged; sending a clean reset report before continuing"
);
Some(KeyboardReport { data: vec![0; 8] })
}
}
}
pub(crate) fn mouse_stream_report(
report: Result<MouseReport, BroadcastStreamRecvError>,
remote_capture_enabled: bool,
remote_capture_was_enabled: &mut bool,
) -> Option<MouseReport> {
if !remote_capture_enabled {
let emit_reset = *remote_capture_was_enabled;
*remote_capture_was_enabled = false;
return emit_reset.then_some(MouseReport { data: vec![0; 4] });
}
*remote_capture_was_enabled = true;
match report {
Ok(report) => Some(report),
Err(BroadcastStreamRecvError::Lagged(skipped)) => {
warn!(
skipped,
"🖱️ live mouse stream lagged; sending a neutral report before continuing"
);
Some(MouseReport { data: vec![0; 4] })
}
}
}