fix(voice): abort screen share when its audio cannot start (#2394)

This commit is contained in:
Hampus
2026-09-02 17:35:26 +02:00
committed by GitHub
parent 8402eb53c8
commit 45cbabd94d
2 changed files with 27 additions and 9 deletions
@@ -101,6 +101,7 @@ vi.mock('@app/features/voice/state/VoiceSettings', () => ({
const {startConfiguredDisplayScreenShare, switchConfiguredDisplayScreenShare} = await import( const {startConfiguredDisplayScreenShare, switchConfiguredDisplayScreenShare} = await import(
'@app/features/voice/utils/ScreenShareStartFlow' '@app/features/voice/utils/ScreenShareStartFlow'
); );
const {isScreenShareAudioCaptureError} = await import('@app/features/voice/utils/ScreenShareAudioCaptureError');
const ActiveScreenShareSource = (await import('@app/features/voice/state/ActiveScreenShareSource')).default; const ActiveScreenShareSource = (await import('@app/features/voice/state/ActiveScreenShareSource')).default;
beforeEach(() => { beforeEach(() => {
@@ -132,6 +133,18 @@ describe('sharing a Fluxer-owned window while app audio is enabled', () => {
}); });
}); });
describe('sharing another application window while app audio is enabled', () => {
test('arms native per-window audio and hard-fails when it cannot be armed', async () => {
armNativeAudioForNextCapture.mockResolvedValue(false);
await expect(
startConfiguredDisplayScreenShare('window:42:0', {preferredDisplaySurface: 'window'}),
).rejects.toSatisfy(isScreenShareAudioCaptureError);
expect(armNativeAudioForNextCapture).toHaveBeenCalledWith('window:42:0');
expect(setScreenShareEnabled).not.toHaveBeenCalled();
});
});
describe('switching the display source', () => { describe('switching the display source', () => {
test('preserves the running share state when the switch fails', async () => { test('preserves the running share state when the switch fails', async () => {
replaceActiveDisplayScreenShare.mockResolvedValue(false); replaceActiveDisplayScreenShare.mockResolvedValue(false);
@@ -28,7 +28,10 @@ import {
disarmPendingNativeAudio, disarmPendingNativeAudio,
getLastNativeAudioArmFailure, getLastNativeAudioArmFailure,
} from '@app/features/voice/utils/NativeAudioCaptureBridge'; } from '@app/features/voice/utils/NativeAudioCaptureBridge';
import type {ScreenShareAudioCaptureDebugInfo} from '@app/features/voice/utils/ScreenShareAudioCaptureError'; import {
type ScreenShareAudioCaptureDebugInfo,
ScreenShareAudioCaptureError,
} from '@app/features/voice/utils/ScreenShareAudioCaptureError';
import { import {
type DisplayShareEnvironment, type DisplayShareEnvironment,
getDisplayShareEnvironment, getDisplayShareEnvironment,
@@ -250,6 +253,11 @@ function degradeAudioToVideoOnly(
removeAudioFromCaptureOptions(captureOptions); removeAudioFromCaptureOptions(captureOptions);
} }
function failRequestedAudioCapture(debugInfo: ScreenShareAudioCaptureDebugInfo): never {
logger.warn('Screen share audio capture was requested but could not start', debugInfo);
throw new ScreenShareAudioCaptureError(debugInfo);
}
function cleanupNativeAudioAfterCaptureDidNotStart(mode: 'start' | 'switch'): void { function cleanupNativeAudioAfterCaptureDidNotStart(mode: 'start' | 'switch'): void {
if (mode === 'switch') { if (mode === 'switch') {
disarmPendingNativeAudio(); disarmPendingNativeAudio();
@@ -399,8 +407,7 @@ async function runConfiguredDisplayScreenShare(
}); });
} }
if (!nativeAudioArmed) { if (!nativeAudioArmed) {
degradeAudioToVideoOnly( failRequestedAudioCapture(
captureOptions,
buildAudioCaptureFailureDebug({ buildAudioCaptureFailureDebug({
sourceId, sourceId,
reason: getLastNativeAudioArmFailure()?.reason ?? 'linux-window-audio-route-unavailable', reason: getLastNativeAudioArmFailure()?.reason ?? 'linux-window-audio-route-unavailable',
@@ -425,7 +432,7 @@ async function runConfiguredDisplayScreenShare(
reason: getLastNativeAudioArmFailure()?.reason ?? 'system-audio-route-unavailable', reason: getLastNativeAudioArmFailure()?.reason ?? 'system-audio-route-unavailable',
}); });
logger.warn('Desktop audio unavailable; aborting screen share because audio was requested', debugInfo); logger.warn('Desktop audio unavailable; aborting screen share because audio was requested', debugInfo);
degradeAudioToVideoOnly(captureOptions, debugInfo); failRequestedAudioCapture(debugInfo);
} }
} else if (linuxDesktopAudioSourceMode === 'none') { } else if (linuxDesktopAudioSourceMode === 'none') {
removeAudioFromCaptureOptions(captureOptions); removeAudioFromCaptureOptions(captureOptions);
@@ -444,8 +451,7 @@ async function runConfiguredDisplayScreenShare(
}); });
} }
if (!nativeAudioArmed) { if (!nativeAudioArmed) {
degradeAudioToVideoOnly( failRequestedAudioCapture(
captureOptions,
buildAudioCaptureFailureDebug({ buildAudioCaptureFailureDebug({
sourceMode, sourceMode,
reason: getLastNativeAudioArmFailure()?.reason ?? 'linux-system-audio-route-unavailable', reason: getLastNativeAudioArmFailure()?.reason ?? 'linux-system-audio-route-unavailable',
@@ -477,7 +483,7 @@ async function runConfiguredDisplayScreenShare(
platform: electronApi.platform, platform: electronApi.platform,
reason: debugInfo.reason, reason: debugInfo.reason,
}); });
degradeAudioToVideoOnly(captureOptions, debugInfo); failRequestedAudioCapture(debugInfo);
} }
} }
if ( if (
@@ -489,8 +495,7 @@ async function runConfiguredDisplayScreenShare(
electronApi.platform !== 'win32' && electronApi.platform !== 'win32' &&
electronApi.platform !== 'linux' electronApi.platform !== 'linux'
) { ) {
degradeAudioToVideoOnly( failRequestedAudioCapture(
captureOptions,
buildAudioCaptureFailureDebug({ buildAudioCaptureFailureDebug({
sourceId, sourceId,
platform: electronApi.platform, platform: electronApi.platform,