Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions electron/electron-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ interface Window {
discarded?: boolean;
/** The take ended before it was stopped, but its recording was kept. */
warning?: string;
/** The stop failed and the recording was recovered from what was on disk. */
recovered?: boolean;
error?: string;
}>;
attachNativeMacWebcamRecording: (payload: {
Expand Down
95 changes: 74 additions & 21 deletions electron/ipc/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ import { findPipeWireCursorHelperPath } from "../native-bridge/cursor/recording/
import type { CursorRecordingSession } from "../native-bridge/cursor/recording/session";
import { toHelperRect } from "../native-bridge/helperCoordinates";
import { scoreDeviceNameMatch } from "../recording/deviceNameMatching";
import {
describeSalvagedTake,
nativeMacSalvageTarget,
salvageNativeMacCapture,
} from "../recording/nativeMacCaptureSalvage";
import {
type NativeMacCaptureExit,
nativeMacDiscardTargets,
Expand Down Expand Up @@ -3230,31 +3235,74 @@ export function registerIpcHandlers(
}
return { success: true, discarded: true };
}
if (!stopResult.ok) {
pendingCursorRecordingData = null;
console.error("Failed to stop native macOS recording:", {
reason: stopResult.reason,
message: stopResult.message,
helperExited: stopResult.exited,
output: (nativeMacCaptureOutputs.get(proc) ?? "").trim(),
});
return { success: false, error: stopResult.message };
}
const screenVideoPath = stopResult.screenVideoPath;
nativeMacRecordingWarning = stopResult.warning
? { screenVideoPath, message: stopResult.warning }
: null;
if (stopResult.warning) {
console.warn("[native-sck] the take ended before it was stopped; its recording was kept", {
warning: stopResult.warning,
let screenVideoPath: string;
let warning: string | undefined;
let recovered = false;
if (stopResult.ok) {
screenVideoPath = stopResult.screenVideoPath;
warning = stopResult.warning;
if (warning) {
console.warn(
"[native-sck] the take ended before it was stopped; its recording was kept",
{
warning,
path: screenVideoPath,
},
);
}
} else {
// A helper that exited left a file nothing writes to any more, and what its
// writer finished before the failure is usually a playable fragmented take.
// One still running may be mid-write, so it is left alone.
const salvageTarget = nativeMacSalvageTarget(stopResult, preferredPath);
const salvage = salvageTarget ? await salvageNativeMacCapture(salvageTarget) : null;
if (!salvage || !salvage.ok) {
pendingCursorRecordingData = null;
console.error("Failed to stop native macOS recording:", {
reason: stopResult.reason,
message: stopResult.message,
helperExited: stopResult.exited,
salvage: salvage ? salvage.reason : "not attempted: the helper had not exited",
output: (nativeMacCaptureOutputs.get(proc) ?? "").trim(),
});
return { success: false, error: stopResult.message };
}
screenVideoPath = salvage.screenVideoPath;
warning = describeSalvagedTake(stopResult.message, salvage.durationSec);
recovered = true;
console.warn("[native-sck] recovered the part of the take written before its stop failed", {
stopFailure: stopResult.message,
path: screenVideoPath,
videoSamples: salvage.videoSamples,
durationSec: salvage.durationSec,
truncatedBytes: salvage.truncatedBytes,
});
}
nativeMacRecordingWarning = warning ? { screenVideoPath, message: warning } : null;

// A recovered take most often follows a disk that filled up, and these writes
// go to the same volume. The video is already safe on disk, so for a recovered
// take a failed side write is logged, and its partial file removed, instead of
// turning the recovery back into a lost take.
const writeAlongside = async (label: string, target: string, write: () => Promise<void>) => {
if (!recovered) {
await write();
return;
}
try {
await write();
} catch (error) {
console.warn(`[native-sck] could not write the recovered take's ${label}:`, error);
await fs.rm(target, { force: true }).catch(() => undefined);
}
};

if (cursorCaptureMode === "editable-overlay") {
compactPendingCursorTelemetryPauseRanges(nativeMacPauseRanges);
shiftPendingCursorTelemetry(nativeMacCursorOffsetMs);
await writePendingCursorTelemetry(screenVideoPath);
await writeAlongside("cursor telemetry", `${screenVideoPath}.cursor.json`, () =>
writePendingCursorTelemetry(screenVideoPath),
);
}

const session: RecordingSession = {
Expand All @@ -3269,15 +3317,20 @@ export function registerIpcHandlers(
RECORDINGS_DIR,
`${path.parse(screenVideoPath).name}${RECORDING_SESSION_SUFFIX}`,
);
await fs.writeFile(sessionManifestPath, JSON.stringify(session, null, 2), "utf-8");
await writeAlongside("session manifest", sessionManifestPath, () =>
fs.writeFile(sessionManifestPath, JSON.stringify(session, null, 2), "utf-8"),
);
await registerRecordingMediaLinks(screenVideoPath, { cursorCaptureMode });

return {
success: true,
path: screenVideoPath,
session,
message: "Native macOS recording session stored successfully",
...(stopResult.warning ? { warning: stopResult.warning } : {}),
message: recovered
? "Native macOS recording recovered from a failed stop"
: "Native macOS recording session stored successfully",
...(warning ? { warning } : {}),
...(recovered ? { recovered: true } : {}),
};
} catch (error) {
console.error("Failed to stop native macOS recording:", error);
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Loading