194 lines
5.6 KiB
JavaScript
194 lines
5.6 KiB
JavaScript
/**
|
|
* AudioManager Module
|
|
* Manages loading and playback of non-TTS audio effects triggered by tags.
|
|
*/
|
|
export class AudioManager {
|
|
constructor() {
|
|
this.sounds = new Map();
|
|
this.currentAudio = null;
|
|
this.currentLoop = null;
|
|
}
|
|
|
|
/**
|
|
* Load a sound file
|
|
* @param {string} id - The identifier for the sound
|
|
* @param {string} url - The URL of the sound file
|
|
* @returns {Promise} A promise that resolves when the sound is loaded
|
|
*/
|
|
loadSound(id, url) {
|
|
return new Promise((resolve, reject) => {
|
|
const audio = new Audio(url);
|
|
audio.addEventListener('canplaythrough', () => {
|
|
this.sounds.set(id, audio);
|
|
resolve(audio);
|
|
}, { once: true });
|
|
audio.addEventListener('error', (error) => {
|
|
reject(error);
|
|
});
|
|
audio.load();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Play a sound
|
|
* @param {string} id - The identifier for the sound
|
|
* @param {boolean} loop - Whether to loop the sound
|
|
* @returns {HTMLAudioElement|null} The audio element or null if not found
|
|
*/
|
|
playSound(id, loop = false) {
|
|
const audio = this.sounds.get(id);
|
|
if (!audio) {
|
|
console.warn(`Sound with id "${id}" not found.`);
|
|
return null;
|
|
}
|
|
|
|
if (loop) {
|
|
if (this.currentLoop) {
|
|
this.currentLoop.pause();
|
|
this.currentLoop.currentTime = 0;
|
|
}
|
|
audio.loop = true;
|
|
this.currentLoop = audio;
|
|
} else {
|
|
if (this.currentAudio) {
|
|
this.currentAudio.pause();
|
|
this.currentAudio.currentTime = 0;
|
|
}
|
|
this.currentAudio = audio;
|
|
}
|
|
|
|
audio.play().catch(error => {
|
|
console.error('Error playing audio:', error);
|
|
});
|
|
|
|
return audio;
|
|
}
|
|
|
|
/**
|
|
* Play a sound from a URL directly (without preloading)
|
|
* @param {string} url - The URL of the sound file
|
|
* @param {boolean} loop - Whether to loop the sound
|
|
* @returns {HTMLAudioElement} The audio element
|
|
*/
|
|
playSoundFromUrl(url, loop = false) {
|
|
if (loop) {
|
|
if (this.currentLoop) {
|
|
this.currentLoop.pause();
|
|
this.currentLoop.removeAttribute('src');
|
|
this.currentLoop.load();
|
|
}
|
|
this.currentLoop = new Audio(url);
|
|
this.currentLoop.loop = true;
|
|
this.currentLoop.play().catch(error => {
|
|
console.error('Error playing audio loop:', error);
|
|
});
|
|
return this.currentLoop;
|
|
} else {
|
|
if (this.currentAudio) {
|
|
this.currentAudio.pause();
|
|
this.currentAudio.removeAttribute('src');
|
|
this.currentAudio.load();
|
|
}
|
|
this.currentAudio = new Audio(url);
|
|
this.currentAudio.play().catch(error => {
|
|
console.error('Error playing audio:', error);
|
|
});
|
|
return this.currentAudio;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop a specific sound
|
|
* @param {string} id - The identifier for the sound
|
|
*/
|
|
stopSound(id) {
|
|
const audio = this.sounds.get(id);
|
|
if (audio) {
|
|
audio.pause();
|
|
audio.currentTime = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop all sounds
|
|
*/
|
|
stopAllSounds() {
|
|
if (this.currentAudio) {
|
|
this.currentAudio.pause();
|
|
this.currentAudio.currentTime = 0;
|
|
this.currentAudio = null;
|
|
}
|
|
|
|
if (this.currentLoop) {
|
|
this.currentLoop.pause();
|
|
this.currentLoop.currentTime = 0;
|
|
this.currentLoop = null;
|
|
}
|
|
|
|
this.sounds.forEach(audio => {
|
|
audio.pause();
|
|
audio.currentTime = 0;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set the volume for all sounds
|
|
* @param {number} volume - The volume level (0.0 to 1.0)
|
|
*/
|
|
setVolume(volume) {
|
|
this.sounds.forEach(audio => {
|
|
audio.volume = volume;
|
|
});
|
|
|
|
if (this.currentAudio) {
|
|
this.currentAudio.volume = volume;
|
|
}
|
|
|
|
if (this.currentLoop) {
|
|
this.currentLoop.volume = volume;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a sound is currently playing
|
|
* @param {string} id - The identifier for the sound
|
|
* @returns {boolean} Whether the sound is playing
|
|
*/
|
|
isPlaying(id) {
|
|
const audio = this.sounds.get(id);
|
|
return audio ? !audio.paused : false;
|
|
}
|
|
|
|
/**
|
|
* Fade out the current audio
|
|
* @param {number} duration - The duration of the fade in milliseconds
|
|
* @returns {Promise} A promise that resolves when the fade is complete
|
|
*/
|
|
fadeOutCurrentAudio(duration = 1000) {
|
|
return new Promise((resolve) => {
|
|
if (!this.currentAudio || this.currentAudio.paused) {
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
const audio = this.currentAudio;
|
|
const initialVolume = audio.volume;
|
|
const volumeStep = initialVolume / (duration / 50);
|
|
let currentVolume = initialVolume;
|
|
|
|
const fadeInterval = setInterval(() => {
|
|
currentVolume -= volumeStep;
|
|
if (currentVolume <= 0) {
|
|
clearInterval(fadeInterval);
|
|
audio.pause();
|
|
audio.currentTime = 0;
|
|
audio.volume = initialVolume; // Reset volume for future use
|
|
resolve();
|
|
} else {
|
|
audio.volume = currentVolume;
|
|
}
|
|
}, 50);
|
|
});
|
|
}
|
|
}
|