From 708bdf546863190e526fc3e5f4a80273daf9597c Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Fri, 20 Feb 2026 15:22:20 +0100 Subject: [PATCH] fix: auto-disable AEC during screen share to prevent Chrome voice ducking Chrome's AEC uses getDisplayMedia audio as a reference signal and aggressively ducks the microphone even when headphones are used. This adds a screenShareActive flag to AudioManager that forces echoCancellation off during screen share, plus Chromium-specific goog* constraints as belt-and-suspenders. --- packages/web/src/audio/AudioManager.ts | 33 +++++++++++++++++-- .../src/components/modals/UserSettings.tsx | 4 +-- .../web/src/components/voice/DmCallView.tsx | 3 ++ .../web/src/components/voice/StreamTile.tsx | 2 ++ .../src/components/voice/VoiceControlBar.tsx | 3 ++ .../src/components/voice/VoiceControls.tsx | 3 ++ packages/web/src/hooks/useLiveKit.ts | 14 ++++++-- 7 files changed, 56 insertions(+), 6 deletions(-) diff --git a/packages/web/src/audio/AudioManager.ts b/packages/web/src/audio/AudioManager.ts index 4756ebfa..b96fda03 100644 --- a/packages/web/src/audio/AudioManager.ts +++ b/packages/web/src/audio/AudioManager.ts @@ -17,6 +17,7 @@ export class AudioManager { private voiceEchoCancellation = true; private voiceNoiseSuppression = true; private voiceAutoGainControl = false; + private screenShareActive = false; private streamGeneration = 0; private constructor() {} @@ -141,13 +142,25 @@ export class AudioManager { this.currentStream.getTracks().forEach(t => t.stop()); } + // When screen sharing with audio, Chrome's AEC uses the getDisplayMedia + // audio as a reference signal and ducks the mic — even with headphones. + // Force AEC off during screen share to prevent this. + const effectiveEchoCancellation = this.screenShareActive ? false : this.voiceEchoCancellation; + const constraints = { audio: { deviceId: deviceId === 'default' ? undefined : { exact: deviceId }, - echoCancellation: this.voiceEchoCancellation, + echoCancellation: effectiveEchoCancellation, noiseSuppression: this.voiceNoiseSuppression, autoGainControl: this.voiceAutoGainControl, - } + // Chromium-specific constraints — belt-and-suspenders to ensure + // Chrome's internal audio engine respects the standard constraints. + googEchoCancellation: effectiveEchoCancellation, + googAutoGainControl: this.voiceAutoGainControl, + googNoiseSuppression: this.voiceNoiseSuppression, + googHighpassFilter: false, + googTypingNoiseDetection: false, + } as any }; this.currentStream = await navigator.mediaDevices.getUserMedia(constraints); @@ -197,6 +210,22 @@ export class AudioManager { } } + /** + * When screen sharing with audio is active, Chrome's AEC uses the screen + * share audio as a reference signal and aggressively ducks the microphone. + * Setting this flag forces echoCancellation OFF regardless of user preference, + * severing the software link that causes the ducking. + */ + setScreenShareActive(active: boolean) { + if (this.screenShareActive === active) return; + this.screenShareActive = active; + console.log(`[AudioManager] Screen share active: ${active} — ${active ? 'forcing AEC off' : 'restoring user AEC preference'}`); + if (this.currentStream) { + this.currentStream.getTracks().forEach(t => t.stop()); + this.currentStream = null; + } + } + getStreamGeneration(): number { return this.streamGeneration; } diff --git a/packages/web/src/components/modals/UserSettings.tsx b/packages/web/src/components/modals/UserSettings.tsx index df972876..c2f41cc2 100644 --- a/packages/web/src/components/modals/UserSettings.tsx +++ b/packages/web/src/components/modals/UserSettings.tsx @@ -127,13 +127,13 @@ export function UserSettingsModal() { Voice Processing

- Disable Auto Gain Control when streaming to prevent your browser from ducking your microphone. + Echo Cancellation is automatically disabled while you screen share to prevent Chrome from ducking your mic volume.

Echo Cancellation
-
Removes echo when using speakers
+
Removes echo when using speakers (auto-disabled during screen share)