import type { MediaConfig } from './mediaConfig'; const audioElements = new Map(); function ensureAudio(src: string): HTMLAudioElement { let audio = audioElements.get(src); if (!audio) { audio = new Audio(); audio.preload = 'auto'; audio.src = src; audioElements.set(src, audio); } return audio; } const AUDIO_URL_KEYS = [ 'incorrectUrl', 'unlockConfirmUrl', 'celebrationPopUrl', 'celebrationHornUrl', ] as const satisfies readonly (keyof MediaConfig)[]; /** Fetch and decode audio clips so first playback is not delayed. */ export function preloadAudioAssets(config: MediaConfig): void { for (const key of AUDIO_URL_KEYS) { ensureAudio(config[key]).load(); } } export function playClip(src: string): HTMLAudioElement { const cached = audioElements.get(src); if (cached) { if (cached.paused || cached.ended) { cached.currentTime = 0; void cached.play(); return cached; } const fallback = new Audio(src); void fallback.play(); return fallback; } const audio = new Audio(src); void audio.play(); return audio; } export function playIncorrectSound(incorrectUrl: string) { playClip(incorrectUrl); } type CelebrationConfig = Pick< MediaConfig, | 'unlockConfirmUrl' | 'celebrationPopUrl' | 'celebrationHornUrl' | 'celebrationHornDurationMs' >; /** Confirm, pop, and horn together; horn is trimmed — ends before unlock video audio. */ export function playCelebrationSounds(config: CelebrationConfig) { playClip(config.unlockConfirmUrl); playClip(config.celebrationPopUrl); const horn = playClip(config.celebrationHornUrl); window.setTimeout(() => { horn.pause(); horn.currentTime = 0; }, config.celebrationHornDurationMs); }