feat: add arrow key frame-by-frame playhead navigation (#302)

This commit is contained in:
theaiagent
2026-04-03 17:50:53 +03:00
parent baa30a9d6a
commit e5430eed39
3 changed files with 39 additions and 10 deletions
@@ -20,6 +20,7 @@ import {
type GifSizePreset,
VideoExporter,
} from "@/lib/exporter";
import { computeFrameStepTime } from "@/lib/frameStep";
import type { ProjectMedia } from "@/lib/recordingSession";
import { matchesShortcut } from "@/lib/shortcuts";
import {
@@ -98,6 +99,10 @@ export default function VideoEditor() {
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
const currentTimeRef = useRef(currentTime);
currentTimeRef.current = currentTime;
const durationRef = useRef(duration);
durationRef.current = duration;
const [cursorTelemetry, setCursorTelemetry] = useState<CursorTelemetryPoint[]>([]);
const [selectedZoomId, setSelectedZoomId] = useState<string | null>(null);
const [selectedTrimId, setSelectedTrimId] = useState<string | null>(null);
@@ -926,6 +931,29 @@ export default function VideoEditor() {
return;
}
// Frame-step navigation (arrow keys, no modifiers)
if (
(e.key === "ArrowLeft" || e.key === "ArrowRight") &&
!e.ctrlKey &&
!e.metaKey &&
!e.shiftKey &&
!e.altKey
) {
const target = e.target;
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
return;
}
e.preventDefault();
const direction = e.key === "ArrowLeft" ? "backward" : "forward";
const newTime = computeFrameStepTime(
currentTimeRef.current,
durationRef.current,
direction,
);
handleSeek(newTime);
return;
}
const isInput =
e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement;
+1 -10
View File
@@ -1,15 +1,6 @@
import { describe, expect, it } from "vitest";
const FRAME_DURATION_SEC = 1 / 60;
function computeFrameStepTime(
currentTime: number,
duration: number,
direction: "forward" | "backward",
): number {
const delta = direction === "forward" ? FRAME_DURATION_SEC : -FRAME_DURATION_SEC;
return Math.min(duration, Math.max(0, currentTime + delta));
}
import { computeFrameStepTime, FRAME_DURATION_SEC } from "@/lib/frameStep";
describe("computeFrameStepTime", () => {
const duration = 10;
+10
View File
@@ -0,0 +1,10 @@
export const FRAME_DURATION_SEC = 1 / 60;
export function computeFrameStepTime(
currentTime: number,
duration: number,
direction: "forward" | "backward",
): number {
const delta = direction === "forward" ? FRAME_DURATION_SEC : -FRAME_DURATION_SEC;
return Math.min(duration, Math.max(0, currentTime + delta));
}