From edcdf8b207a976dd97a62dacc3a40ae5a237b031 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:27:49 +0100 Subject: [PATCH] fix: defer AEC toggle until after screen picker resolves to prevent mic dropout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AudioManager.setScreenShareActive(true) was firing before the browser's screen picker, killing the mic stream. The picker suspends getUserMedia while its secure overlay is open, trapping the mic in a dead state for 5-30s. Now the AEC rebuild fires after the track is acquired — mic stays alive during the picker. --- packages/web/src/utils/screenShare.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/web/src/utils/screenShare.ts b/packages/web/src/utils/screenShare.ts index 8033de9d..2d90ac28 100644 --- a/packages/web/src/utils/screenShare.ts +++ b/packages/web/src/utils/screenShare.ts @@ -70,11 +70,12 @@ export async function startScreenShare(room: Room): Promise { const { videoQuality } = useVoiceStore.getState(); const preset = SCREEN_QUALITY_MAP[videoQuality] || AUTO_PRESET; - // Notify AudioManager BEFORE enabling screen share so the mic track - // gets republished with AEC off, preventing Chrome's ducking. - AudioManager.getInstance().setScreenShareActive(true); - try { + // NOTE: We do NOT call AudioManager.setScreenShareActive(true) before the + // browser picker. The picker suspends getUserMedia while its secure overlay + // is open — if we killed the mic stream here (to rebuild without AEC), the + // mic would stay dead until the user picks a screen (5-30s of silence). + // Instead we defer the AEC toggle to after the track is acquired. const track = await room.localParticipant.setScreenShareEnabled(true, { audio: true, resolution: preset.resolution, @@ -88,10 +89,14 @@ export async function startScreenShare(room: Room): Promise { if (!track) { // User cancelled the screen picker - AudioManager.getInstance().setScreenShareActive(false); return false; } + // NOW that the track is acquired and published, rebuild the mic without AEC. + // Chrome's AEC uses screen share audio as a reference signal and ducks the mic; + // this severs that link. The mic is dead for ~50ms during the rebuild — imperceptible. + AudioManager.getInstance().setScreenShareActive(true); + // Tell the encoder to optimize for motion (more P-frames, fewer I-frames) // Must be set BEFORE the overdrive timer so the encoder knows from frame 1 const screenPub = room.localParticipant.getTrackPublications()