diff --git a/fluxer_app/src/features/voice/utils/ScreenShareStartFlow.test.ts b/fluxer_app/src/features/voice/utils/ScreenShareStartFlow.test.ts index 3af4a68fa..b198c63a1 100644 --- a/fluxer_app/src/features/voice/utils/ScreenShareStartFlow.test.ts +++ b/fluxer_app/src/features/voice/utils/ScreenShareStartFlow.test.ts @@ -101,6 +101,7 @@ vi.mock('@app/features/voice/state/VoiceSettings', () => ({ const {startConfiguredDisplayScreenShare, switchConfiguredDisplayScreenShare} = await import( '@app/features/voice/utils/ScreenShareStartFlow' ); +const {isScreenShareAudioCaptureError} = await import('@app/features/voice/utils/ScreenShareAudioCaptureError'); const ActiveScreenShareSource = (await import('@app/features/voice/state/ActiveScreenShareSource')).default; 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', () => { test('preserves the running share state when the switch fails', async () => { replaceActiveDisplayScreenShare.mockResolvedValue(false); diff --git a/fluxer_app/src/features/voice/utils/ScreenShareStartFlow.ts b/fluxer_app/src/features/voice/utils/ScreenShareStartFlow.ts index 3519978e2..18b713ef2 100644 --- a/fluxer_app/src/features/voice/utils/ScreenShareStartFlow.ts +++ b/fluxer_app/src/features/voice/utils/ScreenShareStartFlow.ts @@ -28,7 +28,10 @@ import { disarmPendingNativeAudio, getLastNativeAudioArmFailure, } 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 { type DisplayShareEnvironment, getDisplayShareEnvironment, @@ -250,6 +253,11 @@ function degradeAudioToVideoOnly( 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 { if (mode === 'switch') { disarmPendingNativeAudio(); @@ -399,8 +407,7 @@ async function runConfiguredDisplayScreenShare( }); } if (!nativeAudioArmed) { - degradeAudioToVideoOnly( - captureOptions, + failRequestedAudioCapture( buildAudioCaptureFailureDebug({ sourceId, reason: getLastNativeAudioArmFailure()?.reason ?? 'linux-window-audio-route-unavailable', @@ -425,7 +432,7 @@ async function runConfiguredDisplayScreenShare( reason: getLastNativeAudioArmFailure()?.reason ?? 'system-audio-route-unavailable', }); logger.warn('Desktop audio unavailable; aborting screen share because audio was requested', debugInfo); - degradeAudioToVideoOnly(captureOptions, debugInfo); + failRequestedAudioCapture(debugInfo); } } else if (linuxDesktopAudioSourceMode === 'none') { removeAudioFromCaptureOptions(captureOptions); @@ -444,8 +451,7 @@ async function runConfiguredDisplayScreenShare( }); } if (!nativeAudioArmed) { - degradeAudioToVideoOnly( - captureOptions, + failRequestedAudioCapture( buildAudioCaptureFailureDebug({ sourceMode, reason: getLastNativeAudioArmFailure()?.reason ?? 'linux-system-audio-route-unavailable', @@ -477,7 +483,7 @@ async function runConfiguredDisplayScreenShare( platform: electronApi.platform, reason: debugInfo.reason, }); - degradeAudioToVideoOnly(captureOptions, debugInfo); + failRequestedAudioCapture(debugInfo); } } if ( @@ -489,8 +495,7 @@ async function runConfiguredDisplayScreenShare( electronApi.platform !== 'win32' && electronApi.platform !== 'linux' ) { - degradeAudioToVideoOnly( - captureOptions, + failRequestedAudioCapture( buildAudioCaptureFailureDebug({ sourceId, platform: electronApi.platform,