GeSHi Source Viewer: howler.orig.jsView Raw


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