143 lines
4.1 KiB
JavaScript
143 lines
4.1 KiB
JavaScript
|
|
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);
|
|
}
|
|
}
|
|
}
|