Conversation
…luded useMicRecorder polled its level/silence meter from requestAnimationFrame. Chromium never runs rAF callbacks for a minimized or occluded window, so a voice turn started in the background (e.g. by the wake word) never detected end of speech and never submitted until the window was brought back. Drive the meter from a ScriptProcessorNode on the recording's own audio graph instead - the same pattern wake-client-capture already relies on to keep streaming wake audio in the background. Levels stay on the old analyser's byte time-domain scale, so silenceLevel thresholds keep their meaning. The rAF loop remains as a fallback where ScriptProcessorNode is unavailable.
Contributor
Competing with #110230 for the same root cause (rAF frozen while the window is occluded). This PR uses a ScriptProcessorNode on the recording audio graph; #110230 uses setTimeout polling plus broader hands-free changes. Flagging for maintainers to pick one or fold this into #110230 as the author offered. |
Contributor
|
Comment assumes head KEEP — Desktop occluded mic metering (use-mic-recorder)
CHECK
One-click close / ordering
Landing-evidence
|
This was referenced Sep 15, 2026
Contributor
|
Comment assumes head KEEP — Desktop occluded mic metering (use-mic-recorder)
CHECK
One-click close / ordering
Landing-evidence
|
This was referenced Sep 15, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Voice turns started while Hermes Desktop is minimized or occluded — typically by the wake word — never finished:
useMicRecorderpolled its level/silence meter fromrequestAnimationFrame, and Chromium doesn't run rAF callbacks for a hidden window. Recording kept going until the window came back to the foreground.This drives the meter from a
ScriptProcessorNodeon the recording's own audio graph — the patternwake-client-capture.tsalready relies on to keep streaming wake audio in the background. Levels stay on the old analyser's scale (float RMS × 128 / 42 = byte time-domain RMS / 42), sosilenceLevelthresholds keep their meaning. The rAF loop remains as a fallback wherecreateScriptProcessoris unavailable.Why not timers: #110230 fixes the same root cause with
setTimeout(40). Measured on Windows 11 (table below), timers in a hidden window were still throttled to ~1 Hz despite an active mic capture, so every background turn ended 0.8–1.65 s late; the audio callback fired on schedule. This PR is intentionally narrower — onlyuse-mic-recorder, no behaviour change beyond the fix — and adds a regression test. Happy to fold it into #110230 if maintainers prefer one PR.Related Issue
No separate issue — the root cause is described in #110230. Related: #110230
Type of Change
Changes Made
apps/desktop/src/app/chat/composer/hooks/use-mic-recorder.ts— level/silence evaluation moved intomeasure(), driven byScriptProcessorNode.onaudioprocess; the node is disconnected incleanup(); the analyser + rAF loop is kept as a fallback.apps/desktop/src/app/chat/composer/hooks/use-mic-recorder.test.ts(new) — window hidden, rAF inert, audio fed through a fake graph: end of speech, idle timeout, level scale, no metering after stop.How to Test
cd apps/desktop && npx vitest run --project ui src/app/chat/composer/hooks/use-mic-recorder.test.ts— against currentmain, 3 of 4 tests fail (onSilencenever called, level frozen at 0); with this change 4/4 pass.mainnothing happens until the window is restored.Checklist
pytest tests/ -q: this PR changes no Python. Ran the full suite anyway withscripts/run_tests.shon Windows 11 with the CI extras: 47,629 passed, 839 failed, 5 files not collected, all in areas this PR does not touch (Windows-specific MCP OAuth, terminal/process and file-read tests). CI runs this suite on Linux.npm run check:lint(0 errors);vitest --project ui7,646 passed, 6 failed, and the same 6 fail with this change reverted (locale-dependent formatting, vitest suite fails under non-en-US system locale (bare.toLocaleString()/Intl.NumberFormat(undefined, ...)) #98849, andvoice-prefsspying on jsdom's Storage instance);vitest --project electron2,155 passed, 39 failed, all POSIX/macOS-specific tests that don't import the changed hook. The composer and voice suites, including the new test, pass.cli-config.yaml.example/ CONTRIBUTING / tool schemas — N/A · [x] Cross-platform:ScriptProcessorNodeis already used by wake capture on all desktop platformsScreenshots / Logs
Standalone Electron repro (Electron 40.10.2 / Chrome 144, default
backgroundThrottling), same method as above:main)setTimeout(40)onaudioprocess(this PR)Patched desktop, window minimized throughout (Windows 11):
Known, not changed here:
apps/desktop/src/lib/voice-barge-in.tsalso drives barge-in detection from rAF; I haven't reproduced that path.