import { addSignals } from './Signals.js' /** * Class representing an audio player with playback control capabilities. * Supports playing, pausing, resuming, and stopping audio files with volume control * and playback speed adjustment. */ class AudioPlayer { /** * Creates an instance of AudioPlayer. * Initializes the player with default settings and sets up signal handling for events. */ constructor() { this.audio = null; this.isPlaying = false; this.isPaused = false; this.isMuted = false; this.previousVolume = 1.0; this.playStartTime = null; this.playDuration = 0; addSignals(AudioPlayer, 'started', 'ended'); } /** * Plays an audio file with optional playback speed adjustment. * If audio is paused, it will resume playback instead of starting a new file. * * @param {string} audioFile - The path or URL to the audio file. * @param {number} [speed=1.0] - Playback speed multiplier (1.0 is normal speed). * @returns {Promise<void>} Resolves when the audio playback completes. */ async play(audioFile, speed = 1.0) { if (!this.isPlaying && !this.isPaused) { this.audio = new Audio(audioFile); this.audio.playbackRate = speed; this.audio.volume = this.previousVolume; this.isPlaying = true; this.isPaused = false; this.playStartTime = Date.now(); this.playDuration = 0; // Setup play handler this.audio.onplay = () => { this.setMute(this.isMuted); this.emit('started'); }; // Setup ended handler this.audio.onended = () => { this.isPlaying = false; this.updatePlayDuration(); this.emit('ended'); }; try { await this.audio.play(); return new Promise((resolve) => { const originalOnEnded = this.audio.onended; this.audio.onended = () => { originalOnEnded.call(this); // Call the original handler resolve(); }; }); } catch (error) { console.error("Error playing audio:", error); this.isPlaying = false; throw error; } } else if (this.isPaused) { await this.continue(); } } /** * Pauses the currently playing audio. * Updates play duration when pausing. */ pause() { if (!this.isPaused && this.audio) { this.audio.pause(); this.isPaused = true; this.updatePlayDuration(); } } /** * Resumes playback of a paused audio file. * * @returns {Promise<void>} Resolves when the resumed audio playback completes. */ async continue() { if (this.isPaused && this.audio) { this.isPaused = false; this.playStartTime = Date.now(); // Setup play handler this.audio.onplay = () => { this.setMute(this.isMuted); this.emit('started'); }; try { await this.audio.play(); return new Promise((resolve) => { const originalOnEnded = this.audio.onended; this.audio.onended = () => { originalOnEnded.call(this); // Call the original handler resolve(); }; }); } catch (error) { console.error("Error continuing audio:", error); this.isPaused = true; throw error; } } else { console.log("No paused audio to continue."); } } /** * Stops the current audio playback and resets all player states. * Removes event listeners and updates final play duration. */ stop() { if (this.audio) { this.audio.pause(); this.audio.currentTime = 0; this.audio.onplay = null; // Clean up play handler this.audio.onended = null; // Clean up ended handler this.isPlaying = false; this.isPaused = false; this.updatePlayDuration(); } } /** * Updates the total play duration based on the current session. * Called internally when playback is paused, stopped, or ends. * @private */ updatePlayDuration() { if (this.playStartTime) { const now = Date.now(); this.playDuration += now - this.playStartTime; this.playStartTime = null; } } /** * Returns the total play duration in milliseconds. * * @returns {number} Total play duration in milliseconds. */ getPlayDuration() { return this.playDuration; } /** * Sets the audio volume level. * * @param {number} volume - Volume level between 0.0 and 1.0. */ setVolume(volume) { if (this.audio) { if (volume >= 0 && volume <= 1) { this.audio.volume = volume; this.previousVolume = volume; } else { console.log("Volume must be between 0.0 and 1.0"); } } else { console.log("No audio loaded."); } } /** * Creates a delay in the execution flow. * * @param {number} ms - Number of milliseconds to wait. * @returns {Promise<void>} Resolves after the specified delay. */ async silence(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Set the mute state of the audio player. * Stores the previous volume level when muting and restores it when unmuting. * @param {boolean} b Whether to mute the audio playback */ setMute(b) { this.isMuted = b; if (this.audio) { if (!this.isMuted) { this.audio.volume = this.previousVolume; } else { this.previousVolume = this.audio.volume; this.audio.volume = 0; } } } /** * Emits an event of the specified type * @param {string} type - The event type to emit */ emit(type) { if (this[`${type}Signal`]) { this[`${type}Signal`].emit(); } } } export { AudioPlayer }