From 13c5dc317abe0fb4458e703f2ab9397a7c24c859 Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Mon, 16 Nov 2015 16:58:18 +0200 Subject: Rengöring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- howler.js | 1353 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1353 insertions(+) create mode 100644 howler.js (limited to 'howler.js') diff --git a/howler.js b/howler.js new file mode 100644 index 0000000..515235a --- /dev/null +++ b/howler.js @@ -0,0 +1,1353 @@ +/*! + * howler.js v1.1.28 + * howlerjs.com + * + * (c) 2013-2015, 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') { + loop ? node.bufferSource.noteGrainOn(0, pos, 86400) : node.bufferSource.noteGrainOn(0, pos, duration); + } else { + loop ? node.bufferSource.start(0, pos, 86400) : 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