diff --git a/asset-manifest.json b/asset-manifest.json new file mode 100644 index 0000000..9ac01da --- /dev/null +++ b/asset-manifest.json @@ -0,0 +1,19 @@ +{ + "files": { + "main.css": "/tic-tac-toe/static/css/main.f2194a03.css", + "main.js": "/tic-tac-toe/static/js/main.8cc180a6.js", + "static/js/845.a5d059c7.chunk.js": "/tic-tac-toe/static/js/845.a5d059c7.chunk.js", + "static/media/newGame.wav": "/tic-tac-toe/static/media/newGame.ab583e220d9d8c0b969c.wav", + "static/media/win.mp3": "/tic-tac-toe/static/media/win.0020897289ad8c523adb.mp3", + "static/media/tie.mp3": "/tic-tac-toe/static/media/tie.c7095a48ea6657c8d942.mp3", + "static/media/btnPopUp.mp3": "/tic-tac-toe/static/media/btnPopUp.27c066957dbe687b7e7d.mp3", + "index.html": "/tic-tac-toe/index.html", + "main.f2194a03.css.map": "/tic-tac-toe/static/css/main.f2194a03.css.map", + "main.8cc180a6.js.map": "/tic-tac-toe/static/js/main.8cc180a6.js.map", + "845.a5d059c7.chunk.js.map": "/tic-tac-toe/static/js/845.a5d059c7.chunk.js.map" + }, + "entrypoints": [ + "static/css/main.f2194a03.css", + "static/js/main.8cc180a6.js" + ] +} \ No newline at end of file diff --git a/favicon.png b/favicon.png new file mode 100644 index 0000000..a9ec0ab Binary files /dev/null and b/favicon.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..1a43c23 --- /dev/null +++ b/index.html @@ -0,0 +1 @@ +Tic Tac Toe
\ No newline at end of file diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/static/css/main.f2194a03.css b/static/css/main.f2194a03.css new file mode 100644 index 0000000..346466d --- /dev/null +++ b/static/css/main.f2194a03.css @@ -0,0 +1,2 @@ +body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;margin:0}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}*{box-sizing:border-box;margin:0;padding:0}h1{border-radius:3px;box-sizing:initial;font-family:"Noto Serif",serif;font-size:3rem;margin:2rem auto;padding:.2rem .6rem;width:18rem}.main{text-align:center}html{font-size:100%}@media (max-width:300px){html{font-size:80%}}.board{display:grid;grid-template-columns:repeat(3,6rem);grid-template-rows:repeat(3,6rem);justify-content:center;place-items:center}.board>div{border:5px solid #020202bb;height:100%;width:100%}.board>div:nth-child(3n+1){border-left:none}.board>div:nth-child(3n){border-right:none}.board>div:nth-child(-n+3){border-top:none}.board>div:nth-child(n+7){border-bottom:none}.square{background-color:#f5f5f5;border:none;font-family:Fredoka,sans-serif;font-size:5rem;height:100%;line-height:100%;width:100%}.x{color:red}.o{color:blue}.xHover:hover{background-color:#f003;cursor:pointer}.oHover:hover{background-color:#0000ff40;cursor:pointer}.animate-font{animation:fontPopUp .4s}@keyframes fontPopUp{0%{font-size:0}to{font-size:5rem}}.scoreBoard{align-items:center;border-radius:1px;box-shadow:2px 2px 6px #cecccc,-1px -1px 5px #cecccc;display:flex;justify-content:space-around;margin:3rem auto auto;padding:.5rem;width:18rem}.forO,.forX{transition:color .3s}.forX>div:first-child{font-family:Fredoka,sans-serif;font-size:2.2rem}.forO>div:first-child{font-family:Fredoka,sans-serif;font-size:2.1rem}.forDraw>div:first-child{font-family:Fredoka,sans-serif;font-size:1.8rem;padding-top:3px}.forO>div:nth-child(2),.forX>div:nth-child(2){font-size:.6rem;font-weight:700}.forDraw>div:nth-child(2){font-size:.8rem;font-weight:600;margin-top:2px;padding-bottom:2px}.modal-overlay{align-items:center;background:#00000080;display:flex;height:100%;justify-content:center;left:0;position:fixed;top:0;width:100%}.modal{animation:popUp .3s ease-out;background:#fff;border-radius:8px;box-shadow:0 0 10px #00000080;max-width:24rem;padding:10px;width:100%}.modal-content{border:2px solid;padding:10px;text-align:center}h2{font-family:"Noto Serif",serif;font-size:3rem;margin:1rem}.winnerX{border:3px solid red;border-radius:2px;color:red;padding:0 1.3rem}.winnerO,.winnerX{background-color:#fff;font-family:Fredoka,sans-serif;font-size:3rem;height:4rem;margin:.2rem;width:5rem}.winnerO{border:3px solid blue;border-radius:2px;color:blue;padding:0 1rem}.modalBtns{background-color:#000;border-radius:4px;color:#fff;cursor:pointer;font-size:14px;letter-spacing:1px;margin:1rem .3rem;padding:8px 16px}.modalBtns,.modalBtns:hover{border:2px solid #000;font-weight:700}.modalBtns:hover{background-color:#fff;box-sizing:initial;color:#000}@keyframes popUp{0%{transform:scale(.1)}to{transform:scale(1)}} +/*# sourceMappingURL=main.f2194a03.css.map*/ \ No newline at end of file diff --git a/static/css/main.f2194a03.css.map b/static/css/main.f2194a03.css.map new file mode 100644 index 0000000..45e41c4 --- /dev/null +++ b/static/css/main.f2194a03.css.map @@ -0,0 +1 @@ +{"version":3,"file":"static/css/main.f2194a03.css","mappings":"AAAA,KAKE,kCAAmC,CACnC,iCAAkC,CAJlC,mIAEY,CAHZ,QAMF,CAEA,KACE,uEAEF,CCZA,EACE,qBAAsB,CACtB,QAAS,CACT,SACF,CAEA,GAOE,iBAAkB,CAHlB,kBAAuB,CAHvB,8BAAgC,CAChC,cAAe,CAIf,gBAAiB,CADjB,mBAAsB,CAFtB,WAKF,CAEA,MACE,iBACF,CAEA,KACE,cACF,CAEA,yBACE,KACI,aACJ,CACF,CC5BA,OACI,YAAa,CACb,oCAAsC,CACtC,iCAAmC,CAEnC,sBAAuB,CADvB,kBAEJ,CAEA,WAGI,0BAA2B,CAD3B,WAAY,CADZ,UAGJ,CAEA,2BACI,gBACJ,CAEA,yBACI,iBACJ,CAEA,2BACI,eACJ,CAEA,0BACI,kBACJ,CC5BA,QAKI,wBAA4B,CAJ5B,WAAY,CAMZ,8BAAkC,CADlC,cAAe,CAHf,WAAY,CACZ,gBAAiB,CAFjB,UAMJ,CAEA,GACI,SACJ,CAEA,GACI,UACJ,CAEA,cACI,sBAAuC,CACvC,cACJ,CAEA,cACI,0BAAuC,CACvC,cACJ,CAEA,cACI,uBACJ,CAEA,qBACI,GACI,WACJ,CAEA,GACI,cACJ,CACJ,CCxCA,YAGI,kBAAmB,CAInB,iBAAkB,CAClB,oDAA4E,CAP5E,YAAa,CACb,4BAA6B,CAO7B,qBAAgB,CAHhB,aAAe,CAFf,WAMJ,CAMA,YACI,oBACJ,CAEA,sBAEI,8BAAkC,CADlC,gBAEJ,CAEA,sBAEI,8BAAkC,CADlC,gBAEJ,CAEA,yBAGI,8BAAkC,CAFlC,gBAAiB,CACjB,eAEJ,CAOA,8CAEI,eAAiB,CADjB,eAEJ,CAEA,0BAEI,eAAiB,CADjB,eAAgB,CAGhB,cAAe,CADf,kBAEJ,CCnDA,eAQE,kBAAmB,CAFnB,oBAA8B,CAC9B,YAAa,CAFb,WAAY,CAIZ,sBAAuB,CANvB,MAAO,CAFP,cAAe,CACf,KAAM,CAEN,UAMF,CAEA,OAOE,4BAA8B,CAN9B,eAAiB,CACjB,iBAAkB,CAClB,6BAAuC,CAEvC,eAAgB,CADhB,YAAa,CAEb,UAEF,CAEA,eAEE,gBAAiB,CACjB,YAAa,CAFb,iBAGF,CAEA,GACE,8BAAgC,CAChC,cAAe,CACf,WACF,CAEA,SAEE,oBAAqB,CACrB,iBAAkB,CAElB,SAAU,CACV,gBAKF,CAEA,kBATE,qBAAuB,CAHvB,8BAAkC,CASlC,cAAe,CAFf,WAAY,CACZ,YAAc,CAFd,UAiBF,CAXA,SAEE,qBAAsB,CACtB,iBAAkB,CAElB,UAAW,CACX,cAKF,CAEA,WACE,qBAAyB,CAIzB,iBAAkB,CAHlB,UAAY,CAIZ,cAAe,CACf,cAAe,CAIf,kBAAmB,CAHnB,iBAAmB,CALnB,gBASF,CAEA,4BALE,qBAAuB,CACvB,eAUF,CANA,iBACE,qBAAyB,CAGzB,kBAAuB,CADvB,UAGF,CAEA,iBACE,GACI,mBACJ,CAEA,GACI,kBACJ,CACF","sources":["index.css","App.css","components/Board/Board.css","components/Button/Button.css","components/ScoreBoard/ScoreBoard.css","components/Modal/Modal.css"],"sourcesContent":["body {\r\n margin: 0;\r\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\r\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\r\n sans-serif;\r\n -webkit-font-smoothing: antialiased;\r\n -moz-osx-font-smoothing: grayscale;\r\n}\r\n\r\ncode {\r\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\r\n monospace;\r\n}\r\n","* {\r\n box-sizing: border-box;\r\n margin: 0;\r\n padding: 0;\r\n}\r\n\r\nh1 {\r\n font-family: 'Noto Serif', serif;\r\n font-size: 3rem;\r\n width: 18rem;\r\n box-sizing: content-box;\r\n padding: 0.2rem 0.6rem;\r\n margin: 2rem auto;\r\n border-radius: 3px;\r\n}\r\n\r\n.main {\r\n text-align: center;\r\n}\r\n\r\nhtml {\r\n font-size: 100%;\r\n}\r\n\r\n@media (max-width: 300px) {\r\n html {\r\n font-size: 80%;\r\n }\r\n}",".board {\r\n display: grid;\r\n grid-template-columns: repeat(3, 6rem);\r\n grid-template-rows: repeat(3, 6rem);\r\n place-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.board>div {\r\n width: 100%;\r\n height: 100%;\r\n border: 5px solid #020202bb;\r\n}\r\n\r\n.board>div:nth-child(3n + 1) {\r\n border-left: none;\r\n}\r\n\r\n.board>div:nth-child(3n) {\r\n border-right: none;\r\n}\r\n\r\n.board>div:nth-child(-n+3) {\r\n border-top: none;\r\n}\r\n\r\n.board>div:nth-child(n+7) {\r\n border-bottom: none;\r\n}",".square {\r\n border: none;\r\n width: 100%;\r\n height: 100%;\r\n line-height: 100%;\r\n background-color: whitesmoke;\r\n font-size: 5rem;\r\n font-family: 'Fredoka', sans-serif;\r\n}\r\n\r\n.x {\r\n color: red;\r\n}\r\n\r\n.o {\r\n color: blue;\r\n}\r\n\r\n.xHover:hover {\r\n background-color: rgba(255, 0, 0, 0.20);\r\n cursor: pointer;\r\n}\r\n\r\n.oHover:hover {\r\n background-color: rgba(0, 0, 255, 0.25);\r\n cursor: pointer;\r\n}\r\n\r\n.animate-font {\r\n animation: fontPopUp 0.4s;\r\n}\r\n\r\n@keyframes fontPopUp {\r\n 0% {\r\n font-size: 0rem;\r\n }\r\n\r\n 100% {\r\n font-size: 5rem;\r\n }\r\n}",".scoreBoard {\r\n display: flex;\r\n justify-content: space-around;\r\n align-items: center;\r\n width: 18rem;\r\n margin: auto;\r\n padding: 0.5rem;\r\n border-radius: 1px;\r\n box-shadow: 2px 2px 6px rgb(206, 204, 204), -1px -1px 5px rgb(206, 204, 204);\r\n margin-top: 3rem;\r\n}\r\n\r\n.forX {\r\n transition: color 0.3s;\r\n}\r\n\r\n.forO {\r\n transition: color 0.3s;\r\n}\r\n\r\n.forX>div:nth-child(1) {\r\n font-size: 2.2rem;\r\n font-family: 'Fredoka', sans-serif;\r\n}\r\n\r\n.forO>div:nth-child(1) {\r\n font-size: 2.1rem;\r\n font-family: 'Fredoka', sans-serif;\r\n}\r\n\r\n.forDraw>div:nth-child(1) {\r\n font-size: 1.8rem;\r\n padding-top: 3px;\r\n font-family: 'Fredoka', sans-serif;\r\n}\r\n\r\n.forX>div:nth-child(2) {\r\n font-weight: bold;\r\n font-size: 0.6rem;\r\n}\r\n\r\n.forO>div:nth-child(2) {\r\n font-weight: bold;\r\n font-size: 0.6rem;\r\n}\r\n\r\n.forDraw>div:nth-child(2) {\r\n font-weight: 600;\r\n font-size: 0.8rem;\r\n padding-bottom: 2px;\r\n margin-top: 2px;\r\n}",".modal-overlay {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n background: rgba(0, 0, 0, 0.5);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n.modal {\r\n background: white;\r\n border-radius: 8px;\r\n box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);\r\n padding: 10px;\r\n max-width: 24rem;\r\n width: 100%;\r\n animation: popUp 0.3s ease-out;\r\n}\r\n\r\n.modal-content {\r\n text-align: center;\r\n border: 2px solid;\r\n padding: 10px;\r\n}\r\n\r\nh2 {\r\n font-family: 'Noto Serif', serif;\r\n font-size: 3rem;\r\n margin: 1rem;\r\n}\r\n\r\n.winnerX {\r\n font-family: 'Fredoka', sans-serif;\r\n border: 3px solid red;\r\n border-radius: 2px;\r\n background-color: white;\r\n color: red;\r\n padding: 0px 1.3rem;\r\n width: 5rem;\r\n height: 4rem;\r\n margin: 0.2rem;\r\n font-size: 3rem;\r\n}\r\n\r\n.winnerO {\r\n font-family: 'Fredoka', sans-serif;\r\n border: 3px solid blue;\r\n border-radius: 2px;\r\n background-color: white;\r\n color: blue;\r\n padding: 0px 1rem;\r\n width: 5rem;\r\n height: 4rem;\r\n margin: 0.2rem;\r\n font-size: 3rem;\r\n}\r\n\r\n.modalBtns {\r\n background-color: #000000;\r\n color: white;\r\n padding: 8px 16px;\r\n border: none;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n font-size: 14px;\r\n margin: 1rem 0.3rem;\r\n border: 2px solid black;\r\n font-weight: bold;\r\n letter-spacing: 1px;\r\n}\r\n\r\n.modalBtns:hover {\r\n background-color: #ffffff;\r\n border: 2px solid black;\r\n color: black;\r\n box-sizing: content-box;\r\n font-weight: bold;\r\n}\r\n\r\n@keyframes popUp {\r\n 0% {\r\n transform: scale(0.1);\r\n }\r\n\r\n 100% {\r\n transform: scale(1);\r\n }\r\n}"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/static/js/845.a5d059c7.chunk.js b/static/js/845.a5d059c7.chunk.js new file mode 100644 index 0000000..d4a5935 --- /dev/null +++ b/static/js/845.a5d059c7.chunk.js @@ -0,0 +1,3 @@ +/*! For license information please see 845.a5d059c7.chunk.js.LICENSE.txt */ +(self.webpackChunktic_tac_toe=self.webpackChunktic_tac_toe||[]).push([[845],{845:(e,n,t)=>{var o;!function(){"use strict";var r=function(){this.init()};r.prototype={init:function(){var e=this||a;return e._counter=1e3,e._html5AudioPool=[],e.html5PoolSize=10,e._codecs={},e._howls=[],e._muted=!1,e._volume=1,e._canPlayEvent="canplaythrough",e._navigator="undefined"!==typeof window&&window.navigator?window.navigator:null,e.masterGain=null,e.noAudio=!1,e.usingWebAudio=!0,e.autoSuspend=!0,e.ctx=null,e.autoUnlock=!0,e._setup(),e},volume:function(e){var n=this||a;if(e=parseFloat(e),n.ctx||p(),"undefined"!==typeof e&&e>=0&&e<=1){if(n._volume=e,n._muted)return n;n.usingWebAudio&&n.masterGain.gain.setValueAtTime(e,a.ctx.currentTime);for(var t=0;t=0;n--)e._howls[n].unload();return e.usingWebAudio&&e.ctx&&"undefined"!==typeof e.ctx.close&&(e.ctx.close(),e.ctx=null,p()),e},codecs:function(e){return(this||a)._codecs[e.replace(/^x-/,"")]},_setup:function(){var e=this||a;if(e.state=e.ctx&&e.ctx.state||"suspended",e._autoSuspend(),!e.usingWebAudio)if("undefined"!==typeof Audio)try{"undefined"===typeof(new Audio).oncanplaythrough&&(e._canPlayEvent="canplay")}catch(n){e.noAudio=!0}else e.noAudio=!0;try{(new Audio).muted&&(e.noAudio=!0)}catch(n){}return e.noAudio||e._setupCodecs(),e},_setupCodecs:function(){var e=this||a,n=null;try{n="undefined"!==typeof Audio?new Audio:null}catch(_){return e}if(!n||"function"!==typeof n.canPlayType)return e;var t=n.canPlayType("audio/mpeg;").replace(/^no$/,""),o=e._navigator?e._navigator.userAgent:"",r=o.match(/OPR\/(\d+)/g),i=r&&parseInt(r[0].split("/")[1],10)<33,u=-1!==o.indexOf("Safari")&&-1===o.indexOf("Chrome"),d=o.match(/Version\/(.*?) /),s=u&&d&&parseInt(d[1],10)<15;return e._codecs={mp3:!(i||!t&&!n.canPlayType("audio/mp3;").replace(/^no$/,"")),mpeg:!!t,opus:!!n.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/,""),ogg:!!n.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),oga:!!n.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),wav:!!(n.canPlayType('audio/wav; codecs="1"')||n.canPlayType("audio/wav")).replace(/^no$/,""),aac:!!n.canPlayType("audio/aac;").replace(/^no$/,""),caf:!!n.canPlayType("audio/x-caf;").replace(/^no$/,""),m4a:!!(n.canPlayType("audio/x-m4a;")||n.canPlayType("audio/m4a;")||n.canPlayType("audio/aac;")).replace(/^no$/,""),m4b:!!(n.canPlayType("audio/x-m4b;")||n.canPlayType("audio/m4b;")||n.canPlayType("audio/aac;")).replace(/^no$/,""),mp4:!!(n.canPlayType("audio/x-mp4;")||n.canPlayType("audio/mp4;")||n.canPlayType("audio/aac;")).replace(/^no$/,""),weba:!(s||!n.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")),webm:!(s||!n.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")),dolby:!!n.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/,""),flac:!!(n.canPlayType("audio/x-flac;")||n.canPlayType("audio/flac;")).replace(/^no$/,"")},e},_unlockAudio:function(){var e=this||a;if(!e._audioUnlocked&&e.ctx){e._audioUnlocked=!1,e.autoUnlock=!1,e._mobileUnloaded||44100===e.ctx.sampleRate||(e._mobileUnloaded=!0,e.unload()),e._scratchBuffer=e.ctx.createBuffer(1,1,22050);var n=function(t){for(;e._html5AudioPool.length0?u._seek:t._sprite[e][0]/1e3),_=Math.max(0,(t._sprite[e][0]+t._sprite[e][1])/1e3-s),l=1e3*_/Math.abs(u._rate),c=t._sprite[e][0]/1e3,p=(t._sprite[e][0]+t._sprite[e][1])/1e3;u._sprite=e,u._ended=!1;var f=function(){u._paused=!1,u._seek=s,u._start=c,u._stop=p,u._loop=!(!u._loop&&!t._sprite[e][2])};if(!(s>=p)){var m=u._node;if(t._webAudio){var h=function(){t._playLock=!1,f(),t._refreshBuffer(u);var e=u._muted||t._muted?0:u._volume;m.gain.setValueAtTime(e,a.ctx.currentTime),u._playStart=a.ctx.currentTime,"undefined"===typeof m.bufferSource.start?u._loop?m.bufferSource.noteGrainOn(0,s,86400):m.bufferSource.noteGrainOn(0,s,_):u._loop?m.bufferSource.start(0,s,86400):m.bufferSource.start(0,s,_),l!==1/0&&(t._endTimers[u._id]=setTimeout(t._ended.bind(t,u),l)),n||setTimeout((function(){t._emit("play",u._id),t._loadQueue()}),0)};"running"===a.state&&"interrupted"!==a.ctx.state?h():(t._playLock=!0,t.once("resume",h),t._clearTimer(u._id))}else{var y=function(){m.currentTime=s,m.muted=u._muted||t._muted||a._muted||m.muted,m.volume=u._volume*a.volume(),m.playbackRate=u._rate;try{var o=m.play();if(o&&"undefined"!==typeof Promise&&(o instanceof Promise||"function"===typeof o.then)?(t._playLock=!0,f(),o.then((function(){t._playLock=!1,m._unlocked=!0,n?t._loadQueue():t._emit("play",u._id)})).catch((function(){t._playLock=!1,t._emit("playerror",u._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."),u._ended=!0,u._paused=!0}))):n||(t._playLock=!1,f(),t._emit("play",u._id)),m.playbackRate=u._rate,m.paused)return void t._emit("playerror",u._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.");"__default"!==e||u._loop?t._endTimers[u._id]=setTimeout(t._ended.bind(t,u),l):(t._endTimers[u._id]=function(){t._ended(u),m.removeEventListener("ended",t._endTimers[u._id],!1)},m.addEventListener("ended",t._endTimers[u._id],!1))}catch(r){t._emit("playerror",u._id,r)}};"data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"===m.src&&(m.src=t._src,m.load());var v=window&&window.ejecta||!m.readyState&&a._navigator.isCocoonJS;if(m.readyState>=3||v)y();else{t._playLock=!0,t._state="loading";var A=function(){t._state="loaded",y(),m.removeEventListener(a._canPlayEvent,A,!1)};m.addEventListener(a._canPlayEvent,A,!1),t._clearTimer(u._id)}}return u._id}t._ended(u)},pause:function(e){var n=this;if("loaded"!==n._state||n._playLock)return n._queue.push({event:"pause",action:function(){n.pause(e)}}),n;for(var t=n._getSoundIds(e),o=0;o=0?n=parseInt(r[0],10):e=parseFloat(r[0]):r.length>=2&&(e=parseFloat(r[0]),n=parseInt(r[1],10)),!("undefined"!==typeof e&&e>=0&&e<=1))return(t=n?o._soundById(n):o._sounds[0])?t._volume:0;if("loaded"!==o._state||o._playLock)return o._queue.push({event:"volume",action:function(){o.volume.apply(o,r)}}),o;"undefined"===typeof n&&(o._volume=e),n=o._getSoundIds(n);for(var i=0;i0?o/s:o),l=Date.now();e._fadeTo=t,e._interval=setInterval((function(){var r=(Date.now()-l)/o;l=Date.now(),u+=d*r,u=Math.round(100*u)/100,u=d<0?Math.max(t,u):Math.min(t,u),i._webAudio?e._volume=u:i.volume(u,e._id,!0),a&&(i._volume=u),(tn&&u>=t)&&(clearInterval(e._interval),e._interval=null,e._fadeTo=null,i.volume(t,e._id),i._emit("fade",e._id))}),_)},_stopFade:function(e){var n=this,t=n._soundById(e);return t&&t._interval&&(n._webAudio&&t._node.gain.cancelScheduledValues(a.ctx.currentTime),clearInterval(t._interval),t._interval=null,n.volume(t._fadeTo,e),t._fadeTo=null,n._emit("fade",e)),n},loop:function(){var e,n,t,o=this,r=arguments;if(0===r.length)return o._loop;if(1===r.length){if("boolean"!==typeof r[0])return!!(t=o._soundById(parseInt(r[0],10)))&&t._loop;e=r[0],o._loop=e}else 2===r.length&&(e=r[0],n=parseInt(r[1],10));for(var a=o._getSoundIds(n),i=0;i=0?n=parseInt(r[0],10):e=parseFloat(r[0])}else 2===r.length&&(e=parseFloat(r[0]),n=parseInt(r[1],10));if("number"!==typeof e)return(t=o._soundById(n))?t._rate:o._rate;if("loaded"!==o._state||o._playLock)return o._queue.push({event:"rate",action:function(){o.rate.apply(o,r)}}),o;"undefined"===typeof n&&(o._rate=e),n=o._getSoundIds(n);for(var i=0;i=0?n=parseInt(o[0],10):t._sounds.length&&(n=t._sounds[0]._id,e=parseFloat(o[0]))}else 2===o.length&&(e=parseFloat(o[0]),n=parseInt(o[1],10));if("undefined"===typeof n)return 0;if("number"===typeof e&&("loaded"!==t._state||t._playLock))return t._queue.push({event:"seek",action:function(){t.seek.apply(t,o)}}),t;var r=t._soundById(n);if(r){if(!("number"===typeof e&&e>=0)){if(t._webAudio){var i=t.playing(n)?a.ctx.currentTime-r._playStart:0,u=r._rateSeek?r._rateSeek-r._seek:0;return r._seek+(u+i*Math.abs(r._rate))}return r._node.currentTime}var d=t.playing(n);d&&t.pause(n,!0),r._seek=e,r._ended=!1,t._clearTimer(n),t._webAudio||!r._node||isNaN(r._node.duration)||(r._node.currentTime=e);var s=function(){d&&t.play(n,!0),t._emit("seek",n)};if(d&&!t._webAudio){var _=function(){t._playLock?setTimeout(_,0):s()};setTimeout(_,0)}else s()}return t},playing:function(e){var n=this;if("number"===typeof e){var t=n._soundById(e);return!!t&&!t._paused}for(var o=0;o=0&&a._howls.splice(o,1);var r=!0;for(t=0;t=0){r=!1;break}return d&&r&&delete d[e._src],a.noAudio=!1,e._state="unloaded",e._sounds=[],e=null,null},on:function(e,n,t,o){var r=this["_on"+e];return"function"===typeof n&&r.push(o?{id:t,fn:n,once:o}:{id:t,fn:n}),this},off:function(e,n,t){var o=this,r=o["_on"+e],a=0;if("number"===typeof n&&(t=n,n=null),n||t)for(a=0;a=0;a--)r[a].id&&r[a].id!==n&&"load"!==e||(setTimeout(function(e){e.call(this,n,t)}.bind(o,r[a].fn),0),r[a].once&&o.off(e,r[a].fn,r[a].id));return o._loadQueue(e),o},_loadQueue:function(e){var n=this;if(n._queue.length>0){var t=n._queue[0];t.event===e&&(n._queue.shift(),n._loadQueue()),e||t.action()}return n},_ended:function(e){var n=this,t=e._sprite;if(!n._webAudio&&e._node&&!e._node.paused&&!e._node.ended&&e._node.currentTime=0;o--){if(t<=n)return;e._sounds[o]._ended&&(e._webAudio&&e._sounds[o]._node&&e._sounds[o]._node.disconnect(0),e._sounds.splice(o,1),t--)}}},_getSoundIds:function(e){if("undefined"===typeof e){for(var n=[],t=0;t=0;if(!e.bufferSource)return this;if(a._scratchBuffer&&e.bufferSource&&(e.bufferSource.onended=null,e.bufferSource.disconnect(0),n))try{e.bufferSource.buffer=a._scratchBuffer}catch(t){}return e.bufferSource=null,this},_clearSound:function(e){/MSIE |Trident\//.test(a._navigator&&a._navigator.userAgent)||(e.src="data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA")}};var u=function(e){this._parent=e,this.init()};u.prototype={init:function(){var e=this,n=e._parent;return e._muted=n._muted,e._loop=n._loop,e._volume=n._volume,e._rate=n._rate,e._seek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++a._counter,n._sounds.push(e),e.create(),e},create:function(){var e=this,n=e._parent,t=a._muted||e._muted||e._parent._muted?0:e._volume;return n._webAudio?(e._node="undefined"===typeof a.ctx.createGain?a.ctx.createGainNode():a.ctx.createGain(),e._node.gain.setValueAtTime(t,a.ctx.currentTime),e._node.paused=!0,e._node.connect(a.masterGain)):a.noAudio||(e._node=a._obtainHtml5Audio(),e._errorFn=e._errorListener.bind(e),e._node.addEventListener("error",e._errorFn,!1),e._loadFn=e._loadListener.bind(e),e._node.addEventListener(a._canPlayEvent,e._loadFn,!1),e._endFn=e._endListener.bind(e),e._node.addEventListener("ended",e._endFn,!1),e._node.src=n._src,e._node.preload=!0===n._preload?"auto":n._preload,e._node.volume=t*a.volume(),e._node.load()),e},reset:function(){var e=this,n=e._parent;return e._muted=n._muted,e._loop=n._loop,e._volume=n._volume,e._rate=n._rate,e._seek=0,e._rateSeek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++a._counter,e},_errorListener:function(){var e=this;e._parent._emit("loaderror",e._id,e._node.error?e._node.error.code:0),e._node.removeEventListener("error",e._errorFn,!1)},_loadListener:function(){var e=this,n=e._parent;n._duration=Math.ceil(10*e._node.duration)/10,0===Object.keys(n._sprite).length&&(n._sprite={__default:[0,1e3*n._duration]}),"loaded"!==n._state&&(n._state="loaded",n._emit("load"),n._loadQueue()),e._node.removeEventListener(a._canPlayEvent,e._loadFn,!1)},_endListener:function(){var e=this,n=e._parent;n._duration===1/0&&(n._duration=Math.ceil(10*e._node.duration)/10,n._sprite.__default[1]===1/0&&(n._sprite.__default[1]=1e3*n._duration),n._ended(e)),e._node.removeEventListener("ended",e._endFn,!1)}};var d={},s=function(e){var n=e._src;if(d[n])return e._duration=d[n].duration,void c(e);if(/^data:[^;]+;base64,/.test(n)){for(var t=atob(n.split(",")[1]),o=new Uint8Array(t.length),r=0;r0?(d[n._src]=e,c(n,e)):t()};"undefined"!==typeof Promise&&1===a.ctx.decodeAudioData.length?a.ctx.decodeAudioData(e).then(o).catch(t):a.ctx.decodeAudioData(e,o,t)},c=function(e,n){n&&!e._duration&&(e._duration=n.duration),0===Object.keys(e._sprite).length&&(e._sprite={__default:[0,1e3*e._duration]}),"loaded"!==e._state&&(e._state="loaded",e._emit("load"),e._loadQueue())},p=function(){if(a.usingWebAudio){try{"undefined"!==typeof AudioContext?a.ctx=new AudioContext:"undefined"!==typeof webkitAudioContext?a.ctx=new webkitAudioContext:a.usingWebAudio=!1}catch(r){a.usingWebAudio=!1}a.ctx||(a.usingWebAudio=!1);var e=/iP(hone|od|ad)/.test(a._navigator&&a._navigator.platform),n=a._navigator&&a._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/),t=n?parseInt(n[1],10):null;if(e&&t&&t<9){var o=/safari/.test(a._navigator&&a._navigator.userAgent.toLowerCase());a._navigator&&!o&&(a.usingWebAudio=!1)}a.usingWebAudio&&(a.masterGain="undefined"===typeof a.ctx.createGain?a.ctx.createGainNode():a.ctx.createGain(),a.masterGain.gain.setValueAtTime(a._muted?0:a._volume,a.ctx.currentTime),a.masterGain.connect(a.ctx.destination)),a._setup()}};void 0===(o=function(){return{Howler:a,Howl:i}}.apply(n,[]))||(e.exports=o),n.Howler=a,n.Howl=i,"undefined"!==typeof t.g?(t.g.HowlerGlobal=r,t.g.Howler=a,t.g.Howl=i,t.g.Sound=u):"undefined"!==typeof window&&(window.HowlerGlobal=r,window.Howler=a,window.Howl=i,window.Sound=u)}(),function(){"use strict";var e;HowlerGlobal.prototype._pos=[0,0,0],HowlerGlobal.prototype._orientation=[0,0,-1,0,1,0],HowlerGlobal.prototype.stereo=function(e){var n=this;if(!n.ctx||!n.ctx.listener)return n;for(var t=n._howls.length-1;t>=0;t--)n._howls[t].stereo(e);return n},HowlerGlobal.prototype.pos=function(e,n,t){var o=this;return o.ctx&&o.ctx.listener?(n="number"!==typeof n?o._pos[1]:n,t="number"!==typeof t?o._pos[2]:t,"number"!==typeof e?o._pos:(o._pos=[e,n,t],"undefined"!==typeof o.ctx.listener.positionX?(o.ctx.listener.positionX.setTargetAtTime(o._pos[0],Howler.ctx.currentTime,.1),o.ctx.listener.positionY.setTargetAtTime(o._pos[1],Howler.ctx.currentTime,.1),o.ctx.listener.positionZ.setTargetAtTime(o._pos[2],Howler.ctx.currentTime,.1)):o.ctx.listener.setPosition(o._pos[0],o._pos[1],o._pos[2]),o)):o},HowlerGlobal.prototype.orientation=function(e,n,t,o,r,a){var i=this;if(!i.ctx||!i.ctx.listener)return i;var u=i._orientation;return n="number"!==typeof n?u[1]:n,t="number"!==typeof t?u[2]:t,o="number"!==typeof o?u[3]:o,r="number"!==typeof r?u[4]:r,a="number"!==typeof a?u[5]:a,"number"!==typeof e?u:(i._orientation=[e,n,t,o,r,a],"undefined"!==typeof i.ctx.listener.forwardX?(i.ctx.listener.forwardX.setTargetAtTime(e,Howler.ctx.currentTime,.1),i.ctx.listener.forwardY.setTargetAtTime(n,Howler.ctx.currentTime,.1),i.ctx.listener.forwardZ.setTargetAtTime(t,Howler.ctx.currentTime,.1),i.ctx.listener.upX.setTargetAtTime(o,Howler.ctx.currentTime,.1),i.ctx.listener.upY.setTargetAtTime(r,Howler.ctx.currentTime,.1),i.ctx.listener.upZ.setTargetAtTime(a,Howler.ctx.currentTime,.1)):i.ctx.listener.setOrientation(e,n,t,o,r,a),i)},Howl.prototype.init=(e=Howl.prototype.init,function(n){var t=this;return t._orientation=n.orientation||[1,0,0],t._stereo=n.stereo||null,t._pos=n.pos||null,t._pannerAttr={coneInnerAngle:"undefined"!==typeof n.coneInnerAngle?n.coneInnerAngle:360,coneOuterAngle:"undefined"!==typeof n.coneOuterAngle?n.coneOuterAngle:360,coneOuterGain:"undefined"!==typeof n.coneOuterGain?n.coneOuterGain:0,distanceModel:"undefined"!==typeof n.distanceModel?n.distanceModel:"inverse",maxDistance:"undefined"!==typeof n.maxDistance?n.maxDistance:1e4,panningModel:"undefined"!==typeof n.panningModel?n.panningModel:"HRTF",refDistance:"undefined"!==typeof n.refDistance?n.refDistance:1,rolloffFactor:"undefined"!==typeof n.rolloffFactor?n.rolloffFactor:1},t._onstereo=n.onstereo?[{fn:n.onstereo}]:[],t._onpos=n.onpos?[{fn:n.onpos}]:[],t._onorientation=n.onorientation?[{fn:n.onorientation}]:[],e.call(this,n)}),Howl.prototype.stereo=function(e,t){var o=this;if(!o._webAudio)return o;if("loaded"!==o._state)return o._queue.push({event:"stereo",action:function(){o.stereo(e,t)}}),o;var r="undefined"===typeof Howler.ctx.createStereoPanner?"spatial":"stereo";if("undefined"===typeof t){if("number"!==typeof e)return o._stereo;o._stereo=e,o._pos=[e,0,0]}for(var a=o._getSoundIds(t),i=0;i= 0 && vol <= 1) {\n self._volume = vol;\n\n // Don't update any of the nodes if we are muted.\n if (self._muted) {\n return self;\n }\n\n // When using Web Audio, we just need to adjust the master gain.\n if (self.usingWebAudio) {\n self.masterGain.gain.setValueAtTime(vol, Howler.ctx.currentTime);\n }\n\n // Loop through and change volume for all HTML5 audio nodes.\n for (var i=0; i=0; i--) {\n self._howls[i].unload();\n }\n\n // Create a new AudioContext to make sure it is fully reset.\n if (self.usingWebAudio && self.ctx && typeof self.ctx.close !== 'undefined') {\n self.ctx.close();\n self.ctx = null;\n setupAudioContext();\n }\n\n return self;\n },\n\n /**\n * Check for codec support of specific extension.\n * @param {String} ext Audio file extention.\n * @return {Boolean}\n */\n codecs: function(ext) {\n return (this || Howler)._codecs[ext.replace(/^x-/, '')];\n },\n\n /**\n * Setup various state values for global tracking.\n * @return {Howler}\n */\n _setup: function() {\n var self = this || Howler;\n\n // Keeps track of the suspend/resume state of the AudioContext.\n self.state = self.ctx ? self.ctx.state || 'suspended' : 'suspended';\n\n // Automatically begin the 30-second suspend process\n self._autoSuspend();\n\n // Check if audio is available.\n if (!self.usingWebAudio) {\n // No audio is available on this system if noAudio is set to true.\n if (typeof Audio !== 'undefined') {\n try {\n var test = new Audio();\n\n // Check if the canplaythrough event is available.\n if (typeof test.oncanplaythrough === 'undefined') {\n self._canPlayEvent = 'canplay';\n }\n } catch(e) {\n self.noAudio = true;\n }\n } else {\n self.noAudio = true;\n }\n }\n\n // Test to make sure audio isn't disabled in Internet Explorer.\n try {\n var test = new Audio();\n if (test.muted) {\n self.noAudio = true;\n }\n } catch (e) {}\n\n // Check for supported codecs.\n if (!self.noAudio) {\n self._setupCodecs();\n }\n\n return self;\n },\n\n /**\n * Check for browser support for various codecs and cache the results.\n * @return {Howler}\n */\n _setupCodecs: function() {\n var self = this || Howler;\n var audioTest = null;\n\n // Must wrap in a try/catch because IE11 in server mode throws an error.\n try {\n audioTest = (typeof Audio !== 'undefined') ? new Audio() : null;\n } catch (err) {\n return self;\n }\n\n if (!audioTest || typeof audioTest.canPlayType !== 'function') {\n return self;\n }\n\n var mpegTest = audioTest.canPlayType('audio/mpeg;').replace(/^no$/, '');\n\n // Opera version <33 has mixed MP3 support, so we need to check for and block it.\n var ua = self._navigator ? self._navigator.userAgent : '';\n var checkOpera = ua.match(/OPR\\/(\\d+)/g);\n var isOldOpera = (checkOpera && parseInt(checkOpera[0].split('/')[1], 10) < 33);\n var checkSafari = ua.indexOf('Safari') !== -1 && ua.indexOf('Chrome') === -1;\n var safariVersion = ua.match(/Version\\/(.*?) /);\n var isOldSafari = (checkSafari && safariVersion && parseInt(safariVersion[1], 10) < 15);\n\n self._codecs = {\n mp3: !!(!isOldOpera && (mpegTest || audioTest.canPlayType('audio/mp3;').replace(/^no$/, ''))),\n mpeg: !!mpegTest,\n opus: !!audioTest.canPlayType('audio/ogg; codecs=\"opus\"').replace(/^no$/, ''),\n ogg: !!audioTest.canPlayType('audio/ogg; codecs=\"vorbis\"').replace(/^no$/, ''),\n oga: !!audioTest.canPlayType('audio/ogg; codecs=\"vorbis\"').replace(/^no$/, ''),\n wav: !!(audioTest.canPlayType('audio/wav; codecs=\"1\"') || audioTest.canPlayType('audio/wav')).replace(/^no$/, ''),\n aac: !!audioTest.canPlayType('audio/aac;').replace(/^no$/, ''),\n caf: !!audioTest.canPlayType('audio/x-caf;').replace(/^no$/, ''),\n m4a: !!(audioTest.canPlayType('audio/x-m4a;') || audioTest.canPlayType('audio/m4a;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''),\n m4b: !!(audioTest.canPlayType('audio/x-m4b;') || audioTest.canPlayType('audio/m4b;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''),\n mp4: !!(audioTest.canPlayType('audio/x-mp4;') || audioTest.canPlayType('audio/mp4;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/, ''),\n weba: !!(!isOldSafari && audioTest.canPlayType('audio/webm; codecs=\"vorbis\"').replace(/^no$/, '')),\n webm: !!(!isOldSafari && audioTest.canPlayType('audio/webm; codecs=\"vorbis\"').replace(/^no$/, '')),\n dolby: !!audioTest.canPlayType('audio/mp4; codecs=\"ec-3\"').replace(/^no$/, ''),\n flac: !!(audioTest.canPlayType('audio/x-flac;') || audioTest.canPlayType('audio/flac;')).replace(/^no$/, '')\n };\n\n return self;\n },\n\n /**\n * Some browsers/devices will only allow audio to be played after a user interaction.\n * Attempt to automatically unlock audio on the first user interaction.\n * Concept from: http://paulbakaus.com/tutorials/html5/web-audio-on-ios/\n * @return {Howler}\n */\n _unlockAudio: function() {\n var self = this || Howler;\n\n // Only run this if Web Audio is supported and it hasn't already been unlocked.\n if (self._audioUnlocked || !self.ctx) {\n return;\n }\n\n self._audioUnlocked = false;\n self.autoUnlock = false;\n\n // Some mobile devices/platforms have distortion issues when opening/closing tabs and/or web views.\n // Bugs in the browser (especially Mobile Safari) can cause the sampleRate to change from 44100 to 48000.\n // By calling Howler.unload(), we create a new AudioContext with the correct sampleRate.\n if (!self._mobileUnloaded && self.ctx.sampleRate !== 44100) {\n self._mobileUnloaded = true;\n self.unload();\n }\n\n // Scratch buffer for enabling iOS to dispose of web audio buffers correctly, as per:\n // http://stackoverflow.com/questions/24119684\n self._scratchBuffer = self.ctx.createBuffer(1, 1, 22050);\n\n // Call this method on touch start to create and play a buffer,\n // then check if the audio actually played to determine if\n // audio has now been unlocked on iOS, Android, etc.\n var unlock = function(e) {\n // Create a pool of unlocked HTML5 Audio objects that can\n // be used for playing sounds without user interaction. HTML5\n // Audio objects must be individually unlocked, as opposed\n // to the WebAudio API which only needs a single activation.\n // This must occur before WebAudio setup or the source.onended\n // event will not fire.\n while (self._html5AudioPool.length < self.html5PoolSize) {\n try {\n var audioNode = new Audio();\n\n // Mark this Audio object as unlocked to ensure it can get returned\n // to the unlocked pool when released.\n audioNode._unlocked = true;\n\n // Add the audio node to the pool.\n self._releaseHtml5Audio(audioNode);\n } catch (e) {\n self.noAudio = true;\n break;\n }\n }\n\n // Loop through any assigned audio nodes and unlock them.\n for (var i=0; i= 55.\n if (typeof self.ctx.resume === 'function') {\n self.ctx.resume();\n }\n\n // Setup a timeout to check that we are unlocked on the next event loop.\n source.onended = function() {\n source.disconnect(0);\n\n // Update the unlocked state and prevent this check from happening again.\n self._audioUnlocked = true;\n\n // Remove the touch start listener.\n document.removeEventListener('touchstart', unlock, true);\n document.removeEventListener('touchend', unlock, true);\n document.removeEventListener('click', unlock, true);\n document.removeEventListener('keydown', unlock, true);\n\n // Let all sounds know that audio has been unlocked.\n for (var i=0; i 0 ? sound._seek : self._sprite[sprite][0] / 1000);\n var duration = Math.max(0, ((self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000) - seek);\n var timeout = (duration * 1000) / Math.abs(sound._rate);\n var start = self._sprite[sprite][0] / 1000;\n var stop = (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000;\n sound._sprite = sprite;\n\n // Mark the sound as ended instantly so that this async playback\n // doesn't get grabbed by another call to play while this one waits to start.\n sound._ended = false;\n\n // Update the parameters of the sound.\n var setParams = function() {\n sound._paused = false;\n sound._seek = seek;\n sound._start = start;\n sound._stop = stop;\n sound._loop = !!(sound._loop || self._sprite[sprite][2]);\n };\n\n // End the sound instantly if seek is at the end.\n if (seek >= stop) {\n self._ended(sound);\n return;\n }\n\n // Begin the actual playback.\n var node = sound._node;\n if (self._webAudio) {\n // Fire this when the sound is ready to play to begin Web Audio playback.\n var playWebAudio = function() {\n self._playLock = false;\n setParams();\n self._refreshBuffer(sound);\n\n // Setup the playback params.\n var vol = (sound._muted || self._muted) ? 0 : sound._volume;\n node.gain.setValueAtTime(vol, Howler.ctx.currentTime);\n sound._playStart = Howler.ctx.currentTime;\n\n // Play the sound using the supported method.\n if (typeof node.bufferSource.start === 'undefined') {\n sound._loop ? node.bufferSource.noteGrainOn(0, seek, 86400) : node.bufferSource.noteGrainOn(0, seek, duration);\n } else {\n sound._loop ? node.bufferSource.start(0, seek, 86400) : node.bufferSource.start(0, seek, duration);\n }\n\n // Start a new timer if none is present.\n if (timeout !== Infinity) {\n self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout);\n }\n\n if (!internal) {\n setTimeout(function() {\n self._emit('play', sound._id);\n self._loadQueue();\n }, 0);\n }\n };\n\n if (Howler.state === 'running' && Howler.ctx.state !== 'interrupted') {\n playWebAudio();\n } else {\n self._playLock = true;\n\n // Wait for the audio context to resume before playing.\n self.once('resume', playWebAudio);\n\n // Cancel the end timer.\n self._clearTimer(sound._id);\n }\n } else {\n // Fire this when the sound is ready to play to begin HTML5 Audio playback.\n var playHtml5 = function() {\n node.currentTime = seek;\n node.muted = sound._muted || self._muted || Howler._muted || node.muted;\n node.volume = sound._volume * Howler.volume();\n node.playbackRate = sound._rate;\n\n // Some browsers will throw an error if this is called without user interaction.\n try {\n var play = node.play();\n\n // Support older browsers that don't support promises, and thus don't have this issue.\n if (play && typeof Promise !== 'undefined' && (play instanceof Promise || typeof play.then === 'function')) {\n // Implements a lock to prevent DOMException: The play() request was interrupted by a call to pause().\n self._playLock = true;\n\n // Set param values immediately.\n setParams();\n\n // Releases the lock and executes queued actions.\n play\n .then(function() {\n self._playLock = false;\n node._unlocked = true;\n if (!internal) {\n self._emit('play', sound._id);\n } else {\n self._loadQueue();\n }\n })\n .catch(function() {\n self._playLock = false;\n self._emit('playerror', sound._id, 'Playback was unable to start. This is most commonly an issue ' +\n 'on mobile devices and Chrome where playback was not within a user interaction.');\n\n // Reset the ended and paused values.\n sound._ended = true;\n sound._paused = true;\n });\n } else if (!internal) {\n self._playLock = false;\n setParams();\n self._emit('play', sound._id);\n }\n\n // Setting rate before playing won't work in IE, so we set it again here.\n node.playbackRate = sound._rate;\n\n // If the node is still paused, then we can assume there was a playback issue.\n if (node.paused) {\n self._emit('playerror', sound._id, 'Playback was unable to start. This is most commonly an issue ' +\n 'on mobile devices and Chrome where playback was not within a user interaction.');\n return;\n }\n\n // Setup the end timer on sprites or listen for the ended event.\n if (sprite !== '__default' || sound._loop) {\n self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout);\n } else {\n self._endTimers[sound._id] = function() {\n // Fire ended on this audio node.\n self._ended(sound);\n\n // Clear this listener.\n node.removeEventListener('ended', self._endTimers[sound._id], false);\n };\n node.addEventListener('ended', self._endTimers[sound._id], false);\n }\n } catch (err) {\n self._emit('playerror', sound._id, err);\n }\n };\n\n // If this is streaming audio, make sure the src is set and load again.\n if (node.src === 'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA') {\n node.src = self._src;\n node.load();\n }\n\n // Play immediately if ready, or wait for the 'canplaythrough'e vent.\n var loadedNoReadyState = (window && window.ejecta) || (!node.readyState && Howler._navigator.isCocoonJS);\n if (node.readyState >= 3 || loadedNoReadyState) {\n playHtml5();\n } else {\n self._playLock = true;\n self._state = 'loading';\n\n var listener = function() {\n self._state = 'loaded';\n \n // Begin playback.\n playHtml5();\n\n // Clear this listener.\n node.removeEventListener(Howler._canPlayEvent, listener, false);\n };\n node.addEventListener(Howler._canPlayEvent, listener, false);\n\n // Cancel the end timer.\n self._clearTimer(sound._id);\n }\n }\n\n return sound._id;\n },\n\n /**\n * Pause playback and save current position.\n * @param {Number} id The sound ID (empty to pause all in group).\n * @return {Howl}\n */\n pause: function(id) {\n var self = this;\n\n // If the sound hasn't loaded or a play() promise is pending, add it to the load queue to pause when capable.\n if (self._state !== 'loaded' || self._playLock) {\n self._queue.push({\n event: 'pause',\n action: function() {\n self.pause(id);\n }\n });\n\n return self;\n }\n\n // If no id is passed, get all ID's to be paused.\n var ids = self._getSoundIds(id);\n\n for (var i=0; i Returns the group's volume value.\n * volume(id) -> Returns the sound id's current volume.\n * volume(vol) -> Sets the volume of all sounds in this Howl group.\n * volume(vol, id) -> Sets the volume of passed sound id.\n * @return {Howl/Number} Returns self or current volume.\n */\n volume: function() {\n var self = this;\n var args = arguments;\n var vol, id;\n\n // Determine the values based on arguments.\n if (args.length === 0) {\n // Return the value of the groups' volume.\n return self._volume;\n } else if (args.length === 1 || args.length === 2 && typeof args[1] === 'undefined') {\n // First check if this is an ID, and if not, assume it is a new volume.\n var ids = self._getSoundIds();\n var index = ids.indexOf(args[0]);\n if (index >= 0) {\n id = parseInt(args[0], 10);\n } else {\n vol = parseFloat(args[0]);\n }\n } else if (args.length >= 2) {\n vol = parseFloat(args[0]);\n id = parseInt(args[1], 10);\n }\n\n // Update the volume or return the current volume.\n var sound;\n if (typeof vol !== 'undefined' && vol >= 0 && vol <= 1) {\n // If the sound hasn't loaded, add it to the load queue to change volume when capable.\n if (self._state !== 'loaded'|| self._playLock) {\n self._queue.push({\n event: 'volume',\n action: function() {\n self.volume.apply(self, args);\n }\n });\n\n return self;\n }\n\n // Set the group volume.\n if (typeof id === 'undefined') {\n self._volume = vol;\n }\n\n // Update one or all volumes.\n id = self._getSoundIds(id);\n for (var i=0; i 0) ? len / steps : len);\n var lastTick = Date.now();\n\n // Store the value being faded to.\n sound._fadeTo = to;\n\n // Update the volume value on each interval tick.\n sound._interval = setInterval(function() {\n // Update the volume based on the time since the last tick.\n var tick = (Date.now() - lastTick) / len;\n lastTick = Date.now();\n vol += diff * tick;\n\n // Round to within 2 decimal points.\n vol = Math.round(vol * 100) / 100;\n\n // Make sure the volume is in the right bounds.\n if (diff < 0) {\n vol = Math.max(to, vol);\n } else {\n vol = Math.min(to, vol);\n }\n\n // Change the volume.\n if (self._webAudio) {\n sound._volume = vol;\n } else {\n self.volume(vol, sound._id, true);\n }\n\n // Set the group's volume.\n if (isGroup) {\n self._volume = vol;\n }\n\n // When the fade is complete, stop it and fire event.\n if ((to < from && vol <= to) || (to > from && vol >= to)) {\n clearInterval(sound._interval);\n sound._interval = null;\n sound._fadeTo = null;\n self.volume(to, sound._id);\n self._emit('fade', sound._id);\n }\n }, stepLen);\n },\n\n /**\n * Internal method that stops the currently playing fade when\n * a new fade starts, volume is changed or the sound is stopped.\n * @param {Number} id The sound id.\n * @return {Howl}\n */\n _stopFade: function(id) {\n var self = this;\n var sound = self._soundById(id);\n\n if (sound && sound._interval) {\n if (self._webAudio) {\n sound._node.gain.cancelScheduledValues(Howler.ctx.currentTime);\n }\n\n clearInterval(sound._interval);\n sound._interval = null;\n self.volume(sound._fadeTo, id);\n sound._fadeTo = null;\n self._emit('fade', id);\n }\n\n return self;\n },\n\n /**\n * Get/set the loop parameter on a sound. This method can optionally take 0, 1 or 2 arguments.\n * loop() -> Returns the group's loop value.\n * loop(id) -> Returns the sound id's loop value.\n * loop(loop) -> Sets the loop value for all sounds in this Howl group.\n * loop(loop, id) -> Sets the loop value of passed sound id.\n * @return {Howl/Boolean} Returns self or current loop value.\n */\n loop: function() {\n var self = this;\n var args = arguments;\n var loop, id, sound;\n\n // Determine the values for loop and id.\n if (args.length === 0) {\n // Return the grou's loop value.\n return self._loop;\n } else if (args.length === 1) {\n if (typeof args[0] === 'boolean') {\n loop = args[0];\n self._loop = loop;\n } else {\n // Return this sound's loop value.\n sound = self._soundById(parseInt(args[0], 10));\n return sound ? sound._loop : false;\n }\n } else if (args.length === 2) {\n loop = args[0];\n id = parseInt(args[1], 10);\n }\n\n // If no id is passed, get all ID's to be looped.\n var ids = self._getSoundIds(id);\n for (var i=0; i Returns the first sound node's current playback rate.\n * rate(id) -> Returns the sound id's current playback rate.\n * rate(rate) -> Sets the playback rate of all sounds in this Howl group.\n * rate(rate, id) -> Sets the playback rate of passed sound id.\n * @return {Howl/Number} Returns self or the current playback rate.\n */\n rate: function() {\n var self = this;\n var args = arguments;\n var rate, id;\n\n // Determine the values based on arguments.\n if (args.length === 0) {\n // We will simply return the current rate of the first node.\n id = self._sounds[0]._id;\n } else if (args.length === 1) {\n // First check if this is an ID, and if not, assume it is a new rate value.\n var ids = self._getSoundIds();\n var index = ids.indexOf(args[0]);\n if (index >= 0) {\n id = parseInt(args[0], 10);\n } else {\n rate = parseFloat(args[0]);\n }\n } else if (args.length === 2) {\n rate = parseFloat(args[0]);\n id = parseInt(args[1], 10);\n }\n\n // Update the playback rate or return the current value.\n var sound;\n if (typeof rate === 'number') {\n // If the sound hasn't loaded, add it to the load queue to change playback rate when capable.\n if (self._state !== 'loaded' || self._playLock) {\n self._queue.push({\n event: 'rate',\n action: function() {\n self.rate.apply(self, args);\n }\n });\n\n return self;\n }\n\n // Set the group rate.\n if (typeof id === 'undefined') {\n self._rate = rate;\n }\n\n // Update one or all volumes.\n id = self._getSoundIds(id);\n for (var i=0; i Returns the first sound node's current seek position.\n * seek(id) -> Returns the sound id's current seek position.\n * seek(seek) -> Sets the seek position of the first sound node.\n * seek(seek, id) -> Sets the seek position of passed sound id.\n * @return {Howl/Number} Returns self or the current seek position.\n */\n seek: function() {\n var self = this;\n var args = arguments;\n var seek, id;\n\n // Determine the values based on arguments.\n if (args.length === 0) {\n // We will simply return the current position of the first node.\n if (self._sounds.length) {\n id = self._sounds[0]._id;\n }\n } else if (args.length === 1) {\n // First check if this is an ID, and if not, assume it is a new seek position.\n var ids = self._getSoundIds();\n var index = ids.indexOf(args[0]);\n if (index >= 0) {\n id = parseInt(args[0], 10);\n } else if (self._sounds.length) {\n id = self._sounds[0]._id;\n seek = parseFloat(args[0]);\n }\n } else if (args.length === 2) {\n seek = parseFloat(args[0]);\n id = parseInt(args[1], 10);\n }\n\n // If there is no ID, bail out.\n if (typeof id === 'undefined') {\n return 0;\n }\n\n // If the sound hasn't loaded, add it to the load queue to seek when capable.\n if (typeof seek === 'number' && (self._state !== 'loaded' || self._playLock)) {\n self._queue.push({\n event: 'seek',\n action: function() {\n self.seek.apply(self, args);\n }\n });\n\n return self;\n }\n\n // Get the sound.\n var sound = self._soundById(id);\n\n if (sound) {\n if (typeof seek === 'number' && seek >= 0) {\n // Pause the sound and update position for restarting playback.\n var playing = self.playing(id);\n if (playing) {\n self.pause(id, true);\n }\n\n // Move the position of the track and cancel timer.\n sound._seek = seek;\n sound._ended = false;\n self._clearTimer(id);\n\n // Update the seek position for HTML5 Audio.\n if (!self._webAudio && sound._node && !isNaN(sound._node.duration)) {\n sound._node.currentTime = seek;\n }\n\n // Seek and emit when ready.\n var seekAndEmit = function() {\n // Restart the playback if the sound was playing.\n if (playing) {\n self.play(id, true);\n }\n\n self._emit('seek', id);\n };\n\n // Wait for the play lock to be unset before emitting (HTML5 Audio).\n if (playing && !self._webAudio) {\n var emitSeek = function() {\n if (!self._playLock) {\n seekAndEmit();\n } else {\n setTimeout(emitSeek, 0);\n }\n };\n setTimeout(emitSeek, 0);\n } else {\n seekAndEmit();\n }\n } else {\n if (self._webAudio) {\n var realTime = self.playing(id) ? Howler.ctx.currentTime - sound._playStart : 0;\n var rateSeek = sound._rateSeek ? sound._rateSeek - sound._seek : 0;\n return sound._seek + (rateSeek + realTime * Math.abs(sound._rate));\n } else {\n return sound._node.currentTime;\n }\n }\n }\n\n return self;\n },\n\n /**\n * Check if a specific sound is currently playing or not (if id is provided), or check if at least one of the sounds in the group is playing or not.\n * @param {Number} id The sound id to check. If none is passed, the whole sound group is checked.\n * @return {Boolean} True if playing and false if not.\n */\n playing: function(id) {\n var self = this;\n\n // Check the passed sound ID (if any).\n if (typeof id === 'number') {\n var sound = self._soundById(id);\n return sound ? !sound._paused : false;\n }\n\n // Otherwise, loop through all sounds and check if any are playing.\n for (var i=0; i= 0) {\n Howler._howls.splice(index, 1);\n }\n\n // Delete this sound from the cache (if no other Howl is using it).\n var remCache = true;\n for (i=0; i= 0) {\n remCache = false;\n break;\n }\n }\n\n if (cache && remCache) {\n delete cache[self._src];\n }\n\n // Clear global errors.\n Howler.noAudio = false;\n\n // Clear out `self`.\n self._state = 'unloaded';\n self._sounds = [];\n self = null;\n\n return null;\n },\n\n /**\n * Listen to a custom event.\n * @param {String} event Event name.\n * @param {Function} fn Listener to call.\n * @param {Number} id (optional) Only listen to events for this sound.\n * @param {Number} once (INTERNAL) Marks event to fire only once.\n * @return {Howl}\n */\n on: function(event, fn, id, once) {\n var self = this;\n var events = self['_on' + event];\n\n if (typeof fn === 'function') {\n events.push(once ? {id: id, fn: fn, once: once} : {id: id, fn: fn});\n }\n\n return self;\n },\n\n /**\n * Remove a custom event. Call without parameters to remove all events.\n * @param {String} event Event name.\n * @param {Function} fn Listener to remove. Leave empty to remove all.\n * @param {Number} id (optional) Only remove events for this sound.\n * @return {Howl}\n */\n off: function(event, fn, id) {\n var self = this;\n var events = self['_on' + event];\n var i = 0;\n\n // Allow passing just an event and ID.\n if (typeof fn === 'number') {\n id = fn;\n fn = null;\n }\n\n if (fn || id) {\n // Loop through event store and remove the passed function.\n for (i=0; i=0; i--) {\n // Only fire the listener if the correct ID is used.\n if (!events[i].id || events[i].id === id || event === 'load') {\n setTimeout(function(fn) {\n fn.call(this, id, msg);\n }.bind(self, events[i].fn), 0);\n\n // If this event was setup with `once`, remove it.\n if (events[i].once) {\n self.off(event, events[i].fn, events[i].id);\n }\n }\n }\n\n // Pass the event type into load queue so that it can continue stepping.\n self._loadQueue(event);\n\n return self;\n },\n\n /**\n * Queue of actions initiated before the sound has loaded.\n * These will be called in sequence, with the next only firing\n * after the previous has finished executing (even if async like play).\n * @return {Howl}\n */\n _loadQueue: function(event) {\n var self = this;\n\n if (self._queue.length > 0) {\n var task = self._queue[0];\n\n // Remove this task if a matching event was passed.\n if (task.event === event) {\n self._queue.shift();\n self._loadQueue();\n }\n\n // Run the task if no event type is passed.\n if (!event) {\n task.action();\n }\n }\n\n return self;\n },\n\n /**\n * Fired when playback ends at the end of the duration.\n * @param {Sound} sound The sound object to work with.\n * @return {Howl}\n */\n _ended: function(sound) {\n var self = this;\n var sprite = sound._sprite;\n\n // If we are using IE and there was network latency we may be clipping\n // audio before it completes playing. Lets check the node to make sure it\n // believes it has completed, before ending the playback.\n if (!self._webAudio && sound._node && !sound._node.paused && !sound._node.ended && sound._node.currentTime < sound._stop) {\n setTimeout(self._ended.bind(self, sound), 100);\n return self;\n }\n\n // Should this sound loop?\n var loop = !!(sound._loop || self._sprite[sprite][2]);\n\n // Fire the ended event.\n self._emit('end', sound._id);\n\n // Restart the playback for HTML5 Audio loop.\n if (!self._webAudio && loop) {\n self.stop(sound._id, true).play(sound._id);\n }\n\n // Restart this timer if on a Web Audio loop.\n if (self._webAudio && loop) {\n self._emit('play', sound._id);\n sound._seek = sound._start || 0;\n sound._rateSeek = 0;\n sound._playStart = Howler.ctx.currentTime;\n\n var timeout = ((sound._stop - sound._start) * 1000) / Math.abs(sound._rate);\n self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout);\n }\n\n // Mark the node as paused.\n if (self._webAudio && !loop) {\n sound._paused = true;\n sound._ended = true;\n sound._seek = sound._start || 0;\n sound._rateSeek = 0;\n self._clearTimer(sound._id);\n\n // Clean up the buffer source.\n self._cleanBuffer(sound._node);\n\n // Attempt to auto-suspend AudioContext if no sounds are still playing.\n Howler._autoSuspend();\n }\n\n // When using a sprite, end the track.\n if (!self._webAudio && !loop) {\n self.stop(sound._id, true);\n }\n\n return self;\n },\n\n /**\n * Clear the end timer for a sound playback.\n * @param {Number} id The sound ID.\n * @return {Howl}\n */\n _clearTimer: function(id) {\n var self = this;\n\n if (self._endTimers[id]) {\n // Clear the timeout or remove the ended listener.\n if (typeof self._endTimers[id] !== 'function') {\n clearTimeout(self._endTimers[id]);\n } else {\n var sound = self._soundById(id);\n if (sound && sound._node) {\n sound._node.removeEventListener('ended', self._endTimers[id], false);\n }\n }\n\n delete self._endTimers[id];\n }\n\n return self;\n },\n\n /**\n * Return the sound identified by this ID, or return null.\n * @param {Number} id Sound ID\n * @return {Object} Sound object or null.\n */\n _soundById: function(id) {\n var self = this;\n\n // Loop through all sounds and find the one with this ID.\n for (var i=0; i=0; i--) {\n if (cnt <= limit) {\n return;\n }\n\n if (self._sounds[i]._ended) {\n // Disconnect the audio source when using Web Audio.\n if (self._webAudio && self._sounds[i]._node) {\n self._sounds[i]._node.disconnect(0);\n }\n\n // Remove sounds until we have the pool size.\n self._sounds.splice(i, 1);\n cnt--;\n }\n }\n },\n\n /**\n * Get all ID's from the sounds pool.\n * @param {Number} id Only return one ID if one is passed.\n * @return {Array} Array of IDs.\n */\n _getSoundIds: function(id) {\n var self = this;\n\n if (typeof id === 'undefined') {\n var ids = [];\n for (var i=0; i= 0;\n\n if (!node.bufferSource) {\n return self;\n }\n\n if (Howler._scratchBuffer && node.bufferSource) {\n node.bufferSource.onended = null;\n node.bufferSource.disconnect(0);\n if (isIOS) {\n try { node.bufferSource.buffer = Howler._scratchBuffer; } catch(e) {}\n }\n }\n node.bufferSource = null;\n\n return self;\n },\n\n /**\n * Set the source to a 0-second silence to stop any downloading (except in IE).\n * @param {Object} node Audio node to clear.\n */\n _clearSound: function(node) {\n var checkIE = /MSIE |Trident\\//.test(Howler._navigator && Howler._navigator.userAgent);\n if (!checkIE) {\n node.src = 'data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA';\n }\n }\n };\n\n /** Single Sound Methods **/\n /***************************************************************************/\n\n /**\n * Setup the sound object, which each node attached to a Howl group is contained in.\n * @param {Object} howl The Howl parent group.\n */\n var Sound = function(howl) {\n this._parent = howl;\n this.init();\n };\n Sound.prototype = {\n /**\n * Initialize a new Sound object.\n * @return {Sound}\n */\n init: function() {\n var self = this;\n var parent = self._parent;\n\n // Setup the default parameters.\n self._muted = parent._muted;\n self._loop = parent._loop;\n self._volume = parent._volume;\n self._rate = parent._rate;\n self._seek = 0;\n self._paused = true;\n self._ended = true;\n self._sprite = '__default';\n\n // Generate a unique ID for this sound.\n self._id = ++Howler._counter;\n\n // Add itself to the parent's pool.\n parent._sounds.push(self);\n\n // Create the new node.\n self.create();\n\n return self;\n },\n\n /**\n * Create and setup a new sound object, whether HTML5 Audio or Web Audio.\n * @return {Sound}\n */\n create: function() {\n var self = this;\n var parent = self._parent;\n var volume = (Howler._muted || self._muted || self._parent._muted) ? 0 : self._volume;\n\n if (parent._webAudio) {\n // Create the gain node for controlling volume (the source will connect to this).\n self._node = (typeof Howler.ctx.createGain === 'undefined') ? Howler.ctx.createGainNode() : Howler.ctx.createGain();\n self._node.gain.setValueAtTime(volume, Howler.ctx.currentTime);\n self._node.paused = true;\n self._node.connect(Howler.masterGain);\n } else if (!Howler.noAudio) {\n // Get an unlocked Audio object from the pool.\n self._node = Howler._obtainHtml5Audio();\n\n // Listen for errors (http://dev.w3.org/html5/spec-author-view/spec.html#mediaerror).\n self._errorFn = self._errorListener.bind(self);\n self._node.addEventListener('error', self._errorFn, false);\n\n // Listen for 'canplaythrough' event to let us know the sound is ready.\n self._loadFn = self._loadListener.bind(self);\n self._node.addEventListener(Howler._canPlayEvent, self._loadFn, false);\n\n // Listen for the 'ended' event on the sound to account for edge-case where\n // a finite sound has a duration of Infinity.\n self._endFn = self._endListener.bind(self);\n self._node.addEventListener('ended', self._endFn, false);\n\n // Setup the new audio node.\n self._node.src = parent._src;\n self._node.preload = parent._preload === true ? 'auto' : parent._preload;\n self._node.volume = volume * Howler.volume();\n\n // Begin loading the source.\n self._node.load();\n }\n\n return self;\n },\n\n /**\n * Reset the parameters of this sound to the original state (for recycle).\n * @return {Sound}\n */\n reset: function() {\n var self = this;\n var parent = self._parent;\n\n // Reset all of the parameters of this sound.\n self._muted = parent._muted;\n self._loop = parent._loop;\n self._volume = parent._volume;\n self._rate = parent._rate;\n self._seek = 0;\n self._rateSeek = 0;\n self._paused = true;\n self._ended = true;\n self._sprite = '__default';\n\n // Generate a new ID so that it isn't confused with the previous sound.\n self._id = ++Howler._counter;\n\n return self;\n },\n\n /**\n * HTML5 Audio error listener callback.\n */\n _errorListener: function() {\n var self = this;\n\n // Fire an error event and pass back the code.\n self._parent._emit('loaderror', self._id, self._node.error ? self._node.error.code : 0);\n\n // Clear the event listener.\n self._node.removeEventListener('error', self._errorFn, false);\n },\n\n /**\n * HTML5 Audio canplaythrough listener callback.\n */\n _loadListener: function() {\n var self = this;\n var parent = self._parent;\n\n // Round up the duration to account for the lower precision in HTML5 Audio.\n parent._duration = Math.ceil(self._node.duration * 10) / 10;\n\n // Setup a sprite if none is defined.\n if (Object.keys(parent._sprite).length === 0) {\n parent._sprite = {__default: [0, parent._duration * 1000]};\n }\n\n if (parent._state !== 'loaded') {\n parent._state = 'loaded';\n parent._emit('load');\n parent._loadQueue();\n }\n\n // Clear the event listener.\n self._node.removeEventListener(Howler._canPlayEvent, self._loadFn, false);\n },\n\n /**\n * HTML5 Audio ended listener callback.\n */\n _endListener: function() {\n var self = this;\n var parent = self._parent;\n\n // Only handle the `ended`` event if the duration is Infinity.\n if (parent._duration === Infinity) {\n // Update the parent duration to match the real audio duration.\n // Round up the duration to account for the lower precision in HTML5 Audio.\n parent._duration = Math.ceil(self._node.duration * 10) / 10;\n\n // Update the sprite that corresponds to the real duration.\n if (parent._sprite.__default[1] === Infinity) {\n parent._sprite.__default[1] = parent._duration * 1000;\n }\n\n // Run the regular ended method.\n parent._ended(self);\n }\n\n // Clear the event listener since the duration is now correct.\n self._node.removeEventListener('ended', self._endFn, false);\n }\n };\n\n /** Helper Methods **/\n /***************************************************************************/\n\n var cache = {};\n\n /**\n * Buffer a sound from URL, Data URI or cache and decode to audio source (Web Audio API).\n * @param {Howl} self\n */\n var loadBuffer = function(self) {\n var url = self._src;\n\n // Check if the buffer has already been cached and use it instead.\n if (cache[url]) {\n // Set the duration from the cache.\n self._duration = cache[url].duration;\n\n // Load the sound into this Howl.\n loadSound(self);\n\n return;\n }\n\n if (/^data:[^;]+;base64,/.test(url)) {\n // Decode the base64 data URI without XHR, since some browsers don't support it.\n var data = atob(url.split(',')[1]);\n var dataView = new Uint8Array(data.length);\n for (var i=0; i 0) {\n cache[self._src] = buffer;\n loadSound(self, buffer);\n } else {\n error();\n }\n };\n\n // Decode the buffer into an audio source.\n if (typeof Promise !== 'undefined' && Howler.ctx.decodeAudioData.length === 1) {\n Howler.ctx.decodeAudioData(arraybuffer).then(success).catch(error);\n } else {\n Howler.ctx.decodeAudioData(arraybuffer, success, error);\n }\n }\n\n /**\n * Sound is now loaded, so finish setting everything up and fire the loaded event.\n * @param {Howl} self\n * @param {Object} buffer The decoded buffer sound source.\n */\n var loadSound = function(self, buffer) {\n // Set the duration.\n if (buffer && !self._duration) {\n self._duration = buffer.duration;\n }\n\n // Setup a sprite if none is defined.\n if (Object.keys(self._sprite).length === 0) {\n self._sprite = {__default: [0, self._duration * 1000]};\n }\n\n // Fire the loaded event.\n if (self._state !== 'loaded') {\n self._state = 'loaded';\n self._emit('load');\n self._loadQueue();\n }\n };\n\n /**\n * Setup the audio context when available, or switch to HTML5 Audio mode.\n */\n var setupAudioContext = function() {\n // If we have already detected that Web Audio isn't supported, don't run this step again.\n if (!Howler.usingWebAudio) {\n return;\n }\n\n // Check if we are using Web Audio and setup the AudioContext if we are.\n try {\n if (typeof AudioContext !== 'undefined') {\n Howler.ctx = new AudioContext();\n } else if (typeof webkitAudioContext !== 'undefined') {\n Howler.ctx = new webkitAudioContext();\n } else {\n Howler.usingWebAudio = false;\n }\n } catch(e) {\n Howler.usingWebAudio = false;\n }\n\n // If the audio context creation still failed, set using web audio to false.\n if (!Howler.ctx) {\n Howler.usingWebAudio = false;\n }\n\n // Check if a webview is being used on iOS8 or earlier (rather than the browser).\n // If it is, disable Web Audio as it causes crashing.\n var iOS = (/iP(hone|od|ad)/.test(Howler._navigator && Howler._navigator.platform));\n var appVersion = Howler._navigator && Howler._navigator.appVersion.match(/OS (\\d+)_(\\d+)_?(\\d+)?/);\n var version = appVersion ? parseInt(appVersion[1], 10) : null;\n if (iOS && version && version < 9) {\n var safari = /safari/.test(Howler._navigator && Howler._navigator.userAgent.toLowerCase());\n if (Howler._navigator && !safari) {\n Howler.usingWebAudio = false;\n }\n }\n\n // Create and expose the master GainNode when using Web Audio (useful for plugins or advanced usage).\n if (Howler.usingWebAudio) {\n Howler.masterGain = (typeof Howler.ctx.createGain === 'undefined') ? Howler.ctx.createGainNode() : Howler.ctx.createGain();\n Howler.masterGain.gain.setValueAtTime(Howler._muted ? 0 : Howler._volume, Howler.ctx.currentTime);\n Howler.masterGain.connect(Howler.ctx.destination);\n }\n\n // Re-run the setup on Howler.\n Howler._setup();\n };\n\n // Add support for AMD (Asynchronous Module Definition) libraries such as require.js.\n if (typeof define === 'function' && define.amd) {\n define([], function() {\n return {\n Howler: Howler,\n Howl: Howl\n };\n });\n }\n\n // Add support for CommonJS libraries such as browserify.\n if (typeof exports !== 'undefined') {\n exports.Howler = Howler;\n exports.Howl = Howl;\n }\n\n // Add to global in Node.js (for testing, etc).\n if (typeof global !== 'undefined') {\n global.HowlerGlobal = HowlerGlobal;\n global.Howler = Howler;\n global.Howl = Howl;\n global.Sound = Sound;\n } else if (typeof window !== 'undefined') { // Define globally in case AMD is not available or unused.\n window.HowlerGlobal = HowlerGlobal;\n window.Howler = Howler;\n window.Howl = Howl;\n window.Sound = Sound;\n }\n})();\n\n\n/*!\n * Spatial Plugin - Adds support for stereo and 3D audio where Web Audio is supported.\n * \n * howler.js v2.2.4\n * howlerjs.com\n *\n * (c) 2013-2020, James Simpson of GoldFire Studios\n * goldfirestudios.com\n *\n * MIT License\n */\n\n(function() {\n\n 'use strict';\n\n // Setup default properties.\n HowlerGlobal.prototype._pos = [0, 0, 0];\n HowlerGlobal.prototype._orientation = [0, 0, -1, 0, 1, 0];\n\n /** Global Methods **/\n /***************************************************************************/\n\n /**\n * Helper method to update the stereo panning position of all current Howls.\n * Future Howls will not use this value unless explicitly set.\n * @param {Number} pan A value of -1.0 is all the way left and 1.0 is all the way right.\n * @return {Howler/Number} Self or current stereo panning value.\n */\n HowlerGlobal.prototype.stereo = function(pan) {\n var self = this;\n\n // Stop right here if not using Web Audio.\n if (!self.ctx || !self.ctx.listener) {\n return self;\n }\n\n // Loop through all Howls and update their stereo panning.\n for (var i=self._howls.length-1; i>=0; i--) {\n self._howls[i].stereo(pan);\n }\n\n return self;\n };\n\n /**\n * Get/set the position of the listener in 3D cartesian space. Sounds using\n * 3D position will be relative to the listener's position.\n * @param {Number} x The x-position of the listener.\n * @param {Number} y The y-position of the listener.\n * @param {Number} z The z-position of the listener.\n * @return {Howler/Array} Self or current listener position.\n */\n HowlerGlobal.prototype.pos = function(x, y, z) {\n var self = this;\n\n // Stop right here if not using Web Audio.\n if (!self.ctx || !self.ctx.listener) {\n return self;\n }\n\n // Set the defaults for optional 'y' & 'z'.\n y = (typeof y !== 'number') ? self._pos[1] : y;\n z = (typeof z !== 'number') ? self._pos[2] : z;\n\n if (typeof x === 'number') {\n self._pos = [x, y, z];\n\n if (typeof self.ctx.listener.positionX !== 'undefined') {\n self.ctx.listener.positionX.setTargetAtTime(self._pos[0], Howler.ctx.currentTime, 0.1);\n self.ctx.listener.positionY.setTargetAtTime(self._pos[1], Howler.ctx.currentTime, 0.1);\n self.ctx.listener.positionZ.setTargetAtTime(self._pos[2], Howler.ctx.currentTime, 0.1);\n } else {\n self.ctx.listener.setPosition(self._pos[0], self._pos[1], self._pos[2]);\n }\n } else {\n return self._pos;\n }\n\n return self;\n };\n\n /**\n * Get/set the direction the listener is pointing in the 3D cartesian space.\n * A front and up vector must be provided. The front is the direction the\n * face of the listener is pointing, and up is the direction the top of the\n * listener is pointing. Thus, these values are expected to be at right angles\n * from each other.\n * @param {Number} x The x-orientation of the listener.\n * @param {Number} y The y-orientation of the listener.\n * @param {Number} z The z-orientation of the listener.\n * @param {Number} xUp The x-orientation of the top of the listener.\n * @param {Number} yUp The y-orientation of the top of the listener.\n * @param {Number} zUp The z-orientation of the top of the listener.\n * @return {Howler/Array} Returns self or the current orientation vectors.\n */\n HowlerGlobal.prototype.orientation = function(x, y, z, xUp, yUp, zUp) {\n var self = this;\n\n // Stop right here if not using Web Audio.\n if (!self.ctx || !self.ctx.listener) {\n return self;\n }\n\n // Set the defaults for optional 'y' & 'z'.\n var or = self._orientation;\n y = (typeof y !== 'number') ? or[1] : y;\n z = (typeof z !== 'number') ? or[2] : z;\n xUp = (typeof xUp !== 'number') ? or[3] : xUp;\n yUp = (typeof yUp !== 'number') ? or[4] : yUp;\n zUp = (typeof zUp !== 'number') ? or[5] : zUp;\n\n if (typeof x === 'number') {\n self._orientation = [x, y, z, xUp, yUp, zUp];\n\n if (typeof self.ctx.listener.forwardX !== 'undefined') {\n self.ctx.listener.forwardX.setTargetAtTime(x, Howler.ctx.currentTime, 0.1);\n self.ctx.listener.forwardY.setTargetAtTime(y, Howler.ctx.currentTime, 0.1);\n self.ctx.listener.forwardZ.setTargetAtTime(z, Howler.ctx.currentTime, 0.1);\n self.ctx.listener.upX.setTargetAtTime(xUp, Howler.ctx.currentTime, 0.1);\n self.ctx.listener.upY.setTargetAtTime(yUp, Howler.ctx.currentTime, 0.1);\n self.ctx.listener.upZ.setTargetAtTime(zUp, Howler.ctx.currentTime, 0.1);\n } else {\n self.ctx.listener.setOrientation(x, y, z, xUp, yUp, zUp);\n }\n } else {\n return or;\n }\n\n return self;\n };\n\n /** Group Methods **/\n /***************************************************************************/\n\n /**\n * Add new properties to the core init.\n * @param {Function} _super Core init method.\n * @return {Howl}\n */\n Howl.prototype.init = (function(_super) {\n return function(o) {\n var self = this;\n\n // Setup user-defined default properties.\n self._orientation = o.orientation || [1, 0, 0];\n self._stereo = o.stereo || null;\n self._pos = o.pos || null;\n self._pannerAttr = {\n coneInnerAngle: typeof o.coneInnerAngle !== 'undefined' ? o.coneInnerAngle : 360,\n coneOuterAngle: typeof o.coneOuterAngle !== 'undefined' ? o.coneOuterAngle : 360,\n coneOuterGain: typeof o.coneOuterGain !== 'undefined' ? o.coneOuterGain : 0,\n distanceModel: typeof o.distanceModel !== 'undefined' ? o.distanceModel : 'inverse',\n maxDistance: typeof o.maxDistance !== 'undefined' ? o.maxDistance : 10000,\n panningModel: typeof o.panningModel !== 'undefined' ? o.panningModel : 'HRTF',\n refDistance: typeof o.refDistance !== 'undefined' ? o.refDistance : 1,\n rolloffFactor: typeof o.rolloffFactor !== 'undefined' ? o.rolloffFactor : 1\n };\n\n // Setup event listeners.\n self._onstereo = o.onstereo ? [{fn: o.onstereo}] : [];\n self._onpos = o.onpos ? [{fn: o.onpos}] : [];\n self._onorientation = o.onorientation ? [{fn: o.onorientation}] : [];\n\n // Complete initilization with howler.js core's init function.\n return _super.call(this, o);\n };\n })(Howl.prototype.init);\n\n /**\n * Get/set the stereo panning of the audio source for this sound or all in the group.\n * @param {Number} pan A value of -1.0 is all the way left and 1.0 is all the way right.\n * @param {Number} id (optional) The sound ID. If none is passed, all in group will be updated.\n * @return {Howl/Number} Returns self or the current stereo panning value.\n */\n Howl.prototype.stereo = function(pan, id) {\n var self = this;\n\n // Stop right here if not using Web Audio.\n if (!self._webAudio) {\n return self;\n }\n\n // If the sound hasn't loaded, add it to the load queue to change stereo pan when capable.\n if (self._state !== 'loaded') {\n self._queue.push({\n event: 'stereo',\n action: function() {\n self.stereo(pan, id);\n }\n });\n\n return self;\n }\n\n // Check for PannerStereoNode support and fallback to PannerNode if it doesn't exist.\n var pannerType = (typeof Howler.ctx.createStereoPanner === 'undefined') ? 'spatial' : 'stereo';\n\n // Setup the group's stereo panning if no ID is passed.\n if (typeof id === 'undefined') {\n // Return the group's stereo panning if no parameters are passed.\n if (typeof pan === 'number') {\n self._stereo = pan;\n self._pos = [pan, 0, 0];\n } else {\n return self._stereo;\n }\n }\n\n // Change the streo panning of one or all sounds in group.\n var ids = self._getSoundIds(id);\n for (var i=0; i Returns the group's values.\n * pannerAttr(id) -> Returns the sound id's values.\n * pannerAttr(o) -> Set's the values of all sounds in this Howl group.\n * pannerAttr(o, id) -> Set's the values of passed sound id.\n *\n * Attributes:\n * coneInnerAngle - (360 by default) A parameter for directional audio sources, this is an angle, in degrees,\n * inside of which there will be no volume reduction.\n * coneOuterAngle - (360 by default) A parameter for directional audio sources, this is an angle, in degrees,\n * outside of which the volume will be reduced to a constant value of `coneOuterGain`.\n * coneOuterGain - (0 by default) A parameter for directional audio sources, this is the gain outside of the\n * `coneOuterAngle`. It is a linear value in the range `[0, 1]`.\n * distanceModel - ('inverse' by default) Determines algorithm used to reduce volume as audio moves away from\n * listener. Can be `linear`, `inverse` or `exponential.\n * maxDistance - (10000 by default) The maximum distance between source and listener, after which the volume\n * will not be reduced any further.\n * refDistance - (1 by default) A reference distance for reducing volume as source moves further from the listener.\n * This is simply a variable of the distance model and has a different effect depending on which model\n * is used and the scale of your coordinates. Generally, volume will be equal to 1 at this distance.\n * rolloffFactor - (1 by default) How quickly the volume reduces as source moves from listener. This is simply a\n * variable of the distance model and can be in the range of `[0, 1]` with `linear` and `[0, ∞]`\n * with `inverse` and `exponential`.\n * panningModel - ('HRTF' by default) Determines which spatialization algorithm is used to position audio.\n * Can be `HRTF` or `equalpower`.\n *\n * @return {Howl/Object} Returns self or current panner attributes.\n */\n Howl.prototype.pannerAttr = function() {\n var self = this;\n var args = arguments;\n var o, id, sound;\n\n // Stop right here if not using Web Audio.\n if (!self._webAudio) {\n return self;\n }\n\n // Determine the values based on arguments.\n if (args.length === 0) {\n // Return the group's panner attribute values.\n return self._pannerAttr;\n } else if (args.length === 1) {\n if (typeof args[0] === 'object') {\n o = args[0];\n\n // Set the grou's panner attribute values.\n if (typeof id === 'undefined') {\n if (!o.pannerAttr) {\n o.pannerAttr = {\n coneInnerAngle: o.coneInnerAngle,\n coneOuterAngle: o.coneOuterAngle,\n coneOuterGain: o.coneOuterGain,\n distanceModel: o.distanceModel,\n maxDistance: o.maxDistance,\n refDistance: o.refDistance,\n rolloffFactor: o.rolloffFactor,\n panningModel: o.panningModel\n };\n }\n\n self._pannerAttr = {\n coneInnerAngle: typeof o.pannerAttr.coneInnerAngle !== 'undefined' ? o.pannerAttr.coneInnerAngle : self._coneInnerAngle,\n coneOuterAngle: typeof o.pannerAttr.coneOuterAngle !== 'undefined' ? o.pannerAttr.coneOuterAngle : self._coneOuterAngle,\n coneOuterGain: typeof o.pannerAttr.coneOuterGain !== 'undefined' ? o.pannerAttr.coneOuterGain : self._coneOuterGain,\n distanceModel: typeof o.pannerAttr.distanceModel !== 'undefined' ? o.pannerAttr.distanceModel : self._distanceModel,\n maxDistance: typeof o.pannerAttr.maxDistance !== 'undefined' ? o.pannerAttr.maxDistance : self._maxDistance,\n refDistance: typeof o.pannerAttr.refDistance !== 'undefined' ? o.pannerAttr.refDistance : self._refDistance,\n rolloffFactor: typeof o.pannerAttr.rolloffFactor !== 'undefined' ? o.pannerAttr.rolloffFactor : self._rolloffFactor,\n panningModel: typeof o.pannerAttr.panningModel !== 'undefined' ? o.pannerAttr.panningModel : self._panningModel\n };\n }\n } else {\n // Return this sound's panner attribute values.\n sound = self._soundById(parseInt(args[0], 10));\n return sound ? sound._pannerAttr : self._pannerAttr;\n }\n } else if (args.length === 2) {\n o = args[0];\n id = parseInt(args[1], 10);\n }\n\n // Update the values of the specified sounds.\n var ids = self._getSoundIds(id);\n for (var i=0; i{"use strict";var e={463:(e,n,t)=>{var r=t(791),l=t(296);function a(e){for(var n="https://reactjs.org/docs/error-decoder.html?invariant="+e,t=1;t