GeSHi Source Viewer: howler.jsView Raw


  1. /*! 
  2.  *  @file howler.js v2.2.3 | howlerjs.com
  3.  *  @version 2.2.3-planeptune
  4.  *  @author James Simpson
  5.  *  @copyright 2013-2020, James Simpson of GoldFire Studios | goldfirestudios.com
  6.  *  @license MIT
  7.  *
  8.  *  This version of howler has been modified by zefie, for the Planeptune Loop Player ("Planeptune Edition"), and minified using uglify-js v3.x
  9.  *  See https://loops.planeptune.org/geshi/source/js/howler.diff.txt for modifications to "Planeptune Edition"
  10.  */
  11.  
  12. (function() {
  13.  
  14.   'use strict';
  15.  
  16.   /** Global Methods **/
  17.   /***************************************************************************/
  18.  
  19.   /**
  20.    * Create the global controller. All contained methods and properties apply
  21.    * to all sounds that are currently playing or will be in the future.
  22.    */
  23.   var HowlerGlobal = function() {
  24.     this.init();
  25.   };
  26.   HowlerGlobal.prototype = {
  27.     /**
  28.      * Initialize the global Howler object.
  29.      * @return {Howler}
  30.      */
  31.     init: function() {
  32.       var self = this || Howler;
  33.       console.log('howler v2.2.3-planeptune initialized');
  34.  
  35.       // Create a global ID counter.
  36.       self._counter = 1000;
  37.  
  38.       // Pool of unlocked HTML5 Audio objects.
  39.       self._html5AudioPool = [];
  40.       self.html5PoolSize = 10;
  41.  
  42.       // Internal properties.
  43.       self._codecs = {};
  44.       self._howls = [];
  45.       self._muted = false;
  46.       self._volume = 1;
  47.       self._canPlayEvent = 'canplaythrough';
  48.       self._navigator = (typeof window !== 'undefined' && window.navigator) ? window.navigator : null;
  49.  
  50.       // Public properties.
  51.       self.masterGain = null;
  52.       self.noAudio = false;
  53.       self.usingWebAudio = true;
  54.       self.autoSuspend = true;
  55.       self.ctx = null;
  56.  
  57.       // Set to false to disable the auto audio unlocker.
  58.       self.autoUnlock = true;
  59.  
  60.       // Setup the various state values for global tracking.
  61.       self._setup();
  62.  
  63.       return self;
  64.     },
  65.  
  66.    /**
  67.     * Check if this browser is Internet Explorer.
  68.     * Returns the version if so, otherwise returns false
  69.     */
  70.  
  71.   detectIE: function () {
  72.     var ua = window.navigator.userAgent;
  73.  
  74.     var msie = ua.indexOf('MSIE ');
  75.     if (msie > 0) {
  76.       // IE 10 or older => return version number
  77.       return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
  78.     }
  79.  
  80.     var trident = ua.indexOf('Trident/');
  81.     if (trident > 0) {
  82.       // IE 11 => return version number
  83.       var rv = ua.indexOf('rv:');
  84.       return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
  85.     }
  86.  
  87.     var edge = ua.indexOf('Edge/');
  88.     if (edge > 0) {
  89.        // Edge (IE 12+) => return version number
  90.        return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
  91.     }
  92.  
  93.     // other browser
  94.     return false;
  95.   },
  96.  
  97.     /**
  98.      * Get/set the global volume for all sounds.
  99.      * @param  {Float} vol Volume from 0.0 to 1.0.
  100.      * @return {Howler/Float}     Returns self or current volume.
  101.      */
  102.     volume: function(vol) {
  103.       var self = this || Howler;
  104.       vol = parseFloat(vol);
  105.  
  106.       // If we don't have an AudioContext created yet, run the setup.
  107.       if (!self.ctx) {
  108.         setupAudioContext();
  109.       }
  110.  
  111.       if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) {
  112.         self._volume = vol;
  113.  
  114.         // Don't update any of the nodes if we are muted.
  115.         if (self._muted) {
  116.           return self;
  117.         }
  118.  
  119.         // When using Web Audio, we just need to adjust the master gain.
  120.         if (self._webAudio && !self._html5) {
  121.           self.masterGain.gain.setValueAtTime(vol, Howler.ctx.currentTime);
  122.         }
  123.  
  124.         // Loop through and change volume for all HTML5 audio nodes.
  125.         for (var i=0; i<self._howls.length; i++) {
  126.           if (!self._howls[i]._webAudio) {
  127.             // Get all of the sounds in this Howl group.
  128.             var ids = self._howls[i]._getSoundIds();
  129.  
  130.             // Loop through all sounds and change the volumes.
  131.             for (var j=0; j<ids.length; j++) {
  132.               var sound = self._howls[i]._soundById(ids[j]);
  133.  
  134.               if (sound && sound._node) {
  135.                 sound._node.volume = sound._volume * vol;
  136.               }
  137.             }
  138.           }
  139.         }
  140.  
  141.         return self;
  142.       }
  143.  
  144.       return self._volume;
  145.     },
  146.  
  147.     /**
  148.      * Handle muting and unmuting globally.
  149.      * @param  {Boolean} muted Is muted or not.
  150.      */
  151.     mute: function(muted) {
  152.       var self = this || Howler;
  153.  
  154.       // If we don't have an AudioContext created yet, run the setup.
  155.       if (!self.ctx) {
  156.         setupAudioContext();
  157.       }
  158.  
  159.       self._muted = muted;
  160.  
  161.       // With Web Audio, we just need to mute the master gain.
  162.       if (self._webAudio) {
  163.         self.masterGain.gain.setValueAtTime(muted ? 0 : self._volume, Howler.ctx.currentTime);
  164.       }
  165.  
  166.       // Loop through and mute all HTML5 Audio nodes.
  167.       for (var i=0; i<self._howls.length; i++) {
  168.         if (!self._howls[i]._webAudio) {
  169.           // Get all of the sounds in this Howl group.
  170.           var ids = self._howls[i]._getSoundIds();
  171.  
  172.           // Loop through all sounds and mark the audio node as muted.
  173.           for (var j=0; j<ids.length; j++) {
  174.             var sound = self._howls[i]._soundById(ids[j]);
  175.  
  176.             if (sound && sound._node) {
  177.               sound._node.muted = (muted) ? true : sound._muted;
  178.             }
  179.           }
  180.         }
  181.       }
  182.  
  183.       return self;
  184.     },
  185.  
  186.     /**
  187.      * Handle stopping all sounds globally.
  188.      */
  189.     stop: function() {
  190.       var self = this || Howler;
  191.  
  192.       // Loop through all Howls and stop them.
  193.       for (var i=0; i<self._howls.length; i++) {
  194.         self._howls[i].stop();
  195.       }
  196.  
  197.       return self;
  198.     },
  199.  
  200.     /**
  201.      * Unload and destroy all currently loaded Howl objects.
  202.      * @return {Howler}
  203.      */
  204.     unload: function() {
  205.       var self = this || Howler;
  206.  
  207.       for (var i=self._howls.length-1; i>=0; i--) {
  208.         self._howls[i].unload();
  209.       }
  210.  
  211.       // Create a new AudioContext to make sure it is fully reset.
  212.       if (self._webAudio && self.ctx && typeof self.ctx.close !== 'undefined') {
  213.         self.ctx.close();
  214.         self.ctx = null;
  215.         setupAudioContext();
  216.       }
  217.  
  218.       return self;
  219.     },
  220.  
  221.     /**
  222.      * Check for codec support of specific extension.
  223.      * @param  {String} ext Audio file extention.
  224.      * @return {Boolean}
  225.      */
  226.     codecs: function(ext) {
  227.       return (this || Howler)._codecs[ext.replace(/^x-/, '')];
  228.     },
  229.  
  230.     /**
  231.      * Setup various state values for global tracking.
  232.      * @return {Howler}
  233.      */
  234.     _setup: function() {
  235.       var self = this || Howler;
  236.  
  237.       // Keeps track of the suspend/resume state of the AudioContext.
  238.       self.state = self.ctx ? self.ctx.state || 'suspended' : 'suspended';
  239.  
  240.       // Automatically begin the 30-second suspend process
  241.       self._autoSuspend();
  242.  
  243.       // Check if audio is available.
  244.       if (!self._webAudio) {
  245.         // No audio is available on this system if noAudio is set to true.
  246.         if (typeof Audio !== 'undefined') {
  247.           try {
  248.             var test = new Audio();
  249.  
  250.             // Check if the canplaythrough event is available.
  251.             if (typeof test.oncanplaythrough === 'undefined') {
  252.               self._canPlayEvent = 'canplay';
  253.             }
  254.           } catch(e) {
  255.             self.noAudio = true;
  256.           }
  257.         } else {
  258.           self.noAudio = true;
  259.         }
  260.       }
  261.  
  262.       // Test to make sure audio isn't disabled in Internet Explorer.
  263.       try {
  264.         var test = new Audio();
  265.         if (test.muted) {
  266.           self.noAudio = true;
  267.         }
  268.       } catch (e) {}
  269.  
  270.       // Check for supported codecs.
  271.       if (!self.noAudio) {
  272.         self._setupCodecs();
  273.       }
  274.  
  275.       return self;
  276.     },
  277.  
  278.     /**
  279.      * Check for browser support for various codecs and cache the results.
  280.      * @return {Howler}
  281.      */
  282.     _setupCodecs: function() {
  283.       var self = this || Howler;
  284.       var audioTest = null;
  285.  
  286.       // Must wrap in a try/catch because IE11 in server mode throws an error.
  287.       try {
  288.         audioTest = (typeof Audio !== 'undefined') ? new Audio() : null;
  289.       } catch (err) {
  290.         return self;
  291.       }
  292.  
  293.       if (!audioTest || typeof audioTest.canPlayType !== 'function') {
  294.         return self;
  295.       }
  296.  
  297.       var mpegTest = audioTest.canPlayType('audio/mpeg;').replace(/^no$/, '');
  298.  
  299.       // Opera version <33 has mixed MP3 support, so we need to check for and block it.
  300.       var ua = self._navigator ? self._navigator.userAgent : '';
  301.       var checkOpera = ua.match(/OPR\/([0-6].)/g);
  302.       var isOldOpera = (checkOpera && parseInt(checkOpera[0].split('/')[1], 10) < 33);
  303.       var checkSafari = ua.indexOf('Safari') !== -1 && ua.indexOf('Chrome') === -1;
  304.       var safariVersion = ua.match(/Version\/(.*?) /);
  305.       var isOldSafari = (checkSafari && safariVersion && parseInt(safariVersion[1], 10) < 15);
  306.  
  307.       self._codecs = {
  308.         mp3: !!(!isOldOpera && (mpegTest || audioTest.canPlayType('audio/mp3;').replace(/^no$/, ''))),
  309.         mpeg: !!mpegTest,
  310.         opus: !!audioTest.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/, ''),
  311.         ogg: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ''),
  312.         oga: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ''),
  313.         wav: !!(audioTest.canPlayType('audio/wav; codecs="1"') || audioTest.canPlayType('audio/wav')).replace(/^no$/, ''),
  314.         aac: !!audioTest.canPlayType('audio/aac;').replace(/^no$/, ''),
  315.         caf: !!audioTest.canPlayType('audio/x-caf;').replace(/^no$/, ''),
  316.         m4a: !!(audioTest.canPlayType('audio/x-m4a;') || audioTest.canPlayType('audio/m4a;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''),
  317.         m4b: !!(audioTest.canPlayType('audio/x-m4b;') || audioTest.canPlayType('audio/m4b;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''),
  318.         mp4: !!(audioTest.canPlayType('audio/x-mp4;') || audioTest.canPlayType('audio/mp4;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''),
  319.         weba: !!(!isOldSafari && audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, '')),
  320.         webm: !!(!isOldSafari && audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, '')),
  321.         dolby: !!audioTest.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/, ''),
  322.         flac: !!(audioTest.canPlayType('audio/x-flac;') || audioTest.canPlayType('audio/flac;')).replace(/^no$/, '')
  323.       };
  324.  
  325.       return self;
  326.     },
  327.  
  328.     /**
  329.      * Some browsers/devices will only allow audio to be played after a user interaction.
  330.      * Attempt to automatically unlock audio on the first user interaction.
  331.      * Concept from: http://paulbakaus.com/tutorials/html5/web-audio-on-ios/
  332.      * @return {Howler}
  333.      */
  334.     _unlockAudio: function() {
  335.       var self = this || Howler;
  336.  
  337.       // Only run this if Web Audio is supported and it hasn't already been unlocked.
  338.       if (self._audioUnlocked || !self.ctx) {
  339.         return;
  340.       }
  341.  
  342.       self._audioUnlocked = false;
  343.       self.autoUnlock = false;
  344.  
  345.       // Some mobile devices/platforms have distortion issues when opening/closing tabs and/or web views.
  346.       // Bugs in the browser (especially Mobile Safari) can cause the sampleRate to change from 44100 to 48000.
  347.       // By calling Howler.unload(), we create a new AudioContext with the correct sampleRate.
  348.       if (!self._mobileUnloaded && self.ctx.sampleRate !== 44100) {
  349.         self._mobileUnloaded = true;
  350.         self.unload();
  351.       }
  352.  
  353.       // Scratch buffer for enabling iOS to dispose of web audio buffers correctly, as per:
  354.       // http://stackoverflow.com/questions/24119684
  355.       self._scratchBuffer = self.ctx.createBuffer(1, 1, 22050);
  356.  
  357.       // Call this method on touch start to create and play a buffer,
  358.       // then check if the audio actually played to determine if
  359.       // audio has now been unlocked on iOS, Android, etc.
  360.       var unlock = function(e) {
  361.         // Create a pool of unlocked HTML5 Audio objects that can
  362.         // be used for playing sounds without user interaction. HTML5
  363.         // Audio objects must be individually unlocked, as opposed
  364.         // to the WebAudio API which only needs a single activation.
  365.         // This must occur before WebAudio setup or the source.onended
  366.         // event will not fire.
  367.         while (self._html5AudioPool.length < self.html5PoolSize) {
  368.           try {
  369.             var audioNode = new Audio();
  370.  
  371.             // Mark this Audio object as unlocked to ensure it can get returned
  372.             // to the unlocked pool when released.
  373.             audioNode._unlocked = true;
  374.  
  375.             // Add the audio node to the pool.
  376.             self._releaseHtml5Audio(audioNode);
  377.           } catch (e) {
  378.             self.noAudio = true;
  379.             break;
  380.           }
  381.         }
  382.  
  383.         // Loop through any assigned audio nodes and unlock them.
  384.         for (var i=0; i<self._howls.length; i++) {
  385.           if (!self._howls[i]._webAudio) {
  386.             // Get all of the sounds in this Howl group.
  387.             var ids = self._howls[i]._getSoundIds();
  388.  
  389.             // Loop through all sounds and unlock the audio nodes.
  390.             for (var j=0; j<ids.length; j++) {
  391.               var sound = self._howls[i]._soundById(ids[j]);
  392.  
  393.               if (sound && sound._node && !sound._node._unlocked) {
  394.                 sound._node._unlocked = true;
  395.                 sound._node.load();
  396.               }
  397.             }
  398.           }
  399.         }
  400.  
  401.         // Fix Android can not play in suspend state.
  402.         self._autoResume();
  403.  
  404.         // Create an empty buffer.
  405.         var source = self.ctx.createBufferSource();
  406.         source.buffer = self._scratchBuffer;
  407.         source.connect(self.ctx.destination);
  408.  
  409.         // Play the empty buffer.
  410.         if (typeof source.start === 'undefined') {
  411.           source.noteOn(0);
  412.         } else {
  413.           source.start(0);
  414.         }
  415.  
  416.         // Calling resume() on a stack initiated by user gesture is what actually unlocks the audio on Android Chrome >= 55.
  417.         if (typeof self.ctx.resume === 'function') {
  418.           self.ctx.resume();
  419.         }
  420.  
  421.         // Setup a timeout to check that we are unlocked on the next event loop.
  422.         source.onended = function() {
  423.           source.disconnect(0);
  424.  
  425.           // Update the unlocked state and prevent this check from happening again.
  426.           self._audioUnlocked = true;
  427.  
  428.           // Remove the touch start listener.
  429.           document.removeEventListener('touchstart', unlock, true);
  430.           document.removeEventListener('touchend', unlock, true);
  431.           document.removeEventListener('click', unlock, true);
  432.           document.removeEventListener('keydown', unlock, true);
  433.  
  434.           // Let all sounds know that audio has been unlocked.
  435.           for (var i=0; i<self._howls.length; i++) {
  436.             self._howls[i]._emit('unlock');
  437.           }
  438.         };
  439.       };
  440.  
  441.       // Setup a touch start listener to attempt an unlock in.
  442.       document.addEventListener('touchstart', unlock, true);
  443.       document.addEventListener('touchend', unlock, true);
  444.       document.addEventListener('click', unlock, true);
  445.       document.addEventListener('keydown', unlock, true);
  446.  
  447.       return self;
  448.     },
  449.  
  450.     /**
  451.      * Get an unlocked HTML5 Audio object from the pool. If none are left,
  452.      * return a new Audio object and throw a warning.
  453.      * @return {Audio} HTML5 Audio object.
  454.      */
  455.     _obtainHtml5Audio: function() {
  456.       var self = this || Howler;
  457.  
  458.       // Return the next object from the pool if one exists.
  459.       if (self._html5AudioPool.length) {
  460.         return self._html5AudioPool.pop();
  461.       }
  462.  
  463.       //.Check if the audio is locked and throw a warning.
  464.       var testPlay = new Audio().play();
  465.       if (testPlay && typeof Promise !== 'undefined' && (testPlay instanceof Promise || typeof testPlay.then === 'function')) {
  466.         testPlay.catch(function() {
  467.           console.warn('HTML5 Audio pool exhausted, returning potentially locked audio object.');
  468.         });
  469.       }
  470.  
  471.       return new Audio();
  472.     },
  473.  
  474.     /**
  475.      * Return an activated HTML5 Audio object to the pool.
  476.      * @return {Howler}
  477.      */
  478.     _releaseHtml5Audio: function(audio) {
  479.       var self = this || Howler;
  480.  
  481.       // Don't add audio to the pool if we don't know if it has been unlocked.
  482.       if (audio._unlocked) {
  483.         self._html5AudioPool.push(audio);
  484.       }
  485.  
  486.       return self;
  487.     },
  488.  
  489.     /**
  490.      * Automatically suspend the Web Audio AudioContext after no sound has played for 30 seconds.
  491.      * This saves processing/energy and fixes various browser-specific bugs with audio getting stuck.
  492.      * @return {Howler}
  493.      */
  494.     _autoSuspend: function() {
  495.       var self = this;
  496.  
  497.       if (!self.autoSuspend || !self.ctx || typeof self.ctx.suspend === 'undefined' || !Howler.usingWebAudio) {
  498.         return;
  499.       }
  500.  
  501.       // Check if any sounds are playing.
  502.       for (var i=0; i<self._howls.length; i++) {
  503.         if (self._howls[i]._webAudio) {
  504.           for (var j=0; j<self._howls[i]._sounds.length; j++) {
  505.             if (!self._howls[i]._sounds[j]._paused) {
  506.               return self;
  507.             }
  508.           }
  509.         }
  510.       }
  511.  
  512.       if (self._suspendTimer) {
  513.         clearTimeout(self._suspendTimer);
  514.       }
  515.  
  516.       // If no sound has played after 30 seconds, suspend the context.
  517.       self._suspendTimer = setTimeout(function() {
  518.         if (!self.autoSuspend) {
  519.           return;
  520.         }
  521.  
  522.         self._suspendTimer = null;
  523.         self.state = 'suspending';
  524.  
  525.         // Handle updating the state of the audio context after suspending.
  526.         var handleSuspension = function() {
  527.           self.state = 'suspended';
  528.  
  529.           if (self._resumeAfterSuspend) {
  530.             delete self._resumeAfterSuspend;
  531.             self._autoResume();
  532.           }
  533.         };
  534.  
  535.         // Either the state gets suspended or it is interrupted.
  536.         // Either way, we need to update the state to suspended.
  537.         self.ctx.suspend().then(handleSuspension, handleSuspension);
  538.       }, 30000);
  539.  
  540.       return self;
  541.     },
  542.  
  543.     /**
  544.      * Automatically resume the Web Audio AudioContext when a new sound is played.
  545.      * @return {Howler}
  546.      */
  547.     _autoResume: function() {
  548.       var self = this;
  549.  
  550.       if (!self.ctx || typeof self.ctx.resume === 'undefined' || !Howler.usingWebAudio) {
  551.         return;
  552.       }
  553.  
  554.       if (self.state === 'running' && self.ctx.state !== 'interrupted' && self._suspendTimer) {
  555.         clearTimeout(self._suspendTimer);
  556.         self._suspendTimer = null;
  557.       } else if (self.state === 'suspended' || self.state === 'running' && self.ctx.state === 'interrupted') {
  558.         self.ctx.resume().then(function() {
  559.           self.state = 'running';
  560.  
  561.           // Emit to all Howls that the audio has resumed.
  562.           for (var i=0; i<self._howls.length; i++) {
  563.             self._howls[i]._emit('resume');
  564.           }
  565.         });
  566.  
  567.         if (self._suspendTimer) {
  568.           clearTimeout(self._suspendTimer);
  569.           self._suspendTimer = null;
  570.         }
  571.       } else if (self.state === 'suspending') {
  572.         self._resumeAfterSuspend = true;
  573.       }
  574.  
  575.       return self;
  576.     }
  577.   };
  578.  
  579.   // Setup the global audio controller.
  580.   var Howler = new HowlerGlobal();
  581.  
  582.   /** Group Methods **/
  583.   /***************************************************************************/
  584.  
  585.   /**
  586.    * Create an audio group controller.
  587.    * @param {Object} o Passed in properties for this group.
  588.    */
  589.   var Howl = function(o) {
  590.     var self = this;
  591.  
  592.     // Throw an error if no source is provided.
  593.     if (((!o.src || o.src.length === 0) && (!o.arraybuffer || !(o.arraybuffer instanceof ArrayBuffer) || !o.contenttype)) || (o.html5 && !o.src)) {
  594.       console.error('An array of source files, or an ArrayBuffer+ContentType, must be passed with any new Howl. Also, ArrayBuffer+ContentType are incompatible with html5 mode.');
  595.       return;
  596.     }
  597.  
  598.     self.init(o);
  599.   };
  600.   Howl.prototype = {
  601.     /**
  602.      * Initialize a new Howl group object.
  603.      * @param  {Object} o Passed in properties for this group.
  604.      * @return {Howl}
  605.      */
  606.     init: function(o) {
  607.       var self = this;
  608.  
  609.       // If we don't have an AudioContext created yet, run the setup.
  610.       if (!Howler.ctx) {
  611.         setupAudioContext();
  612.       }
  613.  
  614.       // Setup user-defined default properties.
  615.       self._autoplay = o.autoplay || false;
  616.       self._format = (typeof o.format !== 'string') ? o.format : [o.format];
  617.       self._html5 = o.html5 || false;
  618.       self._muted = o.mute || false;
  619.       self._loop = o.loop || false;
  620.       self._pool = o.pool || 5;
  621.       self._preload = (typeof o.preload === 'boolean' || o.preload === 'metadata') ? o.preload : true;
  622.       self._rate = o.rate || 1;
  623.       self._sprite = o.sprite || {};
  624.       self._buffer = o.arraybuffer || null;
  625.       self._contenttype = o.contenttype || null;	  
  626.       self._src = (typeof o.src !== 'string') ? o.src : [o.src];
  627.       self._nextsprite = o.nextsprite || null;
  628.       self._timeroffset = o.timeroffset || 30;	  
  629.       self._volume = o.volume !== undefined ? o.volume : 1;
  630.       self._xhr = {
  631.         method: o.xhr && o.xhr.method ? o.xhr.method : 'GET',
  632.         headers: o.xhr && o.xhr.headers ? o.xhr.headers : null,
  633.         withCredentials: o.xhr && o.xhr.withCredentials ? o.xhr.withCredentials : false,
  634.       };
  635.  
  636.       // Setup all other default properties.
  637.       self._duration = 0;
  638.       self._decoded = null;
  639.       self._hires = null;		  
  640.       self._state = 'unloaded';
  641.       self._sounds = [];
  642.       self._endTimers = {};
  643.       self._queue = [];
  644.       self._playLock = false;
  645.  
  646.       // Setup event listeners.
  647.       self._onend = o.onend ? [{fn: o.onend}] : [];
  648.       self._onfade = o.onfade ? [{fn: o.onfade}] : [];
  649.       self._onload = o.onload ? [{fn: o.onload}] : [];
  650.       self._onloaderror = o.onloaderror ? [{fn: o.onloaderror}] : [];
  651.       self._onplayerror = o.onplayerror ? [{fn: o.onplayerror}] : [];
  652.       self._onspritechange = o.onspritechange ? [{fn: o.onspritechange}] : [];	    
  653.       self._onpause = o.onpause ? [{fn: o.onpause}] : [];
  654.       self._onplay = o.onplay ? [{fn: o.onplay}] : [];
  655.       self._onstop = o.onstop ? [{fn: o.onstop}] : [];
  656.       self._onmute = o.onmute ? [{fn: o.onmute}] : [];
  657.       self._onvolume = o.onvolume ? [{fn: o.onvolume}] : [];
  658.       self._onrate = o.onrate ? [{fn: o.onrate}] : [];
  659.       self._onseek = o.onseek ? [{fn: o.onseek}] : [];
  660.       self._onunlock = o.onunlock ? [{fn: o.onunlock}] : [];
  661.       self._onresume = [];
  662.  
  663.       // Web Audio or HTML5 Audio?
  664.       self._webAudio = Howler.usingWebAudio && !self._html5;
  665.  
  666.       // Automatically try to enable audio.
  667.       if (typeof Howler.ctx !== 'undefined' && Howler.ctx && Howler.autoUnlock) {
  668.         Howler._unlockAudio();
  669.       }
  670.  
  671.       // Keep track of this Howl group in the global controller.
  672.       Howler._howls.push(self);
  673.  
  674.       // If they selected autoplay, add a play event to the load queue.
  675.       if (self._autoplay) {
  676.         self._queue.push({
  677.           event: 'play',
  678.           action: function() {
  679.             self.play();
  680.           }
  681.         });
  682.       }
  683.  
  684.       // Load the source file unless otherwise specified.
  685.       if (self._preload && self._preload !== 'none') {
  686.         self.load();
  687.       }
  688.  
  689.       return self;
  690.     },
  691.  
  692.     /**
  693.      * Load the audio file.
  694.      * @return {Howler}
  695.      */
  696.     load: function() {
  697.       var self = this;
  698.       var url = null;
  699.  
  700.       // If no audio is available, quit immediately.
  701.       if (Howler.noAudio) {
  702.         self._emit('loaderror', null, 'No audio support.');
  703.         return;
  704.       }
  705.  
  706.       if (self._buffer instanceof ArrayBuffer && !self._html5) {
  707.         if (!self._src) {
  708.           url = ['internal_buffer'];
  709.           self._src = url;
  710.         }
  711.       }
  712.  
  713.       // Make sure our source is in an array.
  714.       if (typeof self._src === 'string') {
  715.         self._src = [self._src];
  716.       }
  717.  
  718.       // Loop through the sources and pick the first one that is compatible.
  719.       for (var i=0; i<self._src.length; i++) {
  720.         var ext, str;
  721.  
  722.         if (self._format && self._format[i]) {
  723.           // If an extension was specified, use that instead.
  724.           ext = self._format[i];
  725.         } else {
  726.           // Make sure the source is a string.
  727.           str = self._src[i];
  728.           if (typeof str !== 'string') {
  729.             self._emit('loaderror', null, 'Non-string found in selected audio sources - ignoring.');
  730.             continue;
  731.           }
  732.  
  733.           // Extract the file extension from the URL or base64 data URI.
  734.           if (self._contenttype) {
  735.             ext = self._contenttype.split("/")[1];        
  736.           } else {
  737.             ext = /^data:audio\/([^;,]+);/i.exec(str);
  738.           }
  739.           if (!ext) {
  740.             ext = /\.([^.]+)$/.exec(str.split('?', 1)[0]);
  741.           }
  742.  
  743.           if (ext) {
  744.             ext = ext[1].toLowerCase();
  745.           }
  746.         }
  747.  
  748.         // Log a warning if no extension was found.
  749.         if (!ext) {
  750.           console.warn('No file extension was found. Consider using the "format" property or specify an extension.');
  751.         }
  752.  
  753.         // Check if this extension is available.
  754.         if (ext && Howler.codecs(ext)) {
  755.           url = self._src[i];
  756.           break;
  757.         }
  758.       }
  759.  
  760.       if (!url) {
  761.         self._emit('loaderror', null, 'No codec support for selected audio sources.');
  762.         return;
  763.       }
  764.  
  765.       self._src = url;
  766.       self._state = 'loading';
  767.  
  768.       // If the hosting page is HTTPS and the source isn't,
  769.       // drop down to HTML5 Audio to avoid Mixed Content errors.
  770.       if (window.location.protocol === 'https:' && url.slice(0, 5) === 'http:') {
  771.         self._html5 = true;
  772.         self._webAudio = false;
  773.       }
  774.  
  775.       // Create a new sound object and add it to the pool.
  776.       new Sound(self);
  777.  
  778.       // Load and decode the audio data for playback.
  779.       if (self._webAudio) {
  780.         loadBuffer(self);
  781.       }
  782.  
  783.       return self;
  784.     },
  785.  
  786.     /**
  787.      * Play a sound or resume previous playback.
  788.      * @param  {String/Number} sprite   Sprite name for sprite playback or sound id to continue previous.
  789.      * @param  {Boolean} internal Internal Use: true prevents event firing.
  790.      * @return {Number}          Sound ID.
  791.      */
  792.     play: function(sprite, internal) {
  793.       var self = this;
  794.       var id = null;
  795.  
  796.       // Determine if a sprite, sound id or nothing was passed
  797.       if (typeof sprite === 'number') {
  798.         id = sprite;
  799.         sprite = null;
  800.       } else if (typeof sprite === 'string' && self._state === 'loaded' && !self._sprite[sprite]) {
  801.         // If the passed sprite doesn't exist, do nothing.
  802.         return null;
  803.       } else if (typeof sprite === 'undefined') {
  804.         // Use the default sound sprite (plays the full audio length).
  805.         sprite = '__default';
  806.  
  807.         // Check if there is a single paused sound that isn't ended.
  808.         // If there is, play that sound. If not, continue as usual.
  809.         if (!self._playLock) {
  810.           var num = 0;
  811.           for (var i=0; i<self._sounds.length; i++) {
  812.             if (self._sounds[i]._paused && !self._sounds[i]._ended) {
  813.               num++;
  814.               id = self._sounds[i]._id;
  815.             }
  816.           }
  817.  
  818.           if (num === 1) {
  819.             sprite = null;
  820.           } else {
  821.             id = null;
  822.           }
  823.         }
  824.       }
  825.  
  826.       // Get the selected node, or get one from the pool.
  827.       var sound = id ? self._soundById(id) : self._inactiveSound();
  828.  
  829.       // If the sound doesn't exist, do nothing.
  830.       if (!sound) {
  831.         return null;
  832.       }
  833.  
  834.       // Select the sprite definition.
  835.       if (id && !sprite) {
  836.         sprite = sound._sprite || '__default';
  837.       }
  838.  
  839.       // If the sound hasn't loaded, we must wait to get the audio's duration.
  840.       // We also need to wait to make sure we don't run into race conditions with
  841.       // the order of function calls.
  842.       if (self._state !== 'loaded') {
  843.         // Set the sprite value on this sound.
  844.         sound._sprite = sprite;
  845.  
  846.         // Mark this sound as not ended in case another sound is played before this one loads.
  847.         sound._ended = false;
  848.  
  849.         // Add the sound to the queue to be played on load.
  850.         var soundId = sound._id;
  851.         self._queue.push({
  852.           event: 'play',
  853.           action: function() {
  854.             self.play(soundId);
  855.           }
  856.         });
  857.  
  858.         return soundId;
  859.       }
  860.  
  861.       // Don't play the sound if an id was passed and it is already playing.
  862.       if (id && !sound._paused) {
  863.         // Trigger the play event, in order to keep iterating through queue.
  864.         if (!internal) {
  865.           self._loadQueue('play');
  866.         }
  867.  
  868.         return sound._id;
  869.       }
  870.  
  871.       // Make sure the AudioContext isn't suspended, and resume it if it is.
  872.       if (self._webAudio) {
  873.         Howler._autoResume();
  874.       }
  875.  
  876.       // Determine how long to play for and where to start playing.
  877.       var seek = Math.max(0, sound._seek > 0 ? sound._seek : self._sprite[sprite][0] / 1000);
  878.       var duration = Math.max(0, ((self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000) - seek);
  879.       var timeout = (duration * 1000) / Math.abs(sound._rate);
  880.       var start = self._sprite[sprite][0] / 1000;
  881.       var stop = (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000;
  882.       sound._sprite = sprite;
  883.  
  884.       // Mark the sound as ended instantly so that this async playback
  885.       // doesn't get grabbed by another call to play while this one waits to start.
  886.       sound._ended = false;
  887.  
  888.       // Update the parameters of the sound.
  889.       var setParams = function() {
  890.         sound._paused = false;
  891.         sound._seek = seek;
  892.         sound._start = start;
  893.         sound._stop = stop;
  894.         sound._loop = !!(sound._loop || self._sprite[sprite][2]);
  895.       };
  896.  
  897.       // End the sound instantly if seek is at the end.
  898.       if (seek >= stop) {
  899.         self._ended(sound);
  900.         return;
  901.       }
  902.  
  903.       // Begin the actual playback.
  904.       var node = sound._node;
  905.       if (self._webAudio) {
  906.         // Fire this when the sound is ready to play to begin Web Audio playback.
  907.         var playWebAudio = function() {
  908.           self._playLock = false;
  909.           setParams();
  910.           self._refreshBuffer(sound);
  911.  
  912.           // Setup the playback params.
  913.           var vol = (sound._muted || self._muted) ? 0 : sound._volume;
  914.           node.gain.setValueAtTime(vol, Howler.ctx.currentTime);
  915.           sound._playStart = Howler.ctx.currentTime;
  916.  
  917.           // Play the sound using the supported method.
  918.           if (typeof node.bufferSource.start === 'undefined') {
  919.             sound._loop ? node.bufferSource.noteGrainOn(0, seek, 86400) : node.bufferSource.noteGrainOn(0, seek, duration);
  920.           } else {
  921.             sound._loop ? node.bufferSource.start(0, seek, 86400) : node.bufferSource.start(0, seek, duration);
  922.           }
  923.  
  924. 		  if (Howler.detectIE() == 11) {
  925. 			// IE11 Workaround where rate gets reset
  926. 			node.playbackRate = sound._rate;
  927. 		  }		  
  928.  
  929.           // Start a new timer if none is present.
  930.           if (timeout !== Infinity) {
  931.             self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), (timeout - self._timeroffset));
  932.           }
  933.  
  934.           if (!internal) {
  935.             setTimeout(function() {
  936.               self._emit('play', sound._id);
  937.               self._loadQueue();
  938.             }, 0);
  939.           }
  940.         };
  941.  
  942.         if (Howler.state === 'running' && Howler.ctx.state !== 'interrupted') {
  943.           playWebAudio();
  944.         } else {
  945.           self._playLock = true;
  946.  
  947.           // Wait for the audio context to resume before playing.
  948.           self.once('resume', playWebAudio);
  949.  
  950.           // Cancel the end timer.
  951.           self._clearTimer(sound._id);
  952.         }
  953.       } else {
  954.         // Fire this when the sound is ready to play to begin HTML5 Audio playback.
  955.         var playHtml5 = function() {
  956.           node.currentTime = seek;
  957.           node.muted = sound._muted || self._muted || Howler._muted || node.muted;
  958.           node.volume = sound._volume * Howler.volume();
  959.           node.playbackRate = sound._rate;
  960.  
  961.           // Some browsers will throw an error if this is called without user interaction.
  962.           try {
  963.             var play = node.play();
  964.  
  965.             // Support older browsers that don't support promises, and thus don't have this issue.
  966.             if (play && typeof Promise !== 'undefined' && (play instanceof Promise || typeof play.then === 'function')) {
  967.               // Implements a lock to prevent DOMException: The play() request was interrupted by a call to pause().
  968.               self._playLock = true;
  969.  
  970.               // Set param values immediately.
  971.               setParams();
  972.  
  973.               // Releases the lock and executes queued actions.
  974.               play
  975.                 .then(function() {
  976.                   self._playLock = false;
  977.                   node._unlocked = true;
  978.                   if (!internal) {
  979.                     self._emit('play', sound._id);
  980.                   } else {
  981.                     self._loadQueue();
  982.                   }
  983.                 })
  984.                 .catch(function() {
  985.                   self._playLock = false;
  986.                   self._emit('playerror', sound._id, 'Playback was unable to start. This is most commonly an issue ' +
  987.                     'on mobile devices and Chrome where playback was not within a user interaction.');
  988.  
  989.                   // Reset the ended and paused values.
  990.                   sound._ended = true;
  991.                   sound._paused = true;
  992.                 });
  993.             } else if (!internal) {
  994.               self._playLock = false;
  995.               setParams();
  996.               self._emit('play', sound._id);
  997.             }
  998.  
  999.             // Setting rate before playing won't work in IE, so we set it again here.
  1000.             node.playbackRate = sound._rate;
  1001.  
  1002.             // If the node is still paused, then we can assume there was a playback issue.
  1003.             if (node.paused) {
  1004.               self._emit('playerror', sound._id, 'Playback was unable to start. This is most commonly an issue ' +
  1005.                 'on mobile devices and Chrome where playback was not within a user interaction.');
  1006.               return;
  1007.             }
  1008.  
  1009.             // Setup the end timer on sprites or listen for the ended event.
  1010.             if (sprite !== '__default' || sound._loop) {
  1011.               self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout);
  1012.             } else {
  1013.               self._endTimers[sound._id] = function() {
  1014.                 // Fire ended on this audio node.
  1015.                 self._ended(sound);
  1016.  
  1017.                 // Clear this listener.
  1018.                 node.removeEventListener('ended', self._endTimers[sound._id], false);
  1019.               };
  1020.               node.addEventListener('ended', self._endTimers[sound._id], false);
  1021.             }
  1022.           } catch (err) {
  1023.             self._emit('playerror', sound._id, err);
  1024.           }
  1025.         };
  1026.  
  1027.         // If this is streaming audio, make sure the src is set and load again.
  1028.         if (node.src === 'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA') {
  1029.           node.src = self._src;
  1030.           node.load();
  1031.         }
  1032.  
  1033.         // Play immediately if ready, or wait for the 'canplaythrough'e vent.
  1034.         var loadedNoReadyState = (window && window.ejecta) || (!node.readyState && Howler._navigator.isCocoonJS);
  1035.         if (node.readyState >= 3 || loadedNoReadyState) {
  1036.           playHtml5();
  1037.         } else {
  1038.           self._playLock = true;
  1039.           self._state = 'loading';
  1040.  
  1041.           var listener = function() {
  1042.             self._state = 'loaded';
  1043.  
  1044.             // Begin playback.
  1045.             playHtml5();
  1046.  
  1047.             // Clear this listener.
  1048.             node.removeEventListener(Howler._canPlayEvent, listener, false);
  1049.           };
  1050.           node.addEventListener(Howler._canPlayEvent, listener, false);
  1051.  
  1052.           // Cancel the end timer.
  1053.           self._clearTimer(sound._id);
  1054.         }
  1055.       }
  1056.  
  1057.       return sound._id;
  1058.     },
  1059.  
  1060.     /**
  1061.      * Pause playback and save current position.
  1062.      * @param  {Number} id The sound ID (empty to pause all in group).
  1063.      * @return {Howl}
  1064.      */
  1065.     pause: function(id) {
  1066.       var self = this;
  1067.  
  1068.       // If the sound hasn't loaded or a play() promise is pending, add it to the load queue to pause when capable.
  1069.       if (self._state !== 'loaded' || self._playLock) {
  1070.         self._queue.push({
  1071.           event: 'pause',
  1072.           action: function() {
  1073.             self.pause(id);
  1074.           }
  1075.         });
  1076.  
  1077.         return self;
  1078.       }
  1079.  
  1080.       // If no id is passed, get all ID's to be paused.
  1081.       var ids = self._getSoundIds(id);
  1082.  
  1083.       for (var i=0; i<ids.length; i++) {
  1084.         // Clear the end timer.
  1085.         self._clearTimer(ids[i]);
  1086.  
  1087.         // Get the sound.
  1088.         var sound = self._soundById(ids[i]);
  1089.  
  1090.         if (sound && !sound._paused) {
  1091.           // Reset the seek position.
  1092.           sound._seek = self.seek(ids[i]);
  1093.           sound._rateSeek = 0;
  1094.           sound._paused = true;
  1095.  
  1096.           // Stop currently running fades.
  1097.           self._stopFade(ids[i]);
  1098.  
  1099.           if (sound._node) {
  1100.             if (self._webAudio) {
  1101.               // Make sure the sound has been created.
  1102.               if (!sound._node.bufferSource) {
  1103.                 continue;
  1104.               }
  1105.  
  1106.               if (typeof sound._node.bufferSource.stop === 'undefined') {
  1107.                 sound._node.bufferSource.noteOff(0);
  1108.               } else {
  1109.                 sound._node.bufferSource.stop(0);
  1110.               }
  1111.  
  1112.               // Clean up the buffer source.
  1113.               self._cleanBuffer(sound._node);
  1114.             } else if (!isNaN(sound._node.duration) || sound._node.duration === Infinity) {
  1115.               sound._node.pause();
  1116.             }
  1117.           }
  1118.         }
  1119.  
  1120.         // Fire the pause event, unless `true` is passed as the 2nd argument.
  1121.         if (!arguments[1]) {
  1122.           self._emit('pause', sound ? sound._id : null);
  1123.         }
  1124.       }
  1125.  
  1126.       return self;
  1127.     },
  1128.  
  1129.     /**
  1130.      * Stop playback and reset to start.
  1131.      * @param  {Number} id The sound ID (empty to stop all in group).
  1132.      * @param  {Boolean} internal Internal Use: true prevents event firing.
  1133.      * @return {Howl}
  1134.      */
  1135.     stop: function(id, internal) {
  1136.       var self = this;
  1137.  
  1138.       // If the sound hasn't loaded, add it to the load queue to stop when capable.
  1139.       if (self._state !== 'loaded' || self._playLock) {
  1140.         self._queue.push({
  1141.           event: 'stop',
  1142.           action: function() {
  1143.             self.stop(id);
  1144.           }
  1145.         });
  1146.  
  1147.         return self;
  1148.       }
  1149.  
  1150.       // If no id is passed, get all ID's to be stopped.
  1151.       var ids = self._getSoundIds(id);
  1152.  
  1153.       for (var i=0; i<ids.length; i++) {
  1154.         // Clear the end timer.
  1155.         self._clearTimer(ids[i]);
  1156.  
  1157.         // Get the sound.
  1158.         var sound = self._soundById(ids[i]);
  1159.  
  1160.         if (sound) {
  1161.           // Reset the seek position.
  1162.           sound._seek = sound._start || 0;
  1163.           sound._rateSeek = 0;
  1164.           sound._paused = true;
  1165.           sound._ended = true;
  1166.  
  1167.           // Stop currently running fades.
  1168.           self._stopFade(ids[i]);
  1169.  
  1170.           if (sound._node) {
  1171.             if (self._webAudio) {
  1172.               // Make sure the sound's AudioBufferSourceNode has been created.
  1173.               if (sound._node.bufferSource) {
  1174.                 if (typeof sound._node.bufferSource.stop === 'undefined') {
  1175.                   sound._node.bufferSource.noteOff(0);
  1176.                 } else {
  1177.                   sound._node.bufferSource.stop(0);
  1178.                 }
  1179.  
  1180.                 // Clean up the buffer source.
  1181.                 self._cleanBuffer(sound._node);
  1182.               }
  1183.             } else if (!isNaN(sound._node.duration) || sound._node.duration === Infinity) {
  1184.               sound._node.currentTime = sound._start || 0;
  1185.               sound._node.pause();
  1186.  
  1187.               // If this is a live stream, stop download once the audio is stopped.
  1188.               if (sound._node.duration === Infinity) {
  1189.                 self._clearSound(sound._node);
  1190.               }
  1191.             }
  1192.           }
  1193.  
  1194.           if (!internal) {
  1195.             self._emit('stop', sound._id);
  1196.           }
  1197.         }
  1198.       }
  1199.  
  1200.       return self;
  1201.     },
  1202.  
  1203.     /**
  1204.      * Mute/unmute a single sound or all sounds in this Howl group.
  1205.      * @param  {Boolean} muted Set to true to mute and false to unmute.
  1206.      * @param  {Number} id    The sound ID to update (omit to mute/unmute all).
  1207.      * @return {Howl}
  1208.      */
  1209.     mute: function(muted, id) {
  1210.       var self = this;
  1211.  
  1212.       // If the sound hasn't loaded, add it to the load queue to mute when capable.
  1213.       if (self._state !== 'loaded'|| self._playLock) {
  1214.         self._queue.push({
  1215.           event: 'mute',
  1216.           action: function() {
  1217.             self.mute(muted, id);
  1218.           }
  1219.         });
  1220.  
  1221.         return self;
  1222.       }
  1223.  
  1224.       // If applying mute/unmute to all sounds, update the group's value.
  1225.       if (typeof id === 'undefined') {
  1226.         if (typeof muted === 'boolean') {
  1227.           self._muted = muted;
  1228.         } else {
  1229.           return self._muted;
  1230.         }
  1231.       }
  1232.  
  1233.       // If no id is passed, get all ID's to be muted.
  1234.       var ids = self._getSoundIds(id);
  1235.  
  1236.       for (var i=0; i<ids.length; i++) {
  1237.         // Get the sound.
  1238.         var sound = self._soundById(ids[i]);
  1239.  
  1240.         if (sound) {
  1241.           sound._muted = muted;
  1242.  
  1243.           // Cancel active fade and set the volume to the end value.
  1244.           if (sound._interval) {
  1245.             self._stopFade(sound._id);
  1246.           }
  1247.  
  1248.           if (self._webAudio && sound._node) {
  1249.             sound._node.gain.setValueAtTime(muted ? 0 : sound._volume, Howler.ctx.currentTime);
  1250.           } else if (sound._node) {
  1251.             sound._node.muted = Howler._muted ? true : muted;
  1252.           }
  1253.  
  1254.           self._emit('mute', sound._id);
  1255.         }
  1256.       }
  1257.  
  1258.       return self;
  1259.     },
  1260.  
  1261.     /**
  1262.      * Get/set the volume of this sound or of the Howl group. This method can optionally take 0, 1 or 2 arguments.
  1263.      *   volume() -> Returns the group's volume value.
  1264.      *   volume(id) -> Returns the sound id's current volume.
  1265.      *   volume(vol) -> Sets the volume of all sounds in this Howl group.
  1266.      *   volume(vol, id) -> Sets the volume of passed sound id.
  1267.      * @return {Howl/Number} Returns self or current volume.
  1268.      */
  1269.     volume: function() {
  1270.       var self = this;
  1271.       var args = arguments;
  1272.       var vol, id;
  1273.  
  1274.       // Determine the values based on arguments.
  1275.       if (args.length === 0) {
  1276.         // Return the value of the groups' volume.
  1277.         return self._volume;
  1278.       } else if (args.length === 1 || args.length === 2 && typeof args[1] === 'undefined') {
  1279.         // First check if this is an ID, and if not, assume it is a new volume.
  1280.         var ids = self._getSoundIds();
  1281.         var index = ids.indexOf(args[0]);
  1282.         if (index >= 0) {
  1283.           id = parseInt(args[0], 10);
  1284.         } else {
  1285.           vol = parseFloat(args[0]);
  1286.         }
  1287.       } else if (args.length >= 2) {
  1288.         vol = parseFloat(args[0]);
  1289.         id = parseInt(args[1], 10);
  1290.       }
  1291.  
  1292.       // Update the volume or return the current volume.
  1293.       var sound;
  1294.       if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) {
  1295.         // If the sound hasn't loaded, add it to the load queue to change volume when capable.
  1296.         if (self._state !== 'loaded'|| self._playLock) {
  1297.           self._queue.push({
  1298.             event: 'volume',
  1299.             action: function() {
  1300.               self.volume.apply(self, args);
  1301.             }
  1302.           });
  1303.  
  1304.           return self;
  1305.         }
  1306.  
  1307.         // Set the group volume.
  1308.         if (typeof id === 'undefined') {
  1309.           self._volume = vol;
  1310.         }
  1311.  
  1312.         // Update one or all volumes.
  1313.         id = self._getSoundIds(id);
  1314.         for (var i=0; i<id.length; i++) {
  1315.           // Get the sound.
  1316.           sound = self._soundById(id[i]);
  1317.  
  1318.           if (sound) {
  1319.             sound._volume = vol;
  1320.  
  1321.             // Stop currently running fades.
  1322.             if (!args[2]) {
  1323.               self._stopFade(id[i]);
  1324.             }
  1325.  
  1326.             if (self._webAudio && sound._node && !sound._muted) {
  1327.               sound._node.gain.setValueAtTime(vol, Howler.ctx.currentTime);
  1328.             } else if (sound._node && !sound._muted) {
  1329.               sound._node.volume = vol * Howler.volume();
  1330.             }
  1331.  
  1332.             self._emit('volume', sound._id);
  1333.           }
  1334.         }
  1335.       } else {
  1336.         sound = id ? self._soundById(id) : self._sounds[0];
  1337.         return sound ? sound._volume : 0;
  1338.       }
  1339.  
  1340.       return self;
  1341.     },
  1342.  
  1343.     /**
  1344.      * Fade a currently playing sound between two volumes (if no id is passed, all sounds will fade).
  1345.      * @param  {Number} from The value to fade from (0.0 to 1.0).
  1346.      * @param  {Number} to   The volume to fade to (0.0 to 1.0).
  1347.      * @param  {Number} len  Time in milliseconds to fade.
  1348.      * @param  {Number} id   The sound id (omit to fade all sounds).
  1349.      * @return {Howl}
  1350.      */
  1351.     fade: function(from, to, len, id) {
  1352.       var self = this;
  1353.  
  1354.       // If the sound hasn't loaded, add it to the load queue to fade when capable.
  1355.       if (self._state !== 'loaded' || self._playLock) {
  1356.         self._queue.push({
  1357.           event: 'fade',
  1358.           action: function() {
  1359.             self.fade(from, to, len, id);
  1360.           }
  1361.         });
  1362.  
  1363.         return self;
  1364.       }
  1365.  
  1366.       // Make sure the to/from/len values are numbers.
  1367.       from = Math.min(Math.max(0, parseFloat(from)), 1);
  1368.       to = Math.min(Math.max(0, parseFloat(to)), 1);
  1369.       len = parseFloat(len);
  1370.  
  1371.       // Set the volume to the start position.
  1372.       self.volume(from, id);
  1373.  
  1374.       // Fade the volume of one or all sounds.
  1375.       var ids = self._getSoundIds(id);
  1376.       for (var i=0; i<ids.length; i++) {
  1377.         // Get the sound.
  1378.         var sound = self._soundById(ids[i]);
  1379.  
  1380.         // Create a linear fade or fall back to timeouts with HTML5 Audio.
  1381.         if (sound) {
  1382.           // Stop the previous fade if no sprite is being used (otherwise, volume handles this).
  1383.           if (!id) {
  1384.             self._stopFade(ids[i]);
  1385.           }
  1386.  
  1387.           // If we are using Web Audio, let the native methods do the actual fade.
  1388.           if (self._webAudio && !sound._muted) {
  1389.             var currentTime = Howler.ctx.currentTime;
  1390.             var end = currentTime + (len / 1000);
  1391.             sound._volume = from;
  1392.             sound._node.gain.setValueAtTime(from, currentTime);
  1393.             sound._node.gain.linearRampToValueAtTime(to, end);
  1394.           }
  1395.  
  1396.           self._startFadeInterval(sound, from, to, len, ids[i], typeof id === 'undefined');
  1397.         }
  1398.       }
  1399.  
  1400.       return self;
  1401.     },
  1402.  
  1403.     /**
  1404.      * Starts the internal interval to fade a sound.
  1405.      * @param  {Object} sound Reference to sound to fade.
  1406.      * @param  {Number} from The value to fade from (0.0 to 1.0).
  1407.      * @param  {Number} to   The volume to fade to (0.0 to 1.0).
  1408.      * @param  {Number} len  Time in milliseconds to fade.
  1409.      * @param  {Number} id   The sound id to fade.
  1410.      * @param  {Boolean} isGroup   If true, set the volume on the group.
  1411.      */
  1412.     _startFadeInterval: function(sound, from, to, len, id, isGroup) {
  1413.       var self = this;
  1414.       var vol = from;
  1415.       var diff = to - from;
  1416.       var steps = Math.abs(diff / 0.01);
  1417.       var stepLen = Math.max(4, (steps > 0) ? len / steps : len);
  1418.       var lastTick = Date.now();
  1419.  
  1420.       // Store the value being faded to.
  1421.       sound._fadeTo = to;
  1422.  
  1423.       // Update the volume value on each interval tick.
  1424.       sound._interval = setInterval(function() {
  1425.         // Update the volume based on the time since the last tick.
  1426.         var tick = (Date.now() - lastTick) / len;
  1427.         lastTick = Date.now();
  1428.         vol += diff * tick;
  1429.  
  1430.         // Round to within 2 decimal points.
  1431.         vol = Math.round(vol * 100) / 100;
  1432.  
  1433.         // Make sure the volume is in the right bounds.
  1434.         if (diff < 0) {
  1435.           vol = Math.max(to, vol);
  1436.         } else {
  1437.           vol = Math.min(to, vol);
  1438.         }
  1439.  
  1440.         // Change the volume.
  1441.         if (self._webAudio) {
  1442.           sound._volume = vol;
  1443.         } else {
  1444.           self.volume(vol, sound._id, true);
  1445.         }
  1446.  
  1447.         // Set the group's volume.
  1448.         if (isGroup) {
  1449.           self._volume = vol;
  1450.         }
  1451.  
  1452.         // When the fade is complete, stop it and fire event.
  1453.         if ((to < from && vol <= to) || (to > from && vol >= to)) {
  1454.           clearInterval(sound._interval);
  1455.           sound._interval = null;
  1456.           sound._fadeTo = null;
  1457.           self.volume(to, sound._id);
  1458.           self._emit('fade', sound._id);
  1459.         }
  1460.       }, stepLen);
  1461.     },
  1462.  
  1463.     /**
  1464.      * Internal method that stops the currently playing fade when
  1465.      * a new fade starts, volume is changed or the sound is stopped.
  1466.      * @param  {Number} id The sound id.
  1467.      * @return {Howl}
  1468.      */
  1469.     _stopFade: function(id) {
  1470.       var self = this;
  1471.       var sound = self._soundById(id);
  1472.  
  1473.       if (sound && sound._interval) {
  1474.         if (self._webAudio) {
  1475.           sound._node.gain.cancelScheduledValues(Howler.ctx.currentTime);
  1476.         }
  1477.  
  1478.         clearInterval(sound._interval);
  1479.         sound._interval = null;
  1480.         self.volume(sound._fadeTo, id);
  1481.         sound._fadeTo = null;
  1482.         self._emit('fade', id);
  1483.       }
  1484.  
  1485.       return self;
  1486.     },
  1487.  
  1488.     /**
  1489.      * Get/set the loop parameter on a sound. This method can optionally take 0, 1 or 2 arguments.
  1490.      *   loop() -> Returns the group's loop value.
  1491.      *   loop(id) -> Returns the sound id's loop value.
  1492.      *   loop(loop) -> Sets the loop value for all sounds in this Howl group.
  1493.      *   loop(loop, id) -> Sets the loop value of passed sound id.
  1494.      * @return {Howl/Boolean} Returns self or current loop value.
  1495.      */
  1496.     loop: function() {
  1497.       var self = this;
  1498.       var args = arguments;
  1499.       var loop, id, sound;
  1500.  
  1501.       // Determine the values for loop and id.
  1502.       if (args.length === 0) {
  1503.         // Return the grou's loop value.
  1504.         return self._loop;
  1505.       } else if (args.length === 1) {
  1506.         if (typeof args[0] === 'boolean') {
  1507.           loop = args[0];
  1508.           self._loop = loop;
  1509.         } else {
  1510.           // Return this sound's loop value.
  1511.           sound = self._soundById(parseInt(args[0], 10));
  1512.           return sound ? sound._loop : false;
  1513.         }
  1514.       } else if (args.length === 2) {
  1515.         loop = args[0];
  1516.         id = parseInt(args[1], 10);
  1517.       }
  1518.  
  1519.       // If no id is passed, get all ID's to be looped.
  1520.       var ids = self._getSoundIds(id);
  1521.       for (var i=0; i<ids.length; i++) {
  1522.         sound = self._soundById(ids[i]);
  1523.  
  1524.         if (sound) {
  1525.           sound._loop = loop;
  1526.           if (self._webAudio && sound._node && sound._node.bufferSource) {
  1527.             sound._node.bufferSource.loop = loop;
  1528.             if (loop) {
  1529.               sound._node.bufferSource.loopStart = sound._start || 0;
  1530.               sound._node.bufferSource.loopEnd = sound._stop;
  1531.  
  1532.               // If playing, restart playback to ensure looping updates.
  1533.               if (self.playing(ids[i])) {
  1534.                 self.pause(ids[i], true);
  1535.                 self.play(ids[i], true);
  1536.               }
  1537.             }
  1538.           }
  1539.         }
  1540.       }
  1541.  
  1542.       return self;
  1543.     },
  1544.  
  1545.     /**
  1546.      * Get/set the playback rate of a sound. This method can optionally take 0, 1 or 2 arguments.
  1547.      *   rate() -> Returns the first sound node's current playback rate.
  1548.      *   rate(id) -> Returns the sound id's current playback rate.
  1549.      *   rate(rate) -> Sets the playback rate of all sounds in this Howl group.
  1550.      *   rate(rate, id) -> Sets the playback rate of passed sound id.
  1551.      * @return {Howl/Number} Returns self or the current playback rate.
  1552.      */
  1553.     rate: function() {
  1554.       var self = this;
  1555.       var args = arguments;
  1556.       var rate, id;
  1557.  
  1558.       // Determine the values based on arguments.
  1559.       if (args.length === 0) {
  1560.         // We will simply return the current rate of the first node.
  1561.         id = self._sounds[0]._id;
  1562.       } else if (args.length === 1) {
  1563.         // First check if this is an ID, and if not, assume it is a new rate value.
  1564.         var ids = self._getSoundIds();
  1565.         var index = ids.indexOf(args[0]);
  1566.         if (index >= 0) {
  1567.           id = parseInt(args[0], 10);
  1568.         } else {
  1569.           rate = parseFloat(args[0]);
  1570.         }
  1571.       } else if (args.length === 2) {
  1572.         rate = parseFloat(args[0]);
  1573.         id = parseInt(args[1], 10);
  1574.       }
  1575.  
  1576.       // Update the playback rate or return the current value.
  1577.       var sound;
  1578.       if (typeof rate === 'number') {
  1579.         // If the sound hasn't loaded, add it to the load queue to change playback rate when capable.
  1580.         if (self._state !== 'loaded' || self._playLock) {
  1581.           self._queue.push({
  1582.             event: 'rate',
  1583.             action: function() {
  1584.               self.rate.apply(self, args);
  1585.             }
  1586.           });
  1587.  
  1588.           return self;
  1589.         }
  1590.  
  1591.         // Set the group rate.
  1592.         if (typeof id === 'undefined') {
  1593.           self._rate = rate;
  1594.         }
  1595.  
  1596.         // Update one or all volumes.
  1597.         id = self._getSoundIds(id);
  1598.         for (var i=0; i<id.length; i++) {
  1599.           // Get the sound.
  1600.           sound = self._soundById(id[i]);
  1601.  
  1602.           if (sound) {
  1603.             // Keep track of our position when the rate changed and update the playback
  1604.             // start position so we can properly adjust the seek position for time elapsed.
  1605.             if (self.playing(id[i])) {
  1606.               sound._rateSeek = self.seek(id[i]);
  1607.               sound._playStart = self._webAudio ? Howler.ctx.currentTime : sound._playStart;
  1608.             }
  1609.             sound._rate = rate;
  1610.  
  1611.             // Change the playback rate.
  1612.             if (self._webAudio && sound._node && sound._node.bufferSource) {
  1613.               sound._node.bufferSource.playbackRate.setValueAtTime(rate, Howler.ctx.currentTime);
  1614.             } else if (sound._node) {
  1615.               sound._node.playbackRate = rate;
  1616.             }
  1617.  
  1618.             // Reset the timers.
  1619.             var seek = self.seek(id[i]);
  1620.             var duration = ((self._sprite[sound._sprite][0] + self._sprite[sound._sprite][1]) / 1000) - seek;
  1621.             var timeout = (duration * 1000) / Math.abs(sound._rate);
  1622.  
  1623.             if (self._webAudio) {
  1624.                timeout = timeout - self._timeroffset;
  1625.             }
  1626.  
  1627.             // Start a new end timer if sound is already playing.
  1628.             if (self._endTimers[id[i]] || !sound._paused) {
  1629.               self._clearTimer(id[i]);
  1630.               self._endTimers[id[i]] = setTimeout(self._ended.bind(self, sound), timeout);
  1631.             }
  1632.  
  1633.             self._emit('rate', sound._id);
  1634.           }
  1635.         }
  1636.       } else {
  1637.         sound = self._soundById(id);
  1638.         return sound ? sound._rate : self._rate;
  1639.       }
  1640.  
  1641.       return self;
  1642.     },
  1643.  
  1644.     /**
  1645.      * Get/set the seek position of a sound. This method can optionally take 0, 1 or 2 arguments.
  1646.      *   seek() -> Returns the first sound node's current seek position.
  1647.      *   seek(id) -> Returns the sound id's current seek position.
  1648.      *   seek(seek) -> Sets the seek position of the first sound node.
  1649.      *   seek(seek, id) -> Sets the seek position of passed sound id.
  1650.      * @return {Howl/Number} Returns self or the current seek position.
  1651.      */
  1652.     seek: function() {
  1653.       var self = this;
  1654.       var args = arguments;
  1655.       var seek, id;
  1656.  
  1657.       // Determine the values based on arguments.
  1658.       if (args.length === 0) {
  1659.         // We will simply return the current position of the first node.
  1660.         if (self._sounds.length) {
  1661.           id = self._sounds[0]._id;
  1662.         }
  1663.       } else if (args.length === 1) {
  1664.         // First check if this is an ID, and if not, assume it is a new seek position.
  1665.         var ids = self._getSoundIds();
  1666.         var index = ids.indexOf(args[0]);
  1667.         if (index >= 0) {
  1668.           id = parseInt(args[0], 10);
  1669.         } else if (self._sounds.length) {
  1670.           id = self._sounds[0]._id;
  1671.           seek = parseFloat(args[0]);
  1672.         }
  1673.       } else if (args.length === 2) {
  1674.         seek = parseFloat(args[0]);
  1675.         id = parseInt(args[1], 10);
  1676.       }
  1677.  
  1678.       // If there is no ID, bail out.
  1679.       if (typeof id === 'undefined') {
  1680.         return 0;
  1681.       }
  1682.  
  1683.       // If the sound hasn't loaded, add it to the load queue to seek when capable.
  1684.       if (typeof seek === 'number' && (self._state !== 'loaded' || self._playLock)) {
  1685.         self._queue.push({
  1686.           event: 'seek',
  1687.           action: function() {
  1688.             self.seek.apply(self, args);
  1689.           }
  1690.         });
  1691.  
  1692.         return self;
  1693.       }
  1694.  
  1695.       // Get the sound.
  1696.       var sound = self._soundById(id);
  1697.  
  1698.       if (sound) {
  1699.         if (typeof seek === 'number' && seek >= 0) {
  1700.           // Pause the sound and update position for restarting playback.
  1701.           var playing = self.playing(id);
  1702.           var sprite = sound._sprite;
  1703.           sound._rateSeek = 0;
  1704.  
  1705.           var soundDuration = ((self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000);
  1706.           if (seek > soundDuration) {
  1707.             seek = 0;
  1708.           }
  1709.  
  1710.           if (playing && !self._webAudio) {
  1711.             self.pause(id, true);    
  1712.           }
  1713.  
  1714.           // Move the position of the track and cancel timer.
  1715.           sound._seek = seek;
  1716.           sound._ended = false;
  1717.           self._clearTimer(id);
  1718.  
  1719.  
  1720.           // Restart the playback if the sound was playing.
  1721.           if (playing && self._webAudio) {
  1722.             var oldbuffer = sound._node.bufferSource;
  1723.             self._refreshBuffer(sound);
  1724.             sound._playStart = Howler.ctx.currentTime;
  1725.             if (typeof sound._node.bufferSource.start === 'undefined') {
  1726.               sound._node.bufferSource.noteGrainOn(0, seek);
  1727.             } else {
  1728.               sound._node.bufferSource.start(0, seek);
  1729.             }
  1730.             if (oldbuffer) {
  1731.               oldbuffer.stop();
  1732.               oldbuffer = null;
  1733.             }
  1734.             var duration = soundDuration - seek;
  1735.             var timeout = (duration * 1000) / Math.abs(sound._rate);
  1736.             if (timeout !== Infinity) {
  1737.               self._endTimers[id] = setTimeout(self._ended.bind(self, sound), (timeout - self._timeroffset));
  1738.             }      
  1739.           }
  1740.  
  1741.           // Update the seek position for HTML5 Audio.
  1742.           if (!self._webAudio && sound._node && !isNaN(sound._node.duration)) {
  1743.             sound._node.currentTime = seek;
  1744.           }
  1745.  
  1746.           // Seek and emit when ready.
  1747.           var seekAndEmit = function() {
  1748.             // Restart the playback if the sound was playing.
  1749.             if (playing) {
  1750.               self.play(id, true);
  1751.             }
  1752.  
  1753.             self._emit('seek', id);
  1754.           };
  1755.  
  1756.           // Wait for the play lock to be unset before emitting (HTML5 Audio).
  1757.           if (playing && !self._webAudio) {
  1758.             var emitSeek = function() {
  1759.               if (!self._playLock) {
  1760.                 seekAndEmit();
  1761.               } else {
  1762.                 setTimeout(emitSeek, 0);
  1763.               }
  1764.             };
  1765.             setTimeout(emitSeek, 0);
  1766.           } else {
  1767.             seekAndEmit();
  1768.           }
  1769.         } else {
  1770.           if (self._webAudio) {
  1771.             var realTime = self.playing(id) ? Howler.ctx.currentTime - sound._playStart : 0;
  1772.             var rateSeek = sound._rateSeek ? sound._rateSeek - sound._seek : 0;
  1773.             return sound._seek + (rateSeek + realTime * Math.abs(sound._rate));
  1774.           } else {
  1775.             return sound._node.currentTime;
  1776.             // Workaround for IE11 reseting the rate
  1777.             if (Howler.detectIE() == 11) {
  1778.               self.rate(sound._rate,id);
  1779.             }			
  1780.           }
  1781.         }
  1782.       }
  1783.  
  1784.       return self;
  1785.     },
  1786.  
  1787.     /**
  1788.      * Check if a specific sound is currently playing or not (if id is provided), or check if at least one of the sounds in the group is playing or not.
  1789.      * @param  {Number}  id The sound id to check. If none is passed, the whole sound group is checked.
  1790.      * @return {Boolean} True if playing and false if not.
  1791.      */
  1792.     playing: function(id) {
  1793.       var self = this;
  1794.  
  1795.       // Check the passed sound ID (if any).
  1796.       if (typeof id === 'number') {
  1797.         var sound = self._soundById(id);
  1798.         return sound ? !sound._paused : false;
  1799.       }
  1800.  
  1801.       // Otherwise, loop through all sounds and check if any are playing.
  1802.       for (var i=0; i<self._sounds.length; i++) {
  1803.         if (!self._sounds[i]._paused) {
  1804.           return true;
  1805.         }
  1806.       }
  1807.  
  1808.       return false;
  1809.     },
  1810.  
  1811.     /**
  1812.      * Get the duration of this sound. Passing a sound id will return the sprite duration.
  1813.      * @param  {Number} id The sound id to check. If none is passed, return full source duration.
  1814.      * @return {Number} Audio duration in seconds.
  1815.      */
  1816.     duration: function(id) {
  1817.       var self = this;
  1818.       var duration = self._duration;
  1819.  
  1820.       // If we pass an ID, get the sound and return the sprite length.
  1821.       var sound = self._soundById(id);
  1822.       if (sound) {
  1823.         duration = self._sprite[sound._sprite][1] / 1000;
  1824.       }
  1825.  
  1826.       return duration;
  1827.     },
  1828.  
  1829.     /**
  1830.      * Returns the current loaded state of this Howl.
  1831.      * @return {String} 'unloaded', 'loading', 'loaded'
  1832.      */
  1833.     state: function() {
  1834.       return this._state;
  1835.     },
  1836.  
  1837.     /**
  1838.      * Unload and destroy the current Howl object.
  1839.      * This will immediately stop all sound instances attached to this group.
  1840.      */
  1841.     unload: function() {
  1842.       var self = this;
  1843.  
  1844.       // Stop playing any active sounds.
  1845.       var sounds = self._sounds;
  1846.       for (var i=0; i<sounds.length; i++) {
  1847.         // Stop the sound if it is currently playing.
  1848.         if (!sounds[i]._paused) {
  1849.           self.stop(sounds[i]._id);
  1850.         }
  1851.  
  1852.         // Remove the source or disconnect.
  1853.         if (!self._webAudio) {
  1854.           // Set the source to 0-second silence to stop any downloading (except in IE).
  1855.           self._clearSound(sounds[i]._node);
  1856.  
  1857.           // Remove any event listeners.
  1858.           sounds[i]._node.removeEventListener('error', sounds[i]._errorFn, false);
  1859.           sounds[i]._node.removeEventListener(Howler._canPlayEvent, sounds[i]._loadFn, false);
  1860.           sounds[i]._node.removeEventListener('ended', sounds[i]._endFn, false);
  1861.  
  1862.           // Release the Audio object back to the pool.
  1863.           Howler._releaseHtml5Audio(sounds[i]._node);
  1864.         }
  1865.  
  1866.         // Empty out all of the nodes.
  1867.         delete sounds[i]._node;
  1868.  
  1869.         // Make sure all timers are cleared out.
  1870.         self._clearTimer(sounds[i]._id);
  1871.       }
  1872.  
  1873.       // Remove the references in the global Howler object.
  1874.       var index = Howler._howls.indexOf(self);
  1875.       if (index >= 0) {
  1876.         Howler._howls.splice(index, 1);
  1877.       }
  1878.  
  1879.       // Delete this sound from the cache (if no other Howl is using it).
  1880.       var remCache = true;
  1881.       for (i=0; i<Howler._howls.length; i++) {
  1882.         if (Howler._howls[i]._src === self._src || self._src.indexOf(Howler._howls[i]._src) >= 0) {
  1883.           remCache = false;
  1884.           break;
  1885.         }
  1886.       }
  1887.  
  1888.       if (cache && remCache) {
  1889.         delete cache[self._src];
  1890.       }
  1891.  
  1892.       // Clear global errors.
  1893.       Howler.noAudio = false;
  1894.  
  1895.       // Clear out `self`.
  1896.       self._state = 'unloaded';
  1897.       self._sounds = [];
  1898.       self = null;
  1899.  
  1900.       return null;
  1901.     },
  1902.  
  1903.     /**
  1904.      * Listen to a custom event.
  1905.      * @param  {String}   event Event name.
  1906.      * @param  {Function} fn    Listener to call.
  1907.      * @param  {Number}   id    (optional) Only listen to events for this sound.
  1908.      * @param  {Number}   once  (INTERNAL) Marks event to fire only once.
  1909.      * @return {Howl}
  1910.      */
  1911.     on: function(event, fn, id, once) {
  1912.       var self = this;
  1913.       var events = self['_on' + event];
  1914.  
  1915.       if (typeof fn === 'function') {
  1916.         events.push(once ? {id: id, fn: fn, once: once} : {id: id, fn: fn});
  1917.       }
  1918.  
  1919.       return self;
  1920.     },
  1921.  
  1922.     /**
  1923.      * Remove a custom event. Call without parameters to remove all events.
  1924.      * @param  {String}   event Event name.
  1925.      * @param  {Function} fn    Listener to remove. Leave empty to remove all.
  1926.      * @param  {Number}   id    (optional) Only remove events for this sound.
  1927.      * @return {Howl}
  1928.      */
  1929.     off: function(event, fn, id) {
  1930.       var self = this;
  1931.       var events = self['_on' + event];
  1932.       var i = 0;
  1933.  
  1934.       // Allow passing just an event and ID.
  1935.       if (typeof fn === 'number') {
  1936.         id = fn;
  1937.         fn = null;
  1938.       }
  1939.  
  1940.       if (fn || id) {
  1941.         // Loop through event store and remove the passed function.
  1942.         for (i=0; i<events.length; i++) {
  1943.           var isId = (id === events[i].id);
  1944.           if (fn === events[i].fn && isId || !fn && isId) {
  1945.             events.splice(i, 1);
  1946.             break;
  1947.           }
  1948.         }
  1949.       } else if (event) {
  1950.         // Clear out all events of this type.
  1951.         self['_on' + event] = [];
  1952.       } else {
  1953.         // Clear out all events of every type.
  1954.         var keys = Object.keys(self);
  1955.         for (i=0; i<keys.length; i++) {
  1956.           if ((keys[i].indexOf('_on') === 0) && Array.isArray(self[keys[i]])) {
  1957.             self[keys[i]] = [];
  1958.           }
  1959.         }
  1960.       }
  1961.  
  1962.       return self;
  1963.     },
  1964.  
  1965.     /**
  1966.      * Listen to a custom event and remove it once fired.
  1967.      * @param  {String}   event Event name.
  1968.      * @param  {Function} fn    Listener to call.
  1969.      * @param  {Number}   id    (optional) Only listen to events for this sound.
  1970.      * @return {Howl}
  1971.      */
  1972.     once: function(event, fn, id) {
  1973.       var self = this;
  1974.  
  1975.       // Setup the event listener.
  1976.       self.on(event, fn, id, 1);
  1977.  
  1978.       return self;
  1979.     },
  1980.  
  1981.     /**
  1982.      * Emit all events of a specific type and pass the sound id.
  1983.      * @param  {String} event Event name.
  1984.      * @param  {Number} id    Sound ID.
  1985.      * @param  {Number} msg   Message to go with event.
  1986.      * @return {Howl}
  1987.      */
  1988.     _emit: function(event, id, msg) {
  1989.       var self = this;
  1990.       var events = self['_on' + event];
  1991.  
  1992.       // Loop through event store and fire all functions.
  1993.       for (var i=events.length-1; i>=0; i--) {
  1994.         // Only fire the listener if the correct ID is used.
  1995.         if (!events[i].id || events[i].id === id || event === 'load') {
  1996.           setTimeout(function(fn) {
  1997.             fn.call(this, id, msg);
  1998.           }.bind(self, events[i].fn), 0);
  1999.  
  2000.           // If this event was setup with `once`, remove it.
  2001.           if (events[i].once) {
  2002.             self.off(event, events[i].fn, events[i].id);
  2003.           }
  2004.         }
  2005.       }
  2006.  
  2007.       // Pass the event type into load queue so that it can continue stepping.
  2008.       self._loadQueue(event);
  2009.  
  2010.       return self;
  2011.     },
  2012.  
  2013.     /**
  2014.      * Queue of actions initiated before the sound has loaded.
  2015.      * These will be called in sequence, with the next only firing
  2016.      * after the previous has finished executing (even if async like play).
  2017.      * @return {Howl}
  2018.      */
  2019.     _loadQueue: function(event) {
  2020.       var self = this;
  2021.  
  2022.       if (self._queue.length > 0) {
  2023.         var task = self._queue[0];
  2024.  
  2025.         // Remove this task if a matching event was passed.
  2026.         if (task.event === event) {
  2027.           self._queue.shift();
  2028.           self._loadQueue();
  2029.         }
  2030.  
  2031.         // Run the task if no event type is passed.
  2032.         if (!event) {
  2033.           task.action();
  2034.         }
  2035.       }
  2036.  
  2037.       return self;
  2038.     },
  2039.  
  2040.     /**
  2041.      * Fired when playback is switched to another sprite with nextsprite
  2042.      * @param  {Sound} sound The sound object to work with.
  2043.      * @return {Howl}
  2044.      */  
  2045.  
  2046.     _onspritechange: function (sound) {
  2047.       var self = this;
  2048.       return self;
  2049.     },
  2050.  
  2051.     /**
  2052.      * Fired when playback ends at the end of the duration.
  2053.      * @param  {Sound} sound The sound object to work with.
  2054.      * @return {Howl}
  2055.      */
  2056.     _ended: function(sound) {
  2057.       var self = this;
  2058.       var sprite = sound._sprite;
  2059.  
  2060.       // If we are using IE and there was network latency we may be clipping
  2061.       // audio before it completes playing. Lets check the node to make sure it
  2062.       // believes it has completed, before ending the playback.
  2063.       if (!self._webAudio && sound._node && !sound._node.paused && !sound._node.ended && sound._node.currentTime < sound._stop) {
  2064.         setTimeout(self._ended.bind(self, sound), 100);
  2065.         return self;
  2066.       }
  2067.  
  2068.       // Should this sound loop?
  2069.       var loop = !!(sound._loop || self._sprite[sprite][2]);
  2070.  
  2071.       // Fire the ended event.
  2072.       self._emit('end', sound._id);
  2073.  
  2074.       // Restart the playback for HTML5 Audio loop.
  2075.       if (!self._webAudio && loop) {
  2076.         if (self._nextsprite && sound._sprite != self._nextsprite) {
  2077.           sprite = self._nextsprite;
  2078.           self._nextsprite = null;
  2079.           sound._sprite = sprite; // this line may be redundant
  2080.           self.stop(sound._id, true).play(sprite);
  2081.           self._emit('spritechange',sound);
  2082.         } else {
  2083.           self.stop(sound._id, true).play(sound._id);
  2084.         }
  2085.       }
  2086.  
  2087.       // Restart this timer if on a Web Audio loop.
  2088.       if (self._webAudio && loop) {
  2089.         var currpos = Math.round(self.seek() * 1000);
  2090.         var wanted = self._sprite[sprite][0] + self._sprite[sprite][1];
  2091.         var offset = (currpos - wanted);
  2092.         self._clearTimer(sound._id);
  2093.         if (offset < -6) {
  2094.           // "hi-res" mode
  2095.           if (!self._hires) {
  2096.             console.log("entering hi-res emulation mode");
  2097.             self._hires = setInterval(self._ended.bind(self, sound), 1);
  2098.           }
  2099.           return false;
  2100.         }
  2101.  
  2102.         if (self._hires) {
  2103.           console.log("exiting hi-res emulation mode");
  2104.           clearInterval(self._hires)
  2105.           self._hires = null;
  2106.         }
  2107.         console.log("howler: end timer fired. playtime was "+currpos+"ms, expected "+wanted+"ms ("+offset+"ms off)");    
  2108.         sound._rateSeek = 0;    
  2109.         sound._playStart = Howler.ctx.currentTime;  
  2110.         if (self._nextsprite && sound._sprite != self._nextsprite) {
  2111.           // manually change sprite
  2112.           sprite = self._nextsprite;
  2113.           self._nextsprite = null;
  2114.           sound._sprite = sprite;      
  2115.           self._emit('spritechange',sound);      
  2116.           sound._start = self._sprite[sprite][0] / 1000;
  2117.           sound._stop = (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000;
  2118.           sound._loop = loop;
  2119.           sound._seek = sound._start || 0;
  2120.           self.seek(sound._seek,sound._id);
  2121.         } else {
  2122.           self._emit('play', sound._id);
  2123.           sound._seek = sound._start || 0;
  2124.           var timeout = ((sound._stop - sound._start) * 1000) / Math.abs(sound._rate);
  2125.           self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), (timeout - self._timeroffset));      
  2126.         }
  2127.       }
  2128.  
  2129.       // Mark the node as paused.
  2130.       if (self._webAudio && !loop) {
  2131.         sound._paused = true;
  2132.         sound._ended = true;
  2133.         sound._seek = sound._start || 0;
  2134.         sound._rateSeek = 0;
  2135.         self._clearTimer(sound._id);
  2136.  
  2137.         // Clean up the buffer source.
  2138.         self._cleanBuffer(sound._node);
  2139.  
  2140.         // Attempt to auto-suspend AudioContext if no sounds are still playing.
  2141.         Howler._autoSuspend();
  2142.       }
  2143.  
  2144.       // When using a sprite, end the track.
  2145.       if (!self._webAudio && !loop) {
  2146.         self.stop(sound._id, true);
  2147.       }
  2148.  
  2149.       return self;
  2150.     },
  2151.  
  2152.     /**
  2153.      * Clear the end timer for a sound playback.
  2154.      * @param  {Number} id The sound ID.
  2155.      * @return {Howl}
  2156.      */
  2157.     _clearTimer: function(id) {
  2158.       var self = this;
  2159.  
  2160.       if (self._endTimers[id]) {
  2161.         // Clear the timeout or remove the ended listener.
  2162.         if (typeof self._endTimers[id] !== 'function') {
  2163.           clearTimeout(self._endTimers[id]);
  2164.         } else {
  2165.           var sound = self._soundById(id);
  2166.           if (sound && sound._node) {
  2167.             sound._node.removeEventListener('ended', self._endTimers[id], false);
  2168.           }
  2169.         }
  2170.  
  2171.         delete self._endTimers[id];
  2172.       }
  2173.  
  2174.       return self;
  2175.     },
  2176.  
  2177.     /**
  2178.      * Return the sound identified by this ID, or return null.
  2179.      * @param  {Number} id Sound ID
  2180.      * @return {Object}    Sound object or null.
  2181.      */
  2182.     _soundById: function(id) {
  2183.       var self = this;
  2184.  
  2185.       // Loop through all sounds and find the one with this ID.
  2186.       for (var i=0; i<self._sounds.length; i++) {
  2187.         if (id === self._sounds[i]._id) {
  2188.           return self._sounds[i];
  2189.         }
  2190.       }
  2191.  
  2192.       return null;
  2193.     },
  2194.  
  2195.     /**
  2196.      * Return an inactive sound from the pool or create a new one.
  2197.      * @return {Sound} Sound playback object.
  2198.      */
  2199.     _inactiveSound: function() {
  2200.       var self = this;
  2201.  
  2202.       self._drain();
  2203.  
  2204.       // Find the first inactive node to recycle.
  2205.       for (var i=0; i<self._sounds.length; i++) {
  2206.         if (self._sounds[i]._ended) {
  2207.           return self._sounds[i].reset();
  2208.         }
  2209.       }
  2210.  
  2211.       // If no inactive node was found, create a new one.
  2212.       return new Sound(self);
  2213.     },
  2214.  
  2215.     /**
  2216.      * Drain excess inactive sounds from the pool.
  2217.      */
  2218.     _drain: function() {
  2219.       var self = this;
  2220.       var limit = self._pool;
  2221.       var cnt = 0;
  2222.       var i = 0;
  2223.  
  2224.       // If there are less sounds than the max pool size, we are done.
  2225.       if (self._sounds.length < limit) {
  2226.         return;
  2227.       }
  2228.  
  2229.       // Count the number of inactive sounds.
  2230.       for (i=0; i<self._sounds.length; i++) {
  2231.         if (self._sounds[i]._ended) {
  2232.           cnt++;
  2233.         }
  2234.       }
  2235.  
  2236.       // Remove excess inactive sounds, going in reverse order.
  2237.       for (i=self._sounds.length - 1; i>=0; i--) {
  2238.         if (cnt <= limit) {
  2239.           return;
  2240.         }
  2241.  
  2242.         if (self._sounds[i]._ended) {
  2243.           // Disconnect the audio source when using Web Audio.
  2244.           if (self._webAudio && self._sounds[i]._node) {
  2245.             self._sounds[i]._node.disconnect(0);
  2246.           }
  2247.  
  2248.           // Remove sounds until we have the pool size.
  2249.           self._sounds.splice(i, 1);
  2250.           cnt--;
  2251.         }
  2252.       }
  2253.     },
  2254.  
  2255.     /**
  2256.      * Get all ID's from the sounds pool.
  2257.      * @param  {Number} id Only return one ID if one is passed.
  2258.      * @return {Array}    Array of IDs.
  2259.      */
  2260.     _getSoundIds: function(id) {
  2261.       var self = this;
  2262.  
  2263.       if (typeof id === 'undefined') {
  2264.         var ids = [];
  2265.         for (var i=0; i<self._sounds.length; i++) {
  2266.           ids.push(self._sounds[i]._id);
  2267.         }
  2268.  
  2269.         return ids;
  2270.       } else {
  2271.         return [id];
  2272.       }
  2273.     },
  2274.  
  2275.     /**
  2276.      * Load the sound back into the buffer source.
  2277.      * @param  {Sound} sound The sound object to work with.
  2278.      * @return {Howl}
  2279.      */
  2280.     _refreshBuffer: function(sound) {
  2281.       var self = this;
  2282.  
  2283.       // Setup the buffer source for playback.
  2284.       sound._node.bufferSource = Howler.ctx.createBufferSource();
  2285.       sound._node.bufferSource.buffer = cache[self._src];
  2286.  
  2287.       // Connect to the correct node.
  2288.       if (sound._panner) {
  2289.         sound._node.bufferSource.connect(sound._panner);
  2290.       } else {
  2291.         sound._node.bufferSource.connect(sound._node);
  2292.       }
  2293.  
  2294.       // Setup looping and playback rate.
  2295.       sound._node.bufferSource.loop = sound._loop;
  2296.       if (sound._loop) {
  2297.         sound._node.bufferSource.loopStart = sound._start || 0;
  2298.         sound._node.bufferSource.loopEnd = sound._stop || 0;
  2299.       }
  2300.       sound._node.bufferSource.playbackRate.setValueAtTime(sound._rate, Howler.ctx.currentTime);
  2301.  
  2302.       return self;
  2303.     },
  2304.  
  2305.     /**
  2306.      * Prevent memory leaks by cleaning up the buffer source after playback.
  2307.      * @param  {Object} node Sound's audio node containing the buffer source.
  2308.      * @return {Howl}
  2309.      */
  2310.     _cleanBuffer: function(node) {
  2311.       var self = this;
  2312.       var isIOS = Howler._navigator && Howler._navigator.vendor.indexOf('Apple') >= 0;
  2313.  
  2314.       if (Howler._scratchBuffer && node.bufferSource) {
  2315.         node.bufferSource.onended = null;
  2316.         node.bufferSource.disconnect(0);
  2317.         if (isIOS) {
  2318.           try { node.bufferSource.buffer = Howler._scratchBuffer; } catch(e) {}
  2319.         }
  2320.       }
  2321.       node.bufferSource = null;
  2322.  
  2323.       return self;
  2324.     },
  2325.  
  2326.     /**
  2327.      * Set the source to a 0-second silence to stop any downloading (except in IE).
  2328.      * @param  {Object} node Audio node to clear.
  2329.      */
  2330.     _clearSound: function(node) {
  2331.       var checkIE = /MSIE |Trident\//.test(Howler._navigator && Howler._navigator.userAgent);
  2332.       if (!checkIE) {
  2333.         node.src = 'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA';
  2334.       }
  2335.     }
  2336.   };
  2337.  
  2338.   /** Single Sound Methods **/
  2339.   /***************************************************************************/
  2340.  
  2341.   /**
  2342.    * Setup the sound object, which each node attached to a Howl group is contained in.
  2343.    * @param {Object} howl The Howl parent group.
  2344.    */
  2345.   var Sound = function(howl) {
  2346.     this._parent = howl;
  2347.     this.init();
  2348.   };
  2349.   Sound.prototype = {
  2350.     /**
  2351.      * Initialize a new Sound object.
  2352.      * @return {Sound}
  2353.      */
  2354.     init: function() {
  2355.       var self = this;
  2356.       var parent = self._parent;
  2357.  
  2358.       // Setup the default parameters.
  2359.       self._muted = parent._muted;
  2360.       self._loop = parent._loop;
  2361.       self._volume = parent._volume;
  2362.       self._rate = parent._rate;
  2363.       self._seek = 0;
  2364.       self._paused = true;
  2365.       self._ended = true;
  2366.       self._sprite = '__default';
  2367.  
  2368.       // Generate a unique ID for this sound.
  2369.       self._id = ++Howler._counter;
  2370.  
  2371.       // Add itself to the parent's pool.
  2372.       parent._sounds.push(self);
  2373.  
  2374.       // Create the new node.
  2375.       self.create();
  2376.  
  2377.       return self;
  2378.     },
  2379.  
  2380.     /**
  2381.      * Create and setup a new sound object, whether HTML5 Audio or Web Audio.
  2382.      * @return {Sound}
  2383.      */
  2384.     create: function() {
  2385.       var self = this;
  2386.       var parent = self._parent;
  2387.       var volume = (Howler._muted || self._muted || self._parent._muted) ? 0 : self._volume;
  2388.  
  2389.       if (parent._webAudio) {
  2390.         // Create the gain node for controlling volume (the source will connect to this).
  2391.         self._node = (typeof Howler.ctx.createGain === 'undefined') ? Howler.ctx.createGainNode() : Howler.ctx.createGain();
  2392.         self._node.gain.setValueAtTime(volume, Howler.ctx.currentTime);
  2393.         self._node.paused = true;
  2394.         self._node.connect(Howler.masterGain);
  2395.       } else if (!Howler.noAudio) {
  2396.         // Get an unlocked Audio object from the pool.
  2397.         self._node = Howler._obtainHtml5Audio();
  2398.  
  2399.         // Listen for errors (http://dev.w3.org/html5/spec-author-view/spec.html#mediaerror).
  2400.         self._errorFn = self._errorListener.bind(self);
  2401.         self._node.addEventListener('error', self._errorFn, false);
  2402.  
  2403.         // Listen for 'canplaythrough' event to let us know the sound is ready.
  2404.         self._loadFn = self._loadListener.bind(self);
  2405.         self._node.addEventListener(Howler._canPlayEvent, self._loadFn, false);
  2406.  
  2407.         // Listen for the 'ended' event on the sound to account for edge-case where
  2408.         // a finite sound has a duration of Infinity.
  2409.         self._endFn = self._endListener.bind(self);
  2410.         self._node.addEventListener('ended', self._endFn, false);
  2411.  
  2412.         // Setup the new audio node.
  2413.         self._node.src = parent._src;
  2414.         self._node.preload = parent._preload === true ? 'auto' : parent._preload;
  2415.         self._node.volume = volume * Howler.volume();
  2416.  
  2417.         // Begin loading the source.
  2418.         self._node.load();
  2419.       }
  2420.  
  2421.       return self;
  2422.     },
  2423.  
  2424.     /**
  2425.      * Reset the parameters of this sound to the original state (for recycle).
  2426.      * @return {Sound}
  2427.      */
  2428.     reset: function() {
  2429.       var self = this;
  2430.       var parent = self._parent;
  2431.  
  2432.       // Reset all of the parameters of this sound.
  2433.       self._muted = parent._muted;
  2434.       self._loop = parent._loop;
  2435.       self._volume = parent._volume;
  2436.       self._rate = parent._rate;
  2437.       self._seek = 0;
  2438.       self._rateSeek = 0;
  2439.       self._paused = true;
  2440.       self._ended = true;
  2441.       self._sprite = '__default';
  2442.  
  2443.       // Generate a new ID so that it isn't confused with the previous sound.
  2444.       self._id = ++Howler._counter;
  2445.  
  2446.       return self;
  2447.     },
  2448.  
  2449.     /**
  2450.      * HTML5 Audio error listener callback.
  2451.      */
  2452.     _errorListener: function() {
  2453.       var self = this;
  2454.  
  2455.       // Fire an error event and pass back the code.
  2456.       self._parent._emit('loaderror', self._id, self._node.error ? self._node.error.code : 0);
  2457.  
  2458.       // Clear the event listener.
  2459.       self._node.removeEventListener('error', self._errorFn, false);
  2460.     },
  2461.  
  2462.     /**
  2463.      * HTML5 Audio canplaythrough listener callback.
  2464.      */
  2465.     _loadListener: function() {
  2466.       var self = this;
  2467.       var parent = self._parent;
  2468.  
  2469.       // Round up the duration to account for the lower precision in HTML5 Audio.
  2470.       parent._duration = Math.ceil(self._node.duration * 10) / 10;
  2471.  
  2472.       // Setup a sprite if none is defined.
  2473.       if (Object.keys(parent._sprite).length === 0) {
  2474.         parent._sprite = {__default: [0, parent._duration * 1000]};
  2475.       }
  2476.  
  2477.       if (parent._state !== 'loaded') {
  2478.         parent._state = 'loaded';
  2479.         parent._emit('load');
  2480.         parent._loadQueue();
  2481.       }
  2482.  
  2483.       // Clear the event listener.
  2484.       self._node.removeEventListener(Howler._canPlayEvent, self._loadFn, false);
  2485.     },
  2486.  
  2487.     /**
  2488.      * HTML5 Audio ended listener callback.
  2489.      */
  2490.     _endListener: function() {
  2491.       var self = this;
  2492.       var parent = self._parent;
  2493.  
  2494.       // Only handle the `ended`` event if the duration is Infinity.
  2495.       if (parent._duration === Infinity) {
  2496.         // Update the parent duration to match the real audio duration.
  2497.         // Round up the duration to account for the lower precision in HTML5 Audio.
  2498.         parent._duration = Math.ceil(self._node.duration * 10) / 10;
  2499.  
  2500.         // Update the sprite that corresponds to the real duration.
  2501.         if (parent._sprite.__default[1] === Infinity) {
  2502.           parent._sprite.__default[1] = parent._duration * 1000;
  2503.         }
  2504.  
  2505.         // Run the regular ended method.
  2506.         parent._ended(self);
  2507.       }
  2508.  
  2509.       // Clear the event listener since the duration is now correct.
  2510.       self._node.removeEventListener('ended', self._endFn, false);
  2511.     }
  2512.   };
  2513.  
  2514.   /** Helper Methods **/
  2515.   /***************************************************************************/
  2516.  
  2517.   var cache = {};
  2518.  
  2519.   /**
  2520.    * Buffer a sound from URL, Data URI or cache and decode to audio source (Web Audio API).
  2521.    * @param  {Howl} self
  2522.    */
  2523.   var loadBuffer = function(self) {
  2524.     var url = self._src;
  2525.  
  2526.     if (self._buffer instanceof ArrayBuffer && !self._html5) {
  2527.       decodeAudioData(self._buffer, self);
  2528.       return;
  2529.     }
  2530.  
  2531.     // Check if the buffer has already been cached and use it instead.
  2532.     if (cache[url]) {
  2533.       // Set the duration from the cache.
  2534.       self._duration = cache[url].duration;
  2535.  
  2536.       // Load the sound into this Howl.
  2537.       loadSound(self);
  2538.  
  2539.       return;
  2540.     }
  2541.  
  2542.     if (/^data:[^;]+;base64,/.test(url)) {
  2543.       // Decode the base64 data URI without XHR, since some browsers don't support it.
  2544.       var data = atob(url.split(',')[1]);
  2545.       var dataView = new Uint8Array(data.length);
  2546.       for (var i=0; i<data.length; ++i) {
  2547.         dataView[i] = data.charCodeAt(i);
  2548.       }
  2549.  
  2550.       decodeAudioData(dataView.buffer, self);
  2551.     } else {
  2552.       // Load the buffer from the URL.
  2553.       var xhr = new XMLHttpRequest();
  2554.       xhr.open(self._xhr.method, url, true);
  2555.       xhr.withCredentials = self._xhr.withCredentials;
  2556.       xhr.responseType = 'arraybuffer';
  2557.  
  2558.       // Apply any custom headers to the request.
  2559.       if (self._xhr.headers) {
  2560.         Object.keys(self._xhr.headers).forEach(function(key) {
  2561.           xhr.setRequestHeader(key, self._xhr.headers[key]);
  2562.         });
  2563.       }
  2564.  
  2565.       xhr.onload = function() {
  2566.         // Make sure we get a successful response back.
  2567.         var code = (xhr.status + '')[0];
  2568.         if (code !== '0' && code !== '2' && code !== '3') {
  2569.           self._emit('loaderror', null, 'Failed loading audio file with status: ' + xhr.status + '.');
  2570.           return;
  2571.         }
  2572.  
  2573.         decodeAudioData(xhr.response, self);
  2574.       };
  2575.       xhr.onerror = function() {
  2576.         // If there is an error, switch to HTML5 Audio.
  2577.         if (self._webAudio) {
  2578.           self._html5 = true;
  2579.           self._webAudio = false;
  2580.           self._sounds = [];
  2581.           delete cache[url];
  2582.           self.load();
  2583.         }
  2584.       };
  2585.       safeXhrSend(xhr);
  2586.     }
  2587.   };
  2588.  
  2589.   /**
  2590.    * Send the XHR request wrapped in a try/catch.
  2591.    * @param  {Object} xhr XHR to send.
  2592.    */
  2593.   var safeXhrSend = function(xhr) {
  2594.     try {
  2595.       xhr.send();
  2596.     } catch (e) {
  2597.       xhr.onerror();
  2598.     }
  2599.   };
  2600.  
  2601.   /**
  2602.    * Decode audio data from an array buffer.
  2603.    * @param  {ArrayBuffer} arraybuffer The audio data.
  2604.    * @param  {Howl}        self
  2605.    */
  2606.   var decodeAudioData = function(arraybuffer, self) {
  2607.     // Fire a load error if something broke.
  2608.     var error = function() {
  2609.       self._emit('loaderror', null, 'Decoding audio data failed.');
  2610.     };
  2611.  
  2612.     // Load the sound on success.
  2613.     var success = function(buffer) {
  2614.       if (buffer && self._sounds.length > 0) {
  2615.         cache[self._src] = buffer;
  2616.         loadSound(self, buffer);
  2617.       } else {
  2618.         error();
  2619.       }
  2620.     };
  2621.  
  2622.     // Decode the buffer into an audio source.
  2623.     if (typeof Promise !== 'undefined' && Howler.ctx.decodeAudioData.length === 1) {
  2624.       Howler.ctx.decodeAudioData(arraybuffer.slice(0)).then(success).catch(error);
  2625.     } else {
  2626.       Howler.ctx.decodeAudioData(arraybuffer.slice(0), success, error);
  2627.     }
  2628.   }
  2629.  
  2630.   /**
  2631.    * Sound is now loaded, so finish setting everything up and fire the loaded event.
  2632.    * @param  {Howl} self
  2633.    * @param  {Object} buffer The decoded buffer sound source.
  2634.    */
  2635.   var loadSound = function(self, buffer) {
  2636.     // Set the duration.
  2637.     if (buffer && !self._duration) {
  2638.       self._duration = buffer.duration;
  2639.     }
  2640.  
  2641.     // Setup a sprite if none is defined.
  2642.     if (Object.keys(self._sprite).length === 0) {
  2643.       self._sprite = {__default: [0, self._duration * 1000]};
  2644.     }
  2645.  
  2646.     // Fire the loaded event.
  2647.     if (self._state !== 'loaded') {
  2648.       self._decoded = buffer;
  2649.       self._state = 'loaded';
  2650.       self._emit('load');
  2651.       self._loadQueue();
  2652.     }
  2653.   };
  2654.  
  2655.   /**
  2656.    * Setup the audio context when available, or switch to HTML5 Audio mode.
  2657.    */
  2658.   var setupAudioContext = function() {
  2659.     // If we have already detected that Web Audio isn't supported, don't run this step again.
  2660.     if (!Howler.usingWebAudio) {
  2661.       return;
  2662.     }
  2663.  
  2664.     // Check if we are using Web Audio and setup the AudioContext if we are.
  2665.     try {
  2666.       if (typeof AudioContext !== 'undefined') {
  2667.         Howler.ctx = new AudioContext();
  2668.       } else if (typeof webkitAudioContext !== 'undefined') {
  2669.         Howler.ctx = new webkitAudioContext();
  2670.       } else {
  2671.         Howler.usingWebAudio = false;
  2672.       }
  2673.     } catch(e) {
  2674.       Howler.usingWebAudio = false;
  2675.     }
  2676.  
  2677.     // If the audio context creation still failed, set using web audio to false.
  2678.     if (!Howler.ctx) {
  2679.       Howler.usingWebAudio = false;
  2680.     }
  2681.  
  2682.     // Check if a webview is being used on iOS8 or earlier (rather than the browser).
  2683.     // If it is, disable Web Audio as it causes crashing.
  2684.     var iOS = (/iP(hone|od|ad)/.test(Howler._navigator && Howler._navigator.platform));
  2685.     var appVersion = Howler._navigator && Howler._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/);
  2686.     var version = appVersion ? parseInt(appVersion[1], 10) : null;
  2687.     if (iOS && version && version < 9) {
  2688.       var safari = /safari/.test(Howler._navigator && Howler._navigator.userAgent.toLowerCase());
  2689.       if (Howler._navigator && !safari) {
  2690.         Howler.usingWebAudio = false;
  2691.       }
  2692.     }
  2693.  
  2694.     // Create and expose the master GainNode when using Web Audio (useful for plugins or advanced usage).
  2695.     if (Howler.usingWebAudio) {
  2696.       Howler.masterGain = (typeof Howler.ctx.createGain === 'undefined') ? Howler.ctx.createGainNode() : Howler.ctx.createGain();
  2697.       Howler.masterGain.gain.setValueAtTime(Howler._muted ? 0 : Howler._volume, Howler.ctx.currentTime);
  2698.       Howler.masterGain.connect(Howler.ctx.destination);
  2699.     }
  2700.  
  2701.     // Re-run the setup on Howler.
  2702.     Howler._setup();
  2703.   };
  2704.  
  2705.   // Add support for AMD (Asynchronous Module Definition) libraries such as require.js.
  2706.   if (typeof define === 'function' && define.amd) {
  2707.     define([], function() {
  2708.       return {
  2709.         Howler: Howler,
  2710.         Howl: Howl
  2711.       };
  2712.     });
  2713.   }
  2714.  
  2715.   // Add support for CommonJS libraries such as browserify.
  2716.   if (typeof exports !== 'undefined') {
  2717.     exports.Howler = Howler;
  2718.     exports.Howl = Howl;
  2719.   }
  2720.  
  2721.   // Add to global in Node.js (for testing, etc).
  2722.   if (typeof global !== 'undefined') {
  2723.     global.HowlerGlobal = HowlerGlobal;
  2724.     global.Howler = Howler;
  2725.     global.Howl = Howl;
  2726.     global.Sound = Sound;
  2727.   } else if (typeof window !== 'undefined') {  // Define globally in case AMD is not available or unused.
  2728.     window.HowlerGlobal = HowlerGlobal;
  2729.     window.Howler = Howler;
  2730.     window.Howl = Howl;
  2731.     window.Sound = Sound;
  2732.   }
  2733. })();
  2734.  
  2735.  
  2736. /*!
  2737.  *  Spatial Plugin - Adds support for stereo and 3D audio where Web Audio is supported.
  2738.  *  
  2739.  *  howler.js v2.2.3
  2740.  *  howlerjs.com
  2741.  *
  2742.  *  (c) 2013-2020, James Simpson of GoldFire Studios
  2743.  *  goldfirestudios.com
  2744.  *
  2745.  *  MIT License
  2746.  */
  2747.  
  2748. (function() {
  2749.  
  2750.   'use strict';
  2751.  
  2752.   // Setup default properties.
  2753.   HowlerGlobal.prototype._pos = [0, 0, 0];
  2754.   HowlerGlobal.prototype._orientation = [0, 0, -1, 0, 1, 0];
  2755.  
  2756.   /** Global Methods **/
  2757.   /***************************************************************************/
  2758.  
  2759.   /**
  2760.    * Helper method to update the stereo panning position of all current Howls.
  2761.    * Future Howls will not use this value unless explicitly set.
  2762.    * @param  {Number} pan A value of -1.0 is all the way left and 1.0 is all the way right.
  2763.    * @return {Howler/Number}     Self or current stereo panning value.
  2764.    */
  2765.   HowlerGlobal.prototype.stereo = function(pan) {
  2766.     var self = this;
  2767.  
  2768.     // Stop right here if not using Web Audio.
  2769.     if (!self.ctx || !self.ctx.listener) {
  2770.       return self;
  2771.     }
  2772.  
  2773.     // Loop through all Howls and update their stereo panning.
  2774.     for (var i=self._howls.length-1; i>=0; i--) {
  2775.       self._howls[i].stereo(pan);
  2776.     }
  2777.  
  2778.     return self;
  2779.   };
  2780.  
  2781.   /**
  2782.    * Get/set the position of the listener in 3D cartesian space. Sounds using
  2783.    * 3D position will be relative to the listener's position.
  2784.    * @param  {Number} x The x-position of the listener.
  2785.    * @param  {Number} y The y-position of the listener.
  2786.    * @param  {Number} z The z-position of the listener.
  2787.    * @return {Howler/Array}   Self or current listener position.
  2788.    */
  2789.   HowlerGlobal.prototype.pos = function(x, y, z) {
  2790.     var self = this;
  2791.  
  2792.     // Stop right here if not using Web Audio.
  2793.     if (!self.ctx || !self.ctx.listener) {
  2794.       return self;
  2795.     }
  2796.  
  2797.     // Set the defaults for optional 'y' & 'z'.
  2798.     y = (typeof y !== 'number') ? self._pos[1] : y;
  2799.     z = (typeof z !== 'number') ? self._pos[2] : z;
  2800.  
  2801.     if (typeof x === 'number') {
  2802.       self._pos = [x, y, z];
  2803.  
  2804.       if (typeof self.ctx.listener.positionX !== 'undefined') {
  2805.         self.ctx.listener.positionX.setTargetAtTime(self._pos[0], Howler.ctx.currentTime, 0.1);
  2806.         self.ctx.listener.positionY.setTargetAtTime(self._pos[1], Howler.ctx.currentTime, 0.1);
  2807.         self.ctx.listener.positionZ.setTargetAtTime(self._pos[2], Howler.ctx.currentTime, 0.1);
  2808.       } else {
  2809.         self.ctx.listener.setPosition(self._pos[0], self._pos[1], self._pos[2]);
  2810.       }
  2811.     } else {
  2812.       return self._pos;
  2813.     }
  2814.  
  2815.     return self;
  2816.   };
  2817.  
  2818.   /**
  2819.    * Get/set the direction the listener is pointing in the 3D cartesian space.
  2820.    * A front and up vector must be provided. The front is the direction the
  2821.    * face of the listener is pointing, and up is the direction the top of the
  2822.    * listener is pointing. Thus, these values are expected to be at right angles
  2823.    * from each other.
  2824.    * @param  {Number} x   The x-orientation of the listener.
  2825.    * @param  {Number} y   The y-orientation of the listener.
  2826.    * @param  {Number} z   The z-orientation of the listener.
  2827.    * @param  {Number} xUp The x-orientation of the top of the listener.
  2828.    * @param  {Number} yUp The y-orientation of the top of the listener.
  2829.    * @param  {Number} zUp The z-orientation of the top of the listener.
  2830.    * @return {Howler/Array}     Returns self or the current orientation vectors.
  2831.    */
  2832.   HowlerGlobal.prototype.orientation = function(x, y, z, xUp, yUp, zUp) {
  2833.     var self = this;
  2834.  
  2835.     // Stop right here if not using Web Audio.
  2836.     if (!self.ctx || !self.ctx.listener) {
  2837.       return self;
  2838.     }
  2839.  
  2840.     // Set the defaults for optional 'y' & 'z'.
  2841.     var or = self._orientation;
  2842.     y = (typeof y !== 'number') ? or[1] : y;
  2843.     z = (typeof z !== 'number') ? or[2] : z;
  2844.     xUp = (typeof xUp !== 'number') ? or[3] : xUp;
  2845.     yUp = (typeof yUp !== 'number') ? or[4] : yUp;
  2846.     zUp = (typeof zUp !== 'number') ? or[5] : zUp;
  2847.  
  2848.     if (typeof x === 'number') {
  2849.       self._orientation = [x, y, z, xUp, yUp, zUp];
  2850.  
  2851.       if (typeof self.ctx.listener.forwardX !== 'undefined') {
  2852.         self.ctx.listener.forwardX.setTargetAtTime(x, Howler.ctx.currentTime, 0.1);
  2853.         self.ctx.listener.forwardY.setTargetAtTime(y, Howler.ctx.currentTime, 0.1);
  2854.         self.ctx.listener.forwardZ.setTargetAtTime(z, Howler.ctx.currentTime, 0.1);
  2855.         self.ctx.listener.upX.setTargetAtTime(xUp, Howler.ctx.currentTime, 0.1);
  2856.         self.ctx.listener.upY.setTargetAtTime(yUp, Howler.ctx.currentTime, 0.1);
  2857.         self.ctx.listener.upZ.setTargetAtTime(zUp, Howler.ctx.currentTime, 0.1);
  2858.       } else {
  2859.         self.ctx.listener.setOrientation(x, y, z, xUp, yUp, zUp);
  2860.       }
  2861.     } else {
  2862.       return or;
  2863.     }
  2864.  
  2865.     return self;
  2866.   };
  2867.  
  2868.   /** Group Methods **/
  2869.   /***************************************************************************/
  2870.  
  2871.   /**
  2872.    * Add new properties to the core init.
  2873.    * @param  {Function} _super Core init method.
  2874.    * @return {Howl}
  2875.    */
  2876.   Howl.prototype.init = (function(_super) {
  2877.     return function(o) {
  2878.       var self = this;
  2879.  
  2880.       // Setup user-defined default properties.
  2881.       self._orientation = o.orientation || [1, 0, 0];
  2882.       self._stereo = o.stereo || null;
  2883.       self._pos = o.pos || null;
  2884.       self._pannerAttr = {
  2885.         coneInnerAngle: typeof o.coneInnerAngle !== 'undefined' ? o.coneInnerAngle : 360,
  2886.         coneOuterAngle: typeof o.coneOuterAngle !== 'undefined' ? o.coneOuterAngle : 360,
  2887.         coneOuterGain: typeof o.coneOuterGain !== 'undefined' ? o.coneOuterGain : 0,
  2888.         distanceModel: typeof o.distanceModel !== 'undefined' ? o.distanceModel : 'inverse',
  2889.         maxDistance: typeof o.maxDistance !== 'undefined' ? o.maxDistance : 10000,
  2890.         panningModel: typeof o.panningModel !== 'undefined' ? o.panningModel : 'HRTF',
  2891.         refDistance: typeof o.refDistance !== 'undefined' ? o.refDistance : 1,
  2892.         rolloffFactor: typeof o.rolloffFactor !== 'undefined' ? o.rolloffFactor : 1
  2893.       };
  2894.  
  2895.       // Setup event listeners.
  2896.       self._onstereo = o.onstereo ? [{fn: o.onstereo}] : [];
  2897.       self._onpos = o.onpos ? [{fn: o.onpos}] : [];
  2898.       self._onorientation = o.onorientation ? [{fn: o.onorientation}] : [];
  2899.  
  2900.       // Complete initilization with howler.js core's init function.
  2901.       return _super.call(this, o);
  2902.     };
  2903.   })(Howl.prototype.init);
  2904.  
  2905.   /**
  2906.    * Get/set the stereo panning of the audio source for this sound or all in the group.
  2907.    * @param  {Number} pan  A value of -1.0 is all the way left and 1.0 is all the way right.
  2908.    * @param  {Number} id (optional) The sound ID. If none is passed, all in group will be updated.
  2909.    * @return {Howl/Number}    Returns self or the current stereo panning value.
  2910.    */
  2911.   Howl.prototype.stereo = function(pan, id) {
  2912.     var self = this;
  2913.  
  2914.     // Stop right here if not using Web Audio.
  2915.     if (!self._webAudio) {
  2916.       return self;
  2917.     }
  2918.  
  2919.     // If the sound hasn't loaded, add it to the load queue to change stereo pan when capable.
  2920.     if (self._state !== 'loaded') {
  2921.       self._queue.push({
  2922.         event: 'stereo',
  2923.         action: function() {
  2924.           self.stereo(pan, id);
  2925.         }
  2926.       });
  2927.  
  2928.       return self;
  2929.     }
  2930.  
  2931.     // Check for PannerStereoNode support and fallback to PannerNode if it doesn't exist.
  2932.     var pannerType = (typeof Howler.ctx.createStereoPanner === 'undefined') ? 'spatial' : 'stereo';
  2933.  
  2934.     // Setup the group's stereo panning if no ID is passed.
  2935.     if (typeof id === 'undefined') {
  2936.       // Return the group's stereo panning if no parameters are passed.
  2937.       if (typeof pan === 'number') {
  2938.         self._stereo = pan;
  2939.         self._pos = [pan, 0, 0];
  2940.       } else {
  2941.         return self._stereo;
  2942.       }
  2943.     }
  2944.  
  2945.     // Change the streo panning of one or all sounds in group.
  2946.     var ids = self._getSoundIds(id);
  2947.     for (var i=0; i<ids.length; i++) {
  2948.       // Get the sound.
  2949.       var sound = self._soundById(ids[i]);
  2950.  
  2951.       if (sound) {
  2952.         if (typeof pan === 'number') {
  2953.           sound._stereo = pan;
  2954.           sound._pos = [pan, 0, 0];
  2955.  
  2956.           if (sound._node) {
  2957.             // If we are falling back, make sure the panningModel is equalpower.
  2958.             sound._pannerAttr.panningModel = 'equalpower';
  2959.  
  2960.             // Check if there is a panner setup and create a new one if not.
  2961.             if (!sound._panner || !sound._panner.pan) {
  2962.               setupPanner(sound, pannerType);
  2963.             }
  2964.  
  2965.             if (pannerType === 'spatial') {
  2966.               if (typeof sound._panner.positionX !== 'undefined') {
  2967.                 sound._panner.positionX.setValueAtTime(pan, Howler.ctx.currentTime);
  2968.                 sound._panner.positionY.setValueAtTime(0, Howler.ctx.currentTime);
  2969.                 sound._panner.positionZ.setValueAtTime(0, Howler.ctx.currentTime);
  2970.               } else {
  2971.                 sound._panner.setPosition(pan, 0, 0);
  2972.               }
  2973.             } else {
  2974.               sound._panner.pan.setValueAtTime(pan, Howler.ctx.currentTime);
  2975.             }
  2976.           }
  2977.  
  2978.           self._emit('stereo', sound._id);
  2979.         } else {
  2980.           return sound._stereo;
  2981.         }
  2982.       }
  2983.     }
  2984.  
  2985.     return self;
  2986.   };
  2987.  
  2988.   /**
  2989.    * Get/set the 3D spatial position of the audio source for this sound or group relative to the global listener.
  2990.    * @param  {Number} x  The x-position of the audio source.
  2991.    * @param  {Number} y  The y-position of the audio source.
  2992.    * @param  {Number} z  The z-position of the audio source.
  2993.    * @param  {Number} id (optional) The sound ID. If none is passed, all in group will be updated.
  2994.    * @return {Howl/Array}    Returns self or the current 3D spatial position: [x, y, z].
  2995.    */
  2996.   Howl.prototype.pos = function(x, y, z, id) {
  2997.     var self = this;
  2998.  
  2999.     // Stop right here if not using Web Audio.
  3000.     if (!self._webAudio) {
  3001.       return self;
  3002.     }
  3003.  
  3004.     // If the sound hasn't loaded, add it to the load queue to change position when capable.
  3005.     if (self._state !== 'loaded') {
  3006.       self._queue.push({
  3007.         event: 'pos',
  3008.         action: function() {
  3009.           self.pos(x, y, z, id);
  3010.         }
  3011.       });
  3012.  
  3013.       return self;
  3014.     }
  3015.  
  3016.     // Set the defaults for optional 'y' & 'z'.
  3017.     y = (typeof y !== 'number') ? 0 : y;
  3018.     z = (typeof z !== 'number') ? -0.5 : z;
  3019.  
  3020.     // Setup the group's spatial position if no ID is passed.
  3021.     if (typeof id === 'undefined') {
  3022.       // Return the group's spatial position if no parameters are passed.
  3023.       if (typeof x === 'number') {
  3024.         self._pos = [x, y, z];
  3025.       } else {
  3026.         return self._pos;
  3027.       }
  3028.     }
  3029.  
  3030.     // Change the spatial position of one or all sounds in group.
  3031.     var ids = self._getSoundIds(id);
  3032.     for (var i=0; i<ids.length; i++) {
  3033.       // Get the sound.
  3034.       var sound = self._soundById(ids[i]);
  3035.  
  3036.       if (sound) {
  3037.         if (typeof x === 'number') {
  3038.           sound._pos = [x, y, z];
  3039.  
  3040.           if (sound._node) {
  3041.             // Check if there is a panner setup and create a new one if not.
  3042.             if (!sound._panner || sound._panner.pan) {
  3043.               setupPanner(sound, 'spatial');
  3044.             }
  3045.  
  3046.             if (typeof sound._panner.positionX !== 'undefined') {
  3047.               sound._panner.positionX.setValueAtTime(x, Howler.ctx.currentTime);
  3048.               sound._panner.positionY.setValueAtTime(y, Howler.ctx.currentTime);
  3049.               sound._panner.positionZ.setValueAtTime(z, Howler.ctx.currentTime);
  3050.             } else {
  3051.               sound._panner.setPosition(x, y, z);
  3052.             }
  3053.           }
  3054.  
  3055.           self._emit('pos', sound._id);
  3056.         } else {
  3057.           return sound._pos;
  3058.         }
  3059.       }
  3060.     }
  3061.  
  3062.     return self;
  3063.   };
  3064.  
  3065.   /**
  3066.    * Get/set the direction the audio source is pointing in the 3D cartesian coordinate
  3067.    * space. Depending on how direction the sound is, based on the `cone` attributes,
  3068.    * a sound pointing away from the listener can be quiet or silent.
  3069.    * @param  {Number} x  The x-orientation of the source.
  3070.    * @param  {Number} y  The y-orientation of the source.
  3071.    * @param  {Number} z  The z-orientation of the source.
  3072.    * @param  {Number} id (optional) The sound ID. If none is passed, all in group will be updated.
  3073.    * @return {Howl/Array}    Returns self or the current 3D spatial orientation: [x, y, z].
  3074.    */
  3075.   Howl.prototype.orientation = function(x, y, z, id) {
  3076.     var self = this;
  3077.  
  3078.     // Stop right here if not using Web Audio.
  3079.     if (!self._webAudio) {
  3080.       return self;
  3081.     }
  3082.  
  3083.     // If the sound hasn't loaded, add it to the load queue to change orientation when capable.
  3084.     if (self._state !== 'loaded') {
  3085.       self._queue.push({
  3086.         event: 'orientation',
  3087.         action: function() {
  3088.           self.orientation(x, y, z, id);
  3089.         }
  3090.       });
  3091.  
  3092.       return self;
  3093.     }
  3094.  
  3095.     // Set the defaults for optional 'y' & 'z'.
  3096.     y = (typeof y !== 'number') ? self._orientation[1] : y;
  3097.     z = (typeof z !== 'number') ? self._orientation[2] : z;
  3098.  
  3099.     // Setup the group's spatial orientation if no ID is passed.
  3100.     if (typeof id === 'undefined') {
  3101.       // Return the group's spatial orientation if no parameters are passed.
  3102.       if (typeof x === 'number') {
  3103.         self._orientation = [x, y, z];
  3104.       } else {
  3105.         return self._orientation;
  3106.       }
  3107.     }
  3108.  
  3109.     // Change the spatial orientation of one or all sounds in group.
  3110.     var ids = self._getSoundIds(id);
  3111.     for (var i=0; i<ids.length; i++) {
  3112.       // Get the sound.
  3113.       var sound = self._soundById(ids[i]);
  3114.  
  3115.       if (sound) {
  3116.         if (typeof x === 'number') {
  3117.           sound._orientation = [x, y, z];
  3118.  
  3119.           if (sound._node) {
  3120.             // Check if there is a panner setup and create a new one if not.
  3121.             if (!sound._panner) {
  3122.               // Make sure we have a position to setup the node with.
  3123.               if (!sound._pos) {
  3124.                 sound._pos = self._pos || [0, 0, -0.5];
  3125.               }
  3126.  
  3127.               setupPanner(sound, 'spatial');
  3128.             }
  3129.  
  3130.             if (typeof sound._panner.orientationX !== 'undefined') {
  3131.               sound._panner.orientationX.setValueAtTime(x, Howler.ctx.currentTime);
  3132.               sound._panner.orientationY.setValueAtTime(y, Howler.ctx.currentTime);
  3133.               sound._panner.orientationZ.setValueAtTime(z, Howler.ctx.currentTime);
  3134.             } else {
  3135.               sound._panner.setOrientation(x, y, z);
  3136.             }
  3137.           }
  3138.  
  3139.           self._emit('orientation', sound._id);
  3140.         } else {
  3141.           return sound._orientation;
  3142.         }
  3143.       }
  3144.     }
  3145.  
  3146.     return self;
  3147.   };
  3148.  
  3149.   /**
  3150.    * Get/set the panner node's attributes for a sound or group of sounds.
  3151.    * This method can optionall take 0, 1 or 2 arguments.
  3152.    *   pannerAttr() -> Returns the group's values.
  3153.    *   pannerAttr(id) -> Returns the sound id's values.
  3154.    *   pannerAttr(o) -> Set's the values of all sounds in this Howl group.
  3155.    *   pannerAttr(o, id) -> Set's the values of passed sound id.
  3156.    *
  3157.    *   Attributes:
  3158.    *     coneInnerAngle - (360 by default) A parameter for directional audio sources, this is an angle, in degrees,
  3159.    *                      inside of which there will be no volume reduction.
  3160.    *     coneOuterAngle - (360 by default) A parameter for directional audio sources, this is an angle, in degrees,
  3161.    *                      outside of which the volume will be reduced to a constant value of `coneOuterGain`.
  3162.    *     coneOuterGain - (0 by default) A parameter for directional audio sources, this is the gain outside of the
  3163.    *                     `coneOuterAngle`. It is a linear value in the range `[0, 1]`.
  3164.    *     distanceModel - ('inverse' by default) Determines algorithm used to reduce volume as audio moves away from
  3165.    *                     listener. Can be `linear`, `inverse` or `exponential.
  3166.    *     maxDistance - (10000 by default) The maximum distance between source and listener, after which the volume
  3167.    *                   will not be reduced any further.
  3168.    *     refDistance - (1 by default) A reference distance for reducing volume as source moves further from the listener.
  3169.    *                   This is simply a variable of the distance model and has a different effect depending on which model
  3170.    *                   is used and the scale of your coordinates. Generally, volume will be equal to 1 at this distance.
  3171.    *     rolloffFactor - (1 by default) How quickly the volume reduces as source moves from listener. This is simply a
  3172.    *                     variable of the distance model and can be in the range of `[0, 1]` with `linear` and `[0, ∞]`
  3173.    *                     with `inverse` and `exponential`.
  3174.    *     panningModel - ('HRTF' by default) Determines which spatialization algorithm is used to position audio.
  3175.    *                     Can be `HRTF` or `equalpower`.
  3176.    *
  3177.    * @return {Howl/Object} Returns self or current panner attributes.
  3178.    */
  3179.   Howl.prototype.pannerAttr = function() {
  3180.     var self = this;
  3181.     var args = arguments;
  3182.     var o, id, sound;
  3183.  
  3184.     // Stop right here if not using Web Audio.
  3185.     if (!self._webAudio) {
  3186.       return self;
  3187.     }
  3188.  
  3189.     // Determine the values based on arguments.
  3190.     if (args.length === 0) {
  3191.       // Return the group's panner attribute values.
  3192.       return self._pannerAttr;
  3193.     } else if (args.length === 1) {
  3194.       if (typeof args[0] === 'object') {
  3195.         o = args[0];
  3196.  
  3197.         // Set the grou's panner attribute values.
  3198.         if (typeof id === 'undefined') {
  3199.           if (!o.pannerAttr) {
  3200.             o.pannerAttr = {
  3201.               coneInnerAngle: o.coneInnerAngle,
  3202.               coneOuterAngle: o.coneOuterAngle,
  3203.               coneOuterGain: o.coneOuterGain,
  3204.               distanceModel: o.distanceModel,
  3205.               maxDistance: o.maxDistance,
  3206.               refDistance: o.refDistance,
  3207.               rolloffFactor: o.rolloffFactor,
  3208.               panningModel: o.panningModel
  3209.             };
  3210.           }
  3211.  
  3212.           self._pannerAttr = {
  3213.             coneInnerAngle: typeof o.pannerAttr.coneInnerAngle !== 'undefined' ? o.pannerAttr.coneInnerAngle : self._coneInnerAngle,
  3214.             coneOuterAngle: typeof o.pannerAttr.coneOuterAngle !== 'undefined' ? o.pannerAttr.coneOuterAngle : self._coneOuterAngle,
  3215.             coneOuterGain: typeof o.pannerAttr.coneOuterGain !== 'undefined' ? o.pannerAttr.coneOuterGain : self._coneOuterGain,
  3216.             distanceModel: typeof o.pannerAttr.distanceModel !== 'undefined' ? o.pannerAttr.distanceModel : self._distanceModel,
  3217.             maxDistance: typeof o.pannerAttr.maxDistance !== 'undefined' ? o.pannerAttr.maxDistance : self._maxDistance,
  3218.             refDistance: typeof o.pannerAttr.refDistance !== 'undefined' ? o.pannerAttr.refDistance : self._refDistance,
  3219.             rolloffFactor: typeof o.pannerAttr.rolloffFactor !== 'undefined' ? o.pannerAttr.rolloffFactor : self._rolloffFactor,
  3220.             panningModel: typeof o.pannerAttr.panningModel !== 'undefined' ? o.pannerAttr.panningModel : self._panningModel
  3221.           };
  3222.         }
  3223.       } else {
  3224.         // Return this sound's panner attribute values.
  3225.         sound = self._soundById(parseInt(args[0], 10));
  3226.         return sound ? sound._pannerAttr : self._pannerAttr;
  3227.       }
  3228.     } else if (args.length === 2) {
  3229.       o = args[0];
  3230.       id = parseInt(args[1], 10);
  3231.     }
  3232.  
  3233.     // Update the values of the specified sounds.
  3234.     var ids = self._getSoundIds(id);
  3235.     for (var i=0; i<ids.length; i++) {
  3236.       sound = self._soundById(ids[i]);
  3237.  
  3238.       if (sound) {
  3239.         // Merge the new values into the sound.
  3240.         var pa = sound._pannerAttr;
  3241.         pa = {
  3242.           coneInnerAngle: typeof o.coneInnerAngle !== 'undefined' ? o.coneInnerAngle : pa.coneInnerAngle,
  3243.           coneOuterAngle: typeof o.coneOuterAngle !== 'undefined' ? o.coneOuterAngle : pa.coneOuterAngle,
  3244.           coneOuterGain: typeof o.coneOuterGain !== 'undefined' ? o.coneOuterGain : pa.coneOuterGain,
  3245.           distanceModel: typeof o.distanceModel !== 'undefined' ? o.distanceModel : pa.distanceModel,
  3246.           maxDistance: typeof o.maxDistance !== 'undefined' ? o.maxDistance : pa.maxDistance,
  3247.           refDistance: typeof o.refDistance !== 'undefined' ? o.refDistance : pa.refDistance,
  3248.           rolloffFactor: typeof o.rolloffFactor !== 'undefined' ? o.rolloffFactor : pa.rolloffFactor,
  3249.           panningModel: typeof o.panningModel !== 'undefined' ? o.panningModel : pa.panningModel
  3250.         };
  3251.  
  3252.         // Update the panner values or create a new panner if none exists.
  3253.         var panner = sound._panner;
  3254.         if (panner) {
  3255.           panner.coneInnerAngle = pa.coneInnerAngle;
  3256.           panner.coneOuterAngle = pa.coneOuterAngle;
  3257.           panner.coneOuterGain = pa.coneOuterGain;
  3258.           panner.distanceModel = pa.distanceModel;
  3259.           panner.maxDistance = pa.maxDistance;
  3260.           panner.refDistance = pa.refDistance;
  3261.           panner.rolloffFactor = pa.rolloffFactor;
  3262.           panner.panningModel = pa.panningModel;
  3263.         } else {
  3264.           // Make sure we have a position to setup the node with.
  3265.           if (!sound._pos) {
  3266.             sound._pos = self._pos || [0, 0, -0.5];
  3267.           }
  3268.  
  3269.           // Create a new panner node.
  3270.           setupPanner(sound, 'spatial');
  3271.         }
  3272.       }
  3273.     }
  3274.  
  3275.     return self;
  3276.   };
  3277.  
  3278.   /** Single Sound Methods **/
  3279.   /***************************************************************************/
  3280.  
  3281.   /**
  3282.    * Add new properties to the core Sound init.
  3283.    * @param  {Function} _super Core Sound init method.
  3284.    * @return {Sound}
  3285.    */
  3286.   Sound.prototype.init = (function(_super) {
  3287.     return function() {
  3288.       var self = this;
  3289.       var parent = self._parent;
  3290.  
  3291.       // Setup user-defined default properties.
  3292.       self._orientation = parent._orientation;
  3293.       self._stereo = parent._stereo;
  3294.       self._pos = parent._pos;
  3295.       self._pannerAttr = parent._pannerAttr;
  3296.  
  3297.       // Complete initilization with howler.js core Sound's init function.
  3298.       _super.call(this);
  3299.  
  3300.       // If a stereo or position was specified, set it up.
  3301.       if (self._stereo) {
  3302.         parent.stereo(self._stereo);
  3303.       } else if (self._pos) {
  3304.         parent.pos(self._pos[0], self._pos[1], self._pos[2], self._id);
  3305.       }
  3306.     };
  3307.   })(Sound.prototype.init);
  3308.  
  3309.   /**
  3310.    * Override the Sound.reset method to clean up properties from the spatial plugin.
  3311.    * @param  {Function} _super Sound reset method.
  3312.    * @return {Sound}
  3313.    */
  3314.   Sound.prototype.reset = (function(_super) {
  3315.     return function() {
  3316.       var self = this;
  3317.       var parent = self._parent;
  3318.  
  3319.       // Reset all spatial plugin properties on this sound.
  3320.       self._orientation = parent._orientation;
  3321.       self._stereo = parent._stereo;
  3322.       self._pos = parent._pos;
  3323.       self._pannerAttr = parent._pannerAttr;
  3324.  
  3325.       // If a stereo or position was specified, set it up.
  3326.       if (self._stereo) {
  3327.         parent.stereo(self._stereo);
  3328.       } else if (self._pos) {
  3329.         parent.pos(self._pos[0], self._pos[1], self._pos[2], self._id);
  3330.       } else if (self._panner) {
  3331.         // Disconnect the panner.
  3332.         self._panner.disconnect(0);
  3333.         self._panner = undefined;
  3334.         parent._refreshBuffer(self);
  3335.       }
  3336.  
  3337.       // Complete resetting of the sound.
  3338.       return _super.call(this);
  3339.     };
  3340.   })(Sound.prototype.reset);
  3341.  
  3342.   /** Helper Methods **/
  3343.   /***************************************************************************/
  3344.  
  3345.   /**
  3346.    * Create a new panner node and save it on the sound.
  3347.    * @param  {Sound} sound Specific sound to setup panning on.
  3348.    * @param {String} type Type of panner to create: 'stereo' or 'spatial'.
  3349.    */
  3350.   var setupPanner = function(sound, type) {
  3351.     type = type || 'spatial';
  3352.  
  3353.     // Create the new panner node.
  3354.     if (type === 'spatial') {
  3355.       sound._panner = Howler.ctx.createPanner();
  3356.       sound._panner.coneInnerAngle = sound._pannerAttr.coneInnerAngle;
  3357.       sound._panner.coneOuterAngle = sound._pannerAttr.coneOuterAngle;
  3358.       sound._panner.coneOuterGain = sound._pannerAttr.coneOuterGain;
  3359.       sound._panner.distanceModel = sound._pannerAttr.distanceModel;
  3360.       sound._panner.maxDistance = sound._pannerAttr.maxDistance;
  3361.       sound._panner.refDistance = sound._pannerAttr.refDistance;
  3362.       sound._panner.rolloffFactor = sound._pannerAttr.rolloffFactor;
  3363.       sound._panner.panningModel = sound._pannerAttr.panningModel;
  3364.  
  3365.       if (typeof sound._panner.positionX !== 'undefined') {
  3366.         sound._panner.positionX.setValueAtTime(sound._pos[0], Howler.ctx.currentTime);
  3367.         sound._panner.positionY.setValueAtTime(sound._pos[1], Howler.ctx.currentTime);
  3368.         sound._panner.positionZ.setValueAtTime(sound._pos[2], Howler.ctx.currentTime);
  3369.       } else {
  3370.         sound._panner.setPosition(sound._pos[0], sound._pos[1], sound._pos[2]);
  3371.       }
  3372.  
  3373.       if (typeof sound._panner.orientationX !== 'undefined') {
  3374.         sound._panner.orientationX.setValueAtTime(sound._orientation[0], Howler.ctx.currentTime);
  3375.         sound._panner.orientationY.setValueAtTime(sound._orientation[1], Howler.ctx.currentTime);
  3376.         sound._panner.orientationZ.setValueAtTime(sound._orientation[2], Howler.ctx.currentTime);
  3377.       } else {
  3378.         sound._panner.setOrientation(sound._orientation[0], sound._orientation[1], sound._orientation[2]);
  3379.       }
  3380.     } else {
  3381.       sound._panner = Howler.ctx.createStereoPanner();
  3382.       sound._panner.pan.setValueAtTime(sound._stereo, Howler.ctx.currentTime);
  3383.     }
  3384.  
  3385.     sound._panner.connect(sound._node);
  3386.  
  3387.     // Update the connections.
  3388.     if (!sound._paused) {
  3389.       sound._parent.pause(sound._id, true).play(sound._id, true);
  3390.     }
  3391.   };
  3392. })();
  3393.