diff --git a/electron/ipc/handlers.ts b/electron/ipc/handlers.ts index 82440c8..7738c48 100644 --- a/electron/ipc/handlers.ts +++ b/electron/ipc/handlers.ts @@ -68,6 +68,53 @@ function setCurrentRecordingSessionState(session: RecordingSession | null) { currentRecordingSession = session; } +function getSessionManifestPathForVideo(videoPath: string) { + const parsed = path.parse(videoPath); + const baseName = parsed.name.endsWith("-webcam") + ? parsed.name.slice(0, -"-webcam".length) + : parsed.name; + return path.join(parsed.dir, `${baseName}${RECORDING_SESSION_SUFFIX}`); +} + +async function loadRecordedSessionForVideoPath( + videoPath: string, +): Promise { + const normalizedVideoPath = normalizeVideoSourcePath(videoPath); + if (!normalizedVideoPath) { + return null; + } + + try { + const manifestPath = getSessionManifestPathForVideo(normalizedVideoPath); + const content = await fs.readFile(manifestPath, "utf-8"); + const session = normalizeRecordingSession(JSON.parse(content)); + if (!session) { + return null; + } + + const normalizedSession: RecordingSession = { + ...session, + screenVideoPath: normalizeVideoSourcePath(session.screenVideoPath) ?? session.screenVideoPath, + ...(session.webcamVideoPath + ? { + webcamVideoPath: + normalizeVideoSourcePath(session.webcamVideoPath) ?? session.webcamVideoPath, + } + : {}), + }; + + const targetPath = normalizePath(normalizedVideoPath); + const screenMatches = normalizePath(normalizedSession.screenVideoPath) === targetPath; + const webcamMatches = normalizedSession.webcamVideoPath + ? normalizePath(normalizedSession.webcamVideoPath) === targetPath + : false; + + return screenMatches || webcamMatches ? normalizedSession : null; + } catch { + return null; + } +} + async function storeRecordedSessionFiles(payload: StoreRecordedSessionInput) { const createdAt = typeof payload.createdAt === "number" && Number.isFinite(payload.createdAt) @@ -675,11 +722,16 @@ export function registerIpcHandlers( : { success: false }; }); - ipcMain.handle("set-current-video-path", (_, path: string) => { - setCurrentRecordingSessionState({ - screenVideoPath: normalizeVideoSourcePath(path) ?? path, - createdAt: Date.now(), - }); + ipcMain.handle("set-current-video-path", async (_, path: string) => { + const restoredSession = await loadRecordedSessionForVideoPath(path); + if (restoredSession) { + setCurrentRecordingSessionState(restoredSession); + } else { + setCurrentRecordingSessionState({ + screenVideoPath: normalizeVideoSourcePath(path) ?? path, + createdAt: Date.now(), + }); + } currentProjectPath = null; return { success: true }; }); diff --git a/src/lib/exporter/gifExporter.ts b/src/lib/exporter/gifExporter.ts index 2b4195c..af49ce2 100644 --- a/src/lib/exporter/gifExporter.ts +++ b/src/lib/exporter/gifExporter.ts @@ -167,6 +167,7 @@ export class GifExporter { let frameIndex = 0; webcamFrameQueue = this.config.webcamVideoUrl ? new AsyncVideoFrameQueue() : null; + let stopWebcamDecode = false; let webcamDecodeError: Error | null = null; const webcamDecodePromise = this.webcamDecoder && webcamFrameQueue @@ -178,9 +179,13 @@ export class GifExporter { this.config.trimRegions, this.config.speedRegions, async (webcamFrame) => { - while (queue.length >= 12 && !this.cancelled) { + while (queue.length >= 12 && !this.cancelled && !stopWebcamDecode) { await new Promise((resolve) => setTimeout(resolve, 2)); } + if (this.cancelled || stopWebcamDecode) { + webcamFrame.close(); + return; + } queue.enqueue(webcamFrame); }, ) @@ -248,6 +253,9 @@ export class GifExporter { return { success: false, error: "Export cancelled" }; } + stopWebcamDecode = true; + webcamFrameQueue?.destroy(); + this.webcamDecoder?.cancel(); await webcamDecodePromise; // Update progress to show we're now in the finalizing phase diff --git a/src/lib/exporter/videoExporter.ts b/src/lib/exporter/videoExporter.ts index d2f7b0a..c80d470 100644 --- a/src/lib/exporter/videoExporter.ts +++ b/src/lib/exporter/videoExporter.ts @@ -118,6 +118,7 @@ export class VideoExporter { const frameDuration = 1_000_000 / this.config.frameRate; // in microseconds let frameIndex = 0; webcamFrameQueue = this.config.webcamVideoUrl ? new AsyncVideoFrameQueue() : null; + let stopWebcamDecode = false; let webcamDecodeError: Error | null = null; const webcamDecodePromise = this.webcamDecoder && webcamFrameQueue @@ -129,9 +130,13 @@ export class VideoExporter { this.config.trimRegions, this.config.speedRegions, async (webcamFrame) => { - while (queue.length >= 12 && !this.cancelled) { + while (queue.length >= 12 && !this.cancelled && !stopWebcamDecode) { await new Promise((resolve) => setTimeout(resolve, 2)); } + if (this.cancelled || stopWebcamDecode) { + webcamFrame.close(); + return; + } queue.enqueue(webcamFrame); }, ) @@ -229,6 +234,9 @@ export class VideoExporter { return { success: false, error: "Export cancelled" }; } + stopWebcamDecode = true; + webcamFrameQueue?.destroy(); + this.webcamDecoder?.cancel(); await webcamDecodePromise; // Finalize encoding