UNPKG

ts-audio

Version:

1 lines 49.1 kB
{"version":3,"file":"index.cjs","sources":["../src/audio/utils.ts","../src/audio/states.ts","../src/EventEmitter.ts","../src/EventHandler.ts","../src/audio/initializeSource.ts","../src/audio/Audio.ts","../src/audio/AudioCtx.ts","../src/audio/decodeAudioData.ts","../src/playlist/states.ts","../src/playlist/AudioPlaylist.ts","../src/playlist/utils.ts","../src/playlist/playAudio.ts"],"sourcesContent":["/**\n * Fetches an audio file and returns it as an ArrayBuffer.\n */\nexport const getBuffer = (file: string): Promise<ArrayBuffer> =>\n fetch(file).then((response) => {\n if (!response.ok) {\n throw new Error(`HTTP error, status = ${response.status}`)\n }\n\n return response.arrayBuffer()\n })\n\n/**\n * Throws a formatted error with the ts-audio prefix.\n */\nexport const throwsError = (value: string): void => {\n throw new Error(`\\`ts-audio\\`: ${value}`)\n}\n\n/**\n * Attempts to preload an audio file with automatic retry mechanism.\n * Will recursively retry loading the file up to the specified number of attempts.\n */\nexport const preloadFile = (file: string, attempts = 3, done?: () => void): void => {\n fetch(file)\n .then(done)\n .catch(() => {\n if (!attempts) {\n return\n }\n\n preloadFile(file, attempts - 1)\n })\n}\n","/**\n * Type definition for the audio player's internal state.\n */\nexport type AudioState = {\n isDecoded: boolean\n isPlaying: boolean\n hasStarted: boolean\n source: AudioBufferSourceNode | null\n gainNode: GainNode | null\n}\n\n/**\n * Default initial state for audio decoding and playback.\n */\nexport const defaultStates: AudioState = {\n isDecoded: false,\n isPlaying: false,\n hasStarted: false,\n source: null,\n gainNode: null,\n}\n","/**\n * Represents an event with associated data.\n */\nexport type Event = {\n /**\n * The data associated with the event. The type of data is unknown.\n */\n data: unknown\n}\n\n/**\n * Event emitter class that allows registering event listeners and emitting events.\n */\nexport class EventEmitter {\n /**\n * A map of event keys to their respective callback functions.\n * @private\n */\n private events: { [key: string]: (param: Event) => void }\n\n /**\n * Initializes a new instance of the EventEmitter class.\n */\n constructor() {\n this.events = {}\n }\n\n /**\n * Registers a listener for a specific event key.\n *\n * @param {string} keyEvent - The key of the event to listen for.\n * @param {(param: Event) => void} callback - The callback function to be invoked when the event is emitted.\n */\n public listener(keyEvent: string, callback: (param: Event) => void): void {\n this.events[keyEvent] = callback\n }\n\n /**\n * Emits an event, invoking the corresponding listener with the provided parameter.\n *\n * @param {string} keyEvent - The key of the event to emit.\n * @param {Event} param - The parameter to pass to the event's callback function.\n */\n public emit(keyEvent: string, param: Event): void {\n if (this.events[keyEvent]) {\n this.events[keyEvent](param)\n }\n }\n\n /**\n * Removes all registered event listeners.\n * Clears the internal event map to ensure no callbacks are retained.\n */\n public removeAllListeners(): void {\n this.events = {}\n }\n}\n","import type { EventEmitter } from './EventEmitter'\n\ntype callbackType = <T>(param: { [data: string]: T }) => void\n\n/**\n * EventHandler class to manage event listeners for an audio context.\n */\nexport class EventHandler {\n private emitter: EventEmitter\n private audioCtx: AudioContext | undefined\n\n /**\n * Creates an instance of EventHandler.\n * @param emitter - The event emitter instance to manage event listeners.\n * @param audioCtx - AudioContext instance to monitor state changes. Optional to facilitate testing.\n */\n constructor(emitter: EventEmitter, audioCtx?: AudioContext) {\n this.emitter = emitter\n this.audioCtx = audioCtx\n }\n\n /**\n * Registers a callback for the 'decoded' event.\n * @param callback - The callback to be invoked when the event occurs.\n */\n public ready(callback: callbackType) {\n this.emitter.listener('decoded', callback)\n }\n\n /**\n * Registers a callback for the 'start' event.\n * @param callback - The callback to be invoked when the event occurs.\n */\n public start(callback: callbackType) {\n this.emitter.listener('start', callback)\n }\n\n /**\n * Registers a callback for the 'end' event.\n * @param callback - The callback to be invoked when the event occurs.\n */\n public end(callback: callbackType) {\n this.emitter.listener('end', callback)\n }\n\n /**\n * Monitors the state changes of the AudioContext and invokes the callback.\n * @param callback - The callback to be invoked when the AudioContext state changes.\n */\n public state(callback: callbackType) {\n if (!this.audioCtx) return\n\n this.audioCtx.onstatechange = () => callback({ data: this.audioCtx?.state })\n }\n\n /**\n * Disposes the EventHandler by removing the onstatechange listener from the AudioContext.\n * This method should be called when the EventHandler is no longer needed to prevent memory leaks.\n */\n public dispose(): void {\n if (this.audioCtx) {\n this.audioCtx.onstatechange = null\n }\n }\n}\n","import type { EventEmitter } from '../EventEmitter'\nimport type { AudioState } from './states'\n\ntype InitializeSourceConfig = {\n audioCtx: AudioContext\n volume: number\n emitter: EventEmitter\n states: AudioState\n}\n\n/**\n * Initializes and configures an audio source node with gain control.\n * Sets up the audio processing chain and configures event handling for playback completion.\n *\n * @param {InitializeSourceConfig} config - Configuration object containing:\n * - audioCtx: Web Audio API context\n * - volume: Initial volume level (0 to 1)\n * - emitter: Event emitter for broadcasting audio events\n * - states: State management object for tracking audio state\n * @returns {void}\n * @emits {Event} 'end' - Emitted when audio playback completes\n */\nexport const initializeSource = ({\n audioCtx,\n volume,\n emitter,\n states,\n}: InitializeSourceConfig): void => {\n const source = (states.source = audioCtx.createBufferSource())\n const gainNode = (states.gainNode = audioCtx.createGain())\n\n gainNode.gain.value = volume\n gainNode.connect(audioCtx.destination)\n source.connect(gainNode)\n\n source.onended = () => {\n states.hasStarted = false\n states.isPlaying = false\n emitter.emit('end', { data: null })\n }\n}\n","import { AudioCtx } from './AudioCtx'\nimport { defaultStates } from './states'\nimport { EventEmitter } from '../EventEmitter'\nimport { EventHandler } from '../EventHandler'\nimport { decodeAudioData } from './decodeAudioData'\nimport { initializeSource } from './initializeSource'\nimport { getBuffer, preloadFile } from './utils'\n\n/**\n * Configuration options for creating an Audio instance.\n */\ntype AudioProp = {\n /** Path or URL to the audio file */\n file: string\n /** Initial volume level (0 to 1) */\n volume?: number\n /** Time in seconds to start playback */\n time?: number\n /** Whether to start playing automatically */\n autoPlay?: boolean\n /** Whether to loop the audio */\n loop?: boolean\n /** Whether to preload the audio file */\n preload?: boolean\n}\n\n/**\n * Valid event types that can be emitted by the Audio instance.\n */\ntype AudioEvent = 'ready' | 'start' | 'state' | 'end'\n\n/**\n * If `AudioContext` is initialized before a user gesture on the page, its\n * state becomes `suspended` by default. Once `AudioContext.state` is `suspended`,\n * the only way to start it after a user gesture is executing the `resume` method.\n */\nconst start = (audioCtx: AudioContext, source: AudioBufferSourceNode, time: number) =>\n audioCtx.state === 'suspended'\n ? audioCtx.resume().then(() => source.start(0, time))\n : source.start(0, time)\n\n/**\n * Audio player class that provides control over a single audio file.\n * Implements the AudioType interface for managing audio playback, volume, and events.\n */\nexport class AudioClass {\n /** @private Path or URL to the audio file */\n private _file: AudioProp['file']\n /** @private Initial volume level set during construction */\n private _initialVolume: number\n /** @private Initial time in seconds to start playback */\n private _initialTime: number\n /** @private Flag indicating if audio should play automatically */\n private _autoPlay: boolean\n /** @private Initial loop state set during construction */\n private _initialLoop: boolean\n /** @private Web Audio API context */\n private _audioCtx: AudioContext\n /** @private Internal state management object */\n private _states: typeof defaultStates\n /** @private Event emitter for handling audio events */\n private _emitter: EventEmitter\n /** @private Event handler for managing event subscriptions */\n private _eventHandler: EventHandler\n /** @private Track when playback started for currentTime calculation */\n private _startTime = 0\n /** @private Track pause position for accurate seeking */\n private _pauseTime = 0\n /** @private Flag to track if seeking occurred while audio was paused */\n private _hasSeekedWhilePaused = false\n /** @private Store seek time requested before audio is decoded */\n private _pendingSeekTime: number | null = null\n /** @private Flag to track if the instance has been destroyed */\n private _isDestroyed = false\n\n /**\n * Creates an instance of Audio player.\n *\n * @param {AudioProp} config - The audio configuration object\n * @param {string} config.file - Path or URL to the audio file\n * @param {number} [config.volume=1] - Initial volume level (0 to 1)\n * @param {number} [config.time=0] - Time in seconds to start playback\n * @param {boolean} [config.autoPlay=false] - Whether to start playing automatically\n * @param {boolean} [config.loop=false] - Whether to loop the audio\n * @param {boolean} [config.preload=false] - Whether to preload the audio file\n */\n constructor({\n file,\n volume = 1,\n time = 0,\n autoPlay = false,\n loop = false,\n preload = false,\n }: AudioProp) {\n this._file = file\n this._initialVolume = volume\n this._initialTime = time\n this._autoPlay = autoPlay\n this._initialLoop = loop\n this._audioCtx = AudioCtx()\n this._states = { ...defaultStates }\n this._emitter = new EventEmitter()\n this._eventHandler = new EventHandler(this._emitter, this._audioCtx)\n\n if (preload) {\n preloadFile(file)\n }\n }\n\n /**\n * Recreates source and starts playback at specified time.\n * @private\n * @param {number} time - Time in seconds to start playback\n * @param {AudioBuffer} buffer - The audio buffer to use\n */\n private recreateAndStart(time: number, buffer: AudioBuffer): void {\n try {\n // Stop current source if playing\n if (this._states.source) {\n this._states.source.stop(0)\n this._states.source.onended = null\n }\n\n initializeSource({\n audioCtx: this._audioCtx,\n volume: this._states.gainNode?.gain.value ?? this._initialVolume,\n emitter: this._emitter,\n states: this._states,\n })\n\n const { source } = this._states\n\n if (source) {\n source.buffer = buffer\n source.loop = this._initialLoop\n\n start(this._audioCtx, source, time)\n this._startTime = this._audioCtx.currentTime\n this._pauseTime = time\n this._states.isPlaying = true\n this._states.hasStarted = true\n }\n } catch (error) {\n console.error('Failed to recreate audio source:', error)\n this._states.isPlaying = false\n }\n }\n\n /**\n * Fetches and decodes the audio buffer for the given source node.\n * @private\n * @param {AudioBufferSourceNode} source - The audio source node to load buffer into\n */\n private curryGetBuffer(source: AudioBufferSourceNode): void {\n this._states.isDecoded = false\n\n getBuffer(this._file)\n .then((arrayBuffer) => {\n decodeAudioData({\n audioCtx: this._audioCtx,\n source,\n arrayBuffer,\n autoPlay: this._autoPlay,\n loop: this._initialLoop,\n states: this._states,\n emitter: this._emitter,\n })\n })\n .catch(console.error)\n }\n\n /**\n * Starts or resumes audio playback.\n * If playback hasn't started, initializes audio source and begins playback.\n * If playback was paused, resumes from the current position.\n * If seeking occurred while paused, recreates the source at the new position.\n */\n public play(): void {\n if (this._isDestroyed) {\n return\n }\n\n if (this._states.hasStarted && !this._hasSeekedWhilePaused) {\n this._audioCtx.resume()\n this._startTime = this._audioCtx.currentTime\n this._states.isPlaying = true\n return\n }\n\n // If seeked while paused, recreate source at the new position\n if (this._hasSeekedWhilePaused && this._states.source?.buffer) {\n const audioBuffer = this._states.source.buffer\n this.recreateAndStart(this._pauseTime, audioBuffer)\n this._hasSeekedWhilePaused = false\n return\n }\n\n initializeSource({\n audioCtx: this._audioCtx,\n volume: this._initialVolume,\n emitter: this._emitter,\n states: this._states,\n })\n\n // Apply pending seek if one was requested before audio was ready\n if (this._pendingSeekTime !== null) {\n this._pauseTime = this._pendingSeekTime\n this._pendingSeekTime = null\n }\n\n const { source } = this._states\n\n if (source) {\n this.curryGetBuffer(source)\n\n if (this._states.isDecoded) {\n start(this._audioCtx, source, this._pauseTime ?? this._initialTime)\n this._startTime = this._audioCtx.currentTime\n } else {\n this._emitter.listener('decoded', () => {\n start(this._audioCtx, source, this._pauseTime ?? this._initialTime)\n this._startTime = this._audioCtx.currentTime\n })\n }\n\n this._states.hasStarted = true\n this._states.isPlaying = true\n this._emitter.emit('start', { data: null })\n }\n }\n\n /**\n * Pauses audio playback by suspending the audio context.\n */\n public pause(): void {\n if (this._isDestroyed) {\n return\n }\n\n if (this._states.isPlaying) {\n this._pauseTime = this.currentTime\n }\n\n this._audioCtx.suspend()\n this._states.isPlaying = false\n this._hasSeekedWhilePaused = false\n }\n\n /**\n * Toggles between play and pause states.\n */\n public toggle(): void {\n if (this._isDestroyed) {\n return\n }\n\n if (this._states.isPlaying) {\n this.pause()\n } else {\n this.play()\n }\n }\n\n /**\n * Stops audio playback completely.\n * Different from pause as it resets the playback position.\n */\n public stop(): void {\n if (this._isDestroyed) {\n return\n }\n\n if (this._states.hasStarted) {\n this._states.source?.stop(0)\n this._states.isPlaying = false\n }\n }\n\n /**\n * Subscribes to audio events.\n * @param {AudioEvent} eventType - Type of event to listen for\n * @param {Function} callback - Function to call when event occurs\n */\n public on(eventType: AudioEvent, callback: <T>(param: { [data: string]: T }) => void): void {\n if (this._isDestroyed) {\n return\n }\n\n this._eventHandler[eventType]?.(callback)\n }\n\n /**\n * Gets the current volume level.\n * @returns {number} Current volume value between 0 and 1\n */\n public get volume(): number {\n return this._states.gainNode?.gain.value ?? 0\n }\n\n /**\n * Sets the audio volume level.\n * @param {number} newVolume - New volume value between 0 and 1\n */\n public set volume(newVolume: number) {\n if (this._isDestroyed) {\n return\n }\n\n if (this._states.gainNode) {\n this._states.gainNode.gain.value = newVolume\n }\n }\n\n /**\n * Gets the current loop state.\n * @returns {boolean} Whether audio is set to loop\n */\n public get loop(): boolean {\n return this._states.source?.loop ?? false\n }\n\n /**\n * Sets the loop state.\n * @param {boolean} newLoop - Whether audio should loop\n */\n public set loop(newLoop: boolean) {\n if (this._isDestroyed) {\n return\n }\n\n if (this._states.source) {\n this._states.source.loop = newLoop\n }\n }\n\n /**\n * Gets the current state of the audio context.\n * @returns {AudioContextState} Current state of the audio context\n */\n public get state(): AudioContextState {\n return this._audioCtx.state\n }\n\n /**\n * Gets the current AudioContext instance.\n * @returns {AudioContext} The current AudioContext instance\n */\n public get audioCtx(): AudioContext {\n return this._audioCtx\n }\n\n /**\n * Gets the total duration of the loaded audio in seconds.\n * @returns {number} The duration of the audio if available; otherwise, returns 0.\n */\n public get duration(): number {\n return this._states.source?.buffer?.duration ?? 0\n }\n\n /**\n * Gets the current playback position in seconds.\n * @returns {number} The current playback position if available; otherwise, returns 0.\n */\n public get currentTime(): number {\n if (!this._states.hasStarted) {\n return 0\n }\n\n if (!this._states.isPlaying) {\n return this._pauseTime\n }\n\n return this._pauseTime + (this._audioCtx.currentTime - this._startTime)\n }\n\n /**\n * Indicates whether the audio is currently playing.\n * @returns {boolean}\n */\n public get isPlaying(): boolean {\n return this._states.isPlaying\n }\n\n /**\n * Seeks to a specific time position in the audio track.\n * @param {number} time - Time in seconds to seek to (0 ≤ time ≤ duration)\n */\n public seek(time: number): void {\n if (this._isDestroyed) {\n return\n }\n\n // Clamp time if we know duration, otherwise trust the value\n const clampedTime =\n this.duration > 0 ? Math.max(0, Math.min(time, this.duration)) : Math.max(0, time)\n\n // If audio not decoded yet, store pending seek\n if (!this._states.isDecoded || !this._states.source?.buffer) {\n this._pendingSeekTime = clampedTime\n return\n }\n\n // Clear any pending seek\n this._pendingSeekTime = null\n\n // Re-clamp now that we have actual duration\n const finalTime = Math.max(0, Math.min(clampedTime, this.duration))\n const wasPlaying = this._states.isPlaying\n const audioBuffer = this._states.source.buffer\n\n if (wasPlaying && this._states.source) {\n // Temporarily remove onended handler to prevent false \"end\" events during seek\n this._states.source.onended = null\n try {\n this._states.source.stop(0)\n } catch (error) {\n console.error('Error stopping audio source:', error)\n }\n this.recreateAndStart(finalTime, audioBuffer)\n } else {\n // Just update pause position for paused audio\n this._pauseTime = finalTime\n this._hasSeekedWhilePaused = true\n }\n }\n\n /**\n * Destroys the Audio instance, stopping playback, disconnecting audio nodes,\n * removing event listeners, and releasing references.\n * This method is idempotent and safe to call multiple times.\n */\n public destroy(): void {\n if (this._isDestroyed) {\n return\n }\n\n // Stop playback if active\n if (this._states.source) {\n this._states.source.onended = null\n\n try {\n this._states.source.stop(0)\n } catch {\n // Ignore errors if source is already stopped\n }\n\n this._states.source.disconnect()\n }\n\n // Disconnect gain node\n this._states.gainNode?.disconnect()\n\n // Dispose event handler\n this._eventHandler.dispose()\n\n // Clear all event listeners\n this._emitter.removeAllListeners()\n\n // Clear state references\n this._states.source = null\n this._states.gainNode = null\n this._states.isPlaying = false\n this._states.hasStarted = false\n this._states.isDecoded = false\n\n this._isDestroyed = true\n }\n}\n\n/**\n * Factory function to create a new Audio instance.\n *\n * @param {AudioPropType} props - The audio configuration properties\n * @returns {AudioType} A new Audio instance\n */\nexport default (props: AudioProp): AudioClass => new AudioClass(props)\n","import { throwsError } from './utils'\n\ndeclare global {\n interface Window {\n webkitAudioContext: typeof window.AudioContext\n }\n}\n\n/**\n * Creates and returns a new AudioContext instance with cross-browser support.\n * Attempts to use standard AudioContext first, falls back to webkitAudioContext for older browsers.\n *\n * @returns {AudioContext} A new AudioContext instance\n * @throws {Error} If the browser doesn't support AudioContext\n */\nexport const AudioCtx = (): AudioContext => {\n const Context = window.AudioContext || window.webkitAudioContext\n\n if (!Context) {\n throwsError(\"Your browser doesn't support AudioContext - https://bit.ly/2YWmpnX\")\n }\n\n return new Context()\n}\n","import type { AudioState } from './states'\nimport type { EventEmitter } from '../EventEmitter'\n\n/**\n * Configuration options for audio decoding\n */\ntype DecodeAudioDataConfig = {\n /** The Web Audio API context */\n audioCtx: AudioContext\n /** The audio source node to configure */\n source: AudioBufferSourceNode\n /** The raw audio data to decode */\n arrayBuffer: ArrayBuffer\n /** Whether to start playback immediately after decoding */\n autoPlay: boolean\n /** Whether the audio should loop when playing */\n loop: boolean\n /** State object to track decoding and playback status */\n states: AudioState\n /** Event emitter to broadcast decode completion */\n emitter: EventEmitter\n}\n\n/**\n * Decodes audio data from an ArrayBuffer and configures the audio source node.\n * On successful decode, sets up the audio buffer, configures looping, and optionally starts playback.\n *\n * @param {DecodeAudioDataConfig} config - Configuration object containing all necessary parameters\n * @returns {void}\n * @emits {Event} 'decoded' - Emitted when audio data is successfully decoded, includes the AudioBuffer\n */\nexport const decodeAudioData = ({\n audioCtx,\n source,\n arrayBuffer,\n autoPlay,\n loop,\n states,\n emitter,\n}: DecodeAudioDataConfig): void => {\n const onSuccess = (buffer: AudioBuffer) => {\n source.buffer = buffer\n source.loop = loop\n\n states.isDecoded = true\n emitter.emit('decoded', { data: buffer })\n\n if (autoPlay) {\n source.start(0)\n states.isPlaying = true\n }\n }\n\n audioCtx.decodeAudioData(arrayBuffer, onSuccess, console.error)\n}\n","import type { AudioClass } from '../audio/Audio'\n\n/**\n * Represents the global state for an audio playlist.\n */\nexport type AudioPlaylistState = {\n volume: number\n loop: boolean\n audio: AudioClass | null\n isStopped: boolean\n isPlaying: boolean\n audioIndex: number\n}\n\n/**\n * Default initial state for the audio playlist.\n */\nconst states: AudioPlaylistState = {\n volume: 1,\n loop: false,\n audio: null,\n isStopped: false,\n isPlaying: false,\n audioIndex: 0,\n}\n\nexport default states\n","/**\n * @fileoverview Audio playlist manager that handles playback of multiple audio files\n * with features like shuffle, loop, and weighted random selection.\n */\n\nimport Audio from '../audio/Audio'\nimport { EventEmitter } from '../EventEmitter'\nimport playAudio from './playAudio'\nimport globalStates from './states'\nimport { shuffle as shuffleHelper, weightedFiles, preloadFiles } from './utils'\n\n/**\n * Configuration options for initializing an AudioPlaylist instance.\n */\ntype AudioPlaylistConfig = {\n files: string[] | { [key: string]: number }\n volume?: number\n loop?: boolean\n shuffle?: boolean\n preload?: boolean\n preloadLimit?: number\n}\n\n/**\n * Events that can be emitted by the AudioPlaylist.\n */\ntype AudioPlaylistEvent = 'start' | 'end'\n\n/**\n * Manages audio playlist functionality including playback control, file management,\n * and event handling.\n */\nclass AudioPlaylist {\n /** Event emitter for handling playlist events */\n private emiter: EventEmitter\n /** Global state object containing audio playback states */\n private states: typeof globalStates\n /** Array of audio file paths to be played */\n private copiedFiles: string[]\n /** Flag indicating if playlist should loop */\n private shouldLoop: boolean\n /** Curried function for playing audio files */\n private curryPlayAudio: ReturnType<typeof playAudio>\n /** Flag to track if the instance has been destroyed */\n private isDestroyed = false\n\n /**\n * Creates an instance of AudioPlaylist.\n * @param {Object} config - The playlist configuration\n * @param {(string[] | Record<string, number>)} config.files - Array of audio files or weighted object\n * @param {number} [config.volume=1] - Initial volume level (0-1)\n * @param {boolean} [config.loop=false] - Whether to loop the playlist\n * @param {boolean} [config.shuffle=false] - Whether to shuffle the playlist\n * @param {boolean} [config.preload=false] - Whether to preload audio files\n * @param {number} [config.preloadLimit=3] - Number of files to preload\n */\n constructor({\n files,\n volume = 1,\n loop = false,\n shuffle = false,\n preload = false,\n preloadLimit = 3,\n }: AudioPlaylistConfig) {\n this.emiter = new EventEmitter()\n this.states = { ...globalStates, ...{ volume, loop } }\n\n const hasWeights = !Array.isArray(files)\n this.shouldLoop = loop || hasWeights\n\n const normalizedFiles: string[] = hasWeights\n ? weightedFiles(files as { [key: string]: number })\n : (files as string[])\n\n this.copiedFiles =\n shuffle || hasWeights ? shuffleHelper(normalizedFiles) : normalizedFiles.slice()\n\n this.curryPlayAudio = playAudio(this.states, this.emiter)\n\n if (preload) {\n preloadFiles(this.copiedFiles, preloadLimit)\n }\n }\n\n /**\n * Starts or resumes audio playback.\n * If no audio is playing or playback was stopped, starts from current position.\n */\n play(): void {\n if (this.isDestroyed) {\n return\n }\n\n const { audio } = this.states\n this.states.isPlaying = true\n\n if (!audio || this.states.isStopped) {\n this.curryPlayAudio(this.copiedFiles, this.shouldLoop)\n this.states.isStopped = false\n return\n }\n\n audio.play()\n }\n\n /**\n * Toggles between play and pause states.\n */\n toggle(): void {\n if (this.isDestroyed) {\n return\n }\n\n if (this.states.isPlaying) {\n this.pause()\n } else {\n this.play()\n }\n }\n\n /**\n * Pauses the current audio playback.\n */\n pause(): void {\n if (this.isDestroyed) {\n return\n }\n\n this.states.audio?.pause()\n this.states.isPlaying = false\n }\n\n /**\n * Stops the current audio playback and resets the player.\n */\n stop(): void {\n if (this.isDestroyed) {\n return\n }\n\n this.states.isPlaying = false\n this.states.isStopped = true\n this.states.audio?.stop()\n }\n\n /**\n * Plays the next audio file in the playlist sequence.\n * Handles wrapping back to the beginning when reaching the end of the playlist.\n */\n next(): void {\n if (this.isDestroyed) {\n return\n }\n\n const isLastFile = this.states.audioIndex === this.copiedFiles.length - 1\n this.states.audioIndex = isLastFile ? 0 : this.states.audioIndex + 1\n\n this.states.audio?.pause()\n\n const file = this.copiedFiles[this.states.audioIndex]\n const audio = Audio({ file, volume: this.states.volume })\n\n this.states.audio = audio\n audio.play()\n }\n\n /**\n * Plays the previous audio file in the playlist sequence.\n * Handles wrapping to the end of the playlist when at the beginning.\n */\n prev(): void {\n if (this.isDestroyed) {\n return\n }\n\n const isFirstFile = this.states.audioIndex === 0\n this.states.audioIndex = isFirstFile ? this.copiedFiles.length - 1 : this.states.audioIndex - 1\n\n this.states.audio?.pause()\n\n const file = this.copiedFiles[this.states.audioIndex]\n const audio = Audio({ file, volume: this.states.volume })\n this.states.audio = audio\n audio.play()\n }\n\n /**\n * Registers an event listener for playlist events.\n * @param {AudioPlaylistEvent} eventType - Type of event to listen for\n * @param {Function} callback - Callback function to execute when event occurs\n */\n on(eventType: AudioPlaylistEvent, callback: (param: { [data: string]: unknown }) => void): void {\n if (this.isDestroyed) {\n return\n }\n\n this.emiter.listener(eventType, callback)\n }\n\n /**\n * Gets the current volume level.\n * @returns {number} Current volume level (0-1)\n */\n get volume(): number {\n return this.states.volume\n }\n\n /**\n * Sets the volume level.\n * @param {number} newVolume - New volume level (0-1)\n */\n set volume(newVolume: number) {\n if (this.isDestroyed) {\n return\n }\n\n this.states.volume = newVolume\n\n if (this.states.audio) {\n this.states.audio.volume = newVolume\n }\n }\n\n /**\n * Gets the current loop state.\n * @returns {boolean} Whether playlist is set to loop\n */\n get loop(): boolean {\n return this.states.loop\n }\n\n /**\n * Sets the loop state.\n * @param {boolean} newLoop - New loop state\n */\n set loop(newLoop: boolean) {\n if (this.isDestroyed) {\n return\n }\n\n this.states.loop = newLoop\n }\n\n /**\n * Gets the current AudioContext instance.\n * @returns {(AudioContext | undefined)} Current AudioContext or undefined if not initialized\n */\n get audioCtx(): AudioContext | undefined {\n return this.states.audio?.audioCtx\n }\n\n /**\n * Destroys the AudioPlaylist instance, stopping playback, destroying the current audio instance,\n * removing event listeners, and releasing references.\n * This method is safe to call multiple times.\n */\n destroy(): void {\n if (this.isDestroyed) {\n return\n }\n\n // Stop playback\n this.stop()\n\n // Destroy current audio instance if exists\n this.states.audio?.destroy()\n\n // Clear all event listeners\n this.emiter.removeAllListeners()\n\n // Clear references\n this.states.audio = null\n this.copiedFiles = []\n\n // Set destroyed flag\n this.isDestroyed = true\n }\n}\n\n/**\n * Factory function to create a new AudioPlaylist instance.\n * @param {AudioPlaylistConfig} params - Configuration parameters for the playlist\n */\nexport default (params: AudioPlaylistConfig) => new AudioPlaylist(params)\n","/**\n * Converts an object of weighted file paths into an array where each file appears\n * multiple times based on its weight.\n *\n * @example\n * weightedFiles({ 'song1.mp3': 2, 'song2.mp3': 1 })\n * // returns ['song1.mp3', 'song1.mp3', 'song2.mp3']\n *\n * @param {Object.<string, number>} files - Object with file paths as keys and weights as values\n * @returns {string[]} Array of file paths repeated according to their weights\n */\nexport const weightedFiles = (files: { [key: string]: number }): string[] => {\n return Object.entries(files).flatMap(([file, weight]) => Array(Math.floor(weight)).fill(file))\n}\n\n/**\n * Shuffles an array of strings using the Fisher-Yates algorithm.\n * Creates a new array instead of modifying the original.\n *\n * @example\n * shuffle(['a', 'b', 'c'])\n * // might return ['b', 'c', 'a']\n *\n * @param {string[]} list - Array of strings to shuffle\n * @returns {string[]} New array with shuffled elements\n */\nexport const shuffle = (list: string[]): string[] => {\n const result = list.slice()\n let index = list.length - 1\n\n while (index >= 0) {\n const randomIdx = Math.floor(Math.random() * index + 1)\n const tmp = result[index]\n result[index] = result[randomIdx]\n result[randomIdx] = tmp\n\n index--\n }\n\n return result\n}\n\n/**\n * Preloads audio files in batches with a specified limit.\n * Uses a queue system to load files sequentially after the initial batch.\n *\n * @param {string[]} files - Array of file URLs to preload\n * @param {number} limit - Maximum number of files to load simultaneously\n * @param {Function} [api=fetch] - Function to use for fetching files\n * @param {Function} [done] - Callback function to execute when all files are loaded\n */\nexport const preloadFiles = (\n files: string[],\n limit: number,\n api: (input: string, init?: RequestInit | undefined) => Promise<Response> = fetch,\n done?: () => void,\n): void => {\n const queue: string[] = files.slice(limit).reverse()\n let isDone = false\n\n /**\n * Processes the next item in the queue or calls the done callback if queue is empty\n */\n const requestNext = () => {\n if (!queue.length) {\n if (!isDone) {\n done?.()\n isDone = true\n }\n } else {\n request(queue.pop() as string)\n }\n }\n\n const request = (fileName: string) => {\n api(fileName).then(requestNext).catch(requestNext)\n }\n\n for (let i = 0; i < limit; i++) {\n request(files[i])\n }\n}\n","import type { Event, EventEmitter } from '../EventEmitter'\nimport type { AudioPlaylistState } from './states'\n\nimport Audio from '../audio/Audio'\n\n/**\n * Creates an audio playback controller function that manages playlist state and events.\n *\n * @param {AudioPlaylistState} states - Global state object containing audio playback states\n * @param {EventEmitter} emmiter - Event emitter for handling playlist events\n * @returns {(files: string[], loop: boolean) => void} A function that handles audio playback\n */\nconst playAudio = (\n states: AudioPlaylistState,\n emmiter: EventEmitter,\n): ((files: string[], loop: boolean) => void) => {\n const playAudioHelper = (files: string[], loop: boolean) => {\n const file = files[states.audioIndex]\n const audio = Audio({ file, volume: states.volume })\n states.audio = audio\n\n audio.on('start', (e) => {\n emmiter.emit('start', e as Event)\n })\n\n audio.on('end', () => {\n if (states.isStopped) return\n\n if (files.length === states.audioIndex + 1) {\n states.audio = null\n states.audioIndex = 0\n\n if (states.loop) {\n playAudioHelper(files, loop)\n } else {\n emmiter.emit('end', { data: null })\n states.isPlaying = false\n }\n } else {\n states.audioIndex++\n playAudioHelper(files, loop)\n }\n })\n\n audio.play()\n }\n\n return playAudioHelper\n}\n\nexport default playAudio\n"],"names":["preloadFile","file","attempts","done","fetch","then","defaultStates","isDecoded","isPlaying","hasStarted","source","gainNode","EventEmitter","this","events","_proto","prototype","listener","keyEvent","callback","emit","param","removeAllListeners","EventHandler","emitter","audioCtx","ready","start","end","state","_this","onstatechange","_this$audioCtx","data","dispose","initializeSource","_ref","volume","states","createBufferSource","createGain","gain","value","connect","destination","onended","time","resume","AudioClass","Context","_ref$volume","_ref$time","_ref$autoPlay","autoPlay","_ref$loop","loop","_ref$preload","preload","_file","_initialVolume","_initialTime","_autoPlay","_initialLoop","_audioCtx","_states","_emitter","_eventHandler","_startTime","_pauseTime","_hasSeekedWhilePaused","_pendingSeekTime","_isDestroyed","window","AudioContext","webkitAudioContext","Error","throwsError","_extends","recreateAndStart","buffer","_this$_states$gainNod","_this$_states$gainNod2","stop","currentTime","error","console","curryGetBuffer","response","ok","status","arrayBuffer","decodeAudioData","play","_this$_states$source","_this2","_this$_pauseTime","_this2$_pauseTime","pause","suspend","toggle","_this$_states$source2","on","eventType","_this$_eventHandler$e","_this$_eventHandler","call","seek","_this$_states$source3","clampedTime","duration","Math","max","min","finalTime","audioBuffer","destroy","_this$_states$gainNod3","_unused","disconnect","_createClass","key","get","_this$_states$gainNod4","_this$_states$gainNod5","set","newVolume","_this$_states$source$","_this$_states$source4","newLoop","_this$_states$source$2","_this$_states$source5","_this$_states$source6","Audio","props","audio","isStopped","audioIndex","AudioPlaylist","files","_ref$shuffle","shuffle","_ref$preloadLimit","preloadLimit","emiter","copiedFiles","shouldLoop","curryPlayAudio","isDestroyed","globalStates","hasWeights","Array","isArray","normalizedFiles","Object","entries","flatMap","floor","fill","weightedFiles","list","result","slice","index","length","randomIdx","random","tmp","shuffleHelper","emmiter","playAudioHelper","e","playAudio","limit","api","queue","reverse","isDone","requestNext","request","pop","fileName","i","preloadFiles","_this$states$audio","_this$states$audio2","next","_this$states$audio3","prev","_this$states$audio4","_this$states$audio5","_this$states$audio6","params"],"mappings":"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"}