diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index d724e2dd..8b634980 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -151,12 +151,12 @@ interface Window { message?: string; error?: string; }>; - pauseCursorCapture: () => Promise<{ + pauseCursorCapture: (boundaryMs?: number) => Promise<{ success: boolean; message?: string; error?: string; }>; - resumeCursorCapture: () => Promise<{ + resumeCursorCapture: (boundaryMs?: number) => Promise<{ success: boolean; message?: string; error?: string; diff --git a/electron/ipc/cursor/telemetry.ts b/electron/ipc/cursor/telemetry.ts index 20146a38..18b8a174 100644 --- a/electron/ipc/cursor/telemetry.ts +++ b/electron/ipc/cursor/telemetry.ts @@ -168,9 +168,9 @@ export function pushCursorSample( } } -export function sampleCursorPoint() { +export function sampleCursorPoint(sampledAtMs = Date.now()) { const point = getNormalizedCursorPoint(); - pushCursorSample(point.cx, point.cy, getCursorCaptureElapsedMs(), "move"); + pushCursorSample(point.cx, point.cy, getCursorCaptureElapsedMs(sampledAtMs), "move"); } export async function persistPendingCursorTelemetry(videoPath: string) { diff --git a/electron/ipc/register/recording.ts b/electron/ipc/register/recording.ts index e7f4a5e8..0e491b80 100644 --- a/electron/ipc/register/recording.ts +++ b/electron/ipc/register/recording.ts @@ -1312,15 +1312,23 @@ export function registerRecordingHandlers( } }); - ipcMain.handle("pause-cursor-capture", () => { - sampleCursorPoint(); - pauseCursorCapture(Date.now()); + ipcMain.handle("pause-cursor-capture", (_event, boundaryMs?: number) => { + const timestamp = + typeof boundaryMs === "number" && Number.isFinite(boundaryMs) + ? boundaryMs + : Date.now(); + sampleCursorPoint(timestamp); + pauseCursorCapture(timestamp); return { success: true }; }); - ipcMain.handle("resume-cursor-capture", () => { - resumeCursorCapture(Date.now()); - sampleCursorPoint(); + ipcMain.handle("resume-cursor-capture", (_event, boundaryMs?: number) => { + const timestamp = + typeof boundaryMs === "number" && Number.isFinite(boundaryMs) + ? boundaryMs + : Date.now(); + resumeCursorCapture(timestamp); + sampleCursorPoint(timestamp); return { success: true }; }); diff --git a/electron/preload.ts b/electron/preload.ts index 441c1b0e..c9e464f2 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -293,11 +293,11 @@ contextBridge.exposeInMainWorld("electronAPI", { resumeNativeScreenRecording: () => { return ipcRenderer.invoke("resume-native-screen-recording"); }, - pauseCursorCapture: () => { - return ipcRenderer.invoke("pause-cursor-capture"); + pauseCursorCapture: (boundaryMs?: number) => { + return ipcRenderer.invoke("pause-cursor-capture", boundaryMs); }, - resumeCursorCapture: () => { - return ipcRenderer.invoke("resume-cursor-capture"); + resumeCursorCapture: (boundaryMs?: number) => { + return ipcRenderer.invoke("resume-cursor-capture", boundaryMs); }, startFfmpegRecording: (source: ProcessedDesktopSource) => { return ipcRenderer.invoke("start-ffmpeg-recording", source); diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 4a31c95c..994d3e02 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -1440,12 +1440,32 @@ export function useScreenRecorder(): UseScreenRecorderReturn { if (webcamRecorder.current?.state === "recording") { webcamRecorder.current.pause(); } + const boundaryMs = Date.now(); try { - await window.electronAPI.pauseCursorCapture(); + await window.electronAPI.pauseCursorCapture(boundaryMs); } catch (error) { console.warn("Failed to pause cursor capture:", error); + try { + const rollbackResult = + await window.electronAPI.resumeNativeScreenRecording(); + if (!rollbackResult.success) { + console.warn( + "Failed to roll back native pause after cursor pause failure:", + rollbackResult.error ?? rollbackResult.message, + ); + } + } catch (rollbackError) { + console.warn( + "Failed to roll back native pause after cursor pause failure:", + rollbackError, + ); + } + if (webcamRecorder.current?.state === "paused") { + webcamRecorder.current.resume(); + } + return; } - markRecordingPaused(Date.now()); + markRecordingPaused(boundaryMs); setPaused(true); })(); return; @@ -1455,13 +1475,21 @@ export function useScreenRecorder(): UseScreenRecorderReturn { if (webcamRecorder.current?.state === "recording") { webcamRecorder.current.pause(); } + const boundaryMs = Date.now(); void (async () => { try { - await window.electronAPI.pauseCursorCapture(); + await window.electronAPI.pauseCursorCapture(boundaryMs); } catch (error) { console.warn("Failed to pause cursor capture:", error); + if (mediaRecorder.current?.state === "paused") { + mediaRecorder.current.resume(); + } + if (webcamRecorder.current?.state === "paused") { + webcamRecorder.current.resume(); + } + return; } - markRecordingPaused(Date.now()); + markRecordingPaused(boundaryMs); setPaused(true); })(); } @@ -1483,12 +1511,32 @@ export function useScreenRecorder(): UseScreenRecorderReturn { if (webcamRecorder.current?.state === "paused") { webcamRecorder.current.resume(); } + const boundaryMs = Date.now(); try { - await window.electronAPI.resumeCursorCapture(); + await window.electronAPI.resumeCursorCapture(boundaryMs); } catch (error) { console.warn("Failed to resume cursor capture:", error); + try { + const rollbackResult = + await window.electronAPI.pauseNativeScreenRecording(); + if (!rollbackResult.success) { + console.warn( + "Failed to roll back native resume after cursor resume failure:", + rollbackResult.error ?? rollbackResult.message, + ); + } + } catch (rollbackError) { + console.warn( + "Failed to roll back native resume after cursor resume failure:", + rollbackError, + ); + } + if (webcamRecorder.current?.state === "recording") { + webcamRecorder.current.pause(); + } + return; } - markRecordingResumed(Date.now()); + markRecordingResumed(boundaryMs); setPaused(false); })(); return; @@ -1498,13 +1546,21 @@ export function useScreenRecorder(): UseScreenRecorderReturn { if (webcamRecorder.current?.state === "paused") { webcamRecorder.current.resume(); } + const boundaryMs = Date.now(); void (async () => { try { - await window.electronAPI.resumeCursorCapture(); + await window.electronAPI.resumeCursorCapture(boundaryMs); } catch (error) { console.warn("Failed to resume cursor capture:", error); + if (mediaRecorder.current?.state === "recording") { + mediaRecorder.current.pause(); + } + if (webcamRecorder.current?.state === "recording") { + webcamRecorder.current.pause(); + } + return; } - markRecordingResumed(Date.now()); + markRecordingResumed(boundaryMs); setPaused(false); })(); }