fix: stop voice mode listening after leaving the chat page (#30405) (#30417)

Leaving the chat with voice mode active (Admin Panel, Workspace, Notes) left a hidden recorder running in the tab. The voice mode flag stays on when the chat page unmounts, so the overlay's teardown restarted recording on the destroyed component, which kept transcribing speech and sending prompts from other pages. Returning to the chat also opened an extra microphone stream that was never closed.

The overlay now marks itself destroyed on teardown and never starts or restarts recording after that, so leaving the chat shuts the microphone down fully.

Resetting the voice mode flag in the chat page cleanup was considered, but it changes behaviour for the controls panel on return and still runs after the overlay's own teardown.

Verified in Chromium with a fake microphone (14s per phase, transcription requests / live mic tracks):

| Phase | before | after |
|---|---|---|
| Call on chat | 3 / 1 | 3 / 1 |
| After moving to Workspace | 3 / 1 | 0 / 0 |
| Voice mode reopened | 3 / 3 | 3 / 1 |

Fixes #30405
This commit is contained in:
Classic298
2026-09-23 23:30:30 -04:00
committed by GitHub
parent b3bb82e776
commit 869a5b1035
@@ -44,6 +44,7 @@
let mediaRecorder;
let audioStream = null;
let audioChunks = [];
let destroyed = false;
let videoInputDevices = [];
let selectedVideoInputDeviceId = null;
@@ -184,7 +185,8 @@
};
const stopRecordingCallback = async (_continue = true) => {
if ($showCallOverlay) {
// $showCallOverlay stays true when the chat page unmounts
if ($showCallOverlay && !destroyed) {
console.log('%c%s', 'color: red; font-size: 20px;', '🚨 stopRecordingCallback 🚨');
// deep copy the audioChunks array
@@ -231,7 +233,7 @@
};
const startRecording = async () => {
if ($showCallOverlay) {
if ($showCallOverlay && !destroyed) {
if (!audioStream) {
audioStream = await navigator.mediaDevices.getUserMedia({
audio: {
@@ -776,6 +778,7 @@
});
onDestroy(async () => {
destroyed = true;
await stopAllAudio();
await stopRecordingCallback(false);
await stopCamera();