Skip to content

Commit f261fa4

Browse files
committed
fix(mac): count the HUD timer from the helper's first frame (#901)
The start IPC now returns the helper's recording-started timestamp, the instant its first frame reached the file, and the HUD anchors to it instead of to the reply's arrival. The cursor offset uses the same anchor.
1 parent 6e27423 commit f261fa4

4 files changed

Lines changed: 36 additions & 5 deletions

File tree

‎electron/ipc/handlers.ts‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1682,8 +1682,12 @@ function attachNativeMacCaptureOutputDrain(
16821682
});
16831683
}
16841684

1685+
/**
1686+
* Resolves with the helper's own stamp of its first appended frame: the file's t=0, and
1687+
* so the instant the HUD timer counts from (#901). Null from a helper that sends none.
1688+
*/
16851689
function waitForNativeMacCaptureStart(proc: ChildProcessWithoutNullStreams) {
1686-
return new Promise<void>((resolve, reject) => {
1690+
return new Promise<number | null>((resolve, reject) => {
16871691
const timer = setTimeout(() => {
16881692
cleanup();
16891693
reject(new Error("Timed out waiting for native macOS capture to start"));
@@ -1692,7 +1696,11 @@ function waitForNativeMacCaptureStart(proc: ChildProcessWithoutNullStreams) {
16921696
const inspect = (event: Record<string, unknown>) => {
16931697
if (event.event === "recording-started") {
16941698
cleanup();
1695-
resolve();
1699+
resolve(
1700+
typeof event.timestampMs === "number" && Number.isFinite(event.timestampMs)
1701+
? event.timestampMs
1702+
: null,
1703+
);
16961704
return;
16971705
}
16981706
if (event.event === "error") {
@@ -3165,8 +3173,7 @@ export function registerIpcHandlers(
31653173
},
31663174
);
31673175

3168-
await waitForNativeMacCaptureStart(proc);
3169-
const captureStartedAtMs = Date.now();
3176+
const captureStartedAtMs = (await waitForNativeMacCaptureStart(proc)) ?? Date.now();
31703177
const microphoneDefaulted =
31713178
request.audio.microphone.enabled && readMicrophoneDefaulted(nativeMacCaptureOutput);
31723179
if (microphoneDefaulted) {
@@ -3201,6 +3208,7 @@ export function registerIpcHandlers(
32013208
recordingId,
32023209
path: outputPath,
32033210
helperPath,
3211+
startedAtMs: captureStartedAtMs,
32043212
microphoneDefaulted,
32053213
microphoneUnavailable,
32063214
};

‎src/hooks/useScreenRecorder.nativeMacStartWarning.test.tsx‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,25 @@ describe("useScreenRecorder native macOS start warnings", () => {
287287
expect(view.result.current.recording).toBe(true);
288288
});
289289

290+
// The file starts at the helper's first frame, however late the start reply lands (#901).
291+
it("counts the HUD timer from the helper's first frame, not from the start reply", async () => {
292+
api.startNativeMacRecording.mockImplementation(async () => ({
293+
success: true,
294+
recordingId: 9,
295+
startedAtMs: Date.now() - 20_000,
296+
}));
297+
const view = renderHook(() => useScreenRecorder());
298+
await settle();
299+
300+
await act(async () => {
301+
view.result.current.toggleRecording();
302+
});
303+
await settle(3_500);
304+
305+
expect(view.result.current.recording).toBe(true);
306+
expect(view.result.current.elapsedSeconds).toBe(20);
307+
});
308+
290309
it("does not warn after the recording start is cancelled", async () => {
291310
let resolveStart:
292311
| ((result: Awaited<ReturnType<ElectronAPI["startNativeMacRecording"]>>) => void)

‎src/hooks/useScreenRecorder.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1465,7 +1465,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
14651465
};
14661466
webcamRecorder.current = nativeWebcamRecorder;
14671467
accumulatedDurationMs.current = 0;
1468-
segmentStartedAt.current = Date.now();
1468+
// From the file's first frame, not from this reply, so the timer reads what the
1469+
// file holds however late the reply arrives (#901).
1470+
segmentStartedAt.current = result.startedAtMs ?? Date.now();
14691471
allowAutoFinalize.current = true;
14701472
setRecording(true);
14711473
setPaused(false);

‎src/lib/nativeMacRecording.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ export type NativeMacRecordingStartResult = {
9494
recordingId?: number;
9595
path?: string;
9696
helperPath?: string;
97+
/** Wall-clock ms of the take's first appended frame: the file's t=0. */
98+
startedAtMs?: number;
9799
/** The helper could not resolve the selected device and is using the system default. */
98100
microphoneDefaulted?: boolean;
99101
/** The microphone was asked for, but this macOS cannot capture it: the take has no voice. */

0 commit comments

Comments
 (0)