diff --git a/src/core/mod.rs b/src/core/mod.rs index 2238ed6..13e7d74 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -325,20 +325,28 @@ async fn run_core_loop( let mut interval = tokio::time::interval(Duration::from_millis(20)); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + // The 20ms mix cadence is fixed by playback, but pushing a + // level event every tick floods the UI runtime at ~50/sec. We + // peak-hold per-peer levels across this many ticks and emit + // once per window (~10/sec) — peak-hold so a brief transient + // inside the window still lights the speaking indicator. + const LEVEL_EMIT_TICKS: u32 = 5; + let mut level_peaks: HashMap = HashMap::new(); + let mut ticks_since_emit: u32 = 0; + loop { interval.tick().await; let current_volumes = peer_volumes_mixer.lock().await.clone(); - let mut active_levels = Vec::new(); let mut peer_frames = Vec::new(); { let mut guard = jitter_mixer.lock().await; for (&peer_id, buffer) in guard.iter_mut() { - // `None` means idle/buffering: contribute nothing - // and report a zero level so the UI shows idle. + // `None` means idle/buffering: contribute nothing, + // but keep a (zero) entry so the UI sees it idle. let Some(mut frame) = buffer.pop_frame() else { - active_levels.push((peer_id, 0.0)); + level_peaks.entry(peer_id).or_insert(0.0); continue; }; @@ -352,7 +360,8 @@ async fn run_core_loop( let sum_sq: f32 = frame.iter().map(|&x| (x as f32).powi(2)).sum(); let rms = (sum_sq / frame.len().max(1) as f32).sqrt(); let level = (rms / 32768.0).clamp(0.0, 1.0); - active_levels.push((peer_id, level)); + let peak = level_peaks.entry(peer_id).or_insert(0.0); + *peak = peak.max(level); peer_frames.push(frame); } @@ -379,7 +388,13 @@ async fn run_core_loop( break; } - let _ = ui_tx_mixer.send(UiEvent::AudioLevels(active_levels)).await; + // Emit coalesced peaks once per window, then reset. + ticks_since_emit += 1; + if ticks_since_emit >= LEVEL_EMIT_TICKS { + let levels: Vec<(EndpointId, f32)> = level_peaks.drain().collect(); + let _ = ui_tx_mixer.send(UiEvent::AudioLevels(levels)).await; + ticks_since_emit = 0; + } } });