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
16 changes: 12 additions & 4 deletions electron/ipc/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1682,8 +1682,12 @@ function attachNativeMacCaptureOutputDrain(
});
}

/**
* Resolves with the helper's own stamp of its first appended frame: the file's t=0, and
* so the instant the HUD timer counts from (#901). Null from a helper that sends none.
*/
function waitForNativeMacCaptureStart(proc: ChildProcessWithoutNullStreams) {
return new Promise<void>((resolve, reject) => {
return new Promise<number | null>((resolve, reject) => {
const timer = setTimeout(() => {
cleanup();
reject(new Error("Timed out waiting for native macOS capture to start"));
Expand All @@ -1692,7 +1696,11 @@ function waitForNativeMacCaptureStart(proc: ChildProcessWithoutNullStreams) {
const inspect = (event: Record<string, unknown>) => {
if (event.event === "recording-started") {
cleanup();
resolve();
resolve(
typeof event.timestampMs === "number" && Number.isFinite(event.timestampMs)
? event.timestampMs
: null,
);
return;
}
if (event.event === "error") {
Expand Down Expand Up @@ -3165,8 +3173,7 @@ export function registerIpcHandlers(
},
);

await waitForNativeMacCaptureStart(proc);
const captureStartedAtMs = Date.now();
const captureStartedAtMs = (await waitForNativeMacCaptureStart(proc)) ?? Date.now();
const microphoneDefaulted =
request.audio.microphone.enabled && readMicrophoneDefaulted(nativeMacCaptureOutput);
if (microphoneDefaulted) {
Expand Down Expand Up @@ -3201,6 +3208,7 @@ export function registerIpcHandlers(
recordingId,
path: outputPath,
helperPath,
startedAtMs: captureStartedAtMs,
microphoneDefaulted,
microphoneUnavailable,
};
Expand Down
19 changes: 19 additions & 0 deletions src/hooks/useScreenRecorder.nativeMacStartWarning.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,25 @@ describe("useScreenRecorder native macOS start warnings", () => {
expect(view.result.current.recording).toBe(true);
});

// The file starts at the helper's first frame, however late the start reply lands (#901).
it("counts the HUD timer from the helper's first frame, not from the start reply", async () => {
api.startNativeMacRecording.mockImplementation(async () => ({
success: true,
recordingId: 9,
startedAtMs: Date.now() - 20_000,
}));
const view = renderHook(() => useScreenRecorder());
await settle();

await act(async () => {
view.result.current.toggleRecording();
});
await settle(3_500);

expect(view.result.current.recording).toBe(true);
expect(view.result.current.elapsedSeconds).toBe(20);
});

it("does not warn after the recording start is cancelled", async () => {
let resolveStart:
| ((result: Awaited<ReturnType<ElectronAPI["startNativeMacRecording"]>>) => void)
Expand Down
4 changes: 3 additions & 1 deletion src/hooks/useScreenRecorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1465,7 +1465,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
};
webcamRecorder.current = nativeWebcamRecorder;
accumulatedDurationMs.current = 0;
segmentStartedAt.current = Date.now();
// From the file's first frame, not from this reply, so the timer reads what the
// file holds however late the reply arrives (#901).
segmentStartedAt.current = result.startedAtMs ?? Date.now();
allowAutoFinalize.current = true;
setRecording(true);
setPaused(false);
Expand Down
2 changes: 2 additions & 0 deletions src/lib/nativeMacRecording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ export type NativeMacRecordingStartResult = {
recordingId?: number;
path?: string;
helperPath?: string;
/** Wall-clock ms of the take's first appended frame: the file's t=0. */
startedAtMs?: number;
/** The helper could not resolve the selected device and is using the system default. */
microphoneDefaulted?: boolean;
/** The microphone was asked for, but this macOS cannot capture it: the take has no voice. */
Expand Down
Loading