Play Web Audio API AudioBuffer in Howler.js
This article explains how to pass an instantiated Web Audio API
AudioBuffer directly to Howler.js for playback. Because
Howler.js is designed to load audio from file URLs, it does not
officially expose a direct API parameter for raw audio buffers. We will
cover the most efficient method to bypass this limitation by injecting
your buffer directly into Howler’s internal cache, as well as an
alternative standard method using Blobs.
Method 1: Injecting the AudioBuffer into Howler’s Cache (Recommended)
Howler.js maintains an internal cache (Howler._cache) of
decoded AudioBuffer objects to prevent redundant network
requests. You can exploit this mechanism by manually assigning your
AudioBuffer to the cache using a unique dummy string as the
key.
When you instantiate a new Howl object with that same
dummy string as the source, Howler will pull the buffer directly from
the cache instead of attempting to fetch it.
Here is how to implement this:
// 1. Assume you already have a Web Audio API AudioBuffer
const myAudioBuffer = yourExistingAudioBuffer;
// 2. Define a unique cache key (string)
const cacheKey = 'my-unique-buffer-id';
// 3. Manually inject the AudioBuffer into Howler's internal cache
Howler._cache[cacheKey] = myAudioBuffer;
// 4. Instantiate Howl using the cache key as the src
const sound = new Howl({
src: [cacheKey],
format: 'wav', // Provide a dummy format to bypass format-checks
autoplay: false
});
// 5. Play the sound directly from the memory buffer
sound.play();Why This Works:
Before Howler.js sends an XMLHttpRequest to load an
audio file, it checks if the source string exists in
Howler._cache. By pre-populating this cache with your
decoded AudioBuffer, Howler skips the download and decoding
phases entirely, making the playback instantaneous.
Method 2: Converting AudioBuffer to a Blob URL
If you prefer to avoid accessing Howler’s private APIs (like
_cache), you can convert the AudioBuffer into
an audio file (like WAV) in memory, create a Blob URL, and pass that URL
to Howler.
This method requires helper functions to encode the raw PCM data of
the AudioBuffer into a standard WAV format:
// Helper function to encode AudioBuffer to WAV
function bufferToWav(buffer) {
let numOfChan = buffer.numberOfChannels,
length = buffer.length * numOfChan * 2 + 44,
bufferArr = new ArrayBuffer(length),
view = new DataView(bufferArr),
channels = [], i, sample,
offset = 0,
pos = 0;
// Write WAV header
setUint32(0x46464952); // "RIFF"
setUint32(length - 8); // file length - 8
setUint32(0x45564157); // "WAVE"
setUint32(0x20746d66); // "fmt " chunk
setUint32(16); // chunk length
setUint16(1); // sample format (raw)
setUint16(numOfChan);
setUint32(buffer.sampleRate);
setUint32(buffer.sampleRate * 2 * numOfChan); // byte rate
setUint16(numOfChan * 2); // block align
setUint16(16); // bits per sample
setUint32(0x61746164); // "data" - chunk
setUint32(length - pos - 4); // chunk length
// Write interleaved audio data
for(i=0; i<buffer.numberOfChannels; i++) {
channels.push(buffer.getChannelData(i));
}
while(pos < length) {
for(i=0; i<numOfChan; i++) { // interleave channels
sample = Math.max(-1, Math.min(1, channels[i][offset])); // clamp
sample = (sample < 0 ? sample * 0x8000 : sample * 0x7FFF); // scale to 16-bit signed int
view.setInt16(pos, sample, true); // write 16-bit sample
pos += 2;
}
offset++;
}
return new Blob([bufferArr], {type: "audio/wav"});
function setUint16(data) {
view.setUint16(pos, data, true);
pos += 2;
}
function setUint32(data) {
view.setUint32(pos, data, true);
pos += 4;
}
}
// Usage with Howler.js
const wavBlob = bufferToWav(myAudioBuffer);
const blobUrl = URL.createObjectURL(wavBlob);
const sound = new Howl({
src: [blobUrl],
format: 'wav'
});
sound.play();This approach is highly compatible and does not rely on Howler’s private properties, though it introduces a slight processing overhead to encode the audio data in the browser.