|
1 | 1 | /*!
|
2 |
| - * howler.js v2.0.13 |
| 2 | + * howler.js v2.0.14 |
3 | 3 | * howlerjs.com
|
4 | 4 | *
|
5 | 5 | * (c) 2013-2018, James Simpson of GoldFire Studios
|
|
279 | 279 | var self = this || Howler;
|
280 | 280 |
|
281 | 281 | // Only run this on mobile devices if audio isn't already eanbled.
|
282 |
| - var isMobile = /iPhone|iPad|iPod|Android|BlackBerry|BB10|Silk|Mobi/i.test(self._navigator && self._navigator.userAgent); |
283 |
| - var isTouch = !!(('ontouchend' in window) || (self._navigator && self._navigator.maxTouchPoints > 0) || (self._navigator && self._navigator.msMaxTouchPoints > 0)); |
284 |
| - if (self._mobileEnabled || !self.ctx || (!isMobile && !isTouch)) { |
| 282 | + var isMobile = /iPhone|iPad|iPod|Android|BlackBerry|BB10|Silk|Mobi|Chrome/i.test(self._navigator && self._navigator.userAgent); |
| 283 | + if (self._mobileEnabled || !self.ctx || !isMobile) { |
285 | 284 | return;
|
286 | 285 | }
|
287 | 286 |
|
288 | 287 | self._mobileEnabled = false;
|
| 288 | + self.mobileAutoEnable = false; |
289 | 289 |
|
290 | 290 | // Some mobile devices/platforms have distortion issues when opening/closing tabs and/or web views.
|
291 | 291 | // Bugs in the browser (especially Mobile Safari) can cause the sampleRate to change from 44100 to 48000.
|
|
302 | 302 | // Call this method on touch start to create and play a buffer,
|
303 | 303 | // then check if the audio actually played to determine if
|
304 | 304 | // audio has now been unlocked on iOS, Android, etc.
|
305 |
| - var unlock = function() { |
| 305 | + var unlock = function(e) { |
| 306 | + e.preventDefault(); |
| 307 | + |
306 | 308 | // Fix Android can not play in suspend state.
|
307 | 309 | Howler._autoResume();
|
308 | 310 |
|
|
329 | 331 |
|
330 | 332 | // Update the unlocked state and prevent this check from happening again.
|
331 | 333 | self._mobileEnabled = true;
|
332 |
| - self.mobileAutoEnable = false; |
333 | 334 |
|
334 | 335 | // Remove the touch start listener.
|
335 | 336 | document.removeEventListener('touchstart', unlock, true);
|
336 | 337 | document.removeEventListener('touchend', unlock, true);
|
| 338 | + document.removeEventListener('click', unlock, true); |
| 339 | + |
| 340 | + // Let all sounds know that audio has been unlocked. |
| 341 | + for (var i=0; i<self._howls.length; i++) { |
| 342 | + self._howls[i]._emit('unlock'); |
| 343 | + } |
337 | 344 | };
|
338 | 345 | };
|
339 | 346 |
|
340 | 347 | // Setup a touch start listener to attempt an unlock in.
|
341 | 348 | document.addEventListener('touchstart', unlock, true);
|
342 | 349 | document.addEventListener('touchend', unlock, true);
|
| 350 | + document.addEventListener('click', unlock, true); |
343 | 351 |
|
344 | 352 | return self;
|
345 | 353 | },
|
|
498 | 506 | self._onvolume = o.onvolume ? [{fn: o.onvolume}] : [];
|
499 | 507 | self._onrate = o.onrate ? [{fn: o.onrate}] : [];
|
500 | 508 | self._onseek = o.onseek ? [{fn: o.onseek}] : [];
|
| 509 | + self._onunlock = o.onunlock ? [{fn: o.onunlock}] : []; |
501 | 510 | self._onresume = [];
|
502 | 511 |
|
503 | 512 | // Web Audio or HTML5 Audio?
|
|
714 | 723 | sound._stop = (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000;
|
715 | 724 | sound._loop = !!(sound._loop || self._sprite[sprite][2]);
|
716 | 725 |
|
| 726 | + // End the sound instantly if seek is at the end. |
| 727 | + if (sound._seek >= sound._stop) { |
| 728 | + self._ended(sound); |
| 729 | + return; |
| 730 | + } |
| 731 | + |
717 | 732 | // Begin the actual playback.
|
718 | 733 | var node = sound._node;
|
719 | 734 | if (self._webAudio) {
|
|
766 | 781 | var play = node.play();
|
767 | 782 |
|
768 | 783 | // Support older browsers that don't support promises, and thus don't have this issue.
|
769 |
| - if (typeof Promise !== 'undefined' && play instanceof Promise) { |
| 784 | + if (typeof Promise !== 'undefined' && (play instanceof Promise || typeof play.then === 'function')) { |
770 | 785 | // Implements a lock to prevent DOMException: The play() request was interrupted by a call to pause().
|
771 | 786 | self._playLock = true;
|
772 | 787 |
|
773 | 788 | // Releases the lock and executes queued actions.
|
774 |
| - var runLoadQueue = function() { |
775 |
| - self._playLock = false; |
776 |
| - if (!internal) { |
777 |
| - self._emit('play', sound._id); |
778 |
| - } |
779 |
| - }; |
780 |
| - play.then(runLoadQueue, runLoadQueue); |
| 789 | + play |
| 790 | + .then(function() { |
| 791 | + self._playLock = false; |
| 792 | + if (!internal) { |
| 793 | + self._emit('play', sound._id); |
| 794 | + } |
| 795 | + }) |
| 796 | + .catch(function() { |
| 797 | + self._playLock = false; |
| 798 | + self._emit('playerror', sound._id, 'Playback was unable to start. This is most commonly an issue ' + |
| 799 | + 'on mobile devices and Chrome where playback was not within a user interaction.'); |
| 800 | + }); |
781 | 801 | } else if (!internal) {
|
782 | 802 | self._emit('play', sound._id);
|
783 | 803 | }
|
|
788 | 808 | // If the node is still paused, then we can assume there was a playback issue.
|
789 | 809 | if (node.paused) {
|
790 | 810 | self._emit('playerror', sound._id, 'Playback was unable to start. This is most commonly an issue ' +
|
791 |
| - 'on mobile devices where playback was not within a user interaction.'); |
| 811 | + 'on mobile devices and Chrome where playback was not within a user interaction.'); |
792 | 812 | return;
|
793 | 813 | }
|
794 | 814 |
|
|
911 | 931 | var self = this;
|
912 | 932 |
|
913 | 933 | // If the sound hasn't loaded, add it to the load queue to stop when capable.
|
914 |
| - if (self._state !== 'loaded') { |
| 934 | + if (self._state !== 'loaded' || self._playLock) { |
915 | 935 | self._queue.push({
|
916 | 936 | event: 'stop',
|
917 | 937 | action: function() {
|
|
980 | 1000 | var self = this;
|
981 | 1001 |
|
982 | 1002 | // If the sound hasn't loaded, add it to the load queue to mute when capable.
|
983 |
| - if (self._state !== 'loaded') { |
| 1003 | + if (self._state !== 'loaded'|| self._playLock) { |
984 | 1004 | self._queue.push({
|
985 | 1005 | event: 'mute',
|
986 | 1006 | action: function() {
|
|
1063 | 1083 | var sound;
|
1064 | 1084 | if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) {
|
1065 | 1085 | // If the sound hasn't loaded, add it to the load queue to change volume when capable.
|
1066 |
| - if (self._state !== 'loaded') { |
| 1086 | + if (self._state !== 'loaded'|| self._playLock) { |
1067 | 1087 | self._queue.push({
|
1068 | 1088 | event: 'volume',
|
1069 | 1089 | action: function() {
|
|
1122 | 1142 | var self = this;
|
1123 | 1143 |
|
1124 | 1144 | // If the sound hasn't loaded, add it to the load queue to fade when capable.
|
1125 |
| - if (self._state !== 'loaded') { |
| 1145 | + if (self._state !== 'loaded' || self._playLock) { |
1126 | 1146 | self._queue.push({
|
1127 | 1147 | event: 'fade',
|
1128 | 1148 | action: function() {
|
|
1333 | 1353 | var sound;
|
1334 | 1354 | if (typeof rate === 'number') {
|
1335 | 1355 | // If the sound hasn't loaded, add it to the load queue to change playback rate when capable.
|
1336 |
| - if (self._state !== 'loaded') { |
| 1356 | + if (self._state !== 'loaded' || self._playLock) { |
1337 | 1357 | self._queue.push({
|
1338 | 1358 | event: 'rate',
|
1339 | 1359 | action: function() {
|
|
1429 | 1449 | }
|
1430 | 1450 |
|
1431 | 1451 | // If the sound hasn't loaded, add it to the load queue to seek when capable.
|
1432 |
| - if (self._state !== 'loaded') { |
| 1452 | + if (self._state !== 'loaded' || self._playLock) { |
1433 | 1453 | self._queue.push({
|
1434 | 1454 | event: 'seek',
|
1435 | 1455 | action: function() {
|
|
1456 | 1476 | sound._ended = false;
|
1457 | 1477 | self._clearTimer(id);
|
1458 | 1478 |
|
1459 |
| - // Restart the playback if the sound was playing. |
1460 |
| - if (playing) { |
1461 |
| - self.play(id, true); |
1462 |
| - } |
1463 |
| - |
1464 | 1479 | // Update the seek position for HTML5 Audio.
|
1465 | 1480 | if (!self._webAudio && sound._node) {
|
1466 | 1481 | sound._node.currentTime = seek;
|
1467 | 1482 | }
|
1468 | 1483 |
|
| 1484 | + // Seek and emit when ready. |
| 1485 | + var seekAndEmit = function() { |
| 1486 | + self._emit('seek', id); |
| 1487 | + |
| 1488 | + // Restart the playback if the sound was playing. |
| 1489 | + if (playing) { |
| 1490 | + self.play(id, true); |
| 1491 | + } |
| 1492 | + }; |
| 1493 | + |
1469 | 1494 | // Wait for the play lock to be unset before emitting (HTML5 Audio).
|
1470 | 1495 | if (playing && !self._webAudio) {
|
1471 | 1496 | var emitSeek = function() {
|
1472 | 1497 | if (!self._playLock) {
|
1473 |
| - self._emit('seek', id); |
| 1498 | + seekAndEmit(); |
1474 | 1499 | } else {
|
1475 | 1500 | setTimeout(emitSeek, 0);
|
1476 | 1501 | }
|
1477 | 1502 | };
|
1478 | 1503 | setTimeout(emitSeek, 0);
|
1479 | 1504 | } else {
|
1480 |
| - self._emit('seek', id); |
| 1505 | + seekAndEmit(); |
1481 | 1506 | }
|
1482 | 1507 | } else {
|
1483 | 1508 | if (self._webAudio) {
|
|
1953 | 1978 | sound._node.bufferSource.loop = sound._loop;
|
1954 | 1979 | if (sound._loop) {
|
1955 | 1980 | sound._node.bufferSource.loopStart = sound._start || 0;
|
1956 |
| - sound._node.bufferSource.loopEnd = sound._stop; |
| 1981 | + sound._node.bufferSource.loopEnd = sound._stop || 0; |
1957 | 1982 | }
|
1958 | 1983 | sound._node.bufferSource.playbackRate.setValueAtTime(sound._rate, Howler.ctx.currentTime);
|
1959 | 1984 |
|
|
2203 | 2228 | * @param {Howl} self
|
2204 | 2229 | */
|
2205 | 2230 | var decodeAudioData = function(arraybuffer, self) {
|
2206 |
| - // Decode the buffer into an audio source. |
2207 |
| - Howler.ctx.decodeAudioData(arraybuffer, function(buffer) { |
| 2231 | + // Load the sound on success. |
| 2232 | + var success = function(buffer) { |
2208 | 2233 | if (buffer && self._sounds.length > 0) {
|
2209 | 2234 | cache[self._src] = buffer;
|
2210 | 2235 | loadSound(self, buffer);
|
| 2236 | + } else { |
| 2237 | + onError(); |
2211 | 2238 | }
|
2212 |
| - }, function() { |
| 2239 | + }; |
| 2240 | + |
| 2241 | + // Fire a load error if something broke. |
| 2242 | + var error = function() { |
2213 | 2243 | self._emit('loaderror', null, 'Decoding audio data failed.');
|
2214 |
| - }); |
2215 |
| - }; |
| 2244 | + }; |
| 2245 | + |
| 2246 | + // Decode the buffer into an audio source. |
| 2247 | + if (typeof Promise !== 'undefined' && Howler.ctx.decodeAudioData.length === 1) { |
| 2248 | + Howler.ctx.decodeAudioData(arraybuffer).then(success).catch(error); |
| 2249 | + } else { |
| 2250 | + Howler.ctx.decodeAudioData(arraybuffer, success, error); |
| 2251 | + } |
| 2252 | + } |
2216 | 2253 |
|
2217 | 2254 | /**
|
2218 | 2255 | * Sound is now loaded, so finish setting everything up and fire the loaded event.
|
|
2312 | 2349 | /*!
|
2313 | 2350 | * Spatial Plugin - Adds support for stereo and 3D audio where Web Audio is supported.
|
2314 | 2351 | *
|
2315 |
| - * howler.js v2.0.13 |
| 2352 | + * howler.js v2.0.14 |
2316 | 2353 | * howlerjs.com
|
2317 | 2354 | *
|
2318 | 2355 | * (c) 2013-2018, James Simpson of GoldFire Studios
|
|
2703 | 2740 | setupPanner(sound, 'spatial');
|
2704 | 2741 | }
|
2705 | 2742 |
|
2706 |
| - sound._panner.orientationX.setValueAtTime(x, Howler.ctx.currentTime); |
2707 |
| - sound._panner.orientationY.setValueAtTime(y, Howler.ctx.currentTime); |
2708 |
| - sound._panner.orientationZ.setValueAtTime(z, Howler.ctx.currentTime); |
| 2743 | + if (typeof sound._panner.orientationX !== 'undefined') { |
| 2744 | + sound._panner.orientationX.setValueAtTime(x, Howler.ctx.currentTime); |
| 2745 | + sound._panner.orientationY.setValueAtTime(y, Howler.ctx.currentTime); |
| 2746 | + sound._panner.orientationZ.setValueAtTime(z, Howler.ctx.currentTime); |
| 2747 | + } else { |
| 2748 | + sound._panner.setOrientation(x, y, z); |
| 2749 | + } |
2709 | 2750 | }
|
2710 | 2751 |
|
2711 | 2752 | self._emit('orientation', sound._id);
|
|
0 commit comments