fix(discord): pause the voice receiver during mixer-path speech playback - #110005
liuhao1024 wants to merge 1 commit into
Conversation
The mixer playback path never paused the voice receiver while the bot's
own TTS was playing, so the bot's audio returning through the user's
speakers was captured, transcribed, and injected as a fresh user
utterance — one transcription away from a self-reply loop. The legacy
one-shot path already pauses the receiver for exactly this reason
("echo prevention"); the mixer path simply lacked it.
Pause the receiver before mixer.play_speech() and resume it in a
finally once the speech drains (or the drain loop times out),
mirroring the legacy path.
|
Comment assumes head KEEP — pause Discord mixer-path receiver during speech (closes #109998 echo axis)
CHECK
One-click close / ordering
Landing-evidence
|
|
Comment assumes head KEEP — pause Discord mixer-path receiver during speech (closes #109998 echo axis)
CHECK
One-click close / ordering
Landing-evidence
|
|
Thanks for the KEEP triage. Confirming the verdict is still anchored: head remains On the CHECK item (mergeable but empty live status/check-runs): all three workflows on this head sit in Ordering notes acknowledged: if #111090 lands first I will cherry-pick this pause onto its merge tip (and cite the SHA here), and the no-fold / no-one-click-close list stands. |
What does this PR do?
On the Discord voice mixer playback path, the adapter never paused the voice receiver while the bot's own TTS was playing, so the bot's audio returning through the user's speakers was captured, transcribed, and injected as a fresh "user utterance" — one transcription away from a self-reply loop. The legacy one-shot path already pauses the receiver for exactly this reason (its comment says "echo prevention"); the mixer path simply lacked the same guard.
This PR pauses the receiver before
mixer.play_speech()and resumes it in afinallyonce the speech drains (or the drain loop times out), mirroring the legacy path.VoiceReceiver.pause()is a pure drop-packets flag (_on_packetreturns early while paused), so no stale audio is buffered or replayed after resume.Related Issue
Fixes #109998
Type of Change
Changes Made
plugins/platforms/discord/adapter.py— inplay_in_voice_channel(), wrap the mixer-path speech playback (play + drain loop) withreceiver.pause()beforeplay_speech()andreceiver.resume()in afinally, matching the legacy one-shot path's echo-prevention pattern. Net change is confined to theif pcm:block; the decode-failure fallback to legacy playback is untouched.tests/gateway/test_discord_voice_mixer.py— two regression tests:test_mixer_path_pauses_receiver_while_speakingasserts the exactpause → play_speech → resumeordering on the happy path, andtest_mixer_path_resumes_receiver_after_playback_timeoutasserts resume still happens when the drain loop breaks out via the playback timeout.How to Test
pytest tests/gateway/test_discord_voice_mixer.py -q→ 11 passed (9 pre-existing + 2 new).adapter.pyhunk makes both new tests fail (pause/resumenever called), confirming they exercise the fix.pytest tests/gateway/test_voice_command.py -q→ 77 passed.play_speech→ drain window, so nothing the bot plays into the channel can re-enter the capture buffer as input.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests pass (targeted:tests/gateway/test_discord_voice_mixer.py11 passed,tests/gateway/test_voice_command.py77 passed, ruff clean on both changed files)Documentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/AFor New Skills
N/A
Screenshots / Logs