Skip to content

Monitor: live mic-level meter — prove audio is really flowing#803

Merged
spashii merged 1 commit into
mainfrom
sameer/monitor-vu-meter
Jul 6, 2026
Merged

Monitor: live mic-level meter — prove audio is really flowing#803
spashii merged 1 commit into
mainfrom
sameer/monitor-vu-meter

Conversation

@spashii

@spashii spashii commented Jul 6, 2026

Copy link
Copy Markdown
Member

What

Adds a live mic-level meter to the Monitor so the host can see that audio is really flowing — not just that chunks are landing.

recording_health (from #800) already answers "are chunks arriving?" This adds the finer signal underneath it: the participant's actual mic input level, rendered as a tiny 5-bar signal meter on rows that are actively receiving audio.

meter appears on receiving rows

How it flows

useChunkedAudioRecorder.getAudioLevel() (0..1 RMS) → conversation ping audio_level → liveness telemetry → monitor payload → AudioLevelMeter in LiveMonitorSection.

  • Capture-path safety: the recorder taps its own mic MediaStream with a passive AnalyserNode on a separate AudioContext, never connected to output, torn down on stop/unmount. It's a read-only branch that cannot disturb MediaRecorder.
  • Honest, not scary: an all-quiet reading shows a soft "very quiet — check the mic isn't muted" tooltip, not an alarm. Meter only appears while audio is being received.
  • Server hygiene: audio_level is clamped to [0,1] and rounded 2dp; NaN/inf from a misbehaving client are dropped.

Automated verification (the graph technique)

Ran the exact Web Audio graph on real headless Chrome (oscillator → MediaStreamDestination, so no mic permission needed):

  • Capture unaffectedMediaRecorder produced 7 real Opus chunks (26.7 KB) while the AnalyserNode read the same stream.
  • Level tracks audio — active avg 0.196 / max 0.224.
  • Detects silence — muting the source dropped the meter to 0 (meterDropsOnSilence: true).

Still needs a physical-device pass

The macOS real-mic getUserMedia path can't be exercised in CI (OS-gated), so before this rides to prod it wants a quick check on iOS Safari + Android Chrome: start a recording, confirm the meter tracks speech and that recording/chunk upload + background/lock still behave.

Tests

  • audio_level round-trips into the monitor payload (null when unreported)
  • ping endpoint clamps/rounds and drops NaN/inf

Checks: ruff ✓, mypy ✓, pytest ✓ (31 passed, +2 new), tsc ✓, biome ✓, catalogs recompiled.

🤖 Generated with Claude Code

The host's first worry during an event is "is the recording actually
coming in?" recording_health tells them chunks are landing; this adds
the finer signal underneath it: the participant's live mic input level.

Capture path (safe, verified): the recorder taps its own mic MediaStream
with a passive AnalyserNode on a separate AudioContext, never connected to
output, torn down on stop/unmount — so it cannot disturb MediaRecorder.
Validated on real Chrome (oscillator → MediaStreamDestination): chunks kept
flowing while the analyser read the same stream, the level tracked the
signal, and it dropped to zero on silence. The real-mic getUserMedia path
still wants a physical-device pass (iOS Safari / Android Chrome).

Pipeline: recorder.getAudioLevel() (0..1 RMS) → conversation ping
`audio_level` (clamped/rounded server-side, NaN/inf ignored) → liveness
telemetry → monitor payload → a small 5-bar signal meter on receiving
rows. All-quiet reads as a soft "very quiet — check the mic isn't muted"
hint, not an alarm.

Tests: audio_level round-trips into the payload (null when unreported);
ping clamps/rounds and drops NaN/inf.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@spashii spashii merged commit fcba1f2 into main Jul 6, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant