mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-26 07:45:34 +00:00
fixed webcam disappearing
This commit is contained in:
Vendored
+6
@@ -85,6 +85,12 @@ interface Window {
|
||||
message?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
recoverNativeScreenRecording: () => Promise<{
|
||||
success: boolean;
|
||||
path?: string;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
getLastNativeCaptureDiagnostics: () => Promise<{
|
||||
success: boolean;
|
||||
diagnostics?: NativeCaptureDiagnostics | null;
|
||||
|
||||
@@ -2621,6 +2621,34 @@ async function finalizeStoredVideo(videoPath: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function recoverNativeMacCaptureOutput() {
|
||||
const diagnosticsPath =
|
||||
lastNativeCaptureDiagnostics?.backend === 'mac-screencapturekit'
|
||||
? lastNativeCaptureDiagnostics.outputPath ?? null
|
||||
: null
|
||||
const candidatePath = nativeCaptureTargetPath ?? diagnosticsPath
|
||||
|
||||
if (!candidatePath) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
return await finalizeStoredVideo(candidatePath)
|
||||
} catch (error) {
|
||||
recordNativeCaptureDiagnostics({
|
||||
backend: 'mac-screencapturekit',
|
||||
phase: 'stop',
|
||||
outputPath: candidatePath,
|
||||
systemAudioPath: nativeCaptureSystemAudioPath,
|
||||
microphonePath: nativeCaptureMicrophonePath,
|
||||
processOutput: nativeCaptureOutputBuffer.trim() || undefined,
|
||||
fileSizeBytes: await getFileSizeIfPresent(candidatePath),
|
||||
error: String(error),
|
||||
})
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function startInteractionCapture() {
|
||||
if (!isCursorCaptureActive) {
|
||||
return
|
||||
@@ -3370,9 +3398,34 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh}
|
||||
|
||||
await waitForNativeCaptureStart(nativeCaptureProcess)
|
||||
nativeScreenRecordingActive = true
|
||||
recordNativeCaptureDiagnostics({
|
||||
backend: 'mac-screencapturekit',
|
||||
phase: 'start',
|
||||
sourceId: source?.id ?? null,
|
||||
sourceType: source?.sourceType ?? 'unknown',
|
||||
displayId: typeof config.displayId === 'number' ? config.displayId : null,
|
||||
helperPath,
|
||||
outputPath,
|
||||
systemAudioPath: systemAudioOutputPath,
|
||||
microphonePath: microphoneOutputPath,
|
||||
processOutput: nativeCaptureOutputBuffer.trim() || undefined,
|
||||
})
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error('Failed to start native ScreenCaptureKit recording:', error)
|
||||
recordNativeCaptureDiagnostics({
|
||||
backend: 'mac-screencapturekit',
|
||||
phase: 'start',
|
||||
sourceId: source?.id ?? null,
|
||||
sourceType: source?.sourceType ?? 'unknown',
|
||||
helperPath: getNativeCaptureHelperBinaryPath(),
|
||||
outputPath: nativeCaptureTargetPath,
|
||||
systemAudioPath: nativeCaptureSystemAudioPath,
|
||||
microphonePath: nativeCaptureMicrophonePath,
|
||||
processOutput: nativeCaptureOutputBuffer.trim() || undefined,
|
||||
fileSizeBytes: await getFileSizeIfPresent(nativeCaptureTargetPath),
|
||||
error: String(error),
|
||||
})
|
||||
try {
|
||||
nativeCaptureProcess?.kill()
|
||||
} catch {
|
||||
@@ -3485,6 +3538,11 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh}
|
||||
}
|
||||
|
||||
if (!nativeScreenRecordingActive) {
|
||||
const recovered = await recoverNativeMacCaptureOutput()
|
||||
if (recovered) {
|
||||
return recovered
|
||||
}
|
||||
|
||||
return { success: false, message: 'No native screen recording is active.' }
|
||||
}
|
||||
|
||||
@@ -3525,6 +3583,8 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh}
|
||||
} catch (error) {
|
||||
console.error('Failed to stop native ScreenCaptureKit recording:', error)
|
||||
const fallbackPath = nativeCaptureTargetPath
|
||||
const fallbackSystemAudioPath = nativeCaptureSystemAudioPath
|
||||
const fallbackMicrophonePath = nativeCaptureMicrophonePath
|
||||
nativeScreenRecordingActive = false
|
||||
nativeCaptureProcess = null
|
||||
nativeCaptureTargetPath = null
|
||||
@@ -3538,12 +3598,28 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh}
|
||||
try {
|
||||
await fs.access(fallbackPath)
|
||||
console.log('[stop-native-screen-recording] Recovering with fallback path:', fallbackPath)
|
||||
if (fallbackSystemAudioPath || fallbackMicrophonePath) {
|
||||
try {
|
||||
await muxNativeMacRecordingWithAudio(
|
||||
fallbackPath,
|
||||
fallbackSystemAudioPath,
|
||||
fallbackMicrophonePath,
|
||||
)
|
||||
} catch (muxError) {
|
||||
console.warn('Failed to mux recovered native macOS audio into capture:', muxError)
|
||||
}
|
||||
}
|
||||
return await finalizeStoredVideo(fallbackPath)
|
||||
} catch {
|
||||
// File doesn't exist or isn't accessible
|
||||
}
|
||||
}
|
||||
|
||||
const recovered = await recoverNativeMacCaptureOutput()
|
||||
if (recovered) {
|
||||
return recovered
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: 'Failed to stop native ScreenCaptureKit recording',
|
||||
@@ -3552,6 +3628,22 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh}
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('recover-native-screen-recording', async () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
return { success: false, message: 'Native screen recording recovery is only available on macOS.' }
|
||||
}
|
||||
|
||||
const recovered = await recoverNativeMacCaptureOutput()
|
||||
if (recovered) {
|
||||
return recovered
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: 'No recoverable native macOS recording output was found.',
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('pause-native-screen-recording', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
if (!windowsNativeCaptureActive || !windowsCaptureProcess) {
|
||||
|
||||
@@ -68,6 +68,9 @@ contextBridge.exposeInMainWorld("electronAPI", {
|
||||
stopNativeScreenRecording: () => {
|
||||
return ipcRenderer.invoke("stop-native-screen-recording");
|
||||
},
|
||||
recoverNativeScreenRecording: () => {
|
||||
return ipcRenderer.invoke("recover-native-screen-recording");
|
||||
},
|
||||
getLastNativeCaptureDiagnostics: () => {
|
||||
return ipcRenderer.invoke("get-last-native-capture-diagnostics");
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useRef, useEffect, useCallback } from "react";
|
||||
import { fixWebmDuration } from "@fix-webm-duration/fix";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { getEffectiveRecordingDurationMs } from "@/lib/mediaTiming";
|
||||
|
||||
@@ -91,6 +91,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
const pendingWebcamPathPromise = useRef<Promise<string | null> | null>(null);
|
||||
const webcamStopPromise = useRef<Promise<string | null> | null>(null);
|
||||
const webcamStopResolver = useRef<((path: string | null) => void) | null>(null);
|
||||
const resolvedWebcamPath = useRef<string | null>(null);
|
||||
const accumulatedPausedDurationMs = useRef(0);
|
||||
const pauseStartedAtMs = useRef<number | null>(null);
|
||||
|
||||
@@ -312,12 +313,29 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
}
|
||||
|
||||
const result = pending ? await pending : null;
|
||||
resolvedWebcamPath.current = result;
|
||||
pendingWebcamPathPromise.current = null;
|
||||
return result;
|
||||
}, []);
|
||||
|
||||
const recoverNativeRecordingSession = useCallback(async () => {
|
||||
if (typeof window.electronAPI?.recoverNativeScreenRecording !== "function") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = await window.electronAPI.recoverNativeScreenRecording();
|
||||
if (!result.success || !result.path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const webcamPath = await stopWebcamRecorder();
|
||||
await finalizeRecordingSession(result.path, webcamPath);
|
||||
return result.path;
|
||||
}, [finalizeRecordingSession, stopWebcamRecorder]);
|
||||
|
||||
const startWebcamRecorder = useCallback(async () => {
|
||||
if (!webcamEnabled) {
|
||||
resolvedWebcamPath.current = null;
|
||||
pendingWebcamPathPromise.current = Promise.resolve(null);
|
||||
webcamStartTime.current = null;
|
||||
webcamTimeOffsetMs.current = 0;
|
||||
@@ -343,6 +361,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
|
||||
const mimeType = selectMimeType();
|
||||
webcamChunks.current = [];
|
||||
resolvedWebcamPath.current = null;
|
||||
webcamStopPromise.current = new Promise((resolve) => {
|
||||
webcamStopResolver.current = resolve;
|
||||
});
|
||||
@@ -401,6 +420,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
recorder.start(RECORDER_TIMESLICE_MS);
|
||||
} catch (error) {
|
||||
console.warn("Failed to start webcam recording; continuing without webcam layer:", error);
|
||||
resolvedWebcamPath.current = null;
|
||||
pendingWebcamPathPromise.current = Promise.resolve(null);
|
||||
webcamStopPromise.current = Promise.resolve(null);
|
||||
webcamRecorder.current = null;
|
||||
@@ -430,6 +450,14 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
if (!result.success || !result.path) {
|
||||
console.error("Failed to stop native screen recording:", result.error ?? result.message);
|
||||
void logNativeCaptureDiagnostics("stop-native-screen-recording");
|
||||
try {
|
||||
const recoveredPath = await recoverNativeRecordingSession();
|
||||
if (recoveredPath) {
|
||||
return;
|
||||
}
|
||||
} catch (recoveryError) {
|
||||
console.error("Failed to recover native screen recording:", recoveryError);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -501,19 +529,32 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
});
|
||||
|
||||
const removeRecordingInterruptedListener = window.electronAPI?.onRecordingInterrupted?.((state) => {
|
||||
setRecording(false);
|
||||
nativeScreenRecording.current = false;
|
||||
cleanupCapturedMedia();
|
||||
void window.electronAPI.setRecordingState(false);
|
||||
void (async () => {
|
||||
setRecording(false);
|
||||
nativeScreenRecording.current = false;
|
||||
cleanupCapturedMedia();
|
||||
await window.electronAPI.setRecordingState(false);
|
||||
|
||||
if (state.reason === "window-unavailable" && !hasPromptedForReselect.current) {
|
||||
hasPromptedForReselect.current = true;
|
||||
alert(state.message);
|
||||
void window.electronAPI.openSourceSelector();
|
||||
} else {
|
||||
console.error(state.message);
|
||||
toast.error(state.message);
|
||||
}
|
||||
if (state.reason !== "window-unavailable") {
|
||||
try {
|
||||
const recoveredPath = await recoverNativeRecordingSession();
|
||||
if (recoveredPath) {
|
||||
return;
|
||||
}
|
||||
} catch (recoveryError) {
|
||||
console.error("Failed to recover interrupted native screen recording:", recoveryError);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.reason === "window-unavailable" && !hasPromptedForReselect.current) {
|
||||
hasPromptedForReselect.current = true;
|
||||
alert(state.message);
|
||||
await window.electronAPI.openSourceSelector();
|
||||
} else {
|
||||
console.error(state.message);
|
||||
toast.error(state.message);
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
return () => {
|
||||
@@ -532,7 +573,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
|
||||
cleanupCapturedMedia();
|
||||
};
|
||||
}, [cleanupCapturedMedia]);
|
||||
}, [cleanupCapturedMedia, recoverNativeRecordingSession]);
|
||||
|
||||
const startRecording = async () => {
|
||||
if (startInFlight.current) {
|
||||
@@ -853,7 +894,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
}
|
||||
|
||||
if (videoResult.path) {
|
||||
const webcamPath = await (pendingWebcamPathPromise.current ?? Promise.resolve(null));
|
||||
const webcamPath = pendingWebcamPathPromise.current
|
||||
? await pendingWebcamPathPromise.current
|
||||
: resolvedWebcamPath.current;
|
||||
await finalizeRecordingSession(videoResult.path, webcamPath);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -955,6 +998,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
webcamStream.current?.getTracks().forEach((t) => t.stop());
|
||||
webcamStream.current = null;
|
||||
pendingWebcamPathPromise.current = null;
|
||||
resolvedWebcamPath.current = null;
|
||||
|
||||
if (nativeScreenRecording.current) {
|
||||
nativeScreenRecording.current = false;
|
||||
|
||||
Reference in New Issue
Block a user