forked from noxious/client
Cleaned sound composable code
This commit is contained in:
parent
fb3a59aa59
commit
838610d041
@ -16,8 +16,8 @@ import MapEditor from '@/components/screens/MapEditor.vue'
|
|||||||
import BackgroundImageLoader from '@/components/utilities/BackgroundImageLoader.vue'
|
import BackgroundImageLoader from '@/components/utilities/BackgroundImageLoader.vue'
|
||||||
import Debug from '@/components/utilities/Debug.vue'
|
import Debug from '@/components/utilities/Debug.vue'
|
||||||
import Notifications from '@/components/utilities/Notifications.vue'
|
import Notifications from '@/components/utilities/Notifications.vue'
|
||||||
import { useSoundComposable } from '@/composables/useSoundComposable'
|
|
||||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
|
import { useSoundComposable } from '@/composables/useSoundComposable'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { computed, watch } from 'vue'
|
import { computed, watch } from 'vue'
|
||||||
|
|
||||||
|
@ -43,6 +43,9 @@ const handlePositionUpdate = (newValues: any, oldValues: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays walk sound when character is moving
|
||||||
|
*/
|
||||||
watch(
|
watch(
|
||||||
() => props.mapCharacter.isMoving,
|
() => props.mapCharacter.isMoving,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
@ -54,6 +57,9 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays attack animation and sound when character is attacking
|
||||||
|
*/
|
||||||
watch(
|
watch(
|
||||||
() => props.mapCharacter.isAttacking,
|
() => props.mapCharacter.isAttacking,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
@ -67,6 +73,9 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles position updates and movement delay
|
||||||
|
*/
|
||||||
watch(
|
watch(
|
||||||
() => ({
|
() => ({
|
||||||
positionX: props.mapCharacter.character.positionX,
|
positionX: props.mapCharacter.character.positionX,
|
||||||
@ -82,7 +91,6 @@ watch(
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await initializeSprite()
|
await initializeSprite()
|
||||||
|
|
||||||
if (props.mapCharacter.character.id === gameStore.character!.id) {
|
if (props.mapCharacter.character.id === gameStore.character!.id) {
|
||||||
mapStore.setCharacterLoaded(true)
|
mapStore.setCharacterLoaded(true)
|
||||||
scene.cameras.main.startFollow(characterContainer.value as Phaser.GameObjects.Container)
|
scene.cameras.main.startFollow(characterContainer.value as Phaser.GameObjects.Container)
|
||||||
|
@ -1,47 +1,27 @@
|
|||||||
import { SoundStorage } from '@/storage/storages'
|
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 activeSounds = new Map<string, HTMLAudioElement[]>()
|
||||||
const audioCache = new Map<string, HTMLAudioElement>()
|
const audioCache = new Map<string, HTMLAudioElement>()
|
||||||
const soundStorage = new SoundStorage()
|
|
||||||
|
|
||||||
export function useSoundComposable() {
|
export function useSoundComposable() {
|
||||||
// Preload function to reduce initial playback delay
|
/**
|
||||||
const preloadSound = async (soundUrl: string): Promise<HTMLAudioElement> => {
|
* Converts a sound URL to base64 format
|
||||||
if (audioCache.has(soundUrl)) {
|
*/
|
||||||
return audioCache.get(soundUrl)!
|
async function soundToBase64(url: string): Promise<string> {
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
try {
|
||||||
const response = await fetch(url)
|
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()
|
const blob = await response.blob()
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader()
|
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 {
|
try {
|
||||||
// Check if sound is already playing
|
|
||||||
const playingSounds = activeSounds.get(soundUrl) || []
|
const playingSounds = activeSounds.get(soundUrl) || []
|
||||||
if (ignoreIfPlaying && playingSounds.some(audio => !audio.paused)) {
|
|
||||||
|
if (ignoreIfPlaying && playingSounds.some((audio) => !audio.paused)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop previous instances if they exist
|
|
||||||
stopSound(soundUrl)
|
stopSound(soundUrl)
|
||||||
|
|
||||||
const audio = await preloadSound(soundUrl)
|
const audio = await preloadSound(soundUrl)
|
||||||
const playingAudio = audio.cloneNode() as HTMLAudioElement
|
const playingAudio = audio.cloneNode() as HTMLAudioElement
|
||||||
playingAudio.loop = loop
|
playingAudio.loop = loop
|
||||||
|
|
||||||
// Initialize or get the array of active sounds for this URL
|
|
||||||
if (!activeSounds.has(soundUrl)) {
|
if (!activeSounds.has(soundUrl)) {
|
||||||
activeSounds.set(soundUrl, [])
|
activeSounds.set(soundUrl, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
activeSounds.get(soundUrl)!.push(playingAudio)
|
activeSounds.get(soundUrl)!.push(playingAudio)
|
||||||
|
|
||||||
playingAudio.addEventListener('ended', () => {
|
// Cleanup when sound ends
|
||||||
const sounds = activeSounds.get(soundUrl)
|
playingAudio.addEventListener(
|
||||||
if (sounds) {
|
'ended',
|
||||||
|
() => {
|
||||||
|
const sounds = activeSounds.get(soundUrl)
|
||||||
|
if (!sounds) return
|
||||||
|
|
||||||
const index = sounds.indexOf(playingAudio)
|
const index = sounds.indexOf(playingAudio)
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
sounds.splice(index, 1)
|
sounds.splice(index, 1)
|
||||||
@ -86,8 +106,9 @@ export function useSoundComposable() {
|
|||||||
if (sounds.length === 0) {
|
if (sounds.length === 0) {
|
||||||
activeSounds.delete(soundUrl)
|
activeSounds.delete(soundUrl)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}, { once: true })
|
{ once: true }
|
||||||
|
)
|
||||||
|
|
||||||
await playingAudio.play()
|
await playingAudio.play()
|
||||||
} catch (error) {
|
} 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)
|
const sounds = activeSounds.get(soundUrl)
|
||||||
if (!sounds) return
|
if (!sounds) return
|
||||||
|
|
||||||
sounds.forEach(audio => {
|
sounds.forEach((audio) => {
|
||||||
audio.pause()
|
audio.pause()
|
||||||
audio.currentTime = 0
|
audio.currentTime = 0
|
||||||
})
|
})
|
||||||
activeSounds.delete(soundUrl)
|
activeSounds.delete(soundUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopAllSounds = () => {
|
/**
|
||||||
activeSounds.forEach((sounds, url) => {
|
* Stops all currently playing sounds
|
||||||
stopSound(url)
|
*/
|
||||||
})
|
function stopAllSounds(): void {
|
||||||
|
activeSounds.forEach((_, url) => stopSound(url))
|
||||||
activeSounds.clear()
|
activeSounds.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearSoundCache = async () => {
|
/**
|
||||||
|
* Clears all cached sounds and stops playback
|
||||||
|
*/
|
||||||
|
async function clearSoundCache(): Promise<void> {
|
||||||
stopAllSounds()
|
stopAllSounds()
|
||||||
audioCache.clear()
|
audioCache.clear()
|
||||||
await soundStorage.reset()
|
await soundStorage.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
// New method to preload multiple sounds
|
* Preloads multiple sounds simultaneously
|
||||||
const preloadSounds = async (soundUrls: string[]) => {
|
*/
|
||||||
|
async function preloadSounds(soundUrls: string[]): Promise<HTMLAudioElement[]> {
|
||||||
return Promise.all(soundUrls.map(preloadSound))
|
return Promise.all(soundUrls.map(preloadSound))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,4 +161,4 @@ export function useSoundComposable() {
|
|||||||
clearSoundCache,
|
clearSoundCache,
|
||||||
preloadSounds
|
preloadSounds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,4 +51,4 @@ export class SoundStorage extends BaseStorage<{ id: string; name: string; base64
|
|||||||
constructor() {
|
constructor() {
|
||||||
super('sounds', 'id, name, createdAt, updatedAt')
|
super('sounds', 'id, name, createdAt, updatedAt')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user