diff --git a/web/vendor/assets/javascripts/howler.core.js b/web/vendor/assets/javascripts/howler.core.js new file mode 100644 index 000000000..33b8bfbc3 --- /dev/null +++ b/web/vendor/assets/javascripts/howler.core.js @@ -0,0 +1,1651 @@ +/*! + * howler.js v2.0.0-beta + * howlerjs.com + * + * (c) 2013-2015, James Simpson of GoldFire Studios + * goldfirestudios.com + * + * MIT License + */ + +(function() { + + 'use strict'; + + // Setup our audio context. + var ctx = null; + var usingWebAudio = true; + var noAudio = false; + setupAudioContext(); + + // 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); + } + + /** Global Methods **/ + /***************************************************************************/ + + /** + * Create the global controller. All contained methods and properties apply + * to all sounds that are currently playing or will be in the future. + */ + var HowlerGlobal = function() { + this.init(); + }; + HowlerGlobal.prototype = { + /** + * Initialize the global Howler object. + * @return {Howler} + */ + init: function() { + var self = this || Howler; + + // Internal properties. + self._codecs = {}; + self._howls = []; + self._muted = false; + self._volume = 1; + + // Set to false to disable the auto iOS enabler. + self.iOSAutoEnable = true; + + // No audio is available on this system if this is set to true. + self.noAudio = noAudio; + + // This will be true if the Web Audio API is available. + self.usingWebAudio = usingWebAudio; + + // Expose the AudioContext when using Web Audio. + self.ctx = ctx; + + // Check for supported codecs. + if (!noAudio) { + self._setupCodecs(); + } + + return self; + }, + + /** + * 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 || Howler; + vol = parseFloat(vol); + + if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) { + self._volume = vol; + + // When using Web Audio, we just need to adjust the master gain. + if (usingWebAudio) { + masterGain.gain.value = vol; + } + + // Loop through and change volume for all HTML5 audio nodes. + for (var i=0; i 0 ? sound._seek : self._sprite[sprite][0] / 1000; + var duration = ((self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000) - seek; + + // Create a timer to fire at the end of playback or the start of a new loop. + var ended = function() { + // Should this sound loop? + var loop = !!(sound._loop || self._sprite[sprite][2]); + + // Fire the ended event. + self._emit('end', sound._id); + + // Restart the playback for HTML5 Audio loop. + if (!self._webAudio && loop) { + self.stop(sound._id).play(sound._id); + } + + // Restart this timer if on a Web Audio loop. + if (self._webAudio && loop) { + self._emit('play', sound._id); + sound._seek = sound._start || 0; + sound._playStart = ctx.currentTime; + self._endTimers[sound._id] = setTimeout(ended, ((sound._stop - sound._start) * 1000) / Math.abs(self._rate)); + } + + // Mark the node as paused. + if (self._webAudio && !loop) { + sound._paused = true; + sound._ended = true; + sound._seek = sound._start || 0; + self._clearTimer(sound._id); + + // Clean up the buffer source. + sound._node.bufferSource = null; + } + + // When using a sprite, end the track. + if (!self._webAudio && !loop) { + self.stop(sound._id); + } + }; + self._endTimers[sound._id] = setTimeout(ended, (duration * 1000) / Math.abs(self._rate)); + + // Update the parameters of the sound + sound._paused = false; + sound._ended = false; + sound._sprite = sprite; + sound._seek = seek; + sound._start = self._sprite[sprite][0] / 1000; + sound._stop = (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000; + sound._loop = !!(sound._loop || self._sprite[sprite][2]); + + // Begin the actual playback. + var node = sound._node; + if (self._webAudio) { + // Fire this when the sound is ready to play to begin Web Audio playback. + var playWebAudio = function() { + self._refreshBuffer(sound); + + // Setup the playback params. + var vol = (sound._muted || self._muted) ? 0 : sound._volume * Howler.volume(); + node.gain.setValueAtTime(vol, ctx.currentTime); + sound._playStart = ctx.currentTime; + + // Play the sound using the supported method. + if (typeof node.bufferSource.start === 'undefined') { + node.bufferSource.noteGrainOn(0, seek, duration); + } else { + node.bufferSource.start(0, seek, duration); + } + + // Start a new timer if none is present. + if (!self._endTimers[sound._id]) { + self._endTimers[sound._id] = setTimeout(ended, (duration * 1000) / Math.abs(self._rate)); + } + + if (!args[1]) { + setTimeout(function() { + self._emit('play', sound._id); + }, 0); + } + }; + + if (self._loaded) { + playWebAudio(); + } else { + // Wait for the audio to load and then begin playback. + self.once('load', playWebAudio); + + // Cancel the end timer. + self._clearTimer(sound._id); + } + } else { + // Fire this when the sound is ready to play to begin HTML5 Audio playback. + var playHtml5 = function() { + node.currentTime = seek; + node.muted = sound._muted || self._muted || Howler._muted || node.muted; + node.volume = sound._volume * Howler.volume(); + node.playbackRate = self._rate; + setTimeout(function() { + node.play(); + if (!args[1]) { + self._emit('play', sound._id); + } + }, 0); + }; + + // Play immediately if ready, or wait for the 'canplaythrough'e vent. + if (node.readyState === 4 || !node.readyState && navigator.isCocoonJS) { + playHtml5(); + } else { + var listener = function() { + // Setup the new end timer. + self._endTimers[sound._id] = setTimeout(ended, (duration * 1000) / Math.abs(self._rate)); + + // Begin playback. + playHtml5(); + + // Clear this listener. + node.removeEventListener('canplaythrough', listener, false); + }; + node.addEventListener('canplaythrough', listener, false); + + // Cancel the end timer. + self._clearTimer(sound._id); + } + } + + return sound._id; + }, + + /** + * Pause playback and save current position. + * @param {Number} id The sound ID (empty to pause all in group). + * @return {Howl} + */ + pause: function(id) { + var self = this; + + // Wait for the sound to begin playing before pausing it. + if (!self._loaded) { + self.once('play', function() { + self.pause(id); + }); + + return self; + } + + // If no id is passed, get all ID's to be paused. + var ids = self._getSoundIds(id); + + for (var i=0; i Returns the group's volume value. + * volume(id) -> Returns the sound id's current volume. + * volume(vol) -> Sets the volume of all sounds in this Howl group. + * volume(vol, id) -> Sets the volume of passed sound id. + * @return {Howl/Number} Returns self or current volume. + */ + volume: function() { + var self = this; + var args = arguments; + var vol, id; + + // Determine the values based on arguments. + if (args.length === 0) { + // Return the value of the groups' volume. + return self._volume; + } else if (args.length === 1) { + // First check if this is an ID, and if not, assume it is a new volume. + var ids = self._getSoundIds(); + var index = ids.indexOf(args[0]); + if (index >= 0) { + id = parseInt(args[0], 10); + } else { + vol = parseFloat(args[0]); + } + } else if (args.length === 2) { + vol = parseFloat(args[0]); + id = parseInt(args[1], 10); + } + + // Update the volume or return the current volume. + var sound; + if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) { + // Wait for the sound to begin playing before changing the volume. + if (!self._loaded) { + self.once('play', function() { + self.volume.apply(self, args); + }); + + return self; + } + + // Set the group volume. + if (typeof id === 'undefined') { + self._volume = vol; + } + + // Update one or all volumes. + id = self._getSoundIds(id); + for (var i=0; i 0 ? Math.ceil((end - ctx.currentTime) * 1000) : 0); + }.bind(self, ids[i], sound), len); + } else { + var diff = Math.abs(from - to); + var dir = from > to ? 'out' : 'in'; + var steps = diff / 0.01; + var stepLen = len / steps; + + (function() { + var vol = from; + var interval = setInterval(function(id) { + // Update the volume amount. + vol += (dir === 'in' ? 0.01 : -0.01); + + // Make sure the volume is in the right bounds. + vol = Math.max(0, vol); + vol = Math.min(1, vol); + + // Round to within 2 decimal points. + vol = Math.round(vol * 100) / 100; + + // Change the volume. + self.volume(vol, id); + + // When the fade is complete, stop it and fire event. + if (vol === to) { + clearInterval(interval); + self._emit('faded', id); + } + }.bind(self, ids[i]), stepLen); + })(); + } + } + } + + return self; + }, + + /** + * Get/set the loop parameter on a sound. This method can optionally take 0, 1 or 2 arguments. + * loop() -> Returns the group's loop value. + * loop(id) -> Returns the sound id's loop value. + * loop(loop) -> Sets the loop value for all sounds in this Howl group. + * loop(loop, id) -> Sets the loop value of passed sound id. + * @return {Howl/Boolean} Returns self or current loop value. + */ + loop: function() { + var self = this; + var args = arguments; + var loop, id, sound; + + // Determine the values for loop and id. + if (args.length === 0) { + // Return the grou's loop value. + return self._loop; + } else if (args.length === 1) { + if (typeof args[0] === 'boolean') { + loop = args[0]; + self._loop = loop; + } else { + // Return this sound's loop value. + sound = self._soundById(parseInt(args[0], 10)); + return sound ? sound._loop : false; + } + } else if (args.length === 2) { + loop = args[0]; + id = parseInt(args[1], 10); + } + + // If no id is passed, get all ID's to be looped. + var ids = self._getSoundIds(id); + for (var i=0; i Returns the first sound node's current seek position. + * seek(id) -> Returns the sound id's current seek position. + * seek(seek) -> Sets the seek position of the first sound node. + * seek(seek, id) -> Sets the seek position of passed sound id. + * @return {Howl/Number} Returns self or the current seek position. + */ + seek: function() { + var self = this; + var args = arguments; + var seek, id; + + // Determine the values based on arguments. + if (args.length === 0) { + // We will simply return the current position of the first node. + id = self._sounds[0]._id; + } else if (args.length === 1) { + // First check if this is an ID, and if not, assume it is a new seek position. + var ids = self._getSoundIds(); + var index = ids.indexOf(args[0]); + if (index >= 0) { + id = parseInt(args[0], 10); + } else { + id = self._sounds[0]._id; + seek = parseFloat(args[0]); + } + } else if (args.length === 2) { + seek = parseFloat(args[0]); + id = parseInt(args[1], 10); + } + + // If there is no ID, bail out. + if (typeof id === 'undefined') { + return self; + } + + // Wait for the sound to load before seeking it. + if (!self._loaded) { + self.once('load', function() { + self.seek.apply(self, args); + }); + + return self; + } + + // Get the sound. + var sound = self._soundById(id); + + if (sound) { + if (seek >= 0) { + // Pause the sound and update position for restarting playback. + var playing = self.playing(id); + if (playing) { + self.pause(id, true); + } + + // Move the position of the track and cancel timer. + sound._seek = seek; + self._clearTimer(id); + + // Restart the playback if the sound was playing. + if (playing) { + self.play(id, true); + } + } else { + if (self._webAudio) { + return (sound._seek + self.playing(id) ? ctx.currentTime - sound._playStart : 0); + } else { + return sound._node.currentTime; + } + } + } + + return self; + }, + + /** + * Check if a specific sound is currently playing or not. + * @param {Number} id The sound id to check. If none is passed, first sound is used. + * @return {Boolean} True if playing and false if not. + */ + playing: function(id) { + var self = this; + var sound = self._soundById(id) || self._sounds[0]; + + return sound ? !sound._paused : false; + }, + + /** + * Get the duration of this sound. + * @return {Number} Audio duration. + */ + duration: function() { + return this._duration; + }, + + /** + * Unload and destroy the current Howl object. + * This will immediately stop all sound instances attached to this group. + */ + unload: function() { + var self = this; + + // Stop playing any active sounds. + var sounds = self._sounds; + for (var i=0; i= 0) { + Howler._howls.splice(index, 1); + } + } + + // Delete this sound from the cache. + if (cache) { + delete cache[self._src]; + } + + // Clear out `self`. + self = null; + + return null; + }, + + /** + * Listen to a custom event. + * @param {String} event Event name. + * @param {Function} fn Listener to call. + * @param {Number} id (optional) Only listen to events for this sound. + * @return {Howl} + */ + on: function(event, fn, id) { + var self = this; + var events = self['_on' + event]; + + if (typeof fn === 'function') { + events.push({id: id, fn: fn}); + } + + return self; + }, + + /** + * Remove a custom event. + * @param {String} event Event name. + * @param {Function} fn Listener to remove. Leave empty to remove all. + * @param {Number} id (optional) Only remove events for this sound. + * @return {Howl} + */ + off: function(event, fn, id) { + var self = this; + var events = self['_on' + event]; + + if (fn) { + // Loop through event store and remove the passed function. + for (var i=0; i=0; i--) { + if (cnt <= limit) { + return; + } + + if (self._sounds[i]._ended) { + // Disconnect the audio source when using Web Audio. + if (self._webAudio && self._sounds[i]._node) { + self._sounds[i]._node.disconnect(0); + } + + // Remove sounds until we have the pool size. + self._sounds.splice(i, 1); + cnt--; + } + } + }, + + /** + * Get all ID's from the sounds pool. + * @param {Number} id Only return one ID if one is passed. + * @return {Array} Array of IDs. + */ + _getSoundIds: function(id) { + var self = this; + + if (typeof id === 'undefined') { + var ids = []; + for (var i=0; i> (-2 * bc & 6)) : 0 + ) { + buffer = chars.indexOf(buffer); + } + + return output; + }; + + // Decode the base64 data URI without XHR, since some browsers don't support it. + var data = atob(url.split(',')[1]); + var dataView = new Uint8Array(data.length); + for (var i=0; i