From eea6d43c82e1fd09d94d73001090dad205b1f1d2 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Sun, 26 Apr 2026 02:50:04 +0200 Subject: fix: model editor time as wasm MediaTime ticks end-to-end --- apps/web/src/core/managers/clipboard-manager.ts | 5 ++-- apps/web/src/core/managers/playback-manager.ts | 36 +++++++++++++++---------- apps/web/src/core/managers/scenes-manager.ts | 19 ++++++------- apps/web/src/core/managers/timeline-manager.ts | 25 ++++++++--------- 4 files changed, 48 insertions(+), 37 deletions(-) (limited to 'apps/web/src/core/managers') diff --git a/apps/web/src/core/managers/clipboard-manager.ts b/apps/web/src/core/managers/clipboard-manager.ts index 8a2a51c9..283a78ae 100644 --- a/apps/web/src/core/managers/clipboard-manager.ts +++ b/apps/web/src/core/managers/clipboard-manager.ts @@ -6,6 +6,7 @@ import { type CopyContext, type PasteContext, } from "@/clipboard"; +import type { MediaTime } from "@/wasm"; export class ClipboardManager { private entry: ClipboardEntry | null = null; @@ -34,7 +35,7 @@ export class ClipboardManager { return true; } - paste({ time }: { time?: number } = {}): boolean { + paste({ time }: { time?: MediaTime } = {}): boolean { if (!this.entry) { return false; } @@ -64,7 +65,7 @@ export class ClipboardManager { }; } - private getPasteContext({ time }: { time?: number }): PasteContext { + private getPasteContext({ time }: { time?: MediaTime }): PasteContext { return { editor: this.editor, selectedElements: this.editor.selection.getSelectedElements(), diff --git a/apps/web/src/core/managers/playback-manager.ts b/apps/web/src/core/managers/playback-manager.ts index 1ea09826..6748fd42 100644 --- a/apps/web/src/core/managers/playback-manager.ts +++ b/apps/web/src/core/managers/playback-manager.ts @@ -1,10 +1,16 @@ import type { EditorCore } from "@/core"; -import { TICKS_PER_SECOND } from "@/wasm"; +import { + addMediaTime, + clampMediaTime, + type MediaTime, + mediaTimeFromSeconds, + ZERO_MEDIA_TIME, +} from "@/wasm"; import { roundToFrame } from "opencut-wasm"; export class PlaybackManager { private isPlaying = false; - private currentTime = 0; + private currentTime: MediaTime = ZERO_MEDIA_TIME; private volume = 1; private muted = false; private previousVolume = 1; @@ -12,7 +18,7 @@ export class PlaybackManager { private listeners = new Set<() => void>(); private playbackTimer: number | null = null; private playbackStartWallTime = 0; - private playbackStartTime = 0; + private playbackStartTime: MediaTime = ZERO_MEDIA_TIME; private timelineScopeBound = false; constructor(private editor: EditorCore) {} @@ -38,7 +44,7 @@ export class PlaybackManager { } if (this.currentTime >= maxTime) { - this.seek({ time: 0 }); + this.seek({ time: ZERO_MEDIA_TIME }); } this.isPlaying = true; @@ -60,7 +66,7 @@ export class PlaybackManager { } } - seek({ time }: { time: number }): void { + seek({ time }: { time: MediaTime }): void { this.currentTime = this.clampTimeToTimeline(time); if (this.isPlaying) { this.playbackStartWallTime = performance.now(); @@ -107,7 +113,7 @@ export class PlaybackManager { return this.isPlaying; } - getCurrentTime(): number { + getCurrentTime(): MediaTime { return this.currentTime; } @@ -185,11 +191,13 @@ export class PlaybackManager { const fps = this.editor.project.getActive()?.settings.fps; const elapsedSeconds = (performance.now() - this.playbackStartWallTime) / 1000; - const rawTime = - this.playbackStartTime + Math.round(elapsedSeconds * TICKS_PER_SECOND); - const newTime = fps + const rawTime = addMediaTime({ + a: this.playbackStartTime, + b: mediaTimeFromSeconds({ seconds: elapsedSeconds }), + }); + const newTime = (fps ? (roundToFrame({ time: rawTime, rate: fps }) ?? rawTime) - : rawTime; + : rawTime) as MediaTime; const maxTime = this.editor.timeline.getTotalDuration(); if (newTime >= maxTime) { @@ -205,12 +213,12 @@ export class PlaybackManager { this.playbackTimer = requestAnimationFrame(this.updateTime); }; - private clampTimeToTimeline(time: number): number { + private clampTimeToTimeline(time: MediaTime): MediaTime { const maxTime = this.editor.timeline.getTotalDuration(); - return Math.max(0, Math.min(maxTime, time)); + return clampMediaTime({ time, min: ZERO_MEDIA_TIME, max: maxTime }); } - private dispatchSeekEvent(time: number): void { + private dispatchSeekEvent(time: MediaTime): void { if (typeof window === "undefined") { return; } @@ -222,7 +230,7 @@ export class PlaybackManager { ); } - private dispatchUpdateEvent(time: number): void { + private dispatchUpdateEvent(time: MediaTime): void { if (typeof window === "undefined") { return; } diff --git a/apps/web/src/core/managers/scenes-manager.ts b/apps/web/src/core/managers/scenes-manager.ts index 13f251a1..c9558a6d 100644 --- a/apps/web/src/core/managers/scenes-manager.ts +++ b/apps/web/src/core/managers/scenes-manager.ts @@ -1,5 +1,5 @@ import type { EditorCore } from "@/core"; -import type { SceneTracks, TScene } from "@/timeline"; +import type { Bookmark, SceneTracks, TScene } from "@/timeline"; import { storageService } from "@/services/storage/service"; import { getMainScene, @@ -21,6 +21,7 @@ import { ToggleBookmarkCommand, UpdateBookmarkCommand, } from "@/commands/scene"; +import type { MediaTime } from "@/wasm"; export class ScenesManager { private active: TScene | null = null; @@ -106,12 +107,12 @@ export class ScenesManager { this.notify(); } - async toggleBookmark({ time }: { time: number }): Promise { + async toggleBookmark({ time }: { time: MediaTime }): Promise { const command = new ToggleBookmarkCommand(time); this.editor.command.execute({ command }); } - isBookmarked({ time }: { time: number }): boolean { + isBookmarked({ time }: { time: MediaTime }): boolean { const activeScene = this.getActiveScene(); const activeProject = this.editor.project.getActive(); @@ -125,7 +126,7 @@ export class ScenesManager { return isBookmarkAtTime({ bookmarks: activeScene.bookmarks, frameTime }); } - async removeBookmark({ time }: { time: number }): Promise { + async removeBookmark({ time }: { time: MediaTime }): Promise { const command = new RemoveBookmarkCommand(time); this.editor.command.execute({ command }); } @@ -134,8 +135,8 @@ export class ScenesManager { time, updates, }: { - time: number; - updates: Partial<{ note: string; color: string; duration: number }>; + time: MediaTime; + updates: Partial>; }): Promise { const command = new UpdateBookmarkCommand(time, updates); this.editor.command.execute({ command }); @@ -145,14 +146,14 @@ export class ScenesManager { fromTime, toTime, }: { - fromTime: number; - toTime: number; + fromTime: MediaTime; + toTime: MediaTime; }): Promise { const command = new MoveBookmarkCommand(fromTime, toTime); this.editor.command.execute({ command }); } - getBookmarkAtTime({ time }: { time: number }) { + getBookmarkAtTime({ time }: { time: MediaTime }) { const activeScene = this.active; const activeProject = this.editor.project.getActive(); diff --git a/apps/web/src/core/managers/timeline-manager.ts b/apps/web/src/core/managers/timeline-manager.ts index dbc3332b..a1ead6ca 100644 --- a/apps/web/src/core/managers/timeline-manager.ts +++ b/apps/web/src/core/managers/timeline-manager.ts @@ -10,6 +10,7 @@ import type { } from "@/timeline"; import { calculateTotalDuration } from "@/timeline"; import { findTrackInSceneTracks } from "@/timeline/track-element-update"; +import { type MediaTime, ZERO_MEDIA_TIME } from "@/wasm"; import { canElementBeHidden, canElementHaveAudio, @@ -95,10 +96,10 @@ export class TimelineManager { pushHistory = true, }: { elementId: string; - trimStart: number; - trimEnd: number; - startTime?: number; - duration?: number; + trimStart: MediaTime; + trimEnd: MediaTime; + startTime?: MediaTime; + duration?: MediaTime; pushHistory?: boolean; }): void { const trackId = this.findTrackIdForElement({ elementId }); @@ -188,7 +189,7 @@ export class TimelineManager { retainSide = "both", }: { elements: { trackId: string; elementId: string }[]; - splitTime: number; + splitTime: MediaTime; retainSide?: "both" | "left" | "right"; }): { trackId: string; elementId: string }[] { const command = new SplitElementsCommand({ @@ -200,20 +201,20 @@ export class TimelineManager { return command.getRightSideElements(); } - getTotalDuration(): number { + getTotalDuration(): MediaTime { const activeScene = this.editor.scenes.getActiveSceneOrNull(); if (!activeScene) { - return 0; + return ZERO_MEDIA_TIME; } return calculateTotalDuration({ tracks: activeScene.tracks }); } - getLastFrameTime(): number { + getLastFrameTime(): MediaTime { const duration = this.getTotalDuration(); const fps = this.editor.project.getActive()?.settings.fps; if (!fps || duration <= 0) return duration; - return lastFrameTime({ duration, rate: fps }) ?? duration; + return (lastFrameTime({ duration, rate: fps }) ?? duration) as MediaTime; } getTrackById({ trackId }: { trackId: string }): TimelineTrack | null { @@ -483,7 +484,7 @@ export class TimelineManager { trackId: string; elementId: string; propertyPath: AnimationPath; - time: number; + time: MediaTime; value: AnimationValue; interpolation?: AnimationInterpolation; keyframeId?: string; @@ -600,7 +601,7 @@ export class TimelineManager { elementId: string; propertyPath: AnimationPath; keyframeId: string; - time: number; + time: MediaTime; }): void { const command = new RetimeKeyframeCommand({ trackId, @@ -658,7 +659,7 @@ export class TimelineManager { elementId: string; effectId: string; paramKey: string; - time: number; + time: MediaTime; value: number; interpolation?: "linear" | "hold"; keyframeId?: string; -- cgit v1.3.1