import { create } from 'zustand' import { PLAYBACK_SPEEDS } from '../types/areaSearch.types' interface AnimationState { isPlaying: boolean currentTime: number // Unix epoch ms startTime: number endTime: number playbackSpeed: number // 1x ~ 1000x loop: boolean animationFrameId: number | null lastFrameTime: number play: () => void pause: () => void stop: () => void setPlaybackSpeed: (speed: number) => void setCurrentTime: (time: number) => void setProgressByRatio: (ratio: number) => void setTimeRange: (start: number, end: number) => void toggleLoop: () => void getProgress: () => number // 0~100 reset: () => void } export { PLAYBACK_SPEEDS } export const useAreaAnimationStore = create((set, get) => ({ isPlaying: false, currentTime: 0, startTime: 0, endTime: 0, playbackSpeed: 100, loop: false, animationFrameId: null, lastFrameTime: 0, play: () => { const state = get() if (state.isPlaying) return if (state.startTime >= state.endTime) return if (state.currentTime >= state.endTime) { set({ currentTime: state.startTime }) } set({ isPlaying: true, lastFrameTime: performance.now() }) const animate = (timestamp: number) => { const s = get() if (!s.isPlaying) return const deltaMs = timestamp - s.lastFrameTime const increment = (deltaMs / 1000) * s.playbackSpeed * 1000 let newTime = s.currentTime + increment if (newTime > s.endTime) { if (s.loop) { newTime = s.startTime } else { set({ isPlaying: false, currentTime: s.endTime, animationFrameId: null }) return } } set({ currentTime: newTime, lastFrameTime: timestamp }) const frameId = requestAnimationFrame(animate) set({ animationFrameId: frameId }) } const frameId = requestAnimationFrame(animate) set({ animationFrameId: frameId }) }, pause: () => { const { animationFrameId } = get() if (animationFrameId !== null) cancelAnimationFrame(animationFrameId) set({ isPlaying: false, animationFrameId: null }) }, stop: () => { const { animationFrameId, startTime } = get() if (animationFrameId !== null) cancelAnimationFrame(animationFrameId) set({ isPlaying: false, animationFrameId: null, currentTime: startTime }) }, setPlaybackSpeed: (speed) => set({ playbackSpeed: speed }), setCurrentTime: (time) => set({ currentTime: time }), setProgressByRatio: (ratio) => { const { startTime, endTime } = get() const time = startTime + (endTime - startTime) * Math.max(0, Math.min(1, ratio)) set({ currentTime: time }) }, setTimeRange: (start, end) => set({ startTime: start, endTime: end, currentTime: start }), toggleLoop: () => set((s) => ({ loop: !s.loop })), getProgress: () => { const { currentTime, startTime, endTime } = get() if (endTime <= startTime) return 0 return ((currentTime - startTime) / (endTime - startTime)) * 100 }, reset: () => { const { animationFrameId } = get() if (animationFrameId !== null) cancelAnimationFrame(animationFrameId) set({ isPlaying: false, currentTime: 0, startTime: 0, endTime: 0, playbackSpeed: 100, loop: false, animationFrameId: null, lastFrameTime: 0, }) }, }))