import type { VideoPlaybackEvent, VideoPlaybackState } from '../../shared/types'; function nowMs(): number { return Date.now(); } function clamp(v: number, min: number, max: number): number { return Math.max(min, Math.min(max, v)); } export class VideoPlaybackStore { private state: VideoPlaybackState = { revision: 1, serverNowMs: nowMs(), targetAssetId: null, playing: false, playbackRate: 1, anchorServerMs: nowMs(), anchorVideoTimeSec: 0, }; getState(): VideoPlaybackState { return { ...this.state, serverNowMs: nowMs() }; } dispatch(event: VideoPlaybackEvent): VideoPlaybackState { const s = this.getState(); const curTime = computeTimeSec(s, s.serverNowMs); const bump = (patch: Omit): VideoPlaybackState => ({ ...patch, revision: s.revision + 1, serverNowMs: nowMs(), }); switch (event.kind) { case 'target.set': { const nextTarget = event.assetId ?? null; const next: Omit = { ...s, targetAssetId: nextTarget, playing: Boolean(event.autostart) && nextTarget !== null, playbackRate: s.playbackRate, anchorServerMs: s.serverNowMs, anchorVideoTimeSec: 0, }; this.state = bump(next); return this.state; } case 'play': { this.state = bump({ ...s, playing: true, anchorServerMs: s.serverNowMs, anchorVideoTimeSec: curTime, }); return this.state; } case 'pause': { this.state = bump({ ...s, playing: false, anchorServerMs: s.serverNowMs, anchorVideoTimeSec: curTime, }); return this.state; } case 'stop': { this.state = bump({ ...s, playing: false, anchorServerMs: s.serverNowMs, anchorVideoTimeSec: 0, }); return this.state; } case 'seek': { const t = clamp(event.timeSec, 0, 1_000_000); this.state = bump({ ...s, anchorServerMs: s.serverNowMs, anchorVideoTimeSec: t, }); return this.state; } case 'rate.set': { const rate = clamp(event.rate, 0.25, 3); this.state = bump({ ...s, playbackRate: rate, anchorServerMs: s.serverNowMs, anchorVideoTimeSec: curTime, }); return this.state; } default: { // eslint-disable-next-line @typescript-eslint/no-unused-vars const _x: never = event; return this.state; } } } } export function computeTimeSec(state: VideoPlaybackState, atServerNowMs: number): number { if (!state.playing) return state.anchorVideoTimeSec; const dt = Math.max(0, atServerNowMs - state.anchorServerMs); return state.anchorVideoTimeSec + (dt / 1000) * state.playbackRate; }