1
0
forked from noxious/client

Cache audio

This commit is contained in:
2025-02-06 22:32:25 +01:00
parent ccb64fc048
commit fb3a59aa59
8 changed files with 173 additions and 67 deletions

View File

@ -1,40 +0,0 @@
const activeSounds: { [key: string]: HTMLAudioElement } = {}
export function useGameComposable() {
const playSound = (sound: string, loop: boolean = false, ignoreIfPlaying: boolean = false) => {
// If sound is already playing and we want to ignore
if (ignoreIfPlaying && activeSounds[sound] && !activeSounds[sound].paused) {
return
}
// Stop previous instance of this sound if it exists
if (activeSounds[sound]) {
activeSounds[sound].pause()
activeSounds[sound].currentTime = 0
}
const audio = new Audio(sound)
audio.loop = loop
// Store reference to track playing state
activeSounds[sound] = audio
audio.addEventListener('ended', () => {
delete activeSounds[sound]
})
audio.play().catch(console.error)
}
const stopSound = (sound: string) => {
if (!activeSounds[sound]) return
activeSounds[sound].pause()
activeSounds[sound].currentTime = 0
delete activeSounds[sound]
}
return {
playSound,
stopSound
}
}

View File

@ -0,0 +1,135 @@
import { SoundStorage } from '@/storage/storages'
// Use a WeakMap for better garbage collection
const activeSounds = new Map<string, HTMLAudioElement[]>()
const audioCache = new Map<string, HTMLAudioElement>()
const soundStorage = new SoundStorage()
export function useSoundComposable() {
// Preload function to reduce initial playback delay
const preloadSound = async (soundUrl: string): Promise<HTMLAudioElement> => {
if (audioCache.has(soundUrl)) {
return audioCache.get(soundUrl)!
}
let audio: HTMLAudioElement
const cachedSound = await soundStorage.get(soundUrl)
if (cachedSound) {
audio = new Audio(`data:audio/mpeg;base64,${cachedSound.base64}`)
} else {
try {
const base64 = await soundToBase64(soundUrl)
await soundStorage.add({
id: soundUrl,
name: soundUrl.split('/').pop() || soundUrl,
base64
})
audio = new Audio(`data:audio/mpeg;base64,${base64}`)
} catch (error) {
console.error(`Failed to load and cache sound ${soundUrl}:`, error)
audio = new Audio(soundUrl)
}
}
// Preload the audio
audio.load()
audioCache.set(soundUrl, audio)
return audio
}
const soundToBase64 = async (url: string): Promise<string> => {
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
}
}
const playSound = async (soundUrl: string, loop: boolean = false, ignoreIfPlaying: boolean = false) => {
try {
// Check if sound is already playing
const playingSounds = activeSounds.get(soundUrl) || []
if (ignoreIfPlaying && playingSounds.some(audio => !audio.paused)) {
return
}
// Stop previous instances if they exist
stopSound(soundUrl)
const audio = await preloadSound(soundUrl)
const playingAudio = audio.cloneNode() as HTMLAudioElement
playingAudio.loop = loop
// Initialize or get the array of active sounds for this URL
if (!activeSounds.has(soundUrl)) {
activeSounds.set(soundUrl, [])
}
activeSounds.get(soundUrl)!.push(playingAudio)
playingAudio.addEventListener('ended', () => {
const sounds = activeSounds.get(soundUrl)
if (sounds) {
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)
}
}
const stopSound = (soundUrl: string) => {
const sounds = activeSounds.get(soundUrl)
if (!sounds) return
sounds.forEach(audio => {
audio.pause()
audio.currentTime = 0
})
activeSounds.delete(soundUrl)
}
const stopAllSounds = () => {
activeSounds.forEach((sounds, url) => {
stopSound(url)
})
activeSounds.clear()
}
const clearSoundCache = async () => {
stopAllSounds()
audioCache.clear()
await soundStorage.reset()
}
// New method to preload multiple sounds
const preloadSounds = async (soundUrls: string[]) => {
return Promise.all(soundUrls.map(preloadSound))
}
return {
playSound,
stopSound,
stopAllSounds,
clearSoundCache,
preloadSounds
}
}