export class SoundManager { constructor() { this.musicVolume = 0.3; // Default volume (not too loud) this.sfxVolume = 0.5; this.currentMusic = null; this.isMuted = false; // Asset Library this.assets = { music: { 'exploration': '/assets/music/ingame/Abandoned_Ruins.mp3' }, sfx: { 'door_open': '/assets/sfx/opendoor.mp3', 'footsteps': '/assets/sfx/footsteps.mp3', 'sword': '/assets/sfx/sword1.mp3', 'arrow': '/assets/sfx/arrow.mp3' } }; this.initialized = false; this.activeLoops = new Map(); } /** * Initializes the audio context if needed (browser restriction handling) * Can be called on the first user interaction (click) */ init() { if (this.initialized) return; this.initialized = true; console.log("[SoundManager] Audio System Initialized"); } playMusic(key) { if (this.isMuted) return; const url = this.assets.music[key]; if (!url) { console.warn(`[SoundManager] Music track not found: ${key}`); return; } // If same track is playing, do nothing if (this.currentMusic && this.currentMusic.src.includes(url) && !this.currentMusic.paused) { return; } // Stop current this.stopMusic(); // Start new this.currentMusic = new Audio(url); this.currentMusic.loop = true; this.currentMusic.volume = this.musicVolume; // Handle autoplay promises const playPromise = this.currentMusic.play(); if (playPromise !== undefined) { playPromise.catch(error => { console.log("[SoundManager] Autoplay prevented. Waiting for user interaction."); // We can add a one-time click listener to window to resume if needed const resume = () => { this.currentMusic.play(); window.removeEventListener('click', resume); window.removeEventListener('keydown', resume); }; window.addEventListener('click', resume); window.addEventListener('keydown', resume); }); } console.log(`[SoundManager] Playing music: ${key}`); } stopMusic() { if (this.currentMusic) { this.currentMusic.pause(); this.currentMusic.currentTime = 0; this.currentMusic = null; } } setMusicVolume(vol) { this.musicVolume = Math.max(0, Math.min(1, vol)); if (this.currentMusic) { this.currentMusic.volume = this.musicVolume; } } toggleMute() { this.isMuted = !this.isMuted; if (this.isMuted) { if (this.currentMusic) this.currentMusic.pause(); } else { if (this.currentMusic) this.currentMusic.play(); } return this.isMuted; } playSound(key) { if (this.isMuted) return; const url = this.assets.sfx[key]; if (!url) { console.warn(`[SoundManager] SFX not found: ${key}`); return; } const audio = new Audio(url); audio.volume = this.sfxVolume; // Fire and forget, but catch errors audio.play().catch(e => { // Check if error is NotAllowedError (autoplay) - silently ignore usually for SFX // or log if needed }); } startLoop(key) { if (this.isMuted) return; if (this.activeLoops.has(key)) return; // Already playing const url = this.assets.sfx[key]; if (!url) return; const audio = new Audio(url); audio.loop = true; audio.volume = this.sfxVolume; audio.play().catch(() => { }); this.activeLoops.set(key, audio); } stopLoop(key) { const audio = this.activeLoops.get(key); if (audio) { audio.pause(); audio.currentTime = 0; this.activeLoops.delete(key); } } }