fix: model editor time as wasm MediaTime ticks end-to-end

This commit is contained in:
Maze Winther
2026-04-26 02:50:04 +02:00
parent f1b45b4328
commit eea6d43c82
79 changed files with 1589 additions and 511 deletions
+23 -10
View File
@@ -3,7 +3,12 @@ import type { ComputeDropTargetParams, DropTarget } from "@/timeline";
import { resolveTrackPlacement } from "@/timeline/placement";
import { TIMELINE_TRACK_GAP_PX } from "./layout";
import { getTrackHeight } from "./track-layout";
import { TICKS_PER_SECOND } from "@/wasm";
import {
mediaTime,
type MediaTime,
roundMediaTime,
TICKS_PER_SECOND,
} from "@/wasm";
function findElementAtPosition({
mouseX,
@@ -20,9 +25,11 @@ function findElementAtPosition({
pixelsPerSecond: number;
zoomLevel: number;
}): { elementId: string; trackId: string } | null {
const time = Math.round(
(mouseX / (pixelsPerSecond * zoomLevel)) * TICKS_PER_SECOND,
);
const time = mediaTime({
ticks: Math.round(
(mouseX / (pixelsPerSecond * zoomLevel)) * TICKS_PER_SECOND,
),
});
const track = tracks[trackIndex];
if (!track || !("elements" in track)) return null;
@@ -82,7 +89,7 @@ const EMPTY_TARGET_ELEMENT = null;
function fallbackNewTrackDropTarget({
xPosition,
}: {
xPosition: number;
xPosition: MediaTime;
}): DropTarget {
return {
trackIndex: 0,
@@ -111,13 +118,16 @@ export function computeDropTarget({
const orderedTracks = [...tracks.overlay, tracks.main, ...tracks.audio];
const mainTrackIndex = tracks.overlay.length;
const xPosition =
typeof startTimeOverride === "number"
startTimeOverride !== undefined
? startTimeOverride
: isExternalDrop
? playheadTime
: Math.round(
Math.max(0, mouseX / (pixelsPerSecond * zoomLevel)) * TICKS_PER_SECOND,
);
: mediaTime({
ticks: Math.round(
Math.max(0, mouseX / (pixelsPerSecond * zoomLevel)) *
TICKS_PER_SECOND,
),
});
if (orderedTracks.length === 0) {
const placementResult = resolveTrackPlacement({
@@ -221,7 +231,10 @@ export function computeDropTarget({
}
if (placementResult.kind === "existingTrack") {
const adjustedXPosition = placementResult.adjustedStartTime ?? xPosition;
const adjustedXPosition =
placementResult.adjustedStartTime !== undefined
? roundMediaTime({ time: placementResult.adjustedStartTime })
: xPosition;
return {
trackIndex: placementResult.trackIndex,
+2 -1
View File
@@ -29,6 +29,7 @@ import {
useState,
type ReactNode,
} from "react";
import type { MediaTime } from "@/wasm";
import type { ElementDragState, DropTarget } from "@/timeline";
import { TimelineTrackContent } from "./timeline-track";
import { TimelinePlayhead } from "./timeline-playhead";
@@ -132,7 +133,7 @@ export function Timeline() {
[scene],
);
const mainTrackId = scene?.tracks.main.id ?? null;
const seek = (time: number) => editor.playback.seek({ time });
const seek = (time: MediaTime) => editor.playback.seek({ time });
const timelineRef = useRef<HTMLDivElement>(null);
const timelineHeaderRef = useRef<HTMLDivElement>(null);
@@ -49,7 +49,7 @@ import {
import { buildWaveformGainSamples } from "@/timeline/audio-state";
import { getTimelinePixelsPerSecond } from "@/timeline";
import { buildWaveformSourceKey } from "@/media/waveform-summary";
import { TICKS_PER_SECOND } from "@/wasm/ticks";
import { addMediaTime, TICKS_PER_SECOND, ZERO_MEDIA_TIME } from "@/wasm";
import {
getActionDefinition,
type TAction,
@@ -257,10 +257,11 @@ export function TimelineElement({
isBeingDragged && dragState.isDragging
? dragState.currentMouseY - dragState.startMouseY
: 0;
const dragTimeOffset = dragState.dragTimeOffsets[element.id] ?? 0;
const dragTimeOffset =
dragState.dragTimeOffsets[element.id] ?? ZERO_MEDIA_TIME;
const elementStartTime =
isBeingDragged && dragState.isDragging
? dragState.currentTime + dragTimeOffset
? addMediaTime({ a: dragState.currentTime, b: dragTimeOffset })
: renderElement.startTime;
const displayedStartTime = elementStartTime;
const displayedDuration = renderElement.duration;
@@ -7,7 +7,14 @@ import {
timelineTimeToSnappedPixels,
} from "@/timeline";
import { useTimelinePlayhead } from "@/timeline/hooks/use-timeline-playhead";
import { TICKS_PER_SECOND } from "@/wasm";
import {
addMediaTime,
maxMediaTime,
mediaTime,
subMediaTime,
TICKS_PER_SECOND,
ZERO_MEDIA_TIME,
} from "@/wasm";
import { useEditor } from "@/editor/use-editor";
import { TIMELINE_SCROLLBAR_SIZE_PX } from "./layout";
import { TIMELINE_LAYERS } from "./layers";
@@ -72,17 +79,24 @@ export function TimelinePlayhead({
event.preventDefault();
const fps = editor.project.getActive().settings.fps;
const ticksPerFrame = Math.round(
(TICKS_PER_SECOND * fps.denominator) / fps.numerator,
);
const ticksPerFrame = mediaTime({
ticks: Math.round(
(TICKS_PER_SECOND * fps.denominator) / fps.numerator,
),
});
const direction = event.key === "ArrowRight" ? 1 : -1;
const now = editor.playback.getCurrentTime();
const nextTime = Math.max(
0,
Math.min(duration, now + direction * ticksPerFrame),
);
const nextTime =
direction > 0
? addMediaTime({ a: now, b: ticksPerFrame })
: subMediaTime({ a: now, b: ticksPerFrame });
editor.playback.seek({ time: nextTime });
editor.playback.seek({
time: maxMediaTime({
a: ZERO_MEDIA_TIME,
b: duration < nextTime ? duration : nextTime,
}),
});
};
return (