import { SoundStorage } from '@/storage/storages' interface CachedSound { id: string name: string base64: string } // Core storage instances const soundStorage = new SoundStorage() const activeSounds = new Map() const audioCache = new Map() export function useSoundComposable() { /** * Converts a sound URL to base64 format */ async function soundToBase64(url: string): Promise { try { const response = await fetch(url) if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } const blob = await response.blob() return new Promise((resolve, reject) => { const reader = new FileReader() reader.onloadend = () => resolve((reader.result as string).split(',')[1]) reader.onerror = reject reader.readAsDataURL(blob) }) } catch (error) { console.error('Error converting sound to base64:', error) throw error } } /** * Preloads a sound file and caches it for future use */ async function preloadSound(soundUrl: string): Promise { if (audioCache.has(soundUrl)) { return audioCache.get(soundUrl)! } let audio: HTMLAudioElement const cachedSound = await soundStorage.getById(soundUrl) if (cachedSound) { audio = new Audio(`data:audio/mpeg;base64,${cachedSound.base64}`) } else { try { const base64 = await soundToBase64(soundUrl) const soundData: CachedSound = { id: soundUrl, name: soundUrl.split('/').pop() || soundUrl, base64 } await soundStorage.add(soundData) audio = new Audio(`data:audio/mpeg;base64,${base64}`) } catch (error) { console.error(`Failed to load and cache sound ${soundUrl}:`, error) audio = new Audio(soundUrl) } } audio.load() audioCache.set(soundUrl, audio) return audio } /** * Plays a sound with optional looping and duplicate prevention */ async function playSound(soundUrl: string, loop: boolean = false, ignoreIfPlaying: boolean = false): Promise { try { const playingSounds = activeSounds.get(soundUrl) || [] if (ignoreIfPlaying && playingSounds.some((audio) => !audio.paused)) { return } stopSound(soundUrl) const audio = await preloadSound(soundUrl) const playingAudio = audio.cloneNode() as HTMLAudioElement playingAudio.loop = loop if (!activeSounds.has(soundUrl)) { activeSounds.set(soundUrl, []) } activeSounds.get(soundUrl)!.push(playingAudio) // Cleanup when sound ends playingAudio.addEventListener( 'ended', () => { const sounds = activeSounds.get(soundUrl) if (!sounds) return const index = sounds.indexOf(playingAudio) if (index > -1) { sounds.splice(index, 1) } if (sounds.length === 0) { activeSounds.delete(soundUrl) } }, { once: true } ) await playingAudio.play() } catch (error) { console.error(`Failed to play sound ${soundUrl}:`, error) } } /** * Stops all instances of a specific sound */ function stopSound(soundUrl: string): void { const sounds = activeSounds.get(soundUrl) if (!sounds) return sounds.forEach((audio) => { audio.pause() audio.currentTime = 0 }) activeSounds.delete(soundUrl) } /** * Stops all currently playing sounds */ function stopAllSounds(): void { activeSounds.forEach((_, url) => stopSound(url)) activeSounds.clear() } /** * Clears all cached sounds and stops playback */ async function clearSoundCache(): Promise { stopAllSounds() audioCache.clear() await soundStorage.reset() } /** * Preloads multiple sounds simultaneously */ async function preloadSounds(soundUrls: string[]): Promise { return Promise.all(soundUrls.map(preloadSound)) } return { playSound, stopSound, stopAllSounds, clearSoundCache, preloadSounds } }