mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-27 00:05:39 +00:00
fix: warm start native capture before countdown
This commit is contained in:
Binary file not shown.
@@ -5,24 +5,24 @@
|
||||
"helpers": {
|
||||
"wgc-capture": {
|
||||
"binaryName": "wgc-capture.exe",
|
||||
"binarySha256": "298b41f371c3881046061048b466e12ed70dd93fa761bade2fd57d1ccddf3cb9",
|
||||
"binarySha256": "4d89fdff8e3343998c7a3b4d75d964c1f75f93594aa097c6681e477d25ec9f01",
|
||||
"sourceDir": "electron/native/wgc-capture",
|
||||
"sourceFingerprint": "6ee457080c27dc939ff4b61965f86b6d73995e40200440a1dc44865f9708d39f",
|
||||
"updatedAt": "2026-05-24T19:49:15.077Z"
|
||||
"sourceFingerprint": "c6dac250c9d16f7aa881998353441b4ae3fb59731b802e3b8491a136e79726b0",
|
||||
"updatedAt": "2026-07-11T11:58:45.856Z"
|
||||
},
|
||||
"cursor-monitor": {
|
||||
"binaryName": "cursor-monitor.exe",
|
||||
"binarySha256": "6ae6d91103b6e891a851e8ea5791e1c1f9aaab700134c18bc4c46cfffd7fdd12",
|
||||
"binarySha256": "f1d8f30e8d7bee19ecea91c9a90a95ea4824138b8336b18030fa0602a641d70d",
|
||||
"sourceDir": "electron/native/cursor-monitor",
|
||||
"sourceFingerprint": "6ad1b8b50bb336f2a48937b06f5ec56d90b6ab4a3e56a4bca278cf67a5d3e52e",
|
||||
"updatedAt": "2026-05-07T15:22:18.173Z"
|
||||
"sourceFingerprint": "45bb72e4039e061a354af87317d7c42ec3d2118369b3f4379046ff497b701e72",
|
||||
"updatedAt": "2026-07-11T11:58:56.534Z"
|
||||
},
|
||||
"recordly-gpu-export": {
|
||||
"binaryName": "recordly-gpu-export.exe",
|
||||
"binarySha256": "4cb3a293fd36f718af55906820d9b3fd78babc855888c2e248f0b918ec1aff3c",
|
||||
"binarySha256": "49a2ac588206305d0129e6263ce4be49780c50a9dc4efc7df63aad09178a919f",
|
||||
"sourceDir": "electron/native/gpu-export-probe",
|
||||
"sourceFingerprint": "743b386a5f1bbcc99cec5465c3de228d2b045061dead31dfcbf25cf6a1e61de5",
|
||||
"updatedAt": "2026-05-07T20:13:48.585Z"
|
||||
"sourceFingerprint": "37a5842eba63cdeccfddd02cc278a98207248fb0aa6b56153fb85ed152c908c1",
|
||||
"updatedAt": "2026-07-11T11:58:51.659Z"
|
||||
},
|
||||
"recordly-nvidia-cuda-compositor": {
|
||||
"binaryName": "recordly-nvidia-cuda-compositor.exe",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -438,6 +438,7 @@ function LaunchWindowContent() {
|
||||
const hudMode = finalizing ? "finalizing" : recording ? "recording" : "idle";
|
||||
const useNativeHudBarDrag =
|
||||
platform === "linux" || hudOverlayMousePassthroughSupported === false;
|
||||
const shouldAnimateHudLayout = !recording && !showRecordingWebcamPreview && !isHudDragging;
|
||||
|
||||
return (
|
||||
<HudInteractionContext.Provider
|
||||
@@ -464,7 +465,7 @@ function LaunchWindowContent() {
|
||||
>
|
||||
<motion.div
|
||||
ref={hudBarRef}
|
||||
layout={!showRecordingWebcamPreview && !isHudDragging}
|
||||
layout={shouldAnimateHudLayout}
|
||||
transition={hudStateTransition}
|
||||
className={`${styles.bar} launch-theme mb-2`}
|
||||
>
|
||||
@@ -487,7 +488,7 @@ function LaunchWindowContent() {
|
||||
<AnimatePresence initial={false} mode="wait">
|
||||
<motion.div
|
||||
key={hudMode}
|
||||
layout={!showRecordingWebcamPreview && !isHudDragging}
|
||||
layout={shouldAnimateHudLayout}
|
||||
className={styles.barState}
|
||||
initial={{
|
||||
opacity: 0,
|
||||
|
||||
+179
-89
@@ -213,10 +213,7 @@ export function resolveBrowserCaptureCursorPolicy({
|
||||
export function shouldUseNativeWindowsCaptureForSource(
|
||||
source: Pick<ProcessedDesktopSource, "id"> | null | undefined,
|
||||
): boolean {
|
||||
return (
|
||||
source?.id?.startsWith("screen:") === true ||
|
||||
source?.id?.startsWith("window:") === true
|
||||
);
|
||||
return source?.id?.startsWith("screen:") === true || source?.id?.startsWith("window:") === true;
|
||||
}
|
||||
|
||||
export function createProcessedMicrophoneConstraints(
|
||||
@@ -1066,6 +1063,97 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const prepareRecordingStart = useCallback(async () => {
|
||||
const platform = await window.electronAPI.getPlatform();
|
||||
hideEditorOverlayCursorByDefault.current = false;
|
||||
const existingSource = await window.electronAPI.getSelectedSource();
|
||||
const selectedSource =
|
||||
existingSource ?? (platform === "linux" ? LINUX_PORTAL_SOURCE : null);
|
||||
if (!selectedSource) {
|
||||
alert("Please select a source to record");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!existingSource && selectedSource.id === "screen:linux-portal") {
|
||||
try {
|
||||
await window.electronAPI.selectSource(selectedSource);
|
||||
} catch (err) {
|
||||
console.warn("Failed to persist Linux portal sentinel source:", err);
|
||||
}
|
||||
}
|
||||
|
||||
const permissionsReady = await preparePermissions();
|
||||
if (!permissionsReady) {
|
||||
return null;
|
||||
}
|
||||
|
||||
recordingSessionTimestamp.current = Date.now();
|
||||
resetRecordingClock(recordingSessionTimestamp.current);
|
||||
await prepareWebcamRecorder();
|
||||
|
||||
const useNativeMacScreenCapture =
|
||||
platform === "darwin" &&
|
||||
(selectedSource.id?.startsWith("screen:") ||
|
||||
selectedSource.id?.startsWith("window:")) &&
|
||||
typeof window.electronAPI.startNativeScreenRecording === "function";
|
||||
|
||||
let useNativeWindowsCapture = false;
|
||||
if (
|
||||
platform === "win32" &&
|
||||
shouldUseNativeWindowsCaptureForSource(selectedSource) &&
|
||||
typeof window.electronAPI.isNativeWindowsCaptureAvailable === "function"
|
||||
) {
|
||||
try {
|
||||
const nativeWindowsResult =
|
||||
await window.electronAPI.isNativeWindowsCaptureAvailable();
|
||||
useNativeWindowsCapture = nativeWindowsResult.available;
|
||||
if (!useNativeWindowsCapture && !hasShownNativeWindowsFallbackToast.current) {
|
||||
void logNativeCaptureDiagnostics("is-native-windows-capture-available");
|
||||
hasShownNativeWindowsFallbackToast.current = true;
|
||||
toast.info(
|
||||
"Native Windows capture is unavailable. Falling back to browser capture.",
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
useNativeWindowsCapture = false;
|
||||
if (!hasShownNativeWindowsFallbackToast.current) {
|
||||
hasShownNativeWindowsFallbackToast.current = true;
|
||||
toast.info(
|
||||
"Unable to check native Windows capture. Falling back to browser capture.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let micLabel: string | undefined;
|
||||
if ((useNativeMacScreenCapture || useNativeWindowsCapture) && microphoneEnabled) {
|
||||
try {
|
||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||
const mic = devices.find(
|
||||
(d) => d.deviceId === microphoneDeviceId && d.kind === "audioinput",
|
||||
);
|
||||
micLabel = mic?.label || undefined;
|
||||
} catch {
|
||||
// Fall through - native process will use the default mic.
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
platform,
|
||||
selectedSource,
|
||||
useNativeMacScreenCapture,
|
||||
useNativeWindowsCapture,
|
||||
micLabel,
|
||||
};
|
||||
}, [
|
||||
logNativeCaptureDiagnostics,
|
||||
microphoneDeviceId,
|
||||
microphoneEnabled,
|
||||
preparePermissions,
|
||||
prepareWebcamRecorder,
|
||||
resetRecordingClock,
|
||||
]);
|
||||
|
||||
const stopRecording = useRef(() => {
|
||||
setPaused(false);
|
||||
if (nativeScreenRecording.current) {
|
||||
@@ -1375,84 +1463,33 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
setStarting(true);
|
||||
|
||||
try {
|
||||
const platform = await window.electronAPI.getPlatform();
|
||||
hideEditorOverlayCursorByDefault.current = false;
|
||||
const existingSource = await window.electronAPI.getSelectedSource();
|
||||
const selectedSource =
|
||||
existingSource ?? (platform === "linux" ? LINUX_PORTAL_SOURCE : null);
|
||||
if (!selectedSource) {
|
||||
alert("Please select a source to record");
|
||||
const preparedStart = await prepareRecordingStart();
|
||||
if (!preparedStart) {
|
||||
return;
|
||||
}
|
||||
// Persist the synthetic Linux portal sentinel to main so that the
|
||||
// setDisplayMediaRequestHandler can short-circuit getSources() and
|
||||
// avoid triggering an extra portal dialog.
|
||||
if (!existingSource && selectedSource.id === "screen:linux-portal") {
|
||||
|
||||
const { selectedSource, useNativeMacScreenCapture, useNativeWindowsCapture, micLabel } =
|
||||
preparedStart;
|
||||
const useNativeCapture =
|
||||
useNativeMacScreenCapture || useNativeWindowsCapture;
|
||||
const shouldWarmStartNativeCapture = useNativeCapture && countdownDelay > 0;
|
||||
if (countdownDelay > 0 && !shouldWarmStartNativeCapture) {
|
||||
setCountdownActive(true);
|
||||
try {
|
||||
await window.electronAPI.selectSource(selectedSource);
|
||||
} catch (err) {
|
||||
console.warn("Failed to persist Linux portal sentinel source:", err);
|
||||
const result = await window.electronAPI.startCountdown(countdownDelay);
|
||||
if (!result.success || result.cancelled) {
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
setCountdownActive(false);
|
||||
}
|
||||
recordingSessionTimestamp.current = Date.now();
|
||||
resetRecordingClock(recordingSessionTimestamp.current);
|
||||
}
|
||||
|
||||
const permissionsReady = await preparePermissions();
|
||||
if (!permissionsReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
recordingSessionTimestamp.current = Date.now();
|
||||
resetRecordingClock(recordingSessionTimestamp.current);
|
||||
await prepareWebcamRecorder();
|
||||
const useNativeMacScreenCapture =
|
||||
platform === "darwin" &&
|
||||
(selectedSource.id?.startsWith("screen:") ||
|
||||
selectedSource.id?.startsWith("window:")) &&
|
||||
typeof window.electronAPI.startNativeScreenRecording === "function";
|
||||
|
||||
let useNativeWindowsCapture = false;
|
||||
let nativeWindowsCaptureStartFailed = false;
|
||||
if (
|
||||
platform === "win32" &&
|
||||
shouldUseNativeWindowsCaptureForSource(selectedSource) &&
|
||||
typeof window.electronAPI.isNativeWindowsCaptureAvailable === "function"
|
||||
) {
|
||||
try {
|
||||
const nativeWindowsResult =
|
||||
await window.electronAPI.isNativeWindowsCaptureAvailable();
|
||||
useNativeWindowsCapture = nativeWindowsResult.available;
|
||||
if (!useNativeWindowsCapture && !hasShownNativeWindowsFallbackToast.current) {
|
||||
void logNativeCaptureDiagnostics("is-native-windows-capture-available");
|
||||
hasShownNativeWindowsFallbackToast.current = true;
|
||||
toast.info(
|
||||
"Native Windows capture is unavailable. Falling back to browser capture.",
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
useNativeWindowsCapture = false;
|
||||
if (!hasShownNativeWindowsFallbackToast.current) {
|
||||
hasShownNativeWindowsFallbackToast.current = true;
|
||||
toast.info(
|
||||
"Unable to check native Windows capture. Falling back to browser capture.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (useNativeMacScreenCapture || useNativeWindowsCapture) {
|
||||
// Resolve the selected mic label for native capture backends.
|
||||
let micLabel: string | undefined;
|
||||
if (microphoneEnabled) {
|
||||
try {
|
||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||
const mic = devices.find(
|
||||
(d) => d.deviceId === microphoneDeviceId && d.kind === "audioinput",
|
||||
);
|
||||
micLabel = mic?.label || undefined;
|
||||
} catch {
|
||||
// Fall through — native process will use the default mic
|
||||
}
|
||||
}
|
||||
|
||||
if (useNativeCapture) {
|
||||
const nativeResult = await window.electronAPI.startNativeScreenRecording(
|
||||
selectedSource,
|
||||
{
|
||||
@@ -1491,11 +1528,53 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
}
|
||||
|
||||
if (nativeResult.success) {
|
||||
nativeScreenRecording.current = true;
|
||||
nativeWindowsRecording.current = useNativeWindowsCapture;
|
||||
if (shouldWarmStartNativeCapture) {
|
||||
const pauseResult = await window.electronAPI.pauseNativeScreenRecording();
|
||||
if (!pauseResult.success) {
|
||||
throw new Error(
|
||||
pauseResult.error ??
|
||||
pauseResult.message ??
|
||||
"Failed to pause native capture before countdown",
|
||||
);
|
||||
}
|
||||
|
||||
setCountdownActive(true);
|
||||
try {
|
||||
const countdownResult =
|
||||
await window.electronAPI.startCountdown(countdownDelay);
|
||||
if (!countdownResult.success || countdownResult.cancelled) {
|
||||
const stoppedResult =
|
||||
await window.electronAPI.stopNativeScreenRecording();
|
||||
if (stoppedResult.path) {
|
||||
await window.electronAPI.deleteRecordingFile(
|
||||
stoppedResult.path,
|
||||
);
|
||||
}
|
||||
nativeScreenRecording.current = false;
|
||||
nativeWindowsRecording.current = false;
|
||||
cleanupCapturedMedia();
|
||||
await stopWebcamRecorder();
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
setCountdownActive(false);
|
||||
}
|
||||
|
||||
const resumeResult = await window.electronAPI.resumeNativeScreenRecording();
|
||||
if (!resumeResult.success) {
|
||||
throw new Error(
|
||||
resumeResult.error ??
|
||||
resumeResult.message ??
|
||||
"Failed to resume native capture after countdown",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mainStartedAt = Date.now();
|
||||
micFallbackStartDelayMs.current = null;
|
||||
beginWebcamCapture();
|
||||
nativeScreenRecording.current = true;
|
||||
nativeWindowsRecording.current = useNativeWindowsCapture;
|
||||
resetRecordingClock(mainStartedAt);
|
||||
webcamTimeOffsetMs.current =
|
||||
webcamStartTime.current === null
|
||||
@@ -1581,6 +1660,22 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
}
|
||||
}
|
||||
|
||||
if (nativeWindowsCaptureStartFailed && countdownDelay > 0) {
|
||||
setCountdownActive(true);
|
||||
try {
|
||||
const result = await window.electronAPI.startCountdown(countdownDelay);
|
||||
if (!result.success || result.cancelled) {
|
||||
cleanupCapturedMedia();
|
||||
await stopWebcamRecorder();
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
setCountdownActive(false);
|
||||
}
|
||||
recordingSessionTimestamp.current = Date.now();
|
||||
resetRecordingClock(recordingSessionTimestamp.current);
|
||||
}
|
||||
|
||||
const browserCursorPolicy = resolveBrowserCaptureCursorPolicy({
|
||||
nativeWindowsCaptureStartFailed,
|
||||
});
|
||||
@@ -1922,6 +2017,14 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
);
|
||||
setRecording(false);
|
||||
try {
|
||||
if (nativeScreenRecording.current) {
|
||||
nativeScreenRecording.current = false;
|
||||
nativeWindowsRecording.current = false;
|
||||
const stoppedResult = await window.electronAPI.stopNativeScreenRecording();
|
||||
if (stoppedResult.path) {
|
||||
await window.electronAPI.deleteRecordingFile(stoppedResult.path);
|
||||
}
|
||||
}
|
||||
await window.electronAPI?.setRecordingState(false);
|
||||
} catch (stateError) {
|
||||
console.warn("Failed to reset main-process recording state:", stateError);
|
||||
@@ -2085,19 +2188,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start recording with optional countdown
|
||||
if (countdownDelay > 0) {
|
||||
setCountdownActive(true);
|
||||
try {
|
||||
const result = await window.electronAPI.startCountdown(countdownDelay);
|
||||
if (!result.success || result.cancelled) {
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
setCountdownActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
startRecording();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user