import { Howl } from "howler"; export type PlaybackState = { isPlaying: boolean; url: string | null; name: string | null; elapsed: number; duration: number; }; type Listener = (s: PlaybackState) => void; const MAX_SECONDS = 6; const FADE_MS = 500; let state: PlaybackState = { isPlaying: false, url: null, name: null, elapsed: 0, duration: MAX_SECONDS, }; const listeners = new Set(); let howlInstance: Howl | null = null; let soundId: number | null = null; let stopTimer: ReturnType | null = null; let progressTimer: ReturnType | null = null; function emit() { listeners.forEach(fn => fn(state)); } function resetTimers() { if (stopTimer) clearTimeout(stopTimer); if (progressTimer) clearInterval(progressTimer); stopTimer = null; progressTimer = null; } function stopSound() { if (howlInstance && soundId !== null) { howlInstance.stop(soundId); } howlInstance = null; soundId = null; } export function getPlaybackState(): PlaybackState { return state; } export function subscribe(fn: Listener): () => void { listeners.add(fn); return () => listeners.delete(fn); } export function playUrl(url: string, name?: string) { stopCurrent(); const displayName = name || url.split("/").pop() || url; howlInstance = new Howl({ src: [url], format: url.endsWith(".mp3") ? ["mp3"] : url.endsWith(".ogg") ? ["ogg"] : ["mp3", "ogg"], volume: 0.4, }); soundId = howlInstance.play(); state = { isPlaying: true, url, name: displayName, elapsed: 0, duration: MAX_SECONDS }; emit(); // Progress tick progressTimer = setInterval(() => { if (howlInstance && soundId !== null) { const seek = howlInstance.seek(soundId) as number; state = { ...state, elapsed: seek }; emit(); } }, 100); // Cap at MAX_SECONDS with fadeout const fadeStart = (MAX_SECONDS - FADE_MS / 1000) * 1000; stopTimer = setTimeout(() => { if (howlInstance && soundId !== null) { try { howlInstance.fade(0.4, 0, FADE_MS, soundId); } catch {} setTimeout(() => stopCurrent(), FADE_MS); } }, fadeStart); // Natural end howlInstance.on("end", () => stopCurrent()); } export function stopCurrent() { stopSound(); resetTimers(); state = { isPlaying: false, url: null, name: null, elapsed: 0, duration: MAX_SECONDS }; emit(); }