Cleaned sound composable code

This commit is contained in:
Dennis Postma 2025-02-06 22:35:54 +01:00
parent fb3a59aa59
commit 838610d041
4 changed files with 97 additions and 60 deletions

View File

@ -16,8 +16,8 @@ import MapEditor from '@/components/screens/MapEditor.vue'
import BackgroundImageLoader from '@/components/utilities/BackgroundImageLoader.vue'
import Debug from '@/components/utilities/Debug.vue'
import Notifications from '@/components/utilities/Notifications.vue'
import { useSoundComposable } from '@/composables/useSoundComposable'
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { useSoundComposable } from '@/composables/useSoundComposable'
import { useGameStore } from '@/stores/gameStore'
import { computed, watch } from 'vue'

View File

@ -43,6 +43,9 @@ const handlePositionUpdate = (newValues: any, oldValues: any) => {
}
}
/**
* Plays walk sound when character is moving
*/
watch(
() => props.mapCharacter.isMoving,
(newValue) => {
@ -54,6 +57,9 @@ watch(
}
)
/**
* Plays attack animation and sound when character is attacking
*/
watch(
() => props.mapCharacter.isAttacking,
(newValue) => {
@ -67,6 +73,9 @@ watch(
}
)
/**
* Handles position updates and movement delay
*/
watch(
() => ({
positionX: props.mapCharacter.character.positionX,
@ -82,7 +91,6 @@ watch(
onMounted(async () => {
await initializeSprite()
if (props.mapCharacter.character.id === gameStore.character!.id) {
mapStore.setCharacterLoaded(true)
scene.cameras.main.startFollow(characterContainer.value as Phaser.GameObjects.Container)

View File

@ -1,47 +1,27 @@
import { SoundStorage } from '@/storage/storages'
// Use a WeakMap for better garbage collection
interface CachedSound {
id: string
name: string
base64: string
}
// Core storage instances
const soundStorage = new SoundStorage()
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> => {
/**
* Converts a sound URL to base64 format
*/
async function soundToBase64(url: string): Promise<string> {
try {
const response = await fetch(url)
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
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()
@ -55,30 +35,70 @@ export function useSoundComposable() {
}
}
const playSound = async (soundUrl: string, loop: boolean = false, ignoreIfPlaying: boolean = false) => {
/**
* Preloads a sound file and caches it for future use
*/
async function preloadSound(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)
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<void> {
try {
// Check if sound is already playing
const playingSounds = activeSounds.get(soundUrl) || []
if (ignoreIfPlaying && playingSounds.some(audio => !audio.paused)) {
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) {
// 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)
@ -86,8 +106,9 @@ export function useSoundComposable() {
if (sounds.length === 0) {
activeSounds.delete(soundUrl)
}
}
}, { once: true })
},
{ once: true }
)
await playingAudio.play()
} catch (error) {
@ -95,33 +116,41 @@ export function useSoundComposable() {
}
}
const stopSound = (soundUrl: string) => {
/**
* Stops all instances of a specific sound
*/
function stopSound(soundUrl: string): void {
const sounds = activeSounds.get(soundUrl)
if (!sounds) return
sounds.forEach(audio => {
sounds.forEach((audio) => {
audio.pause()
audio.currentTime = 0
})
activeSounds.delete(soundUrl)
}
const stopAllSounds = () => {
activeSounds.forEach((sounds, url) => {
stopSound(url)
})
/**
* Stops all currently playing sounds
*/
function stopAllSounds(): void {
activeSounds.forEach((_, url) => stopSound(url))
activeSounds.clear()
}
const clearSoundCache = async () => {
/**
* Clears all cached sounds and stops playback
*/
async function clearSoundCache(): Promise<void> {
stopAllSounds()
audioCache.clear()
await soundStorage.reset()
}
// New method to preload multiple sounds
const preloadSounds = async (soundUrls: string[]) => {
/**
* Preloads multiple sounds simultaneously
*/
async function preloadSounds(soundUrls: string[]): Promise<HTMLAudioElement[]> {
return Promise.all(soundUrls.map(preloadSound))
}