|
1 | 1 | /*! |
2 | | - * howler.js v2.0.3 |
| 2 | + * howler.js v2.0.4 |
3 | 3 | * howlerjs.com |
4 | 4 | * |
5 | 5 | * (c) 2013-2017, James Simpson of GoldFire Studios |
|
31 | 31 | var self = this || Howler; |
32 | 32 |
|
33 | 33 | // Create a global ID counter. |
34 | | - self._counter = 0; |
| 34 | + self._counter = 1000; |
35 | 35 |
|
36 | 36 | // Internal properties. |
37 | 37 | self._codecs = {}; |
|
303 | 303 | // then check if the audio actually played to determine if |
304 | 304 | // audio has now been unlocked on iOS, Android, etc. |
305 | 305 | var unlock = function() { |
| 306 | + // Fix Android can not play in suspend state. |
| 307 | + Howler._autoResume(); |
| 308 | + |
306 | 309 | // Create an empty buffer. |
307 | 310 | var source = self.ctx.createBufferSource(); |
308 | 311 | source.buffer = self._scratchBuffer; |
|
315 | 318 | source.start(0); |
316 | 319 | } |
317 | 320 |
|
| 321 | + // Calling resume() on a stack initiated by user gesture is what actually unlocks the audio on Android Chrome >= 55. |
| 322 | + if (typeof self.ctx.resume === 'function') { |
| 323 | + self.ctx.resume(); |
| 324 | + } |
| 325 | + |
318 | 326 | // Setup a timeout to check that we are unlocked on the next event loop. |
319 | 327 | source.onended = function() { |
320 | 328 | source.disconnect(0); |
|
397 | 405 | clearTimeout(self._suspendTimer); |
398 | 406 | self._suspendTimer = null; |
399 | 407 | } else if (self.state === 'suspended') { |
400 | | - self.state = 'resuming'; |
401 | 408 | self.ctx.resume().then(function() { |
402 | 409 | self.state = 'running'; |
403 | 410 |
|
|
651 | 658 | sprite = sound._sprite || '__default'; |
652 | 659 | } |
653 | 660 |
|
654 | | - // If we have no sprite and the sound hasn't loaded, we must wait |
655 | | - // for the sound to load to get our audio's duration. |
656 | | - if (self._state !== 'loaded' && !self._sprite[sprite]) { |
| 661 | + // If the sound hasn't loaded, we must wait to get the audio's duration. |
| 662 | + // We also need to wait to make sure we don't run into race conditions with |
| 663 | + // the order of function calls. |
| 664 | + if (self._state !== 'loaded') { |
| 665 | + // Set the sprite value on this sound. |
| 666 | + sound._sprite = sprite; |
| 667 | + |
| 668 | + // Makr this sounded as not ended in case another sound is played before this one loads. |
| 669 | + sound._ended = false; |
| 670 | + |
| 671 | + // Add the sound to the queue to be played on load. |
| 672 | + var soundId = sound._id; |
657 | 673 | self._queue.push({ |
658 | 674 | event: 'play', |
659 | 675 | action: function() { |
660 | | - self.play(self._soundById(sound._id) ? sound._id : undefined); |
| 676 | + self.play(soundId); |
661 | 677 | } |
662 | 678 | }); |
663 | 679 |
|
664 | | - return sound._id; |
| 680 | + return soundId; |
665 | 681 | } |
666 | 682 |
|
667 | 683 | // Don't play the sound if an id was passed and it is already playing. |
|
819 | 835 |
|
820 | 836 | if (sound._node) { |
821 | 837 | if (self._webAudio) { |
822 | | - // make sure the sound has been created |
| 838 | + // Make sure the sound has been created. |
823 | 839 | if (!sound._node.bufferSource) { |
824 | | - return self; |
| 840 | + continue; |
825 | 841 | } |
826 | 842 |
|
827 | 843 | if (typeof sound._node.bufferSource.stop === 'undefined') { |
|
890 | 906 |
|
891 | 907 | if (sound._node) { |
892 | 908 | if (self._webAudio) { |
893 | | - // make sure the sound has been created |
894 | | - if (!sound._node.bufferSource) { |
895 | | - if (!internal) { |
896 | | - self._emit('stop', sound._id); |
| 909 | + // Make sure the sound's AudioBufferSourceNode has been created. |
| 910 | + if (sound._node.bufferSource) { |
| 911 | + if (typeof sound._node.bufferSource.stop === 'undefined') { |
| 912 | + sound._node.bufferSource.noteOff(0); |
| 913 | + } else { |
| 914 | + sound._node.bufferSource.stop(0); |
897 | 915 | } |
898 | 916 |
|
899 | | - return self; |
900 | | - } |
901 | | - |
902 | | - if (typeof sound._node.bufferSource.stop === 'undefined') { |
903 | | - sound._node.bufferSource.noteOff(0); |
904 | | - } else { |
905 | | - sound._node.bufferSource.stop(0); |
| 917 | + // Clean up the buffer source. |
| 918 | + self._cleanBuffer(sound._node); |
906 | 919 | } |
907 | | - |
908 | | - // Clean up the buffer source. |
909 | | - self._cleanBuffer(sound._node); |
910 | 920 | } else if (!isNaN(sound._node.duration) || sound._node.duration === Infinity) { |
911 | 921 | sound._node.currentTime = sound._start || 0; |
912 | 922 | sound._node.pause(); |
913 | 923 | } |
914 | 924 | } |
915 | | - } |
916 | 925 |
|
917 | | - if (sound && !internal) { |
918 | | - self._emit('stop', sound._id); |
| 926 | + if (!internal) { |
| 927 | + self._emit('stop', sound._id); |
| 928 | + } |
919 | 929 | } |
920 | 930 | } |
921 | 931 |
|
|
1478 | 1488 |
|
1479 | 1489 | // Remove the source or disconnect. |
1480 | 1490 | if (!self._webAudio) { |
1481 | | - // Set the source to 0-second silence to stop any downloading. |
1482 | | - sounds[i]._node.src = 'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA'; |
| 1491 | + // Set the source to 0-second silence to stop any downloading (except in IE). |
| 1492 | + var checkIE = /MSIE |Trident\//.test(Howler._navigator && Howler._navigator.userAgent); |
| 1493 | + if (!checkIE) { |
| 1494 | + sounds[i]._node.src = 'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA'; |
| 1495 | + } |
1483 | 1496 |
|
1484 | 1497 | // Remove any event listeners. |
1485 | 1498 | sounds[i]._node.removeEventListener('error', sounds[i]._errorFn, false); |
|
1554 | 1567 | var events = self['_on' + event]; |
1555 | 1568 | var i = 0; |
1556 | 1569 |
|
1557 | | - if (fn) { |
| 1570 | + // Allow passing just an event and ID. |
| 1571 | + if (typeof fn === 'number') { |
| 1572 | + id = fn; |
| 1573 | + fn = null; |
| 1574 | + } |
| 1575 | + |
| 1576 | + if (fn || id) { |
1558 | 1577 | // Loop through event store and remove the passed function. |
1559 | 1578 | for (i=0; i<events.length; i++) { |
1560 | | - if (fn === events[i].fn && id === events[i].id) { |
| 1579 | + var isId = (id === events[i].id); |
| 1580 | + if (fn === events[i].fn && isId || !fn && isId) { |
1561 | 1581 | events.splice(i, 1); |
1562 | 1582 | break; |
1563 | 1583 | } |
|
1655 | 1675 | var self = this; |
1656 | 1676 | var sprite = sound._sprite; |
1657 | 1677 |
|
| 1678 | + // If we are using IE and there was network latency we may be clipping |
| 1679 | + // audio before it completes playing. Lets check the node to make sure it |
| 1680 | + // believes it has completed, before ending the playback. |
| 1681 | + if (!self._webAudio && self._node && !self._node.ended) { |
| 1682 | + setTimeout(self._ended.bind(self, sound), 100); |
| 1683 | + return self; |
| 1684 | + } |
| 1685 | + |
1658 | 1686 | // Should this sound loop? |
1659 | 1687 | var loop = !!(sound._loop || self._sprite[sprite][2]); |
1660 | 1688 |
|
|
1887 | 1915 | self._muted = parent._muted; |
1888 | 1916 | self._loop = parent._loop; |
1889 | 1917 | self._volume = parent._volume; |
1890 | | - self._muted = parent._muted; |
1891 | 1918 | self._rate = parent._rate; |
1892 | 1919 | self._seek = 0; |
1893 | 1920 | self._paused = true; |
|
1956 | 1983 | self._muted = parent._muted; |
1957 | 1984 | self._loop = parent._loop; |
1958 | 1985 | self._volume = parent._volume; |
1959 | | - self._muted = parent._muted; |
1960 | 1986 | self._rate = parent._rate; |
1961 | 1987 | self._seek = 0; |
1962 | 1988 | self._rateSeek = 0; |
|
1980 | 2006 | self._parent._emit('loaderror', self._id, self._node.error ? self._node.error.code : 0); |
1981 | 2007 |
|
1982 | 2008 | // Clear the event listener. |
1983 | | - self._node.removeEventListener('error', self._errorListener, false); |
| 2009 | + self._node.removeEventListener('error', self._errorFn, false); |
1984 | 2010 | }, |
1985 | 2011 |
|
1986 | 2012 | /** |
|
2155 | 2181 | // Create and expose the master GainNode when using Web Audio (useful for plugins or advanced usage). |
2156 | 2182 | if (Howler.usingWebAudio) { |
2157 | 2183 | Howler.masterGain = (typeof Howler.ctx.createGain === 'undefined') ? Howler.ctx.createGainNode() : Howler.ctx.createGain(); |
2158 | | - Howler.masterGain.gain.value = 1; |
| 2184 | + Howler.masterGain.gain.value = Howler._muted ? 0 : 1; |
2159 | 2185 | Howler.masterGain.connect(Howler.ctx.destination); |
2160 | 2186 | } |
2161 | 2187 |
|
|
2197 | 2223 | /*! |
2198 | 2224 | * Spatial Plugin - Adds support for stereo and 3D audio where Web Audio is supported. |
2199 | 2225 | * |
2200 | | - * howler.js v2.0.3 |
| 2226 | + * howler.js v2.0.4 |
2201 | 2227 | * howlerjs.com |
2202 | 2228 | * |
2203 | 2229 | * (c) 2013-2017, James Simpson of GoldFire Studios |
|
0 commit comments