From c5e2bf1f666fd75c81a8c6da207027d7e490d7cf Mon Sep 17 00:00:00 2001 From: EthanShoeDev <13422990+EthanShoeDev@users.noreply.github.com> Date: Thu, 5 Sep 2024 13:51:15 -0400 Subject: [PATCH 1/2] Fix: #1736 --- src/howler.core.js | 998 ++++++++++++++++++++++++++++----------------- 1 file changed, 620 insertions(+), 378 deletions(-) diff --git a/src/howler.core.js b/src/howler.core.js index 5198bdfc..11af396a 100644 --- a/src/howler.core.js +++ b/src/howler.core.js @@ -8,9 +8,8 @@ * MIT License */ -(function() { - - 'use strict'; +(function () { + "use strict"; /** Global Methods **/ /***************************************************************************/ @@ -19,7 +18,7 @@ * Create the global controller. All contained methods and properties apply * to all sounds that are currently playing or will be in the future. */ - var HowlerGlobal = function() { + var HowlerGlobal = function () { this.init(); }; HowlerGlobal.prototype = { @@ -27,7 +26,7 @@ * Initialize the global Howler object. * @return {Howler} */ - init: function() { + init: function () { var self = this || Howler; // Create a global ID counter. @@ -42,8 +41,11 @@ self._howls = []; self._muted = false; self._volume = 1; - self._canPlayEvent = 'canplaythrough'; - self._navigator = (typeof window !== 'undefined' && window.navigator) ? window.navigator : null; + self._canPlayEvent = "canplaythrough"; + self._navigator = + typeof window !== "undefined" && window.navigator + ? window.navigator + : null; // Public properties. self.masterGain = null; @@ -66,7 +68,7 @@ * @param {Float} vol Volume from 0.0 to 1.0. * @return {Howler/Float} Returns self or current volume. */ - volume: function(vol) { + volume: function (vol) { var self = this || Howler; vol = parseFloat(vol); @@ -75,7 +77,7 @@ setupAudioContext(); } - if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) { + if (typeof vol !== "undefined" && vol >= 0 && vol <= 1) { self._volume = vol; // Don't update any of the nodes if we are muted. @@ -89,13 +91,13 @@ } // Loop through and change volume for all HTML5 audio nodes. - for (var i=0; i=0; i--) { + for (var i = self._howls.length - 1; i >= 0; i--) { self._howls[i].unload(); } // Create a new AudioContext to make sure it is fully reset. - if (self.usingWebAudio && self.ctx && typeof self.ctx.close !== 'undefined') { + if ( + self.usingWebAudio && + self.ctx && + typeof self.ctx.close !== "undefined" + ) { self.ctx.close(); self.ctx = null; setupAudioContext(); @@ -190,19 +199,19 @@ * @param {String} ext Audio file extention. * @return {Boolean} */ - codecs: function(ext) { - return (this || Howler)._codecs[ext.replace(/^x-/, '')]; + codecs: function (ext) { + return (this || Howler)._codecs[ext.replace(/^x-/, "")]; }, /** * Setup various state values for global tracking. * @return {Howler} */ - _setup: function() { + _setup: function () { var self = this || Howler; // Keeps track of the suspend/resume state of the AudioContext. - self.state = self.ctx ? self.ctx.state || 'suspended' : 'suspended'; + self.state = self.ctx ? self.ctx.state || "suspended" : "suspended"; // Automatically begin the 30-second suspend process self._autoSuspend(); @@ -210,15 +219,15 @@ // Check if audio is available. if (!self.usingWebAudio) { // No audio is available on this system if noAudio is set to true. - if (typeof Audio !== 'undefined') { + if (typeof Audio !== "undefined") { try { var test = new Audio(); // Check if the canplaythrough event is available. - if (typeof test.oncanplaythrough === 'undefined') { - self._canPlayEvent = 'canplay'; + if (typeof test.oncanplaythrough === "undefined") { + self._canPlayEvent = "canplay"; } - } catch(e) { + } catch (e) { self.noAudio = true; } } else { @@ -246,47 +255,89 @@ * Check for browser support for various codecs and cache the results. * @return {Howler} */ - _setupCodecs: function() { + _setupCodecs: function () { var self = this || Howler; var audioTest = null; // Must wrap in a try/catch because IE11 in server mode throws an error. try { - audioTest = (typeof Audio !== 'undefined') ? new Audio() : null; + audioTest = typeof Audio !== "undefined" ? new Audio() : null; } catch (err) { return self; } - if (!audioTest || typeof audioTest.canPlayType !== 'function') { + if (!audioTest || typeof audioTest.canPlayType !== "function") { return self; } - var mpegTest = audioTest.canPlayType('audio/mpeg;').replace(/^no$/, ''); + var mpegTest = audioTest.canPlayType("audio/mpeg;").replace(/^no$/, ""); // Opera version <33 has mixed MP3 support, so we need to check for and block it. - var ua = self._navigator ? self._navigator.userAgent : ''; + var ua = self._navigator ? self._navigator.userAgent : ""; var checkOpera = ua.match(/OPR\/(\d+)/g); - var isOldOpera = (checkOpera && parseInt(checkOpera[0].split('/')[1], 10) < 33); - var checkSafari = ua.indexOf('Safari') !== -1 && ua.indexOf('Chrome') === -1; + var isOldOpera = + checkOpera && parseInt(checkOpera[0].split("/")[1], 10) < 33; + var checkSafari = + ua.indexOf("Safari") !== -1 && ua.indexOf("Chrome") === -1; var safariVersion = ua.match(/Version\/(.*?) /); - var isOldSafari = (checkSafari && safariVersion && parseInt(safariVersion[1], 10) < 15); + var isOldSafari = + checkSafari && safariVersion && parseInt(safariVersion[1], 10) < 15; self._codecs = { - mp3: !!(!isOldOpera && (mpegTest || audioTest.canPlayType('audio/mp3;').replace(/^no$/, ''))), + mp3: !!( + !isOldOpera && + (mpegTest || audioTest.canPlayType("audio/mp3;").replace(/^no$/, "")) + ), mpeg: !!mpegTest, - opus: !!audioTest.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/, ''), - ogg: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ''), - oga: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ''), - wav: !!(audioTest.canPlayType('audio/wav; codecs="1"') || audioTest.canPlayType('audio/wav')).replace(/^no$/, ''), - aac: !!audioTest.canPlayType('audio/aac;').replace(/^no$/, ''), - caf: !!audioTest.canPlayType('audio/x-caf;').replace(/^no$/, ''), - m4a: !!(audioTest.canPlayType('audio/x-m4a;') || audioTest.canPlayType('audio/m4a;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''), - m4b: !!(audioTest.canPlayType('audio/x-m4b;') || audioTest.canPlayType('audio/m4b;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''), - mp4: !!(audioTest.canPlayType('audio/x-mp4;') || audioTest.canPlayType('audio/mp4;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''), - weba: !!(!isOldSafari && audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, '')), - webm: !!(!isOldSafari && audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, '')), - dolby: !!audioTest.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/, ''), - flac: !!(audioTest.canPlayType('audio/x-flac;') || audioTest.canPlayType('audio/flac;')).replace(/^no$/, '') + opus: !!audioTest + .canPlayType('audio/ogg; codecs="opus"') + .replace(/^no$/, ""), + ogg: !!audioTest + .canPlayType('audio/ogg; codecs="vorbis"') + .replace(/^no$/, ""), + oga: !!audioTest + .canPlayType('audio/ogg; codecs="vorbis"') + .replace(/^no$/, ""), + wav: !!( + audioTest.canPlayType('audio/wav; codecs="1"') || + audioTest.canPlayType("audio/wav") + ).replace(/^no$/, ""), + aac: !!audioTest.canPlayType("audio/aac;").replace(/^no$/, ""), + caf: !!audioTest.canPlayType("audio/x-caf;").replace(/^no$/, ""), + m4a: !!( + audioTest.canPlayType("audio/x-m4a;") || + audioTest.canPlayType("audio/m4a;") || + audioTest.canPlayType("audio/aac;") + ).replace(/^no$/, ""), + m4b: !!( + audioTest.canPlayType("audio/x-m4b;") || + audioTest.canPlayType("audio/m4b;") || + audioTest.canPlayType("audio/aac;") + ).replace(/^no$/, ""), + mp4: !!( + audioTest.canPlayType("audio/x-mp4;") || + audioTest.canPlayType("audio/mp4;") || + audioTest.canPlayType("audio/aac;") + ).replace(/^no$/, ""), + weba: !!( + !isOldSafari && + audioTest + .canPlayType('audio/webm; codecs="vorbis"') + .replace(/^no$/, "") + ), + webm: !!( + !isOldSafari && + audioTest + .canPlayType('audio/webm; codecs="vorbis"') + .replace(/^no$/, "") + ), + dolby: !!audioTest + .canPlayType('audio/mp4; codecs="ec-3"') + .replace(/^no$/, ""), + flac: !!( + audioTest.canPlayType("audio/x-flac;") || + audioTest.canPlayType("audio/flac;") + ).replace(/^no$/, ""), }; return self; @@ -298,7 +349,7 @@ * Concept from: http://paulbakaus.com/tutorials/html5/web-audio-on-ios/ * @return {Howler} */ - _unlockAudio: function() { + _unlockAudio: function () { var self = this || Howler; // Only run this if Web Audio is supported and it hasn't already been unlocked. @@ -324,7 +375,7 @@ // Call this method on touch start to create and play a buffer, // then check if the audio actually played to determine if // audio has now been unlocked on iOS, Android, etc. - var unlock = function(e) { + var unlock = function (e) { // Create a pool of unlocked HTML5 Audio objects that can // be used for playing sounds without user interaction. HTML5 // Audio objects must be individually unlocked, as opposed @@ -348,18 +399,20 @@ } // Loop through any assigned audio nodes and unlock them. - for (var i=0; i= 55. - if (typeof self.ctx.resume === 'function') { + if (typeof self.ctx.resume === "function") { self.ctx.resume(); } // Setup a timeout to check that we are unlocked on the next event loop. - source.onended = function() { + source.onended = function () { source.disconnect(0); // Update the unlocked state and prevent this check from happening again. self._audioUnlocked = true; // Remove the touch start listener. - document.removeEventListener('touchstart', unlock, true); - document.removeEventListener('touchend', unlock, true); - document.removeEventListener('click', unlock, true); - document.removeEventListener('keydown', unlock, true); + document.removeEventListener("touchstart", unlock, true); + document.removeEventListener("touchend", unlock, true); + document.removeEventListener("click", unlock, true); + document.removeEventListener("keydown", unlock, true); // Let all sounds know that audio has been unlocked. - for (var i=0; i 0 ? sound._seek : self._sprite[sprite][0] / 1000); - var duration = Math.max(0, ((self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000) - seek); + var seek = Math.max( + 0, + sound._seek > 0 ? sound._seek : self._sprite[sprite][0] / 1000 + ); + var duration = Math.max( + 0, + (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000 - seek + ); var timeout = (duration * 1000) / Math.abs(sound._rate); var start = self._sprite[sprite][0] / 1000; var stop = (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000; @@ -835,7 +943,7 @@ sound._ended = false; // Update the parameters of the sound. - var setParams = function() { + var setParams = function () { sound._paused = false; sound._seek = seek; sound._start = start; @@ -853,52 +961,60 @@ var node = sound._node; if (self._webAudio) { // Fire this when the sound is ready to play to begin Web Audio playback. - var playWebAudio = function() { + var playWebAudio = function () { self._playLock = false; setParams(); self._refreshBuffer(sound); // Setup the playback params. - var vol = (sound._muted || self._muted) ? 0 : sound._volume; + var vol = sound._muted || self._muted ? 0 : sound._volume; node.gain.setValueAtTime(vol, Howler.ctx.currentTime); sound._playStart = Howler.ctx.currentTime; // Play the sound using the supported method. - if (typeof node.bufferSource.start === 'undefined') { - sound._loop ? node.bufferSource.noteGrainOn(0, seek, 86400) : node.bufferSource.noteGrainOn(0, seek, duration); + if (typeof node.bufferSource.start === "undefined") { + sound._loop + ? node.bufferSource.noteGrainOn(0, seek, 86400) + : node.bufferSource.noteGrainOn(0, seek, duration); } else { - sound._loop ? node.bufferSource.start(0, seek, 86400) : node.bufferSource.start(0, seek, duration); + sound._loop + ? node.bufferSource.start(0, seek, 86400) + : node.bufferSource.start(0, seek, duration); } // Start a new timer if none is present. if (timeout !== Infinity) { - self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout); + self._endTimers[sound._id] = setTimeout( + self._ended.bind(self, sound), + timeout + ); } if (!internal) { - setTimeout(function() { - self._emit('play', sound._id); + setTimeout(function () { + self._emit("play", sound._id); self._loadQueue(); }, 0); } }; - if (Howler.state === 'running' && Howler.ctx.state !== 'interrupted') { + if (Howler.state === "running" && Howler.ctx.state !== "interrupted") { playWebAudio(); } else { self._playLock = true; // Wait for the audio context to resume before playing. - self.once('resume', playWebAudio); + self.once("resume", playWebAudio); // Cancel the end timer. self._clearTimer(sound._id); } } else { // Fire this when the sound is ready to play to begin HTML5 Audio playback. - var playHtml5 = function() { + var playHtml5 = function () { node.currentTime = seek; - node.muted = sound._muted || self._muted || Howler._muted || node.muted; + node.muted = + sound._muted || self._muted || Howler._muted || node.muted; node.volume = sound._volume * Howler.volume(); node.playbackRate = sound._rate; @@ -907,7 +1023,11 @@ var play = node.play(); // Support older browsers that don't support promises, and thus don't have this issue. - if (play && typeof Promise !== 'undefined' && (play instanceof Promise || typeof play.then === 'function')) { + if ( + play && + typeof Promise !== "undefined" && + (play instanceof Promise || typeof play.then === "function") + ) { // Implements a lock to prevent DOMException: The play() request was interrupted by a call to pause(). self._playLock = true; @@ -916,19 +1036,23 @@ // Releases the lock and executes queued actions. play - .then(function() { + .then(function () { self._playLock = false; node._unlocked = true; if (!internal) { - self._emit('play', sound._id); + self._emit("play", sound._id); } else { self._loadQueue(); } }) - .catch(function() { + .catch(function () { self._playLock = false; - self._emit('playerror', sound._id, 'Playback was unable to start. This is most commonly an issue ' + - 'on mobile devices and Chrome where playback was not within a user interaction.'); + self._emit( + "playerror", + sound._id, + "Playback was unable to start. This is most commonly an issue " + + "on mobile devices and Chrome where playback was not within a user interaction." + ); // Reset the ended and paused values. sound._ended = true; @@ -937,7 +1061,7 @@ } else if (!internal) { self._playLock = false; setParams(); - self._emit('play', sound._id); + self._emit("play", sound._id); } // Setting rate before playing won't work in IE, so we set it again here. @@ -945,46 +1069,62 @@ // If the node is still paused, then we can assume there was a playback issue. if (node.paused) { - self._emit('playerror', sound._id, 'Playback was unable to start. This is most commonly an issue ' + - 'on mobile devices and Chrome where playback was not within a user interaction.'); + self._emit( + "playerror", + sound._id, + "Playback was unable to start. This is most commonly an issue " + + "on mobile devices and Chrome where playback was not within a user interaction." + ); return; } // Setup the end timer on sprites or listen for the ended event. - if (sprite !== '__default' || sound._loop) { - self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout); + if (sprite !== "__default" || sound._loop) { + self._endTimers[sound._id] = setTimeout( + self._ended.bind(self, sound), + timeout + ); } else { - self._endTimers[sound._id] = function() { + self._endTimers[sound._id] = function () { // Fire ended on this audio node. self._ended(sound); // Clear this listener. - node.removeEventListener('ended', self._endTimers[sound._id], false); + node.removeEventListener( + "ended", + self._endTimers[sound._id], + false + ); }; - node.addEventListener('ended', self._endTimers[sound._id], false); + node.addEventListener("ended", self._endTimers[sound._id], false); } } catch (err) { - self._emit('playerror', sound._id, err); + self._emit("playerror", sound._id, err); } }; // If this is streaming audio, make sure the src is set and load again. - if (node.src === 'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA') { + if ( + node.src === + "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA" + ) { node.src = self._src; node.load(); } // Play immediately if ready, or wait for the 'canplaythrough'e vent. - var loadedNoReadyState = (window && window.ejecta) || (!node.readyState && Howler._navigator.isCocoonJS); + var loadedNoReadyState = + (window && window.ejecta) || + (!node.readyState && Howler._navigator.isCocoonJS); if (node.readyState >= 3 || loadedNoReadyState) { playHtml5(); } else { self._playLock = true; - self._state = 'loading'; + self._state = "loading"; + + var listener = function () { + self._state = "loaded"; - var listener = function() { - self._state = 'loaded'; - // Begin playback. playHtml5(); @@ -1006,16 +1146,16 @@ * @param {Number} id The sound ID (empty to pause all in group). * @return {Howl} */ - pause: function(id) { + pause: function (id) { var self = this; // If the sound hasn't loaded or a play() promise is pending, add it to the load queue to pause when capable. - if (self._state !== 'loaded' || self._playLock) { + if (self._state !== "loaded" || self._playLock) { self._queue.push({ - event: 'pause', - action: function() { + event: "pause", + action: function () { self.pause(id); - } + }, }); return self; @@ -1024,7 +1164,7 @@ // If no id is passed, get all ID's to be paused. var ids = self._getSoundIds(id); - for (var i=0; i Sets the volume of passed sound id. * @return {Howl/Number} Returns self or current volume. */ - volume: function() { + volume: function () { var self = this; var args = arguments; var vol, id; @@ -1219,7 +1368,10 @@ if (args.length === 0) { // Return the value of the groups' volume. return self._volume; - } else if (args.length === 1 || args.length === 2 && typeof args[1] === 'undefined') { + } else if ( + args.length === 1 || + (args.length === 2 && typeof args[1] === "undefined") + ) { // First check if this is an ID, and if not, assume it is a new volume. var ids = self._getSoundIds(); var index = ids.indexOf(args[0]); @@ -1235,27 +1387,27 @@ // Update the volume or return the current volume. var sound; - if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) { + if (typeof vol !== "undefined" && vol >= 0 && vol <= 1) { // If the sound hasn't loaded, add it to the load queue to change volume when capable. - if (self._state !== 'loaded'|| self._playLock) { + if (self._state !== "loaded" || self._playLock) { self._queue.push({ - event: 'volume', - action: function() { + event: "volume", + action: function () { self.volume.apply(self, args); - } + }, }); return self; } // Set the group volume. - if (typeof id === 'undefined') { + if (typeof id === "undefined") { self._volume = vol; } // Update one or all volumes. id = self._getSoundIds(id); - for (var i=0; i 0) ? len / steps : len); + var stepLen = Math.max(4, steps > 0 ? len / steps : len); var lastTick = Date.now(); // Store the value being faded to. sound._fadeTo = to; // Update the volume value on each interval tick. - sound._interval = setInterval(function() { + sound._interval = setInterval(function () { // Update the volume based on the time since the last tick. var tick = (Date.now() - lastTick) / len; lastTick = Date.now(); @@ -1399,7 +1558,7 @@ sound._interval = null; sound._fadeTo = null; self.volume(to, sound._id); - self._emit('fade', sound._id); + self._emit("fade", sound._id); } }, stepLen); }, @@ -1410,7 +1569,7 @@ * @param {Number} id The sound id. * @return {Howl} */ - _stopFade: function(id) { + _stopFade: function (id) { var self = this; var sound = self._soundById(id); @@ -1423,7 +1582,7 @@ sound._interval = null; self.volume(sound._fadeTo, id); sound._fadeTo = null; - self._emit('fade', id); + self._emit("fade", id); } return self; @@ -1437,7 +1596,7 @@ * loop(loop, id) -> Sets the loop value of passed sound id. * @return {Howl/Boolean} Returns self or current loop value. */ - loop: function() { + loop: function () { var self = this; var args = arguments; var loop, id, sound; @@ -1447,7 +1606,7 @@ // Return the grou's loop value. return self._loop; } else if (args.length === 1) { - if (typeof args[0] === 'boolean') { + if (typeof args[0] === "boolean") { loop = args[0]; self._loop = loop; } else { @@ -1462,7 +1621,7 @@ // If no id is passed, get all ID's to be looped. var ids = self._getSoundIds(id); - for (var i=0; i Sets the playback rate of passed sound id. * @return {Howl/Number} Returns self or the current playback rate. */ - rate: function() { + rate: function () { var self = this; var args = arguments; var rate, id; @@ -1519,27 +1678,27 @@ // Update the playback rate or return the current value. var sound; - if (typeof rate === 'number') { + if (typeof rate === "number") { // If the sound hasn't loaded, add it to the load queue to change playback rate when capable. - if (self._state !== 'loaded' || self._playLock) { + if (self._state !== "loaded" || self._playLock) { self._queue.push({ - event: 'rate', - action: function() { + event: "rate", + action: function () { self.rate.apply(self, args); - } + }, }); return self; } // Set the group rate. - if (typeof id === 'undefined') { + if (typeof id === "undefined") { self._rate = rate; } // Update one or all volumes. id = self._getSoundIds(id); - for (var i=0; i Sets the seek position of passed sound id. * @return {Howl/Number} Returns self or the current seek position. */ - seek: function() { + seek: function () { var self = this; var args = arguments; var seek, id; @@ -1616,17 +1787,20 @@ } // If there is no ID, bail out. - if (typeof id === 'undefined') { + if (typeof id === "undefined") { return 0; } // If the sound hasn't loaded, add it to the load queue to seek when capable. - if (typeof seek === 'number' && (self._state !== 'loaded' || self._playLock)) { + if ( + typeof seek === "number" && + (self._state !== "loaded" || self._playLock) + ) { self._queue.push({ - event: 'seek', - action: function() { + event: "seek", + action: function () { self.seek.apply(self, args); - } + }, }); return self; @@ -1636,7 +1810,7 @@ var sound = self._soundById(id); if (sound) { - if (typeof seek === 'number' && seek >= 0) { + if (typeof seek === "number" && seek >= 0) { // Pause the sound and update position for restarting playback. var playing = self.playing(id); if (playing) { @@ -1654,18 +1828,18 @@ } // Seek and emit when ready. - var seekAndEmit = function() { + var seekAndEmit = function () { // Restart the playback if the sound was playing. if (playing) { self.play(id, true); } - self._emit('seek', id); + self._emit("seek", id); }; // Wait for the play lock to be unset before emitting (HTML5 Audio). if (playing && !self._webAudio) { - var emitSeek = function() { + var emitSeek = function () { if (!self._playLock) { seekAndEmit(); } else { @@ -1678,7 +1852,9 @@ } } else { if (self._webAudio) { - var realTime = self.playing(id) ? Howler.ctx.currentTime - sound._playStart : 0; + var realTime = self.playing(id) + ? Howler.ctx.currentTime - sound._playStart + : 0; var rateSeek = sound._rateSeek ? sound._rateSeek - sound._seek : 0; return sound._seek + (rateSeek + realTime * Math.abs(sound._rate)); } else { @@ -1695,17 +1871,17 @@ * @param {Number} id The sound id to check. If none is passed, the whole sound group is checked. * @return {Boolean} True if playing and false if not. */ - playing: function(id) { + playing: function (id) { var self = this; // Check the passed sound ID (if any). - if (typeof id === 'number') { + if (typeof id === "number") { var sound = self._soundById(id); return sound ? !sound._paused : false; } // Otherwise, loop through all sounds and check if any are playing. - for (var i=0; i= 0) { + for (i = 0; i < Howler._howls.length; i++) { + if ( + Howler._howls[i]._src === self._src || + self._src.indexOf(Howler._howls[i]._src) >= 0 + ) { remCache = false; break; } @@ -1799,7 +1986,7 @@ Howler.noAudio = false; // Clear out `self`. - self._state = 'unloaded'; + self._state = "unloaded"; self._sounds = []; self = null; @@ -1814,12 +2001,12 @@ * @param {Number} once (INTERNAL) Marks event to fire only once. * @return {Howl} */ - on: function(event, fn, id, once) { + on: function (event, fn, id, once) { var self = this; - var events = self['_on' + event]; + var events = self["_on" + event]; - if (typeof fn === 'function') { - events.push(once ? {id: id, fn: fn, once: once} : {id: id, fn: fn}); + if (typeof fn === "function") { + events.push(once ? { id: id, fn: fn, once: once } : { id: id, fn: fn }); } return self; @@ -1832,34 +2019,34 @@ * @param {Number} id (optional) Only remove events for this sound. * @return {Howl} */ - off: function(event, fn, id) { + off: function (event, fn, id) { var self = this; - var events = self['_on' + event]; + var events = self["_on" + event]; var i = 0; // Allow passing just an event and ID. - if (typeof fn === 'number') { + if (typeof fn === "number") { id = fn; fn = null; } if (fn || id) { // Loop through event store and remove the passed function. - for (i=0; i=0; i--) { + for (var i = events.length - 1; i >= 0; i--) { // Only fire the listener if the correct ID is used. - if (!events[i].id || events[i].id === id || event === 'load') { - setTimeout(function(fn) { - fn.call(this, id, msg); - }.bind(self, events[i].fn), 0); + if (!events[i].id || events[i].id === id || event === "load") { + setTimeout( + function (fn) { + fn.call(this, id, msg); + }.bind(self, events[i].fn), + 0 + ); // If this event was setup with `once`, remove it. if (events[i].once) { @@ -1922,7 +2112,7 @@ * after the previous has finished executing (even if async like play). * @return {Howl} */ - _loadQueue: function(event) { + _loadQueue: function (event) { var self = this; if (self._queue.length > 0) { @@ -1948,14 +2138,20 @@ * @param {Sound} sound The sound object to work with. * @return {Howl} */ - _ended: function(sound) { + _ended: function (sound) { var self = this; var sprite = sound._sprite; // If we are using IE and there was network latency we may be clipping // audio before it completes playing. Lets check the node to make sure it // believes it has completed, before ending the playback. - if (!self._webAudio && sound._node && !sound._node.paused && !sound._node.ended && sound._node.currentTime < sound._stop) { + if ( + !self._webAudio && + sound._node && + !sound._node.paused && + !sound._node.ended && + sound._node.currentTime < sound._stop + ) { setTimeout(self._ended.bind(self, sound), 100); return self; } @@ -1964,7 +2160,7 @@ var loop = !!(sound._loop || self._sprite[sprite][2]); // Fire the ended event. - self._emit('end', sound._id); + self._emit("end", sound._id); // Restart the playback for HTML5 Audio loop. if (!self._webAudio && loop) { @@ -1973,13 +2169,17 @@ // Restart this timer if on a Web Audio loop. if (self._webAudio && loop) { - self._emit('play', sound._id); + self._emit("play", sound._id); sound._seek = sound._start || 0; sound._rateSeek = 0; sound._playStart = Howler.ctx.currentTime; - var timeout = ((sound._stop - sound._start) * 1000) / Math.abs(sound._rate); - self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout); + var timeout = + ((sound._stop - sound._start) * 1000) / Math.abs(sound._rate); + self._endTimers[sound._id] = setTimeout( + self._ended.bind(self, sound), + timeout + ); } // Mark the node as paused. @@ -2010,17 +2210,21 @@ * @param {Number} id The sound ID. * @return {Howl} */ - _clearTimer: function(id) { + _clearTimer: function (id) { var self = this; if (self._endTimers[id]) { // Clear the timeout or remove the ended listener. - if (typeof self._endTimers[id] !== 'function') { + if (typeof self._endTimers[id] !== "function") { clearTimeout(self._endTimers[id]); } else { var sound = self._soundById(id); if (sound && sound._node) { - sound._node.removeEventListener('ended', self._endTimers[id], false); + sound._node.removeEventListener( + "ended", + self._endTimers[id], + false + ); } } @@ -2035,11 +2239,11 @@ * @param {Number} id Sound ID * @return {Object} Sound object or null. */ - _soundById: function(id) { + _soundById: function (id) { var self = this; // Loop through all sounds and find the one with this ID. - for (var i=0; i=0; i--) { + for (i = self._sounds.length - 1; i >= 0; i--) { if (cnt <= limit) { return; } @@ -2113,12 +2317,12 @@ * @param {Number} id Only return one ID if one is passed. * @return {Array} Array of IDs. */ - _getSoundIds: function(id) { + _getSoundIds: function (id) { var self = this; - if (typeof id === 'undefined') { + if (typeof id === "undefined") { var ids = []; - for (var i=0; i= 0; + var isIOS = + Howler._navigator && Howler._navigator.vendor.indexOf("Apple") >= 0; if (!node.bufferSource) { return self; @@ -2175,7 +2383,9 @@ node.bufferSource.onended = null; node.bufferSource.disconnect(0); if (isIOS) { - try { node.bufferSource.buffer = Howler._scratchBuffer; } catch(e) {} + try { + node.bufferSource.buffer = Howler._scratchBuffer; + } catch (e) {} } } node.bufferSource = null; @@ -2187,12 +2397,15 @@ * Set the source to a 0-second silence to stop any downloading (except in IE). * @param {Object} node Audio node to clear. */ - _clearSound: function(node) { - var checkIE = /MSIE |Trident\//.test(Howler._navigator && Howler._navigator.userAgent); + _clearSound: function (node) { + var checkIE = /MSIE |Trident\//.test( + Howler._navigator && Howler._navigator.userAgent + ); if (!checkIE) { - node.src = 'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA'; + node.src = + "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"; } - } + }, }; /** Single Sound Methods **/ @@ -2202,7 +2415,7 @@ * Setup the sound object, which each node attached to a Howl group is contained in. * @param {Object} howl The Howl parent group. */ - var Sound = function(howl) { + var Sound = function (howl) { this._parent = howl; this.init(); }; @@ -2211,7 +2424,7 @@ * Initialize a new Sound object. * @return {Sound} */ - init: function() { + init: function () { var self = this; var parent = self._parent; @@ -2223,7 +2436,7 @@ self._seek = 0; self._paused = true; self._ended = true; - self._sprite = '__default'; + self._sprite = "__default"; // Generate a unique ID for this sound. self._id = ++Howler._counter; @@ -2241,14 +2454,18 @@ * Create and setup a new sound object, whether HTML5 Audio or Web Audio. * @return {Sound} */ - create: function() { + create: function () { var self = this; var parent = self._parent; - var volume = (Howler._muted || self._muted || self._parent._muted) ? 0 : self._volume; + var volume = + Howler._muted || self._muted || self._parent._muted ? 0 : self._volume; if (parent._webAudio) { // Create the gain node for controlling volume (the source will connect to this). - self._node = (typeof Howler.ctx.createGain === 'undefined') ? Howler.ctx.createGainNode() : Howler.ctx.createGain(); + self._node = + typeof Howler.ctx.createGain === "undefined" + ? Howler.ctx.createGainNode() + : Howler.ctx.createGain(); self._node.gain.setValueAtTime(volume, Howler.ctx.currentTime); self._node.paused = true; self._node.connect(Howler.masterGain); @@ -2258,7 +2475,7 @@ // Listen for errors (http://dev.w3.org/html5/spec-author-view/spec.html#mediaerror). self._errorFn = self._errorListener.bind(self); - self._node.addEventListener('error', self._errorFn, false); + self._node.addEventListener("error", self._errorFn, false); // Listen for 'canplaythrough' event to let us know the sound is ready. self._loadFn = self._loadListener.bind(self); @@ -2267,11 +2484,12 @@ // Listen for the 'ended' event on the sound to account for edge-case where // a finite sound has a duration of Infinity. self._endFn = self._endListener.bind(self); - self._node.addEventListener('ended', self._endFn, false); + self._node.addEventListener("ended", self._endFn, false); // Setup the new audio node. self._node.src = parent._src; - self._node.preload = parent._preload === true ? 'auto' : parent._preload; + self._node.preload = + parent._preload === true ? "auto" : parent._preload; self._node.volume = volume * Howler.volume(); // Begin loading the source. @@ -2285,7 +2503,7 @@ * Reset the parameters of this sound to the original state (for recycle). * @return {Sound} */ - reset: function() { + reset: function () { var self = this; var parent = self._parent; @@ -2298,7 +2516,7 @@ self._rateSeek = 0; self._paused = true; self._ended = true; - self._sprite = '__default'; + self._sprite = "__default"; // Generate a new ID so that it isn't confused with the previous sound. self._id = ++Howler._counter; @@ -2309,20 +2527,24 @@ /** * HTML5 Audio error listener callback. */ - _errorListener: function() { + _errorListener: function () { var self = this; // Fire an error event and pass back the code. - self._parent._emit('loaderror', self._id, self._node.error ? self._node.error.code : 0); + self._parent._emit( + "loaderror", + self._id, + self._node.error ? self._node.error.code : 0 + ); // Clear the event listener. - self._node.removeEventListener('error', self._errorFn, false); + self._node.removeEventListener("error", self._errorFn, false); }, /** * HTML5 Audio canplaythrough listener callback. */ - _loadListener: function() { + _loadListener: function () { var self = this; var parent = self._parent; @@ -2331,12 +2553,12 @@ // Setup a sprite if none is defined. if (Object.keys(parent._sprite).length === 0) { - parent._sprite = {__default: [0, parent._duration * 1000]}; + parent._sprite = { __default: [0, parent._duration * 1000] }; } - if (parent._state !== 'loaded') { - parent._state = 'loaded'; - parent._emit('load'); + if (parent._state !== "loaded") { + parent._state = "loaded"; + parent._emit("load"); parent._loadQueue(); } @@ -2347,7 +2569,7 @@ /** * HTML5 Audio ended listener callback. */ - _endListener: function() { + _endListener: function () { var self = this; var parent = self._parent; @@ -2367,8 +2589,8 @@ } // Clear the event listener since the duration is now correct. - self._node.removeEventListener('ended', self._endFn, false); - } + self._node.removeEventListener("ended", self._endFn, false); + }, }; /** Helper Methods **/ @@ -2380,7 +2602,7 @@ * Buffer a sound from URL, Data URI or cache and decode to audio source (Web Audio API). * @param {Howl} self */ - var loadBuffer = function(self) { + var loadBuffer = function (self) { var url = self._src; // Check if the buffer has already been cached and use it instead. @@ -2396,9 +2618,9 @@ if (/^data:[^;]+;base64,/.test(url)) { // Decode the base64 data URI without XHR, since some browsers don't support it. - var data = atob(url.split(',')[1]); + var data = atob(url.split(",")[1]); var dataView = new Uint8Array(data.length); - for (var i=0; i 0) { cache[self._src] = buffer; loadSound(self, buffer); @@ -2475,19 +2701,22 @@ }; // Decode the buffer into an audio source. - if (typeof Promise !== 'undefined' && Howler.ctx.decodeAudioData.length === 1) { + if ( + typeof Promise !== "undefined" && + Howler.ctx.decodeAudioData.length === 1 + ) { Howler.ctx.decodeAudioData(arraybuffer).then(success).catch(error); } else { Howler.ctx.decodeAudioData(arraybuffer, success, error); } - } + }; /** * Sound is now loaded, so finish setting everything up and fire the loaded event. * @param {Howl} self * @param {Object} buffer The decoded buffer sound source. */ - var loadSound = function(self, buffer) { + var loadSound = function (self, buffer) { // Set the duration. if (buffer && !self._duration) { self._duration = buffer.duration; @@ -2495,13 +2724,13 @@ // Setup a sprite if none is defined. if (Object.keys(self._sprite).length === 0) { - self._sprite = {__default: [0, self._duration * 1000]}; + self._sprite = { __default: [0, self._duration * 1000] }; } // Fire the loaded event. - if (self._state !== 'loaded') { - self._state = 'loaded'; - self._emit('load'); + if (self._state !== "loaded") { + self._state = "loaded"; + self._emit("load"); self._loadQueue(); } }; @@ -2509,7 +2738,7 @@ /** * Setup the audio context when available, or switch to HTML5 Audio mode. */ - var setupAudioContext = function() { + var setupAudioContext = function () { // If we have already detected that Web Audio isn't supported, don't run this step again. if (!Howler.usingWebAudio) { return; @@ -2517,14 +2746,14 @@ // Check if we are using Web Audio and setup the AudioContext if we are. try { - if (typeof AudioContext !== 'undefined') { + if (typeof AudioContext !== "undefined") { Howler.ctx = new AudioContext(); - } else if (typeof webkitAudioContext !== 'undefined') { + } else if (typeof webkitAudioContext !== "undefined") { Howler.ctx = new webkitAudioContext(); } else { Howler.usingWebAudio = false; } - } catch(e) { + } catch (e) { Howler.usingWebAudio = false; } @@ -2535,11 +2764,17 @@ // Check if a webview is being used on iOS8 or earlier (rather than the browser). // If it is, disable Web Audio as it causes crashing. - var iOS = (/iP(hone|od|ad)/.test(Howler._navigator && Howler._navigator.platform)); - var appVersion = Howler._navigator && Howler._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/); + var iOS = /iP(hone|od|ad)/.test( + Howler._navigator && Howler._navigator.platform + ); + var appVersion = + Howler._navigator && + Howler._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/); var version = appVersion ? parseInt(appVersion[1], 10) : null; if (iOS && version && version < 9) { - var safari = /safari/.test(Howler._navigator && Howler._navigator.userAgent.toLowerCase()); + var safari = /safari/.test( + Howler._navigator && Howler._navigator.userAgent.toLowerCase() + ); if (Howler._navigator && !safari) { Howler.usingWebAudio = false; } @@ -2547,8 +2782,14 @@ // Create and expose the master GainNode when using Web Audio (useful for plugins or advanced usage). if (Howler.usingWebAudio) { - Howler.masterGain = (typeof Howler.ctx.createGain === 'undefined') ? Howler.ctx.createGainNode() : Howler.ctx.createGain(); - Howler.masterGain.gain.setValueAtTime(Howler._muted ? 0 : Howler._volume, Howler.ctx.currentTime); + Howler.masterGain = + typeof Howler.ctx.createGain === "undefined" + ? Howler.ctx.createGainNode() + : Howler.ctx.createGain(); + Howler.masterGain.gain.setValueAtTime( + Howler._muted ? 0 : Howler._volume, + Howler.ctx.currentTime + ); Howler.masterGain.connect(Howler.ctx.destination); } @@ -2557,28 +2798,29 @@ }; // Add support for AMD (Asynchronous Module Definition) libraries such as require.js. - if (typeof define === 'function' && define.amd) { - define([], function() { + if (typeof define === "function" && define.amd) { + define([], function () { return { Howler: Howler, - Howl: Howl + Howl: Howl, }; }); } // Add support for CommonJS libraries such as browserify. - if (typeof exports !== 'undefined') { + if (typeof exports !== "undefined") { exports.Howler = Howler; exports.Howl = Howl; } // Add to global in Node.js (for testing, etc). - if (typeof global !== 'undefined') { + if (typeof global !== "undefined") { global.HowlerGlobal = HowlerGlobal; global.Howler = Howler; global.Howl = Howl; global.Sound = Sound; - } else if (typeof window !== 'undefined') { // Define globally in case AMD is not available or unused. + } else if (typeof window !== "undefined") { + // Define globally in case AMD is not available or unused. window.HowlerGlobal = HowlerGlobal; window.Howler = Howler; window.Howl = Howl; From 964d7fa971c0802ac2e333f6d22a5a79ef33f36e Mon Sep 17 00:00:00 2001 From: EthanShoeDev <13422990+EthanShoeDev@users.noreply.github.com> Date: Thu, 5 Sep 2024 14:10:26 -0400 Subject: [PATCH 2/2] undo fmt --- src/howler.core.js | 996 +++++++++++++++++---------------------------- 1 file changed, 378 insertions(+), 618 deletions(-) diff --git a/src/howler.core.js b/src/howler.core.js index 11af396a..b3fede3d 100644 --- a/src/howler.core.js +++ b/src/howler.core.js @@ -8,8 +8,9 @@ * MIT License */ -(function () { - "use strict"; +(function() { + + 'use strict'; /** Global Methods **/ /***************************************************************************/ @@ -18,7 +19,7 @@ * Create the global controller. All contained methods and properties apply * to all sounds that are currently playing or will be in the future. */ - var HowlerGlobal = function () { + var HowlerGlobal = function() { this.init(); }; HowlerGlobal.prototype = { @@ -26,7 +27,7 @@ * Initialize the global Howler object. * @return {Howler} */ - init: function () { + init: function() { var self = this || Howler; // Create a global ID counter. @@ -41,11 +42,8 @@ self._howls = []; self._muted = false; self._volume = 1; - self._canPlayEvent = "canplaythrough"; - self._navigator = - typeof window !== "undefined" && window.navigator - ? window.navigator - : null; + self._canPlayEvent = 'canplaythrough'; + self._navigator = (typeof window !== 'undefined' && window.navigator) ? window.navigator : null; // Public properties. self.masterGain = null; @@ -68,7 +66,7 @@ * @param {Float} vol Volume from 0.0 to 1.0. * @return {Howler/Float} Returns self or current volume. */ - volume: function (vol) { + volume: function(vol) { var self = this || Howler; vol = parseFloat(vol); @@ -77,7 +75,7 @@ setupAudioContext(); } - if (typeof vol !== "undefined" && vol >= 0 && vol <= 1) { + if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) { self._volume = vol; // Don't update any of the nodes if we are muted. @@ -91,13 +89,13 @@ } // Loop through and change volume for all HTML5 audio nodes. - for (var i = 0; i < self._howls.length; i++) { + for (var i=0; i= 0; i--) { + for (var i=self._howls.length-1; i>=0; i--) { self._howls[i].unload(); } // Create a new AudioContext to make sure it is fully reset. - if ( - self.usingWebAudio && - self.ctx && - typeof self.ctx.close !== "undefined" - ) { + if (self.usingWebAudio && self.ctx && typeof self.ctx.close !== 'undefined') { self.ctx.close(); self.ctx = null; setupAudioContext(); @@ -199,19 +190,19 @@ * @param {String} ext Audio file extention. * @return {Boolean} */ - codecs: function (ext) { - return (this || Howler)._codecs[ext.replace(/^x-/, "")]; + codecs: function(ext) { + return (this || Howler)._codecs[ext.replace(/^x-/, '')]; }, /** * Setup various state values for global tracking. * @return {Howler} */ - _setup: function () { + _setup: function() { var self = this || Howler; // Keeps track of the suspend/resume state of the AudioContext. - self.state = self.ctx ? self.ctx.state || "suspended" : "suspended"; + self.state = self.ctx ? self.ctx.state || 'suspended' : 'suspended'; // Automatically begin the 30-second suspend process self._autoSuspend(); @@ -219,15 +210,15 @@ // Check if audio is available. if (!self.usingWebAudio) { // No audio is available on this system if noAudio is set to true. - if (typeof Audio !== "undefined") { + if (typeof Audio !== 'undefined') { try { var test = new Audio(); // Check if the canplaythrough event is available. - if (typeof test.oncanplaythrough === "undefined") { - self._canPlayEvent = "canplay"; + if (typeof test.oncanplaythrough === 'undefined') { + self._canPlayEvent = 'canplay'; } - } catch (e) { + } catch(e) { self.noAudio = true; } } else { @@ -255,89 +246,47 @@ * Check for browser support for various codecs and cache the results. * @return {Howler} */ - _setupCodecs: function () { + _setupCodecs: function() { var self = this || Howler; var audioTest = null; // Must wrap in a try/catch because IE11 in server mode throws an error. try { - audioTest = typeof Audio !== "undefined" ? new Audio() : null; + audioTest = (typeof Audio !== 'undefined') ? new Audio() : null; } catch (err) { return self; } - if (!audioTest || typeof audioTest.canPlayType !== "function") { + if (!audioTest || typeof audioTest.canPlayType !== 'function') { return self; } - var mpegTest = audioTest.canPlayType("audio/mpeg;").replace(/^no$/, ""); + var mpegTest = audioTest.canPlayType('audio/mpeg;').replace(/^no$/, ''); // Opera version <33 has mixed MP3 support, so we need to check for and block it. - var ua = self._navigator ? self._navigator.userAgent : ""; + var ua = self._navigator ? self._navigator.userAgent : ''; var checkOpera = ua.match(/OPR\/(\d+)/g); - var isOldOpera = - checkOpera && parseInt(checkOpera[0].split("/")[1], 10) < 33; - var checkSafari = - ua.indexOf("Safari") !== -1 && ua.indexOf("Chrome") === -1; + var isOldOpera = (checkOpera && parseInt(checkOpera[0].split('/')[1], 10) < 33); + var checkSafari = ua.indexOf('Safari') !== -1 && ua.indexOf('Chrome') === -1; var safariVersion = ua.match(/Version\/(.*?) /); - var isOldSafari = - checkSafari && safariVersion && parseInt(safariVersion[1], 10) < 15; + var isOldSafari = (checkSafari && safariVersion && parseInt(safariVersion[1], 10) < 15); self._codecs = { - mp3: !!( - !isOldOpera && - (mpegTest || audioTest.canPlayType("audio/mp3;").replace(/^no$/, "")) - ), + mp3: !!(!isOldOpera && (mpegTest || audioTest.canPlayType('audio/mp3;').replace(/^no$/, ''))), mpeg: !!mpegTest, - opus: !!audioTest - .canPlayType('audio/ogg; codecs="opus"') - .replace(/^no$/, ""), - ogg: !!audioTest - .canPlayType('audio/ogg; codecs="vorbis"') - .replace(/^no$/, ""), - oga: !!audioTest - .canPlayType('audio/ogg; codecs="vorbis"') - .replace(/^no$/, ""), - wav: !!( - audioTest.canPlayType('audio/wav; codecs="1"') || - audioTest.canPlayType("audio/wav") - ).replace(/^no$/, ""), - aac: !!audioTest.canPlayType("audio/aac;").replace(/^no$/, ""), - caf: !!audioTest.canPlayType("audio/x-caf;").replace(/^no$/, ""), - m4a: !!( - audioTest.canPlayType("audio/x-m4a;") || - audioTest.canPlayType("audio/m4a;") || - audioTest.canPlayType("audio/aac;") - ).replace(/^no$/, ""), - m4b: !!( - audioTest.canPlayType("audio/x-m4b;") || - audioTest.canPlayType("audio/m4b;") || - audioTest.canPlayType("audio/aac;") - ).replace(/^no$/, ""), - mp4: !!( - audioTest.canPlayType("audio/x-mp4;") || - audioTest.canPlayType("audio/mp4;") || - audioTest.canPlayType("audio/aac;") - ).replace(/^no$/, ""), - weba: !!( - !isOldSafari && - audioTest - .canPlayType('audio/webm; codecs="vorbis"') - .replace(/^no$/, "") - ), - webm: !!( - !isOldSafari && - audioTest - .canPlayType('audio/webm; codecs="vorbis"') - .replace(/^no$/, "") - ), - dolby: !!audioTest - .canPlayType('audio/mp4; codecs="ec-3"') - .replace(/^no$/, ""), - flac: !!( - audioTest.canPlayType("audio/x-flac;") || - audioTest.canPlayType("audio/flac;") - ).replace(/^no$/, ""), + opus: !!audioTest.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/, ''), + ogg: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ''), + oga: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ''), + wav: !!(audioTest.canPlayType('audio/wav; codecs="1"') || audioTest.canPlayType('audio/wav')).replace(/^no$/, ''), + aac: !!audioTest.canPlayType('audio/aac;').replace(/^no$/, ''), + caf: !!audioTest.canPlayType('audio/x-caf;').replace(/^no$/, ''), + m4a: !!(audioTest.canPlayType('audio/x-m4a;') || audioTest.canPlayType('audio/m4a;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''), + m4b: !!(audioTest.canPlayType('audio/x-m4b;') || audioTest.canPlayType('audio/m4b;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''), + mp4: !!(audioTest.canPlayType('audio/x-mp4;') || audioTest.canPlayType('audio/mp4;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''), + weba: !!(!isOldSafari && audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, '')), + webm: !!(!isOldSafari && audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, '')), + dolby: !!audioTest.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/, ''), + flac: !!(audioTest.canPlayType('audio/x-flac;') || audioTest.canPlayType('audio/flac;')).replace(/^no$/, '') }; return self; @@ -349,7 +298,7 @@ * Concept from: http://paulbakaus.com/tutorials/html5/web-audio-on-ios/ * @return {Howler} */ - _unlockAudio: function () { + _unlockAudio: function() { var self = this || Howler; // Only run this if Web Audio is supported and it hasn't already been unlocked. @@ -375,7 +324,7 @@ // Call this method on touch start to create and play a buffer, // then check if the audio actually played to determine if // audio has now been unlocked on iOS, Android, etc. - var unlock = function (e) { + var unlock = function(e) { // Create a pool of unlocked HTML5 Audio objects that can // be used for playing sounds without user interaction. HTML5 // Audio objects must be individually unlocked, as opposed @@ -399,13 +348,13 @@ } // Loop through any assigned audio nodes and unlock them. - for (var i = 0; i < self._howls.length; i++) { + for (var i=0; i= 55. - if (typeof self.ctx.resume === "function") { + if (typeof self.ctx.resume === 'function') { self.ctx.resume(); } // Setup a timeout to check that we are unlocked on the next event loop. - source.onended = function () { + source.onended = function() { source.disconnect(0); // Update the unlocked state and prevent this check from happening again. self._audioUnlocked = true; // Remove the touch start listener. - document.removeEventListener("touchstart", unlock, true); - document.removeEventListener("touchend", unlock, true); - document.removeEventListener("click", unlock, true); - document.removeEventListener("keydown", unlock, true); + document.removeEventListener('touchstart', unlock, true); + document.removeEventListener('touchend', unlock, true); + document.removeEventListener('click', unlock, true); + document.removeEventListener('keydown', unlock, true); // Let all sounds know that audio has been unlocked. - for (var i = 0; i < self._howls.length; i++) { - self._howls[i]._emit("unlock"); + for (var i=0; i 0 ? sound._seek : self._sprite[sprite][0] / 1000 - ); - var duration = Math.max( - 0, - (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000 - seek - ); + var seek = Math.max(0, sound._seek > 0 ? sound._seek : self._sprite[sprite][0] / 1000); + var duration = Math.max(0, ((self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000) - seek); var timeout = (duration * 1000) / Math.abs(sound._rate); var start = self._sprite[sprite][0] / 1000; var stop = (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000; @@ -943,7 +837,7 @@ sound._ended = false; // Update the parameters of the sound. - var setParams = function () { + var setParams = function() { sound._paused = false; sound._seek = seek; sound._start = start; @@ -961,60 +855,52 @@ var node = sound._node; if (self._webAudio) { // Fire this when the sound is ready to play to begin Web Audio playback. - var playWebAudio = function () { + var playWebAudio = function() { self._playLock = false; setParams(); self._refreshBuffer(sound); // Setup the playback params. - var vol = sound._muted || self._muted ? 0 : sound._volume; + var vol = (sound._muted || self._muted) ? 0 : sound._volume; node.gain.setValueAtTime(vol, Howler.ctx.currentTime); sound._playStart = Howler.ctx.currentTime; // Play the sound using the supported method. - if (typeof node.bufferSource.start === "undefined") { - sound._loop - ? node.bufferSource.noteGrainOn(0, seek, 86400) - : node.bufferSource.noteGrainOn(0, seek, duration); + if (typeof node.bufferSource.start === 'undefined') { + sound._loop ? node.bufferSource.noteGrainOn(0, seek, 86400) : node.bufferSource.noteGrainOn(0, seek, duration); } else { - sound._loop - ? node.bufferSource.start(0, seek, 86400) - : node.bufferSource.start(0, seek, duration); + sound._loop ? node.bufferSource.start(0, seek, 86400) : node.bufferSource.start(0, seek, duration); } // Start a new timer if none is present. if (timeout !== Infinity) { - self._endTimers[sound._id] = setTimeout( - self._ended.bind(self, sound), - timeout - ); + self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout); } if (!internal) { - setTimeout(function () { - self._emit("play", sound._id); + setTimeout(function() { + self._emit('play', sound._id); self._loadQueue(); }, 0); } }; - if (Howler.state === "running" && Howler.ctx.state !== "interrupted") { + if (Howler.state === 'running' && Howler.ctx.state !== 'interrupted') { playWebAudio(); } else { self._playLock = true; // Wait for the audio context to resume before playing. - self.once("resume", playWebAudio); + self.once('resume', playWebAudio); // Cancel the end timer. self._clearTimer(sound._id); } } else { // Fire this when the sound is ready to play to begin HTML5 Audio playback. - var playHtml5 = function () { + var playHtml5 = function() { node.currentTime = seek; - node.muted = - sound._muted || self._muted || Howler._muted || node.muted; + node.muted = sound._muted || self._muted || Howler._muted || node.muted; node.volume = sound._volume * Howler.volume(); node.playbackRate = sound._rate; @@ -1023,11 +909,7 @@ var play = node.play(); // Support older browsers that don't support promises, and thus don't have this issue. - if ( - play && - typeof Promise !== "undefined" && - (play instanceof Promise || typeof play.then === "function") - ) { + if (play && typeof Promise !== 'undefined' && (play instanceof Promise || typeof play.then === 'function')) { // Implements a lock to prevent DOMException: The play() request was interrupted by a call to pause(). self._playLock = true; @@ -1036,23 +918,19 @@ // Releases the lock and executes queued actions. play - .then(function () { + .then(function() { self._playLock = false; node._unlocked = true; if (!internal) { - self._emit("play", sound._id); + self._emit('play', sound._id); } else { self._loadQueue(); } }) - .catch(function () { + .catch(function() { self._playLock = false; - self._emit( - "playerror", - sound._id, - "Playback was unable to start. This is most commonly an issue " + - "on mobile devices and Chrome where playback was not within a user interaction." - ); + self._emit('playerror', sound._id, 'Playback was unable to start. This is most commonly an issue ' + + 'on mobile devices and Chrome where playback was not within a user interaction.'); // Reset the ended and paused values. sound._ended = true; @@ -1061,7 +939,7 @@ } else if (!internal) { self._playLock = false; setParams(); - self._emit("play", sound._id); + self._emit('play', sound._id); } // Setting rate before playing won't work in IE, so we set it again here. @@ -1069,62 +947,46 @@ // If the node is still paused, then we can assume there was a playback issue. if (node.paused) { - self._emit( - "playerror", - sound._id, - "Playback was unable to start. This is most commonly an issue " + - "on mobile devices and Chrome where playback was not within a user interaction." - ); + self._emit('playerror', sound._id, 'Playback was unable to start. This is most commonly an issue ' + + 'on mobile devices and Chrome where playback was not within a user interaction.'); return; } // Setup the end timer on sprites or listen for the ended event. - if (sprite !== "__default" || sound._loop) { - self._endTimers[sound._id] = setTimeout( - self._ended.bind(self, sound), - timeout - ); + if (sprite !== '__default' || sound._loop) { + self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout); } else { - self._endTimers[sound._id] = function () { + self._endTimers[sound._id] = function() { // Fire ended on this audio node. self._ended(sound); // Clear this listener. - node.removeEventListener( - "ended", - self._endTimers[sound._id], - false - ); + node.removeEventListener('ended', self._endTimers[sound._id], false); }; - node.addEventListener("ended", self._endTimers[sound._id], false); + node.addEventListener('ended', self._endTimers[sound._id], false); } } catch (err) { - self._emit("playerror", sound._id, err); + self._emit('playerror', sound._id, err); } }; // If this is streaming audio, make sure the src is set and load again. - if ( - node.src === - "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA" - ) { + if (node.src === 'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA') { node.src = self._src; node.load(); } // Play immediately if ready, or wait for the 'canplaythrough'e vent. - var loadedNoReadyState = - (window && window.ejecta) || - (!node.readyState && Howler._navigator.isCocoonJS); + var loadedNoReadyState = (window && window.ejecta) || (!node.readyState && Howler._navigator.isCocoonJS); if (node.readyState >= 3 || loadedNoReadyState) { playHtml5(); } else { self._playLock = true; - self._state = "loading"; - - var listener = function () { - self._state = "loaded"; + self._state = 'loading'; + var listener = function() { + self._state = 'loaded'; + // Begin playback. playHtml5(); @@ -1146,16 +1008,16 @@ * @param {Number} id The sound ID (empty to pause all in group). * @return {Howl} */ - pause: function (id) { + pause: function(id) { var self = this; // If the sound hasn't loaded or a play() promise is pending, add it to the load queue to pause when capable. - if (self._state !== "loaded" || self._playLock) { + if (self._state !== 'loaded' || self._playLock) { self._queue.push({ - event: "pause", - action: function () { + event: 'pause', + action: function() { self.pause(id); - }, + } }); return self; @@ -1164,7 +1026,7 @@ // If no id is passed, get all ID's to be paused. var ids = self._getSoundIds(id); - for (var i = 0; i < ids.length; i++) { + for (var i=0; i Sets the volume of passed sound id. * @return {Howl/Number} Returns self or current volume. */ - volume: function () { + volume: function() { var self = this; var args = arguments; var vol, id; @@ -1368,10 +1221,7 @@ if (args.length === 0) { // Return the value of the groups' volume. return self._volume; - } else if ( - args.length === 1 || - (args.length === 2 && typeof args[1] === "undefined") - ) { + } else if (args.length === 1 || args.length === 2 && typeof args[1] === 'undefined') { // First check if this is an ID, and if not, assume it is a new volume. var ids = self._getSoundIds(); var index = ids.indexOf(args[0]); @@ -1387,27 +1237,27 @@ // Update the volume or return the current volume. var sound; - if (typeof vol !== "undefined" && vol >= 0 && vol <= 1) { + if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) { // If the sound hasn't loaded, add it to the load queue to change volume when capable. - if (self._state !== "loaded" || self._playLock) { + if (self._state !== 'loaded'|| self._playLock) { self._queue.push({ - event: "volume", - action: function () { + event: 'volume', + action: function() { self.volume.apply(self, args); - }, + } }); return self; } // Set the group volume. - if (typeof id === "undefined") { + if (typeof id === 'undefined') { self._volume = vol; } // Update one or all volumes. id = self._getSoundIds(id); - for (var i = 0; i < id.length; i++) { + for (var i=0; i 0 ? len / steps : len); + var stepLen = Math.max(4, (steps > 0) ? len / steps : len); var lastTick = Date.now(); // Store the value being faded to. sound._fadeTo = to; // Update the volume value on each interval tick. - sound._interval = setInterval(function () { + sound._interval = setInterval(function() { // Update the volume based on the time since the last tick. var tick = (Date.now() - lastTick) / len; lastTick = Date.now(); @@ -1558,7 +1401,7 @@ sound._interval = null; sound._fadeTo = null; self.volume(to, sound._id); - self._emit("fade", sound._id); + self._emit('fade', sound._id); } }, stepLen); }, @@ -1569,7 +1412,7 @@ * @param {Number} id The sound id. * @return {Howl} */ - _stopFade: function (id) { + _stopFade: function(id) { var self = this; var sound = self._soundById(id); @@ -1582,7 +1425,7 @@ sound._interval = null; self.volume(sound._fadeTo, id); sound._fadeTo = null; - self._emit("fade", id); + self._emit('fade', id); } return self; @@ -1596,7 +1439,7 @@ * loop(loop, id) -> Sets the loop value of passed sound id. * @return {Howl/Boolean} Returns self or current loop value. */ - loop: function () { + loop: function() { var self = this; var args = arguments; var loop, id, sound; @@ -1606,7 +1449,7 @@ // Return the grou's loop value. return self._loop; } else if (args.length === 1) { - if (typeof args[0] === "boolean") { + if (typeof args[0] === 'boolean') { loop = args[0]; self._loop = loop; } else { @@ -1621,7 +1464,7 @@ // If no id is passed, get all ID's to be looped. var ids = self._getSoundIds(id); - for (var i = 0; i < ids.length; i++) { + for (var i=0; i Sets the playback rate of passed sound id. * @return {Howl/Number} Returns self or the current playback rate. */ - rate: function () { + rate: function() { var self = this; var args = arguments; var rate, id; @@ -1678,27 +1521,27 @@ // Update the playback rate or return the current value. var sound; - if (typeof rate === "number") { + if (typeof rate === 'number') { // If the sound hasn't loaded, add it to the load queue to change playback rate when capable. - if (self._state !== "loaded" || self._playLock) { + if (self._state !== 'loaded' || self._playLock) { self._queue.push({ - event: "rate", - action: function () { + event: 'rate', + action: function() { self.rate.apply(self, args); - }, + } }); return self; } // Set the group rate. - if (typeof id === "undefined") { + if (typeof id === 'undefined') { self._rate = rate; } // Update one or all volumes. id = self._getSoundIds(id); - for (var i = 0; i < id.length; i++) { + for (var i=0; i Sets the seek position of passed sound id. * @return {Howl/Number} Returns self or the current seek position. */ - seek: function () { + seek: function() { var self = this; var args = arguments; var seek, id; @@ -1787,20 +1618,17 @@ } // If there is no ID, bail out. - if (typeof id === "undefined") { + if (typeof id === 'undefined') { return 0; } // If the sound hasn't loaded, add it to the load queue to seek when capable. - if ( - typeof seek === "number" && - (self._state !== "loaded" || self._playLock) - ) { + if (typeof seek === 'number' && (self._state !== 'loaded' || self._playLock)) { self._queue.push({ - event: "seek", - action: function () { + event: 'seek', + action: function() { self.seek.apply(self, args); - }, + } }); return self; @@ -1810,7 +1638,7 @@ var sound = self._soundById(id); if (sound) { - if (typeof seek === "number" && seek >= 0) { + if (typeof seek === 'number' && seek >= 0) { // Pause the sound and update position for restarting playback. var playing = self.playing(id); if (playing) { @@ -1828,18 +1656,18 @@ } // Seek and emit when ready. - var seekAndEmit = function () { + var seekAndEmit = function() { // Restart the playback if the sound was playing. if (playing) { self.play(id, true); } - self._emit("seek", id); + self._emit('seek', id); }; // Wait for the play lock to be unset before emitting (HTML5 Audio). if (playing && !self._webAudio) { - var emitSeek = function () { + var emitSeek = function() { if (!self._playLock) { seekAndEmit(); } else { @@ -1852,9 +1680,7 @@ } } else { if (self._webAudio) { - var realTime = self.playing(id) - ? Howler.ctx.currentTime - sound._playStart - : 0; + var realTime = self.playing(id) ? Howler.ctx.currentTime - sound._playStart : 0; var rateSeek = sound._rateSeek ? sound._rateSeek - sound._seek : 0; return sound._seek + (rateSeek + realTime * Math.abs(sound._rate)); } else { @@ -1871,17 +1697,17 @@ * @param {Number} id The sound id to check. If none is passed, the whole sound group is checked. * @return {Boolean} True if playing and false if not. */ - playing: function (id) { + playing: function(id) { var self = this; // Check the passed sound ID (if any). - if (typeof id === "number") { + if (typeof id === 'number') { var sound = self._soundById(id); return sound ? !sound._paused : false; } // Otherwise, loop through all sounds and check if any are playing. - for (var i = 0; i < self._sounds.length; i++) { + for (var i=0; i= 0 - ) { + for (i=0; i= 0) { remCache = false; break; } @@ -1986,7 +1801,7 @@ Howler.noAudio = false; // Clear out `self`. - self._state = "unloaded"; + self._state = 'unloaded'; self._sounds = []; self = null; @@ -2001,12 +1816,12 @@ * @param {Number} once (INTERNAL) Marks event to fire only once. * @return {Howl} */ - on: function (event, fn, id, once) { + on: function(event, fn, id, once) { var self = this; - var events = self["_on" + event]; + var events = self['_on' + event]; - if (typeof fn === "function") { - events.push(once ? { id: id, fn: fn, once: once } : { id: id, fn: fn }); + if (typeof fn === 'function') { + events.push(once ? {id: id, fn: fn, once: once} : {id: id, fn: fn}); } return self; @@ -2019,34 +1834,34 @@ * @param {Number} id (optional) Only remove events for this sound. * @return {Howl} */ - off: function (event, fn, id) { + off: function(event, fn, id) { var self = this; - var events = self["_on" + event]; + var events = self['_on' + event]; var i = 0; // Allow passing just an event and ID. - if (typeof fn === "number") { + if (typeof fn === 'number') { id = fn; fn = null; } if (fn || id) { // Loop through event store and remove the passed function. - for (i = 0; i < events.length; i++) { - var isId = id === events[i].id; - if ((fn === events[i].fn && isId) || (!fn && isId)) { + for (i=0; i= 0; i--) { + for (var i=events.length-1; i>=0; i--) { // Only fire the listener if the correct ID is used. - if (!events[i].id || events[i].id === id || event === "load") { - setTimeout( - function (fn) { - fn.call(this, id, msg); - }.bind(self, events[i].fn), - 0 - ); + if (!events[i].id || events[i].id === id || event === 'load') { + setTimeout(function(fn) { + fn.call(this, id, msg); + }.bind(self, events[i].fn), 0); // If this event was setup with `once`, remove it. if (events[i].once) { @@ -2112,7 +1924,7 @@ * after the previous has finished executing (even if async like play). * @return {Howl} */ - _loadQueue: function (event) { + _loadQueue: function(event) { var self = this; if (self._queue.length > 0) { @@ -2138,20 +1950,14 @@ * @param {Sound} sound The sound object to work with. * @return {Howl} */ - _ended: function (sound) { + _ended: function(sound) { var self = this; var sprite = sound._sprite; // If we are using IE and there was network latency we may be clipping // audio before it completes playing. Lets check the node to make sure it // believes it has completed, before ending the playback. - if ( - !self._webAudio && - sound._node && - !sound._node.paused && - !sound._node.ended && - sound._node.currentTime < sound._stop - ) { + if (!self._webAudio && sound._node && !sound._node.paused && !sound._node.ended && sound._node.currentTime < sound._stop) { setTimeout(self._ended.bind(self, sound), 100); return self; } @@ -2160,7 +1966,7 @@ var loop = !!(sound._loop || self._sprite[sprite][2]); // Fire the ended event. - self._emit("end", sound._id); + self._emit('end', sound._id); // Restart the playback for HTML5 Audio loop. if (!self._webAudio && loop) { @@ -2169,17 +1975,13 @@ // Restart this timer if on a Web Audio loop. if (self._webAudio && loop) { - self._emit("play", sound._id); + self._emit('play', sound._id); sound._seek = sound._start || 0; sound._rateSeek = 0; sound._playStart = Howler.ctx.currentTime; - var timeout = - ((sound._stop - sound._start) * 1000) / Math.abs(sound._rate); - self._endTimers[sound._id] = setTimeout( - self._ended.bind(self, sound), - timeout - ); + var timeout = ((sound._stop - sound._start) * 1000) / Math.abs(sound._rate); + self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout); } // Mark the node as paused. @@ -2210,21 +2012,17 @@ * @param {Number} id The sound ID. * @return {Howl} */ - _clearTimer: function (id) { + _clearTimer: function(id) { var self = this; if (self._endTimers[id]) { // Clear the timeout or remove the ended listener. - if (typeof self._endTimers[id] !== "function") { + if (typeof self._endTimers[id] !== 'function') { clearTimeout(self._endTimers[id]); } else { var sound = self._soundById(id); if (sound && sound._node) { - sound._node.removeEventListener( - "ended", - self._endTimers[id], - false - ); + sound._node.removeEventListener('ended', self._endTimers[id], false); } } @@ -2239,11 +2037,11 @@ * @param {Number} id Sound ID * @return {Object} Sound object or null. */ - _soundById: function (id) { + _soundById: function(id) { var self = this; // Loop through all sounds and find the one with this ID. - for (var i = 0; i < self._sounds.length; i++) { + for (var i=0; i= 0; i--) { + for (i=self._sounds.length - 1; i>=0; i--) { if (cnt <= limit) { return; } @@ -2317,12 +2115,12 @@ * @param {Number} id Only return one ID if one is passed. * @return {Array} Array of IDs. */ - _getSoundIds: function (id) { + _getSoundIds: function(id) { var self = this; - if (typeof id === "undefined") { + if (typeof id === 'undefined') { var ids = []; - for (var i = 0; i < self._sounds.length; i++) { + for (var i=0; i= 0; + var isIOS = Howler._navigator && Howler._navigator.vendor.indexOf('Apple') >= 0; if (!node.bufferSource) { return self; @@ -2383,9 +2177,7 @@ node.bufferSource.onended = null; node.bufferSource.disconnect(0); if (isIOS) { - try { - node.bufferSource.buffer = Howler._scratchBuffer; - } catch (e) {} + try { node.bufferSource.buffer = Howler._scratchBuffer; } catch(e) {} } } node.bufferSource = null; @@ -2397,15 +2189,12 @@ * Set the source to a 0-second silence to stop any downloading (except in IE). * @param {Object} node Audio node to clear. */ - _clearSound: function (node) { - var checkIE = /MSIE |Trident\//.test( - Howler._navigator && Howler._navigator.userAgent - ); + _clearSound: function(node) { + var checkIE = /MSIE |Trident\//.test(Howler._navigator && Howler._navigator.userAgent); if (!checkIE) { - node.src = - "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"; + node.src = 'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA'; } - }, + } }; /** Single Sound Methods **/ @@ -2415,7 +2204,7 @@ * Setup the sound object, which each node attached to a Howl group is contained in. * @param {Object} howl The Howl parent group. */ - var Sound = function (howl) { + var Sound = function(howl) { this._parent = howl; this.init(); }; @@ -2424,7 +2213,7 @@ * Initialize a new Sound object. * @return {Sound} */ - init: function () { + init: function() { var self = this; var parent = self._parent; @@ -2436,7 +2225,7 @@ self._seek = 0; self._paused = true; self._ended = true; - self._sprite = "__default"; + self._sprite = '__default'; // Generate a unique ID for this sound. self._id = ++Howler._counter; @@ -2454,18 +2243,14 @@ * Create and setup a new sound object, whether HTML5 Audio or Web Audio. * @return {Sound} */ - create: function () { + create: function() { var self = this; var parent = self._parent; - var volume = - Howler._muted || self._muted || self._parent._muted ? 0 : self._volume; + var volume = (Howler._muted || self._muted || self._parent._muted) ? 0 : self._volume; if (parent._webAudio) { // Create the gain node for controlling volume (the source will connect to this). - self._node = - typeof Howler.ctx.createGain === "undefined" - ? Howler.ctx.createGainNode() - : Howler.ctx.createGain(); + self._node = (typeof Howler.ctx.createGain === 'undefined') ? Howler.ctx.createGainNode() : Howler.ctx.createGain(); self._node.gain.setValueAtTime(volume, Howler.ctx.currentTime); self._node.paused = true; self._node.connect(Howler.masterGain); @@ -2475,7 +2260,7 @@ // Listen for errors (http://dev.w3.org/html5/spec-author-view/spec.html#mediaerror). self._errorFn = self._errorListener.bind(self); - self._node.addEventListener("error", self._errorFn, false); + self._node.addEventListener('error', self._errorFn, false); // Listen for 'canplaythrough' event to let us know the sound is ready. self._loadFn = self._loadListener.bind(self); @@ -2484,12 +2269,11 @@ // Listen for the 'ended' event on the sound to account for edge-case where // a finite sound has a duration of Infinity. self._endFn = self._endListener.bind(self); - self._node.addEventListener("ended", self._endFn, false); + self._node.addEventListener('ended', self._endFn, false); // Setup the new audio node. self._node.src = parent._src; - self._node.preload = - parent._preload === true ? "auto" : parent._preload; + self._node.preload = parent._preload === true ? 'auto' : parent._preload; self._node.volume = volume * Howler.volume(); // Begin loading the source. @@ -2503,7 +2287,7 @@ * Reset the parameters of this sound to the original state (for recycle). * @return {Sound} */ - reset: function () { + reset: function() { var self = this; var parent = self._parent; @@ -2516,7 +2300,7 @@ self._rateSeek = 0; self._paused = true; self._ended = true; - self._sprite = "__default"; + self._sprite = '__default'; // Generate a new ID so that it isn't confused with the previous sound. self._id = ++Howler._counter; @@ -2527,24 +2311,20 @@ /** * HTML5 Audio error listener callback. */ - _errorListener: function () { + _errorListener: function() { var self = this; // Fire an error event and pass back the code. - self._parent._emit( - "loaderror", - self._id, - self._node.error ? self._node.error.code : 0 - ); + self._parent._emit('loaderror', self._id, self._node.error ? self._node.error.code : 0); // Clear the event listener. - self._node.removeEventListener("error", self._errorFn, false); + self._node.removeEventListener('error', self._errorFn, false); }, /** * HTML5 Audio canplaythrough listener callback. */ - _loadListener: function () { + _loadListener: function() { var self = this; var parent = self._parent; @@ -2553,12 +2333,12 @@ // Setup a sprite if none is defined. if (Object.keys(parent._sprite).length === 0) { - parent._sprite = { __default: [0, parent._duration * 1000] }; + parent._sprite = {__default: [0, parent._duration * 1000]}; } - if (parent._state !== "loaded") { - parent._state = "loaded"; - parent._emit("load"); + if (parent._state !== 'loaded') { + parent._state = 'loaded'; + parent._emit('load'); parent._loadQueue(); } @@ -2569,7 +2349,7 @@ /** * HTML5 Audio ended listener callback. */ - _endListener: function () { + _endListener: function() { var self = this; var parent = self._parent; @@ -2589,8 +2369,8 @@ } // Clear the event listener since the duration is now correct. - self._node.removeEventListener("ended", self._endFn, false); - }, + self._node.removeEventListener('ended', self._endFn, false); + } }; /** Helper Methods **/ @@ -2602,7 +2382,7 @@ * Buffer a sound from URL, Data URI or cache and decode to audio source (Web Audio API). * @param {Howl} self */ - var loadBuffer = function (self) { + var loadBuffer = function(self) { var url = self._src; // Check if the buffer has already been cached and use it instead. @@ -2618,9 +2398,9 @@ if (/^data:[^;]+;base64,/.test(url)) { // Decode the base64 data URI without XHR, since some browsers don't support it. - var data = atob(url.split(",")[1]); + var data = atob(url.split(',')[1]); var dataView = new Uint8Array(data.length); - for (var i = 0; i < data.length; ++i) { + for (var i=0; i 0) { cache[self._src] = buffer; loadSound(self, buffer); @@ -2701,22 +2477,19 @@ }; // Decode the buffer into an audio source. - if ( - typeof Promise !== "undefined" && - Howler.ctx.decodeAudioData.length === 1 - ) { + if (typeof Promise !== 'undefined' && Howler.ctx.decodeAudioData.length === 1) { Howler.ctx.decodeAudioData(arraybuffer).then(success).catch(error); } else { Howler.ctx.decodeAudioData(arraybuffer, success, error); } - }; + } /** * Sound is now loaded, so finish setting everything up and fire the loaded event. * @param {Howl} self * @param {Object} buffer The decoded buffer sound source. */ - var loadSound = function (self, buffer) { + var loadSound = function(self, buffer) { // Set the duration. if (buffer && !self._duration) { self._duration = buffer.duration; @@ -2724,13 +2497,13 @@ // Setup a sprite if none is defined. if (Object.keys(self._sprite).length === 0) { - self._sprite = { __default: [0, self._duration * 1000] }; + self._sprite = {__default: [0, self._duration * 1000]}; } // Fire the loaded event. - if (self._state !== "loaded") { - self._state = "loaded"; - self._emit("load"); + if (self._state !== 'loaded') { + self._state = 'loaded'; + self._emit('load'); self._loadQueue(); } }; @@ -2738,7 +2511,7 @@ /** * Setup the audio context when available, or switch to HTML5 Audio mode. */ - var setupAudioContext = function () { + var setupAudioContext = function() { // If we have already detected that Web Audio isn't supported, don't run this step again. if (!Howler.usingWebAudio) { return; @@ -2746,14 +2519,14 @@ // Check if we are using Web Audio and setup the AudioContext if we are. try { - if (typeof AudioContext !== "undefined") { + if (typeof AudioContext !== 'undefined') { Howler.ctx = new AudioContext(); - } else if (typeof webkitAudioContext !== "undefined") { + } else if (typeof webkitAudioContext !== 'undefined') { Howler.ctx = new webkitAudioContext(); } else { Howler.usingWebAudio = false; } - } catch (e) { + } catch(e) { Howler.usingWebAudio = false; } @@ -2764,17 +2537,11 @@ // Check if a webview is being used on iOS8 or earlier (rather than the browser). // If it is, disable Web Audio as it causes crashing. - var iOS = /iP(hone|od|ad)/.test( - Howler._navigator && Howler._navigator.platform - ); - var appVersion = - Howler._navigator && - Howler._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/); + var iOS = (/iP(hone|od|ad)/.test(Howler._navigator && Howler._navigator.platform)); + var appVersion = Howler._navigator && Howler._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/); var version = appVersion ? parseInt(appVersion[1], 10) : null; if (iOS && version && version < 9) { - var safari = /safari/.test( - Howler._navigator && Howler._navigator.userAgent.toLowerCase() - ); + var safari = /safari/.test(Howler._navigator && Howler._navigator.userAgent.toLowerCase()); if (Howler._navigator && !safari) { Howler.usingWebAudio = false; } @@ -2782,14 +2549,8 @@ // Create and expose the master GainNode when using Web Audio (useful for plugins or advanced usage). if (Howler.usingWebAudio) { - Howler.masterGain = - typeof Howler.ctx.createGain === "undefined" - ? Howler.ctx.createGainNode() - : Howler.ctx.createGain(); - Howler.masterGain.gain.setValueAtTime( - Howler._muted ? 0 : Howler._volume, - Howler.ctx.currentTime - ); + Howler.masterGain = (typeof Howler.ctx.createGain === 'undefined') ? Howler.ctx.createGainNode() : Howler.ctx.createGain(); + Howler.masterGain.gain.setValueAtTime(Howler._muted ? 0 : Howler._volume, Howler.ctx.currentTime); Howler.masterGain.connect(Howler.ctx.destination); } @@ -2798,29 +2559,28 @@ }; // Add support for AMD (Asynchronous Module Definition) libraries such as require.js. - if (typeof define === "function" && define.amd) { - define([], function () { + if (typeof define === 'function' && define.amd) { + define([], function() { return { Howler: Howler, - Howl: Howl, + Howl: Howl }; }); } // Add support for CommonJS libraries such as browserify. - if (typeof exports !== "undefined") { + if (typeof exports !== 'undefined') { exports.Howler = Howler; exports.Howl = Howl; } // Add to global in Node.js (for testing, etc). - if (typeof global !== "undefined") { + if (typeof global !== 'undefined') { global.HowlerGlobal = HowlerGlobal; global.Howler = Howler; global.Howl = Howl; global.Sound = Sound; - } else if (typeof window !== "undefined") { - // Define globally in case AMD is not available or unused. + } else if (typeof window !== 'undefined') { // Define globally in case AMD is not available or unused. window.HowlerGlobal = HowlerGlobal; window.Howler = Howler; window.Howl = Howl;