diff --git a/electron/ipc/recording/storagePath.test.ts b/electron/ipc/recording/storagePath.test.ts index dced4f52e..cab90c909 100644 --- a/electron/ipc/recording/storagePath.test.ts +++ b/electron/ipc/recording/storagePath.test.ts @@ -1,6 +1,6 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; -import { resolveRecordedVideoStoragePath } from "./storagePath"; +import { isWebcamCompanionRecordingPath, resolveRecordedVideoStoragePath } from "./storagePath"; describe("resolveRecordedVideoStoragePath", () => { const recordingsDir = path.resolve("recordings-root"); @@ -51,3 +51,23 @@ describe("resolveRecordedVideoStoragePath", () => { ); }); }); + +describe("isWebcamCompanionRecordingPath", () => { + const recordingsDir = path.resolve("recordings-root"); + + it.each([ + "recording-1720588800000-webcam.webm", + "recording-1720588800000-webcam.mp4", + ])("detects a webcam companion recording: %s", (fileName) => { + expect(isWebcamCompanionRecordingPath(path.join(recordingsDir, fileName))).toBe(true); + }); + + it.each([ + "recording-1720588800000.webm", + "recording-1720588800000.mp4", + "recording-webcam.webm", + "recording-1720588800000-webcam.mov", + ])("does not treat other files as webcam companions: %s", (fileName) => { + expect(isWebcamCompanionRecordingPath(path.join(recordingsDir, fileName))).toBe(false); + }); +}); diff --git a/electron/ipc/recording/storagePath.ts b/electron/ipc/recording/storagePath.ts index 12c3484f6..426c78ef0 100644 --- a/electron/ipc/recording/storagePath.ts +++ b/electron/ipc/recording/storagePath.ts @@ -1,6 +1,7 @@ import path from "node:path"; const RECORDED_VIDEO_FILE_NAME = /^recording-[0-9]+(?:-webcam)?\.(?:webm|mp4)$/; +const WEBCAM_COMPANION_FILE_NAME = /^recording-[0-9]+-webcam\.(?:webm|mp4)$/; export function resolveRecordedVideoStoragePath(recordingsDir: string, fileName: unknown): string { if (typeof fileName !== "string" || RECORDED_VIDEO_FILE_NAME.exec(fileName)?.[0] !== fileName) { @@ -22,3 +23,7 @@ export function resolveRecordedVideoStoragePath(recordingsDir: string, fileName: return candidatePath; } + +export function isWebcamCompanionRecordingPath(filePath: string): boolean { + return WEBCAM_COMPANION_FILE_NAME.test(path.basename(filePath)); +} diff --git a/electron/ipc/register/recording.ts b/electron/ipc/register/recording.ts index 797f8badd..9ae15ae79 100644 --- a/electron/ipc/register/recording.ts +++ b/electron/ipc/register/recording.ts @@ -71,7 +71,10 @@ import { waitForNativeCaptureStart, waitForNativeCaptureStop, } from "../recording/mac"; -import { resolveRecordedVideoStoragePath } from "../recording/storagePath"; +import { + isWebcamCompanionRecordingPath, + resolveRecordedVideoStoragePath, +} from "../recording/storagePath"; import { attachWindowsCaptureLifecycle, isNativeWindowsCaptureAvailable, @@ -1801,6 +1804,13 @@ export function registerRecordingHandlers( const recordingsDir = await getRecordingsDir(); const videoPath = resolveRecordedVideoStoragePath(recordingsDir, fileName); await fs.writeFile(videoPath, Buffer.from(videoData)); + if (isWebcamCompanionRecordingPath(videoPath)) { + // The webcam companion is stored before the screen recording. Finalizing it + // would persist (and clear) the session's cursor telemetry next to the webcam + // file, leaving the screen recording without cursor data. + await validateRecordedVideo(videoPath); + return { success: true, path: videoPath }; + } return await finalizeStoredVideo(videoPath); } catch (error) { console.error("Failed to store video:", error);