Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/web/__tests__/unit/embed-playback-speed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ vi.mock("@cap/env", () => ({
vi.mock("@cap/ui", () => ({ Logo: () => null }));
vi.mock("@cap/utils", () => ({ userIsPro: () => true }));
vi.mock("@cap/web-backend", () => ({
ImageUploads: Context.GenericTag("ImageUploads"),
VideosPolicy: Context.GenericTag("EmbedTestPolicy"),
provideOptionalAuth: <A, E, R>(effect: Effect.Effect<A, E, R>) => effect,
resolveEffectiveVideoRules: () => ({ settings: {} }),
Expand All @@ -24,6 +25,9 @@ vi.mock("@/lib/server", () => ({
Effect.provideService(Context.GenericTag("EmbedTestPolicy"), {
canView: () => Effect.void,
}),
Effect.provideService(Context.GenericTag("ImageUploads"), {
resolveImageUrl: () => Effect.succeed(""),
}),
Effect.runPromise,
),
}));
Expand Down
2 changes: 1 addition & 1 deletion apps/web/lib/mcp-card-html.json

Large diffs are not rendered by default.

57 changes: 56 additions & 1 deletion crates/recording/src/sources/microphone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ fn should_fabricate_stall_silence(stall_duration: Duration, keepalive_after: Dur
stall_duration >= keepalive_after
}

fn is_audio_starved(captured_delta: u64, silence_delta: u64, is_muted: bool) -> bool {
if is_muted {
return false;
}
let total = captured_delta.saturating_add(silence_delta);
if total == 0 || silence_delta == 0 {
return false;
}
(100.0 * silence_delta as f64 / total as f64) >= 80.0
}

struct ReconnectAttemptGuard(Arc<AtomicBool>);

impl Drop for ReconnectAttemptGuard {
Expand Down Expand Up @@ -683,15 +694,20 @@ impl AudioSource for Microphone {
crate::output_pipeline::spawn_capture_task({
let cancel = cancel.clone();
let health_tx = health_tx.clone();
let recording_muted = recording_muted.clone();
async move {
let frame_counter = mic_frame_counter;
let drop_counter = mic_drop_counter;
let silence_counter = mic_silence_counter;
let mut last_log = Instant::now();
let mut prev_captured: u64 = 0;
let mut prev_dropped: u64 = 0;
let mut prev_silence: u64 = 0;
let mut stale_count: u32 = 0;
let mut high_drop_intervals: u32 = 0;
let mut high_silence_intervals: u32 = 0;
let mut silence_stall_start: Option<Instant> = None;
let mut silence_stall_emitted = false;
loop {
tokio::select! {
biased;
Expand All @@ -714,9 +730,13 @@ impl AudioSource for Microphone {

let captured_delta = captured.saturating_sub(prev_captured);
let dropped_delta = dropped.saturating_sub(prev_dropped);
let data_changed = captured != prev_captured || dropped != prev_dropped;
let silence_delta = silence.saturating_sub(prev_silence);
let data_changed = captured != prev_captured
|| dropped != prev_dropped
|| silence != prev_silence;
prev_captured = captured;
prev_dropped = dropped;
prev_silence = silence;

// Surface a *sustained* high drop rate as a health event, like
// every other source/muxer. The case that matters most is a
Expand Down Expand Up @@ -746,6 +766,31 @@ impl AudioSource for Microphone {
);
}

let is_muted = recording_muted.load(Ordering::Relaxed);
if is_audio_starved(captured_delta, silence_delta, is_muted) {
if silence_stall_start.is_none() {
silence_stall_start = Some(Instant::now());
}
high_silence_intervals = high_silence_intervals.saturating_add(1);
} else {
high_silence_intervals = 0;
silence_stall_start = None;
silence_stall_emitted = false;
}
if high_silence_intervals >= 2 && !silence_stall_emitted {
silence_stall_emitted = true;
let waited_ms = silence_stall_start
.map(|s| s.elapsed().as_millis() as u64)
.unwrap_or(0);
emit_health(
&health_tx,
PipelineHealthEvent::Stalled {
source: "microphone".to_string(),
waited_ms,
},
);
}

if !data_changed {
stale_count = stale_count.saturating_add(1);
} else {
Expand Down Expand Up @@ -1336,6 +1381,16 @@ mod tests {

assert!(prev.is_some(), "resampler produced no frames");
}

#[test]
fn audio_starvation_detects_sustained_silence_dominance() {
assert!(!is_audio_starved(100, 0, false));
assert!(!is_audio_starved(100, 10, false));
assert!(is_audio_starved(0, 100, false));
assert!(is_audio_starved(5, 95, false));
assert!(!is_audio_starved(0, 100, true));
assert!(!is_audio_starved(0, 0, false));
}
}

#[cfg(all(test, target_os = "linux"))]
Expand Down
Loading