mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 13:35:42 +09:00
refactor(voice): make screen-share source swaps atomic (#2385)
This commit is contained in:
1 parent
62577b25bb
commit
8d34c6bcaa
43 files changed
+1168
-240
No files matched your search
@@ -268,6 +268,7 @@ function getConfiguredScreenShareOptions(
|
||||
sourceId?: string | null,
|
||||
preferredDisplaySurface?: 'window' | 'monitor',
|
||||
videoCodec?: VideoCodec,
|
||||
includeAudioOverride?: boolean,
|
||||
) {
|
||||
const currentResolution = VoiceSettings.getScreenshareResolution();
|
||||
const currentStreamingMode = VoiceSettings.getStreamingMode();
|
||||
@@ -288,12 +289,9 @@ function getConfiguredScreenShareOptions(
|
||||
VoiceSettings.getVideoFrameRate(),
|
||||
higherQuality,
|
||||
);
|
||||
const includeAudio = shouldIncludeAudioForShare(
|
||||
shareContext,
|
||||
displayShareEnvironment,
|
||||
sourceId,
|
||||
preferredDisplaySurface,
|
||||
);
|
||||
const includeAudio =
|
||||
includeAudioOverride ??
|
||||
shouldIncludeAudioForShare(shareContext, displayShareEnvironment, sourceId, preferredDisplaySurface);
|
||||
if (!includeAudio && shareContext !== 'device' && supportsDesktopScreenShareAudioCapture()) {
|
||||
logger.info('Screen share audio not requested for this surface', {
|
||||
sourceId,
|
||||
@@ -328,6 +326,7 @@ export interface ConfiguredDisplayScreenShareOptions {
|
||||
};
|
||||
preferredDisplaySurface?: 'window' | 'monitor';
|
||||
isOwnWindow?: boolean;
|
||||
includeAudio?: boolean;
|
||||
}
|
||||
|
||||
async function runConfiguredDisplayScreenShare(
|
||||
@@ -348,6 +347,8 @@ async function runConfiguredDisplayScreenShare(
|
||||
options?.sourceDimensions,
|
||||
sourceId,
|
||||
options?.preferredDisplaySurface,
|
||||
undefined,
|
||||
options?.includeAudio,
|
||||
);
|
||||
if (electronApi) {
|
||||
const restartWaylandPortalForSwitch = useWaylandPortal && mode === 'switch';
|
||||
@@ -373,14 +374,7 @@ async function runConfiguredDisplayScreenShare(
|
||||
sourceId,
|
||||
platform: electronApi.platform,
|
||||
});
|
||||
degradeAudioToVideoOnly(
|
||||
captureOptions,
|
||||
buildAudioCaptureFailureDebug({
|
||||
sourceId,
|
||||
platform: electronApi.platform,
|
||||
reason: 'self-window-audio-route-unavailable',
|
||||
}),
|
||||
);
|
||||
removeAudioFromCaptureOptions(captureOptions);
|
||||
}
|
||||
const requestedAppAudioOnLinux =
|
||||
requestedAudio && !isOwnWindowShare && electronApi.platform === 'linux' && sourceId?.startsWith('window:');
|
||||
@@ -513,11 +507,14 @@ async function runConfiguredDisplayScreenShare(
|
||||
}
|
||||
if (!useWaylandPortal && sourceId) {
|
||||
setDesktopSourceIntent({sourceId, includeAudio: false});
|
||||
ActiveScreenShareSource.setSourceId(sourceId, {isOwnWindow: isOwnWindowShare});
|
||||
}
|
||||
let operationSucceeded = false;
|
||||
if (mode === 'switch' && !restartWaylandPortalForSwitch) {
|
||||
operationSucceeded = await MediaEngine.replaceActiveDisplayScreenShare(captureOptions, publishOptions);
|
||||
operationSucceeded = await MediaEngine.replaceActiveDisplayScreenShare(captureOptions, publishOptions, {
|
||||
sourceId: sourceId ?? null,
|
||||
displayShareEnvironment,
|
||||
requireAudio: requestedAudio && nativeAudioArmed,
|
||||
});
|
||||
} else {
|
||||
await MediaEngine.setScreenShareEnabled(
|
||||
true,
|
||||
@@ -530,7 +527,15 @@ async function runConfiguredDisplayScreenShare(
|
||||
if (nativeAudioArmed && !captured) {
|
||||
cleanupNativeAudioAfterCaptureDidNotStart(mode);
|
||||
}
|
||||
if (!captured) {
|
||||
if (captured && !useWaylandPortal && sourceId) {
|
||||
ActiveScreenShareSource.setPublishedSource(sourceId.startsWith('window:') ? 'app' : 'display', sourceId, {
|
||||
isOwnWindow: isOwnWindowShare,
|
||||
});
|
||||
}
|
||||
if (captured && useWaylandPortal) {
|
||||
ActiveScreenShareSource.setPublishedSource('wayland', null);
|
||||
}
|
||||
if (!captured && mode === 'start' && !didScreenShareStart()) {
|
||||
ActiveScreenShareSource.clear();
|
||||
}
|
||||
if (!captured && useWaylandPortal && mode !== 'switch') {
|
||||
@@ -570,7 +575,7 @@ async function runConfiguredDisplayScreenShare(
|
||||
if (nativeAudioArmed && !capturedAfterError) {
|
||||
cleanupNativeAudioAfterCaptureDidNotStart(mode);
|
||||
}
|
||||
if (!capturedAfterError) {
|
||||
if (!capturedAfterError && mode === 'start' && !didScreenShareStart()) {
|
||||
ActiveScreenShareSource.clear();
|
||||
}
|
||||
if (useWaylandPortal && !capturedAfterError && mode !== 'switch') {
|
||||
@@ -589,80 +594,153 @@ async function runConfiguredDisplayScreenShare(
|
||||
}
|
||||
let operationSucceeded = false;
|
||||
if (mode === 'switch') {
|
||||
operationSucceeded = await MediaEngine.replaceActiveDisplayScreenShare(captureOptions, publishOptions);
|
||||
operationSucceeded = await MediaEngine.replaceActiveDisplayScreenShare(captureOptions, publishOptions, {
|
||||
sourceId: null,
|
||||
displayShareEnvironment,
|
||||
requireAudio: false,
|
||||
});
|
||||
} else {
|
||||
await MediaEngine.setScreenShareEnabled(true, captureOptions, publishOptions);
|
||||
operationSucceeded = didScreenShareStart();
|
||||
}
|
||||
return mode === 'switch' ? operationSucceeded : didScreenShareStart();
|
||||
const captured = mode === 'switch' ? operationSucceeded : didScreenShareStart();
|
||||
if (captured) {
|
||||
ActiveScreenShareSource.setPublishedSource('web', null);
|
||||
}
|
||||
return captured;
|
||||
}
|
||||
|
||||
interface ConfiguredScreenShareMutationRequest {
|
||||
execute: () => Promise<boolean>;
|
||||
promise: Promise<boolean>;
|
||||
resolve: (result: boolean) => void;
|
||||
reject: (error: unknown) => void;
|
||||
}
|
||||
|
||||
let configuredScreenShareMutationActive = false;
|
||||
let pendingConfiguredScreenShareMutation: ConfiguredScreenShareMutationRequest | null = null;
|
||||
|
||||
function createConfiguredScreenShareMutationRequest(
|
||||
execute: () => Promise<boolean>,
|
||||
): ConfiguredScreenShareMutationRequest {
|
||||
let resolveRequest: ((result: boolean) => void) | undefined;
|
||||
let rejectRequest: ((error: unknown) => void) | undefined;
|
||||
const promise = new Promise<boolean>((resolve, reject) => {
|
||||
resolveRequest = resolve;
|
||||
rejectRequest = reject;
|
||||
});
|
||||
if (!resolveRequest || !rejectRequest) {
|
||||
throw new Error('Configured screen share mutation deferred was not initialized');
|
||||
}
|
||||
return {execute, promise, resolve: resolveRequest, reject: rejectRequest};
|
||||
}
|
||||
|
||||
async function drainConfiguredScreenShareMutations(
|
||||
initialRequest: ConfiguredScreenShareMutationRequest,
|
||||
): Promise<void> {
|
||||
let request: ConfiguredScreenShareMutationRequest | null = initialRequest;
|
||||
while (request) {
|
||||
try {
|
||||
request.resolve(await request.execute());
|
||||
} catch (error) {
|
||||
request.reject(error);
|
||||
}
|
||||
request = pendingConfiguredScreenShareMutation;
|
||||
pendingConfiguredScreenShareMutation = null;
|
||||
}
|
||||
configuredScreenShareMutationActive = false;
|
||||
}
|
||||
|
||||
function scheduleConfiguredScreenShareMutation(execute: () => Promise<boolean>): Promise<boolean> {
|
||||
const request = createConfiguredScreenShareMutationRequest(execute);
|
||||
if (!configuredScreenShareMutationActive) {
|
||||
configuredScreenShareMutationActive = true;
|
||||
void drainConfiguredScreenShareMutations(request);
|
||||
return request.promise;
|
||||
}
|
||||
pendingConfiguredScreenShareMutation?.resolve(false);
|
||||
pendingConfiguredScreenShareMutation = request;
|
||||
return request.promise;
|
||||
}
|
||||
|
||||
export async function startConfiguredDisplayScreenShare(
|
||||
sourceId?: string | null,
|
||||
options?: ConfiguredDisplayScreenShareOptions,
|
||||
): Promise<boolean> {
|
||||
let didStart = false;
|
||||
await executeScreenShareOperation(async () => {
|
||||
didStart = await runConfiguredDisplayScreenShare(sourceId, options, 'start');
|
||||
return scheduleConfiguredScreenShareMutation(async () => {
|
||||
let didStart = false;
|
||||
await executeScreenShareOperation(async () => {
|
||||
didStart = await runConfiguredDisplayScreenShare(sourceId, options, 'start');
|
||||
});
|
||||
return didStart;
|
||||
});
|
||||
return didStart;
|
||||
}
|
||||
|
||||
export async function switchConfiguredDisplayScreenShare(
|
||||
sourceId?: string | null,
|
||||
options?: ConfiguredDisplayScreenShareOptions,
|
||||
): Promise<boolean> {
|
||||
let didSwitch = false;
|
||||
await executeScreenShareOperation(async () => {
|
||||
didSwitch = await runConfiguredDisplayScreenShare(sourceId, options, 'switch');
|
||||
return scheduleConfiguredScreenShareMutation(async () => {
|
||||
let didSwitch = false;
|
||||
await executeScreenShareOperation(async () => {
|
||||
didSwitch = await runConfiguredDisplayScreenShare(sourceId, options, 'switch');
|
||||
});
|
||||
return didSwitch;
|
||||
});
|
||||
return didSwitch;
|
||||
}
|
||||
|
||||
export async function startConfiguredDeviceScreenShare(videoDeviceId: string): Promise<boolean> {
|
||||
normaliseDeviceScreenShareSettings();
|
||||
const {captureOptions, publishOptions, includeAudio, audioDeviceId} = getConfiguredScreenShareOptions(
|
||||
'device',
|
||||
'desktop-custom',
|
||||
);
|
||||
try {
|
||||
await MediaEngine.startDeviceScreenShare(
|
||||
{
|
||||
videoDeviceId,
|
||||
audioDeviceId: includeAudio ? audioDeviceId : undefined,
|
||||
resolution: captureOptions.resolution,
|
||||
},
|
||||
publishOptions,
|
||||
return scheduleConfiguredScreenShareMutation(async () => {
|
||||
normaliseDeviceScreenShareSettings();
|
||||
const {captureOptions, publishOptions, includeAudio, audioDeviceId} = getConfiguredScreenShareOptions(
|
||||
'device',
|
||||
'desktop-custom',
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error('Failed to start device screen share', {
|
||||
error,
|
||||
videoDeviceId,
|
||||
});
|
||||
}
|
||||
return didScreenShareStart();
|
||||
try {
|
||||
await MediaEngine.startDeviceScreenShare(
|
||||
{
|
||||
videoDeviceId,
|
||||
audioDeviceId: includeAudio ? audioDeviceId : undefined,
|
||||
resolution: captureOptions.resolution,
|
||||
},
|
||||
publishOptions,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error('Failed to start device screen share', {
|
||||
error,
|
||||
videoDeviceId,
|
||||
});
|
||||
}
|
||||
const didStart = didScreenShareStart();
|
||||
if (didStart) ActiveScreenShareSource.setPublishedSource('device', null);
|
||||
return didStart;
|
||||
});
|
||||
}
|
||||
|
||||
export async function switchConfiguredDeviceScreenShare(videoDeviceId: string): Promise<boolean> {
|
||||
normaliseDeviceScreenShareSettings();
|
||||
const {captureOptions, publishOptions, includeAudio, audioDeviceId} = getConfiguredScreenShareOptions(
|
||||
'device',
|
||||
'desktop-custom',
|
||||
);
|
||||
try {
|
||||
return await MediaEngine.replaceActiveDeviceScreenShare(
|
||||
{
|
||||
videoDeviceId,
|
||||
audioDeviceId: includeAudio ? audioDeviceId : undefined,
|
||||
resolution: captureOptions.resolution,
|
||||
},
|
||||
publishOptions,
|
||||
return scheduleConfiguredScreenShareMutation(async () => {
|
||||
normaliseDeviceScreenShareSettings();
|
||||
const {captureOptions, publishOptions, includeAudio, audioDeviceId} = getConfiguredScreenShareOptions(
|
||||
'device',
|
||||
'desktop-custom',
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error('Failed to switch device screen share source', {
|
||||
error,
|
||||
videoDeviceId,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const didSwitch = await MediaEngine.replaceActiveDeviceScreenShare(
|
||||
{
|
||||
videoDeviceId,
|
||||
audioDeviceId: includeAudio ? audioDeviceId : undefined,
|
||||
resolution: captureOptions.resolution,
|
||||
},
|
||||
publishOptions,
|
||||
);
|
||||
if (didSwitch) ActiveScreenShareSource.setPublishedSource('device', null);
|
||||
return didSwitch;
|
||||
} catch (error) {
|
||||
logger.error('Failed to switch device screen share source', {
|
||||
error,
|
||||
videoDeviceId,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user