feat!: [Experimental hotfix 2] Fix Audio stream handling on windows electron

Experimental fix for solving replacement of mic audio when enabling screenshare audio on windows electron

On Windows Electron, startWithElectronDesktopCapturer uses getUserMedia({ chromeMediaSource: 'desktop' }) for both video and audio. This getUserMedia desktop audio call can interfere with / replace the existing mic getUserMedia stream, killing voice audio.

BREAKING CHANGE: possibly streaming
This commit is contained in:
2026-03-12 23:55:38 +01:00
parent 778e75bef5
commit 781c05294f

View File

@@ -190,10 +190,12 @@ export class ScreenShareManager {
/**
* Begin screen sharing.
*
* On Linux Electron builds, prefers a dedicated PulseAudio/PipeWire routing
* path so remote voice playback is kept out of captured system audio.
* On other Electron builds, uses desktop capture. In browser contexts, uses
* `getDisplayMedia`.
* On Linux Electron builds, prefers a dedicated PulseAudio/PipeWire routing
* path so remote voice playback is kept out of captured system audio.
* On Windows Electron builds, prefers `getDisplayMedia` with system audio
* so the separate mic `getUserMedia` stream is not disrupted; falls back to
* Electron desktop capture only when `getDisplayMedia` fails entirely.
* In browser contexts, uses `getDisplayMedia`.
*
* @param options - Screen-share capture options.
* @returns The captured screen {@link MediaStream}.
@@ -230,16 +232,27 @@ export class ScreenShareManager {
}
}
if (!this.activeScreenStream && shareOptions.includeSystemAudio && !electronDesktopCaptureAvailable) {
if (!this.activeScreenStream && shareOptions.includeSystemAudio) {
try {
this.activeScreenStream = await this.startWithDisplayMedia(shareOptions, preset);
captureMethod = 'display-media';
if (this.activeScreenStream.getAudioTracks().length === 0) {
this.logger.warn('getDisplayMedia did not provide system audio; trying Electron desktop capture');
this.activeScreenStream.getTracks().forEach((track) => track.stop());
this.activeScreenStream = null;
captureMethod = null;
if (electronDesktopCaptureAvailable) {
// On Windows Electron, keep the getDisplayMedia stream for video
// rather than falling through to getUserMedia desktop audio which
// can replace or kill the active mic stream.
this.logger.warn(
'getDisplayMedia did not provide system audio; '
+ 'continuing without system audio to preserve mic stream'
);
shareOptions.includeSystemAudio = false;
} else {
this.logger.warn('getDisplayMedia did not provide system audio; trying next capture method');
this.activeScreenStream.getTracks().forEach((track) => track.stop());
this.activeScreenStream = null;
captureMethod = null;
}
}
} catch (error) {
this.rethrowIfScreenShareAborted(error);
@@ -426,9 +439,12 @@ export class ScreenShareManager {
private shouldSuppressRemotePlaybackDuringShare(
includeSystemAudio: boolean,
captureMethod: ScreenShareCaptureMethod | null
_captureMethod: ScreenShareCaptureMethod | null
): boolean {
return includeSystemAudio && captureMethod === 'electron-desktop' && this.isWindowsElectron();
// On Windows Electron, system audio capture (via getDisplayMedia or
// desktop capturer) includes all output audio. Remote voice playback
// must be suppressed to avoid a feedback loop regardless of capture method.
return includeSystemAudio && this.isWindowsElectron();
}
private getRequiredLinuxElectronApi(): Required<Pick<