Efficient audio loading and playback
I was experiencing difficulties synching sounds to the CSS animation used on the Disqus comment button especially as it's intention was to distract the user while Disqus loaded a huge quantity of files in the background. Different browsers behaved differently when using <audio> elements. Sometimes the sound fired other times it skipped completely, not good.
So Web Audio API to the rescue? Well not quite, but it's far better than the original <audio> solution. Unfortunately loading files in the background still interrupts the playback method employed delaying both visual and audio. Not the APIs fault, I repeat play calls during loading. Perhaps looping a sound would prevent that but I preferred the staccato playback.
That said it is still a better solution so I use this method throughout this site. I'm still looking for a better methodology, one without using a full library.
This is a modification of Matt Harrison's excellent Perfect web audio, with the deprecated instructions replaced. For a full description please read his original article.
JavaScript
Determine support and fix cross-browser implementations.
try {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
window.audioContext = new window.AudioContext();
} catch (e) {
console.log("No Web Audio API support");
}
WebAudioAPISoundManager Constructor
var WebAudioAPISoundManager = function (context) {
this.context = context;
this.bufferList = {};
this.playingSounds = {};
};
WebAudioAPISoundManager Prototype
WebAudioAPISoundManager.prototype = {
addSound: function (url) {
// Load buffer asynchronously
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.responseType = "arraybuffer";
var self = this;
request.onload = function () {
// Asynchronously decode the
// audio file data in request.response
self.context.decodeAudioData(
request.response,
function (buffer) {
if (!buffer) {
console.log('error decoding file data: ' + url);
return;
}
self.bufferList[url] = buffer;
});
};
request.onerror = function () {
console.log('BufferLoader: XHR error');
};
request.send();
},
stopSoundWithUrl: function(url) {
if (this.playingSounds.hasOwnProperty(url)) {
for (var i in this.playingSounds[url]) {
if (this.playingSounds[url].hasOwnProperty(i)) {
this.playingSounds[url][i].stop();
}
}
}
}
};
WebAudioAPISound Constructor
var WebAudioAPISound = function (url) {
this.url = url + '.mp3';
window.webAudioAPISoundManager = window.webAudioAPISoundManager || new WebAudioAPISoundManager(window.audioContext);
this.manager = window.webAudioAPISoundManager;
this.manager.addSound(this.url);
};
WebAudioAPISound Prototype
WebAudioAPISound.prototype = {
play: function (options) {
var buffer = this.manager.bufferList[this.url];
this.settings = {
loop: false,
volume: 0.5
};
for (var i in options) {
if (options.hasOwnProperty(i)) {
this.settings[i] = options[i];
}
}
//Only play if it's loaded yet
if (typeof buffer !== "undefined") {
var source = this.makeSource(buffer);
source.loop = this.settings.loop;
source.start(0);
if (!this.manager.playingSounds.hasOwnProperty(this.url)) {
this.manager.playingSounds[this.url] = [];
}
this.manager.playingSounds[this.url].push(source);
}
},
stop: function () {
this.manager.stopSoundWithUrl(this.url);
},
makeSource: function (buffer) {
var source = this.manager.context.createBufferSource();
var gainNode = this.manager.context.createGain();
gainNode.gain.value = this.settings.volume;
source.buffer = buffer;
source.connect(gainNode);
gainNode.connect(this.manager.context.destination);
return source;
}
};
To use
Note no .mp3 extension on file name.
var typingSound = new WebAudioAPISound("/audio/typing");
var pagefeedSound = new WebAudioAPISound("/audio/pagefeed");
// pagefeedSound.play({loop : false, volume : 0.8});
// typingSound.play({volume : 0.2});
Google closure compiled
682 bytes gzipped (1.67KB uncompressed)
try{window.AudioContext=window.AudioContext||window.webkitAudioContext,window.audioContext=new window.AudioContext}catch(a){console.log("No Web Audio API support")}var WebAudioAPISoundManager=function(a){this.context=a;this.bufferList={};this.playingSounds={}};WebAudioAPISoundManager.prototype={addSound:function(a){var b=new XMLHttpRequest;b.open("GET",a,!0);b.responseType="arraybuffer";var c=this;b.onload=function(){c.context.decodeAudioData(b.response,function(b){b?c.bufferList[a]=b:alert("error decoding file data: "+a)})};b.onerror=function(){console.log("BufferLoader: XHR error")};b.send()},stopSoundWithUrl:function(a){if(this.playingSounds.hasOwnProperty(a))for(var b in this.playingSounds[a])this.playingSounds[a].hasOwnProperty(b)&&this.playingSounds[a][b].stop()}};var WebAudioAPISound=function(a){this.url=a+".mp3";window.webAudioAPISoundManager=window.webAudioAPISoundManager||new WebAudioAPISoundManager(window.audioContext);this.manager=window.webAudioAPISoundManager;this.manager.addSound(this.url)};WebAudioAPISound.prototype={play:function(a){var b=this.manager.bufferList[this.url];this.settings={loop:!1,volume:.5};for(var c in a)a.hasOwnProperty(c)&&(this.settings[c]=a[c]);"undefined"!==typeof b&&(a=this.makeSource(b),a.loop=this.settings.loop,a.start(0),this.manager.playingSounds.hasOwnProperty(this.url)||(this.manager.playingSounds[this.url]=[]),this.manager.playingSounds[this.url].push(a))},stop:function(){this.manager.stopSoundWithUrl(this.url)},makeSource:function(a){var b=this.manager.context.createBufferSource(),c=this.manager.context.createGain();c.gain.value=this.settings.volume;b.buffer=a;b.connect(c);c.connect(this.manager.context.destination);return b}};
Social links and email client: