diff --git a/packages/studio/src/player/store/liveTime.ts b/packages/studio/src/player/store/liveTime.ts new file mode 100644 index 0000000000..94bf6bf17d --- /dev/null +++ b/packages/studio/src/player/store/liveTime.ts @@ -0,0 +1,11 @@ +// ponytail: Playback RAF updates the playhead/time display without per-frame React renders. +type TimeListener = (time: number) => void; +const timeListeners = new Set(); + +export const liveTime = { + notify: (time: number) => timeListeners.forEach((listener) => listener(time)), + subscribe: (listener: TimeListener) => { + timeListeners.add(listener); + return () => timeListeners.delete(listener); + }, +}; diff --git a/packages/studio/src/player/store/playerStore.ts b/packages/studio/src/player/store/playerStore.ts index 26323e458b..7686e7a875 100644 --- a/packages/studio/src/player/store/playerStore.ts +++ b/packages/studio/src/player/store/playerStore.ts @@ -12,6 +12,7 @@ import { clampTimelineZoomPercent, computePinnedZoomPercent } from "../component import { createKeyframeSlice, type KeyframeCacheEntry, type KeyframeSlice } from "./keyframeSlice"; export type { KeyframeCacheEntry } from "./keyframeSlice"; +export { liveTime } from "./liveTime"; export interface TimelineElement { id: string; @@ -279,19 +280,6 @@ interface BeatHistoryEntry { label: string; } -// Lightweight pub-sub for current time during playback. -// Bypasses React state so the RAF loop can update the playhead/time display -// without triggering re-renders on every frame. -type TimeListener = (time: number) => void; -const _timeListeners = new Set(); -export const liveTime = { - notify: (t: number) => _timeListeners.forEach((cb) => cb(t)), - subscribe: (cb: TimeListener) => { - _timeListeners.add(cb); - return () => _timeListeners.delete(cb); - }, -}; - export function createTimelineResetState() { return { isPlaying: false,