goldfire/howler.js · warning

HTML5 Audio pool exhausted, returning potentially locked aud

Error message

HTML5 Audio pool exhausted, returning potentially locked audio object.

What it means

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.

Source

Thrown at src/howler.core.js:434

    /**
     * Get an unlocked HTML5 Audio object from the pool. If none are left,
     * return a new Audio object and throw a warning.
     * @return {Audio} HTML5 Audio object.
     */
    _obtainHtml5Audio: function() {
      var self = this || Howler;

      // Return the next object from the pool if one exists.
      if (self._html5AudioPool.length) {
        return self._html5AudioPool.pop();
      }

      //.Check if the audio is locked and throw a warning.
      var testPlay = new Audio().play();
      if (testPlay && typeof Promise !== 'undefined' && (testPlay instanceof Promise || typeof testPlay.then === 'function')) {
        testPlay.catch(function() {
          console.warn('HTML5 Audio pool exhausted, returning potentially locked audio object.');
        });
      }

      return new Audio();
    },

    /**
     * Return an activated HTML5 Audio object to the pool.
     * @return {Howler}
     */
    _releaseHtml5Audio: function(audio) {
      var self = this || Howler;

      // Don't add audio to the pool if we don't know if it has been unlocked.
      if (audio._unlocked) {
        self._html5AudioPool.push(audio);
      }

View on GitHub (pinned to 1d3053576a)

Solutions

  1. Ensure the first play() happens inside a user-gesture handler (click/touch/keydown) so the Audio object gets unlocked
  2. 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
  3. 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)
  4. Preload/queue fewer sounds and only create Audio nodes as needed; stop/free `Howl` instances you no longer use
  5. 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

Example fix

// before
const music = new Howl({ src: ['song.mp3'], html5: true, autoplay: true }); // autoplay at load => locked Audio on mobile
// after
document.addEventListener('click', function start() {
  const music = new Howl({ src: ['song.mp3'], html5: true });
  music.play();
  document.removeEventListener('click', start);
}, { once: true });
Defensive patterns

Strategy: retry

Validate before calling

// Gate any audio start behind a user gesture and check browser unlock support
function canAutoplayAudio() {
  const a = new Audio();
  const p = a.play();
  if (p && typeof p.catch === 'function') {
    return p.then(() => true).catch(() => false);
  }
  return Promise.resolve(true);
}
// usage: if (!(await canAutoplayAudio())) waitForKeyboardOrClickBeforePlaying();

Type guard

function isPlayableAudioResult(result) {
  return result !== null && result !== undefined &&
    typeof result.play === 'function';
}

Try / catch

try {
  const id = sound.play();
  if (id && typeof id.catch === 'function') {
    await id.catch((err) => {
      // autoplay/lock rejection: wait for user gesture then retry once
      return waitForUserGesture().then(() => sound.play());
    });
  }
} catch (e) {
  // HTML5 Audio element failed entirely: fall back to Web Audio (html5:false)
  sound = new Howl({ src: soundSrc });
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of goldfire/howler.js@1d3053576a (2026-08-30). Data as JSON: /api/errors/5a0152930bd70544. Report an issue: GitHub.