/// /** * Type definitions for customHowler audio system * @private */ interface HowlOptions { src: string | string[]; /** * Force use of HTML5 audio element instead of Web Audio API. * Useful for testing or specific use cases where HTML5 is preferred. * Default: auto-detect (use Web Audio API everywhere for non-blocking playback) * Note: HTML5 audio blocks the main thread and should be avoided. */ html5?: boolean; } /** * Lightweight Howl implementation using Web Audio API * * ## Features * * - **Web Audio API** (Desktop): Off-main-thread audio processing (smooth UI rendering) * - **HTML5 Audio** (iOS): Reliable playback on iOS devices (avoids pitch shifting) * - **HTML5 Sound pooling**: Maintains a pool of audio elements for efficient concurrent playback * - **Caching**: Decoded AudioBuffers cached globally (efficient repeated playback) * - **Overlapping playback**: Multiple source nodes allow simultaneous sounds * - **Tree-shakable**: Side-effect free, lazy initialization * - **Auto-unlock**: Automatically unlocks audio on first user interaction * - **Auto-detect**: Automatically uses HTML5 on iOS, Web Audio API on desktop * * ## Usage * * ```typescript * const beep = new Howl({ src: 'beep.mp3' }); * beep.play(); // First call unlocks audio, subsequent calls play immediately * beep.play(); // Overlapping playback supported * ``` * * ## Performance Notes * * **Desktop (Web Audio API):** * - Each play() call creates a new AudioBufferSourceNode (allows overlapping) * - AudioBuffer is decoded once and shared (efficient repeated playback) * - Audio runs on separate thread (doesn't block UI) * * **iOS (HTML5 Audio):** * - Uses HTML5 audio pool for efficient concurrent playback * - Main thread may block briefly during playback (unavoidable on iOS) * - Reliable pitch and sample rate (avoids Web Audio API issues) * * ## Overlapping Playback * * Each call to play() creates a new AudioBufferSourceNode, allowing the same * sound to play multiple times simultaneously (useful for rapid scanning feedback). * The decoded AudioBuffer is shared, only the source nodes are created per play. */ declare class Howl { /** Audio source URL (data URI, file path, or URL) */ private src; /** Use HTML5 audio element (true) or Web Audio API (false) */ private html5; /** Decoded audio buffer (cached after first load) - for Web Audio API only */ private audioBuffer; /** Loading promise (prevents duplicate loads) */ private loadPromise; /** Pool of HTML5 Sound objects (similar to Howler.js) */ private soundPool; private soundPoolSize; /** Track inactive (available) sounds for O(1) lookup instead of O(n) find */ private inactiveSounds; constructor(options: HowlOptions); /** * Load and decode the audio file * * ## Caching Strategy * * This method implements three levels of caching: * * 1. **Instance cache**: Check if this Howl instance has already loaded the audio * 2. **Instance promise cache**: Check if this Howl instance is currently loading * 3. **Global cache**: Check if any Howl instance has loaded this URL * * Benefits: * - Multiple Howl instances for the same sound share one AudioBuffer * - Prevents duplicate network requests * - Avoids redundant CPU-intensive audio decoding * - Important for rapid scanning where same sound plays frequently * * ## Loading Process * * 1. Fetch audio file (supports data URIs, relative paths, full URLs) * 2. Convert to ArrayBuffer * 3. Decode using AudioContext.decodeAudioData() (async, CPU-intensive) * 4. Cache the decoded AudioBuffer * * Note: decodeAudioData() runs in a separate thread and doesn't block the main thread * * @returns Promise - Decoded audio ready for playback */ private load; /** * Get or create a sound from the pool * Follows Howler.js pattern: pool of Sound objects with state tracking * Uses Set for O(1) lookup of inactive sounds instead of O(n) array find * @private */ private getSoundFromPool; /** * Verify audio buffer contains actual data (debugging helper, no-op in production) * @private */ private verifyAudioBuffer; /** * Handle playback errors for Web Audio API * @private */ private handlePlaybackError; /** * Prepare AudioContext for playback * @private */ private prepareAudioContext; /** * Play sound using HTML5 Sound pool (Howler.js style) * Follows Howler.js pattern of Sound objects with state tracking * * ## Non-blocking Playback * * On iOS, the HTML5 audio play() call can block the main thread. To prevent * this from blocking overlay rendering during continuous scanning, we defer * the play() call to the next animation frame using requestAnimationFrame. * This keeps the current frame free for UI rendering. * * @private */ private playHTML5; /** * Play the sound. * * This method is the public API - call it to play the sound. * It's non-blocking (fire-and-forget) and supports overlapping playback. * * ## Behavior * * - Creates a new AudioBufferSourceNode for each call (allows overlapping) * - Reuses cached AudioBuffer (efficient repeated playback) * - Auto-unlocks audio on first play (if not already unlocked) * - Uses iOS MediaStreamDestination workaround when needed * * ## Example * * ```typescript * const beep = new Howl({ src: 'beep.mp3' }); * beep.play(); // First play * beep.play(); // Overlapping play (both sounds play simultaneously) * ``` */ play(): void; /** * Internal async play method * * ## Playback Flow * * 1. **Unlock check**: Ensure audio is unlocked (iOS requirement) * 2. **Load**: Load and decode audio if not already cached * 3. **Create source**: Create new AudioBufferSourceNode for this playback * 4. **Setup audio graph**: Source → Gain → Destination * 5. **Start playback**: Call source.start(0) to play immediately * 6. **Cleanup**: Disconnect source after playback completes * * ## Platform-specific playback * * **Desktop:** * - Uses Web Audio API with standard AudioContext destination * * **iOS:** * - Uses HTML5