/*! * howler.js v1.1.25 * howlerjs.com * * (c) 2013-2014, James Simpson of GoldFire Studios * goldfirestudios.com * * MIT License */ (function() { // setup var cache = {}; // setup the audio context var ctx = null, usingWebAudio = true, noAudio = false; try { if (typeof AudioContext !== 'undefined') { ctx = new AudioContext(); } else if (typeof webkitAudioContext !== 'undefined') { ctx = new webkitAudioContext(); } else { usingWebAudio = false; } } catch(e) { usingWebAudio = false; } if (!usingWebAudio) { if (typeof Audio !== 'undefined') { try { new Audio(); } catch(e) { noAudio = true; } } else { 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(codecs) { this._volume = 1; this._muted = false; this.usingWebAudio = usingWebAudio; this.ctx = ctx; this.noAudio = noAudio; this._howls = []; this._codecs = codecs; this.iOSAutoEnable = true; }; HowlerGlobal.prototype = { /** * Get/set the global volume for all sounds. * @param {Float} vol Volume from 0.0 to 1.0. * @return {Howler/Float} Returns self or current volume. */ volume: function(vol) { var self = this; // make sure volume is a number vol = parseFloat(vol); if (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 self._howls) { if (self._howls.hasOwnProperty(key) && self._howls[key]._webAudio === false) { // loop through the audio nodes for (var i=0; i 0) ? node._pos : self._sprite[sprite][0] / 1000; // determine how long to play for var duration = 0; if (self._webAudio) { duration = self._sprite[sprite][1] / 1000 - node._pos; if (node._pos > 0) { pos = self._sprite[sprite][0] / 1000 + pos; } } else { duration = self._sprite[sprite][1] / 1000 - (pos - self._sprite[sprite][0] / 1000); } // 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).play(sprite, data.id); } // set web audio node to paused at end if (self._webAudio && !loop) { self._nodeById(data.id).paused = true; self._nodeById(data.id)._pos = 0; // clear the end timer self._clearEndTimer(data.id); } // end the track if it is HTML audio and a sprite if (!self._webAudio && !loop) { self.stop(data.id); } // fire ended event self.on('end', soundId); }, duration * 1000); // store the reference to the timer self._onendTimer.push({timer: timerId, id: data.id}); })(); if (self._webAudio) { var loopStart = self._sprite[sprite][0] / 1000, loopEnd = self._sprite[sprite][1] / 1000; // set the play id to this node and load into context node.id = soundId; node.paused = false; refreshBuffer(self, [loop, loopStart, loopEnd], soundId); self._playStart = ctx.currentTime; node.gain.value = self._volume; 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.readyState && navigator.isCocoonJS) { node.readyState = 4; node.id = soundId; node.currentTime = pos; node.muted = Howler._muted || node.muted; node.volume = self._volume * Howler.volume(); setTimeout(function() { node.play(); }, 0); } else { self._clearEndTimer(soundId); (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. * @return {Howl} */ pause: 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.pause(id); }); return self; } // clear 'onend' timer self._clearEndTimer(id); var activeNode = (id) ? self._nodeById(id) : self._activeNode(); if (activeNode) { activeNode._pos = self.pos(null, id); if (self._webAudio) { // make sure the sound has been created if (!activeNode.bufferSource || activeNode.paused) { return self; } activeNode.paused = true; if (typeof activeNode.bufferSource.stop === 'undefined') { activeNode.bufferSource.noteOff(0); } else { activeNode.bufferSource.stop(0); } } else { activeNode.pause(); } } self.on('pause'); return self; }, /** * Stop playback and reset to start. * @param {String} id (optional) The play instance ID. * @return {Howl} */ stop: 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.stop(id); }); return self; } // clear 'onend' timer self._clearEndTimer(id); 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 || activeNode.paused) { return self; } activeNode.paused = true; if (typeof activeNode.bufferSource.stop === 'undefined') { activeNode.bufferSource.noteOff(0); } else { activeNode.bufferSource.stop(0); } } else if (!isNaN(activeNode.duration)) { activeNode.pause(); activeNode.currentTime = 0; } } return self; }, /** * Mute this sound. * @param {String} id (optional) The play instance ID. * @return {Howl} */ 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.muted = true; } } return self; }, /** * Unmute this sound. * @param {String} id (optional) The play instance ID. * @return {Howl} */ 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.muted = false; } } 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 {Howl/Float} Returns self or current volume. */ volume: function(vol, id) { var self = this; // make sure volume is a number vol = parseFloat(vol); if (vol >= 0 && vol <= 1) { self._volume = vol; // 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; } 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 {Howl/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 {Howl} 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 {Howl/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 typeof pos === 'number' ? self : self._pos || 0; } // make sure we are dealing with a number for pos pos = parseFloat(pos); var activeNode = (id) ? self._nodeById(id) : self._activeNode(); if (activeNode) { if (pos >= 0) { self.pause(id); activeNode._pos = pos; self.play(activeNode._sprite, id); return self; } else { return self._webAudio ? activeNode._pos + (ctx.currentTime - self._playStart) : activeNode.currentTime; } } else if (pos >= 0) { return self; } else { // find the first inactive node to return the pos for for (var i=0; i= 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); activeNode.panner.panningModel = self._model || 'HRTF'; } } } else { return self._pos3d; } return self; }, /** * Fade a currently playing sound between two volumes. * @param {Number} from The volume to fade from (0.0 to 1.0). * @param {Number} to The volume to fade to (0.0 to 1.0). * @param {Number} len Time in milliseconds to fade. * @param {Function} callback (optional) Fired when the fade is complete. * @param {String} id (optional) The play instance ID. * @return {Howl} */ fade: function(from, to, len, callback, id) { var self = this, diff = Math.abs(from - to), dir = from > to ? 'down' : 'up', steps = diff / 0.01, stepTime = len / steps; // if the sound hasn't been loaded, add it to the event queue if (!self._loaded) { self.on('load', function() { self.fade(from, to, len, callback, id); }); return self; } // set the volume to the start position self.volume(from, id); for (var i=1; i<=steps; i++) { (function() { var change = self._volume + (dir === 'up' ? 0.01 : -0.01) * i, vol = Math.round(1000 * change) / 1000, toVol = to; setTimeout(function() { self.volume(vol, id); if (vol === toVol) { if (callback) callback(); } }, stepTime * i); })(); } }, /** * [DEPRECATED] 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 {Howl} */ fadeIn: function(to, len, callback) { return this.volume(0).play().fade(0, to, len, callback); }, /** * [DEPRECATED] 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 {Howl} */ fadeOut: function(to, len, callback, id) { var self = this; return self.fade(self._volume, to, len, function() { if (callback) callback(); self.pause(id); // fire ended event self.on('end'); }, id); }, /** * Get an audio node by ID. * @return {Howl} Audio node. */ _nodeById: function(id) { var self = this, node = self._audioNode[0]; // find the node with this ID for (var i=0; i=0; i--) { if (inactive <= 5) { break; } if (self._audioNode[i].paused) { // disconnect the audio source if using Web Audio if (self._webAudio) { self._audioNode[i].disconnect(0); } inactive--; self._audioNode.splice(i, 1); } } }, /** * Clear 'onend' timeout before it ends. * @param {String} soundId The play instance ID. */ _clearEndTimer: function(soundId) { var self = this, index = 0; // loop through the timers to find the one associated with this sound for (var i=0; i= 0) { Howler._howls.splice(index, 1); } // delete this sound from the cache delete cache[self._src]; self = null; } }; // 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); return; } if (/^data:[^;]+;base64,/.test(url)) { // Decode base64 data-URIs because some browsers cannot load data-URIs with XMLHttpRequest. var data = atob(url.split(',')[1]); var dataView = new Uint8Array(data.length); for (var i=0; i