{"record":{"id":"5a0152930bd70544","repo":"goldfire/howler.js","slug":"html5-audio-pool-exhausted-returning-potentially","errorCode":null,"errorMessage":"HTML5 Audio pool exhausted, returning potentially locked audio object.","messagePattern":"HTML5 Audio pool exhausted, returning potentially locked audio object\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/howler.core.js","lineNumber":434,"sourceCode":"\n    /**\n     * Get an unlocked HTML5 Audio object from the pool. If none are left,\n     * return a new Audio object and throw a warning.\n     * @return {Audio} HTML5 Audio object.\n     */\n    _obtainHtml5Audio: function() {\n      var self = this || Howler;\n\n      // Return the next object from the pool if one exists.\n      if (self._html5AudioPool.length) {\n        return self._html5AudioPool.pop();\n      }\n\n      //.Check if the audio is locked and throw a warning.\n      var testPlay = new Audio().play();\n      if (testPlay && typeof Promise !== 'undefined' && (testPlay instanceof Promise || typeof testPlay.then === 'function')) {\n        testPlay.catch(function() {\n          console.warn('HTML5 Audio pool exhausted, returning potentially locked audio object.');\n        });\n      }\n\n      return new Audio();\n    },\n\n    /**\n     * Return an activated HTML5 Audio object to the pool.\n     * @return {Howler}\n     */\n    _releaseHtml5Audio: function(audio) {\n      var self = this || Howler;\n\n      // Don't add audio to the pool if we don't know if it has been unlocked.\n      if (audio._unlocked) {\n        self._html5AudioPool.push(audio);\n      }\n","sourceCodeStart":416,"sourceCodeEnd":452,"githubUrl":"https://github.com/goldfire/howler.js/blob/1d3053576a860e9854645493ad6c4a72c6cc6e45/src/howler.core.js#L416-L452","documentation":"This is a console.warn (not a thrown exception) emitted by howler.js when the internal pool of reusable HTML5 Audio objects is exhausted. When howler returns a fresh `new Audio()` outside the pool, the browser may not yet have a user-gesture unlock, so play() calls on it can return a rejected Promise (autoplay/lock policy). The library detects this and warns that the returned Audio object may be 'locked' and refuse to play until a user interaction unlocks it.","triggerScenarios":"Creating more simultaneous HTML5-mode Howl sounds than the pool size (default 10 in HowlerGlobal `_html5AudioPool`), e.g. many `new Howl({html5:true, src:[...]})` instances or rapid `play()` calls on html5 audio; calling play without a preceding user gesture (click/keydown) so the newly created Audio is autoplay-locked; mobile browsers (iOS Safari, Chrome autoplay policy) where audio unlock requires user interaction.","commonSituations":"Mobile web games/preloaders that spawn dozens of html5:true Howls at page load before any tap; streaming long audio (html5 mode is used for large files/streaming) with many overlapping channels; autoplaying background audio on page load; upgrading howler or switching from Web Audio (default) to html5:true and hitting browser autoplay policies.","solutions":["Ensure the first play() happens inside a user-gesture handler (click/touch/keydown) so the Audio object gets unlocked","Reduce concurrent HTML5 sounds: pool/sounds, reuse Howl instances, or drop `html5:true` so the default Web Audio path (unlimited voices, no Audio-element pool) is used","Call Howler volume/unlock or resume the Howler context after a user interaction (e.g. `Howler.volume(Howler.volume())` or play a muted sound on first tap)","Preload/queue fewer sounds and only create Audio nodes as needed; stop/free `Howl` instances you no longer use","Treat the warning as non-fatal: it already only fires via testPlay.catch, so gate playback with the promise rejection (`sound.play().catch(...)`) and retry on next user gesture"],"exampleFix":"// before\nconst music = new Howl({ src: ['song.mp3'], html5: true, autoplay: true }); // autoplay at load => locked Audio on mobile\n// after\ndocument.addEventListener('click', function start() {\n  const music = new Howl({ src: ['song.mp3'], html5: true });\n  music.play();\n  document.removeEventListener('click', start);\n}, { once: true });","handlingStrategy":"retry","validationCode":"// Gate any audio start behind a user gesture and check browser unlock support\nfunction canAutoplayAudio() {\n  const a = new Audio();\n  const p = a.play();\n  if (p && typeof p.catch === 'function') {\n    return p.then(() => true).catch(() => false);\n  }\n  return Promise.resolve(true);\n}\n// usage: if (!(await canAutoplayAudio())) waitForKeyboardOrClickBeforePlaying();","typeGuard":"function isPlayableAudioResult(result) {\n  return result !== null && result !== undefined &&\n    typeof result.play === 'function';\n}","tryCatchPattern":"try {\n  const id = sound.play();\n  if (id && typeof id.catch === 'function') {\n    await id.catch((err) => {\n      // autoplay/lock rejection: wait for user gesture then retry once\n      return waitForUserGesture().then(() => sound.play());\n    });\n  }\n} catch (e) {\n  // HTML5 Audio element failed entirely: fall back to Web Audio (html5:false)\n  sound = new Howl({ src: soundSrc });\n}","preventionTips":["Never call play() before a user gesture (click/touch/keydown) on mobile or Chrome autoplay-policy browsers","Prefer the default Web Audio mode; only use html5:true for streaming/large files where an element pool is required","Cap concurrent html5 sounds (pool size defaults to ~10) and reuse a single Howl instance with `.play()` per voice instead of creating new Howls","On first interaction, run a warm-up: `Howler.volume(Howler.volume())` or play a silent sound to unlock audio","Subscribe to rejections: `sound.play().catch(...)` so locked playback is detected and retried"],"tags":["howler","html5-audio","autoplay-policy","browser-lock","mobile-safari"],"backgroundTag":"autoplay-blocked-audio","analyzedSha":"1d3053576a860e9854645493ad6c4a72c6cc6e45","analyzedAt":"2026-08-30T22:51:24.423Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}