Created
April 22, 2013 14:21
-
-
Save lukebitts/5435466 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* howler.js v1.1.1 | |
* howlerjs.com | |
* | |
* (c) 2013, James Simpson of GoldFire Studios | |
* goldfirestudios.com | |
* | |
* MIT License | |
*/ | |
var MusicGroup = function(name) { | |
this.name = name; | |
this.howls = []; | |
this.volume = 1; | |
} | |
window.sound = new MusicGroup("sounds"); | |
window.music = new MusicGroup("musics"); | |
(function () { | |
// setup | |
var cache = {}; | |
var groups = {}; | |
// setup the audio context | |
var ctx = null, | |
usingWebAudio = true, | |
noAudio = false; | |
if (typeof AudioContext !== 'undefined') { | |
ctx = new AudioContext(); | |
} else if (typeof webkitAudioContext !== 'undefined') { | |
ctx = new webkitAudioContext(); | |
} else if (typeof Audio !== 'undefined') { | |
usingWebAudio = false; | |
} else { | |
usingWebAudio = false; | |
noAudio = true; | |
} | |
// create a master gain node | |
if (usingWebAudio) { | |
var masterGain = (typeof ctx.createGain === 'undefined') ? ctx.createGainNode() : ctx.createGain(); | |
masterGain.gain.value = 1; | |
masterGain.connect(ctx.destination); | |
} | |
// create global controller | |
var HowlerGlobal = function () { | |
this._volume = 1; | |
this._muted = false; | |
this.usingWebAudio = usingWebAudio; | |
}; | |
HowlerGlobal.prototype = { | |
/** | |
* Get/set the global volume for all sounds. | |
* @param {Float} vol Volume from 0.0 to 1.0. | |
* @return {Object/Float} Returns self or current volume. | |
*/ | |
volume: function (vol) { | |
var self = this; | |
// make sure volume is a number | |
vol = parseFloat(vol, 10); | |
if (vol && vol >= 0 && vol <= 1) { | |
self._volume = vol; | |
if (usingWebAudio) { | |
masterGain.gain.value = vol; | |
} | |
// loop through cache and change volume of all nodes that are using HTML5 Audio | |
for (var key in cache) { | |
if (cache.hasOwnProperty(key) && cache[key]._webAudio === false) { | |
// loop through the audio nodes | |
for (var i = 0; i < cache[key]._audioNode.length; i++) { | |
cache[key]._audioNode[i].volume = cache[key]._volume * self._volume; | |
} | |
} | |
} | |
return self; | |
} | |
// return the current global volume | |
return (usingWebAudio) ? masterGain.gain.value : self._volume; | |
}, | |
/** | |
* Mute all sounds. | |
* @return {Object} | |
*/ | |
mute: function () { | |
var self = this; | |
self._muted = true; | |
if (usingWebAudio) { | |
masterGain.gain.value = 0; | |
} | |
for (var key in cache) { | |
if (cache.hasOwnProperty(key) && cache[key]._webAudio === false) { | |
// loop through the audio nodes | |
for (var i = 0; i < cache[key]._audioNode.length; i++) { | |
cache[key]._audioNode[i].volume = 0; | |
} | |
} | |
} | |
return self; | |
}, | |
/** | |
* Unmute all sounds. | |
* @return {Object} | |
*/ | |
unmute: function () { | |
var self = this; | |
self._muted = false; | |
if (usingWebAudio) { | |
masterGain.gain.value = self._volume; | |
} | |
for (var key in cache) { | |
if (cache.hasOwnProperty(key) && cache[key]._webAudio === false) { | |
// loop through the audio nodes | |
for (var i = 0; i < cache[key]._audioNode.length; i++) { | |
cache[key]._audioNode[i].volume = cache[key]._volume * self._volume; | |
} | |
} | |
} | |
return self; | |
}, | |
volumeGroup: function(name,volume) { | |
if(groups[name]) { | |
for(var n in groups[name].howls) { | |
var howl = groups[name].howls[n]; | |
howl.volume(volume); | |
} | |
groups[name].volume = volume; | |
} | |
else { | |
groups[name] = new MusicGroup(name); | |
groups[name].volume = volume; | |
} | |
}, | |
groupState: function(group) { | |
if(groups[group]) | |
return groups[group].volume; | |
}, | |
clearGroupHowls: function(group) { | |
if(groups[group]) | |
groups[group].howls = []; | |
}, | |
removeHowlFromGroup: function(group, howl) { | |
} | |
}; | |
// allow access to the global audio controls | |
var Howler = new HowlerGlobal(); | |
// check for browser codec support | |
var audioTest = null; | |
if (!noAudio) { | |
audioTest = new Audio(); | |
var codecs = { | |
mp3: !! audioTest.canPlayType('audio/mpeg;').replace(/^no$/, ''), | |
ogg: !! audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ''), | |
wav: !! audioTest.canPlayType('audio/wav; codecs="1"').replace(/^no$/, ''), | |
m4a: !! (audioTest.canPlayType('audio/x-m4a;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''), | |
webm: !! audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, '') | |
}; | |
} | |
// setup the audio object | |
var Howl = function (o) { | |
var self = this; | |
// setup the defaults | |
self._autoplay = o.autoplay || false; | |
self._buffer = o.buffer || false; | |
self._duration = o.duration || 0; | |
self._format = o.format || null; | |
self._loop = o.loop || false; | |
self._loaded = false; | |
self._sprite = o.sprite || {}; | |
self._src = o.src || ''; | |
self._pos3d = o.pos3d || [0, 0, -0.5]; | |
self._volume = o.volume || 1; | |
self._urls = o.urls || []; | |
self._group = o.group || ""; //LUCAS | |
// setup event functions | |
self._onload = [o.onload || function () {}]; | |
self._onloaderror = [o.onloaderror || function () {}]; | |
self._onend = [o.onend || function () {}]; | |
self._onpause = [o.onpause || function () {}]; | |
self._onplay = [o.onplay || function () {}]; | |
self._onendTimer = []; | |
// Web Audio or HTML5 Audio? | |
self._webAudio = usingWebAudio && !self._buffer; | |
// check if we need to fall back to HTML5 Audio | |
self._audioNode = []; | |
if (self._webAudio) { | |
self._setupAudioNode(); | |
} | |
//LUCAS | |
if(self._group) { | |
if(self._group == "sound") { | |
window.sound.howls.push(this); | |
} | |
else if(self._group == "music") { | |
window.music.howls.push(this); | |
} | |
/*if(!groups[self._group]) | |
groups[self._group] = new MusicGroup(self._group); | |
groups[self._group].howls.push(self); | |
self._volume = groups[self._group].volume; | |
console.log(self._group, self._volume, groups[self._group], groups[self._group].howls.indexOf(this));*/ | |
} | |
// load the track | |
self.load(); | |
}; | |
// setup all of the methods | |
Howl.prototype = { | |
/** | |
* Load an audio file. | |
* @return {Object} | |
*/ | |
load: function () { | |
var self = this, | |
url = null; | |
// if no audio is available, quit immediately | |
if (noAudio) { | |
self.on('loaderror'); | |
return; | |
} | |
// loop through source URLs and pick the first one that is compatible | |
for (var i = 0; i < self._urls.length; i++) { | |
var ext = self._urls[i].toLowerCase().match(/.+\.([^?]+)(\?|$)/), | |
canPlay = false; | |
// figure out the filetype (whether an extension or base64 data) | |
ext = (ext && ext.length >= 2) ? ext[1] : self._urls[i].toLowerCase().match(/data\:audio\/([^?]+);/)[1]; | |
// set audio file format if specified | |
if (self._format) { | |
ext = self._format; | |
} | |
switch (ext) { | |
case 'mp3': | |
canPlay = codecs.mp3; | |
break; | |
case 'ogg': | |
canPlay = codecs.ogg; | |
break; | |
case 'wav': | |
canPlay = codecs.wav; | |
break; | |
case 'm4a': | |
canPlay = codecs.m4a; | |
break; | |
case 'weba': | |
canPlay = codecs.webm; | |
break; | |
} | |
if (canPlay === true) { | |
url = self._urls[i]; | |
break; | |
} | |
} | |
if (!url) { | |
self.on('loaderror'); | |
return; | |
} | |
self._src = url; | |
if (self._webAudio) { | |
loadBuffer(self, url); | |
} else { | |
var newNode = new Audio(); | |
self._audioNode.push(newNode); | |
// setup the new audio node | |
newNode.src = url; | |
newNode._pos = 0; | |
newNode.preload = 'auto'; | |
newNode.volume = (Howler._muted) ? 0 : self._volume * Howler.volume(); | |
// add this sound to the cache | |
cache[url] = self; | |
// setup the event listener to start playing the sound | |
// as soon as it has buffered enough | |
var listener = function () { | |
self._duration = newNode.duration; | |
// setup a sprite if none is defined | |
if (Object.getOwnPropertyNames(self._sprite).length === 0) { | |
self._sprite = { | |
_default: [0, self._duration * 1000] | |
}; | |
} | |
if (!self._loaded) { | |
self._loaded = true; | |
self.on('load'); | |
} | |
if (self._autoplay) { | |
self.play(); | |
} | |
// clear the event listener | |
newNode.removeEventListener('canplaythrough', listener, false); | |
}; | |
newNode.addEventListener('canplaythrough', listener, false); | |
newNode.load(); | |
} | |
return self; | |
}, | |
/** | |
* Get/set the URLs to be pulled from to play in this source. | |
* @param {Array} urls Arry of URLs to load from | |
* @return {Object} Returns self or the current URLs | |
*/ | |
urls: function (urls) { | |
var self = this; | |
if (urls) { | |
self._urls = urls; | |
self.stop(); | |
self.load(); | |
return self; | |
} else { | |
return self._urls; | |
} | |
}, | |
/** | |
* Play a sound from the current time (0 by default). | |
* @param {String} sprite (optional) Plays from the specified position in the sound sprite definition. | |
* @param {Function} callback (optional) Returns the unique playback id for this sound instance. | |
* @return {Object} | |
*/ | |
play: function (sprite, callback) { | |
var self = this; | |
// use the default sprite if none is passed | |
if (!sprite) { | |
sprite = '_default'; | |
} | |
// if the sound hasn't been loaded, add it to the event queue | |
if (!self._loaded) { | |
self.on('load', function () { | |
self.play(sprite, callback); | |
}); | |
return self; | |
} | |
// if the sprite doesn't exist, play nothing | |
if (!self._sprite[sprite]) { | |
if (typeof callback === 'function') callback(); | |
return self; | |
} | |
// get the node to playback | |
self._inactiveNode(function (node) { | |
// persist the sprite being played | |
node._sprite = sprite; | |
// determine where to start playing from | |
var pos = (node._pos > 0) ? node._pos : self._sprite[sprite][0] / 1000, | |
duration = self._sprite[sprite][1] / 1000 - node._pos; | |
// determine if this sound should be looped | |
var loop = !! (self._loop || self._sprite[sprite][2]); | |
// set timer to fire the 'onend' event | |
var soundId = (typeof callback === 'string') ? callback : Math.round(Date.now() * Math.random()) + '', | |
timerId; | |
(function () { | |
var data = { | |
id: soundId, | |
sprite: sprite, | |
loop: loop | |
}; | |
timerId = setTimeout(function () { | |
// if looping, restart the track | |
if (!self._webAudio && loop) { | |
self.stop(data.id, data.timer).play(sprite, data.id); | |
} | |
// set web audio node to paused at end | |
if (self._webAudio && !loop) { | |
self._nodeById(data.id).paused = true; | |
} | |
// end the track if it is HTML audio and a sprite | |
if (!self._webAudio && !loop) { | |
self.stop(data.id, data.timer); | |
} | |
// fire ended event | |
self.on('end'); | |
}, duration * 1000); | |
// store the reference to the timer | |
self._onendTimer.push(timerId); | |
// remember which timer to cancel | |
data.timer = self._onendTimer[self._onendTimer.length - 1]; | |
})(); | |
if (self._webAudio) { | |
// set the play id to this node and load into context | |
node.id = soundId; | |
node.paused = false; | |
refreshBuffer(self, [loop, pos, duration], soundId); | |
self._playStart = ctx.currentTime; | |
if (typeof node.bufferSource.start === 'undefined') { | |
node.bufferSource.noteGrainOn(0, pos, duration); | |
} else { | |
node.bufferSource.start(0, pos, duration); | |
} | |
} else { | |
if (node.readyState === 4) { | |
node.id = soundId; | |
node.currentTime = pos; | |
node.play(); | |
} else { | |
self._clearEndTimer(timerId); | |
(function () { | |
var sound = self, | |
playSprite = sprite, | |
fn = callback, | |
newNode = node; | |
var listener = function () { | |
sound.play(playSprite, fn); | |
// clear the event listener | |
newNode.removeEventListener('canplaythrough', listener, false); | |
}; | |
newNode.addEventListener('canplaythrough', listener, false); | |
})(); | |
return self; | |
} | |
} | |
// fire the play event and send the soundId back in the callback | |
self.on('play'); | |
if (typeof callback === 'function') callback(soundId); | |
return self; | |
}); | |
return self; | |
}, | |
/** | |
* Pause playback and save the current position. | |
* @param {String} id (optional) The play instance ID. | |
* @param {String} id (optional) Clear the correct timeout ID. | |
* @return {Object} | |
*/ | |
pause: function (id, timerId) { | |
var self = this; | |
// if the sound hasn't been loaded, add it to the event queue | |
if (!self._loaded) { | |
self.on('play', function () { | |
self.pause(id); | |
}); | |
return self; | |
} | |
// clear 'onend' timer | |
self._clearEndTimer(timerId || 0); | |
var activeNode = (id) ? self._nodeById(id) : self._activeNode(); | |
if (activeNode) { | |
if (self._webAudio) { | |
// make sure the sound has been created | |
if (!activeNode.bufferSource) { | |
return self; | |
} | |
activeNode.paused = true; | |
activeNode._pos += ctx.currentTime - self._playStart; | |
if (typeof activeNode.bufferSource.stop === 'undefined') { | |
activeNode.bufferSource.noteOff(0); | |
} else { | |
activeNode.bufferSource.stop(0); | |
} | |
} else { | |
activeNode._pos = activeNode.currentTime; | |
activeNode.pause(); | |
} | |
} | |
self.on('pause'); | |
return self; | |
}, | |
/** | |
* Stop playback and reset to start. | |
* @param {String} id (optional) The play instance ID. | |
* @param {String} id (optional) Clear the correct timeout ID. | |
* @return {Object} | |
*/ | |
stop: function (id, timerId) { | |
var self = this; | |
// if the sound hasn't been loaded, add it to the event queue | |
if (!self._loaded) { | |
self.on('play', function () { | |
self.stop(id); | |
}); | |
return self; | |
} | |
// clear 'onend' timer | |
self._clearEndTimer(timerId || 0); | |
var activeNode = (id) ? self._nodeById(id) : self._activeNode(); | |
if (activeNode) { | |
activeNode._pos = 0; | |
if (self._webAudio) { | |
// make sure the sound has been created | |
if (!activeNode.bufferSource) { | |
return self; | |
} | |
activeNode.paused = true; | |
if (typeof activeNode.bufferSource.stop === 'undefined') { | |
activeNode.bufferSource.noteOff(0); | |
} else { | |
activeNode.bufferSource.stop(0); | |
} | |
} else { | |
activeNode.pause(); | |
activeNode.currentTime = 0; | |
} | |
} | |
return self; | |
}, | |
/** | |
* Mute this sound. | |
* @param {String} id (optional) The play instance ID. | |
* @return {Object} | |
*/ | |
mute: function (id) { | |
var self = this; | |
// if the sound hasn't been loaded, add it to the event queue | |
if (!self._loaded) { | |
self.on('play', function () { | |
self.mute(id); | |
}); | |
return self; | |
} | |
var activeNode = (id) ? self._nodeById(id) : self._activeNode(); | |
if (activeNode) { | |
if (self._webAudio) { | |
activeNode.gain.value = 0; | |
} else { | |
activeNode.volume = 0; | |
} | |
} | |
return self; | |
}, | |
/** | |
* Unmute this sound. | |
* @param {String} id (optional) The play instance ID. | |
* @return {Object} | |
*/ | |
unmute: function (id) { | |
var self = this; | |
// if the sound hasn't been loaded, add it to the event queue | |
if (!self._loaded) { | |
self.on('play', function () { | |
self.unmute(id); | |
}); | |
return self; | |
} | |
var activeNode = (id) ? self._nodeById(id) : self._activeNode(); | |
if (activeNode) { | |
if (self._webAudio) { | |
activeNode.gain.value = self._volume; | |
} else { | |
activeNode.volume = self._volume; | |
} | |
} | |
return self; | |
}, | |
/** | |
* Get/set volume of this sound. | |
* @param {Float} vol Volume from 0.0 to 1.0. | |
* @param {String} id (optional) The play instance ID. | |
* @return {Object/Float} Returns self or current volume. | |
*/ | |
volume: function (vol, id) { | |
var self = this; | |
// make sure volume is a number | |
vol = parseFloat(vol, 10); | |
// if the sound hasn't been loaded, add it to the event queue | |
if (!self._loaded) { | |
self.on('play', function () { | |
self.volume(vol, id); | |
}); | |
return self; | |
} | |
if (vol >= 0 && vol <= 1) { | |
self._volume = vol; | |
var activeNode = (id) ? self._nodeById(id) : self._activeNode(); | |
if (activeNode) { | |
if (self._webAudio) { | |
activeNode.gain.value = vol; | |
} else { | |
activeNode.volume = vol * Howler.volume(); | |
} | |
} | |
return self; | |
} else { | |
return self._volume; | |
} | |
}, | |
/** | |
* Get/set whether to loop the sound. | |
* @param {Boolean} loop To loop or not to loop, that is the question. | |
* @return {Object/Boolean} Returns self or current looping value. | |
*/ | |
loop: function (loop) { | |
var self = this; | |
if (typeof loop === 'boolean') { | |
self._loop = loop; | |
return self; | |
} else { | |
return self._loop; | |
} | |
}, | |
/** | |
* Get/set sound sprite definition. | |
* @param {Object} sprite Example: {spriteName: [offset, duration, loop]} | |
* @param {Integer} offset Where to begin playback in milliseconds | |
* @param {Integer} duration How long to play in milliseconds | |
* @param {Boolean} loop (optional) Set true to loop this sprite | |
* @return {Object} Returns current sprite sheet or self. | |
*/ | |
sprite: function (sprite) { | |
var self = this; | |
if (typeof sprite === 'object') { | |
self._sprite = sprite; | |
return self; | |
} else { | |
return self._sprite; | |
} | |
}, | |
/** | |
* Get/set the position of playback. | |
* @param {Float} pos The position to move current playback to. | |
* @param {String} id (optional) The play instance ID. | |
* @return {Object/Float} Returns self or current playback position. | |
*/ | |
pos: function (pos, id) { | |
var self = this; | |
// if the sound hasn't been loaded, add it to the event queue | |
if (!self._loaded) { | |
self.on('load', function () { | |
self.pos(pos); | |
}); | |
return self; | |
} | |
var activeNode = (id) ? self._nodeById(id) : self._activeNode(); | |
if (activeNode) { | |
if (self._webAudio) { | |
if (pos >= 0) { | |
activeNode._pos = pos; | |
self.pause(id).play(activeNode._sprite, id); | |
return self; | |
} else { | |
return activeNode._pos + (ctx.currentTime - self._playStart); | |
} | |
} else { | |
if (pos >= 0) { | |
activeNode.currentTime = pos; | |
return self; | |
} else { | |
return activeNode.currentTime; | |
} | |
} | |
} | |
}, | |
/** | |
* Get/set the 3D position of the audio source. | |
* The most common usage is to set the 'x' position | |
* to affect the left/right ear panning. Setting any value higher than | |
* 1.0 will begin to decrease the volume of the sound as it moves further away. | |
* NOTE: This only works with Web Audio API, HTML5 Audio playback | |
* will not be affected. | |
* @param {Float} x The x-position of the playback from -1000.0 to 1000.0 | |
* @param {Float} y The y-position of the playback from -1000.0 to 1000.0 | |
* @param {Float} z The z-position of the playback from -1000.0 to 1000.0 | |
* @param {String} id (optional) The play instance ID. | |
* @return {Object/Array} Returns self or the current 3D position: [x, y, z] | |
*/ | |
pos3d: function (x, y, z, id) { | |
var self = this; | |
// set a default for the optional 'y' & 'z' | |
y = (typeof y === 'undefined' || !y) ? 0 : y; | |
z = (typeof z === 'undefined' || !z) ? -0.5 : z; | |
// if the sound hasn't been loaded, add it to the event queue | |
if (!self._loaded) { | |
self.on('play', function () { | |
self.pos3d(x, y, z, id); | |
}); | |
return self; | |
} | |
if (x >= 0 || x < 0) { | |
if (self._webAudio) { | |
var activeNode = (id) ? self._nodeById(id) : self._activeNode(); | |
if (activeNode) { | |
self._pos3d = [x, y, z]; | |
activeNode.panner.setPosition(x, y, z); | |
} | |
} | |
} else { | |
return self._pos3d; | |
} | |
return self; | |
}, | |
/** | |
* Fade in the current sound. | |
* @param {Float} to Volume to fade to (0.0 to 1.0). | |
* @param {Number} len Time in milliseconds to fade. | |
* @param {Function} callback | |
* @return {Object} | |
*/ | |
fadeIn: function (to, len, callback) { | |
var self = this, | |
dist = to, | |
iterations = dist / 0.01, | |
hold = len / iterations; | |
// if the sound hasn't been loaded, add it to the event queue | |
if (!self._loaded) { | |
self.on('load', function () { | |
self.fadeIn(to, len, callback); | |
}); | |
return self; | |
} | |
self.volume(0).play(); | |
for (var i = 1; i <= iterations; i++) { | |
(function () { | |
var vol = Math.round(1000 * (self._volume + 0.01 * i)) / 1000, | |
toVol = to; | |
setTimeout(function () { | |
self.volume(vol); | |
if (vol === toVol) { | |
if (callback) callback(); | |
} | |
}, hold * i); | |
})(); | |
} | |
return self; | |
}, | |
/** | |
* Fade out the current sound and pause when finished. | |
* @param {Float} to Volume to fade to (0.0 to 1.0). | |
* @param {Number} len Time in milliseconds to fade. | |
* @param {Function} callback | |
* @param {String} id (optional) The play instance ID. | |
* @return {Object} | |
*/ | |
fadeOut: function (to, len, callback, id) { | |
var self = this, | |
dist = self._volume - to, | |
iterations = dist / 0.01, | |
hold = len / iterations; | |
// if the sound hasn't been loaded, add it to the event queue | |
if (!self._loaded) { | |
self.on('play', function () { | |
self.fadeOut(to, len, callback, id); | |
}); | |
return self; | |
} | |
for (var i = 1; i <= iterations; i++) { | |
(function () { | |
var vol = Math.round(1000 * (self._volume - 0.01 * i)) / 1000, | |
toVol = to; | |
setTimeout(function () { | |
self.volume(vol, id); | |
if (vol === toVol) { | |
if (callback) callback(); | |
self.pause(id); | |
// fire ended event | |
self.on('end'); | |
} | |
}, hold * i); | |
})(); | |
} | |
return self; | |
}, | |
/** | |
* Get an audio node by ID. | |
* @return {Object} Audio node. | |
*/ | |
_nodeById: function (id) { | |
var self = this, | |
node = self._audioNode[0]; | |
// find the node with this ID | |
for (var i = 0; i < self._audioNode.length; i++) { | |
if (self._audioNode[i].id === id) { | |
node = self._audioNode[i]; | |
break; | |
} | |
} | |
return node; | |
}, | |
/** | |
* Get the first active audio node. | |
* @return {Object} Audio node. | |
*/ | |
_activeNode: function () { | |
var self = this, | |
node = null; | |
// find the first playing node | |
for (var i = 0; i < self._audioNode.length; i++) { | |
if (!self._audioNode[i].paused) { | |
node = self._audioNode[i]; | |
break; | |
} | |
} | |
// remove excess inactive nodes | |
self._drainPool(); | |
return node; | |
}, | |
/** | |
* Get the first inactive audio node. | |
* If there is none, create a new one and add it to the pool. | |
* @param {Function} callback Function to call when the audio node is ready. | |
*/ | |
_inactiveNode: function (callback) { | |
var self = this, | |
node = null; | |
// find first inactive node to recycle | |
for (var i = 0; i < self._audioNode.length; i++) { | |
if (self._audioNode[i].paused && self._audioNode[i].readyState === 4) { | |
callback(self._audioNode[i]); | |
node = true; | |
break; | |
} | |
} | |
// remove excess inactive nodes | |
self._drainPool(); | |
if (node) { | |
return; | |
} | |
// create new node if there are no inactives | |
var newNode; | |
if (self._webAudio) { | |
newNode = self._setupAudioNode(); | |
callback(newNode); | |
} else { | |
self.load(); | |
newNode = self._audioNode[self._audioNode.length - 1]; | |
newNode.addEventListener('loadedmetadata', function () { | |
callback(newNode); | |
}); | |
} | |
}, | |
/** | |
* If there are more than 5 inactive audio nodes in the pool, clear out the rest. | |
*/ | |
_drainPool: function () { | |
var self = this, | |
inactive = 0, | |
i; | |
// count the number of inactive nodes | |
for (i = 0; i < self._audioNode.length; i++) { | |
if (self._audioNode[i].paused) { | |
inactive++; | |
} | |
} | |
// remove excess inactive nodes | |
for (i = 0; i < self._audioNode.length; i++) { | |
if (inactive <= 5) { | |
break; | |
} | |
if (self._audioNode[i].paused) { | |
inactive--; | |
self._audioNode.splice(i, 1); | |
} | |
} | |
}, | |
/** | |
* Clear 'onend' timeout before it ends. | |
* @param {Number} timerId The ID of the sound to be cancelled. | |
*/ | |
_clearEndTimer: function (timerId) { | |
var self = this, | |
timer = self._onendTimer.indexOf(timerId); | |
// make sure the timer gets cleared | |
timer = timer >= 0 ? timer : 0; | |
if (self._onendTimer[timer]) { | |
clearTimeout(self._onendTimer[timer]); | |
self._onendTimer.splice(timer, 1); | |
} | |
}, | |
/** | |
* Setup the gain node and panner for a Web Audio instance. | |
* @return {Object} The new audio node. | |
*/ | |
_setupAudioNode: function () { | |
var self = this, | |
node = self._audioNode, | |
index = self._audioNode.length; | |
// create gain node | |
node[index] = (typeof ctx.createGain === 'undefined') ? ctx.createGainNode() : ctx.createGain(); | |
node[index].gain.value = self._volume; | |
node[index].paused = true; | |
node[index]._pos = 0; | |
node[index].readyState = 4; | |
node[index].connect(masterGain); | |
// create the panner | |
node[index].panner = ctx.createPanner(); | |
node[index].panner.setPosition(self._pos3d[0], self._pos3d[1], self._pos3d[2]); | |
node[index].panner.connect(node[index]); | |
return node[index]; | |
}, | |
/** | |
* Call/set custom events. | |
* @param {String} event Event type. | |
* @param {Function} fn Function to call. | |
* @return {Object} | |
*/ | |
on: function (event, fn) { | |
var self = this, | |
events = self['_on' + event]; | |
if (fn) { | |
events.push(fn); | |
} else { | |
for (var i = 0; i < events.length; i++) { | |
events[i].call(self); | |
} | |
} | |
return self; | |
}, | |
/** | |
* Remove a custom event. | |
* @param {String} event Event type. | |
* @param {Function} fn Listener to remove. | |
* @return {Object} [description] | |
*/ | |
off: function (event, fn) { | |
var self = this, | |
events = self['_on' + event], | |
fnString = fn.toString(); | |
// loop through functions in the event for comparison | |
for (var i = 0; i < events.length; i++) { | |
if (fnString === events[i].toString()) { | |
events.splice(i, 1); | |
break; | |
} | |
} | |
return self; | |
} | |
}; | |
// only define these functions when using WebAudio | |
if (usingWebAudio) { | |
/** | |
* Buffer a sound from URL (or from cache) and decode to audio source (Web Audio API). | |
* @param {Object} obj The Howl object for the sound to load. | |
* @param {String} url The path to the sound file. | |
*/ | |
var loadBuffer = function (obj, url) { | |
// check if the buffer has already been cached | |
if (url in cache) { | |
// set the duration from the cache | |
obj._duration = cache[url].duration; | |
// load the sound into this object | |
loadSound(obj); | |
} else { | |
// load the buffer from the URL | |
var xhr = new XMLHttpRequest(); | |
xhr.open('GET', url, true); | |
xhr.responseType = 'arraybuffer'; | |
xhr.onload = function () { | |
// decode the buffer into an audio source | |
ctx.decodeAudioData(xhr.response, function (buffer) { | |
if (buffer) { | |
cache[url] = buffer; | |
loadSound(obj, buffer); | |
} | |
}); | |
}; | |
xhr.onerror = function () { | |
// if there is an error, switch the sound to HTML Audio | |
if (obj._webAudio) { | |
obj._buffer = true; | |
obj._webAudio = false; | |
obj._audioNode = []; | |
delete obj._gainNode; | |
obj.load(); | |
} | |
}; | |
try { | |
xhr.send(); | |
} catch (e) { | |
xhr.onerror(); | |
} | |
} | |
}; | |
/** | |
* Finishes loading the Web Audio API sound and fires the loaded event | |
* @param {Object} obj The Howl object for the sound to load. | |
* @param {Objecct} buffer The decoded buffer sound source. | |
*/ | |
var loadSound = function (obj, buffer) { | |
// set the duration | |
obj._duration = (buffer) ? buffer.duration : obj._duration; | |
// setup a sprite if none is defined | |
if (Object.getOwnPropertyNames(obj._sprite).length === 0) { | |
obj._sprite = { | |
_default: [0, obj._duration * 1000] | |
}; | |
} | |
// fire the loaded event | |
if (!obj._loaded) { | |
obj._loaded = true; | |
obj.on('load'); | |
} | |
if (obj._autoplay) { | |
obj.play(); | |
} | |
}; | |
/** | |
* Load the sound back into the buffer source. | |
* @param {Object} obj The sound to load. | |
* @param {Array} loop Loop boolean, pos, and duration. | |
* @param {String} id (optional) The play instance ID. | |
*/ | |
var refreshBuffer = function (obj, loop, id) { | |
// determine which node to connect to | |
var node = obj._nodeById(id); | |
// setup the buffer source for playback | |
node.bufferSource = ctx.createBufferSource(); | |
node.bufferSource.buffer = cache[obj._src]; | |
node.bufferSource.connect(node.panner); | |
node.bufferSource.loop = loop[0]; | |
if (loop[0]) { | |
node.bufferSource.loopStart = loop[1]; | |
node.bufferSource.loopEnd = loop[1] + loop[2]; | |
} | |
}; | |
} | |
/** | |
* Add support for AMD (Async Module Definition) libraries such as require.js. | |
*/ | |
if (typeof define === 'function' && define.amd) { | |
define('Howler', function () { | |
return { | |
Howler: Howler, | |
Howl: Howl | |
}; | |
}); | |
} else { | |
window.Howler = Howler; | |
window.Howl = Howl; | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment