From f91e29abbaf80484aaad1f1d3dd42e8225f81f23 Mon Sep 17 00:00:00 2001
From: webadderall <131426131+webadderall@users.noreply.github.com>
Date: Fri, 17 Apr 2026 00:23:52 +1000
Subject: [PATCH] fix: address remaining timeline review feedback
---
src/components/video-editor/timeline/Item.tsx | 1 -
.../videoPlayback/zoomAnimation.test.ts | 6 +-
.../videoPlayback/zoomRegionUtils.ts | 2 +-
src/hooks/useScreenRecorder.ts | 142 +++++++++++-------
4 files changed, 91 insertions(+), 60 deletions(-)
diff --git a/src/components/video-editor/timeline/Item.tsx b/src/components/video-editor/timeline/Item.tsx
index 82b85145..44b894c2 100644
--- a/src/components/video-editor/timeline/Item.tsx
+++ b/src/components/video-editor/timeline/Item.tsx
@@ -130,7 +130,6 @@ export default function Item({
}}
onClick={(event) => {
event.stopPropagation();
- onSelect?.();
}}
>
{
it("holds the next region's focus between connected-transition end and next start", () => {
const regions: ZoomRegion[] = [
{ id: "a", startMs: 1000, endMs: 3000, depth: 2, focus: { cx: 0.2, cy: 0.2 } },
- { id: "b", startMs: 4200, endMs: 7000, depth: 3, focus: { cx: 0.7, cy: 0.7 } },
+ { id: "b", startMs: 4300, endMs: 7000, depth: 3, focus: { cx: 0.7, cy: 0.7 } },
];
- // After transition end (3000+200+1000=4200) but before b starts (4200)
- const result = findDominantRegion(regions, 4100, { connectZooms: true });
+ // After transition end (3000+200+1000=4200) but before b starts (4300)
+ const result = findDominantRegion(regions, 4250, { connectZooms: true });
expect(result.strength).toBe(1);
expect(result.region).not.toBeNull();
expect(result.region!.id).toBe("b");
diff --git a/src/components/video-editor/videoPlayback/zoomRegionUtils.ts b/src/components/video-editor/videoPlayback/zoomRegionUtils.ts
index 11a412ed..fc49e046 100644
--- a/src/components/video-editor/videoPlayback/zoomRegionUtils.ts
+++ b/src/components/video-editor/videoPlayback/zoomRegionUtils.ts
@@ -157,7 +157,7 @@ function getActiveRegion(
function getConnectedRegionHold(timeMs: number, connectedPairs: ConnectedRegionPair[]) {
for (const pair of connectedPairs) {
- if (timeMs > pair.transitionEnd && timeMs < pair.nextRegion.startMs) {
+ if (timeMs >= pair.transitionEnd && timeMs < pair.nextRegion.startMs) {
const nextScale = ZOOM_DEPTH_SCALES[pair.nextRegion.depth];
return {
region: {
diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts
index 33f9cb39..5070bd11 100644
--- a/src/hooks/useScreenRecorder.ts
+++ b/src/hooks/useScreenRecorder.ts
@@ -398,38 +398,101 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
[clearRecordingFinalizationToast],
);
+ const stopMicFallbackRecorder = useCallback((): Promise => {
+ return new Promise((resolve) => {
+ const recorder = micFallbackRecorder.current;
+ if (!recorder || recorder.state === "inactive") {
+ micFallbackRecorder.current = null;
+ resolve(null);
+ return;
+ }
+ recorder.ondataavailable = (event) => {
+ if (event.data.size > 0) {
+ micFallbackChunks.current.push(event.data);
+ }
+ };
+ recorder.onstop = () => {
+ const blob =
+ micFallbackChunks.current.length > 0
+ ? new Blob(micFallbackChunks.current, { type: recorder.mimeType })
+ : null;
+ micFallbackChunks.current = [];
+ recorder.stream.getTracks().forEach((track) => track.stop());
+ micFallbackRecorder.current = null;
+ resolve(blob);
+ };
+ recorder.stop();
+ });
+ }, []);
+
+ const storeMicrophoneSidecar = useCallback(
+ async (micFallbackBlobPromise: Promise | null | undefined, finalPath: string) => {
+ const micFallbackBlob = await micFallbackBlobPromise;
+ if (!micFallbackBlob) {
+ return;
+ }
+
+ try {
+ const arrayBuffer = await micFallbackBlob.arrayBuffer();
+ await window.electronAPI.storeMicrophoneSidecar(arrayBuffer, finalPath);
+ } catch (error) {
+ console.warn("Failed to store microphone sidecar:", error);
+ }
+ },
+ [],
+ );
+
const stopWebcamRecorder = useCallback(async () => {
const recorder = webcamRecorder.current;
const pending = webcamStopPromise.current;
if (!recorder) {
- return null;
+ const result = pending ? await pending : resolvedWebcamPath.current;
+ webcamStopPromise.current = null;
+ pendingWebcamPathPromise.current = null;
+ resolvedWebcamPath.current = result ?? null;
+ return result ?? null;
}
if (recorder.state !== "inactive") {
recorder.stop();
+ } else if (pending && webcamStopResolver.current) {
+ webcamStopResolver.current(resolvedWebcamPath.current);
+ webcamStopResolver.current = null;
}
- const result = pending ? await pending : null;
- resolvedWebcamPath.current = result;
+ const result = pending ? await pending : resolvedWebcamPath.current;
+ webcamStopPromise.current = null;
pendingWebcamPathPromise.current = null;
- return result;
+ resolvedWebcamPath.current = result ?? null;
+ return result ?? null;
}, []);
- const recoverNativeRecordingSession = useCallback(async () => {
- if (typeof window.electronAPI?.recoverNativeScreenRecording !== "function") {
- return null;
- }
+ const recoverNativeRecordingSession = useCallback(
+ async (micFallbackBlobPromise?: Promise | null) => {
+ if (typeof window.electronAPI?.recoverNativeScreenRecording !== "function") {
+ return null;
+ }
- const result = await window.electronAPI.recoverNativeScreenRecording();
- if (!result.success || !result.path) {
- return null;
- }
+ const result = await window.electronAPI.recoverNativeScreenRecording();
+ if (!result.success || !result.path) {
+ return null;
+ }
- const webcamPath = await stopWebcamRecorder();
- await finalizeRecordingSession(result.path, webcamPath);
- return result.path;
- }, [finalizeRecordingSession, stopWebcamRecorder]);
+ const resolvedMicFallbackBlobPromise =
+ micFallbackBlobPromise ?? stopMicFallbackRecorder();
+ const webcamPath = await stopWebcamRecorder();
+ await storeMicrophoneSidecar(resolvedMicFallbackBlobPromise, result.path);
+ await finalizeRecordingSession(result.path, webcamPath);
+ return result.path;
+ },
+ [
+ finalizeRecordingSession,
+ stopMicFallbackRecorder,
+ stopWebcamRecorder,
+ storeMicrophoneSidecar,
+ ],
+ );
/**
* Acquire the webcam stream and prepare the MediaRecorder, but do NOT start
@@ -573,7 +636,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
);
void logNativeCaptureDiagnostics("stop-native-screen-recording");
try {
- const recoveredPath = await recoverNativeRecordingSession();
+ const recoveredPath = await recoverNativeRecordingSession(
+ micFallbackBlobPromise,
+ );
if (recoveredPath) {
return;
}
@@ -602,16 +667,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
finalPath = muxResult?.path ?? result.path;
}
- // Save the browser-captured microphone sidecar if one was recorded.
- const micFallbackBlob = await micFallbackBlobPromise;
- if (micFallbackBlob && finalPath) {
- try {
- const arrayBuffer = await micFallbackBlob.arrayBuffer();
- await window.electronAPI.storeMicrophoneSidecar(arrayBuffer, finalPath);
- } catch (e) {
- console.warn("Failed to store microphone sidecar:", e);
- }
- }
+ await storeMicrophoneSidecar(micFallbackBlobPromise, finalPath);
await finalizeRecordingSession(finalPath, webcamPath);
})();
@@ -751,40 +807,16 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
void window.electronAPI.stopNativeScreenRecording();
}
- if (mediaRecorder.current?.state === "recording") {
- mediaRecorder.current.stop();
+ const recorder = mediaRecorder.current;
+ const recorderState = recorder?.state;
+ if (recorder && (recorderState === "recording" || recorderState === "paused")) {
+ recorder.stop();
}
cleanupCapturedMedia();
};
}, [cleanupCapturedMedia, recoverNativeRecordingSession]);
- const stopMicFallbackRecorder = useCallback((): Promise => {
- return new Promise((resolve) => {
- const recorder = micFallbackRecorder.current;
- if (!recorder || recorder.state === "inactive") {
- micFallbackRecorder.current = null;
- resolve(null);
- return;
- }
- recorder.ondataavailable = (event) => {
- if (event.data.size > 0) {
- micFallbackChunks.current.push(event.data);
- }
- };
- recorder.onstop = () => {
- const blob =
- micFallbackChunks.current.length > 0
- ? new Blob(micFallbackChunks.current, { type: recorder.mimeType })
- : null;
- micFallbackChunks.current = [];
- micFallbackRecorder.current = null;
- resolve(blob);
- };
- recorder.stop();
- });
- }, []);
-
const startRecording = async () => {
if (startInFlight.current) {
return;