From 56ca09692a308b22455420d11376ff8d41244c47 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Sun, 26 Apr 2026 17:31:28 +0200 Subject: refactor: thinner hooks --- apps/web/src/core/managers/audio-manager.ts | 23 ++++++------------ apps/web/src/core/managers/playback-manager.ts | 33 ++++++++++++++++++++++---- apps/web/src/core/managers/timeline-manager.ts | 11 +++++---- 3 files changed, 42 insertions(+), 25 deletions(-) (limited to 'apps/web/src/core') diff --git a/apps/web/src/core/managers/audio-manager.ts b/apps/web/src/core/managers/audio-manager.ts index 545565be..d0716aad 100644 --- a/apps/web/src/core/managers/audio-manager.ts +++ b/apps/web/src/core/managers/audio-manager.ts @@ -1,9 +1,6 @@ import type { EditorCore } from "@/core"; import { TICKS_PER_SECOND } from "@/wasm"; -import { - clampRetimeRate, - shouldMaintainPitch, -} from "@/retime/rate"; +import { clampRetimeRate, shouldMaintainPitch } from "@/retime/rate"; import type { AudioClipSource } from "@/media/audio"; import { createAudioContext, collectAudioClips } from "@/media/audio"; import { @@ -56,10 +53,8 @@ export class AudioManager { this.editor.playback.subscribe(this.handlePlaybackChange), this.editor.timeline.subscribe(this.handleTimelineChange), this.editor.media.subscribe(this.handleTimelineChange), + this.editor.playback.onSeek(this.handleSeek), ); - if (typeof window !== "undefined") { - window.addEventListener("playback-seek", this.handleSeek); - } } dispose(): void { @@ -68,9 +63,6 @@ export class AudioManager { unsub(); } this.unsubscribers = []; - if (typeof window !== "undefined") { - window.removeEventListener("playback-seek", this.handleSeek); - } this.disposeSinks(); this.preparedClipBuffers.clear(); this.decodedBuffers.clear(); @@ -102,17 +94,14 @@ export class AudioManager { } }; - private handleSeek = (event: Event): void => { - const detail = (event as CustomEvent<{ time: number }>).detail; - if (!detail) return; - + private handleSeek = (time: number): void => { if (this.editor.playback.getIsScrubbing()) { this.stopPlayback(); return; } if (this.editor.playback.getIsPlaying()) { - void this.startPlayback({ time: detail.time / TICKS_PER_SECOND }); + void this.startPlayback({ time: time / TICKS_PER_SECOND }); return; } @@ -126,7 +115,9 @@ export class AudioManager { if (!this.editor.playback.getIsPlaying()) return; - void this.startPlayback({ time: this.editor.playback.getCurrentTime() / TICKS_PER_SECOND }); + void this.startPlayback({ + time: this.editor.playback.getCurrentTime() / TICKS_PER_SECOND, + }); }; private ensureAudioContext(): AudioContext | null { diff --git a/apps/web/src/core/managers/playback-manager.ts b/apps/web/src/core/managers/playback-manager.ts index 6748fd42..8275344f 100644 --- a/apps/web/src/core/managers/playback-manager.ts +++ b/apps/web/src/core/managers/playback-manager.ts @@ -4,9 +4,9 @@ import { clampMediaTime, type MediaTime, mediaTimeFromSeconds, + roundFrameTime, ZERO_MEDIA_TIME, } from "@/wasm"; -import { roundToFrame } from "opencut-wasm"; export class PlaybackManager { private isPlaying = false; @@ -16,6 +16,8 @@ export class PlaybackManager { private previousVolume = 1; private isScrubbing = false; private listeners = new Set<() => void>(); + private updateListeners = new Set<(time: MediaTime) => void>(); + private seekListeners = new Set<(time: MediaTime) => void>(); private playbackTimer: number | null = null; private playbackStartWallTime = 0; private playbackStartTime: MediaTime = ZERO_MEDIA_TIME; @@ -139,6 +141,16 @@ export class PlaybackManager { return () => this.listeners.delete(listener); } + onUpdate(listener: (time: MediaTime) => void): () => void { + this.updateListeners.add(listener); + return () => this.updateListeners.delete(listener); + } + + onSeek(listener: (time: MediaTime) => void): () => void { + this.seekListeners.add(listener); + return () => this.seekListeners.delete(listener); + } + private reconcileTimelineScope(): void { const maxTime = this.editor.timeline.getTotalDuration(); const nextTime = this.clampTimeToTimeline(this.currentTime); @@ -158,6 +170,7 @@ export class PlaybackManager { this.notify(); if (timeChanged) { + this.notifySeek(this.currentTime); this.dispatchSeekEvent(this.currentTime); } } @@ -168,6 +181,18 @@ export class PlaybackManager { }); } + private notifyUpdate(time: MediaTime): void { + this.updateListeners.forEach((fn) => { + fn(time); + }); + } + + private notifySeek(time: MediaTime): void { + this.seekListeners.forEach((fn) => { + fn(time); + }); + } + private startTimer(): void { if (this.playbackTimer) { cancelAnimationFrame(this.playbackTimer); @@ -195,20 +220,20 @@ export class PlaybackManager { a: this.playbackStartTime, b: mediaTimeFromSeconds({ seconds: elapsedSeconds }), }); - const newTime = (fps - ? (roundToFrame({ time: rawTime, rate: fps }) ?? rawTime) - : rawTime) as MediaTime; + const newTime = fps ? roundFrameTime({ time: rawTime, fps }) : rawTime; const maxTime = this.editor.timeline.getTotalDuration(); if (newTime >= maxTime) { this.pause(); this.currentTime = maxTime; this.notify(); + this.notifySeek(maxTime); this.dispatchSeekEvent(maxTime); return; } this.currentTime = newTime; + this.notifyUpdate(newTime); this.dispatchUpdateEvent(newTime); this.playbackTimer = requestAnimationFrame(this.updateTime); }; diff --git a/apps/web/src/core/managers/timeline-manager.ts b/apps/web/src/core/managers/timeline-manager.ts index a1ead6ca..3c6b7599 100644 --- a/apps/web/src/core/managers/timeline-manager.ts +++ b/apps/web/src/core/managers/timeline-manager.ts @@ -9,8 +9,9 @@ import type { RetimeConfig, } from "@/timeline"; import { calculateTotalDuration } from "@/timeline"; +import { TimelineDragSource } from "@/timeline/drag-source"; import { findTrackInSceneTracks } from "@/timeline/track-element-update"; -import { type MediaTime, ZERO_MEDIA_TIME } from "@/wasm"; +import { lastFrameMediaTime, type MediaTime, ZERO_MEDIA_TIME } from "@/wasm"; import { canElementBeHidden, canElementHaveAudio, @@ -27,7 +28,6 @@ import { resolveAnimationTarget, resolveAnimationPathValueAtTime, } from "@/animation"; -import { lastFrameTime } from "opencut-wasm"; import { BatchCommand } from "@/commands"; import { AddTrackCommand, @@ -68,6 +68,7 @@ export class TimelineManager { private listeners = new Set<() => void>(); private previewOverlay = new Map>(); private previewTracks: SceneTracks | null = null; + public readonly dragSource = new TimelineDragSource(); constructor(private editor: EditorCore) {} @@ -214,7 +215,7 @@ export class TimelineManager { const duration = this.getTotalDuration(); const fps = this.editor.project.getActive()?.settings.fps; if (!fps || duration <= 0) return duration; - return (lastFrameTime({ duration, rate: fps }) ?? duration) as MediaTime; + return lastFrameMediaTime({ duration, fps }); } getTrackById({ trackId }: { trackId: string }): TimelineTrack | null { @@ -707,11 +708,11 @@ export class TimelineManager { previewElements({ updates, }: { - updates: Array<{ + updates: readonly { trackId: string; elementId: string; updates: Partial; - }>; + }[]; }): void { let changedOverlayCount = 0; for (const { elementId, updates: elementUpdates } of updates) { -- cgit v1.3.1