diff --git a/docs/soundjs_docs-NEXT.zip b/docs/soundjs_docs-NEXT.zip index b7abc0e3..924a11d7 100644 Binary files a/docs/soundjs_docs-NEXT.zip and b/docs/soundjs_docs-NEXT.zip differ diff --git a/lib/cordovaaudioplugin-NEXT.combined.js b/lib/cordovaaudioplugin-NEXT.combined.js index efdf8e9c..99d6f87f 100644 --- a/lib/cordovaaudioplugin-NEXT.combined.js +++ b/lib/cordovaaudioplugin-NEXT.combined.js @@ -1,3 +1,30 @@ +/*! +* SoundJS +* Visit http://createjs.com/ for documentation, updates and examples. +* +* Copyright (c) 2010 gskinner.com, inc. +* +* Permission is hereby granted, free of charge, to any person +* obtaining a copy of this software and associated documentation +* files (the "Software"), to deal in the Software without +* restriction, including without limitation the rights to use, +* copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following +* conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +* OTHER DEALINGS IN THE SOFTWARE. +*/ //############################################################################## @@ -538,6 +565,6 @@ this.createjs = this.createjs || {}; * @type String * @static **/ - s.buildDate = /*=date*/"Tue, 19 May 2015 17:26:59 GMT"; // injected by build process + s.buildDate = /*=date*/"Wed, 27 May 2015 18:12:38 GMT"; // injected by build process })(); \ No newline at end of file diff --git a/lib/cordovaaudioplugin-NEXT.min.js b/lib/cordovaaudioplugin-NEXT.min.js index fdb9fb49..0cb6d230 100644 --- a/lib/cordovaaudioplugin-NEXT.min.js +++ b/lib/cordovaaudioplugin-NEXT.min.js @@ -2,7 +2,7 @@ * @license SoundJS * Visit http://createjs.com/ for documentation, updates and examples. * -* Copyright (c) 2011-2013 gskinner.com, inc. +* Copyright (c) 2011-2015 gskinner.com, inc. * * Distributed under the terms of the MIT license. * http://www.opensource.org/licenses/mit-license.html @@ -14,4 +14,4 @@ * SoundJS FlashAudioPlugin also includes swfobject (http://code.google.com/p/swfobject/) */ -this.createjs=this.createjs||{},function(){"use strict";function a(a){this.AbstractLoader_constructor(a,!0,createjs.AbstractLoader.SOUND),this._media=null,this._loadTime=0,this._TIMER_FREQUENCY=100}var b=createjs.extend(a,createjs.AbstractLoader);b.load=function(){this._media=new Media(this._item.src,null,createjs.proxy(this._mediaErrorHandler,this)),this._media.seekTo(0),this._getMediaDuration()},b.toString=function(){return"[CordovaAudioLoader]"},b._mediaErrorHandler=function(){this._media.release(),this._sendError()},b._getMediaDuration=function(){this._result=1e3*this._media.getDuration(),this._result<0?(this._loadTime+=this._TIMER_FREQUENCY,this._loadTime>this._item.loadTimeout?this.handleEvent({type:"timeout"}):setTimeout(createjs.proxy(this._getMediaDuration,this),this._TIMER_FREQUENCY)):(this._media.release(),this._sendComplete())},createjs.CordovaAudioLoader=createjs.promote(a,"AbstractLoader")}(),this.createjs=this.createjs||{},function(){"use strict";function CordovaAudioSoundInstance(a,b,c,d){this.AbstractSoundInstance_constructor(a,b,c,d),this.playWhenScreenLocked=null,this._playStartTime=null,this._audioSpriteTimeout=null,this._audioSprite=!1,this._audioSpriteEndHandler=createjs.proxy(this._handleAudioSpriteComplete,this),this._mediaPlayFinishedHandler=createjs.proxy(this._handleSoundComplete,this),this._mediaErrorHandler=createjs.proxy(this._handleMediaError,this),this._mediaProgressHandler=createjs.proxy(this._handleMediaProgress,this),this._playbackResource=new Media(a,this._mediaPlayFinishedHandler,this._mediaErrorHandler,this._mediaProgressHandler),c?this._audioSprite=!0:this._setDurationFromSource()}var a=createjs.extend(CordovaAudioSoundInstance,createjs.AbstractSoundInstance);a.setMasterVolume=function(){this._updateVolume()},a.setMasterMute=function(){this._updateVolume()},a.destroy=function(){this.AbstractSoundInstance_destroy(),this._playbackResource.release()},a.getCurrentPosition=function(a,b){this._playbackResource.getCurrentPosition(a,b)},a.toString=function(){return"[CordovaAudioSoundInstance]"},a._handleMediaError=function(){clearTimeout(this.delayTimeoutId),this.playState=createjs.Sound.PLAY_FAILED,this._sendEvent("failed")},a._handleMediaProgress=function(){},a._handleAudioSpriteComplete=function(){this._playbackResource.pause(),this._handleSoundComplete()},a._handleCleanUp=function(){clearTimeout(this._audioSpriteTimeout),this._playbackResource.pause()},a._handleSoundReady=function(){this._playbackResource.seekTo(this._startTime+this._position),this._audioSprite&&(this._audioSpriteTimeout=setTimeout(this._audioSpriteEndHandler,this._duration-this._position)),this._playbackResource.play({playAudioWhenScreenIsLocked:this.playWhenScreenLocked}),this._playStartTime=Date.now()},a._pause=function(){clearTimeout(this._audioSpriteTimeout),this._playbackResource.pause(),this._playStartTime&&(this._position=Date.now()-this._playStartTime,this._playStartTime=null),this._playbackResource.getCurrentPosition(createjs.proxy(this._updatePausePos,this))},a._updatePausePos=function(a){this._position=1e3*a-this._startTime,this._playStartTime&&(this._playStartTime=Date.now())},a._resume=function(){this._audioSprite&&(this._audioSpriteTimeout=setTimeout(this._audioSpriteEndHandler,this._duration-this._position)),this._playbackResource.play({playAudioWhenScreenIsLocked:this.playWhenScreenLocked}),this._playStartTime=Date.now()},a._handleStop=function(){clearTimeout(this._audioSpriteTimeout),this._playbackResource.pause(),this._playbackResource.seekTo(this._startTime),this._playStartTime&&(this._position=0,this._playStartTime=null)},a._updateVolume=function(){var a=this._muted||createjs.Sound._masterMute?0:this._volume*createjs.Sound._masterVolume;this._playbackResource.setVolume(a)},a._calculateCurrentPosition=function(){return this._playStartTime&&(this._position=Date.now()-this._playStartTime+this._position,this._playStartTime=Date.now()),this._position},a._updatePosition=function(){this._playbackResource.seekTo(this._startTime+this._position),this._playStartTime=Date.now(),this._audioSprite&&(clearTimeout(this._audioSpriteTimeout),this._audioSpriteTimeout=setTimeout(this._audioSpriteEndHandler,this._duration-this._position))},a._handleLoop=function(){this._handleSoundReady()},a._updateStartTime=function(){this._audioSprite=!0,this.playState==createjs.Sound.PLAY_SUCCEEDED},a._updateDuration=function(){this._audioSprite,this.playState==createjs.Sound.PLAY_SUCCEEDED&&(clearTimeout(this._audioSpriteTimeout),this._audioSpriteTimeout=setTimeout(this._audioSpriteEndHandler,this._duration-this.position))},a._setDurationFromSource=function(){this._duration=createjs.Sound.activePlugin.getSrcDuration(this.src)},createjs.CordovaAudioSoundInstance=createjs.promote(CordovaAudioSoundInstance,"AbstractSoundInstance")}(),this.createjs=this.createjs||{},function(){"use strict";function CordovaAudioPlugin(){this.AbstractPlugin_constructor(),this._capabilities=b._capabilities,this._loaderClass=createjs.CordovaAudioLoader,this._soundInstanceClass=createjs.CordovaAudioSoundInstance,this._srcDurationHash={}}var a=createjs.extend(CordovaAudioPlugin,createjs.AbstractPlugin),b=CordovaAudioPlugin;b.playWhenScreenLocked=!1,b._capabilities=null,b.isSupported=function(){return b._generateCapabilities(),null!=b._capabilities},b._generateCapabilities=function(){if(null==b._capabilities&&(window.cordova||window.PhoneGap||window.phonegap)&&window.Media){var a=document.createElement("audio");if(null==a.canPlayType)return null;b._capabilities={panning:!1,volume:!0,tracks:-1};for(var c=createjs.Sound.SUPPORTED_EXTENSIONS,d=createjs.Sound.EXTENSION_MAP,e=0,f=c.length;f>e;e++){var g=c[e],h=d[g]||g;b._capabilities[g]="no"!=a.canPlayType("audio/"+g)&&""!=a.canPlayType("audio/"+g)||"no"!=a.canPlayType("audio/"+h)&&""!=a.canPlayType("audio/"+h)}}},a.create=function(a,b,c){var d=this.AbstractPlugin_create(a,b,c);return d.playWhenScreenLocked=this.playWhenScreenLocked,d},a.toString=function(){return"[CordovaAudioPlugin]"},a.setVolume=a.getVolume=a.setMute=null,a.getSrcDuration=function(a){return this._srcDurationHash[a]},a._handlePreloadComplete=function(a){var b=a.target.getItem().src;this._srcDurationHash[b]=a.result,this._audioSources[b]=a.result},a.removeSound=function(a){delete this._srcDurationHash[a],this.AbstractPlugin_removeSound(a)},createjs.CordovaAudioPlugin=createjs.promote(CordovaAudioPlugin,"AbstractPlugin")}(),this.createjs=this.createjs||{},function(){var a=createjs.CordovaAudioPlugin=createjs.CordovaAudioPlugin||{};a.version="NEXT",a.buildDate="Tue, 19 May 2015 17:26:59 GMT"}(); \ No newline at end of file +this.createjs=this.createjs||{},function(){"use strict";function a(a){this.AbstractLoader_constructor(a,!0,createjs.AbstractLoader.SOUND),this._media=null,this._loadTime=0,this._TIMER_FREQUENCY=100}var b=createjs.extend(a,createjs.AbstractLoader);b.load=function(){this._media=new Media(this._item.src,null,createjs.proxy(this._mediaErrorHandler,this)),this._media.seekTo(0),this._getMediaDuration()},b.toString=function(){return"[CordovaAudioLoader]"},b._mediaErrorHandler=function(){this._media.release(),this._sendError()},b._getMediaDuration=function(){this._result=1e3*this._media.getDuration(),this._result<0?(this._loadTime+=this._TIMER_FREQUENCY,this._loadTime>this._item.loadTimeout?this.handleEvent({type:"timeout"}):setTimeout(createjs.proxy(this._getMediaDuration,this),this._TIMER_FREQUENCY)):(this._media.release(),this._sendComplete())},createjs.CordovaAudioLoader=createjs.promote(a,"AbstractLoader")}(),this.createjs=this.createjs||{},function(){"use strict";function CordovaAudioSoundInstance(a,b,c,d){this.AbstractSoundInstance_constructor(a,b,c,d),this.playWhenScreenLocked=null,this._playStartTime=null,this._audioSpriteTimeout=null,this._audioSprite=!1,this._audioSpriteEndHandler=createjs.proxy(this._handleAudioSpriteComplete,this),this._mediaPlayFinishedHandler=createjs.proxy(this._handleSoundComplete,this),this._mediaErrorHandler=createjs.proxy(this._handleMediaError,this),this._mediaProgressHandler=createjs.proxy(this._handleMediaProgress,this),this._playbackResource=new Media(a,this._mediaPlayFinishedHandler,this._mediaErrorHandler,this._mediaProgressHandler),c?this._audioSprite=!0:this._setDurationFromSource()}var a=createjs.extend(CordovaAudioSoundInstance,createjs.AbstractSoundInstance);a.setMasterVolume=function(){this._updateVolume()},a.setMasterMute=function(){this._updateVolume()},a.destroy=function(){this.AbstractSoundInstance_destroy(),this._playbackResource.release()},a.getCurrentPosition=function(a,b){this._playbackResource.getCurrentPosition(a,b)},a.toString=function(){return"[CordovaAudioSoundInstance]"},a._handleMediaError=function(){clearTimeout(this.delayTimeoutId),this.playState=createjs.Sound.PLAY_FAILED,this._sendEvent("failed")},a._handleMediaProgress=function(){},a._handleAudioSpriteComplete=function(){this._playbackResource.pause(),this._handleSoundComplete()},a._handleCleanUp=function(){clearTimeout(this._audioSpriteTimeout),this._playbackResource.pause()},a._handleSoundReady=function(){this._playbackResource.seekTo(this._startTime+this._position),this._audioSprite&&(this._audioSpriteTimeout=setTimeout(this._audioSpriteEndHandler,this._duration-this._position)),this._playbackResource.play({playAudioWhenScreenIsLocked:this.playWhenScreenLocked}),this._playStartTime=Date.now()},a._pause=function(){clearTimeout(this._audioSpriteTimeout),this._playbackResource.pause(),this._playStartTime&&(this._position=Date.now()-this._playStartTime,this._playStartTime=null),this._playbackResource.getCurrentPosition(createjs.proxy(this._updatePausePos,this))},a._updatePausePos=function(a){this._position=1e3*a-this._startTime,this._playStartTime&&(this._playStartTime=Date.now())},a._resume=function(){this._audioSprite&&(this._audioSpriteTimeout=setTimeout(this._audioSpriteEndHandler,this._duration-this._position)),this._playbackResource.play({playAudioWhenScreenIsLocked:this.playWhenScreenLocked}),this._playStartTime=Date.now()},a._handleStop=function(){clearTimeout(this._audioSpriteTimeout),this._playbackResource.pause(),this._playbackResource.seekTo(this._startTime),this._playStartTime&&(this._position=0,this._playStartTime=null)},a._updateVolume=function(){var a=this._muted||createjs.Sound._masterMute?0:this._volume*createjs.Sound._masterVolume;this._playbackResource.setVolume(a)},a._calculateCurrentPosition=function(){return this._playStartTime&&(this._position=Date.now()-this._playStartTime+this._position,this._playStartTime=Date.now()),this._position},a._updatePosition=function(){this._playbackResource.seekTo(this._startTime+this._position),this._playStartTime=Date.now(),this._audioSprite&&(clearTimeout(this._audioSpriteTimeout),this._audioSpriteTimeout=setTimeout(this._audioSpriteEndHandler,this._duration-this._position))},a._handleLoop=function(){this._handleSoundReady()},a._updateStartTime=function(){this._audioSprite=!0,this.playState==createjs.Sound.PLAY_SUCCEEDED},a._updateDuration=function(){this._audioSprite,this.playState==createjs.Sound.PLAY_SUCCEEDED&&(clearTimeout(this._audioSpriteTimeout),this._audioSpriteTimeout=setTimeout(this._audioSpriteEndHandler,this._duration-this.position))},a._setDurationFromSource=function(){this._duration=createjs.Sound.activePlugin.getSrcDuration(this.src)},createjs.CordovaAudioSoundInstance=createjs.promote(CordovaAudioSoundInstance,"AbstractSoundInstance")}(),this.createjs=this.createjs||{},function(){"use strict";function CordovaAudioPlugin(){this.AbstractPlugin_constructor(),this._capabilities=b._capabilities,this._loaderClass=createjs.CordovaAudioLoader,this._soundInstanceClass=createjs.CordovaAudioSoundInstance,this._srcDurationHash={}}var a=createjs.extend(CordovaAudioPlugin,createjs.AbstractPlugin),b=CordovaAudioPlugin;b.playWhenScreenLocked=!1,b._capabilities=null,b.isSupported=function(){return b._generateCapabilities(),null!=b._capabilities},b._generateCapabilities=function(){if(null==b._capabilities&&(window.cordova||window.PhoneGap||window.phonegap)&&window.Media){var a=document.createElement("audio");if(null==a.canPlayType)return null;b._capabilities={panning:!1,volume:!0,tracks:-1};for(var c=createjs.Sound.SUPPORTED_EXTENSIONS,d=createjs.Sound.EXTENSION_MAP,e=0,f=c.length;f>e;e++){var g=c[e],h=d[g]||g;b._capabilities[g]="no"!=a.canPlayType("audio/"+g)&&""!=a.canPlayType("audio/"+g)||"no"!=a.canPlayType("audio/"+h)&&""!=a.canPlayType("audio/"+h)}}},a.create=function(a,b,c){var d=this.AbstractPlugin_create(a,b,c);return d.playWhenScreenLocked=this.playWhenScreenLocked,d},a.toString=function(){return"[CordovaAudioPlugin]"},a.setVolume=a.getVolume=a.setMute=null,a.getSrcDuration=function(a){return this._srcDurationHash[a]},a._handlePreloadComplete=function(a){var b=a.target.getItem().src;this._srcDurationHash[b]=a.result,this._audioSources[b]=a.result},a.removeSound=function(a){delete this._srcDurationHash[a],this.AbstractPlugin_removeSound(a)},createjs.CordovaAudioPlugin=createjs.promote(CordovaAudioPlugin,"AbstractPlugin")}(),this.createjs=this.createjs||{},function(){var a=createjs.CordovaAudioPlugin=createjs.CordovaAudioPlugin||{};a.version="NEXT",a.buildDate="Wed, 27 May 2015 18:12:38 GMT"}(); \ No newline at end of file diff --git a/lib/flashaudioplugin-NEXT.combined.js b/lib/flashaudioplugin-NEXT.combined.js index 48bd7ada..34dbf1e3 100644 --- a/lib/flashaudioplugin-NEXT.combined.js +++ b/lib/flashaudioplugin-NEXT.combined.js @@ -1,3 +1,30 @@ +/*! +* SoundJS +* Visit http://createjs.com/ for documentation, updates and examples. +* +* Copyright (c) 2010 gskinner.com, inc. +* +* Permission is hereby granted, free of charge, to any person +* obtaining a copy of this software and associated documentation +* files (the "Software"), to deal in the Software without +* restriction, including without limitation the rights to use, +* copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following +* conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +* OTHER DEALINGS IN THE SOFTWARE. +*/ //############################################################################## @@ -1572,6 +1599,6 @@ this.createjs = this.createjs || {}; * @type String * @static **/ - s.buildDate = /*=date*/"Tue, 19 May 2015 17:26:59 GMT"; // injected by build process + s.buildDate = /*=date*/"Wed, 27 May 2015 18:12:38 GMT"; // injected by build process })(); \ No newline at end of file diff --git a/lib/flashaudioplugin-NEXT.min.js b/lib/flashaudioplugin-NEXT.min.js index ae130998..2817f596 100644 --- a/lib/flashaudioplugin-NEXT.min.js +++ b/lib/flashaudioplugin-NEXT.min.js @@ -2,7 +2,7 @@ * @license SoundJS * Visit http://createjs.com/ for documentation, updates and examples. * -* Copyright (c) 2011-2013 gskinner.com, inc. +* Copyright (c) 2011-2015 gskinner.com, inc. * * Distributed under the terms of the MIT license. * http://www.opensource.org/licenses/mit-license.html @@ -17,4 +17,4 @@ /*! SWFObject v2.2 is released under the MIT License */ -var swfobject=function(){function a(){if(!R){try{var a=K.getElementsByTagName("body")[0].appendChild(q("span"));a.parentNode.removeChild(a)}catch(b){return}R=!0;for(var c=N.length,d=0;c>d;d++)N[d]()}}function b(a){R?a():N[N.length]=a}function c(a){if(typeof J.addEventListener!=C)J.addEventListener("load",a,!1);else if(typeof K.addEventListener!=C)K.addEventListener("load",a,!1);else if(typeof J.attachEvent!=C)r(J,"onload",a);else if("function"==typeof J.onload){var b=J.onload;J.onload=function(){b(),a()}}else J.onload=a}function d(){M?e():f()}function e(){var a=K.getElementsByTagName("body")[0],b=q(D);b.setAttribute("type",G);var c=a.appendChild(b);if(c){var d=0;!function(){if(typeof c.GetVariable!=C){var e=c.GetVariable("$version");e&&(e=e.split(" ")[1].split(","),U.pv=[parseInt(e[0],10),parseInt(e[1],10),parseInt(e[2],10)])}else if(10>d)return d++,void setTimeout(arguments.callee,10);a.removeChild(b),c=null,f()}()}else f()}function f(){var a=O.length;if(a>0)for(var b=0;a>b;b++){var c=O[b].id,d=O[b].callbackFn,e={success:!1,id:c};if(U.pv[0]>0){var f=p(c);if(f)if(!s(O[b].swfVersion)||U.wk&&U.wk<312)if(O[b].expressInstall&&h()){var k={};k.data=O[b].expressInstall,k.width=f.getAttribute("width")||"0",k.height=f.getAttribute("height")||"0",f.getAttribute("class")&&(k.styleclass=f.getAttribute("class")),f.getAttribute("align")&&(k.align=f.getAttribute("align"));for(var l={},m=f.getElementsByTagName("param"),n=m.length,o=0;n>o;o++)"movie"!=m[o].getAttribute("name").toLowerCase()&&(l[m[o].getAttribute("name")]=m[o].getAttribute("value"));i(k,l,c,d)}else j(f),d&&d(e);else u(c,!0),d&&(e.success=!0,e.ref=g(c),d(e))}else if(u(c,!0),d){var q=g(c);q&&typeof q.SetVariable!=C&&(e.success=!0,e.ref=q),d(e)}}}function g(a){var b=null,c=p(a);if(c&&"OBJECT"==c.nodeName)if(typeof c.SetVariable!=C)b=c;else{var d=c.getElementsByTagName(D)[0];d&&(b=d)}return b}function h(){return!S&&s("6.0.65")&&(U.win||U.mac)&&!(U.wk&&U.wk<312)}function i(a,b,c,d){S=!0,y=d||null,z={success:!1,id:c};var e=p(c);if(e){"OBJECT"==e.nodeName?(w=k(e),x=null):(w=e,x=c),a.id=H,(typeof a.width==C||!/%$/.test(a.width)&&parseInt(a.width,10)<310)&&(a.width="310"),(typeof a.height==C||!/%$/.test(a.height)&&parseInt(a.height,10)<137)&&(a.height="137"),K.title=K.title.slice(0,47)+" - Flash Player Installation";var f=U.ie&&U.win?"ActiveX":"PlugIn",g="MMredirectURL="+encodeURI(window.location).toString().replace(/&/g,"%26")+"&MMplayerType="+f+"&MMdoctitle="+K.title;if(typeof b.flashvars!=C?b.flashvars+="&"+g:b.flashvars=g,U.ie&&U.win&&4!=e.readyState){var h=q("div");c+="SWFObjectNew",h.setAttribute("id",c),e.parentNode.insertBefore(h,e),e.style.display="none",function(){4==e.readyState?e.parentNode.removeChild(e):setTimeout(arguments.callee,10)}()}l(a,b,c)}}function j(a){if(U.ie&&U.win&&4!=a.readyState){var b=q("div");a.parentNode.insertBefore(b,a),b.parentNode.replaceChild(k(a),b),a.style.display="none",function(){4==a.readyState?a.parentNode.removeChild(a):setTimeout(arguments.callee,10)}()}else a.parentNode.replaceChild(k(a),a)}function k(a){var b=q("div");if(U.win&&U.ie)b.innerHTML=a.innerHTML;else{var c=a.getElementsByTagName(D)[0];if(c){var d=c.childNodes;if(d)for(var e=d.length,f=0;e>f;f++)1==d[f].nodeType&&"PARAM"==d[f].nodeName||8==d[f].nodeType||b.appendChild(d[f].cloneNode(!0))}}return b}function l(a,b,c){var d,e=p(c);if(U.wk&&U.wk<312)return d;if(e)if(typeof a.id==C&&(a.id=c),U.ie&&U.win){var f="";for(var g in a)a[g]!=Object.prototype[g]&&("data"==g.toLowerCase()?b.movie=a[g]:"styleclass"==g.toLowerCase()?f+=' class="'+a[g]+'"':"classid"!=g.toLowerCase()&&(f+=" "+g+'="'+a[g]+'"'));var h="";for(var i in b)b[i]!=Object.prototype[i]&&(h+='');e.outerHTML='"+h+"",P[P.length]=a.id,d=p(a.id)}else{var j=q(D);j.setAttribute("type",G);for(var k in a)a[k]!=Object.prototype[k]&&("styleclass"==k.toLowerCase()?j.setAttribute("class",a[k]):"classid"!=k.toLowerCase()&&j.setAttribute(k,a[k]));for(var l in b)b[l]!=Object.prototype[l]&&"movie"!=l.toLowerCase()&&m(j,l,b[l]);e.parentNode.replaceChild(j,e),d=j}return d}function m(a,b,c){var d=q("param");d.setAttribute("name",b),d.setAttribute("value",c),a.appendChild(d)}function n(a){var b=p(a);b&&"OBJECT"==b.nodeName&&(U.ie&&U.win?(b.style.display="none",function(){4==b.readyState?o(a):setTimeout(arguments.callee,10)}()):b.parentNode.removeChild(b))}function o(a){var b=p(a);if(b){for(var c in b)"function"==typeof b[c]&&(b[c]=null);b.parentNode.removeChild(b)}}function p(a){var b=null;try{b=K.getElementById(a)}catch(c){}return b}function q(a){return K.createElement(a)}function r(a,b,c){a.attachEvent(b,c),Q[Q.length]=[a,b,c]}function s(a){var b=U.pv,c=a.split(".");return c[0]=parseInt(c[0],10),c[1]=parseInt(c[1],10)||0,c[2]=parseInt(c[2],10)||0,b[0]>c[0]||b[0]==c[0]&&b[1]>c[1]||b[0]==c[0]&&b[1]==c[1]&&b[2]>=c[2]?!0:!1}function t(a,b,c,d){if(!U.ie||!U.mac){var e=K.getElementsByTagName("head")[0];if(e){var f=c&&"string"==typeof c?c:"screen";if(d&&(A=null,B=null),!A||B!=f){var g=q("style");g.setAttribute("type","text/css"),g.setAttribute("media",f),A=e.appendChild(g),U.ie&&U.win&&typeof K.styleSheets!=C&&K.styleSheets.length>0&&(A=K.styleSheets[K.styleSheets.length-1]),B=f}U.ie&&U.win?A&&typeof A.addRule==D&&A.addRule(a,b):A&&typeof K.createTextNode!=C&&A.appendChild(K.createTextNode(a+" {"+b+"}"))}}}function u(a,b){if(T){var c=b?"visible":"hidden";R&&p(a)?p(a).style.visibility=c:t("#"+a,"visibility:"+c)}}function v(a){var b=/[\\\"<>\.;]/,c=null!=b.exec(a);return c&&typeof encodeURIComponent!=C?encodeURIComponent(a):a}{var w,x,y,z,A,B,C="undefined",D="object",E="Shockwave Flash",F="ShockwaveFlash.ShockwaveFlash",G="application/x-shockwave-flash",H="SWFObjectExprInst",I="onreadystatechange",J=window,K=document,L=navigator,M=!1,N=[d],O=[],P=[],Q=[],R=!1,S=!1,T=!0,U=function(){var a=typeof K.getElementById!=C&&typeof K.getElementsByTagName!=C&&typeof K.createElement!=C,b=L.userAgent.toLowerCase(),c=L.platform.toLowerCase(),d=/win/.test(c?c:b),e=/mac/.test(c?c:b),f=/webkit/.test(b)?parseFloat(b.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):!1,g=!1,h=[0,0,0],i=null;if(typeof L.plugins!=C&&typeof L.plugins[E]==D)i=L.plugins[E].description,!i||typeof L.mimeTypes!=C&&L.mimeTypes[G]&&!L.mimeTypes[G].enabledPlugin||(M=!0,g=!1,i=i.replace(/^.*\s+(\S+\s+\S+$)/,"$1"),h[0]=parseInt(i.replace(/^(.*)\..*$/,"$1"),10),h[1]=parseInt(i.replace(/^.*\.(.*)\s.*$/,"$1"),10),h[2]=/[a-zA-Z]/.test(i)?parseInt(i.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0);else if(typeof J.ActiveXObject!=C)try{var j=new ActiveXObject(F);j&&(i=j.GetVariable("$version"),i&&(g=!0,i=i.split(" ")[1].split(","),h=[parseInt(i[0],10),parseInt(i[1],10),parseInt(i[2],10)]))}catch(k){}return{w3:a,pv:h,wk:f,ie:g,win:d,mac:e}}();!function(){U.w3&&((typeof K.readyState!=C&&"complete"==K.readyState||typeof K.readyState==C&&(K.getElementsByTagName("body")[0]||K.body))&&a(),R||(typeof K.addEventListener!=C&&K.addEventListener("DOMContentLoaded",a,!1),U.ie&&U.win&&(K.attachEvent(I,function(){"complete"==K.readyState&&(K.detachEvent(I,arguments.callee),a())}),J==top&&!function(){if(!R){try{K.documentElement.doScroll("left")}catch(b){return void setTimeout(arguments.callee,0)}a()}}()),U.wk&&!function(){return R?void 0:/loaded|complete/.test(K.readyState)?void a():void setTimeout(arguments.callee,0)}(),c(a)))}(),function(){U.ie&&U.win&&window.attachEvent("onunload",function(){for(var a=Q.length,b=0;a>b;b++)Q[b][0].detachEvent(Q[b][1],Q[b][2]);for(var c=P.length,d=0;c>d;d++)n(P[d]);for(var e in U)U[e]=null;U=null;for(var f in swfobject)swfobject[f]=null;swfobject=null})}()}return{registerObject:function(a,b,c,d){if(U.w3&&a&&b){var e={};e.id=a,e.swfVersion=b,e.expressInstall=c,e.callbackFn=d,O[O.length]=e,u(a,!1)}else d&&d({success:!1,id:a})},getObjectById:function(a){return U.w3?g(a):void 0},embedSWF:function(a,c,d,e,f,g,j,k,m,n){var o={success:!1,id:c};U.w3&&!(U.wk&&U.wk<312)&&a&&c&&d&&e&&f?(u(c,!1),b(function(){d+="",e+="";var b={};if(m&&typeof m===D)for(var p in m)b[p]=m[p];b.data=a,b.width=d,b.height=e;var q={};if(k&&typeof k===D)for(var r in k)q[r]=k[r];if(j&&typeof j===D)for(var t in j)typeof q.flashvars!=C?q.flashvars+="&"+t+"="+j[t]:q.flashvars=t+"="+j[t];if(s(f)){var v=l(b,q,c);b.id==c&&u(c,!0),o.success=!0,o.ref=v}else{if(g&&h())return b.data=g,void i(b,q,c,n);u(c,!0)}n&&n(o)})):n&&n(o)},switchOffAutoHideShow:function(){T=!1},ua:U,getFlashPlayerVersion:function(){return{major:U.pv[0],minor:U.pv[1],release:U.pv[2]}},hasFlashPlayerVersion:s,createSWF:function(a,b,c){return U.w3?l(a,b,c):void 0},showExpressInstall:function(a,b,c,d){U.w3&&h()&&i(a,b,c,d)},removeSWF:function(a){U.w3&&n(a)},createCSS:function(a,b,c,d){U.w3&&t(a,b,c,d)},addDomLoadEvent:b,addLoadEvent:c,getQueryParamValue:function(a){var b=K.location.search||K.location.hash;if(b){if(/\?/.test(b)&&(b=b.split("?")[1]),null==a)return v(b);for(var c=b.split("&"),d=0;de;e++)d.push(arguments[e]);try{0==d.length?c[b]():c[b].apply(c,d)}catch(g){}}},a.handlePreloadEvent=function(a,b){var c=this._flashPreloadInstances[a];if(null!=c){for(var d=[],e=2,f=arguments.length;f>e;e++)d.push(arguments[e]);try{0==d.length?c[b]():c[b].apply(c,d)}catch(g){}}},a.handleEvent=function(a){switch(a){case"ready":this._handleFlashReady()}},a.handleErrorEvent=function(){},createjs.FlashAudioPlugin=createjs.promote(FlashAudioPlugin,"AbstractPlugin")}(),this.createjs=this.createjs||{},function(){var a=createjs.FlashAudioPlugin=createjs.FlashAudioPlugin||{};a.version="NEXT",a.buildDate="Tue, 19 May 2015 17:26:59 GMT"}(); \ No newline at end of file +var swfobject=function(){function a(){if(!R){try{var a=K.getElementsByTagName("body")[0].appendChild(q("span"));a.parentNode.removeChild(a)}catch(b){return}R=!0;for(var c=N.length,d=0;c>d;d++)N[d]()}}function b(a){R?a():N[N.length]=a}function c(a){if(typeof J.addEventListener!=C)J.addEventListener("load",a,!1);else if(typeof K.addEventListener!=C)K.addEventListener("load",a,!1);else if(typeof J.attachEvent!=C)r(J,"onload",a);else if("function"==typeof J.onload){var b=J.onload;J.onload=function(){b(),a()}}else J.onload=a}function d(){M?e():f()}function e(){var a=K.getElementsByTagName("body")[0],b=q(D);b.setAttribute("type",G);var c=a.appendChild(b);if(c){var d=0;!function(){if(typeof c.GetVariable!=C){var e=c.GetVariable("$version");e&&(e=e.split(" ")[1].split(","),U.pv=[parseInt(e[0],10),parseInt(e[1],10),parseInt(e[2],10)])}else if(10>d)return d++,void setTimeout(arguments.callee,10);a.removeChild(b),c=null,f()}()}else f()}function f(){var a=O.length;if(a>0)for(var b=0;a>b;b++){var c=O[b].id,d=O[b].callbackFn,e={success:!1,id:c};if(U.pv[0]>0){var f=p(c);if(f)if(!s(O[b].swfVersion)||U.wk&&U.wk<312)if(O[b].expressInstall&&h()){var k={};k.data=O[b].expressInstall,k.width=f.getAttribute("width")||"0",k.height=f.getAttribute("height")||"0",f.getAttribute("class")&&(k.styleclass=f.getAttribute("class")),f.getAttribute("align")&&(k.align=f.getAttribute("align"));for(var l={},m=f.getElementsByTagName("param"),n=m.length,o=0;n>o;o++)"movie"!=m[o].getAttribute("name").toLowerCase()&&(l[m[o].getAttribute("name")]=m[o].getAttribute("value"));i(k,l,c,d)}else j(f),d&&d(e);else u(c,!0),d&&(e.success=!0,e.ref=g(c),d(e))}else if(u(c,!0),d){var q=g(c);q&&typeof q.SetVariable!=C&&(e.success=!0,e.ref=q),d(e)}}}function g(a){var b=null,c=p(a);if(c&&"OBJECT"==c.nodeName)if(typeof c.SetVariable!=C)b=c;else{var d=c.getElementsByTagName(D)[0];d&&(b=d)}return b}function h(){return!S&&s("6.0.65")&&(U.win||U.mac)&&!(U.wk&&U.wk<312)}function i(a,b,c,d){S=!0,y=d||null,z={success:!1,id:c};var e=p(c);if(e){"OBJECT"==e.nodeName?(w=k(e),x=null):(w=e,x=c),a.id=H,(typeof a.width==C||!/%$/.test(a.width)&&parseInt(a.width,10)<310)&&(a.width="310"),(typeof a.height==C||!/%$/.test(a.height)&&parseInt(a.height,10)<137)&&(a.height="137"),K.title=K.title.slice(0,47)+" - Flash Player Installation";var f=U.ie&&U.win?"ActiveX":"PlugIn",g="MMredirectURL="+encodeURI(window.location).toString().replace(/&/g,"%26")+"&MMplayerType="+f+"&MMdoctitle="+K.title;if(typeof b.flashvars!=C?b.flashvars+="&"+g:b.flashvars=g,U.ie&&U.win&&4!=e.readyState){var h=q("div");c+="SWFObjectNew",h.setAttribute("id",c),e.parentNode.insertBefore(h,e),e.style.display="none",function(){4==e.readyState?e.parentNode.removeChild(e):setTimeout(arguments.callee,10)}()}l(a,b,c)}}function j(a){if(U.ie&&U.win&&4!=a.readyState){var b=q("div");a.parentNode.insertBefore(b,a),b.parentNode.replaceChild(k(a),b),a.style.display="none",function(){4==a.readyState?a.parentNode.removeChild(a):setTimeout(arguments.callee,10)}()}else a.parentNode.replaceChild(k(a),a)}function k(a){var b=q("div");if(U.win&&U.ie)b.innerHTML=a.innerHTML;else{var c=a.getElementsByTagName(D)[0];if(c){var d=c.childNodes;if(d)for(var e=d.length,f=0;e>f;f++)1==d[f].nodeType&&"PARAM"==d[f].nodeName||8==d[f].nodeType||b.appendChild(d[f].cloneNode(!0))}}return b}function l(a,b,c){var d,e=p(c);if(U.wk&&U.wk<312)return d;if(e)if(typeof a.id==C&&(a.id=c),U.ie&&U.win){var f="";for(var g in a)a[g]!=Object.prototype[g]&&("data"==g.toLowerCase()?b.movie=a[g]:"styleclass"==g.toLowerCase()?f+=' class="'+a[g]+'"':"classid"!=g.toLowerCase()&&(f+=" "+g+'="'+a[g]+'"'));var h="";for(var i in b)b[i]!=Object.prototype[i]&&(h+='');e.outerHTML='"+h+"",P[P.length]=a.id,d=p(a.id)}else{var j=q(D);j.setAttribute("type",G);for(var k in a)a[k]!=Object.prototype[k]&&("styleclass"==k.toLowerCase()?j.setAttribute("class",a[k]):"classid"!=k.toLowerCase()&&j.setAttribute(k,a[k]));for(var l in b)b[l]!=Object.prototype[l]&&"movie"!=l.toLowerCase()&&m(j,l,b[l]);e.parentNode.replaceChild(j,e),d=j}return d}function m(a,b,c){var d=q("param");d.setAttribute("name",b),d.setAttribute("value",c),a.appendChild(d)}function n(a){var b=p(a);b&&"OBJECT"==b.nodeName&&(U.ie&&U.win?(b.style.display="none",function(){4==b.readyState?o(a):setTimeout(arguments.callee,10)}()):b.parentNode.removeChild(b))}function o(a){var b=p(a);if(b){for(var c in b)"function"==typeof b[c]&&(b[c]=null);b.parentNode.removeChild(b)}}function p(a){var b=null;try{b=K.getElementById(a)}catch(c){}return b}function q(a){return K.createElement(a)}function r(a,b,c){a.attachEvent(b,c),Q[Q.length]=[a,b,c]}function s(a){var b=U.pv,c=a.split(".");return c[0]=parseInt(c[0],10),c[1]=parseInt(c[1],10)||0,c[2]=parseInt(c[2],10)||0,b[0]>c[0]||b[0]==c[0]&&b[1]>c[1]||b[0]==c[0]&&b[1]==c[1]&&b[2]>=c[2]?!0:!1}function t(a,b,c,d){if(!U.ie||!U.mac){var e=K.getElementsByTagName("head")[0];if(e){var f=c&&"string"==typeof c?c:"screen";if(d&&(A=null,B=null),!A||B!=f){var g=q("style");g.setAttribute("type","text/css"),g.setAttribute("media",f),A=e.appendChild(g),U.ie&&U.win&&typeof K.styleSheets!=C&&K.styleSheets.length>0&&(A=K.styleSheets[K.styleSheets.length-1]),B=f}U.ie&&U.win?A&&typeof A.addRule==D&&A.addRule(a,b):A&&typeof K.createTextNode!=C&&A.appendChild(K.createTextNode(a+" {"+b+"}"))}}}function u(a,b){if(T){var c=b?"visible":"hidden";R&&p(a)?p(a).style.visibility=c:t("#"+a,"visibility:"+c)}}function v(a){var b=/[\\\"<>\.;]/,c=null!=b.exec(a);return c&&typeof encodeURIComponent!=C?encodeURIComponent(a):a}{var w,x,y,z,A,B,C="undefined",D="object",E="Shockwave Flash",F="ShockwaveFlash.ShockwaveFlash",G="application/x-shockwave-flash",H="SWFObjectExprInst",I="onreadystatechange",J=window,K=document,L=navigator,M=!1,N=[d],O=[],P=[],Q=[],R=!1,S=!1,T=!0,U=function(){var a=typeof K.getElementById!=C&&typeof K.getElementsByTagName!=C&&typeof K.createElement!=C,b=L.userAgent.toLowerCase(),c=L.platform.toLowerCase(),d=/win/.test(c?c:b),e=/mac/.test(c?c:b),f=/webkit/.test(b)?parseFloat(b.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):!1,g=!1,h=[0,0,0],i=null;if(typeof L.plugins!=C&&typeof L.plugins[E]==D)i=L.plugins[E].description,!i||typeof L.mimeTypes!=C&&L.mimeTypes[G]&&!L.mimeTypes[G].enabledPlugin||(M=!0,g=!1,i=i.replace(/^.*\s+(\S+\s+\S+$)/,"$1"),h[0]=parseInt(i.replace(/^(.*)\..*$/,"$1"),10),h[1]=parseInt(i.replace(/^.*\.(.*)\s.*$/,"$1"),10),h[2]=/[a-zA-Z]/.test(i)?parseInt(i.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0);else if(typeof J.ActiveXObject!=C)try{var j=new ActiveXObject(F);j&&(i=j.GetVariable("$version"),i&&(g=!0,i=i.split(" ")[1].split(","),h=[parseInt(i[0],10),parseInt(i[1],10),parseInt(i[2],10)]))}catch(k){}return{w3:a,pv:h,wk:f,ie:g,win:d,mac:e}}();!function(){U.w3&&((typeof K.readyState!=C&&"complete"==K.readyState||typeof K.readyState==C&&(K.getElementsByTagName("body")[0]||K.body))&&a(),R||(typeof K.addEventListener!=C&&K.addEventListener("DOMContentLoaded",a,!1),U.ie&&U.win&&(K.attachEvent(I,function(){"complete"==K.readyState&&(K.detachEvent(I,arguments.callee),a())}),J==top&&!function(){if(!R){try{K.documentElement.doScroll("left")}catch(b){return void setTimeout(arguments.callee,0)}a()}}()),U.wk&&!function(){return R?void 0:/loaded|complete/.test(K.readyState)?void a():void setTimeout(arguments.callee,0)}(),c(a)))}(),function(){U.ie&&U.win&&window.attachEvent("onunload",function(){for(var a=Q.length,b=0;a>b;b++)Q[b][0].detachEvent(Q[b][1],Q[b][2]);for(var c=P.length,d=0;c>d;d++)n(P[d]);for(var e in U)U[e]=null;U=null;for(var f in swfobject)swfobject[f]=null;swfobject=null})}()}return{registerObject:function(a,b,c,d){if(U.w3&&a&&b){var e={};e.id=a,e.swfVersion=b,e.expressInstall=c,e.callbackFn=d,O[O.length]=e,u(a,!1)}else d&&d({success:!1,id:a})},getObjectById:function(a){return U.w3?g(a):void 0},embedSWF:function(a,c,d,e,f,g,j,k,m,n){var o={success:!1,id:c};U.w3&&!(U.wk&&U.wk<312)&&a&&c&&d&&e&&f?(u(c,!1),b(function(){d+="",e+="";var b={};if(m&&typeof m===D)for(var p in m)b[p]=m[p];b.data=a,b.width=d,b.height=e;var q={};if(k&&typeof k===D)for(var r in k)q[r]=k[r];if(j&&typeof j===D)for(var t in j)typeof q.flashvars!=C?q.flashvars+="&"+t+"="+j[t]:q.flashvars=t+"="+j[t];if(s(f)){var v=l(b,q,c);b.id==c&&u(c,!0),o.success=!0,o.ref=v}else{if(g&&h())return b.data=g,void i(b,q,c,n);u(c,!0)}n&&n(o)})):n&&n(o)},switchOffAutoHideShow:function(){T=!1},ua:U,getFlashPlayerVersion:function(){return{major:U.pv[0],minor:U.pv[1],release:U.pv[2]}},hasFlashPlayerVersion:s,createSWF:function(a,b,c){return U.w3?l(a,b,c):void 0},showExpressInstall:function(a,b,c,d){U.w3&&h()&&i(a,b,c,d)},removeSWF:function(a){U.w3&&n(a)},createCSS:function(a,b,c,d){U.w3&&t(a,b,c,d)},addDomLoadEvent:b,addLoadEvent:c,getQueryParamValue:function(a){var b=K.location.search||K.location.hash;if(b){if(/\?/.test(b)&&(b=b.split("?")[1]),null==a)return v(b);for(var c=b.split("&"),d=0;de;e++)d.push(arguments[e]);try{0==d.length?c[b]():c[b].apply(c,d)}catch(g){}}},a.handlePreloadEvent=function(a,b){var c=this._flashPreloadInstances[a];if(null!=c){for(var d=[],e=2,f=arguments.length;f>e;e++)d.push(arguments[e]);try{0==d.length?c[b]():c[b].apply(c,d)}catch(g){}}},a.handleEvent=function(a){switch(a){case"ready":this._handleFlashReady()}},a.handleErrorEvent=function(){},createjs.FlashAudioPlugin=createjs.promote(FlashAudioPlugin,"AbstractPlugin")}(),this.createjs=this.createjs||{},function(){var a=createjs.FlashAudioPlugin=createjs.FlashAudioPlugin||{};a.version="NEXT",a.buildDate="Wed, 27 May 2015 18:12:38 GMT"}(); \ No newline at end of file diff --git a/lib/soundjs-NEXT.combined.js b/lib/soundjs-NEXT.combined.js index df5d171b..40d36302 100644 --- a/lib/soundjs-NEXT.combined.js +++ b/lib/soundjs-NEXT.combined.js @@ -1,268 +1,295 @@ +/*! +* SoundJS +* Visit http://createjs.com/ for documentation, updates and examples. +* +* Copyright (c) 2010 gskinner.com, inc. +* +* Permission is hereby granted, free of charge, to any person +* obtaining a copy of this software and associated documentation +* files (the "Software"), to deal in the Software without +* restriction, including without limitation the rights to use, +* copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following +* conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +* OTHER DEALINGS IN THE SOFTWARE. +*/ //############################################################################## // version.js //############################################################################## -this.createjs = this.createjs || {}; - -(function () { - - /** - * Static class holding library specific information such as the version and buildDate of the library. - * The SoundJS class has been renamed {{#crossLink "Sound"}}{{/crossLink}}. Please see {{#crossLink "Sound"}}{{/crossLink}} - * for information on using sound. - * @class SoundJS - **/ - var s = createjs.SoundJS = createjs.SoundJS || {}; - - /** - * The version string for this release. - * @property version - * @type String - * @static - **/ - s.version = /*=version*/"NEXT"; // injected by build process - - /** - * The build date for this release in UTC format. - * @property buildDate - * @type String - * @static - **/ - s.buildDate = /*=date*/"Tue, 19 May 2015 17:26:59 GMT"; // injected by build process - +this.createjs = this.createjs || {}; + +(function () { + + /** + * Static class holding library specific information such as the version and buildDate of the library. + * The SoundJS class has been renamed {{#crossLink "Sound"}}{{/crossLink}}. Please see {{#crossLink "Sound"}}{{/crossLink}} + * for information on using sound. + * @class SoundJS + **/ + var s = createjs.SoundJS = createjs.SoundJS || {}; + + /** + * The version string for this release. + * @property version + * @type String + * @static + **/ + s.version = /*=version*/"NEXT"; // injected by build process + + /** + * The build date for this release in UTC format. + * @property buildDate + * @type String + * @static + **/ + s.buildDate = /*=date*/"Wed, 27 May 2015 18:12:38 GMT"; // injected by build process + })(); //############################################################################## // extend.js //############################################################################## -this.createjs = this.createjs||{}; - -/** - * @class Utility Methods - */ - -/** - * Sets up the prototype chain and constructor property for a new class. - * - * This should be called right after creating the class constructor. - * - * function MySubClass() {} - * createjs.extend(MySubClass, MySuperClass); - * ClassB.prototype.doSomething = function() { } - * - * var foo = new MySubClass(); - * console.log(foo instanceof MySuperClass); // true - * console.log(foo.prototype.constructor === MySubClass); // true - * - * @method extend - * @param {Function} subclass The subclass. - * @param {Function} superclass The superclass to extend. - * @return {Function} Returns the subclass's new prototype. - */ -createjs.extend = function(subclass, superclass) { - "use strict"; - - function o() { this.constructor = subclass; } - o.prototype = superclass.prototype; - return (subclass.prototype = new o()); +this.createjs = this.createjs||{}; + +/** + * @class Utility Methods + */ + +/** + * Sets up the prototype chain and constructor property for a new class. + * + * This should be called right after creating the class constructor. + * + * function MySubClass() {} + * createjs.extend(MySubClass, MySuperClass); + * ClassB.prototype.doSomething = function() { } + * + * var foo = new MySubClass(); + * console.log(foo instanceof MySuperClass); // true + * console.log(foo.prototype.constructor === MySubClass); // true + * + * @method extend + * @param {Function} subclass The subclass. + * @param {Function} superclass The superclass to extend. + * @return {Function} Returns the subclass's new prototype. + */ +createjs.extend = function(subclass, superclass) { + "use strict"; + + function o() { this.constructor = subclass; } + o.prototype = superclass.prototype; + return (subclass.prototype = new o()); }; //############################################################################## // promote.js //############################################################################## -this.createjs = this.createjs||{}; - -/** - * @class Utility Methods - */ - -/** - * Promotes any methods on the super class that were overridden, by creating an alias in the format `prefix_methodName`. - * It is recommended to use the super class's name as the prefix. - * An alias to the super class's constructor is always added in the format `prefix_constructor`. - * This allows the subclass to call super class methods without using `function.call`, providing better performance. - * - * For example, if `MySubClass` extends `MySuperClass`, and both define a `draw` method, then calling `promote(MySubClass, "MySuperClass")` - * would add a `MySuperClass_constructor` method to MySubClass and promote the `draw` method on `MySuperClass` to the - * prototype of `MySubClass` as `MySuperClass_draw`. - * - * This should be called after the class's prototype is fully defined. - * - * function ClassA(name) { - * this.name = name; - * } - * ClassA.prototype.greet = function() { - * return "Hello "+this.name; - * } - * - * function ClassB(name, punctuation) { - * this.ClassA_constructor(name); - * this.punctuation = punctuation; - * } - * createjs.extend(ClassB, ClassA); - * ClassB.prototype.greet = function() { - * return this.ClassA_greet()+this.punctuation; - * } - * createjs.promote(ClassB, "ClassA"); - * - * var foo = new ClassB("World", "!?!"); - * console.log(foo.greet()); // Hello World!?! - * - * @method promote - * @param {Function} subclass The class to promote super class methods on. - * @param {String} prefix The prefix to add to the promoted method names. Usually the name of the superclass. - * @return {Function} Returns the subclass. - */ -createjs.promote = function(subclass, prefix) { - "use strict"; - - var subP = subclass.prototype, supP = (Object.getPrototypeOf&&Object.getPrototypeOf(subP))||subP.__proto__; - if (supP) { - subP[(prefix+="_") + "constructor"] = supP.constructor; // constructor is not always innumerable - for (var n in supP) { - if (subP.hasOwnProperty(n) && (typeof supP[n] == "function")) { subP[prefix + n] = supP[n]; } - } - } - return subclass; +this.createjs = this.createjs||{}; + +/** + * @class Utility Methods + */ + +/** + * Promotes any methods on the super class that were overridden, by creating an alias in the format `prefix_methodName`. + * It is recommended to use the super class's name as the prefix. + * An alias to the super class's constructor is always added in the format `prefix_constructor`. + * This allows the subclass to call super class methods without using `function.call`, providing better performance. + * + * For example, if `MySubClass` extends `MySuperClass`, and both define a `draw` method, then calling `promote(MySubClass, "MySuperClass")` + * would add a `MySuperClass_constructor` method to MySubClass and promote the `draw` method on `MySuperClass` to the + * prototype of `MySubClass` as `MySuperClass_draw`. + * + * This should be called after the class's prototype is fully defined. + * + * function ClassA(name) { + * this.name = name; + * } + * ClassA.prototype.greet = function() { + * return "Hello "+this.name; + * } + * + * function ClassB(name, punctuation) { + * this.ClassA_constructor(name); + * this.punctuation = punctuation; + * } + * createjs.extend(ClassB, ClassA); + * ClassB.prototype.greet = function() { + * return this.ClassA_greet()+this.punctuation; + * } + * createjs.promote(ClassB, "ClassA"); + * + * var foo = new ClassB("World", "!?!"); + * console.log(foo.greet()); // Hello World!?! + * + * @method promote + * @param {Function} subclass The class to promote super class methods on. + * @param {String} prefix The prefix to add to the promoted method names. Usually the name of the superclass. + * @return {Function} Returns the subclass. + */ +createjs.promote = function(subclass, prefix) { + "use strict"; + + var subP = subclass.prototype, supP = (Object.getPrototypeOf&&Object.getPrototypeOf(subP))||subP.__proto__; + if (supP) { + subP[(prefix+="_") + "constructor"] = supP.constructor; // constructor is not always innumerable + for (var n in supP) { + if (subP.hasOwnProperty(n) && (typeof supP[n] == "function")) { subP[prefix + n] = supP[n]; } + } + } + return subclass; }; //############################################################################## // IndexOf.js //############################################################################## -this.createjs = this.createjs||{}; - -/** - * @class Utility Methods - */ - -/** - * Finds the first occurrence of a specified value searchElement in the passed in array, and returns the index of - * that value. Returns -1 if value is not found. - * - * var i = createjs.indexOf(myArray, myElementToFind); - * - * @method indexOf - * @param {Array} array Array to search for searchElement - * @param searchElement Element to find in array. - * @return {Number} The first index of searchElement in array. - */ -createjs.indexOf = function (array, searchElement){ - "use strict"; - - for (var i = 0,l=array.length; i < l; i++) { - if (searchElement === array[i]) { - return i; - } - } - return -1; +this.createjs = this.createjs||{}; + +/** + * @class Utility Methods + */ + +/** + * Finds the first occurrence of a specified value searchElement in the passed in array, and returns the index of + * that value. Returns -1 if value is not found. + * + * var i = createjs.indexOf(myArray, myElementToFind); + * + * @method indexOf + * @param {Array} array Array to search for searchElement + * @param searchElement Element to find in array. + * @return {Number} The first index of searchElement in array. + */ +createjs.indexOf = function (array, searchElement){ + "use strict"; + + for (var i = 0,l=array.length; i < l; i++) { + if (searchElement === array[i]) { + return i; + } + } + return -1; }; //############################################################################## // Proxy.js //############################################################################## -this.createjs = this.createjs||{}; - -/** - * Various utilities that the CreateJS Suite uses. Utilities are created as separate files, and will be available on the - * createjs namespace directly. - * - *

Example

- * - * myObject.addEventListener("change", createjs.proxy(myMethod, scope)); - * - * @class Utility Methods - * @main Utility Methods - */ - -(function() { - "use strict"; - - /** - * A function proxy for methods. By default, JavaScript methods do not maintain scope, so passing a method as a - * callback will result in the method getting called in the scope of the caller. Using a proxy ensures that the - * method gets called in the correct scope. - * - * Additional arguments can be passed that will be applied to the function when it is called. - * - *

Example

- * - * myObject.addEventListener("event", createjs.proxy(myHandler, this, arg1, arg2)); - * - * function myHandler(arg1, arg2) { - * // This gets called when myObject.myCallback is executed. - * } - * - * @method proxy - * @param {Function} method The function to call - * @param {Object} scope The scope to call the method name on - * @param {mixed} [arg] * Arguments that are appended to the callback for additional params. - * @public - * @static - */ - createjs.proxy = function (method, scope) { - var aArgs = Array.prototype.slice.call(arguments, 2); - return function () { - return method.apply(scope, Array.prototype.slice.call(arguments, 0).concat(aArgs)); - }; - } - +this.createjs = this.createjs||{}; + +/** + * Various utilities that the CreateJS Suite uses. Utilities are created as separate files, and will be available on the + * createjs namespace directly. + * + *

Example

+ * + * myObject.addEventListener("change", createjs.proxy(myMethod, scope)); + * + * @class Utility Methods + * @main Utility Methods + */ + +(function() { + "use strict"; + + /** + * A function proxy for methods. By default, JavaScript methods do not maintain scope, so passing a method as a + * callback will result in the method getting called in the scope of the caller. Using a proxy ensures that the + * method gets called in the correct scope. + * + * Additional arguments can be passed that will be applied to the function when it is called. + * + *

Example

+ * + * myObject.addEventListener("event", createjs.proxy(myHandler, this, arg1, arg2)); + * + * function myHandler(arg1, arg2) { + * // This gets called when myObject.myCallback is executed. + * } + * + * @method proxy + * @param {Function} method The function to call + * @param {Object} scope The scope to call the method name on + * @param {mixed} [arg] * Arguments that are appended to the callback for additional params. + * @public + * @static + */ + createjs.proxy = function (method, scope) { + var aArgs = Array.prototype.slice.call(arguments, 2); + return function () { + return method.apply(scope, Array.prototype.slice.call(arguments, 0).concat(aArgs)); + }; + } + }()); //############################################################################## // BrowserDetect.js //############################################################################## -this.createjs = this.createjs||{}; - -/** - * @class Utility Methods - */ -(function() { - "use strict"; - - /** - * An object that determines the current browser, version, operating system, and other environment - * variables via user agent string. - * - * Used for audio because feature detection is unable to detect the many limitations of mobile devices. - * - *

Example

- * - * if (createjs.BrowserDetect.isIOS) { // do stuff } - * - * @property BrowserDetect - * @type {Object} - * @param {Boolean} isFirefox True if our browser is Firefox. - * @param {Boolean} isOpera True if our browser is opera. - * @param {Boolean} isChrome True if our browser is Chrome. Note that Chrome for Android returns true, but is a - * completely different browser with different abilities. - * @param {Boolean} isIOS True if our browser is safari for iOS devices (iPad, iPhone, and iPod). - * @param {Boolean} isAndroid True if our browser is Android. - * @param {Boolean} isBlackberry True if our browser is Blackberry. - * @constructor - * @static - */ - function BrowserDetect() { - throw "BrowserDetect cannot be instantiated"; - }; - - var agent = BrowserDetect.agent = window.navigator.userAgent; - BrowserDetect.isWindowPhone = (agent.indexOf("IEMobile") > -1) || (agent.indexOf("Windows Phone") > -1); - BrowserDetect.isFirefox = (agent.indexOf("Firefox") > -1); - BrowserDetect.isOpera = (window.opera != null); - BrowserDetect.isChrome = (agent.indexOf("Chrome") > -1); // NOTE that Chrome on Android returns true but is a completely different browser with different abilities - BrowserDetect.isIOS = (agent.indexOf("iPod") > -1 || agent.indexOf("iPhone") > -1 || agent.indexOf("iPad") > -1) && !BrowserDetect.isWindowPhone; - BrowserDetect.isAndroid = (agent.indexOf("Android") > -1) && !BrowserDetect.isWindowPhone; - BrowserDetect.isBlackberry = (agent.indexOf("Blackberry") > -1); - - createjs.BrowserDetect = BrowserDetect; - +this.createjs = this.createjs||{}; + +/** + * @class Utility Methods + */ +(function() { + "use strict"; + + /** + * An object that determines the current browser, version, operating system, and other environment + * variables via user agent string. + * + * Used for audio because feature detection is unable to detect the many limitations of mobile devices. + * + *

Example

+ * + * if (createjs.BrowserDetect.isIOS) { // do stuff } + * + * @property BrowserDetect + * @type {Object} + * @param {Boolean} isFirefox True if our browser is Firefox. + * @param {Boolean} isOpera True if our browser is opera. + * @param {Boolean} isChrome True if our browser is Chrome. Note that Chrome for Android returns true, but is a + * completely different browser with different abilities. + * @param {Boolean} isIOS True if our browser is safari for iOS devices (iPad, iPhone, and iPod). + * @param {Boolean} isAndroid True if our browser is Android. + * @param {Boolean} isBlackberry True if our browser is Blackberry. + * @constructor + * @static + */ + function BrowserDetect() { + throw "BrowserDetect cannot be instantiated"; + }; + + var agent = BrowserDetect.agent = window.navigator.userAgent; + BrowserDetect.isWindowPhone = (agent.indexOf("IEMobile") > -1) || (agent.indexOf("Windows Phone") > -1); + BrowserDetect.isFirefox = (agent.indexOf("Firefox") > -1); + BrowserDetect.isOpera = (window.opera != null); + BrowserDetect.isChrome = (agent.indexOf("Chrome") > -1); // NOTE that Chrome on Android returns true but is a completely different browser with different abilities + BrowserDetect.isIOS = (agent.indexOf("iPod") > -1 || agent.indexOf("iPhone") > -1 || agent.indexOf("iPad") > -1) && !BrowserDetect.isWindowPhone; + BrowserDetect.isAndroid = (agent.indexOf("Android") > -1) && !BrowserDetect.isWindowPhone; + BrowserDetect.isBlackberry = (agent.indexOf("Blackberry") > -1); + + createjs.BrowserDetect = BrowserDetect; + }()); //############################################################################## @@ -648,7056 +675,7056 @@ this.createjs = this.createjs||{}; // Event.js //############################################################################## -this.createjs = this.createjs||{}; - -(function() { - "use strict"; - -// constructor: - /** - * Contains properties and methods shared by all events for use with - * {{#crossLink "EventDispatcher"}}{{/crossLink}}. - * - * Note that Event objects are often reused, so you should never - * rely on an event object's state outside of the call stack it was received in. - * @class Event - * @param {String} type The event type. - * @param {Boolean} bubbles Indicates whether the event will bubble through the display list. - * @param {Boolean} cancelable Indicates whether the default behaviour of this event can be cancelled. - * @constructor - **/ - function Event(type, bubbles, cancelable) { - - - // public properties: - /** - * The type of event. - * @property type - * @type String - **/ - this.type = type; - - /** - * The object that generated an event. - * @property target - * @type Object - * @default null - * @readonly - */ - this.target = null; - - /** - * The current target that a bubbling event is being dispatched from. For non-bubbling events, this will - * always be the same as target. For example, if childObj.parent = parentObj, and a bubbling event - * is generated from childObj, then a listener on parentObj would receive the event with - * target=childObj (the original target) and currentTarget=parentObj (where the listener was added). - * @property currentTarget - * @type Object - * @default null - * @readonly - */ - this.currentTarget = null; - - /** - * For bubbling events, this indicates the current event phase:
    - *
  1. capture phase: starting from the top parent to the target
  2. - *
  3. at target phase: currently being dispatched from the target
  4. - *
  5. bubbling phase: from the target to the top parent
  6. - *
- * @property eventPhase - * @type Number - * @default 0 - * @readonly - */ - this.eventPhase = 0; - - /** - * Indicates whether the event will bubble through the display list. - * @property bubbles - * @type Boolean - * @default false - * @readonly - */ - this.bubbles = !!bubbles; - - /** - * Indicates whether the default behaviour of this event can be cancelled via - * {{#crossLink "Event/preventDefault"}}{{/crossLink}}. This is set via the Event constructor. - * @property cancelable - * @type Boolean - * @default false - * @readonly - */ - this.cancelable = !!cancelable; - - /** - * The epoch time at which this event was created. - * @property timeStamp - * @type Number - * @default 0 - * @readonly - */ - this.timeStamp = (new Date()).getTime(); - - /** - * Indicates if {{#crossLink "Event/preventDefault"}}{{/crossLink}} has been called - * on this event. - * @property defaultPrevented - * @type Boolean - * @default false - * @readonly - */ - this.defaultPrevented = false; - - /** - * Indicates if {{#crossLink "Event/stopPropagation"}}{{/crossLink}} or - * {{#crossLink "Event/stopImmediatePropagation"}}{{/crossLink}} has been called on this event. - * @property propagationStopped - * @type Boolean - * @default false - * @readonly - */ - this.propagationStopped = false; - - /** - * Indicates if {{#crossLink "Event/stopImmediatePropagation"}}{{/crossLink}} has been called - * on this event. - * @property immediatePropagationStopped - * @type Boolean - * @default false - * @readonly - */ - this.immediatePropagationStopped = false; - - /** - * Indicates if {{#crossLink "Event/remove"}}{{/crossLink}} has been called on this event. - * @property removed - * @type Boolean - * @default false - * @readonly - */ - this.removed = false; - } - var p = Event.prototype; - - /** - * REMOVED. Removed in favor of using `MySuperClass_constructor`. - * See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}} - * for details. - * - * There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance. - * - * @method initialize - * @protected - * @deprecated - */ - // p.initialize = function() {}; // searchable for devs wondering where it is. - - -// public methods: - /** - * Sets {{#crossLink "Event/defaultPrevented"}}{{/crossLink}} to true. - * Mirrors the DOM event standard. - * @method preventDefault - **/ - p.preventDefault = function() { - this.defaultPrevented = this.cancelable&&true; - }; - - /** - * Sets {{#crossLink "Event/propagationStopped"}}{{/crossLink}} to true. - * Mirrors the DOM event standard. - * @method stopPropagation - **/ - p.stopPropagation = function() { - this.propagationStopped = true; - }; - - /** - * Sets {{#crossLink "Event/propagationStopped"}}{{/crossLink}} and - * {{#crossLink "Event/immediatePropagationStopped"}}{{/crossLink}} to true. - * Mirrors the DOM event standard. - * @method stopImmediatePropagation - **/ - p.stopImmediatePropagation = function() { - this.immediatePropagationStopped = this.propagationStopped = true; - }; - - /** - * Causes the active listener to be removed via removeEventListener(); - * - * myBtn.addEventListener("click", function(evt) { - * // do stuff... - * evt.remove(); // removes this listener. - * }); - * - * @method remove - **/ - p.remove = function() { - this.removed = true; - }; - - /** - * Returns a clone of the Event instance. - * @method clone - * @return {Event} a clone of the Event instance. - **/ - p.clone = function() { - return new Event(this.type, this.bubbles, this.cancelable); - }; - - /** - * Provides a chainable shortcut method for setting a number of properties on the instance. - * - * @method set - * @param {Object} props A generic object containing properties to copy to the instance. - * @return {Event} Returns the instance the method is called on (useful for chaining calls.) - * @chainable - */ - p.set = function(props) { - for (var n in props) { this[n] = props[n]; } - return this; - }; - - /** - * Returns a string representation of this object. - * @method toString - * @return {String} a string representation of the instance. - **/ - p.toString = function() { - return "[Event (type="+this.type+")]"; - }; - - createjs.Event = Event; +this.createjs = this.createjs||{}; + +(function() { + "use strict"; + +// constructor: + /** + * Contains properties and methods shared by all events for use with + * {{#crossLink "EventDispatcher"}}{{/crossLink}}. + * + * Note that Event objects are often reused, so you should never + * rely on an event object's state outside of the call stack it was received in. + * @class Event + * @param {String} type The event type. + * @param {Boolean} bubbles Indicates whether the event will bubble through the display list. + * @param {Boolean} cancelable Indicates whether the default behaviour of this event can be cancelled. + * @constructor + **/ + function Event(type, bubbles, cancelable) { + + + // public properties: + /** + * The type of event. + * @property type + * @type String + **/ + this.type = type; + + /** + * The object that generated an event. + * @property target + * @type Object + * @default null + * @readonly + */ + this.target = null; + + /** + * The current target that a bubbling event is being dispatched from. For non-bubbling events, this will + * always be the same as target. For example, if childObj.parent = parentObj, and a bubbling event + * is generated from childObj, then a listener on parentObj would receive the event with + * target=childObj (the original target) and currentTarget=parentObj (where the listener was added). + * @property currentTarget + * @type Object + * @default null + * @readonly + */ + this.currentTarget = null; + + /** + * For bubbling events, this indicates the current event phase:
    + *
  1. capture phase: starting from the top parent to the target
  2. + *
  3. at target phase: currently being dispatched from the target
  4. + *
  5. bubbling phase: from the target to the top parent
  6. + *
+ * @property eventPhase + * @type Number + * @default 0 + * @readonly + */ + this.eventPhase = 0; + + /** + * Indicates whether the event will bubble through the display list. + * @property bubbles + * @type Boolean + * @default false + * @readonly + */ + this.bubbles = !!bubbles; + + /** + * Indicates whether the default behaviour of this event can be cancelled via + * {{#crossLink "Event/preventDefault"}}{{/crossLink}}. This is set via the Event constructor. + * @property cancelable + * @type Boolean + * @default false + * @readonly + */ + this.cancelable = !!cancelable; + + /** + * The epoch time at which this event was created. + * @property timeStamp + * @type Number + * @default 0 + * @readonly + */ + this.timeStamp = (new Date()).getTime(); + + /** + * Indicates if {{#crossLink "Event/preventDefault"}}{{/crossLink}} has been called + * on this event. + * @property defaultPrevented + * @type Boolean + * @default false + * @readonly + */ + this.defaultPrevented = false; + + /** + * Indicates if {{#crossLink "Event/stopPropagation"}}{{/crossLink}} or + * {{#crossLink "Event/stopImmediatePropagation"}}{{/crossLink}} has been called on this event. + * @property propagationStopped + * @type Boolean + * @default false + * @readonly + */ + this.propagationStopped = false; + + /** + * Indicates if {{#crossLink "Event/stopImmediatePropagation"}}{{/crossLink}} has been called + * on this event. + * @property immediatePropagationStopped + * @type Boolean + * @default false + * @readonly + */ + this.immediatePropagationStopped = false; + + /** + * Indicates if {{#crossLink "Event/remove"}}{{/crossLink}} has been called on this event. + * @property removed + * @type Boolean + * @default false + * @readonly + */ + this.removed = false; + } + var p = Event.prototype; + + /** + * REMOVED. Removed in favor of using `MySuperClass_constructor`. + * See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}} + * for details. + * + * There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance. + * + * @method initialize + * @protected + * @deprecated + */ + // p.initialize = function() {}; // searchable for devs wondering where it is. + + +// public methods: + /** + * Sets {{#crossLink "Event/defaultPrevented"}}{{/crossLink}} to true. + * Mirrors the DOM event standard. + * @method preventDefault + **/ + p.preventDefault = function() { + this.defaultPrevented = this.cancelable&&true; + }; + + /** + * Sets {{#crossLink "Event/propagationStopped"}}{{/crossLink}} to true. + * Mirrors the DOM event standard. + * @method stopPropagation + **/ + p.stopPropagation = function() { + this.propagationStopped = true; + }; + + /** + * Sets {{#crossLink "Event/propagationStopped"}}{{/crossLink}} and + * {{#crossLink "Event/immediatePropagationStopped"}}{{/crossLink}} to true. + * Mirrors the DOM event standard. + * @method stopImmediatePropagation + **/ + p.stopImmediatePropagation = function() { + this.immediatePropagationStopped = this.propagationStopped = true; + }; + + /** + * Causes the active listener to be removed via removeEventListener(); + * + * myBtn.addEventListener("click", function(evt) { + * // do stuff... + * evt.remove(); // removes this listener. + * }); + * + * @method remove + **/ + p.remove = function() { + this.removed = true; + }; + + /** + * Returns a clone of the Event instance. + * @method clone + * @return {Event} a clone of the Event instance. + **/ + p.clone = function() { + return new Event(this.type, this.bubbles, this.cancelable); + }; + + /** + * Provides a chainable shortcut method for setting a number of properties on the instance. + * + * @method set + * @param {Object} props A generic object containing properties to copy to the instance. + * @return {Event} Returns the instance the method is called on (useful for chaining calls.) + * @chainable + */ + p.set = function(props) { + for (var n in props) { this[n] = props[n]; } + return this; + }; + + /** + * Returns a string representation of this object. + * @method toString + * @return {String} a string representation of the instance. + **/ + p.toString = function() { + return "[Event (type="+this.type+")]"; + }; + + createjs.Event = Event; }()); //############################################################################## // ErrorEvent.js //############################################################################## -this.createjs = this.createjs||{}; - -(function() { - "use strict"; - - /** - * A general error {{#crossLink "Event"}}{{/crossLink}}, that describes an error that occurred, as well as any details. - * @class ErrorEvent - * @param {String} [title] The error title - * @param {String} [message] The error description - * @param {Object} [data] Additional error data - * @constructor - */ - function ErrorEvent(title, message, data) { - this.Event_constructor("error"); - - /** - * The short error title, which indicates the type of error that occurred. - * @property title - * @type String - */ - this.title = title; - - /** - * The verbose error message, containing details about the error. - * @property message - * @type String - */ - this.message = message; - - /** - * Additional data attached to an error. - * @property data - * @type {Object} - */ - this.data = data; - } - - var p = createjs.extend(ErrorEvent, createjs.Event); - - p.clone = function() { - return new createjs.ErrorEvent(this.title, this.message, this.data); - }; - - createjs.ErrorEvent = createjs.promote(ErrorEvent, "Event"); - +this.createjs = this.createjs||{}; + +(function() { + "use strict"; + + /** + * A general error {{#crossLink "Event"}}{{/crossLink}}, that describes an error that occurred, as well as any details. + * @class ErrorEvent + * @param {String} [title] The error title + * @param {String} [message] The error description + * @param {Object} [data] Additional error data + * @constructor + */ + function ErrorEvent(title, message, data) { + this.Event_constructor("error"); + + /** + * The short error title, which indicates the type of error that occurred. + * @property title + * @type String + */ + this.title = title; + + /** + * The verbose error message, containing details about the error. + * @property message + * @type String + */ + this.message = message; + + /** + * Additional data attached to an error. + * @property data + * @type {Object} + */ + this.data = data; + } + + var p = createjs.extend(ErrorEvent, createjs.Event); + + p.clone = function() { + return new createjs.ErrorEvent(this.title, this.message, this.data); + }; + + createjs.ErrorEvent = createjs.promote(ErrorEvent, "Event"); + }()); //############################################################################## // ProgressEvent.js //############################################################################## -this.createjs = this.createjs || {}; - -(function (scope) { - "use strict"; - - // constructor - /** - * A CreateJS {{#crossLink "Event"}}{{/crossLink}} that is dispatched when progress changes. - * @class ProgressEvent - * @param {Number} loaded The amount that has been loaded. This can be any number relative to the total. - * @param {Number} [total=1] The total amount that will load. This will default to 1, so if the `loaded` value is - * a percentage (between 0 and 1), it can be omitted. - * @todo Consider having this event be a "fileprogress" event as well - * @constructor - */ - function ProgressEvent(loaded, total) { - this.Event_constructor("progress"); - - /** - * The amount that has been loaded (out of a total amount) - * @property loaded - * @type {Number} - */ - this.loaded = loaded; - - /** - * The total "size" of the load. - * @property total - * @type {Number} - * @default 1 - */ - this.total = (total == null) ? 1 : total; - - /** - * The percentage (out of 1) that the load has been completed. This is calculated using `loaded/total`. - * @property progress - * @type {Number} - * @default 0 - */ - this.progress = (total == 0) ? 0 : this.loaded / this.total; - }; - - var p = createjs.extend(ProgressEvent, createjs.Event); - - /** - * Returns a clone of the ProgressEvent instance. - * @method clone - * @return {ProgressEvent} a clone of the Event instance. - **/ - p.clone = function() { - return new createjs.ProgressEvent(this.loaded, this.total); - }; - - createjs.ProgressEvent = createjs.promote(ProgressEvent, "Event"); - +this.createjs = this.createjs || {}; + +(function (scope) { + "use strict"; + + // constructor + /** + * A CreateJS {{#crossLink "Event"}}{{/crossLink}} that is dispatched when progress changes. + * @class ProgressEvent + * @param {Number} loaded The amount that has been loaded. This can be any number relative to the total. + * @param {Number} [total=1] The total amount that will load. This will default to 1, so if the `loaded` value is + * a percentage (between 0 and 1), it can be omitted. + * @todo Consider having this event be a "fileprogress" event as well + * @constructor + */ + function ProgressEvent(loaded, total) { + this.Event_constructor("progress"); + + /** + * The amount that has been loaded (out of a total amount) + * @property loaded + * @type {Number} + */ + this.loaded = loaded; + + /** + * The total "size" of the load. + * @property total + * @type {Number} + * @default 1 + */ + this.total = (total == null) ? 1 : total; + + /** + * The percentage (out of 1) that the load has been completed. This is calculated using `loaded/total`. + * @property progress + * @type {Number} + * @default 0 + */ + this.progress = (total == 0) ? 0 : this.loaded / this.total; + }; + + var p = createjs.extend(ProgressEvent, createjs.Event); + + /** + * Returns a clone of the ProgressEvent instance. + * @method clone + * @return {ProgressEvent} a clone of the Event instance. + **/ + p.clone = function() { + return new createjs.ProgressEvent(this.loaded, this.total); + }; + + createjs.ProgressEvent = createjs.promote(ProgressEvent, "Event"); + }(window)); //############################################################################## // LoadItem.js //############################################################################## -this.createjs = this.createjs || {}; - -(function () { - "use strict"; - - /** - * All loaders accept an item containing the properties defined in this class. If a raw object is passed instead, - * it will not be affected, but it must contain at least a {{#crossLink "src:property"}}{{/crossLink}} property. A - * string path or HTML tag is also acceptable, but it will be automatically converted to a LoadItem using the - * {{#crossLink "create"}}{{/crossLink}} method by {{#crossLink "AbstractLoader"}}{{/crossLink}} - * @class LoadItem - * @constructor - * @since 0.6.0 - */ - function LoadItem() { - /** - * The source of the file that is being loaded. This property is required. The source can either be a - * string (recommended), or an HTML tag. - * This can also be an object, but in that case it has to include a type and be handled by a plugin. - * @property src - * @type {String} - * @default null - */ - this.src = null; - - /** - * The type file that is being loaded. The type of the file is usually inferred by the extension, but can also - * be set manually. This is helpful in cases where a file does not have an extension. - * @property type - * @type {String} - * @default null - */ - this.type = null; - - /** - * A string identifier which can be used to reference the loaded object. If none is provided, this will be - * automatically set to the {{#crossLink "src:property"}}{{/crossLink}}. - * @property id - * @type {String} - * @default null - */ - this.id = null; - - /** - * Determines if a manifest will maintain the order of this item, in relation to other items in the manifest - * that have also set the `maintainOrder` property to `true`. This only applies when the max connections has - * been set above 1 (using {{#crossLink "LoadQueue/setMaxConnections"}}{{/crossLink}}). Everything with this - * property set to `false` will finish as it is loaded. Ordered items are combined with script tags loading in - * order when {{#crossLink "LoadQueue/maintainScriptOrder:property"}}{{/crossLink}} is set to `true`. - * @property maintainOrder - * @type {Boolean} - * @default false - */ - this.maintainOrder = false; - - /** - * A callback used by JSONP requests that defines what global method to call when the JSONP content is loaded. - * @property callback - * @type {String} - * @default null - */ - this.callback = null; - - /** - * An arbitrary data object, which is included with the loaded object. - * @property data - * @type {Object} - * @default null - */ - this.data = null; - - /** - * The request method used for HTTP calls. Both {{#crossLink "AbstractLoader/GET:property"}}{{/crossLink}} or - * {{#crossLink "AbstractLoader/POST:property"}}{{/crossLink}} request types are supported, and are defined as - * constants on {{#crossLink "AbstractLoader"}}{{/crossLink}}. - * @property method - * @type {String} - * @default get - */ - this.method = createjs.LoadItem.GET; - - /** - * An object hash of name/value pairs to send to the server. - * @property values - * @type {Object} - * @default null - */ - this.values = null; - - /** - * An object hash of headers to attach to an XHR request. PreloadJS will automatically attach some default - * headers when required, including "Origin", "Content-Type", and "X-Requested-With". You may override the - * default headers by including them in your headers object. - * @property headers - * @type {Object} - * @default null - */ - this.headers = null; - - /** - * Enable credentials for XHR requests. - * @property withCredentials - * @type {Boolean} - * @default false - */ - this.withCredentials = false; - - /** - * Set the mime type of XHR-based requests. This is automatically set to "text/plain; charset=utf-8" for text - * based files (json, xml, text, css, js). - * @property mimeType - * @type {String} - * @default null - */ - this.mimeType = null; - - /** - * Sets the crossOrigin attribute for CORS-enabled images loading cross-domain. - * @property crossOrigin - * @type {boolean} - * @default Anonymous - */ - this.crossOrigin = null; - - /** - * The duration in milliseconds to wait before a request times out. This only applies to tag-based and and XHR - * (level one) loading, as XHR (level 2) provides its own timeout event. - * @property loadTimeout - * @type {Number} - * @default 8000 (8 seconds) - */ - this.loadTimeout = s.LOAD_TIMEOUT_DEFAULT; - }; - - var p = LoadItem.prototype = {}; - var s = LoadItem; - - /** - * Default duration in milliseconds to wait before a request times out. This only applies to tag-based and and XHR - * (level one) loading, as XHR (level 2) provides its own timeout event. - * @property LOAD_TIMEOUT_DEFAULT - * @type {number} - * @static - */ - s.LOAD_TIMEOUT_DEFAULT = 8000; - - /** - * Create a LoadItem. - *
    - *
  • String-based items are converted to a LoadItem with a populated {{#crossLink "src:property"}}{{/crossLink}}.
  • - *
  • LoadItem instances are returned as-is
  • - *
  • Objects are returned with any needed properties added
  • - *
- * @method create - * @param {LoadItem|String|Object} value The load item value - * @returns {LoadItem|Object} - * @static - */ - s.create = function (value) { - if (typeof value == "string") { - var item = new LoadItem(); - item.src = value; - return item; - } else if (value instanceof s) { - return value; - } else if (value instanceof Object && value.src) { - if (value.loadTimeout == null) { - value.loadTimeout = s.LOAD_TIMEOUT_DEFAULT; - } - return value; - } else { - throw new Error("Type not recognized."); - } - }; - - /** - * Provides a chainable shortcut method for setting a number of properties on the instance. - * - *

Example

- * - * var loadItem = new createjs.LoadItem().set({src:"image.png", maintainOrder:true}); - * - * @method set - * @param {Object} props A generic object containing properties to copy to the LoadItem instance. - * @return {LoadItem} Returns the instance the method is called on (useful for chaining calls.) - */ - p.set = function(props) { - for (var n in props) { this[n] = props[n]; } - return this; - }; - - createjs.LoadItem = s; - +this.createjs = this.createjs || {}; + +(function () { + "use strict"; + + /** + * All loaders accept an item containing the properties defined in this class. If a raw object is passed instead, + * it will not be affected, but it must contain at least a {{#crossLink "src:property"}}{{/crossLink}} property. A + * string path or HTML tag is also acceptable, but it will be automatically converted to a LoadItem using the + * {{#crossLink "create"}}{{/crossLink}} method by {{#crossLink "AbstractLoader"}}{{/crossLink}} + * @class LoadItem + * @constructor + * @since 0.6.0 + */ + function LoadItem() { + /** + * The source of the file that is being loaded. This property is required. The source can either be a + * string (recommended), or an HTML tag. + * This can also be an object, but in that case it has to include a type and be handled by a plugin. + * @property src + * @type {String} + * @default null + */ + this.src = null; + + /** + * The type file that is being loaded. The type of the file is usually inferred by the extension, but can also + * be set manually. This is helpful in cases where a file does not have an extension. + * @property type + * @type {String} + * @default null + */ + this.type = null; + + /** + * A string identifier which can be used to reference the loaded object. If none is provided, this will be + * automatically set to the {{#crossLink "src:property"}}{{/crossLink}}. + * @property id + * @type {String} + * @default null + */ + this.id = null; + + /** + * Determines if a manifest will maintain the order of this item, in relation to other items in the manifest + * that have also set the `maintainOrder` property to `true`. This only applies when the max connections has + * been set above 1 (using {{#crossLink "LoadQueue/setMaxConnections"}}{{/crossLink}}). Everything with this + * property set to `false` will finish as it is loaded. Ordered items are combined with script tags loading in + * order when {{#crossLink "LoadQueue/maintainScriptOrder:property"}}{{/crossLink}} is set to `true`. + * @property maintainOrder + * @type {Boolean} + * @default false + */ + this.maintainOrder = false; + + /** + * A callback used by JSONP requests that defines what global method to call when the JSONP content is loaded. + * @property callback + * @type {String} + * @default null + */ + this.callback = null; + + /** + * An arbitrary data object, which is included with the loaded object. + * @property data + * @type {Object} + * @default null + */ + this.data = null; + + /** + * The request method used for HTTP calls. Both {{#crossLink "AbstractLoader/GET:property"}}{{/crossLink}} or + * {{#crossLink "AbstractLoader/POST:property"}}{{/crossLink}} request types are supported, and are defined as + * constants on {{#crossLink "AbstractLoader"}}{{/crossLink}}. + * @property method + * @type {String} + * @default get + */ + this.method = createjs.LoadItem.GET; + + /** + * An object hash of name/value pairs to send to the server. + * @property values + * @type {Object} + * @default null + */ + this.values = null; + + /** + * An object hash of headers to attach to an XHR request. PreloadJS will automatically attach some default + * headers when required, including "Origin", "Content-Type", and "X-Requested-With". You may override the + * default headers by including them in your headers object. + * @property headers + * @type {Object} + * @default null + */ + this.headers = null; + + /** + * Enable credentials for XHR requests. + * @property withCredentials + * @type {Boolean} + * @default false + */ + this.withCredentials = false; + + /** + * Set the mime type of XHR-based requests. This is automatically set to "text/plain; charset=utf-8" for text + * based files (json, xml, text, css, js). + * @property mimeType + * @type {String} + * @default null + */ + this.mimeType = null; + + /** + * Sets the crossOrigin attribute for CORS-enabled images loading cross-domain. + * @property crossOrigin + * @type {boolean} + * @default Anonymous + */ + this.crossOrigin = null; + + /** + * The duration in milliseconds to wait before a request times out. This only applies to tag-based and and XHR + * (level one) loading, as XHR (level 2) provides its own timeout event. + * @property loadTimeout + * @type {Number} + * @default 8000 (8 seconds) + */ + this.loadTimeout = s.LOAD_TIMEOUT_DEFAULT; + }; + + var p = LoadItem.prototype = {}; + var s = LoadItem; + + /** + * Default duration in milliseconds to wait before a request times out. This only applies to tag-based and and XHR + * (level one) loading, as XHR (level 2) provides its own timeout event. + * @property LOAD_TIMEOUT_DEFAULT + * @type {number} + * @static + */ + s.LOAD_TIMEOUT_DEFAULT = 8000; + + /** + * Create a LoadItem. + *
    + *
  • String-based items are converted to a LoadItem with a populated {{#crossLink "src:property"}}{{/crossLink}}.
  • + *
  • LoadItem instances are returned as-is
  • + *
  • Objects are returned with any needed properties added
  • + *
+ * @method create + * @param {LoadItem|String|Object} value The load item value + * @returns {LoadItem|Object} + * @static + */ + s.create = function (value) { + if (typeof value == "string") { + var item = new LoadItem(); + item.src = value; + return item; + } else if (value instanceof s) { + return value; + } else if (value instanceof Object && value.src) { + if (value.loadTimeout == null) { + value.loadTimeout = s.LOAD_TIMEOUT_DEFAULT; + } + return value; + } else { + throw new Error("Type not recognized."); + } + }; + + /** + * Provides a chainable shortcut method for setting a number of properties on the instance. + * + *

Example

+ * + * var loadItem = new createjs.LoadItem().set({src:"image.png", maintainOrder:true}); + * + * @method set + * @param {Object} props A generic object containing properties to copy to the LoadItem instance. + * @return {LoadItem} Returns the instance the method is called on (useful for chaining calls.) + */ + p.set = function(props) { + for (var n in props) { this[n] = props[n]; } + return this; + }; + + createjs.LoadItem = s; + }()); //############################################################################## // RequestUtils.js //############################################################################## -(function () { - - /** - * Utilities that assist with parsing load items, and determining file types, etc. - * @class RequestUtils - */ - var s = {}; - - /** - * The Regular Expression used to test file URLS for an absolute path. - * @property ABSOLUTE_PATH - * @type {RegExp} - * @static - */ - s.ABSOLUTE_PATT = /^(?:\w+:)?\/{2}/i; - - /** - * The Regular Expression used to test file URLS for a relative path. - * @property RELATIVE_PATH - * @type {RegExp} - * @static - */ - s.RELATIVE_PATT = (/^[./]*?\//i); - - /** - * The Regular Expression used to test file URLS for an extension. Note that URIs must already have the query string - * removed. - * @property EXTENSION_PATT - * @type {RegExp} - * @static - */ - s.EXTENSION_PATT = /\/?[^/]+\.(\w{1,5})$/i; - - /** - * Parse a file path to determine the information we need to work with it. Currently, PreloadJS needs to know: - *
    - *
  • If the path is absolute. Absolute paths start with a protocol (such as `http://`, `file://`, or - * `//networkPath`)
  • - *
  • If the path is relative. Relative paths start with `../` or `/path` (or similar)
  • - *
  • The file extension. This is determined by the filename with an extension. Query strings are dropped, and - * the file path is expected to follow the format `name.ext`.
  • - *
- * @method parseURI - * @param {String} path - * @returns {Object} An Object with an `absolute` and `relative` Boolean values, as well as an optional 'extension` - * property, which is the lowercase extension. - * @static - */ - s.parseURI = function (path) { - var info = {absolute: false, relative: false}; - if (path == null) { return info; } - - // Drop the query string - var queryIndex = path.indexOf("?"); - if (queryIndex > -1) { - path = path.substr(0, queryIndex); - } - - // Absolute - var match; - if (s.ABSOLUTE_PATT.test(path)) { - info.absolute = true; - - // Relative - } else if (s.RELATIVE_PATT.test(path)) { - info.relative = true; - } - - // Extension - if (match = path.match(s.EXTENSION_PATT)) { - info.extension = match[1].toLowerCase(); - } - return info; - }; - - /** - * Formats an object into a query string for either a POST or GET request. - * @method formatQueryString - * @param {Object} data The data to convert to a query string. - * @param {Array} [query] Existing name/value pairs to append on to this query. - * @static - */ - s.formatQueryString = function (data, query) { - if (data == null) { - throw new Error('You must specify data.'); - } - var params = []; - for (var n in data) { - params.push(n + '=' + escape(data[n])); - } - if (query) { - params = params.concat(query); - } - return params.join('&'); - }; - - /** - * A utility method that builds a file path using a source and a data object, and formats it into a new path. - * @method buildPath - * @param {String} src The source path to add values to. - * @param {Object} [data] Object used to append values to this request as a query string. Existing parameters on the - * path will be preserved. - * @returns {string} A formatted string that contains the path and the supplied parameters. - * @static - */ - s.buildPath = function (src, data) { - if (data == null) { - return src; - } - - var query = []; - var idx = src.indexOf('?'); - - if (idx != -1) { - var q = src.slice(idx + 1); - query = query.concat(q.split('&')); - } - - if (idx != -1) { - return src.slice(0, idx) + '?' + this._formatQueryString(data, query); - } else { - return src + '?' + this._formatQueryString(data, query); - } - }; - - /** - * @method isCrossDomain - * @param {LoadItem|Object} item A load item with a `src` property. - * @return {Boolean} If the load item is loading from a different domain than the current location. - * @static - */ - s.isCrossDomain = function (item) { - var target = document.createElement("a"); - target.href = item.src; - - var host = document.createElement("a"); - host.href = location.href; - - var crossdomain = (target.hostname != "") && - (target.port != host.port || - target.protocol != host.protocol || - target.hostname != host.hostname); - return crossdomain; - }; - - /** - * @method isLocal - * @param {LoadItem|Object} item A load item with a `src` property - * @return {Boolean} If the load item is loading from the "file:" protocol. Assume that the host must be local as - * well. - * @static - */ - s.isLocal = function (item) { - var target = document.createElement("a"); - target.href = item.src; - return target.hostname == "" && target.protocol == "file:"; - }; - - /** - * Determine if a specific type should be loaded as a binary file. Currently, only images and items marked - * specifically as "binary" are loaded as binary. Note that audio is not a binary type, as we can not play - * back using an audio tag if it is loaded as binary. Plugins can change the item type to binary to ensure they get - * a binary result to work with. Binary files are loaded using XHR2. Types are defined as static constants on - * {{#crossLink "AbstractLoader"}}{{/crossLink}}. - * @method isBinary - * @param {String} type The item type. - * @return {Boolean} If the specified type is binary. - * @static - */ - s.isBinary = function (type) { - switch (type) { - case createjs.AbstractLoader.IMAGE: - case createjs.AbstractLoader.BINARY: - return true; - default: - return false; - } - }; - - /** - * Check if item is a valid HTMLImageElement - * @method isImageTag - * @param {Object} item - * @returns {Boolean} - * @static - */ - s.isImageTag = function(item) { - return item instanceof HTMLImageElement; - }; - - /** - * Check if item is a valid HTMLAudioElement - * @method isAudioTag - * @param {Object} item - * @returns {Boolean} - * @static - */ - s.isAudioTag = function(item) { - if (window.HTMLAudioElement) { - return item instanceof HTMLAudioElement; - } else { - return false; - } - }; - - /** - * Check if item is a valid HTMLVideoElement - * @method isVideoTag - * @param {Object} item - * @returns {Boolean} - * @static - */ - s.isVideoTag = function(item) { - if (window.HTMLVideoElement) { - return item instanceof HTMLVideoElement; - } else { - return false; - } - }; - - /** - * Determine if a specific type is a text-based asset, and should be loaded as UTF-8. - * @method isText - * @param {String} type The item type. - * @return {Boolean} If the specified type is text. - * @static - */ - s.isText = function (type) { - switch (type) { - case createjs.AbstractLoader.TEXT: - case createjs.AbstractLoader.JSON: - case createjs.AbstractLoader.MANIFEST: - case createjs.AbstractLoader.XML: - case createjs.AbstractLoader.CSS: - case createjs.AbstractLoader.SVG: - case createjs.AbstractLoader.JAVASCRIPT: - case createjs.AbstractLoader.SPRITESHEET: - return true; - default: - return false; - } - }; - - /** - * Determine the type of the object using common extensions. Note that the type can be passed in with the load item - * if it is an unusual extension. - * @method getTypeByExtension - * @param {String} extension The file extension to use to determine the load type. - * @return {String} The determined load type (for example, AbstractLoader.IMAGE). Will return `null` if - * the type can not be determined by the extension. - * @static - */ - s.getTypeByExtension = function (extension) { - if (extension == null) { - return createjs.AbstractLoader.TEXT; - } - - switch (extension.toLowerCase()) { - case "jpeg": - case "jpg": - case "gif": - case "png": - case "webp": - case "bmp": - return createjs.AbstractLoader.IMAGE; - case "ogg": - case "mp3": - case "webm": - return createjs.AbstractLoader.SOUND; - case "mp4": - case "webm": - case "ts": - return createjs.AbstractLoader.VIDEO; - case "json": - return createjs.AbstractLoader.JSON; - case "xml": - return createjs.AbstractLoader.XML; - case "css": - return createjs.AbstractLoader.CSS; - case "js": - return createjs.AbstractLoader.JAVASCRIPT; - case 'svg': - return createjs.AbstractLoader.SVG; - default: - return createjs.AbstractLoader.TEXT; - } - }; - - createjs.RequestUtils = s; - +(function () { + + /** + * Utilities that assist with parsing load items, and determining file types, etc. + * @class RequestUtils + */ + var s = {}; + + /** + * The Regular Expression used to test file URLS for an absolute path. + * @property ABSOLUTE_PATH + * @type {RegExp} + * @static + */ + s.ABSOLUTE_PATT = /^(?:\w+:)?\/{2}/i; + + /** + * The Regular Expression used to test file URLS for a relative path. + * @property RELATIVE_PATH + * @type {RegExp} + * @static + */ + s.RELATIVE_PATT = (/^[./]*?\//i); + + /** + * The Regular Expression used to test file URLS for an extension. Note that URIs must already have the query string + * removed. + * @property EXTENSION_PATT + * @type {RegExp} + * @static + */ + s.EXTENSION_PATT = /\/?[^/]+\.(\w{1,5})$/i; + + /** + * Parse a file path to determine the information we need to work with it. Currently, PreloadJS needs to know: + *
    + *
  • If the path is absolute. Absolute paths start with a protocol (such as `http://`, `file://`, or + * `//networkPath`)
  • + *
  • If the path is relative. Relative paths start with `../` or `/path` (or similar)
  • + *
  • The file extension. This is determined by the filename with an extension. Query strings are dropped, and + * the file path is expected to follow the format `name.ext`.
  • + *
+ * @method parseURI + * @param {String} path + * @returns {Object} An Object with an `absolute` and `relative` Boolean values, as well as an optional 'extension` + * property, which is the lowercase extension. + * @static + */ + s.parseURI = function (path) { + var info = {absolute: false, relative: false}; + if (path == null) { return info; } + + // Drop the query string + var queryIndex = path.indexOf("?"); + if (queryIndex > -1) { + path = path.substr(0, queryIndex); + } + + // Absolute + var match; + if (s.ABSOLUTE_PATT.test(path)) { + info.absolute = true; + + // Relative + } else if (s.RELATIVE_PATT.test(path)) { + info.relative = true; + } + + // Extension + if (match = path.match(s.EXTENSION_PATT)) { + info.extension = match[1].toLowerCase(); + } + return info; + }; + + /** + * Formats an object into a query string for either a POST or GET request. + * @method formatQueryString + * @param {Object} data The data to convert to a query string. + * @param {Array} [query] Existing name/value pairs to append on to this query. + * @static + */ + s.formatQueryString = function (data, query) { + if (data == null) { + throw new Error('You must specify data.'); + } + var params = []; + for (var n in data) { + params.push(n + '=' + escape(data[n])); + } + if (query) { + params = params.concat(query); + } + return params.join('&'); + }; + + /** + * A utility method that builds a file path using a source and a data object, and formats it into a new path. + * @method buildPath + * @param {String} src The source path to add values to. + * @param {Object} [data] Object used to append values to this request as a query string. Existing parameters on the + * path will be preserved. + * @returns {string} A formatted string that contains the path and the supplied parameters. + * @static + */ + s.buildPath = function (src, data) { + if (data == null) { + return src; + } + + var query = []; + var idx = src.indexOf('?'); + + if (idx != -1) { + var q = src.slice(idx + 1); + query = query.concat(q.split('&')); + } + + if (idx != -1) { + return src.slice(0, idx) + '?' + this._formatQueryString(data, query); + } else { + return src + '?' + this._formatQueryString(data, query); + } + }; + + /** + * @method isCrossDomain + * @param {LoadItem|Object} item A load item with a `src` property. + * @return {Boolean} If the load item is loading from a different domain than the current location. + * @static + */ + s.isCrossDomain = function (item) { + var target = document.createElement("a"); + target.href = item.src; + + var host = document.createElement("a"); + host.href = location.href; + + var crossdomain = (target.hostname != "") && + (target.port != host.port || + target.protocol != host.protocol || + target.hostname != host.hostname); + return crossdomain; + }; + + /** + * @method isLocal + * @param {LoadItem|Object} item A load item with a `src` property + * @return {Boolean} If the load item is loading from the "file:" protocol. Assume that the host must be local as + * well. + * @static + */ + s.isLocal = function (item) { + var target = document.createElement("a"); + target.href = item.src; + return target.hostname == "" && target.protocol == "file:"; + }; + + /** + * Determine if a specific type should be loaded as a binary file. Currently, only images and items marked + * specifically as "binary" are loaded as binary. Note that audio is not a binary type, as we can not play + * back using an audio tag if it is loaded as binary. Plugins can change the item type to binary to ensure they get + * a binary result to work with. Binary files are loaded using XHR2. Types are defined as static constants on + * {{#crossLink "AbstractLoader"}}{{/crossLink}}. + * @method isBinary + * @param {String} type The item type. + * @return {Boolean} If the specified type is binary. + * @static + */ + s.isBinary = function (type) { + switch (type) { + case createjs.AbstractLoader.IMAGE: + case createjs.AbstractLoader.BINARY: + return true; + default: + return false; + } + }; + + /** + * Check if item is a valid HTMLImageElement + * @method isImageTag + * @param {Object} item + * @returns {Boolean} + * @static + */ + s.isImageTag = function(item) { + return item instanceof HTMLImageElement; + }; + + /** + * Check if item is a valid HTMLAudioElement + * @method isAudioTag + * @param {Object} item + * @returns {Boolean} + * @static + */ + s.isAudioTag = function(item) { + if (window.HTMLAudioElement) { + return item instanceof HTMLAudioElement; + } else { + return false; + } + }; + + /** + * Check if item is a valid HTMLVideoElement + * @method isVideoTag + * @param {Object} item + * @returns {Boolean} + * @static + */ + s.isVideoTag = function(item) { + if (window.HTMLVideoElement) { + return item instanceof HTMLVideoElement; + } else { + return false; + } + }; + + /** + * Determine if a specific type is a text-based asset, and should be loaded as UTF-8. + * @method isText + * @param {String} type The item type. + * @return {Boolean} If the specified type is text. + * @static + */ + s.isText = function (type) { + switch (type) { + case createjs.AbstractLoader.TEXT: + case createjs.AbstractLoader.JSON: + case createjs.AbstractLoader.MANIFEST: + case createjs.AbstractLoader.XML: + case createjs.AbstractLoader.CSS: + case createjs.AbstractLoader.SVG: + case createjs.AbstractLoader.JAVASCRIPT: + case createjs.AbstractLoader.SPRITESHEET: + return true; + default: + return false; + } + }; + + /** + * Determine the type of the object using common extensions. Note that the type can be passed in with the load item + * if it is an unusual extension. + * @method getTypeByExtension + * @param {String} extension The file extension to use to determine the load type. + * @return {String} The determined load type (for example, AbstractLoader.IMAGE). Will return `null` if + * the type can not be determined by the extension. + * @static + */ + s.getTypeByExtension = function (extension) { + if (extension == null) { + return createjs.AbstractLoader.TEXT; + } + + switch (extension.toLowerCase()) { + case "jpeg": + case "jpg": + case "gif": + case "png": + case "webp": + case "bmp": + return createjs.AbstractLoader.IMAGE; + case "ogg": + case "mp3": + case "webm": + return createjs.AbstractLoader.SOUND; + case "mp4": + case "webm": + case "ts": + return createjs.AbstractLoader.VIDEO; + case "json": + return createjs.AbstractLoader.JSON; + case "xml": + return createjs.AbstractLoader.XML; + case "css": + return createjs.AbstractLoader.CSS; + case "js": + return createjs.AbstractLoader.JAVASCRIPT; + case 'svg': + return createjs.AbstractLoader.SVG; + default: + return createjs.AbstractLoader.TEXT; + } + }; + + createjs.RequestUtils = s; + }()); //############################################################################## // AbstractLoader.js //############################################################################## -this.createjs = this.createjs || {}; - -(function () { - "use strict"; - -// constructor - /** - * The base loader, which defines all the generic methods, properties, and events. All loaders extend this class, - * including the {{#crossLink "LoadQueue"}}{{/crossLink}}. - * @class AbstractLoader - * @param {LoadItem|object|string} loadItem The item to be loaded. - * @param {Boolean} [preferXHR] Determines if the LoadItem should try and load using XHR, or take a - * tag-based approach, which can be better in cross-domain situations. Not all loaders can load using one or the - * other, so this is a suggested directive. - * @param {String} [type] The type of loader. Loader types are defined as constants on the AbstractLoader class, - * such as {{#crossLink "IMAGE:property"}}{{/crossLink}}, {{#crossLink "CSS:property"}}{{/crossLink}}, etc. - * @extends EventDispatcher - */ - function AbstractLoader(loadItem, preferXHR, type) { - this.EventDispatcher_constructor(); - - // public properties - /** - * If the loader has completed loading. This provides a quick check, but also ensures that the different approaches - * used for loading do not pile up resulting in more than one `complete` {{#crossLink "Event"}}{{/crossLink}}. - * @property loaded - * @type {Boolean} - * @default false - */ - this.loaded = false; - - /** - * Determine if the loader was canceled. Canceled loads will not fire complete events. Note that this property - * is readonly, so {{#crossLink "LoadQueue"}}{{/crossLink}} queues should be closed using {{#crossLink "LoadQueue/close"}}{{/crossLink}} - * instead. - * @property canceled - * @type {Boolean} - * @default false - */ - this.canceled = false; - - /** - * The current load progress (percentage) for this item. This will be a number between 0 and 1. - * - *

Example

- * - * var queue = new createjs.LoadQueue(); - * queue.loadFile("largeImage.png"); - * queue.on("progress", function() { - * console.log("Progress:", queue.progress, event.progress); - * }); - * - * @property progress - * @type {Number} - * @default 0 - */ - this.progress = 0; - - /** - * The type of item this loader will load. See {{#crossLink "AbstractLoader"}}{{/crossLink}} for a full list of - * supported types. - * @property type - * @type {String} - */ - this.type = type; - - /** - * A formatter function that converts the loaded raw result into the final result. For example, the JSONLoader - * converts a string of text into a JavaScript object. Not all loaders have a resultFormatter, and this property - * can be overridden to provide custom formatting. - * - * Optionally, a resultFormatter can return a callback function in cases where the formatting needs to be - * asynchronous, such as creating a new image. - * @property resultFormatter - * @type {Function} - * @default null - */ - this.resultFormatter = null; - - // protected properties - /** - * The {{#crossLink "LoadItem"}}{{/crossLink}} this loader represents. Note that this is null in a {{#crossLink "LoadQueue"}}{{/crossLink}}, - * but will be available on loaders such as {{#crossLink "XMLLoader"}}{{/crossLink}} and {{#crossLink "ImageLoader"}}{{/crossLink}}. - * @property _item - * @type {LoadItem|Object} - * @private - */ - if (loadItem) { - this._item = createjs.LoadItem.create(loadItem); - } else { - this._item = null; - } - - /** - * Whether the loader will try and load content using XHR (true) or HTML tags (false). - * @property _preferXHR - * @type {Boolean} - * @private - */ - this._preferXHR = preferXHR; - - /** - * The loaded result after it is formatted by an optional {{#crossLink "resultFormatter"}}{{/crossLink}}. For - * items that are not formatted, this will be the same as the {{#crossLink "_rawResult:property"}}{{/crossLink}}. - * The result is accessed using the {{#crossLink "getResult"}}{{/crossLink}} method. - * @property _result - * @type {Object|String} - * @private - */ - this._result = null; - - /** - * The loaded result before it is formatted. The rawResult is accessed using the {{#crossLink "getResult"}}{{/crossLink}} - * method, and passing `true`. - * @property _rawResult - * @type {Object|String} - * @private - */ - this._rawResult = null; - - /** - * A list of items that loaders load behind the scenes. This does not include the main item the loader is - * responsible for loading. Examples of loaders that have sub-items include the {{#crossLink "SpriteSheetLoader"}}{{/crossLink}} and - * {{#crossLink "ManifestLoader"}}{{/crossLink}}. - * @property _loadItems - * @type {null} - * @protected - */ - this._loadedItems = null; - - /** - * The attribute the items loaded using tags use for the source. - * @type {string} - * @default null - * @private - */ - this._tagSrcAttribute = null; - - /** - * An HTML tag (or similar) that a loader may use to load HTML content, such as images, scripts, etc. - * @property _tag - * @type {Object} - * @private - */ - this._tag = null; - }; - - var p = createjs.extend(AbstractLoader, createjs.EventDispatcher); - var s = AbstractLoader; - - // TODO: deprecated - // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. - - - /** - * Defines a POST request, use for a method value when loading data. - * @property POST - * @type {string} - * @default post - * @static - */ - s.POST = "POST"; - - /** - * Defines a GET request, use for a method value when loading data. - * @property GET - * @type {string} - * @default get - * @static - */ - s.GET = "GET"; - - /** - * The preload type for generic binary types. Note that images are loaded as binary files when using XHR. - * @property BINARY - * @type {String} - * @default binary - * @static - * @since 0.6.0 - */ - s.BINARY = "binary"; - - /** - * The preload type for css files. CSS files are loaded using a <link> when loaded with XHR, or a - * <style> tag when loaded with tags. - * @property CSS - * @type {String} - * @default css - * @static - * @since 0.6.0 - */ - s.CSS = "css"; - - /** - * The preload type for image files, usually png, gif, or jpg/jpeg. Images are loaded into an <image> tag. - * @property IMAGE - * @type {String} - * @default image - * @static - * @since 0.6.0 - */ - s.IMAGE = "image"; - - /** - * The preload type for javascript files, usually with the "js" file extension. JavaScript files are loaded into a - * <script> tag. - * - * Since version 0.4.1+, due to how tag-loaded scripts work, all JavaScript files are automatically injected into - * the body of the document to maintain parity between XHR and tag-loaded scripts. In version 0.4.0 and earlier, - * only tag-loaded scripts are injected. - * @property JAVASCRIPT - * @type {String} - * @default javascript - * @static - * @since 0.6.0 - */ - s.JAVASCRIPT = "javascript"; - - /** - * The preload type for json files, usually with the "json" file extension. JSON data is loaded and parsed into a - * JavaScript object. Note that if a `callback` is present on the load item, the file will be loaded with JSONP, - * no matter what the {{#crossLink "LoadQueue/preferXHR:property"}}{{/crossLink}} property is set to, and the JSON - * must contain a matching wrapper function. - * @property JSON - * @type {String} - * @default json - * @static - * @since 0.6.0 - */ - s.JSON = "json"; - - /** - * The preload type for jsonp files, usually with the "json" file extension. JSON data is loaded and parsed into a - * JavaScript object. You are required to pass a callback parameter that matches the function wrapper in the JSON. - * Note that JSONP will always be used if there is a callback present, no matter what the {{#crossLink "LoadQueue/preferXHR:property"}}{{/crossLink}} - * property is set to. - * @property JSONP - * @type {String} - * @default jsonp - * @static - * @since 0.6.0 - */ - s.JSONP = "jsonp"; - - /** - * The preload type for json-based manifest files, usually with the "json" file extension. The JSON data is loaded - * and parsed into a JavaScript object. PreloadJS will then look for a "manifest" property in the JSON, which is an - * Array of files to load, following the same format as the {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}} - * method. If a "callback" is specified on the manifest object, then it will be loaded using JSONP instead, - * regardless of what the {{#crossLink "LoadQueue/preferXHR:property"}}{{/crossLink}} property is set to. - * @property MANIFEST - * @type {String} - * @default manifest - * @static - * @since 0.6.0 - */ - s.MANIFEST = "manifest"; - - /** - * The preload type for sound files, usually mp3, ogg, or wav. When loading via tags, audio is loaded into an - * <audio> tag. - * @property SOUND - * @type {String} - * @default sound - * @static - * @since 0.6.0 - */ - s.SOUND = "sound"; - - /** - * The preload type for video files, usually mp4, ts, or ogg. When loading via tags, video is loaded into an - * <video> tag. - * @property VIDEO - * @type {String} - * @default video - * @static - * @since 0.6.0 - */ - s.VIDEO = "video"; - - /** - * The preload type for SpriteSheet files. SpriteSheet files are JSON files that contain string image paths. - * @property SPRITESHEET - * @type {String} - * @default spritesheet - * @static - * @since 0.6.0 - */ - s.SPRITESHEET = "spritesheet"; - - /** - * The preload type for SVG files. - * @property SVG - * @type {String} - * @default svg - * @static - * @since 0.6.0 - */ - s.SVG = "svg"; - - /** - * The preload type for text files, which is also the default file type if the type can not be determined. Text is - * loaded as raw text. - * @property TEXT - * @type {String} - * @default text - * @static - * @since 0.6.0 - */ - s.TEXT = "text"; - - /** - * The preload type for xml files. XML is loaded into an XML document. - * @property XML - * @type {String} - * @default xml - * @static - * @since 0.6.0 - */ - s.XML = "xml"; - -// Events - /** - * The {{#crossLink "ProgressEvent"}}{{/crossLink}} that is fired when the overall progress changes. Prior to - * version 0.6.0, this was just a regular {{#crossLink "Event"}}{{/crossLink}}. - * @event progress - * @since 0.3.0 - */ - - /** - * The {{#crossLink "Event"}}{{/crossLink}} that is fired when a load starts. - * @event loadstart - * @param {Object} target The object that dispatched the event. - * @param {String} type The event type. - * @since 0.3.1 - */ - - /** - * The {{#crossLink "Event"}}{{/crossLink}} that is fired when the entire queue has been loaded. - * @event complete - * @param {Object} target The object that dispatched the event. - * @param {String} type The event type. - * @since 0.3.0 - */ - - /** - * The {{#crossLink "ErrorEvent"}}{{/crossLink}} that is fired when the loader encounters an error. If the error was - * encountered by a file, the event will contain the item that caused the error. Prior to version 0.6.0, this was - * just a regular {{#crossLink "Event"}}{{/crossLink}}. - * @event error - * @since 0.3.0 - */ - - /** - * The {{#crossLink "Event"}}{{/crossLink}} that is fired when the loader encounters an internal file load error. - * This enables loaders to maintain internal queues, and surface file load errors. - * @event fileerror - * @param {Object} target The object that dispatched the event. - * @param {String} type The even type ("fileerror") - * @param {LoadItem|object} The item that encountered the error - * @since 0.6.0 - */ - - /** - * The {{#crossLink "Event"}}{{/crossLink}} that is fired when a loader internally loads a file. This enables - * loaders such as {{#crossLink "ManifestLoader"}}{{/crossLink}} to maintain internal {{#crossLink "LoadQueue"}}{{/crossLink}}s - * and notify when they have loaded a file. The {{#crossLink "LoadQueue"}}{{/crossLink}} class dispatches a - * slightly different {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}} event. - * @event fileload - * @param {Object} target The object that dispatched the event. - * @param {String} type The event type ("fileload") - * @param {Object} item The file item which was specified in the {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} - * or {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}} call. If only a string path or tag was specified, the - * object will contain that value as a `src` property. - * @param {Object} result The HTML tag or parsed result of the loaded item. - * @param {Object} rawResult The unprocessed result, usually the raw text or binary data before it is converted - * to a usable object. - * @since 0.6.0 - */ - - /** - * The {{#crossLink "Event"}}{{/crossLink}} that is fired after the internal request is created, but before a load. - * This allows updates to the loader for specific loading needs, such as binary or XHR image loading. - * @event initialize - * @param {Object} target The object that dispatched the event. - * @param {String} type The event type ("initialize") - * @param {AbstractLoader} loader The loader that has been initialized. - */ - - - /** - * Get a reference to the manifest item that is loaded by this loader. In some cases this will be the value that was - * passed into {{#crossLink "LoadQueue"}}{{/crossLink}} using {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} or - * {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}}. However if only a String path was passed in, then it will - * be a {{#crossLink "LoadItem"}}{{/crossLink}}. - * @method getItem - * @return {Object} The manifest item that this loader is responsible for loading. - * @since 0.6.0 - */ - p.getItem = function () { - return this._item; - }; - - /** - * Get a reference to the content that was loaded by the loader (only available after the {{#crossLink "complete:event"}}{{/crossLink}} - * event is dispatched. - * @method getResult - * @param {Boolean} [raw=false] Determines if the returned result will be the formatted content, or the raw loaded - * data (if it exists). - * @return {Object} - * @since 0.6.0 - */ - p.getResult = function (raw) { - return raw ? this._rawResult : this._result; - }; - - /** - * Return the `tag` this object creates or uses for loading. - * @method getTag - * @return {Object} The tag instance - * @since 0.6.0 - */ - p.getTag = function () { - return this._tag; - }; - - /** - * Set the `tag` this item uses for loading. - * @method setTag - * @param {Object} tag The tag instance - * @since 0.6.0 - */ - p.setTag = function(tag) { - this._tag = tag; - }; - - /** - * Begin loading the item. This method is required when using a loader by itself. - * - *

Example

- * - * var queue = new createjs.LoadQueue(); - * queue.on("complete", handleComplete); - * queue.loadManifest(fileArray, false); // Note the 2nd argument that tells the queue not to start loading yet - * queue.load(); - * - * @method load - */ - p.load = function () { - this._createRequest(); - - this._request.on("complete", this, this); - this._request.on("progress", this, this); - this._request.on("loadStart", this, this); - this._request.on("abort", this, this); - this._request.on("timeout", this, this); - this._request.on("error", this, this); - - var evt = new createjs.Event("initialize"); - evt.loader = this._request; - this.dispatchEvent(evt); - - this._request.load(); - }; - - /** - * Close the the item. This will stop any open requests (although downloads using HTML tags may still continue in - * the background), but events will not longer be dispatched. - * @method cancel - */ - p.cancel = function () { - this.canceled = true; - this.destroy(); - }; - - /** - * Clean up the loader. - * @method destroy - */ - p.destroy = function() { - if (this._request) { - this._request.removeAllEventListeners(); - this._request.destroy(); - } - - this._request = null; - - this._item = null; - this._rawResult = null; - this._result = null; - - this._loadItems = null; - - this.removeAllEventListeners(); - }; - - /** - * Get any items loaded internally by the loader. The enables loaders such as {{#crossLink "ManifestLoader"}}{{/crossLink}} - * to expose items it loads internally. - * @method getLoadedItems - * @return {Array} A list of the items loaded by the loader. - * @since 0.6.0 - */ - p.getLoadedItems = function () { - return this._loadedItems; - }; - - - // Private methods - /** - * Create an internal request used for loading. By default, an {{#crossLink "XHRRequest"}}{{/crossLink}} or - * {{#crossLink "TagRequest"}}{{/crossLink}} is created, depending on the value of {{#crossLink "preferXHR:property"}}{{/crossLink}}. - * Other loaders may override this to use different request types, such as {{#crossLink "ManifestLoader"}}{{/crossLink}}, - * which uses {{#crossLink "JSONLoader"}}{{/crossLink}} or {{#crossLink "JSONPLoader"}}{{/crossLink}} under the hood. - * @method _createRequest - * @protected - */ - p._createRequest = function() { - if (!this._preferXHR) { - this._request = new createjs.TagRequest(this._item, this._tag || this._createTag(), this._tagSrcAttribute); - } else { - this._request = new createjs.XHRRequest(this._item); - } - }; - - /** - * Create the HTML tag used for loading. This method does nothing by default, and needs to be implemented - * by loaders that require tag loading. - * @method _createTag - * @param {String} src The tag source - * @return {HTMLElement} The tag that was created - * @protected - */ - p._createTag = function(src) { return null; }; - - /** - * Dispatch a loadstart {{#crossLink "Event"}}{{/crossLink}}. Please see the {{#crossLink "AbstractLoader/loadstart:event"}}{{/crossLink}} - * event for details on the event payload. - * @method _sendLoadStart - * @protected - */ - p._sendLoadStart = function () { - if (this._isCanceled()) { return; } - this.dispatchEvent("loadstart"); - }; - - /** - * Dispatch a {{#crossLink "ProgressEvent"}}{{/crossLink}}. - * @method _sendProgress - * @param {Number | Object} value The progress of the loaded item, or an object containing loaded - * and total properties. - * @protected - */ - p._sendProgress = function (value) { - if (this._isCanceled()) { return; } - var event = null; - if (typeof(value) == "number") { - this.progress = value; - event = new createjs.ProgressEvent(this.progress); - } else { - event = value; - this.progress = value.loaded / value.total; - event.progress = this.progress; - if (isNaN(this.progress) || this.progress == Infinity) { this.progress = 0; } - } - this.hasEventListener("progress") && this.dispatchEvent(event); - }; - - /** - * Dispatch a complete {{#crossLink "Event"}}{{/crossLink}}. Please see the {{#crossLink "AbstractLoader/complete:event"}}{{/crossLink}} event - * @method _sendComplete - * @protected - */ - p._sendComplete = function () { - if (this._isCanceled()) { return; } - - this.loaded = true; - - var event = new createjs.Event("complete"); - event.rawResult = this._rawResult; - - if (this._result != null) { - event.result = this._result; - } - - this.dispatchEvent(event); - }; - - /** - * Dispatch an error {{#crossLink "Event"}}{{/crossLink}}. Please see the {{#crossLink "AbstractLoader/error:event"}}{{/crossLink}} - * event for details on the event payload. - * @method _sendError - * @param {ErrorEvent} event The event object containing specific error properties. - * @protected - */ - p._sendError = function (event) { - if (this._isCanceled() || !this.hasEventListener("error")) { return; } - if (event == null) { - event = new createjs.ErrorEvent("PRELOAD_ERROR_EMPTY"); // TODO: Populate error - } - this.dispatchEvent(event); - }; - - /** - * Determine if the load has been canceled. This is important to ensure that method calls or asynchronous events - * do not cause issues after the queue has been cleaned up. - * @method _isCanceled - * @return {Boolean} If the loader has been canceled. - * @protected - */ - p._isCanceled = function () { - if (window.createjs == null || this.canceled) { - return true; - } - return false; - }; - - /** - * A custom result formatter function, which is called just before a request dispatches its complete event. Most - * loader types already have an internal formatter, but this can be user-overridden for custom formatting. The - * formatted result will be available on Loaders using {{#crossLink "getResult"}}{{/crossLink}}, and passing `true`. - * @property resultFormatter - * @type Function - * @return {Object} The formatted result - * @since 0.6.0 - */ - p.resultFormatter = null; //TODO: Add support for async formatting. - - /** - * Handle events from internal requests. By default, loaders will handle, and redispatch the necessary events, but - * this method can be overridden for custom behaviours. - * @method handleEvent - * @param {Event} event The event that the internal request dispatches. - * @protected - * @since 0.6.0 - */ - p.handleEvent = function (event) { - switch (event.type) { - case "complete": - this._rawResult = event.target._response; - var result = this.resultFormatter && this.resultFormatter(this); - var _this = this; - if (result instanceof Function) { - result(function(result) { - _this._result = result; - _this._sendComplete(); - }); - } else { - this._result = result || this._rawResult; - this._sendComplete(); - } - break; - case "progress": - this._sendProgress(event); - break; - case "error": - this._sendError(event); - break; - case "loadstart": - this._sendLoadStart(); - break; - case "abort": - case "timeout": - if (!this._isCanceled()) { - this.dispatchEvent(event.type); - } - break; - } - }; - - /** - * @method buildPath - * @protected - * @deprecated Use the {{#crossLink "RequestUtils"}}{{/crossLink}} method {{#crossLink "RequestUtils/buildPath"}}{{/crossLink}} - * instead. - */ - p.buildPath = function (src, data) { - return createjs.RequestUtils.buildPath(src, data); - }; - - /** - * @method toString - * @return {String} a string representation of the instance. - */ - p.toString = function () { - return "[PreloadJS AbstractLoader]"; - }; - - createjs.AbstractLoader = createjs.promote(AbstractLoader, "EventDispatcher"); - -}()); +this.createjs = this.createjs || {}; -//############################################################################## -// AbstractMediaLoader.js -//############################################################################## +(function () { + "use strict"; -this.createjs = this.createjs || {}; - -(function () { - "use strict"; - - // constructor - /** - * The AbstractMediaLoader is a base class that handles some of the shared methods and properties of loaders that - * handle HTML media elements, such as Video and Audio. - * @class AbstractMediaLoader - * @param {LoadItem|Object} loadItem - * @param {Boolean} preferXHR - * @param {String} type The type of media to load. Usually "video" or "audio". - * @extends AbstractLoader - * @constructor - */ - function AbstractMediaLoader(loadItem, preferXHR, type) { - this.AbstractLoader_constructor(loadItem, preferXHR, type); - - // public properties - this.resultFormatter = this._formatResult; - - // protected properties - this._tagSrcAttribute = "src"; - }; - - var p = createjs.extend(AbstractMediaLoader, createjs.AbstractLoader); - - // static properties - // public methods - p.load = function () { - // TagRequest will handle most of this, but Sound / Video need a few custom properties, so just handle them here. - if (!this._tag) { - this._tag = this._createTag(this._item.src); - } - - this._tag.preload = "auto"; - this._tag.load(); - - this.AbstractLoader_load(); - }; - - // protected methods - /** - * Creates a new tag for loading if it doesn't exist yet. - * @method _createTag - * @private - */ - p._createTag = function () {}; - - - p._createRequest = function() { - if (!this._preferXHR) { - this._request = new createjs.MediaTagRequest(this._item, this._tag || this._createTag(), this._tagSrcAttribute); - } else { - this._request = new createjs.XHRRequest(this._item); - } - }; - - /** - * The result formatter for media files. - * @method _formatResult - * @param {AbstractLoader} loader - * @returns {HTMLVideoElement|HTMLAudioElement} - * @private - */ - p._formatResult = function (loader) { - this._tag.removeEventListener && this._tag.removeEventListener("canplaythrough", this._loadedHandler); - this._tag.onstalled = null; - if (this._preferXHR) { - loader.getTag().src = loader.getResult(true); - } - return loader.getTag(); - }; - - createjs.AbstractMediaLoader = createjs.promote(AbstractMediaLoader, "AbstractLoader"); - -}()); +// constructor + /** + * The base loader, which defines all the generic methods, properties, and events. All loaders extend this class, + * including the {{#crossLink "LoadQueue"}}{{/crossLink}}. + * @class AbstractLoader + * @param {LoadItem|object|string} loadItem The item to be loaded. + * @param {Boolean} [preferXHR] Determines if the LoadItem should try and load using XHR, or take a + * tag-based approach, which can be better in cross-domain situations. Not all loaders can load using one or the + * other, so this is a suggested directive. + * @param {String} [type] The type of loader. Loader types are defined as constants on the AbstractLoader class, + * such as {{#crossLink "IMAGE:property"}}{{/crossLink}}, {{#crossLink "CSS:property"}}{{/crossLink}}, etc. + * @extends EventDispatcher + */ + function AbstractLoader(loadItem, preferXHR, type) { + this.EventDispatcher_constructor(); -//############################################################################## -// AbstractRequest.js -//############################################################################## + // public properties + /** + * If the loader has completed loading. This provides a quick check, but also ensures that the different approaches + * used for loading do not pile up resulting in more than one `complete` {{#crossLink "Event"}}{{/crossLink}}. + * @property loaded + * @type {Boolean} + * @default false + */ + this.loaded = false; -this.createjs = this.createjs || {}; - -(function () { - "use strict"; - - /** - * A base class for actual data requests, such as {{#crossLink "XHRRequest"}}{{/crossLink}}, {{#crossLink "TagRequest"}}{{/crossLink}}, - * and {{#crossLink "MediaRequest"}}{{/crossLink}}. PreloadJS loaders will typically use a data loader under the - * hood to get data. - * @class AbstractRequest - * @param {LoadItem} item - * @constructor - */ - var AbstractRequest = function (item) { - this._item = item; - }; - - var p = createjs.extend(AbstractRequest, createjs.EventDispatcher); - - // public methods - /** - * Begin a load. - * @method load - */ - p.load = function() {}; - - /** - * Clean up a request. - * @method destroy - */ - p.destroy = function() {}; - - /** - * Cancel an in-progress request. - * @method cancel - */ - p.cancel = function() {}; - - createjs.AbstractRequest = createjs.promote(AbstractRequest, "EventDispatcher"); - -}()); + /** + * Determine if the loader was canceled. Canceled loads will not fire complete events. Note that this property + * is readonly, so {{#crossLink "LoadQueue"}}{{/crossLink}} queues should be closed using {{#crossLink "LoadQueue/close"}}{{/crossLink}} + * instead. + * @property canceled + * @type {Boolean} + * @default false + */ + this.canceled = false; -//############################################################################## -// TagRequest.js -//############################################################################## + /** + * The current load progress (percentage) for this item. This will be a number between 0 and 1. + * + *

Example

+ * + * var queue = new createjs.LoadQueue(); + * queue.loadFile("largeImage.png"); + * queue.on("progress", function() { + * console.log("Progress:", queue.progress, event.progress); + * }); + * + * @property progress + * @type {Number} + * @default 0 + */ + this.progress = 0; + + /** + * The type of item this loader will load. See {{#crossLink "AbstractLoader"}}{{/crossLink}} for a full list of + * supported types. + * @property type + * @type {String} + */ + this.type = type; + + /** + * A formatter function that converts the loaded raw result into the final result. For example, the JSONLoader + * converts a string of text into a JavaScript object. Not all loaders have a resultFormatter, and this property + * can be overridden to provide custom formatting. + * + * Optionally, a resultFormatter can return a callback function in cases where the formatting needs to be + * asynchronous, such as creating a new image. + * @property resultFormatter + * @type {Function} + * @default null + */ + this.resultFormatter = null; + + // protected properties + /** + * The {{#crossLink "LoadItem"}}{{/crossLink}} this loader represents. Note that this is null in a {{#crossLink "LoadQueue"}}{{/crossLink}}, + * but will be available on loaders such as {{#crossLink "XMLLoader"}}{{/crossLink}} and {{#crossLink "ImageLoader"}}{{/crossLink}}. + * @property _item + * @type {LoadItem|Object} + * @private + */ + if (loadItem) { + this._item = createjs.LoadItem.create(loadItem); + } else { + this._item = null; + } + + /** + * Whether the loader will try and load content using XHR (true) or HTML tags (false). + * @property _preferXHR + * @type {Boolean} + * @private + */ + this._preferXHR = preferXHR; + + /** + * The loaded result after it is formatted by an optional {{#crossLink "resultFormatter"}}{{/crossLink}}. For + * items that are not formatted, this will be the same as the {{#crossLink "_rawResult:property"}}{{/crossLink}}. + * The result is accessed using the {{#crossLink "getResult"}}{{/crossLink}} method. + * @property _result + * @type {Object|String} + * @private + */ + this._result = null; + + /** + * The loaded result before it is formatted. The rawResult is accessed using the {{#crossLink "getResult"}}{{/crossLink}} + * method, and passing `true`. + * @property _rawResult + * @type {Object|String} + * @private + */ + this._rawResult = null; + + /** + * A list of items that loaders load behind the scenes. This does not include the main item the loader is + * responsible for loading. Examples of loaders that have sub-items include the {{#crossLink "SpriteSheetLoader"}}{{/crossLink}} and + * {{#crossLink "ManifestLoader"}}{{/crossLink}}. + * @property _loadItems + * @type {null} + * @protected + */ + this._loadedItems = null; + + /** + * The attribute the items loaded using tags use for the source. + * @type {string} + * @default null + * @private + */ + this._tagSrcAttribute = null; + + /** + * An HTML tag (or similar) that a loader may use to load HTML content, such as images, scripts, etc. + * @property _tag + * @type {Object} + * @private + */ + this._tag = null; + }; + + var p = createjs.extend(AbstractLoader, createjs.EventDispatcher); + var s = AbstractLoader; + + // TODO: deprecated + // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. + + + /** + * Defines a POST request, use for a method value when loading data. + * @property POST + * @type {string} + * @default post + * @static + */ + s.POST = "POST"; + + /** + * Defines a GET request, use for a method value when loading data. + * @property GET + * @type {string} + * @default get + * @static + */ + s.GET = "GET"; + + /** + * The preload type for generic binary types. Note that images are loaded as binary files when using XHR. + * @property BINARY + * @type {String} + * @default binary + * @static + * @since 0.6.0 + */ + s.BINARY = "binary"; + + /** + * The preload type for css files. CSS files are loaded using a <link> when loaded with XHR, or a + * <style> tag when loaded with tags. + * @property CSS + * @type {String} + * @default css + * @static + * @since 0.6.0 + */ + s.CSS = "css"; + + /** + * The preload type for image files, usually png, gif, or jpg/jpeg. Images are loaded into an <image> tag. + * @property IMAGE + * @type {String} + * @default image + * @static + * @since 0.6.0 + */ + s.IMAGE = "image"; + + /** + * The preload type for javascript files, usually with the "js" file extension. JavaScript files are loaded into a + * <script> tag. + * + * Since version 0.4.1+, due to how tag-loaded scripts work, all JavaScript files are automatically injected into + * the body of the document to maintain parity between XHR and tag-loaded scripts. In version 0.4.0 and earlier, + * only tag-loaded scripts are injected. + * @property JAVASCRIPT + * @type {String} + * @default javascript + * @static + * @since 0.6.0 + */ + s.JAVASCRIPT = "javascript"; + + /** + * The preload type for json files, usually with the "json" file extension. JSON data is loaded and parsed into a + * JavaScript object. Note that if a `callback` is present on the load item, the file will be loaded with JSONP, + * no matter what the {{#crossLink "LoadQueue/preferXHR:property"}}{{/crossLink}} property is set to, and the JSON + * must contain a matching wrapper function. + * @property JSON + * @type {String} + * @default json + * @static + * @since 0.6.0 + */ + s.JSON = "json"; + + /** + * The preload type for jsonp files, usually with the "json" file extension. JSON data is loaded and parsed into a + * JavaScript object. You are required to pass a callback parameter that matches the function wrapper in the JSON. + * Note that JSONP will always be used if there is a callback present, no matter what the {{#crossLink "LoadQueue/preferXHR:property"}}{{/crossLink}} + * property is set to. + * @property JSONP + * @type {String} + * @default jsonp + * @static + * @since 0.6.0 + */ + s.JSONP = "jsonp"; + + /** + * The preload type for json-based manifest files, usually with the "json" file extension. The JSON data is loaded + * and parsed into a JavaScript object. PreloadJS will then look for a "manifest" property in the JSON, which is an + * Array of files to load, following the same format as the {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}} + * method. If a "callback" is specified on the manifest object, then it will be loaded using JSONP instead, + * regardless of what the {{#crossLink "LoadQueue/preferXHR:property"}}{{/crossLink}} property is set to. + * @property MANIFEST + * @type {String} + * @default manifest + * @static + * @since 0.6.0 + */ + s.MANIFEST = "manifest"; + + /** + * The preload type for sound files, usually mp3, ogg, or wav. When loading via tags, audio is loaded into an + * <audio> tag. + * @property SOUND + * @type {String} + * @default sound + * @static + * @since 0.6.0 + */ + s.SOUND = "sound"; + + /** + * The preload type for video files, usually mp4, ts, or ogg. When loading via tags, video is loaded into an + * <video> tag. + * @property VIDEO + * @type {String} + * @default video + * @static + * @since 0.6.0 + */ + s.VIDEO = "video"; + + /** + * The preload type for SpriteSheet files. SpriteSheet files are JSON files that contain string image paths. + * @property SPRITESHEET + * @type {String} + * @default spritesheet + * @static + * @since 0.6.0 + */ + s.SPRITESHEET = "spritesheet"; + + /** + * The preload type for SVG files. + * @property SVG + * @type {String} + * @default svg + * @static + * @since 0.6.0 + */ + s.SVG = "svg"; + + /** + * The preload type for text files, which is also the default file type if the type can not be determined. Text is + * loaded as raw text. + * @property TEXT + * @type {String} + * @default text + * @static + * @since 0.6.0 + */ + s.TEXT = "text"; + + /** + * The preload type for xml files. XML is loaded into an XML document. + * @property XML + * @type {String} + * @default xml + * @static + * @since 0.6.0 + */ + s.XML = "xml"; + +// Events + /** + * The {{#crossLink "ProgressEvent"}}{{/crossLink}} that is fired when the overall progress changes. Prior to + * version 0.6.0, this was just a regular {{#crossLink "Event"}}{{/crossLink}}. + * @event progress + * @since 0.3.0 + */ + + /** + * The {{#crossLink "Event"}}{{/crossLink}} that is fired when a load starts. + * @event loadstart + * @param {Object} target The object that dispatched the event. + * @param {String} type The event type. + * @since 0.3.1 + */ + + /** + * The {{#crossLink "Event"}}{{/crossLink}} that is fired when the entire queue has been loaded. + * @event complete + * @param {Object} target The object that dispatched the event. + * @param {String} type The event type. + * @since 0.3.0 + */ + + /** + * The {{#crossLink "ErrorEvent"}}{{/crossLink}} that is fired when the loader encounters an error. If the error was + * encountered by a file, the event will contain the item that caused the error. Prior to version 0.6.0, this was + * just a regular {{#crossLink "Event"}}{{/crossLink}}. + * @event error + * @since 0.3.0 + */ + + /** + * The {{#crossLink "Event"}}{{/crossLink}} that is fired when the loader encounters an internal file load error. + * This enables loaders to maintain internal queues, and surface file load errors. + * @event fileerror + * @param {Object} target The object that dispatched the event. + * @param {String} type The even type ("fileerror") + * @param {LoadItem|object} The item that encountered the error + * @since 0.6.0 + */ + + /** + * The {{#crossLink "Event"}}{{/crossLink}} that is fired when a loader internally loads a file. This enables + * loaders such as {{#crossLink "ManifestLoader"}}{{/crossLink}} to maintain internal {{#crossLink "LoadQueue"}}{{/crossLink}}s + * and notify when they have loaded a file. The {{#crossLink "LoadQueue"}}{{/crossLink}} class dispatches a + * slightly different {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}} event. + * @event fileload + * @param {Object} target The object that dispatched the event. + * @param {String} type The event type ("fileload") + * @param {Object} item The file item which was specified in the {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} + * or {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}} call. If only a string path or tag was specified, the + * object will contain that value as a `src` property. + * @param {Object} result The HTML tag or parsed result of the loaded item. + * @param {Object} rawResult The unprocessed result, usually the raw text or binary data before it is converted + * to a usable object. + * @since 0.6.0 + */ + + /** + * The {{#crossLink "Event"}}{{/crossLink}} that is fired after the internal request is created, but before a load. + * This allows updates to the loader for specific loading needs, such as binary or XHR image loading. + * @event initialize + * @param {Object} target The object that dispatched the event. + * @param {String} type The event type ("initialize") + * @param {AbstractLoader} loader The loader that has been initialized. + */ + + + /** + * Get a reference to the manifest item that is loaded by this loader. In some cases this will be the value that was + * passed into {{#crossLink "LoadQueue"}}{{/crossLink}} using {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} or + * {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}}. However if only a String path was passed in, then it will + * be a {{#crossLink "LoadItem"}}{{/crossLink}}. + * @method getItem + * @return {Object} The manifest item that this loader is responsible for loading. + * @since 0.6.0 + */ + p.getItem = function () { + return this._item; + }; + + /** + * Get a reference to the content that was loaded by the loader (only available after the {{#crossLink "complete:event"}}{{/crossLink}} + * event is dispatched. + * @method getResult + * @param {Boolean} [raw=false] Determines if the returned result will be the formatted content, or the raw loaded + * data (if it exists). + * @return {Object} + * @since 0.6.0 + */ + p.getResult = function (raw) { + return raw ? this._rawResult : this._result; + }; + + /** + * Return the `tag` this object creates or uses for loading. + * @method getTag + * @return {Object} The tag instance + * @since 0.6.0 + */ + p.getTag = function () { + return this._tag; + }; + + /** + * Set the `tag` this item uses for loading. + * @method setTag + * @param {Object} tag The tag instance + * @since 0.6.0 + */ + p.setTag = function(tag) { + this._tag = tag; + }; + + /** + * Begin loading the item. This method is required when using a loader by itself. + * + *

Example

+ * + * var queue = new createjs.LoadQueue(); + * queue.on("complete", handleComplete); + * queue.loadManifest(fileArray, false); // Note the 2nd argument that tells the queue not to start loading yet + * queue.load(); + * + * @method load + */ + p.load = function () { + this._createRequest(); + + this._request.on("complete", this, this); + this._request.on("progress", this, this); + this._request.on("loadStart", this, this); + this._request.on("abort", this, this); + this._request.on("timeout", this, this); + this._request.on("error", this, this); + + var evt = new createjs.Event("initialize"); + evt.loader = this._request; + this.dispatchEvent(evt); + + this._request.load(); + }; + + /** + * Close the the item. This will stop any open requests (although downloads using HTML tags may still continue in + * the background), but events will not longer be dispatched. + * @method cancel + */ + p.cancel = function () { + this.canceled = true; + this.destroy(); + }; + + /** + * Clean up the loader. + * @method destroy + */ + p.destroy = function() { + if (this._request) { + this._request.removeAllEventListeners(); + this._request.destroy(); + } + + this._request = null; + + this._item = null; + this._rawResult = null; + this._result = null; + + this._loadItems = null; + + this.removeAllEventListeners(); + }; + + /** + * Get any items loaded internally by the loader. The enables loaders such as {{#crossLink "ManifestLoader"}}{{/crossLink}} + * to expose items it loads internally. + * @method getLoadedItems + * @return {Array} A list of the items loaded by the loader. + * @since 0.6.0 + */ + p.getLoadedItems = function () { + return this._loadedItems; + }; + + + // Private methods + /** + * Create an internal request used for loading. By default, an {{#crossLink "XHRRequest"}}{{/crossLink}} or + * {{#crossLink "TagRequest"}}{{/crossLink}} is created, depending on the value of {{#crossLink "preferXHR:property"}}{{/crossLink}}. + * Other loaders may override this to use different request types, such as {{#crossLink "ManifestLoader"}}{{/crossLink}}, + * which uses {{#crossLink "JSONLoader"}}{{/crossLink}} or {{#crossLink "JSONPLoader"}}{{/crossLink}} under the hood. + * @method _createRequest + * @protected + */ + p._createRequest = function() { + if (!this._preferXHR) { + this._request = new createjs.TagRequest(this._item, this._tag || this._createTag(), this._tagSrcAttribute); + } else { + this._request = new createjs.XHRRequest(this._item); + } + }; + + /** + * Create the HTML tag used for loading. This method does nothing by default, and needs to be implemented + * by loaders that require tag loading. + * @method _createTag + * @param {String} src The tag source + * @return {HTMLElement} The tag that was created + * @protected + */ + p._createTag = function(src) { return null; }; + + /** + * Dispatch a loadstart {{#crossLink "Event"}}{{/crossLink}}. Please see the {{#crossLink "AbstractLoader/loadstart:event"}}{{/crossLink}} + * event for details on the event payload. + * @method _sendLoadStart + * @protected + */ + p._sendLoadStart = function () { + if (this._isCanceled()) { return; } + this.dispatchEvent("loadstart"); + }; + + /** + * Dispatch a {{#crossLink "ProgressEvent"}}{{/crossLink}}. + * @method _sendProgress + * @param {Number | Object} value The progress of the loaded item, or an object containing loaded + * and total properties. + * @protected + */ + p._sendProgress = function (value) { + if (this._isCanceled()) { return; } + var event = null; + if (typeof(value) == "number") { + this.progress = value; + event = new createjs.ProgressEvent(this.progress); + } else { + event = value; + this.progress = value.loaded / value.total; + event.progress = this.progress; + if (isNaN(this.progress) || this.progress == Infinity) { this.progress = 0; } + } + this.hasEventListener("progress") && this.dispatchEvent(event); + }; + + /** + * Dispatch a complete {{#crossLink "Event"}}{{/crossLink}}. Please see the {{#crossLink "AbstractLoader/complete:event"}}{{/crossLink}} event + * @method _sendComplete + * @protected + */ + p._sendComplete = function () { + if (this._isCanceled()) { return; } + + this.loaded = true; + + var event = new createjs.Event("complete"); + event.rawResult = this._rawResult; + + if (this._result != null) { + event.result = this._result; + } + + this.dispatchEvent(event); + }; + + /** + * Dispatch an error {{#crossLink "Event"}}{{/crossLink}}. Please see the {{#crossLink "AbstractLoader/error:event"}}{{/crossLink}} + * event for details on the event payload. + * @method _sendError + * @param {ErrorEvent} event The event object containing specific error properties. + * @protected + */ + p._sendError = function (event) { + if (this._isCanceled() || !this.hasEventListener("error")) { return; } + if (event == null) { + event = new createjs.ErrorEvent("PRELOAD_ERROR_EMPTY"); // TODO: Populate error + } + this.dispatchEvent(event); + }; + + /** + * Determine if the load has been canceled. This is important to ensure that method calls or asynchronous events + * do not cause issues after the queue has been cleaned up. + * @method _isCanceled + * @return {Boolean} If the loader has been canceled. + * @protected + */ + p._isCanceled = function () { + if (window.createjs == null || this.canceled) { + return true; + } + return false; + }; + + /** + * A custom result formatter function, which is called just before a request dispatches its complete event. Most + * loader types already have an internal formatter, but this can be user-overridden for custom formatting. The + * formatted result will be available on Loaders using {{#crossLink "getResult"}}{{/crossLink}}, and passing `true`. + * @property resultFormatter + * @type Function + * @return {Object} The formatted result + * @since 0.6.0 + */ + p.resultFormatter = null; //TODO: Add support for async formatting. + + /** + * Handle events from internal requests. By default, loaders will handle, and redispatch the necessary events, but + * this method can be overridden for custom behaviours. + * @method handleEvent + * @param {Event} event The event that the internal request dispatches. + * @protected + * @since 0.6.0 + */ + p.handleEvent = function (event) { + switch (event.type) { + case "complete": + this._rawResult = event.target._response; + var result = this.resultFormatter && this.resultFormatter(this); + var _this = this; + if (result instanceof Function) { + result(function(result) { + _this._result = result; + _this._sendComplete(); + }); + } else { + this._result = result || this._rawResult; + this._sendComplete(); + } + break; + case "progress": + this._sendProgress(event); + break; + case "error": + this._sendError(event); + break; + case "loadstart": + this._sendLoadStart(); + break; + case "abort": + case "timeout": + if (!this._isCanceled()) { + this.dispatchEvent(event.type); + } + break; + } + }; + + /** + * @method buildPath + * @protected + * @deprecated Use the {{#crossLink "RequestUtils"}}{{/crossLink}} method {{#crossLink "RequestUtils/buildPath"}}{{/crossLink}} + * instead. + */ + p.buildPath = function (src, data) { + return createjs.RequestUtils.buildPath(src, data); + }; + + /** + * @method toString + * @return {String} a string representation of the instance. + */ + p.toString = function () { + return "[PreloadJS AbstractLoader]"; + }; + + createjs.AbstractLoader = createjs.promote(AbstractLoader, "EventDispatcher"); + +}()); + +//############################################################################## +// AbstractMediaLoader.js +//############################################################################## + +this.createjs = this.createjs || {}; + +(function () { + "use strict"; + + // constructor + /** + * The AbstractMediaLoader is a base class that handles some of the shared methods and properties of loaders that + * handle HTML media elements, such as Video and Audio. + * @class AbstractMediaLoader + * @param {LoadItem|Object} loadItem + * @param {Boolean} preferXHR + * @param {String} type The type of media to load. Usually "video" or "audio". + * @extends AbstractLoader + * @constructor + */ + function AbstractMediaLoader(loadItem, preferXHR, type) { + this.AbstractLoader_constructor(loadItem, preferXHR, type); + + // public properties + this.resultFormatter = this._formatResult; + + // protected properties + this._tagSrcAttribute = "src"; + }; + + var p = createjs.extend(AbstractMediaLoader, createjs.AbstractLoader); + + // static properties + // public methods + p.load = function () { + // TagRequest will handle most of this, but Sound / Video need a few custom properties, so just handle them here. + if (!this._tag) { + this._tag = this._createTag(this._item.src); + } + + this._tag.preload = "auto"; + this._tag.load(); + + this.AbstractLoader_load(); + }; + + // protected methods + /** + * Creates a new tag for loading if it doesn't exist yet. + * @method _createTag + * @private + */ + p._createTag = function () {}; + + + p._createRequest = function() { + if (!this._preferXHR) { + this._request = new createjs.MediaTagRequest(this._item, this._tag || this._createTag(), this._tagSrcAttribute); + } else { + this._request = new createjs.XHRRequest(this._item); + } + }; + + /** + * The result formatter for media files. + * @method _formatResult + * @param {AbstractLoader} loader + * @returns {HTMLVideoElement|HTMLAudioElement} + * @private + */ + p._formatResult = function (loader) { + this._tag.removeEventListener && this._tag.removeEventListener("canplaythrough", this._loadedHandler); + this._tag.onstalled = null; + if (this._preferXHR) { + loader.getTag().src = loader.getResult(true); + } + return loader.getTag(); + }; + + createjs.AbstractMediaLoader = createjs.promote(AbstractMediaLoader, "AbstractLoader"); + +}()); + +//############################################################################## +// AbstractRequest.js +//############################################################################## + +this.createjs = this.createjs || {}; + +(function () { + "use strict"; + + /** + * A base class for actual data requests, such as {{#crossLink "XHRRequest"}}{{/crossLink}}, {{#crossLink "TagRequest"}}{{/crossLink}}, + * and {{#crossLink "MediaRequest"}}{{/crossLink}}. PreloadJS loaders will typically use a data loader under the + * hood to get data. + * @class AbstractRequest + * @param {LoadItem} item + * @constructor + */ + var AbstractRequest = function (item) { + this._item = item; + }; + + var p = createjs.extend(AbstractRequest, createjs.EventDispatcher); + + // public methods + /** + * Begin a load. + * @method load + */ + p.load = function() {}; + + /** + * Clean up a request. + * @method destroy + */ + p.destroy = function() {}; + + /** + * Cancel an in-progress request. + * @method cancel + */ + p.cancel = function() {}; + + createjs.AbstractRequest = createjs.promote(AbstractRequest, "EventDispatcher"); + +}()); + +//############################################################################## +// TagRequest.js +//############################################################################## + +this.createjs = this.createjs || {}; + +(function () { + "use strict"; + + // constructor + /** + * An {{#crossLink "AbstractRequest"}}{{/crossLink}} that loads HTML tags, such as images and scripts. + * @class TagRequest + * @param {LoadItem} loadItem + * @param {HTMLElement} tag + * @param {String} srcAttribute The tag attribute that specifies the source, such as "src", "href", etc. + */ + function TagRequest(loadItem, tag, srcAttribute) { + this.AbstractRequest_constructor(loadItem); + + // protected properties + /** + * The HTML tag instance that is used to load. + * @property _tag + * @type {HTMLElement} + * @protected + */ + this._tag = tag; + + /** + * The tag attribute that specifies the source, such as "src", "href", etc. + * @property _tagSrcAttribute + * @type {String} + * @protected + */ + this._tagSrcAttribute = srcAttribute; + + /** + * A method closure used for handling the tag load event. + * @property _loadedHandler + * @type {Function} + * @private + */ + this._loadedHandler = createjs.proxy(this._handleTagComplete, this); + + /** + * Determines if the element was added to the DOM automatically by PreloadJS, so it can be cleaned up after. + * @property _addedToDOM + * @type {Boolean} + * @private + */ + this._addedToDOM = false; + + /** + * Determines what the tags initial style.visibility was, so we can set it correctly after a load. + * + * @type {null} + * @private + */ + this._startTagVisibility = null; + }; + + var p = createjs.extend(TagRequest, createjs.AbstractRequest); + + // public methods + p.load = function () { + this._tag.onload = createjs.proxy(this._handleTagComplete, this); + this._tag.onreadystatechange = createjs.proxy(this._handleReadyStateChange, this); + this._tag.onerror = createjs.proxy(this._handleError, this); + + var evt = new createjs.Event("initialize"); + evt.loader = this._tag; + + this.dispatchEvent(evt); + + this._hideTag(); + + this._loadTimeout = setTimeout(createjs.proxy(this._handleTimeout, this), this._item.loadTimeout); + + this._tag[this._tagSrcAttribute] = this._item.src; + + // wdg:: Append the tag AFTER setting the src, or SVG loading on iOS will fail. + if (this._tag.parentNode == null) { + window.document.body.appendChild(this._tag); + this._addedToDOM = true; + } + }; + + p.destroy = function() { + this._clean(); + this._tag = null; + + this.AbstractRequest_destroy(); + }; + + // private methods + /** + * Handle the readyStateChange event from a tag. We need this in place of the `onload` callback (mainly SCRIPT + * and LINK tags), but other cases may exist. + * @method _handleReadyStateChange + * @private + */ + p._handleReadyStateChange = function () { + clearTimeout(this._loadTimeout); + // This is strictly for tags in browsers that do not support onload. + var tag = this._tag; + + // Complete is for old IE support. + if (tag.readyState == "loaded" || tag.readyState == "complete") { + this._handleTagComplete(); + } + }; + + /** + * Handle any error events from the tag. + * @method _handleError + * @protected + */ + p._handleError = function() { + this._clean(); + this.dispatchEvent("error"); + }; + + /** + * Handle the tag's onload callback. + * @method _handleTagComplete + * @private + */ + p._handleTagComplete = function () { + this._rawResult = this._tag; + this._result = this.resultFormatter && this.resultFormatter(this) || this._rawResult; + + this._clean(); + this._showTag(); + + this.dispatchEvent("complete"); + }; + + /** + * The tag request has not loaded within the time specified in loadTimeout. + * @method _handleError + * @param {Object} event The XHR error event. + * @private + */ + p._handleTimeout = function () { + this._clean(); + this.dispatchEvent(new createjs.Event("timeout")); + }; + + /** + * Remove event listeners, but don't destroy the request object + * @method _clean + * @private + */ + p._clean = function() { + this._tag.onload = null; + this._tag.onreadystatechange = null; + this._tag.onerror = null; + if (this._addedToDOM && this._tag.parentNode != null) { + this._tag.parentNode.removeChild(this._tag); + } + clearTimeout(this._loadTimeout); + }; + + p._hideTag = function() { + this._startTagVisibility = this._tag.style.visibility; + this._tag.style.visibility = "hidden"; + }; + + p._showTag = function() { + this._tag.style.visibility = this._startTagVisibility; + }; + + /** + * Handle a stalled audio event. The main place this happens is with HTMLAudio in Chrome when playing back audio + * that is already in a load, but not complete. + * @method _handleStalled + * @private + */ + p._handleStalled = function () { + //Ignore, let the timeout take care of it. Sometimes its not really stopped. + }; + + createjs.TagRequest = createjs.promote(TagRequest, "AbstractRequest"); -this.createjs = this.createjs || {}; - -(function () { - "use strict"; - - // constructor - /** - * An {{#crossLink "AbstractRequest"}}{{/crossLink}} that loads HTML tags, such as images and scripts. - * @class TagRequest - * @param {LoadItem} loadItem - * @param {HTMLElement} tag - * @param {String} srcAttribute The tag attribute that specifies the source, such as "src", "href", etc. - */ - function TagRequest(loadItem, tag, srcAttribute) { - this.AbstractRequest_constructor(loadItem); - - // protected properties - /** - * The HTML tag instance that is used to load. - * @property _tag - * @type {HTMLElement} - * @protected - */ - this._tag = tag; - - /** - * The tag attribute that specifies the source, such as "src", "href", etc. - * @property _tagSrcAttribute - * @type {String} - * @protected - */ - this._tagSrcAttribute = srcAttribute; - - /** - * A method closure used for handling the tag load event. - * @property _loadedHandler - * @type {Function} - * @private - */ - this._loadedHandler = createjs.proxy(this._handleTagComplete, this); - - /** - * Determines if the element was added to the DOM automatically by PreloadJS, so it can be cleaned up after. - * @property _addedToDOM - * @type {Boolean} - * @private - */ - this._addedToDOM = false; - - /** - * Determines what the tags initial style.visibility was, so we can set it correctly after a load. - * - * @type {null} - * @private - */ - this._startTagVisibility = null; - }; - - var p = createjs.extend(TagRequest, createjs.AbstractRequest); - - // public methods - p.load = function () { - this._tag.onload = createjs.proxy(this._handleTagComplete, this); - this._tag.onreadystatechange = createjs.proxy(this._handleReadyStateChange, this); - this._tag.onerror = createjs.proxy(this._handleError, this); - - var evt = new createjs.Event("initialize"); - evt.loader = this._tag; - - this.dispatchEvent(evt); - - this._hideTag(); - - this._loadTimeout = setTimeout(createjs.proxy(this._handleTimeout, this), this._item.loadTimeout); - - this._tag[this._tagSrcAttribute] = this._item.src; - - // wdg:: Append the tag AFTER setting the src, or SVG loading on iOS will fail. - if (this._tag.parentNode == null) { - window.document.body.appendChild(this._tag); - this._addedToDOM = true; - } - }; - - p.destroy = function() { - this._clean(); - this._tag = null; - - this.AbstractRequest_destroy(); - }; - - // private methods - /** - * Handle the readyStateChange event from a tag. We need this in place of the `onload` callback (mainly SCRIPT - * and LINK tags), but other cases may exist. - * @method _handleReadyStateChange - * @private - */ - p._handleReadyStateChange = function () { - clearTimeout(this._loadTimeout); - // This is strictly for tags in browsers that do not support onload. - var tag = this._tag; - - // Complete is for old IE support. - if (tag.readyState == "loaded" || tag.readyState == "complete") { - this._handleTagComplete(); - } - }; - - /** - * Handle any error events from the tag. - * @method _handleError - * @protected - */ - p._handleError = function() { - this._clean(); - this.dispatchEvent("error"); - }; - - /** - * Handle the tag's onload callback. - * @method _handleTagComplete - * @private - */ - p._handleTagComplete = function () { - this._rawResult = this._tag; - this._result = this.resultFormatter && this.resultFormatter(this) || this._rawResult; - - this._clean(); - this._showTag(); - - this.dispatchEvent("complete"); - }; - - /** - * The tag request has not loaded within the time specified in loadTimeout. - * @method _handleError - * @param {Object} event The XHR error event. - * @private - */ - p._handleTimeout = function () { - this._clean(); - this.dispatchEvent(new createjs.Event("timeout")); - }; - - /** - * Remove event listeners, but don't destroy the request object - * @method _clean - * @private - */ - p._clean = function() { - this._tag.onload = null; - this._tag.onreadystatechange = null; - this._tag.onerror = null; - if (this._addedToDOM && this._tag.parentNode != null) { - this._tag.parentNode.removeChild(this._tag); - } - clearTimeout(this._loadTimeout); - }; - - p._hideTag = function() { - this._startTagVisibility = this._tag.style.visibility; - this._tag.style.visibility = "hidden"; - }; - - p._showTag = function() { - this._tag.style.visibility = this._startTagVisibility; - }; - - /** - * Handle a stalled audio event. The main place this happens is with HTMLAudio in Chrome when playing back audio - * that is already in a load, but not complete. - * @method _handleStalled - * @private - */ - p._handleStalled = function () { - //Ignore, let the timeout take care of it. Sometimes its not really stopped. - }; - - createjs.TagRequest = createjs.promote(TagRequest, "AbstractRequest"); - }()); //############################################################################## // MediaTagRequest.js //############################################################################## -this.createjs = this.createjs || {}; - -(function () { - "use strict"; - - // constructor - /** - * An {{#crossLink "TagRequest"}}{{/crossLink}} that loads HTML tags for video and audio. - * @class MediaTagRequest - * @param {LoadItem} loadItem - * @param {HTMLAudioElement|HTMLVideoElement} tag - * @param {String} srcAttribute The tag attribute that specifies the source, such as "src", "href", etc. - * @constructor - */ - function MediaTagRequest(loadItem, tag, srcAttribute) { - this.AbstractRequest_constructor(loadItem); - - // protected properties - this._tag = tag; - this._tagSrcAttribute = srcAttribute; - this._loadedHandler = createjs.proxy(this._handleTagComplete, this); - }; - - var p = createjs.extend(MediaTagRequest, createjs.TagRequest); - var s = MediaTagRequest; - - // public methods - p.load = function () { - var sc = createjs.proxy(this._handleStalled, this); - this._stalledCallback = sc; - - var pc = createjs.proxy(this._handleProgress, this); - this._handleProgress = pc; - - this._tag.addEventListener("stalled", sc); - this._tag.addEventListener("progress", pc); - - // This will tell us when audio is buffered enough to play through, but not when its loaded. - // The tag doesn't keep loading in Chrome once enough has buffered, and we have decided that behaviour is sufficient. - this._tag.addEventListener && this._tag.addEventListener("canplaythrough", this._loadedHandler, false); // canplaythrough callback doesn't work in Chrome, so we use an event. - - this.TagRequest_load(); - }; - - // private methods - p._handleReadyStateChange = function () { - clearTimeout(this._loadTimeout); - // This is strictly for tags in browsers that do not support onload. - var tag = this._tag; - - // Complete is for old IE support. - if (tag.readyState == "loaded" || tag.readyState == "complete") { - this._handleTagComplete(); - } - }; - - p._handleStalled = function () { - //Ignore, let the timeout take care of it. Sometimes its not really stopped. - }; - - /** - * An XHR request has reported progress. - * @method _handleProgress - * @param {Object} event The XHR progress event. - * @private - */ - p._handleProgress = function (event) { - if (!event || event.loaded > 0 && event.total == 0) { - return; // Sometimes we get no "total", so just ignore the progress event. - } - - var newEvent = new createjs.ProgressEvent(event.loaded, event.total); - this.dispatchEvent(newEvent); - }; - - // protected methods - p._clean = function () { - this._tag.removeEventListener && this._tag.removeEventListener("canplaythrough", this._loadedHandler); - this._tag.removeEventListener("stalled", this._stalledCallback); - this._tag.removeEventListener("progress", this._progressCallback); - - this.TagRequest__clean(); - }; - - createjs.MediaTagRequest = createjs.promote(MediaTagRequest, "TagRequest"); - +this.createjs = this.createjs || {}; + +(function () { + "use strict"; + + // constructor + /** + * An {{#crossLink "TagRequest"}}{{/crossLink}} that loads HTML tags for video and audio. + * @class MediaTagRequest + * @param {LoadItem} loadItem + * @param {HTMLAudioElement|HTMLVideoElement} tag + * @param {String} srcAttribute The tag attribute that specifies the source, such as "src", "href", etc. + * @constructor + */ + function MediaTagRequest(loadItem, tag, srcAttribute) { + this.AbstractRequest_constructor(loadItem); + + // protected properties + this._tag = tag; + this._tagSrcAttribute = srcAttribute; + this._loadedHandler = createjs.proxy(this._handleTagComplete, this); + }; + + var p = createjs.extend(MediaTagRequest, createjs.TagRequest); + var s = MediaTagRequest; + + // public methods + p.load = function () { + var sc = createjs.proxy(this._handleStalled, this); + this._stalledCallback = sc; + + var pc = createjs.proxy(this._handleProgress, this); + this._handleProgress = pc; + + this._tag.addEventListener("stalled", sc); + this._tag.addEventListener("progress", pc); + + // This will tell us when audio is buffered enough to play through, but not when its loaded. + // The tag doesn't keep loading in Chrome once enough has buffered, and we have decided that behaviour is sufficient. + this._tag.addEventListener && this._tag.addEventListener("canplaythrough", this._loadedHandler, false); // canplaythrough callback doesn't work in Chrome, so we use an event. + + this.TagRequest_load(); + }; + + // private methods + p._handleReadyStateChange = function () { + clearTimeout(this._loadTimeout); + // This is strictly for tags in browsers that do not support onload. + var tag = this._tag; + + // Complete is for old IE support. + if (tag.readyState == "loaded" || tag.readyState == "complete") { + this._handleTagComplete(); + } + }; + + p._handleStalled = function () { + //Ignore, let the timeout take care of it. Sometimes its not really stopped. + }; + + /** + * An XHR request has reported progress. + * @method _handleProgress + * @param {Object} event The XHR progress event. + * @private + */ + p._handleProgress = function (event) { + if (!event || event.loaded > 0 && event.total == 0) { + return; // Sometimes we get no "total", so just ignore the progress event. + } + + var newEvent = new createjs.ProgressEvent(event.loaded, event.total); + this.dispatchEvent(newEvent); + }; + + // protected methods + p._clean = function () { + this._tag.removeEventListener && this._tag.removeEventListener("canplaythrough", this._loadedHandler); + this._tag.removeEventListener("stalled", this._stalledCallback); + this._tag.removeEventListener("progress", this._progressCallback); + + this.TagRequest__clean(); + }; + + createjs.MediaTagRequest = createjs.promote(MediaTagRequest, "TagRequest"); + }()); //############################################################################## // XHRRequest.js //############################################################################## -this.createjs = this.createjs || {}; - -(function () { - "use strict"; - -// constructor - /** - * A preloader that loads items using XHR requests, usually XMLHttpRequest. However XDomainRequests will be used - * for cross-domain requests if possible, and older versions of IE fall back on to ActiveX objects when necessary. - * XHR requests load the content as text or binary data, provide progress and consistent completion events, and - * can be canceled during load. Note that XHR is not supported in IE 6 or earlier, and is not recommended for - * cross-domain loading. - * @class XHRRequest - * @constructor - * @param {Object} item The object that defines the file to load. Please see the {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} - * for an overview of supported file properties. - * @extends AbstractLoader - */ - function XHRRequest(item) { - this.AbstractRequest_constructor(item); - - // protected properties - /** - * A reference to the XHR request used to load the content. - * @property _request - * @type {XMLHttpRequest | XDomainRequest | ActiveX.XMLHTTP} - * @private - */ - this._request = null; - - /** - * A manual load timeout that is used for browsers that do not support the onTimeout event on XHR (XHR level 1, - * typically IE9). - * @property _loadTimeout - * @type {Number} - * @private - */ - this._loadTimeout = null; - - /** - * The browser's XHR (XMLHTTPRequest) version. Supported versions are 1 and 2. There is no official way to detect - * the version, so we use capabilities to make a best guess. - * @property _xhrLevel - * @type {Number} - * @default 1 - * @private - */ - this._xhrLevel = 1; - - /** - * The response of a loaded file. This is set because it is expensive to look up constantly. This property will be - * null until the file is loaded. - * @property _response - * @type {mixed} - * @private - */ - this._response = null; - - /** - * The response of the loaded file before it is modified. In most cases, content is converted from raw text to - * an HTML tag or a formatted object which is set to the result property, but the developer may still - * want to access the raw content as it was loaded. - * @property _rawResponse - * @type {String|Object} - * @private - */ - this._rawResponse = null; - - this._canceled = false; - - // Setup our event handlers now. - this._handleLoadStartProxy = createjs.proxy(this._handleLoadStart, this); - this._handleProgressProxy = createjs.proxy(this._handleProgress, this); - this._handleAbortProxy = createjs.proxy(this._handleAbort, this); - this._handleErrorProxy = createjs.proxy(this._handleError, this); - this._handleTimeoutProxy = createjs.proxy(this._handleTimeout, this); - this._handleLoadProxy = createjs.proxy(this._handleLoad, this); - this._handleReadyStateChangeProxy = createjs.proxy(this._handleReadyStateChange, this); - - if (!this._createXHR(item)) { - //TODO: Throw error? - } - }; - - var p = createjs.extend(XHRRequest, createjs.AbstractRequest); - -// static properties - /** - * A list of XMLHTTP object IDs to try when building an ActiveX object for XHR requests in earlier versions of IE. - * @property ACTIVEX_VERSIONS - * @type {Array} - * @since 0.4.2 - * @private - */ - XHRRequest.ACTIVEX_VERSIONS = [ - "Msxml2.XMLHTTP.6.0", - "Msxml2.XMLHTTP.5.0", - "Msxml2.XMLHTTP.4.0", - "MSXML2.XMLHTTP.3.0", - "MSXML2.XMLHTTP", - "Microsoft.XMLHTTP" - ]; - -// Public methods - /** - * Look up the loaded result. - * @method getResult - * @param {Boolean} [raw=false] Return a raw result instead of a formatted result. This applies to content - * loaded via XHR such as scripts, XML, CSS, and Images. If there is no raw result, the formatted result will be - * returned instead. - * @return {Object} A result object containing the content that was loaded, such as: - *
    - *
  • An image tag (<image />) for images
  • - *
  • A script tag for JavaScript (<script />). Note that scripts loaded with tags may be added to the - * HTML head.
  • - *
  • A style tag for CSS (<style />)
  • - *
  • Raw text for TEXT
  • - *
  • A formatted JavaScript object defined by JSON
  • - *
  • An XML document
  • - *
  • An binary arraybuffer loaded by XHR
  • - *
- * Note that if a raw result is requested, but not found, the result will be returned instead. - */ - p.getResult = function (raw) { - if (raw && this._rawResponse) { - return this._rawResponse; - } - return this._response; - }; - - // Overrides abstract method in AbstractRequest - p.cancel = function () { - this.canceled = true; - this._clean(); - this._request.abort(); - }; - - // Overrides abstract method in AbstractLoader - p.load = function () { - if (this._request == null) { - this._handleError(); - return; - } - - //Events - this._request.addEventListener("loadstart", this._handleLoadStartProxy, false); - this._request.addEventListener("progress", this._handleProgressProxy, false); - this._request.addEventListener("abort", this._handleAbortProxy, false); - this._request.addEventListener("error",this._handleErrorProxy, false); - this._request.addEventListener("timeout", this._handleTimeoutProxy, false); - - // Note: We don't get onload in all browsers (earlier FF and IE). onReadyStateChange handles these. - this._request.addEventListener("load", this._handleLoadProxy, false); - this._request.addEventListener("readystatechange", this._handleReadyStateChangeProxy, false); - - // Set up a timeout if we don't have XHR2 - if (this._xhrLevel == 1) { - this._loadTimeout = setTimeout(createjs.proxy(this._handleTimeout, this), this._item.loadTimeout); - } - - // Sometimes we get back 404s immediately, particularly when there is a cross origin request. // note this does not catch in Chrome - try { - if (!this._item.values || this._item.method == createjs.AbstractLoader.GET) { - this._request.send(); - } else if (this._item.method == createjs.AbstractLoader.POST) { - this._request.send(createjs.RequestUtils.formatQueryString(this._item.values)); - } - } catch (error) { - this.dispatchEvent(new createjs.ErrorEvent("XHR_SEND", null, error)); - } - }; - - p.setResponseType = function (type) { - this._request.responseType = type; - }; - - /** - * Get all the response headers from the XmlHttpRequest. - * - * From the docs: Return all the HTTP headers, excluding headers that are a case-insensitive match - * for Set-Cookie or Set-Cookie2, as a single string, with each header line separated by a U+000D CR U+000A LF pair, - * excluding the status line, and with each header name and header value separated by a U+003A COLON U+0020 SPACE - * pair. - * @method getAllResponseHeaders - * @return {String} - * @since 0.4.1 - */ - p.getAllResponseHeaders = function () { - if (this._request.getAllResponseHeaders instanceof Function) { - return this._request.getAllResponseHeaders(); - } else { - return null; - } - }; - - /** - * Get a specific response header from the XmlHttpRequest. - * - * From the docs: Returns the header field value from the response of which the field name matches - * header, unless the field name is Set-Cookie or Set-Cookie2. - * @method getResponseHeader - * @param {String} header The header name to retrieve. - * @return {String} - * @since 0.4.1 - */ - p.getResponseHeader = function (header) { - if (this._request.getResponseHeader instanceof Function) { - return this._request.getResponseHeader(header); - } else { - return null; - } - }; - -// protected methods - /** - * The XHR request has reported progress. - * @method _handleProgress - * @param {Object} event The XHR progress event. - * @private - */ - p._handleProgress = function (event) { - if (!event || event.loaded > 0 && event.total == 0) { - return; // Sometimes we get no "total", so just ignore the progress event. - } - - var newEvent = new createjs.ProgressEvent(event.loaded, event.total); - this.dispatchEvent(newEvent); - }; - - /** - * The XHR request has reported a load start. - * @method _handleLoadStart - * @param {Object} event The XHR loadStart event. - * @private - */ - p._handleLoadStart = function (event) { - clearTimeout(this._loadTimeout); - this.dispatchEvent("loadstart"); - }; - - /** - * The XHR request has reported an abort event. - * @method handleAbort - * @param {Object} event The XHR abort event. - * @private - */ - p._handleAbort = function (event) { - this._clean(); - this.dispatchEvent(new createjs.ErrorEvent("XHR_ABORTED", null, event)); - }; - - /** - * The XHR request has reported an error event. - * @method _handleError - * @param {Object} event The XHR error event. - * @private - */ - p._handleError = function (event) { - this._clean(); - this.dispatchEvent(new createjs.ErrorEvent(event.message)); - }; - - /** - * The XHR request has reported a readyState change. Note that older browsers (IE 7 & 8) do not provide an onload - * event, so we must monitor the readyStateChange to determine if the file is loaded. - * @method _handleReadyStateChange - * @param {Object} event The XHR readyStateChange event. - * @private - */ - p._handleReadyStateChange = function (event) { - if (this._request.readyState == 4) { - this._handleLoad(); - } - }; - - /** - * The XHR request has completed. This is called by the XHR request directly, or by a readyStateChange that has - * request.readyState == 4. Only the first call to this method will be processed. - * @method _handleLoad - * @param {Object} event The XHR load event. - * @private - */ - p._handleLoad = function (event) { - if (this.loaded) { - return; - } - this.loaded = true; - - var error = this._checkError(); - if (error) { - this._handleError(error); - return; - } - - this._response = this._getResponse(); - this._clean(); - - this.dispatchEvent(new createjs.Event("complete")); - }; - - /** - * The XHR request has timed out. This is called by the XHR request directly, or via a setTimeout - * callback. - * @method _handleTimeout - * @param {Object} [event] The XHR timeout event. This is occasionally null when called by the backup setTimeout. - * @private - */ - p._handleTimeout = function (event) { - this._clean(); - - this.dispatchEvent(new createjs.ErrorEvent("PRELOAD_TIMEOUT", null, event)); - }; - -// Protected - /** - * Determine if there is an error in the current load. This checks the status of the request for problem codes. Note - * that this does not check for an actual response. Currently, it only checks for 404 or 0 error code. - * @method _checkError - * @return {int} If the request status returns an error code. - * @private - */ - p._checkError = function () { - //LM: Probably need additional handlers here, maybe 501 - var status = parseInt(this._request.status); - - switch (status) { - case 404: // Not Found - case 0: // Not Loaded - return new Error(status); - } - return null; - }; - - /** - * Validate the response. Different browsers have different approaches, some of which throw errors when accessed - * in other browsers. If there is no response, the _response property will remain null. - * @method _getResponse - * @private - */ - p._getResponse = function () { - if (this._response != null) { - return this._response; - } - - if (this._request.response != null) { - return this._request.response; - } - - // Android 2.2 uses .responseText - try { - if (this._request.responseText != null) { - return this._request.responseText; - } - } catch (e) { - } - - // When loading XML, IE9 does not return .response, instead it returns responseXML.xml - try { - if (this._request.responseXML != null) { - return this._request.responseXML; - } - } catch (e) { - } - - return null; - }; - - /** - * Create an XHR request. Depending on a number of factors, we get totally different results. - *
  1. Some browsers get an XDomainRequest when loading cross-domain.
  2. - *
  3. XMLHttpRequest are created when available.
  4. - *
  5. ActiveX.XMLHTTP objects are used in older IE browsers.
  6. - *
  7. Text requests override the mime type if possible
  8. - *
  9. Origin headers are sent for crossdomain requests in some browsers.
  10. - *
  11. Binary loads set the response type to "arraybuffer"
- * @method _createXHR - * @param {Object} item The requested item that is being loaded. - * @return {Boolean} If an XHR request or equivalent was successfully created. - * @private - */ - p._createXHR = function (item) { - // Check for cross-domain loads. We can't fully support them, but we can try. - var crossdomain = createjs.RequestUtils.isCrossDomain(item); - var headers = {}; - - // Create the request. Fallback to whatever support we have. - var req = null; - if (window.XMLHttpRequest) { - req = new XMLHttpRequest(); - // This is 8 or 9, so use XDomainRequest instead. - if (crossdomain && req.withCredentials === undefined && window.XDomainRequest) { - req = new XDomainRequest(); - } - } else { // Old IE versions use a different approach - for (var i = 0, l = s.ACTIVEX_VERSIONS.length; i < l; i++) { - var axVersion = s.ACTIVEX_VERSIONS[i]; - try { - req = new ActiveXObject(axVersions); - break; - } catch (e) {} - } - if (req == null) { return false; } - } - - // Default to utf-8 for Text requests. - if (item.mimeType == null && createjs.RequestUtils.isText(item.type)) { - item.mimeType = "text/plain; charset=utf-8"; - } - - // IE9 doesn't support overrideMimeType(), so we need to check for it. - if (item.mimeType && req.overrideMimeType) { - req.overrideMimeType(item.mimeType); - } - - // Determine the XHR level - this._xhrLevel = (typeof req.responseType === "string") ? 2 : 1; - - var src = null; - if (item.method == createjs.AbstractLoader.GET) { - src = createjs.RequestUtils.buildPath(item.src, item.values); - } else { - src = item.src; - } - - // Open the request. Set cross-domain flags if it is supported (XHR level 1 only) - req.open(item.method || createjs.AbstractLoader.GET, src, true); - - if (crossdomain && req instanceof XMLHttpRequest && this._xhrLevel == 1) { - headers["Origin"] = location.origin; - } - - // To send data we need to set the Content-type header) - if (item.values && item.method == createjs.AbstractLoader.POST) { - headers["Content-Type"] = "application/x-www-form-urlencoded"; - } - - if (!crossdomain && !headers["X-Requested-With"]) { - headers["X-Requested-With"] = "XMLHttpRequest"; - } - - if (item.headers) { - for (var n in item.headers) { - headers[n] = item.headers[n]; - } - } - - for (n in headers) { - req.setRequestHeader(n, headers[n]) - } - - if (req instanceof XMLHttpRequest && item.withCredentials !== undefined) { - req.withCredentials = item.withCredentials; - } - - this._request = req; - - return true; - }; - - /** - * A request has completed (or failed or canceled), and needs to be disposed. - * @method _clean - * @private - */ - p._clean = function () { - clearTimeout(this._loadTimeout); - - this._request.removeEventListener("loadstart", this._handleLoadStartProxy); - this._request.removeEventListener("progress", this._handleProgressProxy); - this._request.removeEventListener("abort", this._handleAbortProxy); - this._request.removeEventListener("error",this._handleErrorProxy); - this._request.removeEventListener("timeout", this._handleTimeoutProxy); - this._request.removeEventListener("load", this._handleLoadProxy); - this._request.removeEventListener("readystatechange", this._handleReadyStateChangeProxy); - }; - - p.toString = function () { - return "[PreloadJS XHRRequest]"; - }; - - createjs.XHRRequest = createjs.promote(XHRRequest, "AbstractRequest"); - +this.createjs = this.createjs || {}; + +(function () { + "use strict"; + +// constructor + /** + * A preloader that loads items using XHR requests, usually XMLHttpRequest. However XDomainRequests will be used + * for cross-domain requests if possible, and older versions of IE fall back on to ActiveX objects when necessary. + * XHR requests load the content as text or binary data, provide progress and consistent completion events, and + * can be canceled during load. Note that XHR is not supported in IE 6 or earlier, and is not recommended for + * cross-domain loading. + * @class XHRRequest + * @constructor + * @param {Object} item The object that defines the file to load. Please see the {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} + * for an overview of supported file properties. + * @extends AbstractLoader + */ + function XHRRequest(item) { + this.AbstractRequest_constructor(item); + + // protected properties + /** + * A reference to the XHR request used to load the content. + * @property _request + * @type {XMLHttpRequest | XDomainRequest | ActiveX.XMLHTTP} + * @private + */ + this._request = null; + + /** + * A manual load timeout that is used for browsers that do not support the onTimeout event on XHR (XHR level 1, + * typically IE9). + * @property _loadTimeout + * @type {Number} + * @private + */ + this._loadTimeout = null; + + /** + * The browser's XHR (XMLHTTPRequest) version. Supported versions are 1 and 2. There is no official way to detect + * the version, so we use capabilities to make a best guess. + * @property _xhrLevel + * @type {Number} + * @default 1 + * @private + */ + this._xhrLevel = 1; + + /** + * The response of a loaded file. This is set because it is expensive to look up constantly. This property will be + * null until the file is loaded. + * @property _response + * @type {mixed} + * @private + */ + this._response = null; + + /** + * The response of the loaded file before it is modified. In most cases, content is converted from raw text to + * an HTML tag or a formatted object which is set to the result property, but the developer may still + * want to access the raw content as it was loaded. + * @property _rawResponse + * @type {String|Object} + * @private + */ + this._rawResponse = null; + + this._canceled = false; + + // Setup our event handlers now. + this._handleLoadStartProxy = createjs.proxy(this._handleLoadStart, this); + this._handleProgressProxy = createjs.proxy(this._handleProgress, this); + this._handleAbortProxy = createjs.proxy(this._handleAbort, this); + this._handleErrorProxy = createjs.proxy(this._handleError, this); + this._handleTimeoutProxy = createjs.proxy(this._handleTimeout, this); + this._handleLoadProxy = createjs.proxy(this._handleLoad, this); + this._handleReadyStateChangeProxy = createjs.proxy(this._handleReadyStateChange, this); + + if (!this._createXHR(item)) { + //TODO: Throw error? + } + }; + + var p = createjs.extend(XHRRequest, createjs.AbstractRequest); + +// static properties + /** + * A list of XMLHTTP object IDs to try when building an ActiveX object for XHR requests in earlier versions of IE. + * @property ACTIVEX_VERSIONS + * @type {Array} + * @since 0.4.2 + * @private + */ + XHRRequest.ACTIVEX_VERSIONS = [ + "Msxml2.XMLHTTP.6.0", + "Msxml2.XMLHTTP.5.0", + "Msxml2.XMLHTTP.4.0", + "MSXML2.XMLHTTP.3.0", + "MSXML2.XMLHTTP", + "Microsoft.XMLHTTP" + ]; + +// Public methods + /** + * Look up the loaded result. + * @method getResult + * @param {Boolean} [raw=false] Return a raw result instead of a formatted result. This applies to content + * loaded via XHR such as scripts, XML, CSS, and Images. If there is no raw result, the formatted result will be + * returned instead. + * @return {Object} A result object containing the content that was loaded, such as: + *
    + *
  • An image tag (<image />) for images
  • + *
  • A script tag for JavaScript (<script />). Note that scripts loaded with tags may be added to the + * HTML head.
  • + *
  • A style tag for CSS (<style />)
  • + *
  • Raw text for TEXT
  • + *
  • A formatted JavaScript object defined by JSON
  • + *
  • An XML document
  • + *
  • An binary arraybuffer loaded by XHR
  • + *
+ * Note that if a raw result is requested, but not found, the result will be returned instead. + */ + p.getResult = function (raw) { + if (raw && this._rawResponse) { + return this._rawResponse; + } + return this._response; + }; + + // Overrides abstract method in AbstractRequest + p.cancel = function () { + this.canceled = true; + this._clean(); + this._request.abort(); + }; + + // Overrides abstract method in AbstractLoader + p.load = function () { + if (this._request == null) { + this._handleError(); + return; + } + + //Events + this._request.addEventListener("loadstart", this._handleLoadStartProxy, false); + this._request.addEventListener("progress", this._handleProgressProxy, false); + this._request.addEventListener("abort", this._handleAbortProxy, false); + this._request.addEventListener("error",this._handleErrorProxy, false); + this._request.addEventListener("timeout", this._handleTimeoutProxy, false); + + // Note: We don't get onload in all browsers (earlier FF and IE). onReadyStateChange handles these. + this._request.addEventListener("load", this._handleLoadProxy, false); + this._request.addEventListener("readystatechange", this._handleReadyStateChangeProxy, false); + + // Set up a timeout if we don't have XHR2 + if (this._xhrLevel == 1) { + this._loadTimeout = setTimeout(createjs.proxy(this._handleTimeout, this), this._item.loadTimeout); + } + + // Sometimes we get back 404s immediately, particularly when there is a cross origin request. // note this does not catch in Chrome + try { + if (!this._item.values || this._item.method == createjs.AbstractLoader.GET) { + this._request.send(); + } else if (this._item.method == createjs.AbstractLoader.POST) { + this._request.send(createjs.RequestUtils.formatQueryString(this._item.values)); + } + } catch (error) { + this.dispatchEvent(new createjs.ErrorEvent("XHR_SEND", null, error)); + } + }; + + p.setResponseType = function (type) { + this._request.responseType = type; + }; + + /** + * Get all the response headers from the XmlHttpRequest. + * + * From the docs: Return all the HTTP headers, excluding headers that are a case-insensitive match + * for Set-Cookie or Set-Cookie2, as a single string, with each header line separated by a U+000D CR U+000A LF pair, + * excluding the status line, and with each header name and header value separated by a U+003A COLON U+0020 SPACE + * pair. + * @method getAllResponseHeaders + * @return {String} + * @since 0.4.1 + */ + p.getAllResponseHeaders = function () { + if (this._request.getAllResponseHeaders instanceof Function) { + return this._request.getAllResponseHeaders(); + } else { + return null; + } + }; + + /** + * Get a specific response header from the XmlHttpRequest. + * + * From the docs: Returns the header field value from the response of which the field name matches + * header, unless the field name is Set-Cookie or Set-Cookie2. + * @method getResponseHeader + * @param {String} header The header name to retrieve. + * @return {String} + * @since 0.4.1 + */ + p.getResponseHeader = function (header) { + if (this._request.getResponseHeader instanceof Function) { + return this._request.getResponseHeader(header); + } else { + return null; + } + }; + +// protected methods + /** + * The XHR request has reported progress. + * @method _handleProgress + * @param {Object} event The XHR progress event. + * @private + */ + p._handleProgress = function (event) { + if (!event || event.loaded > 0 && event.total == 0) { + return; // Sometimes we get no "total", so just ignore the progress event. + } + + var newEvent = new createjs.ProgressEvent(event.loaded, event.total); + this.dispatchEvent(newEvent); + }; + + /** + * The XHR request has reported a load start. + * @method _handleLoadStart + * @param {Object} event The XHR loadStart event. + * @private + */ + p._handleLoadStart = function (event) { + clearTimeout(this._loadTimeout); + this.dispatchEvent("loadstart"); + }; + + /** + * The XHR request has reported an abort event. + * @method handleAbort + * @param {Object} event The XHR abort event. + * @private + */ + p._handleAbort = function (event) { + this._clean(); + this.dispatchEvent(new createjs.ErrorEvent("XHR_ABORTED", null, event)); + }; + + /** + * The XHR request has reported an error event. + * @method _handleError + * @param {Object} event The XHR error event. + * @private + */ + p._handleError = function (event) { + this._clean(); + this.dispatchEvent(new createjs.ErrorEvent(event.message)); + }; + + /** + * The XHR request has reported a readyState change. Note that older browsers (IE 7 & 8) do not provide an onload + * event, so we must monitor the readyStateChange to determine if the file is loaded. + * @method _handleReadyStateChange + * @param {Object} event The XHR readyStateChange event. + * @private + */ + p._handleReadyStateChange = function (event) { + if (this._request.readyState == 4) { + this._handleLoad(); + } + }; + + /** + * The XHR request has completed. This is called by the XHR request directly, or by a readyStateChange that has + * request.readyState == 4. Only the first call to this method will be processed. + * @method _handleLoad + * @param {Object} event The XHR load event. + * @private + */ + p._handleLoad = function (event) { + if (this.loaded) { + return; + } + this.loaded = true; + + var error = this._checkError(); + if (error) { + this._handleError(error); + return; + } + + this._response = this._getResponse(); + this._clean(); + + this.dispatchEvent(new createjs.Event("complete")); + }; + + /** + * The XHR request has timed out. This is called by the XHR request directly, or via a setTimeout + * callback. + * @method _handleTimeout + * @param {Object} [event] The XHR timeout event. This is occasionally null when called by the backup setTimeout. + * @private + */ + p._handleTimeout = function (event) { + this._clean(); + + this.dispatchEvent(new createjs.ErrorEvent("PRELOAD_TIMEOUT", null, event)); + }; + +// Protected + /** + * Determine if there is an error in the current load. This checks the status of the request for problem codes. Note + * that this does not check for an actual response. Currently, it only checks for 404 or 0 error code. + * @method _checkError + * @return {int} If the request status returns an error code. + * @private + */ + p._checkError = function () { + //LM: Probably need additional handlers here, maybe 501 + var status = parseInt(this._request.status); + + switch (status) { + case 404: // Not Found + case 0: // Not Loaded + return new Error(status); + } + return null; + }; + + /** + * Validate the response. Different browsers have different approaches, some of which throw errors when accessed + * in other browsers. If there is no response, the _response property will remain null. + * @method _getResponse + * @private + */ + p._getResponse = function () { + if (this._response != null) { + return this._response; + } + + if (this._request.response != null) { + return this._request.response; + } + + // Android 2.2 uses .responseText + try { + if (this._request.responseText != null) { + return this._request.responseText; + } + } catch (e) { + } + + // When loading XML, IE9 does not return .response, instead it returns responseXML.xml + try { + if (this._request.responseXML != null) { + return this._request.responseXML; + } + } catch (e) { + } + + return null; + }; + + /** + * Create an XHR request. Depending on a number of factors, we get totally different results. + *
  1. Some browsers get an XDomainRequest when loading cross-domain.
  2. + *
  3. XMLHttpRequest are created when available.
  4. + *
  5. ActiveX.XMLHTTP objects are used in older IE browsers.
  6. + *
  7. Text requests override the mime type if possible
  8. + *
  9. Origin headers are sent for crossdomain requests in some browsers.
  10. + *
  11. Binary loads set the response type to "arraybuffer"
+ * @method _createXHR + * @param {Object} item The requested item that is being loaded. + * @return {Boolean} If an XHR request or equivalent was successfully created. + * @private + */ + p._createXHR = function (item) { + // Check for cross-domain loads. We can't fully support them, but we can try. + var crossdomain = createjs.RequestUtils.isCrossDomain(item); + var headers = {}; + + // Create the request. Fallback to whatever support we have. + var req = null; + if (window.XMLHttpRequest) { + req = new XMLHttpRequest(); + // This is 8 or 9, so use XDomainRequest instead. + if (crossdomain && req.withCredentials === undefined && window.XDomainRequest) { + req = new XDomainRequest(); + } + } else { // Old IE versions use a different approach + for (var i = 0, l = s.ACTIVEX_VERSIONS.length; i < l; i++) { + var axVersion = s.ACTIVEX_VERSIONS[i]; + try { + req = new ActiveXObject(axVersions); + break; + } catch (e) {} + } + if (req == null) { return false; } + } + + // Default to utf-8 for Text requests. + if (item.mimeType == null && createjs.RequestUtils.isText(item.type)) { + item.mimeType = "text/plain; charset=utf-8"; + } + + // IE9 doesn't support overrideMimeType(), so we need to check for it. + if (item.mimeType && req.overrideMimeType) { + req.overrideMimeType(item.mimeType); + } + + // Determine the XHR level + this._xhrLevel = (typeof req.responseType === "string") ? 2 : 1; + + var src = null; + if (item.method == createjs.AbstractLoader.GET) { + src = createjs.RequestUtils.buildPath(item.src, item.values); + } else { + src = item.src; + } + + // Open the request. Set cross-domain flags if it is supported (XHR level 1 only) + req.open(item.method || createjs.AbstractLoader.GET, src, true); + + if (crossdomain && req instanceof XMLHttpRequest && this._xhrLevel == 1) { + headers["Origin"] = location.origin; + } + + // To send data we need to set the Content-type header) + if (item.values && item.method == createjs.AbstractLoader.POST) { + headers["Content-Type"] = "application/x-www-form-urlencoded"; + } + + if (!crossdomain && !headers["X-Requested-With"]) { + headers["X-Requested-With"] = "XMLHttpRequest"; + } + + if (item.headers) { + for (var n in item.headers) { + headers[n] = item.headers[n]; + } + } + + for (n in headers) { + req.setRequestHeader(n, headers[n]) + } + + if (req instanceof XMLHttpRequest && item.withCredentials !== undefined) { + req.withCredentials = item.withCredentials; + } + + this._request = req; + + return true; + }; + + /** + * A request has completed (or failed or canceled), and needs to be disposed. + * @method _clean + * @private + */ + p._clean = function () { + clearTimeout(this._loadTimeout); + + this._request.removeEventListener("loadstart", this._handleLoadStartProxy); + this._request.removeEventListener("progress", this._handleProgressProxy); + this._request.removeEventListener("abort", this._handleAbortProxy); + this._request.removeEventListener("error",this._handleErrorProxy); + this._request.removeEventListener("timeout", this._handleTimeoutProxy); + this._request.removeEventListener("load", this._handleLoadProxy); + this._request.removeEventListener("readystatechange", this._handleReadyStateChangeProxy); + }; + + p.toString = function () { + return "[PreloadJS XHRRequest]"; + }; + + createjs.XHRRequest = createjs.promote(XHRRequest, "AbstractRequest"); + }()); //############################################################################## // SoundLoader.js //############################################################################## -this.createjs = this.createjs || {}; - -(function () { - "use strict"; - - // constructor - /** - * A loader for HTML audio files. PreloadJS can not load WebAudio files, as a WebAudio context is required, which - * should be created by either a library playing the sound (such as SoundJS, or an - * external framework that handles audio playback. To load content that can be played by WebAudio, use the - * {{#crossLink "BinaryLoader"}}{{/crossLink}}, and handle the audio context decoding manually. - * @class SoundLoader - * @param {LoadItem|Object} loadItem - * @param {Boolean} preferXHR - * @extends AbstractMediaLoader - * @constructor - */ - function SoundLoader(loadItem, preferXHR) { - this.AbstractMediaLoader_constructor(loadItem, preferXHR, createjs.AbstractLoader.SOUND); - - // protected properties - if (createjs.RequestUtils.isAudioTag(loadItem)) { - this._tag = loadItem; - } else if (createjs.RequestUtils.isAudioTag(loadItem.src)) { - this._tag = loadItem; - } else if (createjs.RequestUtils.isAudioTag(loadItem.tag)) { - this._tag = createjs.RequestUtils.isAudioTag(loadItem) ? loadItem : loadItem.src; - } - - if (this._tag != null) { - this._preferXHR = false; - } - }; - - var p = createjs.extend(SoundLoader, createjs.AbstractMediaLoader); - var s = SoundLoader; - - // static methods - /** - * Determines if the loader can load a specific item. This loader can only load items that are of type - * {{#crossLink "AbstractLoader/SOUND:property"}}{{/crossLink}}. - * @method canLoadItem - * @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load. - * @returns {Boolean} Whether the loader can load the item. - * @static - */ - s.canLoadItem = function (item) { - return item.type == createjs.AbstractLoader.SOUND; - }; - - // protected methods - p._createTag = function (src) { - var tag = document.createElement("audio"); - tag.autoplay = false; - tag.preload = "none"; - - //LM: Firefox fails when this the preload="none" for other tags, but it needs to be "none" to ensure PreloadJS works. - tag.src = src; - return tag; - }; - - createjs.SoundLoader = createjs.promote(SoundLoader, "AbstractMediaLoader"); - +this.createjs = this.createjs || {}; + +(function () { + "use strict"; + + // constructor + /** + * A loader for HTML audio files. PreloadJS can not load WebAudio files, as a WebAudio context is required, which + * should be created by either a library playing the sound (such as SoundJS, or an + * external framework that handles audio playback. To load content that can be played by WebAudio, use the + * {{#crossLink "BinaryLoader"}}{{/crossLink}}, and handle the audio context decoding manually. + * @class SoundLoader + * @param {LoadItem|Object} loadItem + * @param {Boolean} preferXHR + * @extends AbstractMediaLoader + * @constructor + */ + function SoundLoader(loadItem, preferXHR) { + this.AbstractMediaLoader_constructor(loadItem, preferXHR, createjs.AbstractLoader.SOUND); + + // protected properties + if (createjs.RequestUtils.isAudioTag(loadItem)) { + this._tag = loadItem; + } else if (createjs.RequestUtils.isAudioTag(loadItem.src)) { + this._tag = loadItem; + } else if (createjs.RequestUtils.isAudioTag(loadItem.tag)) { + this._tag = createjs.RequestUtils.isAudioTag(loadItem) ? loadItem : loadItem.src; + } + + if (this._tag != null) { + this._preferXHR = false; + } + }; + + var p = createjs.extend(SoundLoader, createjs.AbstractMediaLoader); + var s = SoundLoader; + + // static methods + /** + * Determines if the loader can load a specific item. This loader can only load items that are of type + * {{#crossLink "AbstractLoader/SOUND:property"}}{{/crossLink}}. + * @method canLoadItem + * @param {LoadItem|Object} item The LoadItem that a LoadQueue is trying to load. + * @returns {Boolean} Whether the loader can load the item. + * @static + */ + s.canLoadItem = function (item) { + return item.type == createjs.AbstractLoader.SOUND; + }; + + // protected methods + p._createTag = function (src) { + var tag = document.createElement("audio"); + tag.autoplay = false; + tag.preload = "none"; + + //LM: Firefox fails when this the preload="none" for other tags, but it needs to be "none" to ensure PreloadJS works. + tag.src = src; + return tag; + }; + + createjs.SoundLoader = createjs.promote(SoundLoader, "AbstractMediaLoader"); + }()); //############################################################################## // AudioSprite.js //############################################################################## -// NOTE this is "Class" is purely to document audioSprite Setup and usage. - - -/** - * Note: AudioSprite is not a class, but its usage is easily lost in the documentation, so it has been called - * out here for quick reference. - * - * Audio sprites are much like CSS sprites or image sprite sheets: multiple audio assets grouped into a single file. - * Audio sprites work around limitations in certain browsers, where only a single sound can be loaded and played at a - * time. We recommend at least 300ms of silence between audio clips to deal with HTML audio tag inaccuracy, and to prevent - * accidentally playing bits of the neighbouring clips. - * - * Benefits of Audio Sprites: - *
    - *
  • More robust support for older browsers and devices that only allow a single audio instance, such as iOS 5.
  • - *
  • They provide a work around for the Internet Explorer 9 audio tag limit, which restricts how many different - * sounds that could be loaded at once.
  • - *
  • Faster loading by only requiring a single network request for several sounds, especially on mobile devices - * where the network round trip for each file can add significant latency.
  • - *
- * - * Drawbacks of Audio Sprites - *
    - *
  • No guarantee of smooth looping when using HTML or Flash audio. If you have a track that needs to loop - * smoothly and you are supporting non-web audio browsers, do not use audio sprites for that sound if you can avoid - * it.
  • - *
  • No guarantee that HTML audio will play back immediately, especially the first time. In some browsers - * (Chrome!), HTML audio will only load enough to play through at the current download speed – so we rely on the - * `canplaythrough` event to determine if the audio is loaded. Since audio sprites must jump ahead to play specific - * sounds, the audio may not yet have downloaded fully.
  • - *
  • Audio sprites share the same core source, so if you have a sprite with 5 sounds and are limited to 2 - * concurrently playing instances, you can only play 2 of the sounds at the same time.
  • - *
- * - *

Example

- * - * createjs.Sound.initializeDefaultPlugins(); - * var assetsPath = "./assets/"; - * var sounds = [{ - * src:"MyAudioSprite.ogg", data: { - * audioSprite: [ - * {id:"sound1", startTime:0, duration:500}, - * {id:"sound2", startTime:1000, duration:400}, - * {id:"sound3", startTime:1700, duration: 1000} - * ]} - * } - * ]; - * createjs.Sound.alternateExtensions = ["mp3"]; - * createjs.Sound.on("fileload", loadSound); - * createjs.Sound.registerSounds(sounds, assetsPath); - * // after load is complete - * createjs.Sound.play("sound2"); - * - * You can also create audio sprites on the fly by setting the startTime and duration when creating an new AbstractSoundInstance. - * - * createjs.Sound.play("MyAudioSprite", {startTime: 1000, duration: 400}); - * - * The excellent CreateJS community has created a tool to create audio sprites, available at - * https://github.com/tonistiigi/audiosprite, - * as well as a jsfiddle to convert the output - * to SoundJS format. - * - * @class AudioSprite - * @since 0.6.0 +// NOTE this is "Class" is purely to document audioSprite Setup and usage. + + +/** + * Note: AudioSprite is not a class, but its usage is easily lost in the documentation, so it has been called + * out here for quick reference. + * + * Audio sprites are much like CSS sprites or image sprite sheets: multiple audio assets grouped into a single file. + * Audio sprites work around limitations in certain browsers, where only a single sound can be loaded and played at a + * time. We recommend at least 300ms of silence between audio clips to deal with HTML audio tag inaccuracy, and to prevent + * accidentally playing bits of the neighbouring clips. + * + * Benefits of Audio Sprites: + *
    + *
  • More robust support for older browsers and devices that only allow a single audio instance, such as iOS 5.
  • + *
  • They provide a work around for the Internet Explorer 9 audio tag limit, which restricts how many different + * sounds that could be loaded at once.
  • + *
  • Faster loading by only requiring a single network request for several sounds, especially on mobile devices + * where the network round trip for each file can add significant latency.
  • + *
+ * + * Drawbacks of Audio Sprites + *
    + *
  • No guarantee of smooth looping when using HTML or Flash audio. If you have a track that needs to loop + * smoothly and you are supporting non-web audio browsers, do not use audio sprites for that sound if you can avoid + * it.
  • + *
  • No guarantee that HTML audio will play back immediately, especially the first time. In some browsers + * (Chrome!), HTML audio will only load enough to play through at the current download speed – so we rely on the + * `canplaythrough` event to determine if the audio is loaded. Since audio sprites must jump ahead to play specific + * sounds, the audio may not yet have downloaded fully.
  • + *
  • Audio sprites share the same core source, so if you have a sprite with 5 sounds and are limited to 2 + * concurrently playing instances, you can only play 2 of the sounds at the same time.
  • + *
+ * + *

Example

+ * + * createjs.Sound.initializeDefaultPlugins(); + * var assetsPath = "./assets/"; + * var sounds = [{ + * src:"MyAudioSprite.ogg", data: { + * audioSprite: [ + * {id:"sound1", startTime:0, duration:500}, + * {id:"sound2", startTime:1000, duration:400}, + * {id:"sound3", startTime:1700, duration: 1000} + * ]} + * } + * ]; + * createjs.Sound.alternateExtensions = ["mp3"]; + * createjs.Sound.on("fileload", loadSound); + * createjs.Sound.registerSounds(sounds, assetsPath); + * // after load is complete + * createjs.Sound.play("sound2"); + * + * You can also create audio sprites on the fly by setting the startTime and duration when creating an new AbstractSoundInstance. + * + * createjs.Sound.play("MyAudioSprite", {startTime: 1000, duration: 400}); + * + * The excellent CreateJS community has created a tool to create audio sprites, available at + * https://github.com/tonistiigi/audiosprite, + * as well as a jsfiddle to convert the output + * to SoundJS format. + * + * @class AudioSprite + * @since 0.6.0 */ //############################################################################## // PlayPropsConfig.js //############################################################################## -this.createjs = this.createjs || {}; - -(function () { - "use strict"; - /** - * A class to store the optional play properties passed in {{#crossLink "Sound/play"}}{{/crossLink}} and - * {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}} calls. - * - * Optional Play Properties Include: - *
    - *
  • interrupt - How to interrupt any currently playing instances of audio with the same source, - * if the maximum number of instances of the sound are already playing. Values are defined as INTERRUPT_TYPE - * constants on the Sound class, with the default defined by {{#crossLink "Sound/defaultInterruptBehavior:property"}}{{/crossLink}}.
  • - *
  • delay - The amount of time to delay the start of audio playback, in milliseconds.
  • - *
  • offset - The offset from the start of the audio to begin playback, in milliseconds.
  • - *
  • loop - How many times the audio loops when it reaches the end of playback. The default is 0 (no - * loops), and -1 can be used for infinite playback.
  • - *
  • volume - The volume of the sound, between 0 and 1. Note that the master volume is applied - * against the individual volume.
  • - *
  • pan - The left-right pan of the sound (if supported), between -1 (left) and 1 (right).
  • - *
  • startTime - To create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds.
  • - *
  • duration - To create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds.
  • - *
- * - *

Example

- * - * var ppc = new createjs.PlayPropsConfig().set({interrupt: createjs.Sound.INTERRUPT_ANY, loop: -1, volume: 0.5}) - * createjs.Sound.play("mySound", ppc); - * mySoundInstance.play(ppc); - * - * @class PlayPropsConfig - * @constructor - * @since 0.6.1 - */ - // TODO think of a better name for this class - var PlayPropsConfig = function () { -// Public Properties - /** - * How to interrupt any currently playing instances of audio with the same source, - * if the maximum number of instances of the sound are already playing. Values are defined as - * INTERRUPT_TYPE constants on the Sound class, with the default defined by - * {{#crossLink "Sound/defaultInterruptBehavior:property"}}{{/crossLink}}. - * @property interrupt - * @type {string} - * @default null - */ - this.interrupt = null; - - /** - * The amount of time to delay the start of audio playback, in milliseconds. - * @property delay - * @type {Number} - * @default null - */ - this.delay = null; - - /** - * The offset from the start of the audio to begin playback, in milliseconds. - * @property offset - * @type {number} - * @default null - */ - this.offset = null; - - /** - * How many times the audio loops when it reaches the end of playback. The default is 0 (no - * loops), and -1 can be used for infinite playback. - * @property loop - * @type {number} - * @default null - */ - this.loop = null; - - /** - * The volume of the sound, between 0 and 1. Note that the master volume is applied - * against the individual volume. - * @property volume - * @type {number} - * @default null - */ - this.volume = null; - - /** - * The left-right pan of the sound (if supported), between -1 (left) and 1 (right). - * @property pan - * @type {number} - * @default null - */ - this.pan = null; - - /** - * Used to create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds. - * @property startTime - * @type {number} - * @default null - */ - this.startTime = null; - - /** - * Used to create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds. - * @property duration - * @type {number} - * @default null - */ - this.duration = null; - }; - var p = PlayPropsConfig.prototype = {}; - var s = PlayPropsConfig; - - -// Static Methods - /** - * Creates a PlayPropsConfig from another PlayPropsConfig or an Object. - * - * @method create - * @param {PlayPropsConfig|Object} value The play properties - * @returns {PlayPropsConfig} - * @static - */ - s.create = function (value) { - if (value instanceof s || value instanceof Object) { - var ppc = new createjs.PlayPropsConfig(); - ppc.set(value); - return ppc; - } else { - throw new Error("Type not recognized."); - } - }; - -// Public Methods - /** - * Provides a chainable shortcut method for setting a number of properties on the instance. - * - *

Example

- * - * var PlayPropsConfig = new createjs.PlayPropsConfig().set({loop:-1, volume:0.7}); - * - * @method set - * @param {Object} props A generic object containing properties to copy to the PlayPropsConfig instance. - * @return {PlayPropsConfig} Returns the instance the method is called on (useful for chaining calls.) - */ - p.set = function(props) { - for (var n in props) { this[n] = props[n]; } - return this; - }; - - p.toString = function() { - return "[PlayPropsConfig]"; - }; - - createjs.PlayPropsConfig = s; - +this.createjs = this.createjs || {}; + +(function () { + "use strict"; + /** + * A class to store the optional play properties passed in {{#crossLink "Sound/play"}}{{/crossLink}} and + * {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}} calls. + * + * Optional Play Properties Include: + *
    + *
  • interrupt - How to interrupt any currently playing instances of audio with the same source, + * if the maximum number of instances of the sound are already playing. Values are defined as INTERRUPT_TYPE + * constants on the Sound class, with the default defined by {{#crossLink "Sound/defaultInterruptBehavior:property"}}{{/crossLink}}.
  • + *
  • delay - The amount of time to delay the start of audio playback, in milliseconds.
  • + *
  • offset - The offset from the start of the audio to begin playback, in milliseconds.
  • + *
  • loop - How many times the audio loops when it reaches the end of playback. The default is 0 (no + * loops), and -1 can be used for infinite playback.
  • + *
  • volume - The volume of the sound, between 0 and 1. Note that the master volume is applied + * against the individual volume.
  • + *
  • pan - The left-right pan of the sound (if supported), between -1 (left) and 1 (right).
  • + *
  • startTime - To create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds.
  • + *
  • duration - To create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds.
  • + *
+ * + *

Example

+ * + * var ppc = new createjs.PlayPropsConfig().set({interrupt: createjs.Sound.INTERRUPT_ANY, loop: -1, volume: 0.5}) + * createjs.Sound.play("mySound", ppc); + * mySoundInstance.play(ppc); + * + * @class PlayPropsConfig + * @constructor + * @since 0.6.1 + */ + // TODO think of a better name for this class + var PlayPropsConfig = function () { +// Public Properties + /** + * How to interrupt any currently playing instances of audio with the same source, + * if the maximum number of instances of the sound are already playing. Values are defined as + * INTERRUPT_TYPE constants on the Sound class, with the default defined by + * {{#crossLink "Sound/defaultInterruptBehavior:property"}}{{/crossLink}}. + * @property interrupt + * @type {string} + * @default null + */ + this.interrupt = null; + + /** + * The amount of time to delay the start of audio playback, in milliseconds. + * @property delay + * @type {Number} + * @default null + */ + this.delay = null; + + /** + * The offset from the start of the audio to begin playback, in milliseconds. + * @property offset + * @type {number} + * @default null + */ + this.offset = null; + + /** + * How many times the audio loops when it reaches the end of playback. The default is 0 (no + * loops), and -1 can be used for infinite playback. + * @property loop + * @type {number} + * @default null + */ + this.loop = null; + + /** + * The volume of the sound, between 0 and 1. Note that the master volume is applied + * against the individual volume. + * @property volume + * @type {number} + * @default null + */ + this.volume = null; + + /** + * The left-right pan of the sound (if supported), between -1 (left) and 1 (right). + * @property pan + * @type {number} + * @default null + */ + this.pan = null; + + /** + * Used to create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds. + * @property startTime + * @type {number} + * @default null + */ + this.startTime = null; + + /** + * Used to create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds. + * @property duration + * @type {number} + * @default null + */ + this.duration = null; + }; + var p = PlayPropsConfig.prototype = {}; + var s = PlayPropsConfig; + + +// Static Methods + /** + * Creates a PlayPropsConfig from another PlayPropsConfig or an Object. + * + * @method create + * @param {PlayPropsConfig|Object} value The play properties + * @returns {PlayPropsConfig} + * @static + */ + s.create = function (value) { + if (value instanceof s || value instanceof Object) { + var ppc = new createjs.PlayPropsConfig(); + ppc.set(value); + return ppc; + } else { + throw new Error("Type not recognized."); + } + }; + +// Public Methods + /** + * Provides a chainable shortcut method for setting a number of properties on the instance. + * + *

Example

+ * + * var PlayPropsConfig = new createjs.PlayPropsConfig().set({loop:-1, volume:0.7}); + * + * @method set + * @param {Object} props A generic object containing properties to copy to the PlayPropsConfig instance. + * @return {PlayPropsConfig} Returns the instance the method is called on (useful for chaining calls.) + */ + p.set = function(props) { + for (var n in props) { this[n] = props[n]; } + return this; + }; + + p.toString = function() { + return "[PlayPropsConfig]"; + }; + + createjs.PlayPropsConfig = s; + }()); //############################################################################## // Sound.js //############################################################################## -this.createjs = this.createjs || {}; - - - -(function () { - "use strict"; - - /** - * The Sound class is the public API for creating sounds, controlling the overall sound levels, and managing plugins. - * All Sound APIs on this class are static. - * - * Registering and Preloading
- * Before you can play a sound, it must be registered. You can do this with {{#crossLink "Sound/registerSound"}}{{/crossLink}}, - * or register multiple sounds using {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. If you don't register a - * sound prior to attempting to play it using {{#crossLink "Sound/play"}}{{/crossLink}} or create it using {{#crossLink "Sound/createInstance"}}{{/crossLink}}, - * the sound source will be automatically registered but playback will fail as the source will not be ready. If you use - * PreloadJS, registration is handled for you when the sound is - * preloaded. It is recommended to preload sounds either internally using the register functions or externally using - * PreloadJS so they are ready when you want to use them. - * - * Playback
- * To play a sound once it's been registered and preloaded, use the {{#crossLink "Sound/play"}}{{/crossLink}} method. - * This method returns a {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} which can be paused, resumed, muted, etc. - * Please see the {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} documentation for more on the instance control APIs. - * - * Plugins
- * By default, the {{#crossLink "WebAudioPlugin"}}{{/crossLink}} or the {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}} - * are used (when available), although developers can change plugin priority or add new plugins (such as the - * provided {{#crossLink "FlashAudioPlugin"}}{{/crossLink}}). Please see the {{#crossLink "Sound"}}{{/crossLink}} API - * methods for more on the playback and plugin APIs. To install plugins, or specify a different plugin order, see - * {{#crossLink "Sound/installPlugins"}}{{/crossLink}}. - * - *

Example

- * - * createjs.FlashAudioPlugin.swfPath = "../src/soundjs/flashaudio"; - * createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.FlashAudioPlugin]); - * createjs.Sound.alternateExtensions = ["mp3"]; - * createjs.Sound.on("fileload", createjs.proxy(this.loadHandler, (this))); - * createjs.Sound.registerSound("path/to/mySound.ogg", "sound"); - * function loadHandler(event) { - * // This is fired for each sound that is registered. - * var instance = createjs.Sound.play("sound"); // play using id. Could also use full source path or event.src. - * instance.on("complete", createjs.proxy(this.handleComplete, this)); - * instance.volume = 0.5; - * } - * - * The maximum number of concurrently playing instances of the same sound can be specified in the "data" argument - * of {{#crossLink "Sound/registerSound"}}{{/crossLink}}. Note that if not specified, the active plugin will apply - * a default limit. Currently HTMLAudioPlugin sets a default limit of 2, while WebAudioPlugin and FlashAudioPlugin set a - * default limit of 100. - * - * createjs.Sound.registerSound("sound.mp3", "soundId", 4); - * - * Sound can be used as a plugin with PreloadJS to help preload audio properly. Audio preloaded with PreloadJS is - * automatically registered with the Sound class. When audio is not preloaded, Sound will do an automatic internal - * load. As a result, it may fail to play the first time play is called if the audio is not finished loading. Use the - * {{#crossLink "Sound/fileload"}}{{/crossLink}} event to determine when a sound has finished internally preloading. - * It is recommended that all audio is preloaded before it is played. - * - * var queue = new createjs.LoadQueue(); - * queue.installPlugin(createjs.Sound); - * - * Audio Sprites
- * SoundJS has added support for {{#crossLink "AudioSprite"}}{{/crossLink}}, available as of version 0.6.0. - * For those unfamiliar with audio sprites, they are much like CSS sprites or sprite sheets: multiple audio assets - * grouped into a single file. - * - *

Example

- * - * var assetsPath = "./assets/"; - * var sounds = [{ - * src:"MyAudioSprite.ogg", data: { - * audioSprite: [ - * {id:"sound1", startTime:0, duration:500}, - * {id:"sound2", startTime:1000, duration:400}, - * {id:"sound3", startTime:1700, duration: 1000} - * ]} - * } - * ]; - * createjs.Sound.alternateExtensions = ["mp3"]; - * createjs.Sound.on("fileload", loadSound); - * createjs.Sound.registerSounds(sounds, assetsPath); - * // after load is complete - * createjs.Sound.play("sound2"); - * - * Mobile Safe Approach
- * Mobile devices require sounds to be played inside of a user initiated event (touch/click) in varying degrees. - * As of SoundJS 0.4.1, you can launch a site inside of a user initiated event and have audio playback work. To - * enable as broadly as possible, the site needs to setup the Sound plugin in its initialization (for example via - * createjs.Sound.initializeDefaultPlugins();), and all sounds need to be played in the scope of the - * application. See the MobileSafe demo for a working example. - * - *

Example

- * - * document.getElementById("status").addEventListener("click", handleTouch, false); // works on Android and iPad - * function handleTouch(event) { - * document.getElementById("status").removeEventListener("click", handleTouch, false); // remove the listener - * var thisApp = new myNameSpace.MyApp(); // launch the app - * } - * - * Loading Alternate Paths and Extensionless Files
- * SoundJS supports loading alternate paths and extensionless files by passing an object for src that has various paths - * with property labels matching the extension. These labels are how SoundJS determines if the browser will support the sound. - * Priority is determined by the property order (first property is tried first). This is supported by both internal loading - * and loading with PreloadJS. - * - * Note an id is required for playback. - * - *

Example

- * - * var sounds = {path:"./audioPath/", - * manifest: [ - * {id: "cool", src: {mp3:"mp3/awesome.mp3", ogg:"noExtensionOggFile"}} - * ]}; - * - * createjs.Sound.alternateExtensions = ["mp3"]; - * createjs.Sound.addEventListener("fileload", handleLoad); - * createjs.Sound.registerSounds(sounds); - * - *

Known Browser and OS issues

- * IE 9 HTML Audio limitations
- *
  • There is a delay in applying volume changes to tags that occurs once playback is started. So if you have - * muted all sounds, they will all play during this delay until the mute applies internally. This happens regardless of - * when or how you apply the volume change, as the tag seems to need to play to apply it.
  • - *
  • MP3 encoding will not always work for audio tags, particularly in Internet Explorer. We've found default - * encoding with 64kbps works.
  • - *
  • Occasionally very short samples will get cut off.
  • - *
  • There is a limit to how many audio tags you can load and play at once, which appears to be determined by - * hardware and browser settings. See {{#crossLink "HTMLAudioPlugin.MAX_INSTANCES"}}{{/crossLink}} for a safe estimate.
- * - * Firefox 25 Web Audio limitations - *
  • mp3 audio files do not load properly on all windows machines, reported - * here.
    - * For this reason it is recommended to pass another FF supported type (ie ogg) first until this bug is resolved, if possible.
- - * Safari limitations
- *
  • Safari requires Quicktime to be installed for audio playback.
- * - * iOS 6 Web Audio limitations
- *
  • Sound is initially muted and will only unmute through play being called inside a user initiated event - * (touch/click).
  • - *
  • A bug exists that will distort un-cached web audio when a video element is present in the DOM that has audio at a different sampleRate.
  • - *
- * - * Android HTML Audio limitations
- *
  • We have no control over audio volume. Only the user can set volume on their device.
  • - *
  • We can only play audio inside a user event (touch/click). This currently means you cannot loop sound or use - * a delay.
- * - * Web Audio and PreloadJS
- *
  • Web Audio must be loaded through XHR, therefore when used with PreloadJS tag loading is not possible. This means that tag loading cannot - * be used to avoid cross domain issues if WebAudioPlugin is used
    • - * - * @class Sound - * @static - * @uses EventDispatcher - */ - function Sound() { - throw "Sound cannot be instantiated"; - } - - var s = Sound; - - -// Static Properties - /** - * The interrupt value to interrupt any currently playing instance with the same source, if the maximum number of - * instances of the sound are already playing. - * @property INTERRUPT_ANY - * @type {String} - * @default any - * @static - */ - s.INTERRUPT_ANY = "any"; - - /** - * The interrupt value to interrupt the earliest currently playing instance with the same source that progressed the - * least distance in the audio track, if the maximum number of instances of the sound are already playing. - * @property INTERRUPT_EARLY - * @type {String} - * @default early - * @static - */ - s.INTERRUPT_EARLY = "early"; - - /** - * The interrupt value to interrupt the currently playing instance with the same source that progressed the most - * distance in the audio track, if the maximum number of instances of the sound are already playing. - * @property INTERRUPT_LATE - * @type {String} - * @default late - * @static - */ - s.INTERRUPT_LATE = "late"; - - /** - * The interrupt value to not interrupt any currently playing instances with the same source, if the maximum number of - * instances of the sound are already playing. - * @property INTERRUPT_NONE - * @type {String} - * @default none - * @static - */ - s.INTERRUPT_NONE = "none"; - - /** - * Defines the playState of an instance that is still initializing. - * @property PLAY_INITED - * @type {String} - * @default playInited - * @static - */ - s.PLAY_INITED = "playInited"; - - /** - * Defines the playState of an instance that is currently playing or paused. - * @property PLAY_SUCCEEDED - * @type {String} - * @default playSucceeded - * @static - */ - s.PLAY_SUCCEEDED = "playSucceeded"; - - /** - * Defines the playState of an instance that was interrupted by another instance. - * @property PLAY_INTERRUPTED - * @type {String} - * @default playInterrupted - * @static - */ - s.PLAY_INTERRUPTED = "playInterrupted"; - - /** - * Defines the playState of an instance that completed playback. - * @property PLAY_FINISHED - * @type {String} - * @default playFinished - * @static - */ - s.PLAY_FINISHED = "playFinished"; - - /** - * Defines the playState of an instance that failed to play. This is usually caused by a lack of available channels - * when the interrupt mode was "INTERRUPT_NONE", the playback stalled, or the sound could not be found. - * @property PLAY_FAILED - * @type {String} - * @default playFailed - * @static - */ - s.PLAY_FAILED = "playFailed"; - - /** - * A list of the default supported extensions that Sound will try to play. Plugins will check if the browser - * can play these types, so modifying this list before a plugin is initialized will allow the plugins to try to - * support additional media types. - * - * NOTE this does not currently work for {{#crossLink "FlashAudioPlugin"}}{{/crossLink}}. - * - * More details on file formats can be found at http://en.wikipedia.org/wiki/Audio_file_format.
      - * A very detailed list of file formats can be found at http://www.fileinfo.com/filetypes/audio. - * @property SUPPORTED_EXTENSIONS - * @type {Array[String]} - * @default ["mp3", "ogg", "opus", "mpeg", "wav", "m4a", "mp4", "aiff", "wma", "mid"] - * @since 0.4.0 - * @static - */ - s.SUPPORTED_EXTENSIONS = ["mp3", "ogg", "opus", "mpeg", "wav", "m4a", "mp4", "aiff", "wma", "mid"]; - - /** - * Some extensions use another type of extension support to play (one of them is a codex). This allows you to map - * that support so plugins can accurately determine if an extension is supported. Adding to this list can help - * plugins determine more accurately if an extension is supported. - * - * A useful list of extensions for each format can be found at http://html5doctor.com/html5-audio-the-state-of-play/. - * @property EXTENSION_MAP - * @type {Object} - * @since 0.4.0 - * @default {m4a:"mp4"} - * @static - */ - s.EXTENSION_MAP = { - m4a:"mp4" - }; - - /** - * The RegExp pattern used to parse file URIs. This supports simple file names, as well as full domain URIs with - * query strings. The resulting match is: protocol:$1 domain:$2 path:$3 file:$4 extension:$5 query:$6. - * @property FILE_PATTERN - * @type {RegExp} - * @static - * @protected - */ - s.FILE_PATTERN = /^(?:(\w+:)\/{2}(\w+(?:\.\w+)*\/?))?([/.]*?(?:[^?]+)?\/)?((?:[^/?]+)\.(\w+))(?:\?(\S+)?)?$/; - - -// Class Public properties - /** - * Determines the default behavior for interrupting other currently playing instances with the same source, if the - * maximum number of instances of the sound are already playing. Currently the default is {{#crossLink "Sound/INTERRUPT_NONE:property"}}{{/crossLink}} - * but this can be set and will change playback behavior accordingly. This is only used when {{#crossLink "Sound/play"}}{{/crossLink}} - * is called without passing a value for interrupt. - * @property defaultInterruptBehavior - * @type {String} - * @default Sound.INTERRUPT_NONE, or "none" - * @static - * @since 0.4.0 - */ - s.defaultInterruptBehavior = s.INTERRUPT_NONE; // OJR does s.INTERRUPT_ANY make more sense as default? Needs game dev testing to see which case makes more sense. - - /** - * An array of extensions to attempt to use when loading sound, if the default is unsupported by the active plugin. - * These are applied in order, so if you try to Load Thunder.ogg in a browser that does not support ogg, and your - * extensions array is ["mp3", "m4a", "wav"] it will check mp3 support, then m4a, then wav. The audio files need - * to exist in the same location, as only the extension is altered. - * - * Note that regardless of which file is loaded, you can call {{#crossLink "Sound/createInstance"}}{{/crossLink}} - * and {{#crossLink "Sound/play"}}{{/crossLink}} using the same id or full source path passed for loading. - * - *

      Example

      - * - * var sounds = [ - * {src:"myPath/mySound.ogg", id:"example"}, - * ]; - * createjs.Sound.alternateExtensions = ["mp3"]; // now if ogg is not supported, SoundJS will try asset0.mp3 - * createjs.Sound.on("fileload", handleLoad); // call handleLoad when each sound loads - * createjs.Sound.registerSounds(sounds, assetPath); - * // ... - * createjs.Sound.play("myPath/mySound.ogg"); // works regardless of what extension is supported. Note calling with ID is a better approach - * - * @property alternateExtensions - * @type {Array} - * @since 0.5.2 - * @static - */ - s.alternateExtensions = []; - - /** - * The currently active plugin. If this is null, then no plugin could be initialized. If no plugin was specified, - * Sound attempts to apply the default plugins: {{#crossLink "WebAudioPlugin"}}{{/crossLink}}, followed by - * {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}. - * @property activePlugin - * @type {Object} - * @static - */ - s.activePlugin = null; - - -// class getter / setter properties - /** - * Set the master volume of Sound. The master volume is multiplied against each sound's individual volume. For - * example, if master volume is 0.5 and a sound's volume is 0.5, the resulting volume is 0.25. To set individual - * sound volume, use AbstractSoundInstance {{#crossLink "AbstractSoundInstance/volume:property"}}{{/crossLink}} instead. - * - *

      Example

      - * - * createjs.Sound.volume = 0.5; - * - * - * @property volume - * @type {Number} - * @default 1 - * @since 0.6.1 - */ - s._masterVolume = 1; - Object.defineProperty(s, "volume", { - get: function () {return this._masterVolume;}, - set: function (value) { - if (Number(value) == null) {return false;} - value = Math.max(0, Math.min(1, value)); - s._masterVolume = value; - if (!this.activePlugin || !this.activePlugin.setVolume || !this.activePlugin.setVolume(value)) { - var instances = this._instances; - for (var i = 0, l = instances.length; i < l; i++) { - instances[i].setMasterVolume(value); - } - } - } - }); - - /** - * Mute/Unmute all audio. Note that muted audio still plays at 0 volume. This global mute value is maintained - * separately and when set will override, but not change the mute property of individual instances. To mute an individual - * instance, use AbstractSoundInstance {{#crossLink "AbstractSoundInstance/muted:property"}}{{/crossLink}} instead. - * - *

      Example

      - * - * createjs.Sound.muted = true; - * - * - * @property muted - * @type {Boolean} - * @default false - * @since 0.6.1 - */ - s._masterMute = false; - // OJR references to the methods were not working, so the code had to be duplicated here - Object.defineProperty(s, "muted", { - get: function () {return this._masterMute;}, - set: function (value) { - if (value == null) {return false;} - - this._masterMute = value; - if (!this.activePlugin || !this.activePlugin.setMute || !this.activePlugin.setMute(value)) { - var instances = this._instances; - for (var i = 0, l = instances.length; i < l; i++) { - instances[i].setMasterMute(value); - } - } - return true; - } - }); - - /** - * Get the active plugins capabilities, which help determine if a plugin can be used in the current environment, - * or if the plugin supports a specific feature. Capabilities include: - *
        - *
      • panning: If the plugin can pan audio from left to right
      • - *
      • volume; If the plugin can control audio volume.
      • - *
      • tracks: The maximum number of audio tracks that can be played back at a time. This will be -1 - * if there is no known limit.
      • - *
        An entry for each file type in {{#crossLink "Sound/SUPPORTED_EXTENSIONS:property"}}{{/crossLink}}: - *
      • mp3: If MP3 audio is supported.
      • - *
      • ogg: If OGG audio is supported.
      • - *
      • wav: If WAV audio is supported.
      • - *
      • mpeg: If MPEG audio is supported.
      • - *
      • m4a: If M4A audio is supported.
      • - *
      • mp4: If MP4 audio is supported.
      • - *
      • aiff: If aiff audio is supported.
      • - *
      • wma: If wma audio is supported.
      • - *
      • mid: If mid audio is supported.
      • - *
      - * - * You can get a specific capability of the active plugin using standard object notation - * - *

      Example

      - * - * var mp3 = createjs.Sound.capabilities.mp3; - * - * Note this property is read only. - * - * @property capabilities - * @type {Object} - * @static - * @readOnly - * @since 0.6.1 - */ - Object.defineProperty(s, "capabilities", { - get: function () { - if (s.activePlugin == null) {return null;} - return s.activePlugin._capabilities; - }, - set: function (value) { return false;} - }); - - -// Class Private properties - /** - * Determines if the plugins have been registered. If false, the first call to play() will instantiate the default - * plugins ({{#crossLink "WebAudioPlugin"}}{{/crossLink}}, followed by {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}). - * If plugins have been registered, but none are applicable, then sound playback will fail. - * @property _pluginsRegistered - * @type {Boolean} - * @default false - * @static - * @protected - */ - s._pluginsRegistered = false; - - /** - * Used internally to assign unique IDs to each AbstractSoundInstance. - * @property _lastID - * @type {Number} - * @static - * @protected - */ - s._lastID = 0; - - /** - * An array containing all currently playing instances. This allows Sound to control the volume, mute, and playback of - * all instances when using static APIs like {{#crossLink "Sound/stop"}}{{/crossLink}} and {{#crossLink "Sound/setVolume"}}{{/crossLink}}. - * When an instance has finished playback, it gets removed via the {{#crossLink "Sound/finishedPlaying"}}{{/crossLink}} - * method. If the user replays an instance, it gets added back in via the {{#crossLink "Sound/_beginPlaying"}}{{/crossLink}} - * method. - * @property _instances - * @type {Array} - * @protected - * @static - */ - s._instances = []; - - /** - * An object hash storing objects with sound sources, startTime, and duration via there corresponding ID. - * @property _idHash - * @type {Object} - * @protected - * @static - */ - s._idHash = {}; - - /** - * An object hash that stores preloading sound sources via the parsed source that is passed to the plugin. Contains the - * source, id, and data that was passed in by the user. Parsed sources can contain multiple instances of source, id, - * and data. - * @property _preloadHash - * @type {Object} - * @protected - * @static - */ - s._preloadHash = {}; - - /** - * An object hash storing {{#crossLink "PlayPropsConfig"}}{{/crossLink}} via the parsed source that is passed as defaultPlayProps in - * {{#crossLink "Sound/registerSound"}}{{/crossLink}} and {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. - * @property _defaultPlayPropsHash - * @type {Object} - * @protected - * @static - * @since 0.6.1 - */ - s._defaultPlayPropsHash = {}; - - -// EventDispatcher methods: - s.addEventListener = null; - s.removeEventListener = null; - s.removeAllEventListeners = null; - s.dispatchEvent = null; - s.hasEventListener = null; - s._listeners = null; - - createjs.EventDispatcher.initialize(s); // inject EventDispatcher methods. - - -// Events - /** - * This event is fired when a file finishes loading internally. This event is fired for each loaded sound, - * so any handler methods should look up the event.src to handle a particular sound. - * @event fileload - * @param {Object} target The object that dispatched the event. - * @param {String} type The event type. - * @param {String} src The source of the sound that was loaded. - * @param {String} [id] The id passed in when the sound was registered. If one was not provided, it will be null. - * @param {Number|Object} [data] Any additional data associated with the item. If not provided, it will be undefined. - * @since 0.4.1 - */ - - /** - * This event is fired when a file fails loading internally. This event is fired for each loaded sound, - * so any handler methods should look up the event.src to handle a particular sound. - * @event fileerror - * @param {Object} target The object that dispatched the event. - * @param {String} type The event type. - * @param {String} src The source of the sound that was loaded. - * @param {String} [id] The id passed in when the sound was registered. If one was not provided, it will be null. - * @param {Number|Object} [data] Any additional data associated with the item. If not provided, it will be undefined. - * @since 0.6.0 - */ - - -// Class Public Methods - /** - * Get the preload rules to allow Sound to be used as a plugin by PreloadJS. - * Any load calls that have the matching type or extension will fire the callback method, and use the resulting - * object, which is potentially modified by Sound. This helps when determining the correct path, as well as - * registering the audio instance(s) with Sound. This method should not be called, except by PreloadJS. - * @method getPreloadHandlers - * @return {Object} An object containing: - *
      • callback: A preload callback that is fired when a file is added to PreloadJS, which provides - * Sound a mechanism to modify the load parameters, select the correct file format, register the sound, etc.
      • - *
      • types: A list of file types that are supported by Sound (currently supports "sound").
      • - *
      • extensions: A list of file extensions that are supported by Sound (see {{#crossLink "Sound/SUPPORTED_EXTENSIONS:property"}}{{/crossLink}}).
      - * @static - * @protected - */ - s.getPreloadHandlers = function () { - return { - callback:createjs.proxy(s.initLoad, s), - types:["sound"], - extensions:s.SUPPORTED_EXTENSIONS - }; - }; - - /** - * Used to dispatch fileload events from internal loading. - * @method _handleLoadComplete - * @param event A loader event. - * @protected - * @static - * @since 0.6.0 - */ - s._handleLoadComplete = function(event) { - var src = event.target.getItem().src; - if (!s._preloadHash[src]) {return;} - - for (var i = 0, l = s._preloadHash[src].length; i < l; i++) { - var item = s._preloadHash[src][i]; - s._preloadHash[src][i] = true; - - if (!s.hasEventListener("fileload")) { continue; } - - var event = new createjs.Event("fileload"); - event.src = item.src; - event.id = item.id; - event.data = item.data; - event.sprite = item.sprite; - - s.dispatchEvent(event); - } - }; - - /** - * Used to dispatch error events from internal preloading. - * @param event - * @protected - * @since 0.6.0 - * @static - */ - s._handleLoadError = function(event) { - var src = event.target.getItem().src; - if (!s._preloadHash[src]) {return;} - - for (var i = 0, l = s._preloadHash[src].length; i < l; i++) { - var item = s._preloadHash[src][i]; - s._preloadHash[src][i] = false; - - if (!s.hasEventListener("fileerror")) { continue; } - - var event = new createjs.Event("fileerror"); - event.src = item.src; - event.id = item.id; - event.data = item.data; - event.sprite = item.sprite; - - s.dispatchEvent(event); - } - }; - - /** - * Used by {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} to register a Sound plugin. - * - * @method _registerPlugin - * @param {Object} plugin The plugin class to install. - * @return {Boolean} Whether the plugin was successfully initialized. - * @static - * @private - */ - s._registerPlugin = function (plugin) { - // Note: Each plugin is passed in as a class reference, but we store the activePlugin as an instance - if (plugin.isSupported()) { - s.activePlugin = new plugin(); - return true; - } - return false; - }; - - /** - * Register a list of Sound plugins, in order of precedence. To register a single plugin, pass a single element in the array. - * - *

      Example

      - * - * createjs.FlashAudioPlugin.swfPath = "../src/soundjs/flashaudio/"; - * createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin, createjs.FlashAudioPlugin]); - * - * @method registerPlugins - * @param {Array} plugins An array of plugins classes to install. - * @return {Boolean} Whether a plugin was successfully initialized. - * @static - */ - s.registerPlugins = function (plugins) { - s._pluginsRegistered = true; - for (var i = 0, l = plugins.length; i < l; i++) { - if (s._registerPlugin(plugins[i])) { - return true; - } - } - return false; - }; - - /** - * Initialize the default plugins. This method is automatically called when any audio is played or registered before - * the user has manually registered plugins, and enables Sound to work without manual plugin setup. Currently, the - * default plugins are {{#crossLink "WebAudioPlugin"}}{{/crossLink}} followed by {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}. - * - *

      Example

      - * - * if (!createjs.initializeDefaultPlugins()) { return; } - * - * @method initializeDefaultPlugins - * @returns {Boolean} True if a plugin was initialized, false otherwise. - * @since 0.4.0 - * @static - */ - s.initializeDefaultPlugins = function () { - if (s.activePlugin != null) {return true;} - if (s._pluginsRegistered) {return false;} - if (s.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin])) {return true;} - return false; - }; - - /** - * Determines if Sound has been initialized, and a plugin has been activated. - * - *

      Example

      - * This example sets up a Flash fallback, but only if there is no plugin specified yet. - * - * if (!createjs.Sound.isReady()) { - * createjs.FlashAudioPlugin.swfPath = "../src/soundjs/flashaudio/"; - * createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin, createjs.FlashAudioPlugin]); - * } - * - * @method isReady - * @return {Boolean} If Sound has initialized a plugin. - * @static - */ - s.isReady = function () { - return (s.activePlugin != null); - }; - - /** - * Deprecated, please use {{#crossLink "Sound/capabilities:property"}}{{/crossLink}} instead. - * - * @method getCapabilities - * @return {Object} An object containing the capabilities of the active plugin. - * @static - * @deprecated - */ - s.getCapabilities = function () { - if (s.activePlugin == null) {return null;} - return s.activePlugin._capabilities; - }; - - /** - * Deprecated, please use {{#crossLink "Sound/capabilities:property"}}{{/crossLink}} instead. - * - * @method getCapability - * @param {String} key The capability to retrieve - * @return {Number|Boolean} The value of the capability. - * @static - * @see getCapabilities - * @deprecated - */ - s.getCapability = function (key) { - if (s.activePlugin == null) {return null;} - return s.activePlugin._capabilities[key]; - }; - - /** - * Process manifest items from PreloadJS. This method is intended - * for usage by a plugin, and not for direct interaction. - * @method initLoad - * @param {Object} src The object to load. - * @return {Object|AbstractLoader} An instance of AbstractLoader. - * @protected - * @static - */ - s.initLoad = function (loadItem) { - return s._registerSound(loadItem); - }; - - /** - * Internal method for loading sounds. This should not be called directly. - * - * @method _registerSound - * @param {Object} src The object to load, containing src property and optionally containing id and data. - * @return {Object} An object with the modified values that were passed in, which defines the sound. - * Returns false if the source cannot be parsed or no plugins can be initialized. - * Returns true if the source is already loaded. - * @static - * @private - * @since 0.6.0 - */ - - s._registerSound = function (loadItem) { - if (!s.initializeDefaultPlugins()) {return false;} - - var details; - if (loadItem.src instanceof Object) { - details = s._parseSrc(loadItem.src); - details.src = loadItem.path + details.src; - } else { - details = s._parsePath(loadItem.src); - } - if (details == null) {return false;} - loadItem.src = details.src; - loadItem.type = "sound"; - - var data = loadItem.data; - var numChannels = null; - if (data != null) { - if (!isNaN(data.channels)) { - numChannels = parseInt(data.channels); - } else if (!isNaN(data)) { - numChannels = parseInt(data); - } - - if(data.audioSprite) { - var sp; - for(var i = data.audioSprite.length; i--; ) { - sp = data.audioSprite[i]; - s._idHash[sp.id] = {src: loadItem.src, startTime: parseInt(sp.startTime), duration: parseInt(sp.duration)}; - - if (sp.defaultPlayProps) { - s._defaultPlayPropsHash[sp.id] = createjs.PlayPropsConfig.create(sp.defaultPlayProps); - } - } - } - } - if (loadItem.id != null) {s._idHash[loadItem.id] = {src: loadItem.src}}; - var loader = s.activePlugin.register(loadItem); - - SoundChannel.create(loadItem.src, numChannels); - - // return the number of instances to the user. This will also be returned in the load event. - if (data == null || !isNaN(data)) { - loadItem.data = numChannels || SoundChannel.maxPerChannel(); - } else { - loadItem.data.channels = numChannels || SoundChannel.maxPerChannel(); - } - - if (loader.type) {loadItem.type = loader.type;} - - if (loadItem.defaultPlayProps) { - s._defaultPlayPropsHash[loadItem.src] = createjs.PlayPropsConfig.create(loadItem.defaultPlayProps); - } - return loader; - }; - - /** - * Register an audio file for loading and future playback in Sound. This is automatically called when using - * PreloadJS. It is recommended to register all sounds that - * need to be played back in order to properly prepare and preload them. Sound does internal preloading when required. - * - *

      Example

      - * - * createjs.Sound.alternateExtensions = ["mp3"]; - * createjs.Sound.on("fileload", handleLoad); // add an event listener for when load is completed - * createjs.Sound.registerSound("myAudioPath/mySound.ogg", "myID", 3); - * createjs.Sound.registerSound({ogg:"path1/mySound.ogg", mp3:"path2/mySoundNoExtension"}, "myID", 3); - * - * - * @method registerSound - * @param {String | Object} src The source or an Object with a "src" property or an Object with multiple extension labeled src properties. - * @param {String} [id] An id specified by the user to play the sound later. Note id is required for when src is multiple extension labeled src properties. - * @param {Number | Object} [data] Data associated with the item. Sound uses the data parameter as the number of - * channels for an audio instance, however a "channels" property can be appended to the data object if it is used - * for other information. The audio channels will set a default based on plugin if no value is found. - * Sound also uses the data property to hold an {{#crossLink "AudioSprite"}}{{/crossLink}} array of objects in the following format {id, startTime, duration}.
      - * id used to play the sound later, in the same manner as a sound src with an id.
      - * startTime is the initial offset to start playback and loop from, in milliseconds.
      - * duration is the amount of time to play the clip for, in milliseconds.
      - * This allows Sound to support audio sprites that are played back by id. - * @param {string} basePath Set a path that will be prepended to src for loading. - * @param {Object | PlayPropsConfig} defaultPlayProps Optional Playback properties that will be set as the defaults on any new AbstractSoundInstance. - * See {{#crossLink "PlayPropsConfig"}}{{/crossLink}} for options. - * @return {Object} An object with the modified values that were passed in, which defines the sound. - * Returns false if the source cannot be parsed or no plugins can be initialized. - * Returns true if the source is already loaded. - * @static - * @since 0.4.0 - */ - s.registerSound = function (src, id, data, basePath, defaultPlayProps) { - var loadItem = {src: src, id: id, data:data, defaultPlayProps:defaultPlayProps}; - if (src instanceof Object && src.src) { - basePath = id; - loadItem = src; - } - loadItem = createjs.LoadItem.create(loadItem); - loadItem.path = basePath; - - if (basePath != null && !(loadItem.src instanceof Object)) {loadItem.src = basePath + src;} - - var loader = s._registerSound(loadItem); - if(!loader) {return false;} - - if (!s._preloadHash[loadItem.src]) { s._preloadHash[loadItem.src] = [];} - s._preloadHash[loadItem.src].push(loadItem); - if (s._preloadHash[loadItem.src].length == 1) { - // OJR note this will disallow reloading a sound if loading fails or the source changes - loader.on("complete", createjs.proxy(this._handleLoadComplete, this)); - loader.on("error", createjs.proxy(this._handleLoadError, this)); - s.activePlugin.preload(loader); - } else { - if (s._preloadHash[loadItem.src][0] == true) {return true;} - } - - return loadItem; - }; - - /** - * Register an array of audio files for loading and future playback in Sound. It is recommended to register all - * sounds that need to be played back in order to properly prepare and preload them. Sound does internal preloading - * when required. - * - *

      Example

      - * - * var assetPath = "./myAudioPath/"; - * var sounds = [ - * {src:"asset0.ogg", id:"example"}, - * {src:"asset1.ogg", id:"1", data:6}, - * {src:"asset2.mp3", id:"works"} - * {src:{mp3:"path1/asset3.mp3", ogg:"path2/asset3NoExtension}, id:"better"} - * ]; - * createjs.Sound.alternateExtensions = ["mp3"]; // if the passed extension is not supported, try this extension - * createjs.Sound.on("fileload", handleLoad); // call handleLoad when each sound loads - * createjs.Sound.registerSounds(sounds, assetPath); - * - * @method registerSounds - * @param {Array} sounds An array of objects to load. Objects are expected to be in the format needed for - * {{#crossLink "Sound/registerSound"}}{{/crossLink}}: {src:srcURI, id:ID, data:Data} - * with "id" and "data" being optional. - * You can also pass an object with path and manifest properties, where path is a basePath and manifest is an array of objects to load. - * Note id is required if src is an object with extension labeled src properties. - * @param {string} basePath Set a path that will be prepended to each src when loading. When creating, playing, or removing - * audio that was loaded with a basePath by src, the basePath must be included. - * @return {Object} An array of objects with the modified values that were passed in, which defines each sound. - * Like registerSound, it will return false for any values when the source cannot be parsed or if no plugins can be initialized. - * Also, it will return true for any values when the source is already loaded. - * @static - * @since 0.6.0 - */ - s.registerSounds = function (sounds, basePath) { - var returnValues = []; - if (sounds.path) { - if (!basePath) { - basePath = sounds.path; - } else { - basePath = basePath + sounds.path; - } - sounds = sounds.manifest; - // TODO document this feature - } - for (var i = 0, l = sounds.length; i < l; i++) { - returnValues[i] = createjs.Sound.registerSound(sounds[i].src, sounds[i].id, sounds[i].data, basePath, sounds[i].defaultPlayProps); - } - return returnValues; - }; - - /** - * Remove a sound that has been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or - * {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. - *
      Note this will stop playback on active instances playing this sound before deleting them. - *
      Note if you passed in a basePath, you need to pass it or prepend it to the src here. - * - *

      Example

      - * - * createjs.Sound.removeSound("myID"); - * createjs.Sound.removeSound("myAudioBasePath/mySound.ogg"); - * createjs.Sound.removeSound("myPath/myOtherSound.mp3", "myBasePath/"); - * createjs.Sound.removeSound({mp3:"musicNoExtension", ogg:"music.ogg"}, "myBasePath/"); - * - * @method removeSound - * @param {String | Object} src The src or ID of the audio, or an Object with a "src" property, or an Object with multiple extension labeled src properties. - * @param {string} basePath Set a path that will be prepended to each src when removing. - * @return {Boolean} True if sound is successfully removed. - * @static - * @since 0.4.1 - */ - s.removeSound = function(src, basePath) { - if (s.activePlugin == null) {return false;} - - if (src instanceof Object && src.src) {src = src.src;} - - var details; - if (src instanceof Object) { - details = s._parseSrc(src); - } else { - src = s._getSrcById(src).src; - details = s._parsePath(src); - } - if (details == null) {return false;} - src = details.src; - if (basePath != null) {src = basePath + src;} - - for(var prop in s._idHash){ - if(s._idHash[prop].src == src) { - delete(s._idHash[prop]); - } - } - - // clear from SoundChannel, which also stops and deletes all instances - SoundChannel.removeSrc(src); - - delete(s._preloadHash[src]); - - s.activePlugin.removeSound(src); - - return true; - }; - - /** - * Remove an array of audio files that have been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or - * {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. - *
      Note this will stop playback on active instances playing this audio before deleting them. - *
      Note if you passed in a basePath, you need to pass it or prepend it to the src here. - * - *

      Example

      - * - * assetPath = "./myPath/"; - * var sounds = [ - * {src:"asset0.ogg", id:"example"}, - * {src:"asset1.ogg", id:"1", data:6}, - * {src:"asset2.mp3", id:"works"} - * ]; - * createjs.Sound.removeSounds(sounds, assetPath); - * - * @method removeSounds - * @param {Array} sounds An array of objects to remove. Objects are expected to be in the format needed for - * {{#crossLink "Sound/removeSound"}}{{/crossLink}}: {srcOrID:srcURIorID}. - * You can also pass an object with path and manifest properties, where path is a basePath and manifest is an array of objects to remove. - * @param {string} basePath Set a path that will be prepended to each src when removing. - * @return {Object} An array of Boolean values representing if the sounds with the same array index were - * successfully removed. - * @static - * @since 0.4.1 - */ - s.removeSounds = function (sounds, basePath) { - var returnValues = []; - if (sounds.path) { - if (!basePath) { - basePath = sounds.path; - } else { - basePath = basePath + sounds.path; - } - sounds = sounds.manifest; - } - for (var i = 0, l = sounds.length; i < l; i++) { - returnValues[i] = createjs.Sound.removeSound(sounds[i].src, basePath); - } - return returnValues; - }; - - /** - * Remove all sounds that have been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or - * {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. - *
      Note this will stop playback on all active sound instances before deleting them. - * - *

      Example

      - * - * createjs.Sound.removeAllSounds(); - * - * @method removeAllSounds - * @static - * @since 0.4.1 - */ - s.removeAllSounds = function() { - s._idHash = {}; - s._preloadHash = {}; - SoundChannel.removeAll(); - if (s.activePlugin) {s.activePlugin.removeAllSounds();} - }; - - /** - * Check if a source has been loaded by internal preloaders. This is necessary to ensure that sounds that are - * not completed preloading will not kick off a new internal preload if they are played. - * - *

      Example

      - * - * var mySound = "assetPath/asset0.ogg"; - * if(createjs.Sound.loadComplete(mySound) { - * createjs.Sound.play(mySound); - * } - * - * @method loadComplete - * @param {String} src The src or id that is being loaded. - * @return {Boolean} If the src is already loaded. - * @since 0.4.0 - * @static - */ - s.loadComplete = function (src) { - if (!s.isReady()) { return false; } - var details = s._parsePath(src); - if (details) { - src = s._getSrcById(details.src).src; - } else { - src = s._getSrcById(src).src; - } - if(s._preloadHash[src] == undefined) {return false;} - return (s._preloadHash[src][0] == true); // src only loads once, so if it's true for the first it's true for all - }; - - /** - * Parse the path of a sound. Alternate extensions will be attempted in order if the - * current extension is not supported - * @method _parsePath - * @param {String} value The path to an audio source. - * @return {Object} A formatted object that can be registered with the {{#crossLink "Sound/activePlugin:property"}}{{/crossLink}} - * and returned to a preloader like PreloadJS. - * @protected - * @static - */ - s._parsePath = function (value) { - if (typeof(value) != "string") {value = value.toString();} - - var match = value.match(s.FILE_PATTERN); - if (match == null) {return false;} - - var name = match[4]; - var ext = match[5]; - var c = s.capabilities; - var i = 0; - while (!c[ext]) { - ext = s.alternateExtensions[i++]; - if (i > s.alternateExtensions.length) { return null;} // no extensions are supported - } - value = value.replace("."+match[5], "."+ext); - - var ret = {name:name, src:value, extension:ext}; - return ret; - }; - - /** - * Parse the path of a sound based on properties of src matching with supported extensions. - * Returns false if none of the properties are supported - * @method _parseSrc - * @param {Object} value The paths to an audio source, indexed by extension type. - * @return {Object} A formatted object that can be registered with the {{#crossLink "Sound/activePlugin:property"}}{{/crossLink}} - * and returned to a preloader like PreloadJS. - * @protected - * @static - */ - s._parseSrc = function (value) { - var ret = {name:undefined, src:undefined, extension:undefined}; - var c = s.capabilities; - - for (var prop in value) { - if(value.hasOwnProperty(prop) && c[prop]) { - ret.src = value[prop]; - ret.extension = prop; - break; - } - } - if (!ret.src) {return false;} // no matches - - var i = ret.src.lastIndexOf("/"); - if (i != -1) { - ret.name = ret.src.slice(i+1); - } else { - ret.name = ret.src; - } - - return ret; - }; - - /* --------------- - Static API. - --------------- */ - /** - * Play a sound and get a {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} to control. If the sound fails to play, a - * AbstractSoundInstance will still be returned, and have a playState of {{#crossLink "Sound/PLAY_FAILED:property"}}{{/crossLink}}. - * Note that even on sounds with failed playback, you may still be able to call AbstractSoundInstance {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}}, - * since the failure could be due to lack of available channels. If the src does not have a supported extension or - * if there is no available plugin, a default AbstractSoundInstance will be returned which will not play any audio, but will not generate errors. - * - *

      Example

      - * - * createjs.Sound.on("fileload", handleLoad); - * createjs.Sound.registerSound("myAudioPath/mySound.mp3", "myID", 3); - * function handleLoad(event) { - * createjs.Sound.play("myID"); - * // store off AbstractSoundInstance for controlling - * var myInstance = createjs.Sound.play("myID", {interrupt: createjs.Sound.INTERRUPT_ANY, loop:-1}); - * } - * - * NOTE to create an audio sprite that has not already been registered, both startTime and duration need to be set. - * This is only when creating a new audio sprite, not when playing using the id of an already registered audio sprite. - * - * Parameters Deprecated
      - * The parameters for this method are deprecated in favor of a single parameter that is an Object or {{#crossLink "PlayPropsConfig"}}{{/crossLink}}. - * - * @method play - * @param {String} src The src or ID of the audio. - * @param {String | Object} [interrupt="none"|options] This parameter will be renamed playProps in the next release.
      - * This parameter can be an instance of {{#crossLink "PlayPropsConfig"}}{{/crossLink}} or an Object that contains any or all optional properties by name, - * including: interrupt, delay, offset, loop, volume, pan, startTime, and duration (see the above code sample). - *
      OR
      - * Deprecated How to interrupt any currently playing instances of audio with the same source, - * if the maximum number of instances of the sound are already playing. Values are defined as INTERRUPT_TYPE - * constants on the Sound class, with the default defined by {{#crossLink "Sound/defaultInterruptBehavior:property"}}{{/crossLink}}. - * @param {Number} [delay=0] Deprecated The amount of time to delay the start of audio playback, in milliseconds. - * @param {Number} [offset=0] Deprecated The offset from the start of the audio to begin playback, in milliseconds. - * @param {Number} [loop=0] Deprecated How many times the audio loops when it reaches the end of playback. The default is 0 (no - * loops), and -1 can be used for infinite playback. - * @param {Number} [volume=1] Deprecated The volume of the sound, between 0 and 1. Note that the master volume is applied - * against the individual volume. - * @param {Number} [pan=0] Deprecated The left-right pan of the sound (if supported), between -1 (left) and 1 (right). - * @param {Number} [startTime=null] Deprecated To create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds. - * @param {Number} [duration=null] Deprecated To create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds. - * @return {AbstractSoundInstance} A {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} that can be controlled after it is created. - * @static - */ - s.play = function (src, interrupt, delay, offset, loop, volume, pan, startTime, duration) { - var playProps; - if (interrupt instanceof Object || interrupt instanceof createjs.PlayPropsConfig) { - playProps = createjs.PlayPropsConfig.create(interrupt); - } else { - playProps = createjs.PlayPropsConfig.create({interrupt:interrupt, delay:delay, offset:offset, loop:loop, volume:volume, pan:pan, startTime:startTime, duration:duration}); - } - var instance = s.createInstance(src, playProps.startTime, playProps.duration); - var ok = s._playInstance(instance, playProps); - if (!ok) {instance._playFailed();} - return instance; - }; - - /** - * Creates a {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} using the passed in src. If the src does not have a - * supported extension or if there is no available plugin, a default AbstractSoundInstance will be returned that can be - * called safely but does nothing. - * - *

      Example

      - * - * var myInstance = null; - * createjs.Sound.on("fileload", handleLoad); - * createjs.Sound.registerSound("myAudioPath/mySound.mp3", "myID", 3); - * function handleLoad(event) { - * myInstance = createjs.Sound.createInstance("myID"); - * // alternately we could call the following - * myInstance = createjs.Sound.createInstance("myAudioPath/mySound.mp3"); - * } - * - * NOTE to create an audio sprite that has not already been registered, both startTime and duration need to be set. - * This is only when creating a new audio sprite, not when playing using the id of an already registered audio sprite. - * - * @method createInstance - * @param {String} src The src or ID of the audio. - * @param {Number} [startTime=null] To create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds. - * @param {Number} [duration=null] To create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds. - * @return {AbstractSoundInstance} A {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} that can be controlled after it is created. - * Unsupported extensions will return the default AbstractSoundInstance. - * @since 0.4.0 - * @static - */ - s.createInstance = function (src, startTime, duration) { - if (!s.initializeDefaultPlugins()) {return new createjs.DefaultSoundInstance(src, startTime, duration);} - - var defaultPlayProps = s._defaultPlayPropsHash[src]; // for audio sprites, which create and store defaults by id - src = s._getSrcById(src); - - var details = s._parsePath(src.src); - - var instance = null; - if (details != null && details.src != null) { - SoundChannel.create(details.src); - if (startTime == null) {startTime = src.startTime;} - instance = s.activePlugin.create(details.src, startTime, duration || src.duration); - - defaultPlayProps = defaultPlayProps || s._defaultPlayPropsHash[details.src]; - if(defaultPlayProps) { - instance.applyPlayProps(defaultPlayProps); - } - } else { - instance = new createjs.DefaultSoundInstance(src, startTime, duration); - } - - instance.uniqueId = s._lastID++; - - return instance; - }; - - /** - * Stop all audio (global stop). Stopped audio is reset, and not paused. To play audio that has been stopped, - * call AbstractSoundInstance {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}}. - * - *

      Example

      - * - * createjs.Sound.stop(); - * - * @method stop - * @static - */ - s.stop = function () { - var instances = this._instances; - for (var i = instances.length; i--; ) { - instances[i].stop(); // NOTE stop removes instance from this._instances - } - }; - - /** - * Deprecated, please use {{#crossLink "Sound/volume:property"}}{{/crossLink}} instead. - * - * @method setVolume - * @param {Number} value The master volume value. The acceptable range is 0-1. - * @static - * @deprecated - */ - s.setVolume = function (value) { - if (Number(value) == null) {return false;} - value = Math.max(0, Math.min(1, value)); - s._masterVolume = value; - if (!this.activePlugin || !this.activePlugin.setVolume || !this.activePlugin.setVolume(value)) { - var instances = this._instances; - for (var i = 0, l = instances.length; i < l; i++) { - instances[i].setMasterVolume(value); - } - } - }; - - /** - * Deprecated, please use {{#crossLink "Sound/volume:property"}}{{/crossLink}} instead. - * - * @method getVolume - * @return {Number} The master volume, in a range of 0-1. - * @static - * @deprecated - */ - s.getVolume = function () { - return this._masterVolume; - }; - - /** - * Deprecated, please use {{#crossLink "Sound/muted:property"}}{{/crossLink}} instead. - * - * @method setMute - * @param {Boolean} value Whether the audio should be muted or not. - * @return {Boolean} If the mute was set. - * @static - * @since 0.4.0 - * @deprecated - */ - s.setMute = function (value) { - if (value == null) {return false;} - - this._masterMute = value; - if (!this.activePlugin || !this.activePlugin.setMute || !this.activePlugin.setMute(value)) { - var instances = this._instances; - for (var i = 0, l = instances.length; i < l; i++) { - instances[i].setMasterMute(value); - } - } - return true; - }; - - /** - * Deprecated, please use {{#crossLink "Sound/muted:property"}}{{/crossLink}} instead. - * - * @method getMute - * @return {Boolean} The mute value of Sound. - * @static - * @since 0.4.0 - * @deprecated - */ - s.getMute = function () { - return this._masterMute; - }; - - /** - * Set the default playback properties for all new SoundInstances of the passed in src or ID. - * See {{#crossLink "PlayPropsConfig"}}{{/crossLink}} for available properties. - * - * @method setDefaultPlayProps - * @param {String} src The src or ID used to register the audio. - * @param {Object | PlayPropsConfig} playProps The playback properties you would like to set. - * @since 0.6.1 - */ - s.setDefaultPlayProps = function(src, playProps) { - src = s._getSrcById(src); - s._defaultPlayPropsHash[s._parsePath(src.src).src] = createjs.PlayPropsConfig.create(playProps); - }; - - /** - * Get the default playback properties for the passed in src or ID. These properties are applied to all - * new SoundInstances. Returns null if default does not exist. - * - * @method getDefaultPlayProps - * @param {String} src The src or ID used to register the audio. - * @returns {PlayPropsConfig} returns an existing PlayPropsConfig or null if one does not exist - * @since 0.6.1 - */ - s.getDefaultPlayProps = function(src) { - src = s._getSrcById(src); - return s._defaultPlayPropsHash[s._parsePath(src.src).src]; - }; - - - /* --------------- - Internal methods - --------------- */ - /** - * Play an instance. This is called by the static API, as well as from plugins. This allows the core class to - * control delays. - * @method _playInstance - * @param {AbstractSoundInstance} instance The {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} to start playing. - * @param {PlayPropsConfig} playProps A PlayPropsConfig object. - * @return {Boolean} If the sound can start playing. Sounds that fail immediately will return false. Sounds that - * have a delay will return true, but may still fail to play. - * @protected - * @static - */ - s._playInstance = function (instance, playProps) { - var defaultPlayProps = s._defaultPlayPropsHash[instance.src] || {}; - if (playProps.interrupt == null) {playProps.interrupt = defaultPlayProps.interrupt || s.defaultInterruptBehavior}; - if (playProps.delay == null) {playProps.delay = defaultPlayProps.delay || 0;} - if (playProps.offset == null) {playProps.offset = instance.getPosition();} - if (playProps.loop == null) {playProps.loop = instance.loop;} - if (playProps.volume == null) {playProps.volume = instance.volume;} - if (playProps.pan == null) {playProps.pan = instance.pan;} - - if (playProps.delay == 0) { - var ok = s._beginPlaying(instance, playProps); - if (!ok) {return false;} - } else { - //Note that we can't pass arguments to proxy OR setTimeout (IE only), so just wrap the function call. - // OJR WebAudio may want to handle this differently, so it might make sense to move this functionality into the plugins in the future - var delayTimeoutId = setTimeout(function () { - s._beginPlaying(instance, playProps); - }, playProps.delay); - instance.delayTimeoutId = delayTimeoutId; - } - - this._instances.push(instance); - - return true; - }; - - /** - * Begin playback. This is called immediately or after delay by {{#crossLink "Sound/playInstance"}}{{/crossLink}}. - * @method _beginPlaying - * @param {AbstractSoundInstance} instance A {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} to begin playback. - * @param {PlayPropsConfig} playProps A PlayPropsConfig object. - * @return {Boolean} If the sound can start playing. If there are no available channels, or the instance fails to - * start, this will return false. - * @protected - * @static - */ - s._beginPlaying = function (instance, playProps) { - if (!SoundChannel.add(instance, playProps.interrupt)) { - return false; - } - var result = instance._beginPlaying(playProps); - if (!result) { - var index = createjs.indexOf(this._instances, instance); - if (index > -1) {this._instances.splice(index, 1);} - return false; - } - return true; - }; - - /** - * Get the source of a sound via the ID passed in with a register call. If no ID is found the value is returned - * instead. - * @method _getSrcById - * @param {String} value The ID the sound was registered with. - * @return {String} The source of the sound if it has been registered with this ID or the value that was passed in. - * @protected - * @static - */ - s._getSrcById = function (value) { - return s._idHash[value] || {src: value}; - }; - - /** - * A sound has completed playback, been interrupted, failed, or been stopped. This method removes the instance from - * Sound management. It will be added again, if the sound re-plays. Note that this method is called from the - * instances themselves. - * @method _playFinished - * @param {AbstractSoundInstance} instance The instance that finished playback. - * @protected - * @static - */ - s._playFinished = function (instance) { - SoundChannel.remove(instance); - var index = createjs.indexOf(this._instances, instance); - if (index > -1) {this._instances.splice(index, 1);} // OJR this will always be > -1, there is no way for an instance to exist without being added to this._instances - }; - - createjs.Sound = Sound; - - /** - * An internal class that manages the number of active {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} instances for - * each sound type. This method is only used internally by the {{#crossLink "Sound"}}{{/crossLink}} class. - * - * The number of sounds is artificially limited by Sound in order to prevent over-saturation of a - * single sound, as well as to stay within hardware limitations, although the latter may disappear with better - * browser support. - * - * When a sound is played, this class ensures that there is an available instance, or interrupts an appropriate - * sound that is already playing. - * #class SoundChannel - * @param {String} src The source of the instances - * @param {Number} [max=1] The number of instances allowed - * @constructor - * @protected - */ - function SoundChannel(src, max) { - this.init(src, max); - } - - /* ------------ - Static API - ------------ */ - /** - * A hash of channel instances indexed by source. - * #property channels - * @type {Object} - * @static - */ - SoundChannel.channels = {}; - - /** - * Create a sound channel. Note that if the sound channel already exists, this will fail. - * #method create - * @param {String} src The source for the channel - * @param {Number} max The maximum amount this channel holds. The default is {{#crossLink "SoundChannel.maxDefault"}}{{/crossLink}}. - * @return {Boolean} If the channels were created. - * @static - */ - SoundChannel.create = function (src, max) { - var channel = SoundChannel.get(src); - if (channel == null) { - SoundChannel.channels[src] = new SoundChannel(src, max); - return true; - } - return false; - }; - /** - * Delete a sound channel, stop and delete all related instances. Note that if the sound channel does not exist, this will fail. - * #method remove - * @param {String} src The source for the channel - * @return {Boolean} If the channels were deleted. - * @static - */ - SoundChannel.removeSrc = function (src) { - var channel = SoundChannel.get(src); - if (channel == null) {return false;} - channel._removeAll(); // this stops and removes all active instances - delete(SoundChannel.channels[src]); - return true; - }; - /** - * Delete all sound channels, stop and delete all related instances. - * #method removeAll - * @static - */ - SoundChannel.removeAll = function () { - for(var channel in SoundChannel.channels) { - SoundChannel.channels[channel]._removeAll(); // this stops and removes all active instances - } - SoundChannel.channels = {}; - }; - /** - * Add an instance to a sound channel. - * #method add - * @param {AbstractSoundInstance} instance The instance to add to the channel - * @param {String} interrupt The interrupt value to use. Please see the {{#crossLink "Sound/play"}}{{/crossLink}} - * for details on interrupt modes. - * @return {Boolean} The success of the method call. If the channel is full, it will return false. - * @static - */ - SoundChannel.add = function (instance, interrupt) { - var channel = SoundChannel.get(instance.src); - if (channel == null) {return false;} - return channel._add(instance, interrupt); - }; - /** - * Remove an instance from the channel. - * #method remove - * @param {AbstractSoundInstance} instance The instance to remove from the channel - * @return The success of the method call. If there is no channel, it will return false. - * @static - */ - SoundChannel.remove = function (instance) { - var channel = SoundChannel.get(instance.src); - if (channel == null) {return false;} - channel._remove(instance); - return true; - }; - /** - * Get the maximum number of sounds you can have in a channel. - * #method maxPerChannel - * @return {Number} The maximum number of sounds you can have in a channel. - */ - SoundChannel.maxPerChannel = function () { - return p.maxDefault; - }; - /** - * Get a channel instance by its src. - * #method get - * @param {String} src The src to use to look up the channel - * @static - */ - SoundChannel.get = function (src) { - return SoundChannel.channels[src]; - }; - - var p = SoundChannel.prototype; - p.constructor = SoundChannel; - - /** - * REMOVED. Removed in favor of using `MySuperClass_constructor`. - * See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}} - * for details. - * - * There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance. - * - * @method initialize - * @protected - * @deprecated - */ - // p.initialize = function() {}; // searchable for devs wondering where it is. - - - /** - * The source of the channel. - * #property src - * @type {String} - */ - p.src = null; - - /** - * The maximum number of instances in this channel. -1 indicates no limit - * #property max - * @type {Number} - */ - p.max = null; - - /** - * The default value to set for max, if it isn't passed in. Also used if -1 is passed. - * #property maxDefault - * @type {Number} - * @default 100 - * @since 0.4.0 - */ - p.maxDefault = 100; - - /** - * The current number of active instances. - * #property length - * @type {Number} - */ - p.length = 0; - - /** - * Initialize the channel. - * #method init - * @param {String} src The source of the channel - * @param {Number} max The maximum number of instances in the channel - * @protected - */ - p.init = function (src, max) { - this.src = src; - this.max = max || this.maxDefault; - if (this.max == -1) {this.max = this.maxDefault;} - this._instances = []; - }; - - /** - * Get an instance by index. - * #method get - * @param {Number} index The index to return. - * @return {AbstractSoundInstance} The AbstractSoundInstance at a specific instance. - */ - p._get = function (index) { - return this._instances[index]; - }; - - /** - * Add a new instance to the channel. - * #method add - * @param {AbstractSoundInstance} instance The instance to add. - * @return {Boolean} The success of the method call. If the channel is full, it will return false. - */ - p._add = function (instance, interrupt) { - if (!this._getSlot(interrupt, instance)) {return false;} - this._instances.push(instance); - this.length++; - return true; - }; - - /** - * Remove an instance from the channel, either when it has finished playing, or it has been interrupted. - * #method remove - * @param {AbstractSoundInstance} instance The instance to remove - * @return {Boolean} The success of the remove call. If the instance is not found in this channel, it will - * return false. - */ - p._remove = function (instance) { - var index = createjs.indexOf(this._instances, instance); - if (index == -1) {return false;} - this._instances.splice(index, 1); - this.length--; - return true; - }; - - /** - * Stop playback and remove all instances from the channel. Usually in response to a delete call. - * #method removeAll - */ - p._removeAll = function () { - // Note that stop() removes the item from the list - for (var i=this.length-1; i>=0; i--) { - this._instances[i].stop(); - } - }; - - /** - * Get an available slot depending on interrupt value and if slots are available. - * #method getSlot - * @param {String} interrupt The interrupt value to use. - * @param {AbstractSoundInstance} instance The sound instance that will go in the channel if successful. - * @return {Boolean} Determines if there is an available slot. Depending on the interrupt mode, if there are no slots, - * an existing AbstractSoundInstance may be interrupted. If there are no slots, this method returns false. - */ - p._getSlot = function (interrupt, instance) { - var target, replacement; - - if (interrupt != Sound.INTERRUPT_NONE) { - // First replacement candidate - replacement = this._get(0); - if (replacement == null) { - return true; - } - } - - for (var i = 0, l = this.max; i < l; i++) { - target = this._get(i); - - // Available Space - if (target == null) { - return true; - } - - // Audio is complete or not playing - if (target.playState == Sound.PLAY_FINISHED || - target.playState == Sound.PLAY_INTERRUPTED || - target.playState == Sound.PLAY_FAILED) { - replacement = target; - break; - } - - if (interrupt == Sound.INTERRUPT_NONE) { - continue; - } - - // Audio is a better candidate than the current target, according to playhead - if ((interrupt == Sound.INTERRUPT_EARLY && target.getPosition() < replacement.getPosition()) || - (interrupt == Sound.INTERRUPT_LATE && target.getPosition() > replacement.getPosition())) { - replacement = target; - } - } - - if (replacement != null) { - replacement._interrupt(); - this._remove(replacement); - return true; - } - return false; - }; - - p.toString = function () { - return "[Sound SoundChannel]"; - }; - // do not add SoundChannel to namespace - -}()); - -//############################################################################## -// AbstractSoundInstance.js -//############################################################################## - -this.createjs = this.createjs || {}; - -/** - * A AbstractSoundInstance is created when any calls to the Sound API method {{#crossLink "Sound/play"}}{{/crossLink}} or - * {{#crossLink "Sound/createInstance"}}{{/crossLink}} are made. The AbstractSoundInstance is returned by the active plugin - * for control by the user. - * - *

      Example

      - * - * var myInstance = createjs.Sound.play("myAssetPath/mySrcFile.mp3"); - * - * A number of additional parameters provide a quick way to determine how a sound is played. Please see the Sound - * API method {{#crossLink "Sound/play"}}{{/crossLink}} for a list of arguments. - * - * Once a AbstractSoundInstance is created, a reference can be stored that can be used to control the audio directly through - * the AbstractSoundInstance. If the reference is not stored, the AbstractSoundInstance will play out its audio (and any loops), and - * is then de-referenced from the {{#crossLink "Sound"}}{{/crossLink}} class so that it can be cleaned up. If audio - * playback has completed, a simple call to the {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}} instance method - * will rebuild the references the Sound class need to control it. - * - * var myInstance = createjs.Sound.play("myAssetPath/mySrcFile.mp3", {loop:2}); - * myInstance.on("loop", handleLoop); - * function handleLoop(event) { - * myInstance.volume = myInstance.volume * 0.5; - * } - * - * Events are dispatched from the instance to notify when the sound has completed, looped, or when playback fails - * - * var myInstance = createjs.Sound.play("myAssetPath/mySrcFile.mp3"); - * myInstance.on("complete", handleComplete); - * myInstance.on("loop", handleLoop); - * myInstance.on("failed", handleFailed); - * - * - * @class AbstractSoundInstance - * @param {String} src The path to and file name of the sound. - * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. - * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. - * @param {Object} playbackResource Any resource needed by plugin to support audio playback. - * @extends EventDispatcher - * @constructor - */ - -(function () { - "use strict"; - - -// Constructor: - var AbstractSoundInstance = function (src, startTime, duration, playbackResource) { - this.EventDispatcher_constructor(); - - - // public properties: - /** - * The source of the sound. - * @property src - * @type {String} - * @default null - */ - this.src = src; - - /** - * The unique ID of the instance. This is set by {{#crossLink "Sound"}}{{/crossLink}}. - * @property uniqueId - * @type {String} | Number - * @default -1 - */ - this.uniqueId = -1; - - /** - * The play state of the sound. Play states are defined as constants on {{#crossLink "Sound"}}{{/crossLink}}. - * @property playState - * @type {String} - * @default null - */ - this.playState = null; - - /** - * A Timeout created by {{#crossLink "Sound"}}{{/crossLink}} when this AbstractSoundInstance is played with a delay. - * This allows AbstractSoundInstance to remove the delay if stop, pause, or cleanup are called before playback begins. - * @property delayTimeoutId - * @type {timeoutVariable} - * @default null - * @protected - * @since 0.4.0 - */ - this.delayTimeoutId = null; - // TODO consider moving delay into AbstractSoundInstance so it can be handled by plugins - - - // private properties - // Getter / Setter Properties - // OJR TODO find original reason that we didn't use defined functions. I think it was performance related - /** - * The volume of the sound, between 0 and 1. - * - * The actual output volume of a sound can be calculated using: - * myInstance.volume * createjs.Sound.getVolume(); - * - * @property volume - * @type {Number} - * @default 1 - */ - this._volume = 1; - Object.defineProperty(this, "volume", { - get: this.getVolume, - set: this.setVolume - }); - - /** - * The pan of the sound, between -1 (left) and 1 (right). Note that pan is not supported by HTML Audio. - * - *
      Note in WebAudioPlugin this only gives us the "x" value of what is actually 3D audio. - * - * @property pan - * @type {Number} - * @default 0 - */ - this._pan = 0; - Object.defineProperty(this, "pan", { - get: this.getPan, - set: this.setPan - }); - - /** - * Audio sprite property used to determine the starting offset. - * @property startTime - * @type {Number} - * @default 0 - * @since 0.6.1 - */ - this._startTime = Math.max(0, startTime || 0); - Object.defineProperty(this, "startTime", { - get: this.getStartTime, - set: this.setStartTime - }); - - /** - * The length of the audio clip, in milliseconds. - * - * @property duration - * @type {Number} - * @default 0 - * @since 0.6.0 - */ - this._duration = Math.max(0, duration || 0); - Object.defineProperty(this, "duration", { - get: this.getDuration, - set: this.setDuration - }); - - /** - * Object that holds plugin specific resource need for audio playback. - * This is set internally by the plugin. For example, WebAudioPlugin will set an array buffer, - * HTMLAudioPlugin will set a tag, FlashAudioPlugin will set a flash reference. - * - * @property playbackResource - * @type {Object} - * @default null - */ - this._playbackResource = null; - Object.defineProperty(this, "playbackResource", { - get: this.getPlaybackResource, - set: this.setPlaybackResource - }); - if(playbackResource !== false && playbackResource !== true) { this.setPlaybackResource(playbackResource); } - - /** - * The position of the playhead in milliseconds. This can be set while a sound is playing, paused, or stopped. - * - * @property position - * @type {Number} - * @default 0 - * @since 0.6.0 - */ - this._position = 0; - Object.defineProperty(this, "position", { - get: this.getPosition, - set: this.setPosition - }); - - /** - * The number of play loops remaining. Negative values will loop infinitely. - * - * @property loop - * @type {Number} - * @default 0 - * @public - * @since 0.6.0 - */ - this._loop = 0; - Object.defineProperty(this, "loop", { - get: this.getLoop, - set: this.setLoop - }); - - /** - * Determines if the audio is currently muted. - * - * @property muted - * @type {Boolean} - * @default false - * @since 0.6.0 - */ - this._muted = false; - Object.defineProperty(this, "muted", { - get: this.getMuted, - set: this.setMuted - }); - - /** - * Tells you if the audio is currently paused. - * - * @property paused - * @type {Boolean} - */ - this._paused = false; - Object.defineProperty(this, "paused", { - get: this.getPaused, - set: this.setPaused - }); - - - // Events - /** - * The event that is fired when playback has started successfully. - * @event succeeded - * @param {Object} target The object that dispatched the event. - * @param {String} type The event type. - * @since 0.4.0 - */ - - /** - * The event that is fired when playback is interrupted. This happens when another sound with the same - * src property is played using an interrupt value that causes this instance to stop playing. - * @event interrupted - * @param {Object} target The object that dispatched the event. - * @param {String} type The event type. - * @since 0.4.0 - */ - - /** - * The event that is fired when playback has failed. This happens when there are too many channels with the same - * src property already playing (and the interrupt value doesn't cause an interrupt of another instance), or - * the sound could not be played, perhaps due to a 404 error. - * @event failed - * @param {Object} target The object that dispatched the event. - * @param {String} type The event type. - * @since 0.4.0 - */ - - /** - * The event that is fired when a sound has completed playing but has loops remaining. - * @event loop - * @param {Object} target The object that dispatched the event. - * @param {String} type The event type. - * @since 0.4.0 - */ - - /** - * The event that is fired when playback completes. This means that the sound has finished playing in its - * entirety, including its loop iterations. - * @event complete - * @param {Object} target The object that dispatched the event. - * @param {String} type The event type. - * @since 0.4.0 - */ - }; - - var p = createjs.extend(AbstractSoundInstance, createjs.EventDispatcher); - - // TODO: deprecated - // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. - - -// Public Methods: - /** - * Play an instance. This method is intended to be called on SoundInstances that already exist (created - * with the Sound API {{#crossLink "Sound/createInstance"}}{{/crossLink}} or {{#crossLink "Sound/play"}}{{/crossLink}}). - * - *

      Example

      - * - * var myInstance = createjs.Sound.createInstance(mySrc); - * myInstance.play({interrupt:createjs.Sound.INTERRUPT_ANY, loop:2, pan:0.5}); - * - * Note that if this sound is already playing, this call will still set the passed in parameters. - - * Parameters Deprecated
      - * The parameters for this method are deprecated in favor of a single parameter that is an Object or {{#crossLink "PlayPropsConfig"}}{{/crossLink}}. - * - * @method play - * @param {String | Object} [interrupt="none"|options] This parameter will be renamed playProps in the next release.
      - * This parameter can be an instance of {{#crossLink "PlayPropsConfig"}}{{/crossLink}} or an Object that contains any or all optional properties by name, - * including: interrupt, delay, offset, loop, volume, pan, startTime, and duration (see the above code sample). - *
      OR
      - * Deprecated How to interrupt any currently playing instances of audio with the same source, - * if the maximum number of instances of the sound are already playing. Values are defined as INTERRUPT_TYPE - * constants on the Sound class, with the default defined by {{#crossLink "Sound/defaultInterruptBehavior:property"}}{{/crossLink}}. - * @param {Number} [delay=0] Deprecated The amount of time to delay the start of audio playback, in milliseconds. - * @param {Number} [offset=0] Deprecated The offset from the start of the audio to begin playback, in milliseconds. - * @param {Number} [loop=0] Deprecated How many times the audio loops when it reaches the end of playback. The default is 0 (no - * loops), and -1 can be used for infinite playback. - * @param {Number} [volume=1] Deprecated The volume of the sound, between 0 and 1. Note that the master volume is applied - * against the individual volume. - * @param {Number} [pan=0] Deprecated The left-right pan of the sound (if supported), between -1 (left) and 1 (right). - * Note that pan is not supported for HTML Audio. - * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. - */ - p.play = function (interrupt, delay, offset, loop, volume, pan) { - var playProps; - if (interrupt instanceof Object || interrupt instanceof createjs.PlayPropsConfig) { - playProps = createjs.PlayPropsConfig.create(interrupt); - } else { - playProps = createjs.PlayPropsConfig.create({interrupt:interrupt, delay:delay, offset:offset, loop:loop, volume:volume, pan:pan}); - } - - if (this.playState == createjs.Sound.PLAY_SUCCEEDED) { - this.applyPlayProps(playProps); - if (this._paused) { this.setPaused(false); } - return; - } - this._cleanUp(); - createjs.Sound._playInstance(this, playProps); // make this an event dispatch?? - return this; - }; - - /** - * Stop playback of the instance. Stopped sounds will reset their position to 0, and calls to {{#crossLink "AbstractSoundInstance/resume"}}{{/crossLink}} - * will fail. To start playback again, call {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}}. - * - *

      Example

      - * - * myInstance.stop(); - * - * @method stop - * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. - */ - p.stop = function () { - this._position = 0; - this._paused = false; - this._handleStop(); - this._cleanUp(); - this.playState = createjs.Sound.PLAY_FINISHED; - return this; - }; - - /** - * Remove all external references and resources from AbstractSoundInstance. Note this is irreversible and AbstractSoundInstance will no longer work - * @method destroy - * @since 0.6.0 - */ - p.destroy = function() { - this._cleanUp(); - this.src = null; - this.playbackResource = null; - - this.removeAllEventListeners(); - }; - - /** - * Takes an PlayPropsConfig or Object with the same properties and sets them on this instance. - * @method applyPlayProps - * @param {PlayPropsConfig | Object} playProps A PlayPropsConfig or object containing the same properties. - * @since 0.6.1 - * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. - */ - p.applyPlayProps = function(playProps) { - if (playProps.offset != null) { this.setPosition(playProps.offset) } - if (playProps.loop != null) { this.setLoop(playProps.loop); } - if (playProps.volume != null) { this.setVolume(playProps.volume); } - if (playProps.pan != null) { this.setPan(playProps.pan); } - if (playProps.startTime != null) { - this.setStartTime(playProps.startTime); - this.setDuration(playProps.duration); - } - return this; - }; - - p.toString = function () { - return "[AbstractSoundInstance]"; - }; - -// get/set methods that allow support for IE8 - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/paused:property"}}{{/crossLink}} directly as a property, - * - * @deprecated - * @method getPaused - * @returns {boolean} If the instance is currently paused - * @since 0.6.0 - */ - p.getPaused = function() { - return this._paused; - }; - - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/paused:property"}}{{/crossLink}} directly as a property - * - * @deprecated - * @method setPaused - * @param {boolean} value - * @since 0.6.0 - * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. - */ - p.setPaused = function (value) { - if ((value !== true && value !== false) || this._paused == value) {return;} - if (value == true && this.playState != createjs.Sound.PLAY_SUCCEEDED) {return;} - this._paused = value; - if(value) { - this._pause(); - } else { - this._resume(); - } - clearTimeout(this.delayTimeoutId); - return this; - }; - - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/volume:property"}}{{/crossLink}} directly as a property - * - * @deprecated - * @method setVolume - * @param {Number} value The volume to set, between 0 and 1. - * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. - */ - p.setVolume = function (value) { - if (value == this._volume) { return this; } - this._volume = Math.max(0, Math.min(1, value)); - if (!this._muted) { - this._updateVolume(); - } - return this; - }; - - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/volume:property"}}{{/crossLink}} directly as a property - * - * @deprecated - * @method getVolume - * @return {Number} The current volume of the sound instance. - */ - p.getVolume = function () { - return this._volume; - }; - - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/muted:property"}}{{/crossLink}} directly as a property - * - * @deprecated - * @method setMuted - * @param {Boolean} value If the sound should be muted. - * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. - * @since 0.6.0 - */ - p.setMuted = function (value) { - if (value !== true && value !== false) {return;} - this._muted = value; - this._updateVolume(); - return this; - }; - - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/muted:property"}}{{/crossLink}} directly as a property - * - * @deprecated - * @method getMuted - * @return {Boolean} If the sound is muted. - * @since 0.6.0 - */ - p.getMuted = function () { - return this._muted; - }; - - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/pan:property"}}{{/crossLink}} directly as a property - * - * @deprecated - * @method setPan - * @param {Number} value The pan value, between -1 (left) and 1 (right). - * @return {AbstractSoundInstance} Returns reference to itself for chaining calls - */ - p.setPan = function (value) { - if(value == this._pan) { return this; } - this._pan = Math.max(-1, Math.min(1, value)); - this._updatePan(); - return this; - }; - - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/pan:property"}}{{/crossLink}} directly as a property - * - * @deprecated - * @method getPan - * @return {Number} The value of the pan, between -1 (left) and 1 (right). - */ - p.getPan = function () { - return this._pan; - }; - - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/position:property"}}{{/crossLink}} directly as a property - * - * @deprecated - * @method getPosition - * @return {Number} The position of the playhead in the sound, in milliseconds. - */ - p.getPosition = function () { - if (!this._paused && this.playState == createjs.Sound.PLAY_SUCCEEDED) { - this._position = this._calculateCurrentPosition(); - } - return this._position; - }; - - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/position:property"}}{{/crossLink}} directly as a property - * - * @deprecated - * @method setPosition - * @param {Number} value The position to place the playhead, in milliseconds. - * @return {AbstractSoundInstance} Returns reference to itself for chaining calls - */ - p.setPosition = function (value) { - this._position = Math.max(0, value); - if (this.playState == createjs.Sound.PLAY_SUCCEEDED) { - this._updatePosition(); - } - return this; - }; - - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/startTime:property"}}{{/crossLink}} directly as a property - * - * @deprecated - * @method getStartTime - * @return {Number} The startTime of the sound instance in milliseconds. - */ - p.getStartTime = function () { - return this._startTime; - }; - - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/startTime:property"}}{{/crossLink}} directly as a property - * - * @deprecated - * @method setStartTime - * @param {number} value The new startTime time in milli seconds. - * @return {AbstractSoundInstance} Returns reference to itself for chaining calls - */ - p.setStartTime = function (value) { - if (value == this._startTime) { return this; } - this._startTime = Math.max(0, value || 0); - this._updateStartTime(); - return this; - }; - - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/duration:property"}}{{/crossLink}} directly as a property - * - * @deprecated - * @method getDuration - * @return {Number} The duration of the sound instance in milliseconds. - */ - p.getDuration = function () { - return this._duration; - }; - - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/duration:property"}}{{/crossLink}} directly as a property - * - * @deprecated - * @method setDuration - * @param {number} value The new duration time in milli seconds. - * @return {AbstractSoundInstance} Returns reference to itself for chaining calls - * @since 0.6.0 - */ - p.setDuration = function (value) { - if (value == this._duration) { return this; } - this._duration = Math.max(0, value || 0); - this._updateDuration(); - return this; - }; - - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/playbackResource:property"}}{{/crossLink}} directly as a property - * - * @deprecated - * @method setPlayback - * @param {Object} value The new playback resource. - * @return {AbstractSoundInstance} Returns reference to itself for chaining calls - * @since 0.6.0 - **/ - p.setPlaybackResource = function (value) { - this._playbackResource = value; - if (this._duration == 0) { this._setDurationFromSource(); } - return this; - }; - - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/playbackResource:property"}}{{/crossLink}} directly as a property - * - * @deprecated - * @method setPlayback - * @param {Object} value The new playback resource. - * @return {Object} playback resource used for playing audio - * @since 0.6.0 - **/ - p.getPlaybackResource = function () { - return this._playbackResource; - }; - - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/loop:property"}}{{/crossLink}} directly as a property - * - * @deprecated - * @method getLoop - * @return {number} - * @since 0.6.0 - **/ - p.getLoop = function () { - return this._loop; - }; - - /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/loop:property"}}{{/crossLink}} directly as a property, - * - * @deprecated - * @method setLoop - * @param {number} value The number of times to loop after play. - * @since 0.6.0 - */ - p.setLoop = function (value) { - if(this._playbackResource != null) { - // remove looping - if (this._loop != 0 && value == 0) { - this._removeLooping(value); - } - // add looping - else if (this._loop == 0 && value != 0) { - this._addLooping(value); - } - } - this._loop = value; - }; - - -// Private Methods: - /** - * A helper method that dispatches all events for AbstractSoundInstance. - * @method _sendEvent - * @param {String} type The event type - * @protected - */ - p._sendEvent = function (type) { - var event = new createjs.Event(type); - this.dispatchEvent(event); - }; - - /** - * Clean up the instance. Remove references and clean up any additional properties such as timers. - * @method _cleanUp - * @protected - */ - p._cleanUp = function () { - clearTimeout(this.delayTimeoutId); // clear timeout that plays delayed sound - this._handleCleanUp(); - this._paused = false; - - createjs.Sound._playFinished(this); // TODO change to an event - }; - - /** - * The sound has been interrupted. - * @method _interrupt - * @protected - */ - p._interrupt = function () { - this._cleanUp(); - this.playState = createjs.Sound.PLAY_INTERRUPTED; - this._sendEvent("interrupted"); - }; - - /** - * Called by the Sound class when the audio is ready to play (delay has completed). Starts sound playing if the - * src is loaded, otherwise playback will fail. - * @method _beginPlaying - * @param {PlayPropsConfig} playProps A PlayPropsConfig object. - * @return {Boolean} If playback succeeded. - * @protected - */ - // OJR FlashAudioSoundInstance overwrites - p._beginPlaying = function (playProps) { - this.setPosition(playProps.offset); - this.setLoop(playProps.loop); - this.setVolume(playProps.volume); - this.setPan(playProps.pan); - if (playProps.startTime != null) { - this.setStartTime(playProps.startTime); - this.setDuration(playProps.duration); - } - - if (this._playbackResource != null && this._position < this._duration) { - this._paused = false; - this._handleSoundReady(); - this.playState = createjs.Sound.PLAY_SUCCEEDED; - this._sendEvent("succeeded"); - return true; - } else { - this._playFailed(); - return false; - } - }; - - /** - * Play has failed, which can happen for a variety of reasons. - * Cleans up instance and dispatches failed event - * @method _playFailed - * @private - */ - p._playFailed = function () { - this._cleanUp(); - this.playState = createjs.Sound.PLAY_FAILED; - this._sendEvent("failed"); - }; - - /** - * Audio has finished playing. Manually loop it if required. - * @method _handleSoundComplete - * @param event - * @protected - */ - p._handleSoundComplete = function (event) { - this._position = 0; // have to set this as it can be set by pause during playback - - if (this._loop != 0) { - this._loop--; // NOTE this introduces a theoretical limit on loops = float max size x 2 - 1 - this._handleLoop(); - this._sendEvent("loop"); - return; - } - - this._cleanUp(); - this.playState = createjs.Sound.PLAY_FINISHED; - this._sendEvent("complete"); - }; - -// Plugin specific code - /** - * Handles starting playback when the sound is ready for playing. - * @method _handleSoundReady - * @protected - */ - p._handleSoundReady = function () { - // plugin specific code - }; - - /** - * Internal function used to update the volume based on the instance volume, master volume, instance mute value, - * and master mute value. - * @method _updateVolume - * @protected - */ - p._updateVolume = function () { - // plugin specific code - }; - - /** - * Internal function used to update the pan - * @method _updatePan - * @protected - * @since 0.6.0 - */ - p._updatePan = function () { - // plugin specific code - }; - - /** - * Internal function used to update the startTime of the audio. - * @method _updateStartTime - * @protected - * @since 0.6.1 - */ - p._updateStartTime = function () { - // plugin specific code - }; - - /** - * Internal function used to update the duration of the audio. - * @method _updateDuration - * @protected - * @since 0.6.0 - */ - p._updateDuration = function () { - // plugin specific code - }; - - /** - * Internal function used to get the duration of the audio from the source we'll be playing. - * @method _updateDuration - * @protected - * @since 0.6.0 - */ - p._setDurationFromSource = function () { - // plugin specific code - }; - - /** - * Internal function that calculates the current position of the playhead and sets this._position to that value - * @method _calculateCurrentPosition - * @protected - * @since 0.6.0 - */ - p._calculateCurrentPosition = function () { - // plugin specific code that sets this.position - }; - - /** - * Internal function used to update the position of the playhead. - * @method _updatePosition - * @protected - * @since 0.6.0 - */ - p._updatePosition = function () { - // plugin specific code - }; - - /** - * Internal function called when looping is removed during playback. - * @method _removeLooping - * @param {number} value The number of times to loop after play. - * @protected - * @since 0.6.0 - */ - p._removeLooping = function (value) { - // plugin specific code - }; - - /** - * Internal function called when looping is added during playback. - * @method _addLooping - * @param {number} value The number of times to loop after play. - * @protected - * @since 0.6.0 - */ - p._addLooping = function (value) { - // plugin specific code - }; - - /** - * Internal function called when pausing playback - * @method _pause - * @protected - * @since 0.6.0 - */ - p._pause = function () { - // plugin specific code - }; - - /** - * Internal function called when resuming playback - * @method _resume - * @protected - * @since 0.6.0 - */ - p._resume = function () { - // plugin specific code - }; - - /** - * Internal function called when stopping playback - * @method _handleStop - * @protected - * @since 0.6.0 - */ - p._handleStop = function() { - // plugin specific code - }; - - /** - * Internal function called when AbstractSoundInstance is being cleaned up - * @method _handleCleanUp - * @protected - * @since 0.6.0 - */ - p._handleCleanUp = function() { - // plugin specific code - }; - - /** - * Internal function called when AbstractSoundInstance has played to end and is looping - * @method _handleLoop - * @protected - * @since 0.6.0 - */ - p._handleLoop = function () { - // plugin specific code - }; - - createjs.AbstractSoundInstance = createjs.promote(AbstractSoundInstance, "EventDispatcher"); - createjs.DefaultSoundInstance = createjs.AbstractSoundInstance; // used when no plugin is supported +this.createjs = this.createjs || {}; + + + +(function () { + "use strict"; + + /** + * The Sound class is the public API for creating sounds, controlling the overall sound levels, and managing plugins. + * All Sound APIs on this class are static. + * + * Registering and Preloading
      + * Before you can play a sound, it must be registered. You can do this with {{#crossLink "Sound/registerSound"}}{{/crossLink}}, + * or register multiple sounds using {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. If you don't register a + * sound prior to attempting to play it using {{#crossLink "Sound/play"}}{{/crossLink}} or create it using {{#crossLink "Sound/createInstance"}}{{/crossLink}}, + * the sound source will be automatically registered but playback will fail as the source will not be ready. If you use + * PreloadJS, registration is handled for you when the sound is + * preloaded. It is recommended to preload sounds either internally using the register functions or externally using + * PreloadJS so they are ready when you want to use them. + * + * Playback
      + * To play a sound once it's been registered and preloaded, use the {{#crossLink "Sound/play"}}{{/crossLink}} method. + * This method returns a {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} which can be paused, resumed, muted, etc. + * Please see the {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} documentation for more on the instance control APIs. + * + * Plugins
      + * By default, the {{#crossLink "WebAudioPlugin"}}{{/crossLink}} or the {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}} + * are used (when available), although developers can change plugin priority or add new plugins (such as the + * provided {{#crossLink "FlashAudioPlugin"}}{{/crossLink}}). Please see the {{#crossLink "Sound"}}{{/crossLink}} API + * methods for more on the playback and plugin APIs. To install plugins, or specify a different plugin order, see + * {{#crossLink "Sound/installPlugins"}}{{/crossLink}}. + * + *

      Example

      + * + * createjs.FlashAudioPlugin.swfPath = "../src/soundjs/flashaudio"; + * createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.FlashAudioPlugin]); + * createjs.Sound.alternateExtensions = ["mp3"]; + * createjs.Sound.on("fileload", createjs.proxy(this.loadHandler, (this))); + * createjs.Sound.registerSound("path/to/mySound.ogg", "sound"); + * function loadHandler(event) { + * // This is fired for each sound that is registered. + * var instance = createjs.Sound.play("sound"); // play using id. Could also use full source path or event.src. + * instance.on("complete", createjs.proxy(this.handleComplete, this)); + * instance.volume = 0.5; + * } + * + * The maximum number of concurrently playing instances of the same sound can be specified in the "data" argument + * of {{#crossLink "Sound/registerSound"}}{{/crossLink}}. Note that if not specified, the active plugin will apply + * a default limit. Currently HTMLAudioPlugin sets a default limit of 2, while WebAudioPlugin and FlashAudioPlugin set a + * default limit of 100. + * + * createjs.Sound.registerSound("sound.mp3", "soundId", 4); + * + * Sound can be used as a plugin with PreloadJS to help preload audio properly. Audio preloaded with PreloadJS is + * automatically registered with the Sound class. When audio is not preloaded, Sound will do an automatic internal + * load. As a result, it may fail to play the first time play is called if the audio is not finished loading. Use the + * {{#crossLink "Sound/fileload"}}{{/crossLink}} event to determine when a sound has finished internally preloading. + * It is recommended that all audio is preloaded before it is played. + * + * var queue = new createjs.LoadQueue(); + * queue.installPlugin(createjs.Sound); + * + * Audio Sprites
      + * SoundJS has added support for {{#crossLink "AudioSprite"}}{{/crossLink}}, available as of version 0.6.0. + * For those unfamiliar with audio sprites, they are much like CSS sprites or sprite sheets: multiple audio assets + * grouped into a single file. + * + *

      Example

      + * + * var assetsPath = "./assets/"; + * var sounds = [{ + * src:"MyAudioSprite.ogg", data: { + * audioSprite: [ + * {id:"sound1", startTime:0, duration:500}, + * {id:"sound2", startTime:1000, duration:400}, + * {id:"sound3", startTime:1700, duration: 1000} + * ]} + * } + * ]; + * createjs.Sound.alternateExtensions = ["mp3"]; + * createjs.Sound.on("fileload", loadSound); + * createjs.Sound.registerSounds(sounds, assetsPath); + * // after load is complete + * createjs.Sound.play("sound2"); + * + * Mobile Safe Approach
      + * Mobile devices require sounds to be played inside of a user initiated event (touch/click) in varying degrees. + * As of SoundJS 0.4.1, you can launch a site inside of a user initiated event and have audio playback work. To + * enable as broadly as possible, the site needs to setup the Sound plugin in its initialization (for example via + * createjs.Sound.initializeDefaultPlugins();), and all sounds need to be played in the scope of the + * application. See the MobileSafe demo for a working example. + * + *

      Example

      + * + * document.getElementById("status").addEventListener("click", handleTouch, false); // works on Android and iPad + * function handleTouch(event) { + * document.getElementById("status").removeEventListener("click", handleTouch, false); // remove the listener + * var thisApp = new myNameSpace.MyApp(); // launch the app + * } + * + * Loading Alternate Paths and Extensionless Files
      + * SoundJS supports loading alternate paths and extensionless files by passing an object for src that has various paths + * with property labels matching the extension. These labels are how SoundJS determines if the browser will support the sound. + * Priority is determined by the property order (first property is tried first). This is supported by both internal loading + * and loading with PreloadJS. + * + * Note an id is required for playback. + * + *

      Example

      + * + * var sounds = {path:"./audioPath/", + * manifest: [ + * {id: "cool", src: {mp3:"mp3/awesome.mp3", ogg:"noExtensionOggFile"}} + * ]}; + * + * createjs.Sound.alternateExtensions = ["mp3"]; + * createjs.Sound.addEventListener("fileload", handleLoad); + * createjs.Sound.registerSounds(sounds); + * + *

      Known Browser and OS issues

      + * IE 9 HTML Audio limitations
      + *
      • There is a delay in applying volume changes to tags that occurs once playback is started. So if you have + * muted all sounds, they will all play during this delay until the mute applies internally. This happens regardless of + * when or how you apply the volume change, as the tag seems to need to play to apply it.
      • + *
      • MP3 encoding will not always work for audio tags, particularly in Internet Explorer. We've found default + * encoding with 64kbps works.
      • + *
      • Occasionally very short samples will get cut off.
      • + *
      • There is a limit to how many audio tags you can load and play at once, which appears to be determined by + * hardware and browser settings. See {{#crossLink "HTMLAudioPlugin.MAX_INSTANCES"}}{{/crossLink}} for a safe estimate.
      + * + * Firefox 25 Web Audio limitations + *
      • mp3 audio files do not load properly on all windows machines, reported + * here.
        + * For this reason it is recommended to pass another FF supported type (ie ogg) first until this bug is resolved, if possible.
      + + * Safari limitations
      + *
      • Safari requires Quicktime to be installed for audio playback.
      + * + * iOS 6 Web Audio limitations
      + *
      • Sound is initially muted and will only unmute through play being called inside a user initiated event + * (touch/click).
      • + *
      • A bug exists that will distort un-cached web audio when a video element is present in the DOM that has audio at a different sampleRate.
      • + *
      + * + * Android HTML Audio limitations
      + *
      • We have no control over audio volume. Only the user can set volume on their device.
      • + *
      • We can only play audio inside a user event (touch/click). This currently means you cannot loop sound or use + * a delay.
      + * + * Web Audio and PreloadJS
      + *
      • Web Audio must be loaded through XHR, therefore when used with PreloadJS tag loading is not possible. This means that tag loading cannot + * be used to avoid cross domain issues if WebAudioPlugin is used
        • + * + * @class Sound + * @static + * @uses EventDispatcher + */ + function Sound() { + throw "Sound cannot be instantiated"; + } + + var s = Sound; + + +// Static Properties + /** + * The interrupt value to interrupt any currently playing instance with the same source, if the maximum number of + * instances of the sound are already playing. + * @property INTERRUPT_ANY + * @type {String} + * @default any + * @static + */ + s.INTERRUPT_ANY = "any"; + + /** + * The interrupt value to interrupt the earliest currently playing instance with the same source that progressed the + * least distance in the audio track, if the maximum number of instances of the sound are already playing. + * @property INTERRUPT_EARLY + * @type {String} + * @default early + * @static + */ + s.INTERRUPT_EARLY = "early"; + + /** + * The interrupt value to interrupt the currently playing instance with the same source that progressed the most + * distance in the audio track, if the maximum number of instances of the sound are already playing. + * @property INTERRUPT_LATE + * @type {String} + * @default late + * @static + */ + s.INTERRUPT_LATE = "late"; + + /** + * The interrupt value to not interrupt any currently playing instances with the same source, if the maximum number of + * instances of the sound are already playing. + * @property INTERRUPT_NONE + * @type {String} + * @default none + * @static + */ + s.INTERRUPT_NONE = "none"; + + /** + * Defines the playState of an instance that is still initializing. + * @property PLAY_INITED + * @type {String} + * @default playInited + * @static + */ + s.PLAY_INITED = "playInited"; + + /** + * Defines the playState of an instance that is currently playing or paused. + * @property PLAY_SUCCEEDED + * @type {String} + * @default playSucceeded + * @static + */ + s.PLAY_SUCCEEDED = "playSucceeded"; + + /** + * Defines the playState of an instance that was interrupted by another instance. + * @property PLAY_INTERRUPTED + * @type {String} + * @default playInterrupted + * @static + */ + s.PLAY_INTERRUPTED = "playInterrupted"; + + /** + * Defines the playState of an instance that completed playback. + * @property PLAY_FINISHED + * @type {String} + * @default playFinished + * @static + */ + s.PLAY_FINISHED = "playFinished"; + + /** + * Defines the playState of an instance that failed to play. This is usually caused by a lack of available channels + * when the interrupt mode was "INTERRUPT_NONE", the playback stalled, or the sound could not be found. + * @property PLAY_FAILED + * @type {String} + * @default playFailed + * @static + */ + s.PLAY_FAILED = "playFailed"; + + /** + * A list of the default supported extensions that Sound will try to play. Plugins will check if the browser + * can play these types, so modifying this list before a plugin is initialized will allow the plugins to try to + * support additional media types. + * + * NOTE this does not currently work for {{#crossLink "FlashAudioPlugin"}}{{/crossLink}}. + * + * More details on file formats can be found at http://en.wikipedia.org/wiki/Audio_file_format.
          + * A very detailed list of file formats can be found at http://www.fileinfo.com/filetypes/audio. + * @property SUPPORTED_EXTENSIONS + * @type {Array[String]} + * @default ["mp3", "ogg", "opus", "mpeg", "wav", "m4a", "mp4", "aiff", "wma", "mid"] + * @since 0.4.0 + * @static + */ + s.SUPPORTED_EXTENSIONS = ["mp3", "ogg", "opus", "mpeg", "wav", "m4a", "mp4", "aiff", "wma", "mid"]; + + /** + * Some extensions use another type of extension support to play (one of them is a codex). This allows you to map + * that support so plugins can accurately determine if an extension is supported. Adding to this list can help + * plugins determine more accurately if an extension is supported. + * + * A useful list of extensions for each format can be found at http://html5doctor.com/html5-audio-the-state-of-play/. + * @property EXTENSION_MAP + * @type {Object} + * @since 0.4.0 + * @default {m4a:"mp4"} + * @static + */ + s.EXTENSION_MAP = { + m4a:"mp4" + }; + + /** + * The RegExp pattern used to parse file URIs. This supports simple file names, as well as full domain URIs with + * query strings. The resulting match is: protocol:$1 domain:$2 path:$3 file:$4 extension:$5 query:$6. + * @property FILE_PATTERN + * @type {RegExp} + * @static + * @protected + */ + s.FILE_PATTERN = /^(?:(\w+:)\/{2}(\w+(?:\.\w+)*\/?))?([/.]*?(?:[^?]+)?\/)?((?:[^/?]+)\.(\w+))(?:\?(\S+)?)?$/; + + +// Class Public properties + /** + * Determines the default behavior for interrupting other currently playing instances with the same source, if the + * maximum number of instances of the sound are already playing. Currently the default is {{#crossLink "Sound/INTERRUPT_NONE:property"}}{{/crossLink}} + * but this can be set and will change playback behavior accordingly. This is only used when {{#crossLink "Sound/play"}}{{/crossLink}} + * is called without passing a value for interrupt. + * @property defaultInterruptBehavior + * @type {String} + * @default Sound.INTERRUPT_NONE, or "none" + * @static + * @since 0.4.0 + */ + s.defaultInterruptBehavior = s.INTERRUPT_NONE; // OJR does s.INTERRUPT_ANY make more sense as default? Needs game dev testing to see which case makes more sense. + + /** + * An array of extensions to attempt to use when loading sound, if the default is unsupported by the active plugin. + * These are applied in order, so if you try to Load Thunder.ogg in a browser that does not support ogg, and your + * extensions array is ["mp3", "m4a", "wav"] it will check mp3 support, then m4a, then wav. The audio files need + * to exist in the same location, as only the extension is altered. + * + * Note that regardless of which file is loaded, you can call {{#crossLink "Sound/createInstance"}}{{/crossLink}} + * and {{#crossLink "Sound/play"}}{{/crossLink}} using the same id or full source path passed for loading. + * + *

          Example

          + * + * var sounds = [ + * {src:"myPath/mySound.ogg", id:"example"}, + * ]; + * createjs.Sound.alternateExtensions = ["mp3"]; // now if ogg is not supported, SoundJS will try asset0.mp3 + * createjs.Sound.on("fileload", handleLoad); // call handleLoad when each sound loads + * createjs.Sound.registerSounds(sounds, assetPath); + * // ... + * createjs.Sound.play("myPath/mySound.ogg"); // works regardless of what extension is supported. Note calling with ID is a better approach + * + * @property alternateExtensions + * @type {Array} + * @since 0.5.2 + * @static + */ + s.alternateExtensions = []; + + /** + * The currently active plugin. If this is null, then no plugin could be initialized. If no plugin was specified, + * Sound attempts to apply the default plugins: {{#crossLink "WebAudioPlugin"}}{{/crossLink}}, followed by + * {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}. + * @property activePlugin + * @type {Object} + * @static + */ + s.activePlugin = null; + + +// class getter / setter properties + /** + * Set the master volume of Sound. The master volume is multiplied against each sound's individual volume. For + * example, if master volume is 0.5 and a sound's volume is 0.5, the resulting volume is 0.25. To set individual + * sound volume, use AbstractSoundInstance {{#crossLink "AbstractSoundInstance/volume:property"}}{{/crossLink}} instead. + * + *

          Example

          + * + * createjs.Sound.volume = 0.5; + * + * + * @property volume + * @type {Number} + * @default 1 + * @since 0.6.1 + */ + s._masterVolume = 1; + Object.defineProperty(s, "volume", { + get: function () {return this._masterVolume;}, + set: function (value) { + if (Number(value) == null) {return false;} + value = Math.max(0, Math.min(1, value)); + s._masterVolume = value; + if (!this.activePlugin || !this.activePlugin.setVolume || !this.activePlugin.setVolume(value)) { + var instances = this._instances; + for (var i = 0, l = instances.length; i < l; i++) { + instances[i].setMasterVolume(value); + } + } + } + }); + + /** + * Mute/Unmute all audio. Note that muted audio still plays at 0 volume. This global mute value is maintained + * separately and when set will override, but not change the mute property of individual instances. To mute an individual + * instance, use AbstractSoundInstance {{#crossLink "AbstractSoundInstance/muted:property"}}{{/crossLink}} instead. + * + *

          Example

          + * + * createjs.Sound.muted = true; + * + * + * @property muted + * @type {Boolean} + * @default false + * @since 0.6.1 + */ + s._masterMute = false; + // OJR references to the methods were not working, so the code had to be duplicated here + Object.defineProperty(s, "muted", { + get: function () {return this._masterMute;}, + set: function (value) { + if (value == null) {return false;} + + this._masterMute = value; + if (!this.activePlugin || !this.activePlugin.setMute || !this.activePlugin.setMute(value)) { + var instances = this._instances; + for (var i = 0, l = instances.length; i < l; i++) { + instances[i].setMasterMute(value); + } + } + return true; + } + }); + + /** + * Get the active plugins capabilities, which help determine if a plugin can be used in the current environment, + * or if the plugin supports a specific feature. Capabilities include: + *
            + *
          • panning: If the plugin can pan audio from left to right
          • + *
          • volume; If the plugin can control audio volume.
          • + *
          • tracks: The maximum number of audio tracks that can be played back at a time. This will be -1 + * if there is no known limit.
          • + *
            An entry for each file type in {{#crossLink "Sound/SUPPORTED_EXTENSIONS:property"}}{{/crossLink}}: + *
          • mp3: If MP3 audio is supported.
          • + *
          • ogg: If OGG audio is supported.
          • + *
          • wav: If WAV audio is supported.
          • + *
          • mpeg: If MPEG audio is supported.
          • + *
          • m4a: If M4A audio is supported.
          • + *
          • mp4: If MP4 audio is supported.
          • + *
          • aiff: If aiff audio is supported.
          • + *
          • wma: If wma audio is supported.
          • + *
          • mid: If mid audio is supported.
          • + *
          + * + * You can get a specific capability of the active plugin using standard object notation + * + *

          Example

          + * + * var mp3 = createjs.Sound.capabilities.mp3; + * + * Note this property is read only. + * + * @property capabilities + * @type {Object} + * @static + * @readOnly + * @since 0.6.1 + */ + Object.defineProperty(s, "capabilities", { + get: function () { + if (s.activePlugin == null) {return null;} + return s.activePlugin._capabilities; + }, + set: function (value) { return false;} + }); + + +// Class Private properties + /** + * Determines if the plugins have been registered. If false, the first call to play() will instantiate the default + * plugins ({{#crossLink "WebAudioPlugin"}}{{/crossLink}}, followed by {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}). + * If plugins have been registered, but none are applicable, then sound playback will fail. + * @property _pluginsRegistered + * @type {Boolean} + * @default false + * @static + * @protected + */ + s._pluginsRegistered = false; + + /** + * Used internally to assign unique IDs to each AbstractSoundInstance. + * @property _lastID + * @type {Number} + * @static + * @protected + */ + s._lastID = 0; + + /** + * An array containing all currently playing instances. This allows Sound to control the volume, mute, and playback of + * all instances when using static APIs like {{#crossLink "Sound/stop"}}{{/crossLink}} and {{#crossLink "Sound/setVolume"}}{{/crossLink}}. + * When an instance has finished playback, it gets removed via the {{#crossLink "Sound/finishedPlaying"}}{{/crossLink}} + * method. If the user replays an instance, it gets added back in via the {{#crossLink "Sound/_beginPlaying"}}{{/crossLink}} + * method. + * @property _instances + * @type {Array} + * @protected + * @static + */ + s._instances = []; + + /** + * An object hash storing objects with sound sources, startTime, and duration via there corresponding ID. + * @property _idHash + * @type {Object} + * @protected + * @static + */ + s._idHash = {}; + + /** + * An object hash that stores preloading sound sources via the parsed source that is passed to the plugin. Contains the + * source, id, and data that was passed in by the user. Parsed sources can contain multiple instances of source, id, + * and data. + * @property _preloadHash + * @type {Object} + * @protected + * @static + */ + s._preloadHash = {}; + + /** + * An object hash storing {{#crossLink "PlayPropsConfig"}}{{/crossLink}} via the parsed source that is passed as defaultPlayProps in + * {{#crossLink "Sound/registerSound"}}{{/crossLink}} and {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. + * @property _defaultPlayPropsHash + * @type {Object} + * @protected + * @static + * @since 0.6.1 + */ + s._defaultPlayPropsHash = {}; + + +// EventDispatcher methods: + s.addEventListener = null; + s.removeEventListener = null; + s.removeAllEventListeners = null; + s.dispatchEvent = null; + s.hasEventListener = null; + s._listeners = null; + + createjs.EventDispatcher.initialize(s); // inject EventDispatcher methods. + + +// Events + /** + * This event is fired when a file finishes loading internally. This event is fired for each loaded sound, + * so any handler methods should look up the event.src to handle a particular sound. + * @event fileload + * @param {Object} target The object that dispatched the event. + * @param {String} type The event type. + * @param {String} src The source of the sound that was loaded. + * @param {String} [id] The id passed in when the sound was registered. If one was not provided, it will be null. + * @param {Number|Object} [data] Any additional data associated with the item. If not provided, it will be undefined. + * @since 0.4.1 + */ + + /** + * This event is fired when a file fails loading internally. This event is fired for each loaded sound, + * so any handler methods should look up the event.src to handle a particular sound. + * @event fileerror + * @param {Object} target The object that dispatched the event. + * @param {String} type The event type. + * @param {String} src The source of the sound that was loaded. + * @param {String} [id] The id passed in when the sound was registered. If one was not provided, it will be null. + * @param {Number|Object} [data] Any additional data associated with the item. If not provided, it will be undefined. + * @since 0.6.0 + */ + + +// Class Public Methods + /** + * Get the preload rules to allow Sound to be used as a plugin by PreloadJS. + * Any load calls that have the matching type or extension will fire the callback method, and use the resulting + * object, which is potentially modified by Sound. This helps when determining the correct path, as well as + * registering the audio instance(s) with Sound. This method should not be called, except by PreloadJS. + * @method getPreloadHandlers + * @return {Object} An object containing: + *
          • callback: A preload callback that is fired when a file is added to PreloadJS, which provides + * Sound a mechanism to modify the load parameters, select the correct file format, register the sound, etc.
          • + *
          • types: A list of file types that are supported by Sound (currently supports "sound").
          • + *
          • extensions: A list of file extensions that are supported by Sound (see {{#crossLink "Sound/SUPPORTED_EXTENSIONS:property"}}{{/crossLink}}).
          + * @static + * @protected + */ + s.getPreloadHandlers = function () { + return { + callback:createjs.proxy(s.initLoad, s), + types:["sound"], + extensions:s.SUPPORTED_EXTENSIONS + }; + }; + + /** + * Used to dispatch fileload events from internal loading. + * @method _handleLoadComplete + * @param event A loader event. + * @protected + * @static + * @since 0.6.0 + */ + s._handleLoadComplete = function(event) { + var src = event.target.getItem().src; + if (!s._preloadHash[src]) {return;} + + for (var i = 0, l = s._preloadHash[src].length; i < l; i++) { + var item = s._preloadHash[src][i]; + s._preloadHash[src][i] = true; + + if (!s.hasEventListener("fileload")) { continue; } + + var event = new createjs.Event("fileload"); + event.src = item.src; + event.id = item.id; + event.data = item.data; + event.sprite = item.sprite; + + s.dispatchEvent(event); + } + }; + + /** + * Used to dispatch error events from internal preloading. + * @param event + * @protected + * @since 0.6.0 + * @static + */ + s._handleLoadError = function(event) { + var src = event.target.getItem().src; + if (!s._preloadHash[src]) {return;} + + for (var i = 0, l = s._preloadHash[src].length; i < l; i++) { + var item = s._preloadHash[src][i]; + s._preloadHash[src][i] = false; + + if (!s.hasEventListener("fileerror")) { continue; } + + var event = new createjs.Event("fileerror"); + event.src = item.src; + event.id = item.id; + event.data = item.data; + event.sprite = item.sprite; + + s.dispatchEvent(event); + } + }; + + /** + * Used by {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} to register a Sound plugin. + * + * @method _registerPlugin + * @param {Object} plugin The plugin class to install. + * @return {Boolean} Whether the plugin was successfully initialized. + * @static + * @private + */ + s._registerPlugin = function (plugin) { + // Note: Each plugin is passed in as a class reference, but we store the activePlugin as an instance + if (plugin.isSupported()) { + s.activePlugin = new plugin(); + return true; + } + return false; + }; + + /** + * Register a list of Sound plugins, in order of precedence. To register a single plugin, pass a single element in the array. + * + *

          Example

          + * + * createjs.FlashAudioPlugin.swfPath = "../src/soundjs/flashaudio/"; + * createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin, createjs.FlashAudioPlugin]); + * + * @method registerPlugins + * @param {Array} plugins An array of plugins classes to install. + * @return {Boolean} Whether a plugin was successfully initialized. + * @static + */ + s.registerPlugins = function (plugins) { + s._pluginsRegistered = true; + for (var i = 0, l = plugins.length; i < l; i++) { + if (s._registerPlugin(plugins[i])) { + return true; + } + } + return false; + }; + + /** + * Initialize the default plugins. This method is automatically called when any audio is played or registered before + * the user has manually registered plugins, and enables Sound to work without manual plugin setup. Currently, the + * default plugins are {{#crossLink "WebAudioPlugin"}}{{/crossLink}} followed by {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}. + * + *

          Example

          + * + * if (!createjs.initializeDefaultPlugins()) { return; } + * + * @method initializeDefaultPlugins + * @returns {Boolean} True if a plugin was initialized, false otherwise. + * @since 0.4.0 + * @static + */ + s.initializeDefaultPlugins = function () { + if (s.activePlugin != null) {return true;} + if (s._pluginsRegistered) {return false;} + if (s.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin])) {return true;} + return false; + }; + + /** + * Determines if Sound has been initialized, and a plugin has been activated. + * + *

          Example

          + * This example sets up a Flash fallback, but only if there is no plugin specified yet. + * + * if (!createjs.Sound.isReady()) { + * createjs.FlashAudioPlugin.swfPath = "../src/soundjs/flashaudio/"; + * createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin, createjs.FlashAudioPlugin]); + * } + * + * @method isReady + * @return {Boolean} If Sound has initialized a plugin. + * @static + */ + s.isReady = function () { + return (s.activePlugin != null); + }; + + /** + * Deprecated, please use {{#crossLink "Sound/capabilities:property"}}{{/crossLink}} instead. + * + * @method getCapabilities + * @return {Object} An object containing the capabilities of the active plugin. + * @static + * @deprecated + */ + s.getCapabilities = function () { + if (s.activePlugin == null) {return null;} + return s.activePlugin._capabilities; + }; + + /** + * Deprecated, please use {{#crossLink "Sound/capabilities:property"}}{{/crossLink}} instead. + * + * @method getCapability + * @param {String} key The capability to retrieve + * @return {Number|Boolean} The value of the capability. + * @static + * @see getCapabilities + * @deprecated + */ + s.getCapability = function (key) { + if (s.activePlugin == null) {return null;} + return s.activePlugin._capabilities[key]; + }; + + /** + * Process manifest items from PreloadJS. This method is intended + * for usage by a plugin, and not for direct interaction. + * @method initLoad + * @param {Object} src The object to load. + * @return {Object|AbstractLoader} An instance of AbstractLoader. + * @protected + * @static + */ + s.initLoad = function (loadItem) { + return s._registerSound(loadItem); + }; + + /** + * Internal method for loading sounds. This should not be called directly. + * + * @method _registerSound + * @param {Object} src The object to load, containing src property and optionally containing id and data. + * @return {Object} An object with the modified values that were passed in, which defines the sound. + * Returns false if the source cannot be parsed or no plugins can be initialized. + * Returns true if the source is already loaded. + * @static + * @private + * @since 0.6.0 + */ + + s._registerSound = function (loadItem) { + if (!s.initializeDefaultPlugins()) {return false;} + + var details; + if (loadItem.src instanceof Object) { + details = s._parseSrc(loadItem.src); + details.src = loadItem.path + details.src; + } else { + details = s._parsePath(loadItem.src); + } + if (details == null) {return false;} + loadItem.src = details.src; + loadItem.type = "sound"; + + var data = loadItem.data; + var numChannels = null; + if (data != null) { + if (!isNaN(data.channels)) { + numChannels = parseInt(data.channels); + } else if (!isNaN(data)) { + numChannels = parseInt(data); + } + + if(data.audioSprite) { + var sp; + for(var i = data.audioSprite.length; i--; ) { + sp = data.audioSprite[i]; + s._idHash[sp.id] = {src: loadItem.src, startTime: parseInt(sp.startTime), duration: parseInt(sp.duration)}; + + if (sp.defaultPlayProps) { + s._defaultPlayPropsHash[sp.id] = createjs.PlayPropsConfig.create(sp.defaultPlayProps); + } + } + } + } + if (loadItem.id != null) {s._idHash[loadItem.id] = {src: loadItem.src}}; + var loader = s.activePlugin.register(loadItem); + + SoundChannel.create(loadItem.src, numChannels); + + // return the number of instances to the user. This will also be returned in the load event. + if (data == null || !isNaN(data)) { + loadItem.data = numChannels || SoundChannel.maxPerChannel(); + } else { + loadItem.data.channels = numChannels || SoundChannel.maxPerChannel(); + } + + if (loader.type) {loadItem.type = loader.type;} + + if (loadItem.defaultPlayProps) { + s._defaultPlayPropsHash[loadItem.src] = createjs.PlayPropsConfig.create(loadItem.defaultPlayProps); + } + return loader; + }; + + /** + * Register an audio file for loading and future playback in Sound. This is automatically called when using + * PreloadJS. It is recommended to register all sounds that + * need to be played back in order to properly prepare and preload them. Sound does internal preloading when required. + * + *

          Example

          + * + * createjs.Sound.alternateExtensions = ["mp3"]; + * createjs.Sound.on("fileload", handleLoad); // add an event listener for when load is completed + * createjs.Sound.registerSound("myAudioPath/mySound.ogg", "myID", 3); + * createjs.Sound.registerSound({ogg:"path1/mySound.ogg", mp3:"path2/mySoundNoExtension"}, "myID", 3); + * + * + * @method registerSound + * @param {String | Object} src The source or an Object with a "src" property or an Object with multiple extension labeled src properties. + * @param {String} [id] An id specified by the user to play the sound later. Note id is required for when src is multiple extension labeled src properties. + * @param {Number | Object} [data] Data associated with the item. Sound uses the data parameter as the number of + * channels for an audio instance, however a "channels" property can be appended to the data object if it is used + * for other information. The audio channels will set a default based on plugin if no value is found. + * Sound also uses the data property to hold an {{#crossLink "AudioSprite"}}{{/crossLink}} array of objects in the following format {id, startTime, duration}.
          + * id used to play the sound later, in the same manner as a sound src with an id.
          + * startTime is the initial offset to start playback and loop from, in milliseconds.
          + * duration is the amount of time to play the clip for, in milliseconds.
          + * This allows Sound to support audio sprites that are played back by id. + * @param {string} basePath Set a path that will be prepended to src for loading. + * @param {Object | PlayPropsConfig} defaultPlayProps Optional Playback properties that will be set as the defaults on any new AbstractSoundInstance. + * See {{#crossLink "PlayPropsConfig"}}{{/crossLink}} for options. + * @return {Object} An object with the modified values that were passed in, which defines the sound. + * Returns false if the source cannot be parsed or no plugins can be initialized. + * Returns true if the source is already loaded. + * @static + * @since 0.4.0 + */ + s.registerSound = function (src, id, data, basePath, defaultPlayProps) { + var loadItem = {src: src, id: id, data:data, defaultPlayProps:defaultPlayProps}; + if (src instanceof Object && src.src) { + basePath = id; + loadItem = src; + } + loadItem = createjs.LoadItem.create(loadItem); + loadItem.path = basePath; + + if (basePath != null && !(loadItem.src instanceof Object)) {loadItem.src = basePath + src;} + + var loader = s._registerSound(loadItem); + if(!loader) {return false;} + + if (!s._preloadHash[loadItem.src]) { s._preloadHash[loadItem.src] = [];} + s._preloadHash[loadItem.src].push(loadItem); + if (s._preloadHash[loadItem.src].length == 1) { + // OJR note this will disallow reloading a sound if loading fails or the source changes + loader.on("complete", createjs.proxy(this._handleLoadComplete, this)); + loader.on("error", createjs.proxy(this._handleLoadError, this)); + s.activePlugin.preload(loader); + } else { + if (s._preloadHash[loadItem.src][0] == true) {return true;} + } + + return loadItem; + }; + + /** + * Register an array of audio files for loading and future playback in Sound. It is recommended to register all + * sounds that need to be played back in order to properly prepare and preload them. Sound does internal preloading + * when required. + * + *

          Example

          + * + * var assetPath = "./myAudioPath/"; + * var sounds = [ + * {src:"asset0.ogg", id:"example"}, + * {src:"asset1.ogg", id:"1", data:6}, + * {src:"asset2.mp3", id:"works"} + * {src:{mp3:"path1/asset3.mp3", ogg:"path2/asset3NoExtension}, id:"better"} + * ]; + * createjs.Sound.alternateExtensions = ["mp3"]; // if the passed extension is not supported, try this extension + * createjs.Sound.on("fileload", handleLoad); // call handleLoad when each sound loads + * createjs.Sound.registerSounds(sounds, assetPath); + * + * @method registerSounds + * @param {Array} sounds An array of objects to load. Objects are expected to be in the format needed for + * {{#crossLink "Sound/registerSound"}}{{/crossLink}}: {src:srcURI, id:ID, data:Data} + * with "id" and "data" being optional. + * You can also pass an object with path and manifest properties, where path is a basePath and manifest is an array of objects to load. + * Note id is required if src is an object with extension labeled src properties. + * @param {string} basePath Set a path that will be prepended to each src when loading. When creating, playing, or removing + * audio that was loaded with a basePath by src, the basePath must be included. + * @return {Object} An array of objects with the modified values that were passed in, which defines each sound. + * Like registerSound, it will return false for any values when the source cannot be parsed or if no plugins can be initialized. + * Also, it will return true for any values when the source is already loaded. + * @static + * @since 0.6.0 + */ + s.registerSounds = function (sounds, basePath) { + var returnValues = []; + if (sounds.path) { + if (!basePath) { + basePath = sounds.path; + } else { + basePath = basePath + sounds.path; + } + sounds = sounds.manifest; + // TODO document this feature + } + for (var i = 0, l = sounds.length; i < l; i++) { + returnValues[i] = createjs.Sound.registerSound(sounds[i].src, sounds[i].id, sounds[i].data, basePath, sounds[i].defaultPlayProps); + } + return returnValues; + }; + + /** + * Remove a sound that has been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or + * {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. + *
          Note this will stop playback on active instances playing this sound before deleting them. + *
          Note if you passed in a basePath, you need to pass it or prepend it to the src here. + * + *

          Example

          + * + * createjs.Sound.removeSound("myID"); + * createjs.Sound.removeSound("myAudioBasePath/mySound.ogg"); + * createjs.Sound.removeSound("myPath/myOtherSound.mp3", "myBasePath/"); + * createjs.Sound.removeSound({mp3:"musicNoExtension", ogg:"music.ogg"}, "myBasePath/"); + * + * @method removeSound + * @param {String | Object} src The src or ID of the audio, or an Object with a "src" property, or an Object with multiple extension labeled src properties. + * @param {string} basePath Set a path that will be prepended to each src when removing. + * @return {Boolean} True if sound is successfully removed. + * @static + * @since 0.4.1 + */ + s.removeSound = function(src, basePath) { + if (s.activePlugin == null) {return false;} + + if (src instanceof Object && src.src) {src = src.src;} + + var details; + if (src instanceof Object) { + details = s._parseSrc(src); + } else { + src = s._getSrcById(src).src; + details = s._parsePath(src); + } + if (details == null) {return false;} + src = details.src; + if (basePath != null) {src = basePath + src;} + + for(var prop in s._idHash){ + if(s._idHash[prop].src == src) { + delete(s._idHash[prop]); + } + } + + // clear from SoundChannel, which also stops and deletes all instances + SoundChannel.removeSrc(src); + + delete(s._preloadHash[src]); + + s.activePlugin.removeSound(src); + + return true; + }; + + /** + * Remove an array of audio files that have been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or + * {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. + *
          Note this will stop playback on active instances playing this audio before deleting them. + *
          Note if you passed in a basePath, you need to pass it or prepend it to the src here. + * + *

          Example

          + * + * assetPath = "./myPath/"; + * var sounds = [ + * {src:"asset0.ogg", id:"example"}, + * {src:"asset1.ogg", id:"1", data:6}, + * {src:"asset2.mp3", id:"works"} + * ]; + * createjs.Sound.removeSounds(sounds, assetPath); + * + * @method removeSounds + * @param {Array} sounds An array of objects to remove. Objects are expected to be in the format needed for + * {{#crossLink "Sound/removeSound"}}{{/crossLink}}: {srcOrID:srcURIorID}. + * You can also pass an object with path and manifest properties, where path is a basePath and manifest is an array of objects to remove. + * @param {string} basePath Set a path that will be prepended to each src when removing. + * @return {Object} An array of Boolean values representing if the sounds with the same array index were + * successfully removed. + * @static + * @since 0.4.1 + */ + s.removeSounds = function (sounds, basePath) { + var returnValues = []; + if (sounds.path) { + if (!basePath) { + basePath = sounds.path; + } else { + basePath = basePath + sounds.path; + } + sounds = sounds.manifest; + } + for (var i = 0, l = sounds.length; i < l; i++) { + returnValues[i] = createjs.Sound.removeSound(sounds[i].src, basePath); + } + return returnValues; + }; + + /** + * Remove all sounds that have been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or + * {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. + *
          Note this will stop playback on all active sound instances before deleting them. + * + *

          Example

          + * + * createjs.Sound.removeAllSounds(); + * + * @method removeAllSounds + * @static + * @since 0.4.1 + */ + s.removeAllSounds = function() { + s._idHash = {}; + s._preloadHash = {}; + SoundChannel.removeAll(); + if (s.activePlugin) {s.activePlugin.removeAllSounds();} + }; + + /** + * Check if a source has been loaded by internal preloaders. This is necessary to ensure that sounds that are + * not completed preloading will not kick off a new internal preload if they are played. + * + *

          Example

          + * + * var mySound = "assetPath/asset0.ogg"; + * if(createjs.Sound.loadComplete(mySound) { + * createjs.Sound.play(mySound); + * } + * + * @method loadComplete + * @param {String} src The src or id that is being loaded. + * @return {Boolean} If the src is already loaded. + * @since 0.4.0 + * @static + */ + s.loadComplete = function (src) { + if (!s.isReady()) { return false; } + var details = s._parsePath(src); + if (details) { + src = s._getSrcById(details.src).src; + } else { + src = s._getSrcById(src).src; + } + if(s._preloadHash[src] == undefined) {return false;} + return (s._preloadHash[src][0] == true); // src only loads once, so if it's true for the first it's true for all + }; + + /** + * Parse the path of a sound. Alternate extensions will be attempted in order if the + * current extension is not supported + * @method _parsePath + * @param {String} value The path to an audio source. + * @return {Object} A formatted object that can be registered with the {{#crossLink "Sound/activePlugin:property"}}{{/crossLink}} + * and returned to a preloader like PreloadJS. + * @protected + * @static + */ + s._parsePath = function (value) { + if (typeof(value) != "string") {value = value.toString();} + + var match = value.match(s.FILE_PATTERN); + if (match == null) {return false;} + + var name = match[4]; + var ext = match[5]; + var c = s.capabilities; + var i = 0; + while (!c[ext]) { + ext = s.alternateExtensions[i++]; + if (i > s.alternateExtensions.length) { return null;} // no extensions are supported + } + value = value.replace("."+match[5], "."+ext); + + var ret = {name:name, src:value, extension:ext}; + return ret; + }; + + /** + * Parse the path of a sound based on properties of src matching with supported extensions. + * Returns false if none of the properties are supported + * @method _parseSrc + * @param {Object} value The paths to an audio source, indexed by extension type. + * @return {Object} A formatted object that can be registered with the {{#crossLink "Sound/activePlugin:property"}}{{/crossLink}} + * and returned to a preloader like PreloadJS. + * @protected + * @static + */ + s._parseSrc = function (value) { + var ret = {name:undefined, src:undefined, extension:undefined}; + var c = s.capabilities; + + for (var prop in value) { + if(value.hasOwnProperty(prop) && c[prop]) { + ret.src = value[prop]; + ret.extension = prop; + break; + } + } + if (!ret.src) {return false;} // no matches + + var i = ret.src.lastIndexOf("/"); + if (i != -1) { + ret.name = ret.src.slice(i+1); + } else { + ret.name = ret.src; + } + + return ret; + }; + + /* --------------- + Static API. + --------------- */ + /** + * Play a sound and get a {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} to control. If the sound fails to play, a + * AbstractSoundInstance will still be returned, and have a playState of {{#crossLink "Sound/PLAY_FAILED:property"}}{{/crossLink}}. + * Note that even on sounds with failed playback, you may still be able to call AbstractSoundInstance {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}}, + * since the failure could be due to lack of available channels. If the src does not have a supported extension or + * if there is no available plugin, a default AbstractSoundInstance will be returned which will not play any audio, but will not generate errors. + * + *

          Example

          + * + * createjs.Sound.on("fileload", handleLoad); + * createjs.Sound.registerSound("myAudioPath/mySound.mp3", "myID", 3); + * function handleLoad(event) { + * createjs.Sound.play("myID"); + * // store off AbstractSoundInstance for controlling + * var myInstance = createjs.Sound.play("myID", {interrupt: createjs.Sound.INTERRUPT_ANY, loop:-1}); + * } + * + * NOTE to create an audio sprite that has not already been registered, both startTime and duration need to be set. + * This is only when creating a new audio sprite, not when playing using the id of an already registered audio sprite. + * + * Parameters Deprecated
          + * The parameters for this method are deprecated in favor of a single parameter that is an Object or {{#crossLink "PlayPropsConfig"}}{{/crossLink}}. + * + * @method play + * @param {String} src The src or ID of the audio. + * @param {String | Object} [interrupt="none"|options] This parameter will be renamed playProps in the next release.
          + * This parameter can be an instance of {{#crossLink "PlayPropsConfig"}}{{/crossLink}} or an Object that contains any or all optional properties by name, + * including: interrupt, delay, offset, loop, volume, pan, startTime, and duration (see the above code sample). + *
          OR
          + * Deprecated How to interrupt any currently playing instances of audio with the same source, + * if the maximum number of instances of the sound are already playing. Values are defined as INTERRUPT_TYPE + * constants on the Sound class, with the default defined by {{#crossLink "Sound/defaultInterruptBehavior:property"}}{{/crossLink}}. + * @param {Number} [delay=0] Deprecated The amount of time to delay the start of audio playback, in milliseconds. + * @param {Number} [offset=0] Deprecated The offset from the start of the audio to begin playback, in milliseconds. + * @param {Number} [loop=0] Deprecated How many times the audio loops when it reaches the end of playback. The default is 0 (no + * loops), and -1 can be used for infinite playback. + * @param {Number} [volume=1] Deprecated The volume of the sound, between 0 and 1. Note that the master volume is applied + * against the individual volume. + * @param {Number} [pan=0] Deprecated The left-right pan of the sound (if supported), between -1 (left) and 1 (right). + * @param {Number} [startTime=null] Deprecated To create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds. + * @param {Number} [duration=null] Deprecated To create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds. + * @return {AbstractSoundInstance} A {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} that can be controlled after it is created. + * @static + */ + s.play = function (src, interrupt, delay, offset, loop, volume, pan, startTime, duration) { + var playProps; + if (interrupt instanceof Object || interrupt instanceof createjs.PlayPropsConfig) { + playProps = createjs.PlayPropsConfig.create(interrupt); + } else { + playProps = createjs.PlayPropsConfig.create({interrupt:interrupt, delay:delay, offset:offset, loop:loop, volume:volume, pan:pan, startTime:startTime, duration:duration}); + } + var instance = s.createInstance(src, playProps.startTime, playProps.duration); + var ok = s._playInstance(instance, playProps); + if (!ok) {instance._playFailed();} + return instance; + }; + + /** + * Creates a {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} using the passed in src. If the src does not have a + * supported extension or if there is no available plugin, a default AbstractSoundInstance will be returned that can be + * called safely but does nothing. + * + *

          Example

          + * + * var myInstance = null; + * createjs.Sound.on("fileload", handleLoad); + * createjs.Sound.registerSound("myAudioPath/mySound.mp3", "myID", 3); + * function handleLoad(event) { + * myInstance = createjs.Sound.createInstance("myID"); + * // alternately we could call the following + * myInstance = createjs.Sound.createInstance("myAudioPath/mySound.mp3"); + * } + * + * NOTE to create an audio sprite that has not already been registered, both startTime and duration need to be set. + * This is only when creating a new audio sprite, not when playing using the id of an already registered audio sprite. + * + * @method createInstance + * @param {String} src The src or ID of the audio. + * @param {Number} [startTime=null] To create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds. + * @param {Number} [duration=null] To create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds. + * @return {AbstractSoundInstance} A {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} that can be controlled after it is created. + * Unsupported extensions will return the default AbstractSoundInstance. + * @since 0.4.0 + * @static + */ + s.createInstance = function (src, startTime, duration) { + if (!s.initializeDefaultPlugins()) {return new createjs.DefaultSoundInstance(src, startTime, duration);} + + var defaultPlayProps = s._defaultPlayPropsHash[src]; // for audio sprites, which create and store defaults by id + src = s._getSrcById(src); + + var details = s._parsePath(src.src); + + var instance = null; + if (details != null && details.src != null) { + SoundChannel.create(details.src); + if (startTime == null) {startTime = src.startTime;} + instance = s.activePlugin.create(details.src, startTime, duration || src.duration); + + defaultPlayProps = defaultPlayProps || s._defaultPlayPropsHash[details.src]; + if(defaultPlayProps) { + instance.applyPlayProps(defaultPlayProps); + } + } else { + instance = new createjs.DefaultSoundInstance(src, startTime, duration); + } + + instance.uniqueId = s._lastID++; + + return instance; + }; + + /** + * Stop all audio (global stop). Stopped audio is reset, and not paused. To play audio that has been stopped, + * call AbstractSoundInstance {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}}. + * + *

          Example

          + * + * createjs.Sound.stop(); + * + * @method stop + * @static + */ + s.stop = function () { + var instances = this._instances; + for (var i = instances.length; i--; ) { + instances[i].stop(); // NOTE stop removes instance from this._instances + } + }; + + /** + * Deprecated, please use {{#crossLink "Sound/volume:property"}}{{/crossLink}} instead. + * + * @method setVolume + * @param {Number} value The master volume value. The acceptable range is 0-1. + * @static + * @deprecated + */ + s.setVolume = function (value) { + if (Number(value) == null) {return false;} + value = Math.max(0, Math.min(1, value)); + s._masterVolume = value; + if (!this.activePlugin || !this.activePlugin.setVolume || !this.activePlugin.setVolume(value)) { + var instances = this._instances; + for (var i = 0, l = instances.length; i < l; i++) { + instances[i].setMasterVolume(value); + } + } + }; + + /** + * Deprecated, please use {{#crossLink "Sound/volume:property"}}{{/crossLink}} instead. + * + * @method getVolume + * @return {Number} The master volume, in a range of 0-1. + * @static + * @deprecated + */ + s.getVolume = function () { + return this._masterVolume; + }; + + /** + * Deprecated, please use {{#crossLink "Sound/muted:property"}}{{/crossLink}} instead. + * + * @method setMute + * @param {Boolean} value Whether the audio should be muted or not. + * @return {Boolean} If the mute was set. + * @static + * @since 0.4.0 + * @deprecated + */ + s.setMute = function (value) { + if (value == null) {return false;} + + this._masterMute = value; + if (!this.activePlugin || !this.activePlugin.setMute || !this.activePlugin.setMute(value)) { + var instances = this._instances; + for (var i = 0, l = instances.length; i < l; i++) { + instances[i].setMasterMute(value); + } + } + return true; + }; + + /** + * Deprecated, please use {{#crossLink "Sound/muted:property"}}{{/crossLink}} instead. + * + * @method getMute + * @return {Boolean} The mute value of Sound. + * @static + * @since 0.4.0 + * @deprecated + */ + s.getMute = function () { + return this._masterMute; + }; + + /** + * Set the default playback properties for all new SoundInstances of the passed in src or ID. + * See {{#crossLink "PlayPropsConfig"}}{{/crossLink}} for available properties. + * + * @method setDefaultPlayProps + * @param {String} src The src or ID used to register the audio. + * @param {Object | PlayPropsConfig} playProps The playback properties you would like to set. + * @since 0.6.1 + */ + s.setDefaultPlayProps = function(src, playProps) { + src = s._getSrcById(src); + s._defaultPlayPropsHash[s._parsePath(src.src).src] = createjs.PlayPropsConfig.create(playProps); + }; + + /** + * Get the default playback properties for the passed in src or ID. These properties are applied to all + * new SoundInstances. Returns null if default does not exist. + * + * @method getDefaultPlayProps + * @param {String} src The src or ID used to register the audio. + * @returns {PlayPropsConfig} returns an existing PlayPropsConfig or null if one does not exist + * @since 0.6.1 + */ + s.getDefaultPlayProps = function(src) { + src = s._getSrcById(src); + return s._defaultPlayPropsHash[s._parsePath(src.src).src]; + }; + + + /* --------------- + Internal methods + --------------- */ + /** + * Play an instance. This is called by the static API, as well as from plugins. This allows the core class to + * control delays. + * @method _playInstance + * @param {AbstractSoundInstance} instance The {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} to start playing. + * @param {PlayPropsConfig} playProps A PlayPropsConfig object. + * @return {Boolean} If the sound can start playing. Sounds that fail immediately will return false. Sounds that + * have a delay will return true, but may still fail to play. + * @protected + * @static + */ + s._playInstance = function (instance, playProps) { + var defaultPlayProps = s._defaultPlayPropsHash[instance.src] || {}; + if (playProps.interrupt == null) {playProps.interrupt = defaultPlayProps.interrupt || s.defaultInterruptBehavior}; + if (playProps.delay == null) {playProps.delay = defaultPlayProps.delay || 0;} + if (playProps.offset == null) {playProps.offset = instance.getPosition();} + if (playProps.loop == null) {playProps.loop = instance.loop;} + if (playProps.volume == null) {playProps.volume = instance.volume;} + if (playProps.pan == null) {playProps.pan = instance.pan;} + + if (playProps.delay == 0) { + var ok = s._beginPlaying(instance, playProps); + if (!ok) {return false;} + } else { + //Note that we can't pass arguments to proxy OR setTimeout (IE only), so just wrap the function call. + // OJR WebAudio may want to handle this differently, so it might make sense to move this functionality into the plugins in the future + var delayTimeoutId = setTimeout(function () { + s._beginPlaying(instance, playProps); + }, playProps.delay); + instance.delayTimeoutId = delayTimeoutId; + } + + this._instances.push(instance); + + return true; + }; + + /** + * Begin playback. This is called immediately or after delay by {{#crossLink "Sound/playInstance"}}{{/crossLink}}. + * @method _beginPlaying + * @param {AbstractSoundInstance} instance A {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} to begin playback. + * @param {PlayPropsConfig} playProps A PlayPropsConfig object. + * @return {Boolean} If the sound can start playing. If there are no available channels, or the instance fails to + * start, this will return false. + * @protected + * @static + */ + s._beginPlaying = function (instance, playProps) { + if (!SoundChannel.add(instance, playProps.interrupt)) { + return false; + } + var result = instance._beginPlaying(playProps); + if (!result) { + var index = createjs.indexOf(this._instances, instance); + if (index > -1) {this._instances.splice(index, 1);} + return false; + } + return true; + }; + + /** + * Get the source of a sound via the ID passed in with a register call. If no ID is found the value is returned + * instead. + * @method _getSrcById + * @param {String} value The ID the sound was registered with. + * @return {String} The source of the sound if it has been registered with this ID or the value that was passed in. + * @protected + * @static + */ + s._getSrcById = function (value) { + return s._idHash[value] || {src: value}; + }; + + /** + * A sound has completed playback, been interrupted, failed, or been stopped. This method removes the instance from + * Sound management. It will be added again, if the sound re-plays. Note that this method is called from the + * instances themselves. + * @method _playFinished + * @param {AbstractSoundInstance} instance The instance that finished playback. + * @protected + * @static + */ + s._playFinished = function (instance) { + SoundChannel.remove(instance); + var index = createjs.indexOf(this._instances, instance); + if (index > -1) {this._instances.splice(index, 1);} // OJR this will always be > -1, there is no way for an instance to exist without being added to this._instances + }; + + createjs.Sound = Sound; + + /** + * An internal class that manages the number of active {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} instances for + * each sound type. This method is only used internally by the {{#crossLink "Sound"}}{{/crossLink}} class. + * + * The number of sounds is artificially limited by Sound in order to prevent over-saturation of a + * single sound, as well as to stay within hardware limitations, although the latter may disappear with better + * browser support. + * + * When a sound is played, this class ensures that there is an available instance, or interrupts an appropriate + * sound that is already playing. + * #class SoundChannel + * @param {String} src The source of the instances + * @param {Number} [max=1] The number of instances allowed + * @constructor + * @protected + */ + function SoundChannel(src, max) { + this.init(src, max); + } + + /* ------------ + Static API + ------------ */ + /** + * A hash of channel instances indexed by source. + * #property channels + * @type {Object} + * @static + */ + SoundChannel.channels = {}; + + /** + * Create a sound channel. Note that if the sound channel already exists, this will fail. + * #method create + * @param {String} src The source for the channel + * @param {Number} max The maximum amount this channel holds. The default is {{#crossLink "SoundChannel.maxDefault"}}{{/crossLink}}. + * @return {Boolean} If the channels were created. + * @static + */ + SoundChannel.create = function (src, max) { + var channel = SoundChannel.get(src); + if (channel == null) { + SoundChannel.channels[src] = new SoundChannel(src, max); + return true; + } + return false; + }; + /** + * Delete a sound channel, stop and delete all related instances. Note that if the sound channel does not exist, this will fail. + * #method remove + * @param {String} src The source for the channel + * @return {Boolean} If the channels were deleted. + * @static + */ + SoundChannel.removeSrc = function (src) { + var channel = SoundChannel.get(src); + if (channel == null) {return false;} + channel._removeAll(); // this stops and removes all active instances + delete(SoundChannel.channels[src]); + return true; + }; + /** + * Delete all sound channels, stop and delete all related instances. + * #method removeAll + * @static + */ + SoundChannel.removeAll = function () { + for(var channel in SoundChannel.channels) { + SoundChannel.channels[channel]._removeAll(); // this stops and removes all active instances + } + SoundChannel.channels = {}; + }; + /** + * Add an instance to a sound channel. + * #method add + * @param {AbstractSoundInstance} instance The instance to add to the channel + * @param {String} interrupt The interrupt value to use. Please see the {{#crossLink "Sound/play"}}{{/crossLink}} + * for details on interrupt modes. + * @return {Boolean} The success of the method call. If the channel is full, it will return false. + * @static + */ + SoundChannel.add = function (instance, interrupt) { + var channel = SoundChannel.get(instance.src); + if (channel == null) {return false;} + return channel._add(instance, interrupt); + }; + /** + * Remove an instance from the channel. + * #method remove + * @param {AbstractSoundInstance} instance The instance to remove from the channel + * @return The success of the method call. If there is no channel, it will return false. + * @static + */ + SoundChannel.remove = function (instance) { + var channel = SoundChannel.get(instance.src); + if (channel == null) {return false;} + channel._remove(instance); + return true; + }; + /** + * Get the maximum number of sounds you can have in a channel. + * #method maxPerChannel + * @return {Number} The maximum number of sounds you can have in a channel. + */ + SoundChannel.maxPerChannel = function () { + return p.maxDefault; + }; + /** + * Get a channel instance by its src. + * #method get + * @param {String} src The src to use to look up the channel + * @static + */ + SoundChannel.get = function (src) { + return SoundChannel.channels[src]; + }; + + var p = SoundChannel.prototype; + p.constructor = SoundChannel; + + /** + * REMOVED. Removed in favor of using `MySuperClass_constructor`. + * See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}} + * for details. + * + * There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance. + * + * @method initialize + * @protected + * @deprecated + */ + // p.initialize = function() {}; // searchable for devs wondering where it is. + + + /** + * The source of the channel. + * #property src + * @type {String} + */ + p.src = null; + + /** + * The maximum number of instances in this channel. -1 indicates no limit + * #property max + * @type {Number} + */ + p.max = null; + + /** + * The default value to set for max, if it isn't passed in. Also used if -1 is passed. + * #property maxDefault + * @type {Number} + * @default 100 + * @since 0.4.0 + */ + p.maxDefault = 100; + + /** + * The current number of active instances. + * #property length + * @type {Number} + */ + p.length = 0; + + /** + * Initialize the channel. + * #method init + * @param {String} src The source of the channel + * @param {Number} max The maximum number of instances in the channel + * @protected + */ + p.init = function (src, max) { + this.src = src; + this.max = max || this.maxDefault; + if (this.max == -1) {this.max = this.maxDefault;} + this._instances = []; + }; + + /** + * Get an instance by index. + * #method get + * @param {Number} index The index to return. + * @return {AbstractSoundInstance} The AbstractSoundInstance at a specific instance. + */ + p._get = function (index) { + return this._instances[index]; + }; + + /** + * Add a new instance to the channel. + * #method add + * @param {AbstractSoundInstance} instance The instance to add. + * @return {Boolean} The success of the method call. If the channel is full, it will return false. + */ + p._add = function (instance, interrupt) { + if (!this._getSlot(interrupt, instance)) {return false;} + this._instances.push(instance); + this.length++; + return true; + }; + + /** + * Remove an instance from the channel, either when it has finished playing, or it has been interrupted. + * #method remove + * @param {AbstractSoundInstance} instance The instance to remove + * @return {Boolean} The success of the remove call. If the instance is not found in this channel, it will + * return false. + */ + p._remove = function (instance) { + var index = createjs.indexOf(this._instances, instance); + if (index == -1) {return false;} + this._instances.splice(index, 1); + this.length--; + return true; + }; + + /** + * Stop playback and remove all instances from the channel. Usually in response to a delete call. + * #method removeAll + */ + p._removeAll = function () { + // Note that stop() removes the item from the list + for (var i=this.length-1; i>=0; i--) { + this._instances[i].stop(); + } + }; + + /** + * Get an available slot depending on interrupt value and if slots are available. + * #method getSlot + * @param {String} interrupt The interrupt value to use. + * @param {AbstractSoundInstance} instance The sound instance that will go in the channel if successful. + * @return {Boolean} Determines if there is an available slot. Depending on the interrupt mode, if there are no slots, + * an existing AbstractSoundInstance may be interrupted. If there are no slots, this method returns false. + */ + p._getSlot = function (interrupt, instance) { + var target, replacement; + + if (interrupt != Sound.INTERRUPT_NONE) { + // First replacement candidate + replacement = this._get(0); + if (replacement == null) { + return true; + } + } + + for (var i = 0, l = this.max; i < l; i++) { + target = this._get(i); + + // Available Space + if (target == null) { + return true; + } + + // Audio is complete or not playing + if (target.playState == Sound.PLAY_FINISHED || + target.playState == Sound.PLAY_INTERRUPTED || + target.playState == Sound.PLAY_FAILED) { + replacement = target; + break; + } + + if (interrupt == Sound.INTERRUPT_NONE) { + continue; + } + + // Audio is a better candidate than the current target, according to playhead + if ((interrupt == Sound.INTERRUPT_EARLY && target.getPosition() < replacement.getPosition()) || + (interrupt == Sound.INTERRUPT_LATE && target.getPosition() > replacement.getPosition())) { + replacement = target; + } + } + + if (replacement != null) { + replacement._interrupt(); + this._remove(replacement); + return true; + } + return false; + }; + + p.toString = function () { + return "[Sound SoundChannel]"; + }; + // do not add SoundChannel to namespace + }()); //############################################################################## -// AbstractPlugin.js +// AbstractSoundInstance.js //############################################################################## -this.createjs = this.createjs || {}; - -(function () { - "use strict"; - - -// constructor: - /** - * A default plugin class used as a base for all other plugins. - * @class AbstractPlugin - * @constructor - * @since 0.6.0 - */ - - var AbstractPlugin = function () { - // private properties: - /** - * The capabilities of the plugin. - * method and is used internally. - * @property _capabilities - * @type {Object} - * @default null - * @protected - * @static - */ - this._capabilities = null; - - /** - * Object hash indexed by the source URI of all created loaders, used to properly destroy them if sources are removed. - * @type {Object} - * @protected - */ - this._loaders = {}; - - /** - * Object hash indexed by the source URI of each file to indicate if an audio source has begun loading, - * is currently loading, or has completed loading. Can be used to store non boolean data after loading - * is complete (for example arrayBuffers for web audio). - * @property _audioSources - * @type {Object} - * @protected - */ - this._audioSources = {}; - - /** - * Object hash indexed by the source URI of all created SoundInstances, updates the playbackResource if it loads after they are created, - * and properly destroy them if sources are removed - * @type {Object} - * @protected - */ - this._soundInstances = {}; - - /** - * The internal master volume value of the plugin. - * @property _volume - * @type {Number} - * @default 1 - * @protected - */ - this._volume = 1; - - /** - * A reference to a loader class used by a plugin that must be set. - * @type {Object} - * @protected - */ - this._loaderClass; - - /** - * A reference to an AbstractSoundInstance class used by a plugin that must be set. - * @type {Object} - * @protected; - */ - this._soundInstanceClass; - }; - var p = AbstractPlugin.prototype; - - /** - * REMOVED. Removed in favor of using `MySuperClass_constructor`. - * See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}} - * for details. - * - * There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance. - * - * @method initialize - * @protected - * @deprecated - */ - // p.initialize = function() {}; // searchable for devs wondering where it is. - - -// Static Properties: -// NOTE THESE PROPERTIES NEED TO BE ADDED TO EACH PLUGIN - /** - * The capabilities of the plugin. This is generated via the _generateCapabilities method and is used internally. - * @property _capabilities - * @type {Object} - * @default null - * @protected - * @static - */ - AbstractPlugin._capabilities = null; - - /** - * Determine if the plugin can be used in the current browser/OS. - * @method isSupported - * @return {Boolean} If the plugin can be initialized. - * @static - */ - AbstractPlugin.isSupported = function () { - return true; - }; - - -// public methods: - /** - * Pre-register a sound for preloading and setup. This is called by {{#crossLink "Sound"}}{{/crossLink}}. - * Note all plugins provide a Loader instance, which PreloadJS - * can use to assist with preloading. - * @method register - * @param {String} loadItem An Object containing the source of the audio - * Note that not every plugin will manage this value. - * @return {Object} A result object, containing a "tag" for preloading purposes. - */ - p.register = function (loadItem) { - var loader = this._loaders[loadItem.src]; - if(loader && !loader.canceled) {return this._loaders[loadItem.src];} // already loading/loaded this, so don't load twice - // OJR potential issue that we won't be firing loaded event, might need to trigger if this is already loaded? - this._audioSources[loadItem.src] = true; - this._soundInstances[loadItem.src] = []; - loader = new this._loaderClass(loadItem); - loader.on("complete", createjs.proxy(this._handlePreloadComplete, this)); - this._loaders[loadItem.src] = loader; - return loader; - }; - - // note sound calls register before calling preload - /** - * Internally preload a sound. - * @method preload - * @param {Loader} loader The sound URI to load. - */ - p.preload = function (loader) { - loader.on("error", createjs.proxy(this._handlePreloadError, this)); - loader.load(); - }; - - /** - * Checks if preloading has started for a specific source. If the source is found, we can assume it is loading, - * or has already finished loading. - * @method isPreloadStarted - * @param {String} src The sound URI to check. - * @return {Boolean} - */ - p.isPreloadStarted = function (src) { - return (this._audioSources[src] != null); - }; - - /** - * Checks if preloading has finished for a specific source. - * @method isPreloadComplete - * @param {String} src The sound URI to load. - * @return {Boolean} - */ - p.isPreloadComplete = function (src) { - return (!(this._audioSources[src] == null || this._audioSources[src] == true)); - }; - - /** - * Remove a sound added using {{#crossLink "WebAudioPlugin/register"}}{{/crossLink}}. Note this does not cancel a preload. - * @method removeSound - * @param {String} src The sound URI to unload. - */ - p.removeSound = function (src) { - if (!this._soundInstances[src]) { return; } - for (var i = this._soundInstances[src].length; i--; ) { - var item = this._soundInstances[src][i]; - item.destroy(); - } - delete(this._soundInstances[src]); - delete(this._audioSources[src]); - if(this._loaders[src]) { this._loaders[src].destroy(); } - delete(this._loaders[src]); - }; - - /** - * Remove all sounds added using {{#crossLink "WebAudioPlugin/register"}}{{/crossLink}}. Note this does not cancel a preload. - * @method removeAllSounds - * @param {String} src The sound URI to unload. - */ - p.removeAllSounds = function () { - for(var key in this._audioSources) { - this.removeSound(key); - } - }; - - /** - * Create a sound instance. If the sound has not been preloaded, it is internally preloaded here. - * @method create - * @param {String} src The sound source to use. - * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. - * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. - * @return {AbstractSoundInstance} A sound instance for playback and control. - */ - p.create = function (src, startTime, duration) { - if (!this.isPreloadStarted(src)) { - this.preload(this.register(src)); - } - var si = new this._soundInstanceClass(src, startTime, duration, this._audioSources[src]); - this._soundInstances[src].push(si); - return si; - }; - - // if a plugin does not support volume and mute, it should set these to null - /** - * Set the master volume of the plugin, which affects all SoundInstances. - * @method setVolume - * @param {Number} value The volume to set, between 0 and 1. - * @return {Boolean} If the plugin processes the setVolume call (true). The Sound class will affect all the - * instances manually otherwise. - */ - p.setVolume = function (value) { - this._volume = value; - this._updateVolume(); - return true; - }; - - /** - * Get the master volume of the plugin, which affects all SoundInstances. - * @method getVolume - * @return {Number} The volume level, between 0 and 1. - */ - p.getVolume = function () { - return this._volume; - }; - - /** - * Mute all sounds via the plugin. - * @method setMute - * @param {Boolean} value If all sound should be muted or not. Note that plugin-level muting just looks up - * the mute value of Sound {{#crossLink "Sound/getMute"}}{{/crossLink}}, so this property is not used here. - * @return {Boolean} If the mute call succeeds. - */ - p.setMute = function (value) { - this._updateVolume(); - return true; - }; - - // plugins should overwrite this method - p.toString = function () { - return "[AbstractPlugin]"; - }; - - -// private methods: - /** - * Handles internal preload completion. - * @method _handlePreloadComplete - * @protected - */ - p._handlePreloadComplete = function (event) { - var src = event.target.getItem().src; - this._audioSources[src] = event.result; - for (var i = 0, l = this._soundInstances[src].length; i < l; i++) { - var item = this._soundInstances[src][i]; - item.setPlaybackResource(this._audioSources[src]); - // ToDo consider adding play call here if playstate == playfailed - } - }; - - /** - * Handles internal preload erros - * @method _handlePreloadError - * @param event - * @protected - */ - p._handlePreloadError = function(event) { - //delete(this._audioSources[src]); - }; - - /** - * Set the gain value for master audio. Should not be called externally. - * @method _updateVolume - * @protected - */ - p._updateVolume = function () { - // Plugin Specific code - }; - - createjs.AbstractPlugin = AbstractPlugin; +this.createjs = this.createjs || {}; + +/** + * A AbstractSoundInstance is created when any calls to the Sound API method {{#crossLink "Sound/play"}}{{/crossLink}} or + * {{#crossLink "Sound/createInstance"}}{{/crossLink}} are made. The AbstractSoundInstance is returned by the active plugin + * for control by the user. + * + *

          Example

          + * + * var myInstance = createjs.Sound.play("myAssetPath/mySrcFile.mp3"); + * + * A number of additional parameters provide a quick way to determine how a sound is played. Please see the Sound + * API method {{#crossLink "Sound/play"}}{{/crossLink}} for a list of arguments. + * + * Once a AbstractSoundInstance is created, a reference can be stored that can be used to control the audio directly through + * the AbstractSoundInstance. If the reference is not stored, the AbstractSoundInstance will play out its audio (and any loops), and + * is then de-referenced from the {{#crossLink "Sound"}}{{/crossLink}} class so that it can be cleaned up. If audio + * playback has completed, a simple call to the {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}} instance method + * will rebuild the references the Sound class need to control it. + * + * var myInstance = createjs.Sound.play("myAssetPath/mySrcFile.mp3", {loop:2}); + * myInstance.on("loop", handleLoop); + * function handleLoop(event) { + * myInstance.volume = myInstance.volume * 0.5; + * } + * + * Events are dispatched from the instance to notify when the sound has completed, looped, or when playback fails + * + * var myInstance = createjs.Sound.play("myAssetPath/mySrcFile.mp3"); + * myInstance.on("complete", handleComplete); + * myInstance.on("loop", handleLoop); + * myInstance.on("failed", handleFailed); + * + * + * @class AbstractSoundInstance + * @param {String} src The path to and file name of the sound. + * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. + * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. + * @param {Object} playbackResource Any resource needed by plugin to support audio playback. + * @extends EventDispatcher + * @constructor + */ + +(function () { + "use strict"; + + +// Constructor: + var AbstractSoundInstance = function (src, startTime, duration, playbackResource) { + this.EventDispatcher_constructor(); + + + // public properties: + /** + * The source of the sound. + * @property src + * @type {String} + * @default null + */ + this.src = src; + + /** + * The unique ID of the instance. This is set by {{#crossLink "Sound"}}{{/crossLink}}. + * @property uniqueId + * @type {String} | Number + * @default -1 + */ + this.uniqueId = -1; + + /** + * The play state of the sound. Play states are defined as constants on {{#crossLink "Sound"}}{{/crossLink}}. + * @property playState + * @type {String} + * @default null + */ + this.playState = null; + + /** + * A Timeout created by {{#crossLink "Sound"}}{{/crossLink}} when this AbstractSoundInstance is played with a delay. + * This allows AbstractSoundInstance to remove the delay if stop, pause, or cleanup are called before playback begins. + * @property delayTimeoutId + * @type {timeoutVariable} + * @default null + * @protected + * @since 0.4.0 + */ + this.delayTimeoutId = null; + // TODO consider moving delay into AbstractSoundInstance so it can be handled by plugins + + + // private properties + // Getter / Setter Properties + // OJR TODO find original reason that we didn't use defined functions. I think it was performance related + /** + * The volume of the sound, between 0 and 1. + * + * The actual output volume of a sound can be calculated using: + * myInstance.volume * createjs.Sound.getVolume(); + * + * @property volume + * @type {Number} + * @default 1 + */ + this._volume = 1; + Object.defineProperty(this, "volume", { + get: this.getVolume, + set: this.setVolume + }); + + /** + * The pan of the sound, between -1 (left) and 1 (right). Note that pan is not supported by HTML Audio. + * + *
          Note in WebAudioPlugin this only gives us the "x" value of what is actually 3D audio. + * + * @property pan + * @type {Number} + * @default 0 + */ + this._pan = 0; + Object.defineProperty(this, "pan", { + get: this.getPan, + set: this.setPan + }); + + /** + * Audio sprite property used to determine the starting offset. + * @property startTime + * @type {Number} + * @default 0 + * @since 0.6.1 + */ + this._startTime = Math.max(0, startTime || 0); + Object.defineProperty(this, "startTime", { + get: this.getStartTime, + set: this.setStartTime + }); + + /** + * The length of the audio clip, in milliseconds. + * + * @property duration + * @type {Number} + * @default 0 + * @since 0.6.0 + */ + this._duration = Math.max(0, duration || 0); + Object.defineProperty(this, "duration", { + get: this.getDuration, + set: this.setDuration + }); + + /** + * Object that holds plugin specific resource need for audio playback. + * This is set internally by the plugin. For example, WebAudioPlugin will set an array buffer, + * HTMLAudioPlugin will set a tag, FlashAudioPlugin will set a flash reference. + * + * @property playbackResource + * @type {Object} + * @default null + */ + this._playbackResource = null; + Object.defineProperty(this, "playbackResource", { + get: this.getPlaybackResource, + set: this.setPlaybackResource + }); + if(playbackResource !== false && playbackResource !== true) { this.setPlaybackResource(playbackResource); } + + /** + * The position of the playhead in milliseconds. This can be set while a sound is playing, paused, or stopped. + * + * @property position + * @type {Number} + * @default 0 + * @since 0.6.0 + */ + this._position = 0; + Object.defineProperty(this, "position", { + get: this.getPosition, + set: this.setPosition + }); + + /** + * The number of play loops remaining. Negative values will loop infinitely. + * + * @property loop + * @type {Number} + * @default 0 + * @public + * @since 0.6.0 + */ + this._loop = 0; + Object.defineProperty(this, "loop", { + get: this.getLoop, + set: this.setLoop + }); + + /** + * Determines if the audio is currently muted. + * + * @property muted + * @type {Boolean} + * @default false + * @since 0.6.0 + */ + this._muted = false; + Object.defineProperty(this, "muted", { + get: this.getMuted, + set: this.setMuted + }); + + /** + * Tells you if the audio is currently paused. + * + * @property paused + * @type {Boolean} + */ + this._paused = false; + Object.defineProperty(this, "paused", { + get: this.getPaused, + set: this.setPaused + }); + + + // Events + /** + * The event that is fired when playback has started successfully. + * @event succeeded + * @param {Object} target The object that dispatched the event. + * @param {String} type The event type. + * @since 0.4.0 + */ + + /** + * The event that is fired when playback is interrupted. This happens when another sound with the same + * src property is played using an interrupt value that causes this instance to stop playing. + * @event interrupted + * @param {Object} target The object that dispatched the event. + * @param {String} type The event type. + * @since 0.4.0 + */ + + /** + * The event that is fired when playback has failed. This happens when there are too many channels with the same + * src property already playing (and the interrupt value doesn't cause an interrupt of another instance), or + * the sound could not be played, perhaps due to a 404 error. + * @event failed + * @param {Object} target The object that dispatched the event. + * @param {String} type The event type. + * @since 0.4.0 + */ + + /** + * The event that is fired when a sound has completed playing but has loops remaining. + * @event loop + * @param {Object} target The object that dispatched the event. + * @param {String} type The event type. + * @since 0.4.0 + */ + + /** + * The event that is fired when playback completes. This means that the sound has finished playing in its + * entirety, including its loop iterations. + * @event complete + * @param {Object} target The object that dispatched the event. + * @param {String} type The event type. + * @since 0.4.0 + */ + }; + + var p = createjs.extend(AbstractSoundInstance, createjs.EventDispatcher); + + // TODO: deprecated + // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. + + +// Public Methods: + /** + * Play an instance. This method is intended to be called on SoundInstances that already exist (created + * with the Sound API {{#crossLink "Sound/createInstance"}}{{/crossLink}} or {{#crossLink "Sound/play"}}{{/crossLink}}). + * + *

          Example

          + * + * var myInstance = createjs.Sound.createInstance(mySrc); + * myInstance.play({interrupt:createjs.Sound.INTERRUPT_ANY, loop:2, pan:0.5}); + * + * Note that if this sound is already playing, this call will still set the passed in parameters. + + * Parameters Deprecated
          + * The parameters for this method are deprecated in favor of a single parameter that is an Object or {{#crossLink "PlayPropsConfig"}}{{/crossLink}}. + * + * @method play + * @param {String | Object} [interrupt="none"|options] This parameter will be renamed playProps in the next release.
          + * This parameter can be an instance of {{#crossLink "PlayPropsConfig"}}{{/crossLink}} or an Object that contains any or all optional properties by name, + * including: interrupt, delay, offset, loop, volume, pan, startTime, and duration (see the above code sample). + *
          OR
          + * Deprecated How to interrupt any currently playing instances of audio with the same source, + * if the maximum number of instances of the sound are already playing. Values are defined as INTERRUPT_TYPE + * constants on the Sound class, with the default defined by {{#crossLink "Sound/defaultInterruptBehavior:property"}}{{/crossLink}}. + * @param {Number} [delay=0] Deprecated The amount of time to delay the start of audio playback, in milliseconds. + * @param {Number} [offset=0] Deprecated The offset from the start of the audio to begin playback, in milliseconds. + * @param {Number} [loop=0] Deprecated How many times the audio loops when it reaches the end of playback. The default is 0 (no + * loops), and -1 can be used for infinite playback. + * @param {Number} [volume=1] Deprecated The volume of the sound, between 0 and 1. Note that the master volume is applied + * against the individual volume. + * @param {Number} [pan=0] Deprecated The left-right pan of the sound (if supported), between -1 (left) and 1 (right). + * Note that pan is not supported for HTML Audio. + * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. + */ + p.play = function (interrupt, delay, offset, loop, volume, pan) { + var playProps; + if (interrupt instanceof Object || interrupt instanceof createjs.PlayPropsConfig) { + playProps = createjs.PlayPropsConfig.create(interrupt); + } else { + playProps = createjs.PlayPropsConfig.create({interrupt:interrupt, delay:delay, offset:offset, loop:loop, volume:volume, pan:pan}); + } + + if (this.playState == createjs.Sound.PLAY_SUCCEEDED) { + this.applyPlayProps(playProps); + if (this._paused) { this.setPaused(false); } + return; + } + this._cleanUp(); + createjs.Sound._playInstance(this, playProps); // make this an event dispatch?? + return this; + }; + + /** + * Stop playback of the instance. Stopped sounds will reset their position to 0, and calls to {{#crossLink "AbstractSoundInstance/resume"}}{{/crossLink}} + * will fail. To start playback again, call {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}}. + * + *

          Example

          + * + * myInstance.stop(); + * + * @method stop + * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. + */ + p.stop = function () { + this._position = 0; + this._paused = false; + this._handleStop(); + this._cleanUp(); + this.playState = createjs.Sound.PLAY_FINISHED; + return this; + }; + + /** + * Remove all external references and resources from AbstractSoundInstance. Note this is irreversible and AbstractSoundInstance will no longer work + * @method destroy + * @since 0.6.0 + */ + p.destroy = function() { + this._cleanUp(); + this.src = null; + this.playbackResource = null; + + this.removeAllEventListeners(); + }; + + /** + * Takes an PlayPropsConfig or Object with the same properties and sets them on this instance. + * @method applyPlayProps + * @param {PlayPropsConfig | Object} playProps A PlayPropsConfig or object containing the same properties. + * @since 0.6.1 + * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. + */ + p.applyPlayProps = function(playProps) { + if (playProps.offset != null) { this.setPosition(playProps.offset) } + if (playProps.loop != null) { this.setLoop(playProps.loop); } + if (playProps.volume != null) { this.setVolume(playProps.volume); } + if (playProps.pan != null) { this.setPan(playProps.pan); } + if (playProps.startTime != null) { + this.setStartTime(playProps.startTime); + this.setDuration(playProps.duration); + } + return this; + }; + + p.toString = function () { + return "[AbstractSoundInstance]"; + }; + +// get/set methods that allow support for IE8 + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/paused:property"}}{{/crossLink}} directly as a property, + * + * @deprecated + * @method getPaused + * @returns {boolean} If the instance is currently paused + * @since 0.6.0 + */ + p.getPaused = function() { + return this._paused; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/paused:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method setPaused + * @param {boolean} value + * @since 0.6.0 + * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. + */ + p.setPaused = function (value) { + if ((value !== true && value !== false) || this._paused == value) {return;} + if (value == true && this.playState != createjs.Sound.PLAY_SUCCEEDED) {return;} + this._paused = value; + if(value) { + this._pause(); + } else { + this._resume(); + } + clearTimeout(this.delayTimeoutId); + return this; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/volume:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method setVolume + * @param {Number} value The volume to set, between 0 and 1. + * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. + */ + p.setVolume = function (value) { + if (value == this._volume) { return this; } + this._volume = Math.max(0, Math.min(1, value)); + if (!this._muted) { + this._updateVolume(); + } + return this; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/volume:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method getVolume + * @return {Number} The current volume of the sound instance. + */ + p.getVolume = function () { + return this._volume; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/muted:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method setMuted + * @param {Boolean} value If the sound should be muted. + * @return {AbstractSoundInstance} A reference to itself, intended for chaining calls. + * @since 0.6.0 + */ + p.setMuted = function (value) { + if (value !== true && value !== false) {return;} + this._muted = value; + this._updateVolume(); + return this; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/muted:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method getMuted + * @return {Boolean} If the sound is muted. + * @since 0.6.0 + */ + p.getMuted = function () { + return this._muted; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/pan:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method setPan + * @param {Number} value The pan value, between -1 (left) and 1 (right). + * @return {AbstractSoundInstance} Returns reference to itself for chaining calls + */ + p.setPan = function (value) { + if(value == this._pan) { return this; } + this._pan = Math.max(-1, Math.min(1, value)); + this._updatePan(); + return this; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/pan:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method getPan + * @return {Number} The value of the pan, between -1 (left) and 1 (right). + */ + p.getPan = function () { + return this._pan; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/position:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method getPosition + * @return {Number} The position of the playhead in the sound, in milliseconds. + */ + p.getPosition = function () { + if (!this._paused && this.playState == createjs.Sound.PLAY_SUCCEEDED) { + this._position = this._calculateCurrentPosition(); + } + return this._position; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/position:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method setPosition + * @param {Number} value The position to place the playhead, in milliseconds. + * @return {AbstractSoundInstance} Returns reference to itself for chaining calls + */ + p.setPosition = function (value) { + this._position = Math.max(0, value); + if (this.playState == createjs.Sound.PLAY_SUCCEEDED) { + this._updatePosition(); + } + return this; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/startTime:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method getStartTime + * @return {Number} The startTime of the sound instance in milliseconds. + */ + p.getStartTime = function () { + return this._startTime; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/startTime:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method setStartTime + * @param {number} value The new startTime time in milli seconds. + * @return {AbstractSoundInstance} Returns reference to itself for chaining calls + */ + p.setStartTime = function (value) { + if (value == this._startTime) { return this; } + this._startTime = Math.max(0, value || 0); + this._updateStartTime(); + return this; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/duration:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method getDuration + * @return {Number} The duration of the sound instance in milliseconds. + */ + p.getDuration = function () { + return this._duration; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/duration:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method setDuration + * @param {number} value The new duration time in milli seconds. + * @return {AbstractSoundInstance} Returns reference to itself for chaining calls + * @since 0.6.0 + */ + p.setDuration = function (value) { + if (value == this._duration) { return this; } + this._duration = Math.max(0, value || 0); + this._updateDuration(); + return this; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/playbackResource:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method setPlayback + * @param {Object} value The new playback resource. + * @return {AbstractSoundInstance} Returns reference to itself for chaining calls + * @since 0.6.0 + **/ + p.setPlaybackResource = function (value) { + this._playbackResource = value; + if (this._duration == 0) { this._setDurationFromSource(); } + return this; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/playbackResource:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method setPlayback + * @param {Object} value The new playback resource. + * @return {Object} playback resource used for playing audio + * @since 0.6.0 + **/ + p.getPlaybackResource = function () { + return this._playbackResource; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/loop:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method getLoop + * @return {number} + * @since 0.6.0 + **/ + p.getLoop = function () { + return this._loop; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/loop:property"}}{{/crossLink}} directly as a property, + * + * @deprecated + * @method setLoop + * @param {number} value The number of times to loop after play. + * @since 0.6.0 + */ + p.setLoop = function (value) { + if(this._playbackResource != null) { + // remove looping + if (this._loop != 0 && value == 0) { + this._removeLooping(value); + } + // add looping + else if (this._loop == 0 && value != 0) { + this._addLooping(value); + } + } + this._loop = value; + }; + + +// Private Methods: + /** + * A helper method that dispatches all events for AbstractSoundInstance. + * @method _sendEvent + * @param {String} type The event type + * @protected + */ + p._sendEvent = function (type) { + var event = new createjs.Event(type); + this.dispatchEvent(event); + }; + + /** + * Clean up the instance. Remove references and clean up any additional properties such as timers. + * @method _cleanUp + * @protected + */ + p._cleanUp = function () { + clearTimeout(this.delayTimeoutId); // clear timeout that plays delayed sound + this._handleCleanUp(); + this._paused = false; + + createjs.Sound._playFinished(this); // TODO change to an event + }; + + /** + * The sound has been interrupted. + * @method _interrupt + * @protected + */ + p._interrupt = function () { + this._cleanUp(); + this.playState = createjs.Sound.PLAY_INTERRUPTED; + this._sendEvent("interrupted"); + }; + + /** + * Called by the Sound class when the audio is ready to play (delay has completed). Starts sound playing if the + * src is loaded, otherwise playback will fail. + * @method _beginPlaying + * @param {PlayPropsConfig} playProps A PlayPropsConfig object. + * @return {Boolean} If playback succeeded. + * @protected + */ + // OJR FlashAudioSoundInstance overwrites + p._beginPlaying = function (playProps) { + this.setPosition(playProps.offset); + this.setLoop(playProps.loop); + this.setVolume(playProps.volume); + this.setPan(playProps.pan); + if (playProps.startTime != null) { + this.setStartTime(playProps.startTime); + this.setDuration(playProps.duration); + } + + if (this._playbackResource != null && this._position < this._duration) { + this._paused = false; + this._handleSoundReady(); + this.playState = createjs.Sound.PLAY_SUCCEEDED; + this._sendEvent("succeeded"); + return true; + } else { + this._playFailed(); + return false; + } + }; + + /** + * Play has failed, which can happen for a variety of reasons. + * Cleans up instance and dispatches failed event + * @method _playFailed + * @private + */ + p._playFailed = function () { + this._cleanUp(); + this.playState = createjs.Sound.PLAY_FAILED; + this._sendEvent("failed"); + }; + + /** + * Audio has finished playing. Manually loop it if required. + * @method _handleSoundComplete + * @param event + * @protected + */ + p._handleSoundComplete = function (event) { + this._position = 0; // have to set this as it can be set by pause during playback + + if (this._loop != 0) { + this._loop--; // NOTE this introduces a theoretical limit on loops = float max size x 2 - 1 + this._handleLoop(); + this._sendEvent("loop"); + return; + } + + this._cleanUp(); + this.playState = createjs.Sound.PLAY_FINISHED; + this._sendEvent("complete"); + }; + +// Plugin specific code + /** + * Handles starting playback when the sound is ready for playing. + * @method _handleSoundReady + * @protected + */ + p._handleSoundReady = function () { + // plugin specific code + }; + + /** + * Internal function used to update the volume based on the instance volume, master volume, instance mute value, + * and master mute value. + * @method _updateVolume + * @protected + */ + p._updateVolume = function () { + // plugin specific code + }; + + /** + * Internal function used to update the pan + * @method _updatePan + * @protected + * @since 0.6.0 + */ + p._updatePan = function () { + // plugin specific code + }; + + /** + * Internal function used to update the startTime of the audio. + * @method _updateStartTime + * @protected + * @since 0.6.1 + */ + p._updateStartTime = function () { + // plugin specific code + }; + + /** + * Internal function used to update the duration of the audio. + * @method _updateDuration + * @protected + * @since 0.6.0 + */ + p._updateDuration = function () { + // plugin specific code + }; + + /** + * Internal function used to get the duration of the audio from the source we'll be playing. + * @method _updateDuration + * @protected + * @since 0.6.0 + */ + p._setDurationFromSource = function () { + // plugin specific code + }; + + /** + * Internal function that calculates the current position of the playhead and sets this._position to that value + * @method _calculateCurrentPosition + * @protected + * @since 0.6.0 + */ + p._calculateCurrentPosition = function () { + // plugin specific code that sets this.position + }; + + /** + * Internal function used to update the position of the playhead. + * @method _updatePosition + * @protected + * @since 0.6.0 + */ + p._updatePosition = function () { + // plugin specific code + }; + + /** + * Internal function called when looping is removed during playback. + * @method _removeLooping + * @param {number} value The number of times to loop after play. + * @protected + * @since 0.6.0 + */ + p._removeLooping = function (value) { + // plugin specific code + }; + + /** + * Internal function called when looping is added during playback. + * @method _addLooping + * @param {number} value The number of times to loop after play. + * @protected + * @since 0.6.0 + */ + p._addLooping = function (value) { + // plugin specific code + }; + + /** + * Internal function called when pausing playback + * @method _pause + * @protected + * @since 0.6.0 + */ + p._pause = function () { + // plugin specific code + }; + + /** + * Internal function called when resuming playback + * @method _resume + * @protected + * @since 0.6.0 + */ + p._resume = function () { + // plugin specific code + }; + + /** + * Internal function called when stopping playback + * @method _handleStop + * @protected + * @since 0.6.0 + */ + p._handleStop = function() { + // plugin specific code + }; + + /** + * Internal function called when AbstractSoundInstance is being cleaned up + * @method _handleCleanUp + * @protected + * @since 0.6.0 + */ + p._handleCleanUp = function() { + // plugin specific code + }; + + /** + * Internal function called when AbstractSoundInstance has played to end and is looping + * @method _handleLoop + * @protected + * @since 0.6.0 + */ + p._handleLoop = function () { + // plugin specific code + }; + + createjs.AbstractSoundInstance = createjs.promote(AbstractSoundInstance, "EventDispatcher"); + createjs.DefaultSoundInstance = createjs.AbstractSoundInstance; // used when no plugin is supported +}()); + +//############################################################################## +// AbstractPlugin.js +//############################################################################## + +this.createjs = this.createjs || {}; + +(function () { + "use strict"; + + +// constructor: + /** + * A default plugin class used as a base for all other plugins. + * @class AbstractPlugin + * @constructor + * @since 0.6.0 + */ + + var AbstractPlugin = function () { + // private properties: + /** + * The capabilities of the plugin. + * method and is used internally. + * @property _capabilities + * @type {Object} + * @default null + * @protected + * @static + */ + this._capabilities = null; + + /** + * Object hash indexed by the source URI of all created loaders, used to properly destroy them if sources are removed. + * @type {Object} + * @protected + */ + this._loaders = {}; + + /** + * Object hash indexed by the source URI of each file to indicate if an audio source has begun loading, + * is currently loading, or has completed loading. Can be used to store non boolean data after loading + * is complete (for example arrayBuffers for web audio). + * @property _audioSources + * @type {Object} + * @protected + */ + this._audioSources = {}; + + /** + * Object hash indexed by the source URI of all created SoundInstances, updates the playbackResource if it loads after they are created, + * and properly destroy them if sources are removed + * @type {Object} + * @protected + */ + this._soundInstances = {}; + + /** + * The internal master volume value of the plugin. + * @property _volume + * @type {Number} + * @default 1 + * @protected + */ + this._volume = 1; + + /** + * A reference to a loader class used by a plugin that must be set. + * @type {Object} + * @protected + */ + this._loaderClass; + + /** + * A reference to an AbstractSoundInstance class used by a plugin that must be set. + * @type {Object} + * @protected; + */ + this._soundInstanceClass; + }; + var p = AbstractPlugin.prototype; + + /** + * REMOVED. Removed in favor of using `MySuperClass_constructor`. + * See {{#crossLink "Utility Methods/extend"}}{{/crossLink}} and {{#crossLink "Utility Methods/promote"}}{{/crossLink}} + * for details. + * + * There is an inheritance tutorial distributed with EaselJS in /tutorials/Inheritance. + * + * @method initialize + * @protected + * @deprecated + */ + // p.initialize = function() {}; // searchable for devs wondering where it is. + + +// Static Properties: +// NOTE THESE PROPERTIES NEED TO BE ADDED TO EACH PLUGIN + /** + * The capabilities of the plugin. This is generated via the _generateCapabilities method and is used internally. + * @property _capabilities + * @type {Object} + * @default null + * @protected + * @static + */ + AbstractPlugin._capabilities = null; + + /** + * Determine if the plugin can be used in the current browser/OS. + * @method isSupported + * @return {Boolean} If the plugin can be initialized. + * @static + */ + AbstractPlugin.isSupported = function () { + return true; + }; + + +// public methods: + /** + * Pre-register a sound for preloading and setup. This is called by {{#crossLink "Sound"}}{{/crossLink}}. + * Note all plugins provide a Loader instance, which PreloadJS + * can use to assist with preloading. + * @method register + * @param {String} loadItem An Object containing the source of the audio + * Note that not every plugin will manage this value. + * @return {Object} A result object, containing a "tag" for preloading purposes. + */ + p.register = function (loadItem) { + var loader = this._loaders[loadItem.src]; + if(loader && !loader.canceled) {return this._loaders[loadItem.src];} // already loading/loaded this, so don't load twice + // OJR potential issue that we won't be firing loaded event, might need to trigger if this is already loaded? + this._audioSources[loadItem.src] = true; + this._soundInstances[loadItem.src] = []; + loader = new this._loaderClass(loadItem); + loader.on("complete", createjs.proxy(this._handlePreloadComplete, this)); + this._loaders[loadItem.src] = loader; + return loader; + }; + + // note sound calls register before calling preload + /** + * Internally preload a sound. + * @method preload + * @param {Loader} loader The sound URI to load. + */ + p.preload = function (loader) { + loader.on("error", createjs.proxy(this._handlePreloadError, this)); + loader.load(); + }; + + /** + * Checks if preloading has started for a specific source. If the source is found, we can assume it is loading, + * or has already finished loading. + * @method isPreloadStarted + * @param {String} src The sound URI to check. + * @return {Boolean} + */ + p.isPreloadStarted = function (src) { + return (this._audioSources[src] != null); + }; + + /** + * Checks if preloading has finished for a specific source. + * @method isPreloadComplete + * @param {String} src The sound URI to load. + * @return {Boolean} + */ + p.isPreloadComplete = function (src) { + return (!(this._audioSources[src] == null || this._audioSources[src] == true)); + }; + + /** + * Remove a sound added using {{#crossLink "WebAudioPlugin/register"}}{{/crossLink}}. Note this does not cancel a preload. + * @method removeSound + * @param {String} src The sound URI to unload. + */ + p.removeSound = function (src) { + if (!this._soundInstances[src]) { return; } + for (var i = this._soundInstances[src].length; i--; ) { + var item = this._soundInstances[src][i]; + item.destroy(); + } + delete(this._soundInstances[src]); + delete(this._audioSources[src]); + if(this._loaders[src]) { this._loaders[src].destroy(); } + delete(this._loaders[src]); + }; + + /** + * Remove all sounds added using {{#crossLink "WebAudioPlugin/register"}}{{/crossLink}}. Note this does not cancel a preload. + * @method removeAllSounds + * @param {String} src The sound URI to unload. + */ + p.removeAllSounds = function () { + for(var key in this._audioSources) { + this.removeSound(key); + } + }; + + /** + * Create a sound instance. If the sound has not been preloaded, it is internally preloaded here. + * @method create + * @param {String} src The sound source to use. + * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. + * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. + * @return {AbstractSoundInstance} A sound instance for playback and control. + */ + p.create = function (src, startTime, duration) { + if (!this.isPreloadStarted(src)) { + this.preload(this.register(src)); + } + var si = new this._soundInstanceClass(src, startTime, duration, this._audioSources[src]); + this._soundInstances[src].push(si); + return si; + }; + + // if a plugin does not support volume and mute, it should set these to null + /** + * Set the master volume of the plugin, which affects all SoundInstances. + * @method setVolume + * @param {Number} value The volume to set, between 0 and 1. + * @return {Boolean} If the plugin processes the setVolume call (true). The Sound class will affect all the + * instances manually otherwise. + */ + p.setVolume = function (value) { + this._volume = value; + this._updateVolume(); + return true; + }; + + /** + * Get the master volume of the plugin, which affects all SoundInstances. + * @method getVolume + * @return {Number} The volume level, between 0 and 1. + */ + p.getVolume = function () { + return this._volume; + }; + + /** + * Mute all sounds via the plugin. + * @method setMute + * @param {Boolean} value If all sound should be muted or not. Note that plugin-level muting just looks up + * the mute value of Sound {{#crossLink "Sound/getMute"}}{{/crossLink}}, so this property is not used here. + * @return {Boolean} If the mute call succeeds. + */ + p.setMute = function (value) { + this._updateVolume(); + return true; + }; + + // plugins should overwrite this method + p.toString = function () { + return "[AbstractPlugin]"; + }; + + +// private methods: + /** + * Handles internal preload completion. + * @method _handlePreloadComplete + * @protected + */ + p._handlePreloadComplete = function (event) { + var src = event.target.getItem().src; + this._audioSources[src] = event.result; + for (var i = 0, l = this._soundInstances[src].length; i < l; i++) { + var item = this._soundInstances[src][i]; + item.setPlaybackResource(this._audioSources[src]); + // ToDo consider adding play call here if playstate == playfailed + } + }; + + /** + * Handles internal preload erros + * @method _handlePreloadError + * @param event + * @protected + */ + p._handlePreloadError = function(event) { + //delete(this._audioSources[src]); + }; + + /** + * Set the gain value for master audio. Should not be called externally. + * @method _updateVolume + * @protected + */ + p._updateVolume = function () { + // Plugin Specific code + }; + + createjs.AbstractPlugin = AbstractPlugin; }()); //############################################################################## // WebAudioLoader.js //############################################################################## -this.createjs = this.createjs || {}; - -(function () { - "use strict"; - - /** - * Loader provides a mechanism to preload Web Audio content via PreloadJS or internally. Instances are returned to - * the preloader, and the load method is called when the asset needs to be requested. - * - * @class WebAudioLoader - * @param {String} loadItem The item to be loaded - * @extends XHRRequest - * @protected - */ - function Loader(loadItem) { - this.AbstractLoader_constructor(loadItem, true, createjs.AbstractLoader.SOUND); - - }; - var p = createjs.extend(Loader, createjs.AbstractLoader); - - // TODO: deprecated - // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. - - - /** - * web audio context required for decoding audio - * @property context - * @type {AudioContext} - * @static - */ - Loader.context = null; - - -// public methods - p.toString = function () { - return "[WebAudioLoader]"; - }; - - -// private methods - p._createRequest = function() { - this._request = new createjs.XHRRequest(this._item, false); - this._request.setResponseType("arraybuffer"); - }; - - p._sendComplete = function (event) { - // OJR we leave this wrapped in Loader because we need to reference src and the handler only receives a single argument, the decodedAudio - Loader.context.decodeAudioData(this._rawResult, - createjs.proxy(this._handleAudioDecoded, this), - createjs.proxy(this._sendError, this)); - }; - - - /** - * The audio has been decoded. - * @method handleAudioDecoded - * @param decoded - * @protected - */ - p._handleAudioDecoded = function (decodedAudio) { - this._result = decodedAudio; - this.AbstractLoader__sendComplete(); - }; - - createjs.WebAudioLoader = createjs.promote(Loader, "AbstractLoader"); +this.createjs = this.createjs || {}; + +(function () { + "use strict"; + + /** + * Loader provides a mechanism to preload Web Audio content via PreloadJS or internally. Instances are returned to + * the preloader, and the load method is called when the asset needs to be requested. + * + * @class WebAudioLoader + * @param {String} loadItem The item to be loaded + * @extends XHRRequest + * @protected + */ + function Loader(loadItem) { + this.AbstractLoader_constructor(loadItem, true, createjs.AbstractLoader.SOUND); + + }; + var p = createjs.extend(Loader, createjs.AbstractLoader); + + // TODO: deprecated + // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. + + + /** + * web audio context required for decoding audio + * @property context + * @type {AudioContext} + * @static + */ + Loader.context = null; + + +// public methods + p.toString = function () { + return "[WebAudioLoader]"; + }; + + +// private methods + p._createRequest = function() { + this._request = new createjs.XHRRequest(this._item, false); + this._request.setResponseType("arraybuffer"); + }; + + p._sendComplete = function (event) { + // OJR we leave this wrapped in Loader because we need to reference src and the handler only receives a single argument, the decodedAudio + Loader.context.decodeAudioData(this._rawResult, + createjs.proxy(this._handleAudioDecoded, this), + createjs.proxy(this._sendError, this)); + }; + + + /** + * The audio has been decoded. + * @method handleAudioDecoded + * @param decoded + * @protected + */ + p._handleAudioDecoded = function (decodedAudio) { + this._result = decodedAudio; + this.AbstractLoader__sendComplete(); + }; + + createjs.WebAudioLoader = createjs.promote(Loader, "AbstractLoader"); }()); //############################################################################## // WebAudioSoundInstance.js //############################################################################## -this.createjs = this.createjs || {}; - -/** - * WebAudioSoundInstance extends the base api of {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} and is used by - * {{#crossLink "WebAudioPlugin"}}{{/crossLink}}. - * - * WebAudioSoundInstance exposes audioNodes for advanced users. - * - * @param {String} src The path to and file name of the sound. - * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. - * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. - * @param {Object} playbackResource Any resource needed by plugin to support audio playback. - * @class WebAudioSoundInstance - * @extends AbstractSoundInstance - * @constructor - */ -(function () { - "use strict"; - - function WebAudioSoundInstance(src, startTime, duration, playbackResource) { - this.AbstractSoundInstance_constructor(src, startTime, duration, playbackResource); - - -// public properties - /** - * NOTE this is only intended for use by advanced users. - *
          GainNode for controlling WebAudioSoundInstance volume. Connected to the {{#crossLink "WebAudioSoundInstance/destinationNode:property"}}{{/crossLink}}. - * @property gainNode - * @type {AudioGainNode} - * @since 0.4.0 - * - */ - this.gainNode = s.context.createGain(); - - /** - * NOTE this is only intended for use by advanced users. - *
          A panNode allowing left and right audio channel panning only. Connected to WebAudioSoundInstance {{#crossLink "WebAudioSoundInstance/gainNode:property"}}{{/crossLink}}. - * @property panNode - * @type {AudioPannerNode} - * @since 0.4.0 - */ - this.panNode = s.context.createPanner(); - this.panNode.panningModel = s._panningModel; - this.panNode.connect(this.gainNode); - - /** - * NOTE this is only intended for use by advanced users. - *
          sourceNode is the audio source. Connected to WebAudioSoundInstance {{#crossLink "WebAudioSoundInstance/panNode:property"}}{{/crossLink}}. - * @property sourceNode - * @type {AudioNode} - * @since 0.4.0 - * - */ - this.sourceNode = null; - - -// private properties - /** - * Timeout that is created internally to handle sound playing to completion. - * Stored so we can remove it when stop, pause, or cleanup are called - * @property _soundCompleteTimeout - * @type {timeoutVariable} - * @default null - * @protected - * @since 0.4.0 - */ - this._soundCompleteTimeout = null; - - /** - * NOTE this is only intended for use by very advanced users. - * _sourceNodeNext is the audio source for the next loop, inserted in a look ahead approach to allow for smooth - * looping. Connected to {{#crossLink "WebAudioSoundInstance/gainNode:property"}}{{/crossLink}}. - * @property _sourceNodeNext - * @type {AudioNode} - * @default null - * @protected - * @since 0.4.1 - * - */ - this._sourceNodeNext = null; - - /** - * Time audio started playback, in seconds. Used to handle set position, get position, and resuming from paused. - * @property _playbackStartTime - * @type {Number} - * @default 0 - * @protected - * @since 0.4.0 - */ - this._playbackStartTime = 0; - - // Proxies, make removing listeners easier. - this._endedHandler = createjs.proxy(this._handleSoundComplete, this); - }; - var p = createjs.extend(WebAudioSoundInstance, createjs.AbstractSoundInstance); - var s = WebAudioSoundInstance; - - // TODO: deprecated - // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. - - - /** - * Note this is only intended for use by advanced users. - *
          Audio context used to create nodes. This is and needs to be the same context used by {{#crossLink "WebAudioPlugin"}}{{/crossLink}}. - * @property context - * @type {AudioContext} - * @static - * @since 0.6.0 - */ - s.context = null; - - /** - * Note this is only intended for use by advanced users. - *
          Audio node from WebAudioPlugin that sequences to context.destination - * @property destinationNode - * @type {AudioNode} - * @static - * @since 0.6.0 - */ - s.destinationNode = null; - - /** - * Value to set panning model to equal power for WebAudioSoundInstance. Can be "equalpower" or 0 depending on browser implementation. - * @property _panningModel - * @type {Number / String} - * @protected - * @static - * @since 0.6.0 - */ - s._panningModel = "equalpower"; - - -// Public methods - p.destroy = function() { - this.AbstractSoundInstance_destroy(); - - this.panNode.disconnect(0); - this.panNode = null; - this.gainNode.disconnect(0); - this.gainNode = null; - }; - - p.toString = function () { - return "[WebAudioSoundInstance]"; - }; - - -// Private Methods - p._updatePan = function() { - this.panNode.setPosition(this._pan, 0, -0.5); - // z need to be -0.5 otherwise the sound only plays in left, right, or center - }; - - p._removeLooping = function(value) { - this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); - }; - - p._addLooping = function(value) { - if (this.playState != createjs.Sound.PLAY_SUCCEEDED) { return; } - this._sourceNodeNext = this._createAndPlayAudioNode(this._playbackStartTime, 0); - }; - - p._setDurationFromSource = function () { - this._duration = this.playbackResource.duration * 1000; - }; - - p._handleCleanUp = function () { - if (this.sourceNode && this.playState == createjs.Sound.PLAY_SUCCEEDED) { - this.sourceNode = this._cleanUpAudioNode(this.sourceNode); - this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); - } - - if (this.gainNode.numberOfOutputs != 0) {this.gainNode.disconnect(0);} - // OJR there appears to be a bug that this doesn't always work in webkit (Chrome and Safari). According to the documentation, this should work. - - clearTimeout(this._soundCompleteTimeout); - - this._playbackStartTime = 0; // This is used by getPosition - }; - - /** - * Turn off and disconnect an audioNode, then set reference to null to release it for garbage collection - * @method _cleanUpAudioNode - * @param audioNode - * @return {audioNode} - * @protected - * @since 0.4.1 - */ - p._cleanUpAudioNode = function(audioNode) { - if(audioNode) { - audioNode.stop(0); - audioNode.disconnect(0); - audioNode = null; - } - return audioNode; - }; - - p._handleSoundReady = function (event) { - this.gainNode.connect(s.destinationNode); // this line can cause a memory leak. Nodes need to be disconnected from the audioDestination or any sequence that leads to it. - - var dur = this._duration * 0.001; - var pos = this._position * 0.001; - if (pos > dur) {pos = dur;} - this.sourceNode = this._createAndPlayAudioNode((s.context.currentTime - dur), pos); - this._playbackStartTime = this.sourceNode.startTime - pos; - - this._soundCompleteTimeout = setTimeout(this._endedHandler, (dur - pos) * 1000); - - if(this._loop != 0) { - this._sourceNodeNext = this._createAndPlayAudioNode(this._playbackStartTime, 0); - } - }; - - /** - * Creates an audio node using the current src and context, connects it to the gain node, and starts playback. - * @method _createAndPlayAudioNode - * @param {Number} startTime The time to add this to the web audio context, in seconds. - * @param {Number} offset The amount of time into the src audio to start playback, in seconds. - * @return {audioNode} - * @protected - * @since 0.4.1 - */ - p._createAndPlayAudioNode = function(startTime, offset) { - var audioNode = s.context.createBufferSource(); - audioNode.buffer = this.playbackResource; - audioNode.connect(this.panNode); - var dur = this._duration * 0.001; - audioNode.startTime = startTime + dur; - audioNode.start(audioNode.startTime, offset+(this._startTime*0.001), dur - offset); - return audioNode; - }; - - p._pause = function () { - this._position = (s.context.currentTime - this._playbackStartTime) * 1000; // * 1000 to give milliseconds, lets us restart at same point - this.sourceNode = this._cleanUpAudioNode(this.sourceNode); - this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); - - if (this.gainNode.numberOfOutputs != 0) {this.gainNode.disconnect(0);} - - clearTimeout(this._soundCompleteTimeout); - }; - - p._resume = function () { - this._handleSoundReady(); - }; - - /* - p._handleStop = function () { - // web audio does not need to do anything extra - }; - */ - - p._updateVolume = function () { - var newVolume = this._muted ? 0 : this._volume; - if (newVolume != this.gainNode.gain.value) { - this.gainNode.gain.value = newVolume; - } - }; - - p._calculateCurrentPosition = function () { - return ((s.context.currentTime - this._playbackStartTime) * 1000); // pos in seconds * 1000 to give milliseconds - }; - - p._updatePosition = function () { - this.sourceNode = this._cleanUpAudioNode(this.sourceNode); - this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); - clearTimeout(this._soundCompleteTimeout); - - if (!this._paused) {this._handleSoundReady();} - }; - - // OJR we are using a look ahead approach to ensure smooth looping. - // We add _sourceNodeNext to the audio context so that it starts playing even if this callback is delayed. - // This technique is described here: http://www.html5rocks.com/en/tutorials/audio/scheduling/ - // NOTE the cost of this is that our audio loop may not always match the loop event timing precisely. - p._handleLoop = function () { - this._cleanUpAudioNode(this.sourceNode); - this.sourceNode = this._sourceNodeNext; - this._playbackStartTime = this.sourceNode.startTime; - this._sourceNodeNext = this._createAndPlayAudioNode(this._playbackStartTime, 0); - this._soundCompleteTimeout = setTimeout(this._endedHandler, this._duration); - }; - - p._updateDuration = function () { - if(this.playState == createjs.Sound.PLAY_SUCCEEDED) { - this._pause(); - this._resume(); - } - }; - - createjs.WebAudioSoundInstance = createjs.promote(WebAudioSoundInstance, "AbstractSoundInstance"); +this.createjs = this.createjs || {}; + +/** + * WebAudioSoundInstance extends the base api of {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} and is used by + * {{#crossLink "WebAudioPlugin"}}{{/crossLink}}. + * + * WebAudioSoundInstance exposes audioNodes for advanced users. + * + * @param {String} src The path to and file name of the sound. + * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. + * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. + * @param {Object} playbackResource Any resource needed by plugin to support audio playback. + * @class WebAudioSoundInstance + * @extends AbstractSoundInstance + * @constructor + */ +(function () { + "use strict"; + + function WebAudioSoundInstance(src, startTime, duration, playbackResource) { + this.AbstractSoundInstance_constructor(src, startTime, duration, playbackResource); + + +// public properties + /** + * NOTE this is only intended for use by advanced users. + *
          GainNode for controlling WebAudioSoundInstance volume. Connected to the {{#crossLink "WebAudioSoundInstance/destinationNode:property"}}{{/crossLink}}. + * @property gainNode + * @type {AudioGainNode} + * @since 0.4.0 + * + */ + this.gainNode = s.context.createGain(); + + /** + * NOTE this is only intended for use by advanced users. + *
          A panNode allowing left and right audio channel panning only. Connected to WebAudioSoundInstance {{#crossLink "WebAudioSoundInstance/gainNode:property"}}{{/crossLink}}. + * @property panNode + * @type {AudioPannerNode} + * @since 0.4.0 + */ + this.panNode = s.context.createPanner(); + this.panNode.panningModel = s._panningModel; + this.panNode.connect(this.gainNode); + + /** + * NOTE this is only intended for use by advanced users. + *
          sourceNode is the audio source. Connected to WebAudioSoundInstance {{#crossLink "WebAudioSoundInstance/panNode:property"}}{{/crossLink}}. + * @property sourceNode + * @type {AudioNode} + * @since 0.4.0 + * + */ + this.sourceNode = null; + + +// private properties + /** + * Timeout that is created internally to handle sound playing to completion. + * Stored so we can remove it when stop, pause, or cleanup are called + * @property _soundCompleteTimeout + * @type {timeoutVariable} + * @default null + * @protected + * @since 0.4.0 + */ + this._soundCompleteTimeout = null; + + /** + * NOTE this is only intended for use by very advanced users. + * _sourceNodeNext is the audio source for the next loop, inserted in a look ahead approach to allow for smooth + * looping. Connected to {{#crossLink "WebAudioSoundInstance/gainNode:property"}}{{/crossLink}}. + * @property _sourceNodeNext + * @type {AudioNode} + * @default null + * @protected + * @since 0.4.1 + * + */ + this._sourceNodeNext = null; + + /** + * Time audio started playback, in seconds. Used to handle set position, get position, and resuming from paused. + * @property _playbackStartTime + * @type {Number} + * @default 0 + * @protected + * @since 0.4.0 + */ + this._playbackStartTime = 0; + + // Proxies, make removing listeners easier. + this._endedHandler = createjs.proxy(this._handleSoundComplete, this); + }; + var p = createjs.extend(WebAudioSoundInstance, createjs.AbstractSoundInstance); + var s = WebAudioSoundInstance; + + // TODO: deprecated + // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. + + + /** + * Note this is only intended for use by advanced users. + *
          Audio context used to create nodes. This is and needs to be the same context used by {{#crossLink "WebAudioPlugin"}}{{/crossLink}}. + * @property context + * @type {AudioContext} + * @static + * @since 0.6.0 + */ + s.context = null; + + /** + * Note this is only intended for use by advanced users. + *
          Audio node from WebAudioPlugin that sequences to context.destination + * @property destinationNode + * @type {AudioNode} + * @static + * @since 0.6.0 + */ + s.destinationNode = null; + + /** + * Value to set panning model to equal power for WebAudioSoundInstance. Can be "equalpower" or 0 depending on browser implementation. + * @property _panningModel + * @type {Number / String} + * @protected + * @static + * @since 0.6.0 + */ + s._panningModel = "equalpower"; + + +// Public methods + p.destroy = function() { + this.AbstractSoundInstance_destroy(); + + this.panNode.disconnect(0); + this.panNode = null; + this.gainNode.disconnect(0); + this.gainNode = null; + }; + + p.toString = function () { + return "[WebAudioSoundInstance]"; + }; + + +// Private Methods + p._updatePan = function() { + this.panNode.setPosition(this._pan, 0, -0.5); + // z need to be -0.5 otherwise the sound only plays in left, right, or center + }; + + p._removeLooping = function(value) { + this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); + }; + + p._addLooping = function(value) { + if (this.playState != createjs.Sound.PLAY_SUCCEEDED) { return; } + this._sourceNodeNext = this._createAndPlayAudioNode(this._playbackStartTime, 0); + }; + + p._setDurationFromSource = function () { + this._duration = this.playbackResource.duration * 1000; + }; + + p._handleCleanUp = function () { + if (this.sourceNode && this.playState == createjs.Sound.PLAY_SUCCEEDED) { + this.sourceNode = this._cleanUpAudioNode(this.sourceNode); + this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); + } + + if (this.gainNode.numberOfOutputs != 0) {this.gainNode.disconnect(0);} + // OJR there appears to be a bug that this doesn't always work in webkit (Chrome and Safari). According to the documentation, this should work. + + clearTimeout(this._soundCompleteTimeout); + + this._playbackStartTime = 0; // This is used by getPosition + }; + + /** + * Turn off and disconnect an audioNode, then set reference to null to release it for garbage collection + * @method _cleanUpAudioNode + * @param audioNode + * @return {audioNode} + * @protected + * @since 0.4.1 + */ + p._cleanUpAudioNode = function(audioNode) { + if(audioNode) { + audioNode.stop(0); + audioNode.disconnect(0); + audioNode = null; + } + return audioNode; + }; + + p._handleSoundReady = function (event) { + this.gainNode.connect(s.destinationNode); // this line can cause a memory leak. Nodes need to be disconnected from the audioDestination or any sequence that leads to it. + + var dur = this._duration * 0.001; + var pos = this._position * 0.001; + if (pos > dur) {pos = dur;} + this.sourceNode = this._createAndPlayAudioNode((s.context.currentTime - dur), pos); + this._playbackStartTime = this.sourceNode.startTime - pos; + + this._soundCompleteTimeout = setTimeout(this._endedHandler, (dur - pos) * 1000); + + if(this._loop != 0) { + this._sourceNodeNext = this._createAndPlayAudioNode(this._playbackStartTime, 0); + } + }; + + /** + * Creates an audio node using the current src and context, connects it to the gain node, and starts playback. + * @method _createAndPlayAudioNode + * @param {Number} startTime The time to add this to the web audio context, in seconds. + * @param {Number} offset The amount of time into the src audio to start playback, in seconds. + * @return {audioNode} + * @protected + * @since 0.4.1 + */ + p._createAndPlayAudioNode = function(startTime, offset) { + var audioNode = s.context.createBufferSource(); + audioNode.buffer = this.playbackResource; + audioNode.connect(this.panNode); + var dur = this._duration * 0.001; + audioNode.startTime = startTime + dur; + audioNode.start(audioNode.startTime, offset+(this._startTime*0.001), dur - offset); + return audioNode; + }; + + p._pause = function () { + this._position = (s.context.currentTime - this._playbackStartTime) * 1000; // * 1000 to give milliseconds, lets us restart at same point + this.sourceNode = this._cleanUpAudioNode(this.sourceNode); + this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); + + if (this.gainNode.numberOfOutputs != 0) {this.gainNode.disconnect(0);} + + clearTimeout(this._soundCompleteTimeout); + }; + + p._resume = function () { + this._handleSoundReady(); + }; + + /* + p._handleStop = function () { + // web audio does not need to do anything extra + }; + */ + + p._updateVolume = function () { + var newVolume = this._muted ? 0 : this._volume; + if (newVolume != this.gainNode.gain.value) { + this.gainNode.gain.value = newVolume; + } + }; + + p._calculateCurrentPosition = function () { + return ((s.context.currentTime - this._playbackStartTime) * 1000); // pos in seconds * 1000 to give milliseconds + }; + + p._updatePosition = function () { + this.sourceNode = this._cleanUpAudioNode(this.sourceNode); + this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); + clearTimeout(this._soundCompleteTimeout); + + if (!this._paused) {this._handleSoundReady();} + }; + + // OJR we are using a look ahead approach to ensure smooth looping. + // We add _sourceNodeNext to the audio context so that it starts playing even if this callback is delayed. + // This technique is described here: http://www.html5rocks.com/en/tutorials/audio/scheduling/ + // NOTE the cost of this is that our audio loop may not always match the loop event timing precisely. + p._handleLoop = function () { + this._cleanUpAudioNode(this.sourceNode); + this.sourceNode = this._sourceNodeNext; + this._playbackStartTime = this.sourceNode.startTime; + this._sourceNodeNext = this._createAndPlayAudioNode(this._playbackStartTime, 0); + this._soundCompleteTimeout = setTimeout(this._endedHandler, this._duration); + }; + + p._updateDuration = function () { + if(this.playState == createjs.Sound.PLAY_SUCCEEDED) { + this._pause(); + this._resume(); + } + }; + + createjs.WebAudioSoundInstance = createjs.promote(WebAudioSoundInstance, "AbstractSoundInstance"); }()); //############################################################################## // WebAudioPlugin.js //############################################################################## -this.createjs = this.createjs || {}; - -(function () { - - "use strict"; - - /** - * Play sounds using Web Audio in the browser. The WebAudioPlugin is currently the default plugin, and will be used - * anywhere that it is supported. To change plugin priority, check out the Sound API - * {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} method. - - *

          Known Browser and OS issues for Web Audio

          - * Firefox 25 - *
          • mp3 audio files do not load properly on all windows machines, reported - * here.
            - * For this reason it is recommended to pass another FF supported type (ie ogg) first until this bug is resolved, if possible.
          - *
          - * Webkit (Chrome and Safari) - *
          • AudioNode.disconnect does not always seem to work. This can cause the file size to grow over time if you - * are playing a lot of audio files.
          - *
          - * iOS 6 limitations - *
          • Sound is initially muted and will only unmute through play being called inside a user initiated event (touch/click).
          • - *
          • A bug exists that will distort uncached audio when a video element is present in the DOM. You can avoid this bug - * by ensuring the audio and video audio share the same sampleRate.
          • - *
          - * @class WebAudioPlugin - * @extends AbstractPlugin - * @constructor - * @since 0.4.0 - */ - function WebAudioPlugin() { - this.AbstractPlugin_constructor(); - - -// Private Properties - /** - * Value to set panning model to equal power for WebAudioSoundInstance. Can be "equalpower" or 0 depending on browser implementation. - * @property _panningModel - * @type {Number / String} - * @protected - */ - this._panningModel = s._panningModel;; - - /** - * The web audio context, which WebAudio uses to play audio. All nodes that interact with the WebAudioPlugin - * need to be created within this context. - * @property context - * @type {AudioContext} - */ - this.context = s.context; - - /** - * A DynamicsCompressorNode, which is used to improve sound quality and prevent audio distortion. - * It is connected to context.destination. - * - * Can be accessed by advanced users through createjs.Sound.activePlugin.dynamicsCompressorNode. - * @property dynamicsCompressorNode - * @type {AudioNode} - */ - this.dynamicsCompressorNode = this.context.createDynamicsCompressor(); - this.dynamicsCompressorNode.connect(this.context.destination); - - /** - * A GainNode for controlling master volume. It is connected to {{#crossLink "WebAudioPlugin/dynamicsCompressorNode:property"}}{{/crossLink}}. - * - * Can be accessed by advanced users through createjs.Sound.activePlugin.gainNode. - * @property gainNode - * @type {AudioGainNode} - */ - this.gainNode = this.context.createGain(); - this.gainNode.connect(this.dynamicsCompressorNode); - createjs.WebAudioSoundInstance.destinationNode = this.gainNode; - - this._capabilities = s._capabilities; - - this._loaderClass = createjs.WebAudioLoader; - this._soundInstanceClass = createjs.WebAudioSoundInstance; - - this._addPropsToClasses(); - } - var p = createjs.extend(WebAudioPlugin, createjs.AbstractPlugin); - - // TODO: deprecated - // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. - - -// Static Properties - var s = WebAudioPlugin; - /** - * The capabilities of the plugin. This is generated via the {{#crossLink "WebAudioPlugin/_generateCapabilities:method"}}{{/crossLink}} - * method and is used internally. - * @property _capabilities - * @type {Object} - * @default null - * @protected - * @static - */ - s._capabilities = null; - - /** - * Value to set panning model to equal power for WebAudioSoundInstance. Can be "equalpower" or 0 depending on browser implementation. - * @property _panningModel - * @type {Number / String} - * @protected - * @static - */ - s._panningModel = "equalpower"; - - /** - * The web audio context, which WebAudio uses to play audio. All nodes that interact with the WebAudioPlugin - * need to be created within this context. - * - * Advanced users can set this to an existing context, but must do so before they call - * {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} or {{#crossLink "Sound/initializeDefaultPlugins"}}{{/crossLink}}. - * - * @property context - * @type {AudioContext} - * @static - */ - s.context = null; - - -// Static Public Methods - /** - * Determine if the plugin can be used in the current browser/OS. - * @method isSupported - * @return {Boolean} If the plugin can be initialized. - * @static - */ - s.isSupported = function () { - // check if this is some kind of mobile device, Web Audio works with local protocol under PhoneGap and it is unlikely someone is trying to run a local file - var isMobilePhoneGap = createjs.BrowserDetect.isIOS || createjs.BrowserDetect.isAndroid || createjs.BrowserDetect.isBlackberry; - // OJR isMobile may be redundant with _isFileXHRSupported available. Consider removing. - if (location.protocol == "file:" && !isMobilePhoneGap && !this._isFileXHRSupported()) { return false; } // Web Audio requires XHR, which is not usually available locally - s._generateCapabilities(); - if (s.context == null) {return false;} - return true; - }; - - /** - * Plays an empty sound in the web audio context. This is used to enable web audio on iOS devices, as they - * require the first sound to be played inside of a user initiated event (touch/click). This is called when - * {{#crossLink "WebAudioPlugin"}}{{/crossLink}} is initialized (by Sound {{#crossLink "Sound/initializeDefaultPlugins"}}{{/crossLink}} - * for example). - * - *

          Example

          - * - * function handleTouch(event) { - * createjs.WebAudioPlugin.playEmptySound(); - * } - * - * @method playEmptySound - * @static - * @since 0.4.1 - */ - s.playEmptySound = function() { - if (s.context == null) {return;} - var source = s.context.createBufferSource(); - source.buffer = s.context.createBuffer(1, 1, 22050); - source.connect(s.context.destination); - source.start(0, 0, 0); - }; - - -// Static Private Methods - /** - * Determine if XHR is supported, which is necessary for web audio. - * @method _isFileXHRSupported - * @return {Boolean} If XHR is supported. - * @since 0.4.2 - * @protected - * @static - */ - s._isFileXHRSupported = function() { - // it's much easier to detect when something goes wrong, so let's start optimistically - var supported = true; - - var xhr = new XMLHttpRequest(); - try { - xhr.open("GET", "WebAudioPluginTest.fail", false); // loading non-existant file triggers 404 only if it could load (synchronous call) - } catch (error) { - // catch errors in cases where the onerror is passed by - supported = false; - return supported; - } - xhr.onerror = function() { supported = false; }; // cause irrelevant - // with security turned off, we can get empty success results, which is actually a failed read (status code 0?) - xhr.onload = function() { supported = this.status == 404 || (this.status == 200 || (this.status == 0 && this.response != "")); }; - try { - xhr.send(); - } catch (error) { - // catch errors in cases where the onerror is passed by - supported = false; - } - - return supported; - }; - - /** - * Determine the capabilities of the plugin. Used internally. Please see the Sound API {{#crossLink "Sound/getCapabilities"}}{{/crossLink}} - * method for an overview of plugin capabilities. - * @method _generateCapabilities - * @static - * @protected - */ - s._generateCapabilities = function () { - if (s._capabilities != null) {return;} - // Web Audio can be in any formats supported by the audio element, from http://www.w3.org/TR/webaudio/#AudioContext-section - var t = document.createElement("audio"); - if (t.canPlayType == null) {return null;} - - if (s.context == null) { - if (window.AudioContext) { - s.context = new AudioContext(); - } else if (window.webkitAudioContext) { - s.context = new webkitAudioContext(); - } else { - return null; - } - } - - s._compatibilitySetUp(); - - // playing this inside of a touch event will enable audio on iOS, which starts muted - s.playEmptySound(); - - s._capabilities = { - panning:true, - volume:true, - tracks:-1 - }; - - // determine which extensions our browser supports for this plugin by iterating through Sound.SUPPORTED_EXTENSIONS - var supportedExtensions = createjs.Sound.SUPPORTED_EXTENSIONS; - var extensionMap = createjs.Sound.EXTENSION_MAP; - for (var i = 0, l = supportedExtensions.length; i < l; i++) { - var ext = supportedExtensions[i]; - var playType = extensionMap[ext] || ext; - s._capabilities[ext] = (t.canPlayType("audio/" + ext) != "no" && t.canPlayType("audio/" + ext) != "") || (t.canPlayType("audio/" + playType) != "no" && t.canPlayType("audio/" + playType) != ""); - } // OJR another way to do this might be canPlayType:"m4a", codex: mp4 - - // 0=no output, 1=mono, 2=stereo, 4=surround, 6=5.1 surround. - // See http://www.w3.org/TR/webaudio/#AudioChannelSplitter for more details on channels. - if (s.context.destination.numberOfChannels < 2) { - s._capabilities.panning = false; - } - }; - - /** - * Set up compatibility if only deprecated web audio calls are supported. - * See http://www.w3.org/TR/webaudio/#DeprecationNotes - * Needed so we can support new browsers that don't support deprecated calls (Firefox) as well as old browsers that - * don't support new calls. - * - * @method _compatibilitySetUp - * @static - * @protected - * @since 0.4.2 - */ - s._compatibilitySetUp = function() { - s._panningModel = "equalpower"; - //assume that if one new call is supported, they all are - if (s.context.createGain) { return; } - - // simple name change, functionality the same - s.context.createGain = s.context.createGainNode; - - // source node, add to prototype - var audioNode = s.context.createBufferSource(); - audioNode.__proto__.start = audioNode.__proto__.noteGrainOn; // note that noteGrainOn requires all 3 parameters - audioNode.__proto__.stop = audioNode.__proto__.noteOff; - - // panningModel - s._panningModel = 0; - }; - - -// Public Methods - p.toString = function () { - return "[WebAudioPlugin]"; - }; - - -// Private Methods - /** - * Set up needed properties on supported classes WebAudioSoundInstance and WebAudioLoader. - * @method _addPropsToClasses - * @static - * @protected - * @since 0.6.0 - */ - p._addPropsToClasses = function() { - var c = this._soundInstanceClass; - c.context = this.context; - c.destinationNode = this.gainNode; - c._panningModel = this._panningModel; - - this._loaderClass.context = this.context; - }; - - - /** - * Set the gain value for master audio. Should not be called externally. - * @method _updateVolume - * @protected - */ - p._updateVolume = function () { - var newVolume = createjs.Sound._masterMute ? 0 : this._volume; - if (newVolume != this.gainNode.gain.value) { - this.gainNode.gain.value = newVolume; - } - }; - - createjs.WebAudioPlugin = createjs.promote(WebAudioPlugin, "AbstractPlugin"); +this.createjs = this.createjs || {}; + +(function () { + + "use strict"; + + /** + * Play sounds using Web Audio in the browser. The WebAudioPlugin is currently the default plugin, and will be used + * anywhere that it is supported. To change plugin priority, check out the Sound API + * {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} method. + + *

          Known Browser and OS issues for Web Audio

          + * Firefox 25 + *
          • mp3 audio files do not load properly on all windows machines, reported + * here.
            + * For this reason it is recommended to pass another FF supported type (ie ogg) first until this bug is resolved, if possible.
          + *
          + * Webkit (Chrome and Safari) + *
          • AudioNode.disconnect does not always seem to work. This can cause the file size to grow over time if you + * are playing a lot of audio files.
          + *
          + * iOS 6 limitations + *
          • Sound is initially muted and will only unmute through play being called inside a user initiated event (touch/click).
          • + *
          • A bug exists that will distort uncached audio when a video element is present in the DOM. You can avoid this bug + * by ensuring the audio and video audio share the same sampleRate.
          • + *
          + * @class WebAudioPlugin + * @extends AbstractPlugin + * @constructor + * @since 0.4.0 + */ + function WebAudioPlugin() { + this.AbstractPlugin_constructor(); + + +// Private Properties + /** + * Value to set panning model to equal power for WebAudioSoundInstance. Can be "equalpower" or 0 depending on browser implementation. + * @property _panningModel + * @type {Number / String} + * @protected + */ + this._panningModel = s._panningModel;; + + /** + * The web audio context, which WebAudio uses to play audio. All nodes that interact with the WebAudioPlugin + * need to be created within this context. + * @property context + * @type {AudioContext} + */ + this.context = s.context; + + /** + * A DynamicsCompressorNode, which is used to improve sound quality and prevent audio distortion. + * It is connected to context.destination. + * + * Can be accessed by advanced users through createjs.Sound.activePlugin.dynamicsCompressorNode. + * @property dynamicsCompressorNode + * @type {AudioNode} + */ + this.dynamicsCompressorNode = this.context.createDynamicsCompressor(); + this.dynamicsCompressorNode.connect(this.context.destination); + + /** + * A GainNode for controlling master volume. It is connected to {{#crossLink "WebAudioPlugin/dynamicsCompressorNode:property"}}{{/crossLink}}. + * + * Can be accessed by advanced users through createjs.Sound.activePlugin.gainNode. + * @property gainNode + * @type {AudioGainNode} + */ + this.gainNode = this.context.createGain(); + this.gainNode.connect(this.dynamicsCompressorNode); + createjs.WebAudioSoundInstance.destinationNode = this.gainNode; + + this._capabilities = s._capabilities; + + this._loaderClass = createjs.WebAudioLoader; + this._soundInstanceClass = createjs.WebAudioSoundInstance; + + this._addPropsToClasses(); + } + var p = createjs.extend(WebAudioPlugin, createjs.AbstractPlugin); + + // TODO: deprecated + // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. + + +// Static Properties + var s = WebAudioPlugin; + /** + * The capabilities of the plugin. This is generated via the {{#crossLink "WebAudioPlugin/_generateCapabilities:method"}}{{/crossLink}} + * method and is used internally. + * @property _capabilities + * @type {Object} + * @default null + * @protected + * @static + */ + s._capabilities = null; + + /** + * Value to set panning model to equal power for WebAudioSoundInstance. Can be "equalpower" or 0 depending on browser implementation. + * @property _panningModel + * @type {Number / String} + * @protected + * @static + */ + s._panningModel = "equalpower"; + + /** + * The web audio context, which WebAudio uses to play audio. All nodes that interact with the WebAudioPlugin + * need to be created within this context. + * + * Advanced users can set this to an existing context, but must do so before they call + * {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} or {{#crossLink "Sound/initializeDefaultPlugins"}}{{/crossLink}}. + * + * @property context + * @type {AudioContext} + * @static + */ + s.context = null; + + +// Static Public Methods + /** + * Determine if the plugin can be used in the current browser/OS. + * @method isSupported + * @return {Boolean} If the plugin can be initialized. + * @static + */ + s.isSupported = function () { + // check if this is some kind of mobile device, Web Audio works with local protocol under PhoneGap and it is unlikely someone is trying to run a local file + var isMobilePhoneGap = createjs.BrowserDetect.isIOS || createjs.BrowserDetect.isAndroid || createjs.BrowserDetect.isBlackberry; + // OJR isMobile may be redundant with _isFileXHRSupported available. Consider removing. + if (location.protocol == "file:" && !isMobilePhoneGap && !this._isFileXHRSupported()) { return false; } // Web Audio requires XHR, which is not usually available locally + s._generateCapabilities(); + if (s.context == null) {return false;} + return true; + }; + + /** + * Plays an empty sound in the web audio context. This is used to enable web audio on iOS devices, as they + * require the first sound to be played inside of a user initiated event (touch/click). This is called when + * {{#crossLink "WebAudioPlugin"}}{{/crossLink}} is initialized (by Sound {{#crossLink "Sound/initializeDefaultPlugins"}}{{/crossLink}} + * for example). + * + *

          Example

          + * + * function handleTouch(event) { + * createjs.WebAudioPlugin.playEmptySound(); + * } + * + * @method playEmptySound + * @static + * @since 0.4.1 + */ + s.playEmptySound = function() { + if (s.context == null) {return;} + var source = s.context.createBufferSource(); + source.buffer = s.context.createBuffer(1, 1, 22050); + source.connect(s.context.destination); + source.start(0, 0, 0); + }; + + +// Static Private Methods + /** + * Determine if XHR is supported, which is necessary for web audio. + * @method _isFileXHRSupported + * @return {Boolean} If XHR is supported. + * @since 0.4.2 + * @protected + * @static + */ + s._isFileXHRSupported = function() { + // it's much easier to detect when something goes wrong, so let's start optimistically + var supported = true; + + var xhr = new XMLHttpRequest(); + try { + xhr.open("GET", "WebAudioPluginTest.fail", false); // loading non-existant file triggers 404 only if it could load (synchronous call) + } catch (error) { + // catch errors in cases where the onerror is passed by + supported = false; + return supported; + } + xhr.onerror = function() { supported = false; }; // cause irrelevant + // with security turned off, we can get empty success results, which is actually a failed read (status code 0?) + xhr.onload = function() { supported = this.status == 404 || (this.status == 200 || (this.status == 0 && this.response != "")); }; + try { + xhr.send(); + } catch (error) { + // catch errors in cases where the onerror is passed by + supported = false; + } + + return supported; + }; + + /** + * Determine the capabilities of the plugin. Used internally. Please see the Sound API {{#crossLink "Sound/getCapabilities"}}{{/crossLink}} + * method for an overview of plugin capabilities. + * @method _generateCapabilities + * @static + * @protected + */ + s._generateCapabilities = function () { + if (s._capabilities != null) {return;} + // Web Audio can be in any formats supported by the audio element, from http://www.w3.org/TR/webaudio/#AudioContext-section + var t = document.createElement("audio"); + if (t.canPlayType == null) {return null;} + + if (s.context == null) { + if (window.AudioContext) { + s.context = new AudioContext(); + } else if (window.webkitAudioContext) { + s.context = new webkitAudioContext(); + } else { + return null; + } + } + + s._compatibilitySetUp(); + + // playing this inside of a touch event will enable audio on iOS, which starts muted + s.playEmptySound(); + + s._capabilities = { + panning:true, + volume:true, + tracks:-1 + }; + + // determine which extensions our browser supports for this plugin by iterating through Sound.SUPPORTED_EXTENSIONS + var supportedExtensions = createjs.Sound.SUPPORTED_EXTENSIONS; + var extensionMap = createjs.Sound.EXTENSION_MAP; + for (var i = 0, l = supportedExtensions.length; i < l; i++) { + var ext = supportedExtensions[i]; + var playType = extensionMap[ext] || ext; + s._capabilities[ext] = (t.canPlayType("audio/" + ext) != "no" && t.canPlayType("audio/" + ext) != "") || (t.canPlayType("audio/" + playType) != "no" && t.canPlayType("audio/" + playType) != ""); + } // OJR another way to do this might be canPlayType:"m4a", codex: mp4 + + // 0=no output, 1=mono, 2=stereo, 4=surround, 6=5.1 surround. + // See http://www.w3.org/TR/webaudio/#AudioChannelSplitter for more details on channels. + if (s.context.destination.numberOfChannels < 2) { + s._capabilities.panning = false; + } + }; + + /** + * Set up compatibility if only deprecated web audio calls are supported. + * See http://www.w3.org/TR/webaudio/#DeprecationNotes + * Needed so we can support new browsers that don't support deprecated calls (Firefox) as well as old browsers that + * don't support new calls. + * + * @method _compatibilitySetUp + * @static + * @protected + * @since 0.4.2 + */ + s._compatibilitySetUp = function() { + s._panningModel = "equalpower"; + //assume that if one new call is supported, they all are + if (s.context.createGain) { return; } + + // simple name change, functionality the same + s.context.createGain = s.context.createGainNode; + + // source node, add to prototype + var audioNode = s.context.createBufferSource(); + audioNode.__proto__.start = audioNode.__proto__.noteGrainOn; // note that noteGrainOn requires all 3 parameters + audioNode.__proto__.stop = audioNode.__proto__.noteOff; + + // panningModel + s._panningModel = 0; + }; + + +// Public Methods + p.toString = function () { + return "[WebAudioPlugin]"; + }; + + +// Private Methods + /** + * Set up needed properties on supported classes WebAudioSoundInstance and WebAudioLoader. + * @method _addPropsToClasses + * @static + * @protected + * @since 0.6.0 + */ + p._addPropsToClasses = function() { + var c = this._soundInstanceClass; + c.context = this.context; + c.destinationNode = this.gainNode; + c._panningModel = this._panningModel; + + this._loaderClass.context = this.context; + }; + + + /** + * Set the gain value for master audio. Should not be called externally. + * @method _updateVolume + * @protected + */ + p._updateVolume = function () { + var newVolume = createjs.Sound._masterMute ? 0 : this._volume; + if (newVolume != this.gainNode.gain.value) { + this.gainNode.gain.value = newVolume; + } + }; + + createjs.WebAudioPlugin = createjs.promote(WebAudioPlugin, "AbstractPlugin"); }()); //############################################################################## // HTMLAudioTagPool.js //############################################################################## -this.createjs = this.createjs || {}; - -(function () { - "use strict"; - - /** - * HTMLAudioTagPool is an object pool for HTMLAudio tag instances. - * @class HTMLAudioTagPool - * @param {String} src The source of the channel. - * @protected - */ - function HTMLAudioTagPool() { - throw "HTMLAudioTagPool cannot be instantiated"; - } - - var s = HTMLAudioTagPool; - -// Static Properties - /** - * A hash lookup of each base audio tag, indexed by the audio source. - * @property _tags - * @type {{}} - * @static - * @protected - */ - s._tags = {}; - - /** - * An object pool for html audio tags - * @property _tagPool - * @type {TagPool} - * @static - * @protected - */ - s._tagPool = new TagPool(); - - /** - * A hash lookup of if a base audio tag is available, indexed by the audio source - * @property _tagsUsed - * @type {{}} - * @protected - * @static - */ - s._tagUsed = {}; - -// Static Methods - /** - * Get an audio tag with the given source. - * @method get - * @param {String} src The source file used by the audio tag. - * @static - */ - s.get = function (src) { - var t = s._tags[src]; - if (t == null) { - // create new base tag - t = s._tags[src] = s._tagPool.get(); - t.src = src; - } else { - // get base or pool - if (s._tagUsed[src]) { - t = s._tagPool.get(); - t.src = src; - } else { - s._tagUsed[src] = true; - } - } - return t; - }; - - /** - * Return an audio tag to the pool. - * @method set - * @param {String} src The source file used by the audio tag. - * @param {HTMLElement} tag Audio tag to set. - * @static - */ - s.set = function (src, tag) { - // check if this is base, if yes set boolean if not return to pool - if(tag == s._tags[src]) { - s._tagUsed[src] = false; - } else { - s._tagPool.set(tag); - } - }; - - /** - * Delete stored tag reference and return them to pool. Note that if the tag reference does not exist, this will fail. - * @method remove - * @param {String} src The source for the tag - * @return {Boolean} If the TagPool was deleted. - * @static - */ - s.remove = function (src) { - var tag = s._tags[src]; - if (tag == null) {return false;} - s._tagPool.set(tag); - delete(s._tags[src]); - delete(s._tagUsed[src]); - return true; - }; - - /** - * Gets the duration of the src audio in milliseconds - * @method getDuration - * @param {String} src The source file used by the audio tag. - * @return {Number} Duration of src in milliseconds - * @static - */ - s.getDuration= function (src) { - var t = s._tags[src]; - if (t == null) {return 0;} - return t.duration * 1000; - }; - - createjs.HTMLAudioTagPool = HTMLAudioTagPool; - - -// ************************************************************************************************************ - /** - * The TagPool is an object pool for HTMLAudio tag instances. - * #class TagPool - * @param {String} src The source of the channel. - * @protected - */ - function TagPool(src) { - -// Public Properties - /** - * A list of all available tags in the pool. - * #property tags - * @type {Array} - * @protected - */ - this._tags = []; - }; - - var p = TagPool.prototype; - p.constructor = TagPool; - - -// Public Methods - /** - * Get an HTMLAudioElement for immediate playback. This takes it out of the pool. - * #method get - * @return {HTMLAudioElement} An HTML audio tag. - */ - p.get = function () { - var tag; - if (this._tags.length == 0) { - tag = this._createTag(); - } else { - tag = this._tags.pop(); - } - if (tag.parentNode == null) {document.body.appendChild(tag);} - return tag; - }; - - /** - * Put an HTMLAudioElement back in the pool for use. - * #method set - * @param {HTMLAudioElement} tag HTML audio tag - */ - p.set = function (tag) { - // OJR this first step seems unnecessary - var index = createjs.indexOf(this._tags, tag); - if (index == -1) { - this._tags.src = null; - this._tags.push(tag); - } - }; - - p.toString = function () { - return "[TagPool]"; - }; - - -// Private Methods - /** - * Create an HTML audio tag. - * #method _createTag - * @param {String} src The source file to set for the audio tag. - * @return {HTMLElement} Returns an HTML audio tag. - * @protected - */ - p._createTag = function () { - var tag = document.createElement("audio"); - tag.autoplay = false; - tag.preload = "none"; - //LM: Firefox fails when this the preload="none" for other tags, but it needs to be "none" to ensure PreloadJS works. - return tag; - }; - +this.createjs = this.createjs || {}; + +(function () { + "use strict"; + + /** + * HTMLAudioTagPool is an object pool for HTMLAudio tag instances. + * @class HTMLAudioTagPool + * @param {String} src The source of the channel. + * @protected + */ + function HTMLAudioTagPool() { + throw "HTMLAudioTagPool cannot be instantiated"; + } + + var s = HTMLAudioTagPool; + +// Static Properties + /** + * A hash lookup of each base audio tag, indexed by the audio source. + * @property _tags + * @type {{}} + * @static + * @protected + */ + s._tags = {}; + + /** + * An object pool for html audio tags + * @property _tagPool + * @type {TagPool} + * @static + * @protected + */ + s._tagPool = new TagPool(); + + /** + * A hash lookup of if a base audio tag is available, indexed by the audio source + * @property _tagsUsed + * @type {{}} + * @protected + * @static + */ + s._tagUsed = {}; + +// Static Methods + /** + * Get an audio tag with the given source. + * @method get + * @param {String} src The source file used by the audio tag. + * @static + */ + s.get = function (src) { + var t = s._tags[src]; + if (t == null) { + // create new base tag + t = s._tags[src] = s._tagPool.get(); + t.src = src; + } else { + // get base or pool + if (s._tagUsed[src]) { + t = s._tagPool.get(); + t.src = src; + } else { + s._tagUsed[src] = true; + } + } + return t; + }; + + /** + * Return an audio tag to the pool. + * @method set + * @param {String} src The source file used by the audio tag. + * @param {HTMLElement} tag Audio tag to set. + * @static + */ + s.set = function (src, tag) { + // check if this is base, if yes set boolean if not return to pool + if(tag == s._tags[src]) { + s._tagUsed[src] = false; + } else { + s._tagPool.set(tag); + } + }; + + /** + * Delete stored tag reference and return them to pool. Note that if the tag reference does not exist, this will fail. + * @method remove + * @param {String} src The source for the tag + * @return {Boolean} If the TagPool was deleted. + * @static + */ + s.remove = function (src) { + var tag = s._tags[src]; + if (tag == null) {return false;} + s._tagPool.set(tag); + delete(s._tags[src]); + delete(s._tagUsed[src]); + return true; + }; + + /** + * Gets the duration of the src audio in milliseconds + * @method getDuration + * @param {String} src The source file used by the audio tag. + * @return {Number} Duration of src in milliseconds + * @static + */ + s.getDuration= function (src) { + var t = s._tags[src]; + if (t == null) {return 0;} + return t.duration * 1000; + }; + + createjs.HTMLAudioTagPool = HTMLAudioTagPool; + + +// ************************************************************************************************************ + /** + * The TagPool is an object pool for HTMLAudio tag instances. + * #class TagPool + * @param {String} src The source of the channel. + * @protected + */ + function TagPool(src) { + +// Public Properties + /** + * A list of all available tags in the pool. + * #property tags + * @type {Array} + * @protected + */ + this._tags = []; + }; + + var p = TagPool.prototype; + p.constructor = TagPool; + + +// Public Methods + /** + * Get an HTMLAudioElement for immediate playback. This takes it out of the pool. + * #method get + * @return {HTMLAudioElement} An HTML audio tag. + */ + p.get = function () { + var tag; + if (this._tags.length == 0) { + tag = this._createTag(); + } else { + tag = this._tags.pop(); + } + if (tag.parentNode == null) {document.body.appendChild(tag);} + return tag; + }; + + /** + * Put an HTMLAudioElement back in the pool for use. + * #method set + * @param {HTMLAudioElement} tag HTML audio tag + */ + p.set = function (tag) { + // OJR this first step seems unnecessary + var index = createjs.indexOf(this._tags, tag); + if (index == -1) { + this._tags.src = null; + this._tags.push(tag); + } + }; + + p.toString = function () { + return "[TagPool]"; + }; + + +// Private Methods + /** + * Create an HTML audio tag. + * #method _createTag + * @param {String} src The source file to set for the audio tag. + * @return {HTMLElement} Returns an HTML audio tag. + * @protected + */ + p._createTag = function () { + var tag = document.createElement("audio"); + tag.autoplay = false; + tag.preload = "none"; + //LM: Firefox fails when this the preload="none" for other tags, but it needs to be "none" to ensure PreloadJS works. + return tag; + }; + }()); //############################################################################## // HTMLAudioSoundInstance.js //############################################################################## -this.createjs = this.createjs || {}; - -(function () { - "use strict"; - - /** - * HTMLAudioSoundInstance extends the base api of {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} and is used by - * {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}. - * - * @param {String} src The path to and file name of the sound. - * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. - * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. - * @param {Object} playbackResource Any resource needed by plugin to support audio playback. - * @class HTMLAudioSoundInstance - * @extends AbstractSoundInstance - * @constructor - */ - function HTMLAudioSoundInstance(src, startTime, duration, playbackResource) { - this.AbstractSoundInstance_constructor(src, startTime, duration, playbackResource); - - -// Private Properties - this._audioSpriteStopTime = null; - this._delayTimeoutId = null; - - // Proxies, make removing listeners easier. - this._endedHandler = createjs.proxy(this._handleSoundComplete, this); - this._readyHandler = createjs.proxy(this._handleTagReady, this); - this._stalledHandler = createjs.proxy(this._playFailed, this); - this._audioSpriteEndHandler = createjs.proxy(this._handleAudioSpriteLoop, this); - this._loopHandler = createjs.proxy(this._handleSoundComplete, this); - - if (duration) { - this._audioSpriteStopTime = (startTime + duration) * 0.001; - } else { - this._duration = createjs.HTMLAudioTagPool.getDuration(this.src); - } - } - var p = createjs.extend(HTMLAudioSoundInstance, createjs.AbstractSoundInstance); - - // TODO: deprecated - // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. - - -// Public Methods - /** - * Called by {{#crossLink "Sound"}}{{/crossLink}} when plugin does not handle master volume. - * undoc'd because it is not meant to be used outside of Sound - * #method setMasterVolume - * @param value - */ - p.setMasterVolume = function (value) { - this._updateVolume(); - }; - - /** - * Called by {{#crossLink "Sound"}}{{/crossLink}} when plugin does not handle master mute. - * undoc'd because it is not meant to be used outside of Sound - * #method setMasterMute - * @param value - */ - p.setMasterMute = function (isMuted) { - this._updateVolume(); - }; - - p.toString = function () { - return "[HTMLAudioSoundInstance]"; - }; - -//Private Methods - p._removeLooping = function() { - if(this._playbackResource == null) {return;} - this._playbackResource.loop = false; - this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); - }; - - p._addLooping = function() { - if(this._playbackResource == null || this._audioSpriteStopTime) {return;} - this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); - this._playbackResource.loop = true; - }; - - p._handleCleanUp = function () { - var tag = this._playbackResource; - if (tag != null) { - tag.pause(); - tag.loop = false; - tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED, this._endedHandler, false); - tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_READY, this._readyHandler, false); - tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_STALLED, this._stalledHandler, false); - tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); - tag.removeEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE, this._audioSpriteEndHandler, false); - - try { - tag.currentTime = this._startTime; - } catch (e) { - } // Reset Position - createjs.HTMLAudioTagPool.set(this.src, tag); - this._playbackResource = null; - } - }; - - p._beginPlaying = function (playProps) { - this._playbackResource = createjs.HTMLAudioTagPool.get(this.src); - return this.AbstractSoundInstance__beginPlaying(playProps); - }; - - p._handleSoundReady = function (event) { - if (this._playbackResource.readyState !== 4) { - var tag = this._playbackResource; - tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_READY, this._readyHandler, false); - tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_STALLED, this._stalledHandler, false); - tag.preload = "auto"; // This is necessary for Firefox, as it won't ever "load" until this is set. - tag.load(); - return; - } - - this._updateVolume(); - this._playbackResource.currentTime = (this._startTime + this._position) * 0.001; - if (this._audioSpriteStopTime) { - this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE, this._audioSpriteEndHandler, false); - } else { - this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED, this._endedHandler, false); - if(this._loop != 0) { - this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); - this._playbackResource.loop = true; - } - } - - this._playbackResource.play(); - }; - - /** - * Used to handle when a tag is not ready for immediate playback when it is returned from the HTMLAudioTagPool. - * @method _handleTagReady - * @param event - * @protected - */ - p._handleTagReady = function (event) { - this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_READY, this._readyHandler, false); - this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_STALLED, this._stalledHandler, false); - - this._handleSoundReady(); - }; - - p._pause = function () { - this._playbackResource.pause(); - }; - - p._resume = function () { - this._playbackResource.play(); - }; - - p._updateVolume = function () { - if (this._playbackResource != null) { - var newVolume = (this._muted || createjs.Sound._masterMute) ? 0 : this._volume * createjs.Sound._masterVolume; - if (newVolume != this._playbackResource.volume) {this._playbackResource.volume = newVolume;} - } - }; - - p._calculateCurrentPosition = function() { - return (this._playbackResource.currentTime * 1000) - this._startTime; - }; - - p._updatePosition = function() { - this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); - this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._handleSetPositionSeek, false); - try { - this._playbackResource.currentTime = (this._position + this._startTime) * 0.001; - } catch (error) { // Out of range - this._handleSetPositionSeek(null); - } - }; - - /** - * Used to enable setting position, as we need to wait for that seek to be done before we add back our loop handling seek listener - * @method _handleSetPositionSeek - * @param event - * @protected - */ - p._handleSetPositionSeek = function(event) { - if (this._playbackResource == null) { return; } - this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._handleSetPositionSeek, false); - this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); - }; - - /** - * Timer used to loop audio sprites. - * NOTE because of the inaccuracies in the timeupdate event (15 - 250ms) and in setting the tag to the desired timed - * (up to 300ms), it is strongly recommended not to loop audio sprites with HTML Audio if smooth looping is desired - * - * @method _handleAudioSpriteLoop - * @param event - * @private - */ - p._handleAudioSpriteLoop = function (event) { - if(this._playbackResource.currentTime <= this._audioSpriteStopTime) {return;} - this._playbackResource.pause(); - if(this._loop == 0) { - this._handleSoundComplete(null); - } else { - this._position = 0; - this._loop--; - this._playbackResource.currentTime = this._startTime * 0.001; - if(!this._paused) {this._playbackResource.play();} - this._sendEvent("loop"); - } - }; - - // NOTE with this approach audio will loop as reliably as the browser allows - // but we could end up sending the loop event after next loop playback begins - p._handleLoop = function (event) { - if(this._loop == 0) { - this._playbackResource.loop = false; - this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); - } - }; - - p._updateStartTime = function () { - this._audioSpriteStopTime = (this._startTime + this._duration) * 0.001; - - if(this.playState == createjs.Sound.PLAY_SUCCEEDED) { - this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED, this._endedHandler, false); - this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE, this._audioSpriteEndHandler, false); - } - }; - - p._updateDuration = function () { - this._audioSpriteStopTime = (this._startTime + this._duration) * 0.001; - - if(this.playState == createjs.Sound.PLAY_SUCCEEDED) { - this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED, this._endedHandler, false); - this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE, this._audioSpriteEndHandler, false); - } - }; - - /* This should never change - p._setDurationFromSource = function () { - this._duration = createjs.HTMLAudioTagPool.getDuration(this.src); - }; - */ - - createjs.HTMLAudioSoundInstance = createjs.promote(HTMLAudioSoundInstance, "AbstractSoundInstance"); +this.createjs = this.createjs || {}; + +(function () { + "use strict"; + + /** + * HTMLAudioSoundInstance extends the base api of {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} and is used by + * {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}. + * + * @param {String} src The path to and file name of the sound. + * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. + * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. + * @param {Object} playbackResource Any resource needed by plugin to support audio playback. + * @class HTMLAudioSoundInstance + * @extends AbstractSoundInstance + * @constructor + */ + function HTMLAudioSoundInstance(src, startTime, duration, playbackResource) { + this.AbstractSoundInstance_constructor(src, startTime, duration, playbackResource); + + +// Private Properties + this._audioSpriteStopTime = null; + this._delayTimeoutId = null; + + // Proxies, make removing listeners easier. + this._endedHandler = createjs.proxy(this._handleSoundComplete, this); + this._readyHandler = createjs.proxy(this._handleTagReady, this); + this._stalledHandler = createjs.proxy(this._playFailed, this); + this._audioSpriteEndHandler = createjs.proxy(this._handleAudioSpriteLoop, this); + this._loopHandler = createjs.proxy(this._handleSoundComplete, this); + + if (duration) { + this._audioSpriteStopTime = (startTime + duration) * 0.001; + } else { + this._duration = createjs.HTMLAudioTagPool.getDuration(this.src); + } + } + var p = createjs.extend(HTMLAudioSoundInstance, createjs.AbstractSoundInstance); + + // TODO: deprecated + // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. + + +// Public Methods + /** + * Called by {{#crossLink "Sound"}}{{/crossLink}} when plugin does not handle master volume. + * undoc'd because it is not meant to be used outside of Sound + * #method setMasterVolume + * @param value + */ + p.setMasterVolume = function (value) { + this._updateVolume(); + }; + + /** + * Called by {{#crossLink "Sound"}}{{/crossLink}} when plugin does not handle master mute. + * undoc'd because it is not meant to be used outside of Sound + * #method setMasterMute + * @param value + */ + p.setMasterMute = function (isMuted) { + this._updateVolume(); + }; + + p.toString = function () { + return "[HTMLAudioSoundInstance]"; + }; + +//Private Methods + p._removeLooping = function() { + if(this._playbackResource == null) {return;} + this._playbackResource.loop = false; + this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); + }; + + p._addLooping = function() { + if(this._playbackResource == null || this._audioSpriteStopTime) {return;} + this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); + this._playbackResource.loop = true; + }; + + p._handleCleanUp = function () { + var tag = this._playbackResource; + if (tag != null) { + tag.pause(); + tag.loop = false; + tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED, this._endedHandler, false); + tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_READY, this._readyHandler, false); + tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_STALLED, this._stalledHandler, false); + tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); + tag.removeEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE, this._audioSpriteEndHandler, false); + + try { + tag.currentTime = this._startTime; + } catch (e) { + } // Reset Position + createjs.HTMLAudioTagPool.set(this.src, tag); + this._playbackResource = null; + } + }; + + p._beginPlaying = function (playProps) { + this._playbackResource = createjs.HTMLAudioTagPool.get(this.src); + return this.AbstractSoundInstance__beginPlaying(playProps); + }; + + p._handleSoundReady = function (event) { + if (this._playbackResource.readyState !== 4) { + var tag = this._playbackResource; + tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_READY, this._readyHandler, false); + tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_STALLED, this._stalledHandler, false); + tag.preload = "auto"; // This is necessary for Firefox, as it won't ever "load" until this is set. + tag.load(); + return; + } + + this._updateVolume(); + this._playbackResource.currentTime = (this._startTime + this._position) * 0.001; + if (this._audioSpriteStopTime) { + this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE, this._audioSpriteEndHandler, false); + } else { + this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED, this._endedHandler, false); + if(this._loop != 0) { + this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); + this._playbackResource.loop = true; + } + } + + this._playbackResource.play(); + }; + + /** + * Used to handle when a tag is not ready for immediate playback when it is returned from the HTMLAudioTagPool. + * @method _handleTagReady + * @param event + * @protected + */ + p._handleTagReady = function (event) { + this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_READY, this._readyHandler, false); + this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_STALLED, this._stalledHandler, false); + + this._handleSoundReady(); + }; + + p._pause = function () { + this._playbackResource.pause(); + }; + + p._resume = function () { + this._playbackResource.play(); + }; + + p._updateVolume = function () { + if (this._playbackResource != null) { + var newVolume = (this._muted || createjs.Sound._masterMute) ? 0 : this._volume * createjs.Sound._masterVolume; + if (newVolume != this._playbackResource.volume) {this._playbackResource.volume = newVolume;} + } + }; + + p._calculateCurrentPosition = function() { + return (this._playbackResource.currentTime * 1000) - this._startTime; + }; + + p._updatePosition = function() { + this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); + this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._handleSetPositionSeek, false); + try { + this._playbackResource.currentTime = (this._position + this._startTime) * 0.001; + } catch (error) { // Out of range + this._handleSetPositionSeek(null); + } + }; + + /** + * Used to enable setting position, as we need to wait for that seek to be done before we add back our loop handling seek listener + * @method _handleSetPositionSeek + * @param event + * @protected + */ + p._handleSetPositionSeek = function(event) { + if (this._playbackResource == null) { return; } + this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._handleSetPositionSeek, false); + this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); + }; + + /** + * Timer used to loop audio sprites. + * NOTE because of the inaccuracies in the timeupdate event (15 - 250ms) and in setting the tag to the desired timed + * (up to 300ms), it is strongly recommended not to loop audio sprites with HTML Audio if smooth looping is desired + * + * @method _handleAudioSpriteLoop + * @param event + * @private + */ + p._handleAudioSpriteLoop = function (event) { + if(this._playbackResource.currentTime <= this._audioSpriteStopTime) {return;} + this._playbackResource.pause(); + if(this._loop == 0) { + this._handleSoundComplete(null); + } else { + this._position = 0; + this._loop--; + this._playbackResource.currentTime = this._startTime * 0.001; + if(!this._paused) {this._playbackResource.play();} + this._sendEvent("loop"); + } + }; + + // NOTE with this approach audio will loop as reliably as the browser allows + // but we could end up sending the loop event after next loop playback begins + p._handleLoop = function (event) { + if(this._loop == 0) { + this._playbackResource.loop = false; + this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._loopHandler, false); + } + }; + + p._updateStartTime = function () { + this._audioSpriteStopTime = (this._startTime + this._duration) * 0.001; + + if(this.playState == createjs.Sound.PLAY_SUCCEEDED) { + this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED, this._endedHandler, false); + this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE, this._audioSpriteEndHandler, false); + } + }; + + p._updateDuration = function () { + this._audioSpriteStopTime = (this._startTime + this._duration) * 0.001; + + if(this.playState == createjs.Sound.PLAY_SUCCEEDED) { + this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED, this._endedHandler, false); + this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE, this._audioSpriteEndHandler, false); + } + }; + + /* This should never change + p._setDurationFromSource = function () { + this._duration = createjs.HTMLAudioTagPool.getDuration(this.src); + }; + */ + + createjs.HTMLAudioSoundInstance = createjs.promote(HTMLAudioSoundInstance, "AbstractSoundInstance"); }()); //############################################################################## // HTMLAudioPlugin.js //############################################################################## -this.createjs = this.createjs || {}; - -(function () { - - "use strict"; - - /** - * Play sounds using HTML <audio> tags in the browser. This plugin is the second priority plugin installed - * by default, after the {{#crossLink "WebAudioPlugin"}}{{/crossLink}}. For older browsers that do not support html - * audio, include and install the {{#crossLink "FlashAudioPlugin"}}{{/crossLink}}. - * - *

          Known Browser and OS issues for HTML Audio

          - * All browsers
          - * Testing has shown in all browsers there is a limit to how many audio tag instances you are allowed. If you exceed - * this limit, you can expect to see unpredictable results. Please use {{#crossLink "Sound.MAX_INSTANCES"}}{{/crossLink}} as - * a guide to how many total audio tags you can safely use in all browsers. This issue is primarily limited to IE9. - * - * IE html limitations
          - *
          • There is a delay in applying volume changes to tags that occurs once playback is started. So if you have - * muted all sounds, they will all play during this delay until the mute applies internally. This happens regardless of - * when or how you apply the volume change, as the tag seems to need to play to apply it.
          • - *
          • MP3 encoding will not always work for audio tags if it's not default. We've found default encoding with - * 64kbps works.
          • - *
          • Occasionally very short samples will get cut off.
          • - *
          • There is a limit to how many audio tags you can load or play at once, which appears to be determined by - * hardware and browser settings. See {{#crossLink "HTMLAudioPlugin.MAX_INSTANCES"}}{{/crossLink}} for a safe estimate. - * Note that audio sprites can be used as a solution to this issue.
          - * - * Safari limitations
          - *
          • Safari requires Quicktime to be installed for audio playback.
          - * - * iOS 6 limitations
          - *
          • can only have one <audio> tag
          • - *
          • can not preload or autoplay the audio
          • - *
          • can not cache the audio
          • - *
          • can not play the audio except inside a user initiated event.
          • - *
          • Note it is recommended to use {{#crossLink "WebAudioPlugin"}}{{/crossLink}} for iOS (6+)
          • - *
          • audio sprites can be used to mitigate some of these issues and are strongly recommended on iOS
          • - *
          - * - * Android Native Browser limitations
          - *
          • We have no control over audio volume. Only the user can set volume on their device.
          • - *
          • We can only play audio inside a user event (touch/click). This currently means you cannot loop sound or use a delay.
          - * Android Chrome 26.0.1410.58 specific limitations
          - *
          • Can only play 1 sound at a time.
          • - *
          • Sound is not cached.
          • - *
          • Sound can only be loaded in a user initiated touch/click event.
          • - *
          • There is a delay before a sound is played, presumably while the src is loaded.
          • - *
          - * - * See {{#crossLink "Sound"}}{{/crossLink}} for general notes on known issues. - * - * @class HTMLAudioPlugin - * @extends AbstractPlugin - * @constructor - */ - function HTMLAudioPlugin() { - this.AbstractPlugin_constructor(); - - - // Public Properties - /** - * This is no longer needed as we are now using object pooling for tags. - * - * NOTE this property only exists as a limitation of HTML audio. - * @property defaultNumChannels - * @type {Number} - * @default 2 - * @since 0.4.0 - * @deprecated - */ - this.defaultNumChannels = 2; - - this._capabilities = s._capabilities; - - this._loaderClass = createjs.SoundLoader; - this._soundInstanceClass = createjs.HTMLAudioSoundInstance; - } - - var p = createjs.extend(HTMLAudioPlugin, createjs.AbstractPlugin); - var s = HTMLAudioPlugin; - - // TODO: deprecated - // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. - - -// Static Properties - /** - * The maximum number of instances that can be loaded or played. This is a browser limitation, primarily limited to IE9. - * The actual number varies from browser to browser (and is largely hardware dependant), but this is a safe estimate. - * Audio sprites work around this limitation. - * @property MAX_INSTANCES - * @type {Number} - * @default 30 - * @static - */ - s.MAX_INSTANCES = 30; - - /** - * Event constant for the "canPlayThrough" event for cleaner code. - * @property _AUDIO_READY - * @type {String} - * @default canplaythrough - * @static - * @protected - */ - s._AUDIO_READY = "canplaythrough"; - - /** - * Event constant for the "ended" event for cleaner code. - * @property _AUDIO_ENDED - * @type {String} - * @default ended - * @static - * @protected - */ - s._AUDIO_ENDED = "ended"; - - /** - * Event constant for the "seeked" event for cleaner code. We utilize this event for maintaining loop events. - * @property _AUDIO_SEEKED - * @type {String} - * @default seeked - * @static - * @protected - */ - s._AUDIO_SEEKED = "seeked"; - - /** - * Event constant for the "stalled" event for cleaner code. - * @property _AUDIO_STALLED - * @type {String} - * @default stalled - * @static - * @protected - */ - s._AUDIO_STALLED = "stalled"; - - /** - * Event constant for the "timeupdate" event for cleaner code. Utilized for looping audio sprites. - * This event callsback ever 15 to 250ms and can be dropped by the browser for performance. - * @property _TIME_UPDATE - * @type {String} - * @default timeupdate - * @static - * @protected - */ - s._TIME_UPDATE = "timeupdate"; - - /** - * The capabilities of the plugin. This is generated via the {{#crossLink "HTMLAudioPlugin/_generateCapabilities"}}{{/crossLink}} - * method. Please see the Sound {{#crossLink "Sound/getCapabilities"}}{{/crossLink}} method for an overview of all - * of the available properties. - * @property _capabilities - * @type {Object} - * @protected - * @static - */ - s._capabilities = null; - - -// Static Methods - /** - * Determine if the plugin can be used in the current browser/OS. Note that HTML audio is available in most modern - * browsers, but is disabled in iOS because of its limitations. - * @method isSupported - * @return {Boolean} If the plugin can be initialized. - * @static - */ - s.isSupported = function () { - s._generateCapabilities(); - return (s._capabilities != null); - }; - - /** - * Determine the capabilities of the plugin. Used internally. Please see the Sound API {{#crossLink "Sound/getCapabilities"}}{{/crossLink}} - * method for an overview of plugin capabilities. - * @method _generateCapabilities - * @static - * @protected - */ - s._generateCapabilities = function () { - if (s._capabilities != null) {return;} - var t = document.createElement("audio"); - if (t.canPlayType == null) {return null;} - - s._capabilities = { - panning:false, - volume:true, - tracks:-1 - }; - - // determine which extensions our browser supports for this plugin by iterating through Sound.SUPPORTED_EXTENSIONS - var supportedExtensions = createjs.Sound.SUPPORTED_EXTENSIONS; - var extensionMap = createjs.Sound.EXTENSION_MAP; - for (var i = 0, l = supportedExtensions.length; i < l; i++) { - var ext = supportedExtensions[i]; - var playType = extensionMap[ext] || ext; - s._capabilities[ext] = (t.canPlayType("audio/" + ext) != "no" && t.canPlayType("audio/" + ext) != "") || (t.canPlayType("audio/" + playType) != "no" && t.canPlayType("audio/" + playType) != ""); - } // OJR another way to do this might be canPlayType:"m4a", codex: mp4 - }; - - -// public methods - p.register = function (loadItem) { - var tag = createjs.HTMLAudioTagPool.get(loadItem.src); - var loader = this.AbstractPlugin_register(loadItem); - loader.setTag(tag); - - return loader; - }; - - p.removeSound = function (src) { - this.AbstractPlugin_removeSound(src); - createjs.HTMLAudioTagPool.remove(src); - }; - - p.create = function (src, startTime, duration) { - var si = this.AbstractPlugin_create(src, startTime, duration); - si.setPlaybackResource(null); - return si; - }; - - p.toString = function () { - return "[HTMLAudioPlugin]"; - }; - - // plugin does not support these - p.setVolume = p.getVolume = p.setMute = null; - - - createjs.HTMLAudioPlugin = createjs.promote(HTMLAudioPlugin, "AbstractPlugin"); +this.createjs = this.createjs || {}; + +(function () { + + "use strict"; + + /** + * Play sounds using HTML <audio> tags in the browser. This plugin is the second priority plugin installed + * by default, after the {{#crossLink "WebAudioPlugin"}}{{/crossLink}}. For older browsers that do not support html + * audio, include and install the {{#crossLink "FlashAudioPlugin"}}{{/crossLink}}. + * + *

          Known Browser and OS issues for HTML Audio

          + * All browsers
          + * Testing has shown in all browsers there is a limit to how many audio tag instances you are allowed. If you exceed + * this limit, you can expect to see unpredictable results. Please use {{#crossLink "Sound.MAX_INSTANCES"}}{{/crossLink}} as + * a guide to how many total audio tags you can safely use in all browsers. This issue is primarily limited to IE9. + * + * IE html limitations
          + *
          • There is a delay in applying volume changes to tags that occurs once playback is started. So if you have + * muted all sounds, they will all play during this delay until the mute applies internally. This happens regardless of + * when or how you apply the volume change, as the tag seems to need to play to apply it.
          • + *
          • MP3 encoding will not always work for audio tags if it's not default. We've found default encoding with + * 64kbps works.
          • + *
          • Occasionally very short samples will get cut off.
          • + *
          • There is a limit to how many audio tags you can load or play at once, which appears to be determined by + * hardware and browser settings. See {{#crossLink "HTMLAudioPlugin.MAX_INSTANCES"}}{{/crossLink}} for a safe estimate. + * Note that audio sprites can be used as a solution to this issue.
          + * + * Safari limitations
          + *
          • Safari requires Quicktime to be installed for audio playback.
          + * + * iOS 6 limitations
          + *
          • can only have one <audio> tag
          • + *
          • can not preload or autoplay the audio
          • + *
          • can not cache the audio
          • + *
          • can not play the audio except inside a user initiated event.
          • + *
          • Note it is recommended to use {{#crossLink "WebAudioPlugin"}}{{/crossLink}} for iOS (6+)
          • + *
          • audio sprites can be used to mitigate some of these issues and are strongly recommended on iOS
          • + *
          + * + * Android Native Browser limitations
          + *
          • We have no control over audio volume. Only the user can set volume on their device.
          • + *
          • We can only play audio inside a user event (touch/click). This currently means you cannot loop sound or use a delay.
          + * Android Chrome 26.0.1410.58 specific limitations
          + *
          • Can only play 1 sound at a time.
          • + *
          • Sound is not cached.
          • + *
          • Sound can only be loaded in a user initiated touch/click event.
          • + *
          • There is a delay before a sound is played, presumably while the src is loaded.
          • + *
          + * + * See {{#crossLink "Sound"}}{{/crossLink}} for general notes on known issues. + * + * @class HTMLAudioPlugin + * @extends AbstractPlugin + * @constructor + */ + function HTMLAudioPlugin() { + this.AbstractPlugin_constructor(); + + + // Public Properties + /** + * This is no longer needed as we are now using object pooling for tags. + * + * NOTE this property only exists as a limitation of HTML audio. + * @property defaultNumChannels + * @type {Number} + * @default 2 + * @since 0.4.0 + * @deprecated + */ + this.defaultNumChannels = 2; + + this._capabilities = s._capabilities; + + this._loaderClass = createjs.SoundLoader; + this._soundInstanceClass = createjs.HTMLAudioSoundInstance; + } + + var p = createjs.extend(HTMLAudioPlugin, createjs.AbstractPlugin); + var s = HTMLAudioPlugin; + + // TODO: deprecated + // p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details. + + +// Static Properties + /** + * The maximum number of instances that can be loaded or played. This is a browser limitation, primarily limited to IE9. + * The actual number varies from browser to browser (and is largely hardware dependant), but this is a safe estimate. + * Audio sprites work around this limitation. + * @property MAX_INSTANCES + * @type {Number} + * @default 30 + * @static + */ + s.MAX_INSTANCES = 30; + + /** + * Event constant for the "canPlayThrough" event for cleaner code. + * @property _AUDIO_READY + * @type {String} + * @default canplaythrough + * @static + * @protected + */ + s._AUDIO_READY = "canplaythrough"; + + /** + * Event constant for the "ended" event for cleaner code. + * @property _AUDIO_ENDED + * @type {String} + * @default ended + * @static + * @protected + */ + s._AUDIO_ENDED = "ended"; + + /** + * Event constant for the "seeked" event for cleaner code. We utilize this event for maintaining loop events. + * @property _AUDIO_SEEKED + * @type {String} + * @default seeked + * @static + * @protected + */ + s._AUDIO_SEEKED = "seeked"; + + /** + * Event constant for the "stalled" event for cleaner code. + * @property _AUDIO_STALLED + * @type {String} + * @default stalled + * @static + * @protected + */ + s._AUDIO_STALLED = "stalled"; + + /** + * Event constant for the "timeupdate" event for cleaner code. Utilized for looping audio sprites. + * This event callsback ever 15 to 250ms and can be dropped by the browser for performance. + * @property _TIME_UPDATE + * @type {String} + * @default timeupdate + * @static + * @protected + */ + s._TIME_UPDATE = "timeupdate"; + + /** + * The capabilities of the plugin. This is generated via the {{#crossLink "HTMLAudioPlugin/_generateCapabilities"}}{{/crossLink}} + * method. Please see the Sound {{#crossLink "Sound/getCapabilities"}}{{/crossLink}} method for an overview of all + * of the available properties. + * @property _capabilities + * @type {Object} + * @protected + * @static + */ + s._capabilities = null; + + +// Static Methods + /** + * Determine if the plugin can be used in the current browser/OS. Note that HTML audio is available in most modern + * browsers, but is disabled in iOS because of its limitations. + * @method isSupported + * @return {Boolean} If the plugin can be initialized. + * @static + */ + s.isSupported = function () { + s._generateCapabilities(); + return (s._capabilities != null); + }; + + /** + * Determine the capabilities of the plugin. Used internally. Please see the Sound API {{#crossLink "Sound/getCapabilities"}}{{/crossLink}} + * method for an overview of plugin capabilities. + * @method _generateCapabilities + * @static + * @protected + */ + s._generateCapabilities = function () { + if (s._capabilities != null) {return;} + var t = document.createElement("audio"); + if (t.canPlayType == null) {return null;} + + s._capabilities = { + panning:false, + volume:true, + tracks:-1 + }; + + // determine which extensions our browser supports for this plugin by iterating through Sound.SUPPORTED_EXTENSIONS + var supportedExtensions = createjs.Sound.SUPPORTED_EXTENSIONS; + var extensionMap = createjs.Sound.EXTENSION_MAP; + for (var i = 0, l = supportedExtensions.length; i < l; i++) { + var ext = supportedExtensions[i]; + var playType = extensionMap[ext] || ext; + s._capabilities[ext] = (t.canPlayType("audio/" + ext) != "no" && t.canPlayType("audio/" + ext) != "") || (t.canPlayType("audio/" + playType) != "no" && t.canPlayType("audio/" + playType) != ""); + } // OJR another way to do this might be canPlayType:"m4a", codex: mp4 + }; + + +// public methods + p.register = function (loadItem) { + var tag = createjs.HTMLAudioTagPool.get(loadItem.src); + var loader = this.AbstractPlugin_register(loadItem); + loader.setTag(tag); + + return loader; + }; + + p.removeSound = function (src) { + this.AbstractPlugin_removeSound(src); + createjs.HTMLAudioTagPool.remove(src); + }; + + p.create = function (src, startTime, duration) { + var si = this.AbstractPlugin_create(src, startTime, duration); + si.setPlaybackResource(null); + return si; + }; + + p.toString = function () { + return "[HTMLAudioPlugin]"; + }; + + // plugin does not support these + p.setVolume = p.getVolume = p.setMute = null; + + + createjs.HTMLAudioPlugin = createjs.promote(HTMLAudioPlugin, "AbstractPlugin"); }()); \ No newline at end of file diff --git a/lib/soundjs-NEXT.min.js b/lib/soundjs-NEXT.min.js index c1459dfa..e6fc6b04 100644 --- a/lib/soundjs-NEXT.min.js +++ b/lib/soundjs-NEXT.min.js @@ -2,7 +2,7 @@ * @license SoundJS * Visit http://createjs.com/ for documentation, updates and examples. * -* Copyright (c) 2011-2013 gskinner.com, inc. +* Copyright (c) 2011-2015 gskinner.com, inc. * * Distributed under the terms of the MIT license. * http://www.opensource.org/licenses/mit-license.html @@ -14,5 +14,5 @@ * SoundJS FlashAudioPlugin also includes swfobject (http://code.google.com/p/swfobject/) */ -this.createjs=this.createjs||{},function(){var a=createjs.SoundJS=createjs.SoundJS||{};a.version="NEXT",a.buildDate="Tue, 19 May 2015 17:26:59 GMT"}(),this.createjs=this.createjs||{},createjs.extend=function(a,b){"use strict";function c(){this.constructor=a}return c.prototype=b.prototype,a.prototype=new c},this.createjs=this.createjs||{},createjs.promote=function(a,b){"use strict";var c=a.prototype,d=Object.getPrototypeOf&&Object.getPrototypeOf(c)||c.__proto__;if(d){c[(b+="_")+"constructor"]=d.constructor;for(var e in d)c.hasOwnProperty(e)&&"function"==typeof d[e]&&(c[b+e]=d[e])}return a},this.createjs=this.createjs||{},createjs.indexOf=function(a,b){"use strict";for(var c=0,d=a.length;d>c;c++)if(b===a[c])return c;return-1},this.createjs=this.createjs||{},function(){"use strict";createjs.proxy=function(a,b){var c=Array.prototype.slice.call(arguments,2);return function(){return a.apply(b,Array.prototype.slice.call(arguments,0).concat(c))}}}(),this.createjs=this.createjs||{},function(){"use strict";function BrowserDetect(){throw"BrowserDetect cannot be instantiated"}var a=BrowserDetect.agent=window.navigator.userAgent;BrowserDetect.isWindowPhone=a.indexOf("IEMobile")>-1||a.indexOf("Windows Phone")>-1,BrowserDetect.isFirefox=a.indexOf("Firefox")>-1,BrowserDetect.isOpera=null!=window.opera,BrowserDetect.isChrome=a.indexOf("Chrome")>-1,BrowserDetect.isIOS=(a.indexOf("iPod")>-1||a.indexOf("iPhone")>-1||a.indexOf("iPad")>-1)&&!BrowserDetect.isWindowPhone,BrowserDetect.isAndroid=a.indexOf("Android")>-1&&!BrowserDetect.isWindowPhone,BrowserDetect.isBlackberry=a.indexOf("Blackberry")>-1,createjs.BrowserDetect=BrowserDetect}(),this.createjs=this.createjs||{},function(){"use strict";function EventDispatcher(){this._listeners=null,this._captureListeners=null}var a=EventDispatcher.prototype;EventDispatcher.initialize=function(b){b.addEventListener=a.addEventListener,b.on=a.on,b.removeEventListener=b.off=a.removeEventListener,b.removeAllEventListeners=a.removeAllEventListeners,b.hasEventListener=a.hasEventListener,b.dispatchEvent=a.dispatchEvent,b._dispatchEvent=a._dispatchEvent,b.willTrigger=a.willTrigger},a.addEventListener=function(a,b,c){var d;d=c?this._captureListeners=this._captureListeners||{}:this._listeners=this._listeners||{};var e=d[a];return e&&this.removeEventListener(a,b,c),e=d[a],e?e.push(b):d[a]=[b],b},a.on=function(a,b,c,d,e,f){return b.handleEvent&&(c=c||b,b=b.handleEvent),c=c||this,this.addEventListener(a,function(a){b.call(c,a,e),d&&a.remove()},f)},a.removeEventListener=function(a,b,c){var d=c?this._captureListeners:this._listeners;if(d){var e=d[a];if(e)for(var f=0,g=e.length;g>f;f++)if(e[f]==b){1==g?delete d[a]:e.splice(f,1);break}}},a.off=a.removeEventListener,a.removeAllEventListeners=function(a){a?(this._listeners&&delete this._listeners[a],this._captureListeners&&delete this._captureListeners[a]):this._listeners=this._captureListeners=null},a.dispatchEvent=function(a){if("string"==typeof a){var b=this._listeners;if(!b||!b[a])return!1;a=new createjs.Event(a)}else a.target&&a.clone&&(a=a.clone());try{a.target=this}catch(c){}if(a.bubbles&&this.parent){for(var d=this,e=[d];d.parent;)e.push(d=d.parent);var f,g=e.length;for(f=g-1;f>=0&&!a.propagationStopped;f--)e[f]._dispatchEvent(a,1+(0==f));for(f=1;g>f&&!a.propagationStopped;f++)e[f]._dispatchEvent(a,3)}else this._dispatchEvent(a,2);return a.defaultPrevented},a.hasEventListener=function(a){var b=this._listeners,c=this._captureListeners;return!!(b&&b[a]||c&&c[a])},a.willTrigger=function(a){for(var b=this;b;){if(b.hasEventListener(a))return!0;b=b.parent}return!1},a.toString=function(){return"[EventDispatcher]"},a._dispatchEvent=function(a,b){var c,d=1==b?this._captureListeners:this._listeners;if(a&&d){var e=d[a.type];if(!e||!(c=e.length))return;try{a.currentTarget=this}catch(f){}try{a.eventPhase=b}catch(f){}a.removed=!1,e=e.slice();for(var g=0;c>g&&!a.immediatePropagationStopped;g++){var h=e[g];h.handleEvent?h.handleEvent(a):h(a),a.removed&&(this.off(a.type,h,1==b),a.removed=!1)}}},createjs.EventDispatcher=EventDispatcher}(),this.createjs=this.createjs||{},function(){"use strict";function Event(a,b,c){this.type=a,this.target=null,this.currentTarget=null,this.eventPhase=0,this.bubbles=!!b,this.cancelable=!!c,this.timeStamp=(new Date).getTime(),this.defaultPrevented=!1,this.propagationStopped=!1,this.immediatePropagationStopped=!1,this.removed=!1}var a=Event.prototype;a.preventDefault=function(){this.defaultPrevented=this.cancelable&&!0},a.stopPropagation=function(){this.propagationStopped=!0},a.stopImmediatePropagation=function(){this.immediatePropagationStopped=this.propagationStopped=!0},a.remove=function(){this.removed=!0},a.clone=function(){return new Event(this.type,this.bubbles,this.cancelable)},a.set=function(a){for(var b in a)this[b]=a[b];return this},a.toString=function(){return"[Event (type="+this.type+")]"},createjs.Event=Event}(),this.createjs=this.createjs||{},function(){"use strict";function ErrorEvent(a,b,c){this.Event_constructor("error"),this.title=a,this.message=b,this.data=c}var a=createjs.extend(ErrorEvent,createjs.Event);a.clone=function(){return new createjs.ErrorEvent(this.title,this.message,this.data)},createjs.ErrorEvent=createjs.promote(ErrorEvent,"Event")}(),this.createjs=this.createjs||{},function(){"use strict";function ProgressEvent(a,b){this.Event_constructor("progress"),this.loaded=a,this.total=null==b?1:b,this.progress=0==b?0:this.loaded/this.total}var a=createjs.extend(ProgressEvent,createjs.Event);a.clone=function(){return new createjs.ProgressEvent(this.loaded,this.total)},createjs.ProgressEvent=createjs.promote(ProgressEvent,"Event")}(window),this.createjs=this.createjs||{},function(){"use strict";function LoadItem(){this.src=null,this.type=null,this.id=null,this.maintainOrder=!1,this.callback=null,this.data=null,this.method=createjs.LoadItem.GET,this.values=null,this.headers=null,this.withCredentials=!1,this.mimeType=null,this.crossOrigin=null,this.loadTimeout=b.LOAD_TIMEOUT_DEFAULT}var a=LoadItem.prototype={},b=LoadItem;b.LOAD_TIMEOUT_DEFAULT=8e3,b.create=function(a){if("string"==typeof a){var c=new LoadItem;return c.src=a,c}if(a instanceof b)return a;if(a instanceof Object&&a.src)return null==a.loadTimeout&&(a.loadTimeout=b.LOAD_TIMEOUT_DEFAULT),a;throw new Error("Type not recognized.")},a.set=function(a){for(var b in a)this[b]=a[b];return this},createjs.LoadItem=b}(),function(){var a={};a.ABSOLUTE_PATT=/^(?:\w+:)?\/{2}/i,a.RELATIVE_PATT=/^[./]*?\//i,a.EXTENSION_PATT=/\/?[^/]+\.(\w{1,5})$/i,a.parseURI=function(b){var c={absolute:!1,relative:!1};if(null==b)return c;var d=b.indexOf("?");d>-1&&(b=b.substr(0,d));var e;return a.ABSOLUTE_PATT.test(b)?c.absolute=!0:a.RELATIVE_PATT.test(b)&&(c.relative=!0),(e=b.match(a.EXTENSION_PATT))&&(c.extension=e[1].toLowerCase()),c},a.formatQueryString=function(a,b){if(null==a)throw new Error("You must specify data.");var c=[];for(var d in a)c.push(d+"="+escape(a[d]));return b&&(c=c.concat(b)),c.join("&")},a.buildPath=function(a,b){if(null==b)return a;var c=[],d=a.indexOf("?");if(-1!=d){var e=a.slice(d+1);c=c.concat(e.split("&"))}return-1!=d?a.slice(0,d)+"?"+this._formatQueryString(b,c):a+"?"+this._formatQueryString(b,c)},a.isCrossDomain=function(a){var b=document.createElement("a");b.href=a.src;var c=document.createElement("a");c.href=location.href;var d=""!=b.hostname&&(b.port!=c.port||b.protocol!=c.protocol||b.hostname!=c.hostname);return d},a.isLocal=function(a){var b=document.createElement("a");return b.href=a.src,""==b.hostname&&"file:"==b.protocol},a.isBinary=function(a){switch(a){case createjs.AbstractLoader.IMAGE:case createjs.AbstractLoader.BINARY:return!0;default:return!1}},a.isImageTag=function(a){return a instanceof HTMLImageElement},a.isAudioTag=function(a){return window.HTMLAudioElement?a instanceof HTMLAudioElement:!1},a.isVideoTag=function(a){return window.HTMLVideoElement?a instanceof HTMLVideoElement:!1},a.isText=function(a){switch(a){case createjs.AbstractLoader.TEXT:case createjs.AbstractLoader.JSON:case createjs.AbstractLoader.MANIFEST:case createjs.AbstractLoader.XML:case createjs.AbstractLoader.CSS:case createjs.AbstractLoader.SVG:case createjs.AbstractLoader.JAVASCRIPT:case createjs.AbstractLoader.SPRITESHEET:return!0;default:return!1}},a.getTypeByExtension=function(a){if(null==a)return createjs.AbstractLoader.TEXT;switch(a.toLowerCase()){case"jpeg":case"jpg":case"gif":case"png":case"webp":case"bmp":return createjs.AbstractLoader.IMAGE;case"ogg":case"mp3":case"webm":return createjs.AbstractLoader.SOUND;case"mp4":case"webm":case"ts":return createjs.AbstractLoader.VIDEO;case"json":return createjs.AbstractLoader.JSON;case"xml":return createjs.AbstractLoader.XML;case"css":return createjs.AbstractLoader.CSS;case"js":return createjs.AbstractLoader.JAVASCRIPT;case"svg":return createjs.AbstractLoader.SVG;default:return createjs.AbstractLoader.TEXT}},createjs.RequestUtils=a}(),this.createjs=this.createjs||{},function(){"use strict";function AbstractLoader(a,b,c){this.EventDispatcher_constructor(),this.loaded=!1,this.canceled=!1,this.progress=0,this.type=c,this.resultFormatter=null,this._item=a?createjs.LoadItem.create(a):null,this._preferXHR=b,this._result=null,this._rawResult=null,this._loadedItems=null,this._tagSrcAttribute=null,this._tag=null}var a=createjs.extend(AbstractLoader,createjs.EventDispatcher),b=AbstractLoader;b.POST="POST",b.GET="GET",b.BINARY="binary",b.CSS="css",b.IMAGE="image",b.JAVASCRIPT="javascript",b.JSON="json",b.JSONP="jsonp",b.MANIFEST="manifest",b.SOUND="sound",b.VIDEO="video",b.SPRITESHEET="spritesheet",b.SVG="svg",b.TEXT="text",b.XML="xml",a.getItem=function(){return this._item},a.getResult=function(a){return a?this._rawResult:this._result},a.getTag=function(){return this._tag},a.setTag=function(a){this._tag=a},a.load=function(){this._createRequest(),this._request.on("complete",this,this),this._request.on("progress",this,this),this._request.on("loadStart",this,this),this._request.on("abort",this,this),this._request.on("timeout",this,this),this._request.on("error",this,this);var a=new createjs.Event("initialize");a.loader=this._request,this.dispatchEvent(a),this._request.load()},a.cancel=function(){this.canceled=!0,this.destroy()},a.destroy=function(){this._request&&(this._request.removeAllEventListeners(),this._request.destroy()),this._request=null,this._item=null,this._rawResult=null,this._result=null,this._loadItems=null,this.removeAllEventListeners()},a.getLoadedItems=function(){return this._loadedItems},a._createRequest=function(){this._request=this._preferXHR?new createjs.XHRRequest(this._item):new createjs.TagRequest(this._item,this._tag||this._createTag(),this._tagSrcAttribute)},a._createTag=function(){return null},a._sendLoadStart=function(){this._isCanceled()||this.dispatchEvent("loadstart")},a._sendProgress=function(a){if(!this._isCanceled()){var b=null;"number"==typeof a?(this.progress=a,b=new createjs.ProgressEvent(this.progress)):(b=a,this.progress=a.loaded/a.total,b.progress=this.progress,(isNaN(this.progress)||1/0==this.progress)&&(this.progress=0)),this.hasEventListener("progress")&&this.dispatchEvent(b)}},a._sendComplete=function(){if(!this._isCanceled()){this.loaded=!0;var a=new createjs.Event("complete");a.rawResult=this._rawResult,null!=this._result&&(a.result=this._result),this.dispatchEvent(a)}},a._sendError=function(a){!this._isCanceled()&&this.hasEventListener("error")&&(null==a&&(a=new createjs.ErrorEvent("PRELOAD_ERROR_EMPTY")),this.dispatchEvent(a))},a._isCanceled=function(){return null==window.createjs||this.canceled?!0:!1},a.resultFormatter=null,a.handleEvent=function(a){switch(a.type){case"complete":this._rawResult=a.target._response;var b=this.resultFormatter&&this.resultFormatter(this),c=this;b instanceof Function?b(function(a){c._result=a,c._sendComplete()}):(this._result=b||this._rawResult,this._sendComplete());break;case"progress":this._sendProgress(a);break;case"error":this._sendError(a);break;case"loadstart":this._sendLoadStart();break;case"abort":case"timeout":this._isCanceled()||this.dispatchEvent(a.type)}},a.buildPath=function(a,b){return createjs.RequestUtils.buildPath(a,b)},a.toString=function(){return"[PreloadJS AbstractLoader]"},createjs.AbstractLoader=createjs.promote(AbstractLoader,"EventDispatcher")}(),this.createjs=this.createjs||{},function(){"use strict";function AbstractMediaLoader(a,b,c){this.AbstractLoader_constructor(a,b,c),this.resultFormatter=this._formatResult,this._tagSrcAttribute="src"}var a=createjs.extend(AbstractMediaLoader,createjs.AbstractLoader);a.load=function(){this._tag||(this._tag=this._createTag(this._item.src)),this._tag.preload="auto",this._tag.load(),this.AbstractLoader_load()},a._createTag=function(){},a._createRequest=function(){this._request=this._preferXHR?new createjs.XHRRequest(this._item):new createjs.MediaTagRequest(this._item,this._tag||this._createTag(),this._tagSrcAttribute)},a._formatResult=function(a){return this._tag.removeEventListener&&this._tag.removeEventListener("canplaythrough",this._loadedHandler),this._tag.onstalled=null,this._preferXHR&&(a.getTag().src=a.getResult(!0)),a.getTag()},createjs.AbstractMediaLoader=createjs.promote(AbstractMediaLoader,"AbstractLoader")}(),this.createjs=this.createjs||{},function(){"use strict";var AbstractRequest=function(a){this._item=a},a=createjs.extend(AbstractRequest,createjs.EventDispatcher);a.load=function(){},a.destroy=function(){},a.cancel=function(){},createjs.AbstractRequest=createjs.promote(AbstractRequest,"EventDispatcher")}(),this.createjs=this.createjs||{},function(){"use strict";function TagRequest(a,b,c){this.AbstractRequest_constructor(a),this._tag=b,this._tagSrcAttribute=c,this._loadedHandler=createjs.proxy(this._handleTagComplete,this),this._addedToDOM=!1,this._startTagVisibility=null}var a=createjs.extend(TagRequest,createjs.AbstractRequest);a.load=function(){this._tag.onload=createjs.proxy(this._handleTagComplete,this),this._tag.onreadystatechange=createjs.proxy(this._handleReadyStateChange,this),this._tag.onerror=createjs.proxy(this._handleError,this);var a=new createjs.Event("initialize");a.loader=this._tag,this.dispatchEvent(a),this._hideTag(),this._loadTimeout=setTimeout(createjs.proxy(this._handleTimeout,this),this._item.loadTimeout),this._tag[this._tagSrcAttribute]=this._item.src,null==this._tag.parentNode&&(window.document.body.appendChild(this._tag),this._addedToDOM=!0)},a.destroy=function(){this._clean(),this._tag=null,this.AbstractRequest_destroy()},a._handleReadyStateChange=function(){clearTimeout(this._loadTimeout);var a=this._tag;("loaded"==a.readyState||"complete"==a.readyState)&&this._handleTagComplete()},a._handleError=function(){this._clean(),this.dispatchEvent("error")},a._handleTagComplete=function(){this._rawResult=this._tag,this._result=this.resultFormatter&&this.resultFormatter(this)||this._rawResult,this._clean(),this._showTag(),this.dispatchEvent("complete")},a._handleTimeout=function(){this._clean(),this.dispatchEvent(new createjs.Event("timeout"))},a._clean=function(){this._tag.onload=null,this._tag.onreadystatechange=null,this._tag.onerror=null,this._addedToDOM&&null!=this._tag.parentNode&&this._tag.parentNode.removeChild(this._tag),clearTimeout(this._loadTimeout)},a._hideTag=function(){this._startTagVisibility=this._tag.style.visibility,this._tag.style.visibility="hidden"},a._showTag=function(){this._tag.style.visibility=this._startTagVisibility},a._handleStalled=function(){},createjs.TagRequest=createjs.promote(TagRequest,"AbstractRequest")}(),this.createjs=this.createjs||{},function(){"use strict";function MediaTagRequest(a,b,c){this.AbstractRequest_constructor(a),this._tag=b,this._tagSrcAttribute=c,this._loadedHandler=createjs.proxy(this._handleTagComplete,this)}var a=createjs.extend(MediaTagRequest,createjs.TagRequest);a.load=function(){var a=createjs.proxy(this._handleStalled,this);this._stalledCallback=a;var b=createjs.proxy(this._handleProgress,this);this._handleProgress=b,this._tag.addEventListener("stalled",a),this._tag.addEventListener("progress",b),this._tag.addEventListener&&this._tag.addEventListener("canplaythrough",this._loadedHandler,!1),this.TagRequest_load()},a._handleReadyStateChange=function(){clearTimeout(this._loadTimeout);var a=this._tag;("loaded"==a.readyState||"complete"==a.readyState)&&this._handleTagComplete()},a._handleStalled=function(){},a._handleProgress=function(a){if(a&&!(a.loaded>0&&0==a.total)){var b=new createjs.ProgressEvent(a.loaded,a.total);this.dispatchEvent(b)}},a._clean=function(){this._tag.removeEventListener&&this._tag.removeEventListener("canplaythrough",this._loadedHandler),this._tag.removeEventListener("stalled",this._stalledCallback),this._tag.removeEventListener("progress",this._progressCallback),this.TagRequest__clean()},createjs.MediaTagRequest=createjs.promote(MediaTagRequest,"TagRequest")}(),this.createjs=this.createjs||{},function(){"use strict";function XHRRequest(a){this.AbstractRequest_constructor(a),this._request=null,this._loadTimeout=null,this._xhrLevel=1,this._response=null,this._rawResponse=null,this._canceled=!1,this._handleLoadStartProxy=createjs.proxy(this._handleLoadStart,this),this._handleProgressProxy=createjs.proxy(this._handleProgress,this),this._handleAbortProxy=createjs.proxy(this._handleAbort,this),this._handleErrorProxy=createjs.proxy(this._handleError,this),this._handleTimeoutProxy=createjs.proxy(this._handleTimeout,this),this._handleLoadProxy=createjs.proxy(this._handleLoad,this),this._handleReadyStateChangeProxy=createjs.proxy(this._handleReadyStateChange,this),!this._createXHR(a)}var a=createjs.extend(XHRRequest,createjs.AbstractRequest);XHRRequest.ACTIVEX_VERSIONS=["Msxml2.XMLHTTP.6.0","Msxml2.XMLHTTP.5.0","Msxml2.XMLHTTP.4.0","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP"],a.getResult=function(a){return a&&this._rawResponse?this._rawResponse:this._response},a.cancel=function(){this.canceled=!0,this._clean(),this._request.abort()},a.load=function(){if(null==this._request)return void this._handleError();this._request.addEventListener("loadstart",this._handleLoadStartProxy,!1),this._request.addEventListener("progress",this._handleProgressProxy,!1),this._request.addEventListener("abort",this._handleAbortProxy,!1),this._request.addEventListener("error",this._handleErrorProxy,!1),this._request.addEventListener("timeout",this._handleTimeoutProxy,!1),this._request.addEventListener("load",this._handleLoadProxy,!1),this._request.addEventListener("readystatechange",this._handleReadyStateChangeProxy,!1),1==this._xhrLevel&&(this._loadTimeout=setTimeout(createjs.proxy(this._handleTimeout,this),this._item.loadTimeout));try{this._item.values&&this._item.method!=createjs.AbstractLoader.GET?this._item.method==createjs.AbstractLoader.POST&&this._request.send(createjs.RequestUtils.formatQueryString(this._item.values)):this._request.send()}catch(a){this.dispatchEvent(new createjs.ErrorEvent("XHR_SEND",null,a))}},a.setResponseType=function(a){this._request.responseType=a},a.getAllResponseHeaders=function(){return this._request.getAllResponseHeaders instanceof Function?this._request.getAllResponseHeaders():null},a.getResponseHeader=function(a){return this._request.getResponseHeader instanceof Function?this._request.getResponseHeader(a):null},a._handleProgress=function(a){if(a&&!(a.loaded>0&&0==a.total)){var b=new createjs.ProgressEvent(a.loaded,a.total);this.dispatchEvent(b)}},a._handleLoadStart=function(){clearTimeout(this._loadTimeout),this.dispatchEvent("loadstart")},a._handleAbort=function(a){this._clean(),this.dispatchEvent(new createjs.ErrorEvent("XHR_ABORTED",null,a))},a._handleError=function(a){this._clean(),this.dispatchEvent(new createjs.ErrorEvent(a.message))},a._handleReadyStateChange=function(){4==this._request.readyState&&this._handleLoad()},a._handleLoad=function(){if(!this.loaded){this.loaded=!0;var a=this._checkError();if(a)return void this._handleError(a);this._response=this._getResponse(),this._clean(),this.dispatchEvent(new createjs.Event("complete"))}},a._handleTimeout=function(a){this._clean(),this.dispatchEvent(new createjs.ErrorEvent("PRELOAD_TIMEOUT",null,a))},a._checkError=function(){var a=parseInt(this._request.status);switch(a){case 404:case 0:return new Error(a)}return null},a._getResponse=function(){if(null!=this._response)return this._response;if(null!=this._request.response)return this._request.response;try{if(null!=this._request.responseText)return this._request.responseText}catch(a){}try{if(null!=this._request.responseXML)return this._request.responseXML}catch(a){}return null},a._createXHR=function(a){var b=createjs.RequestUtils.isCrossDomain(a),c={},d=null;if(window.XMLHttpRequest)d=new XMLHttpRequest,b&&void 0===d.withCredentials&&window.XDomainRequest&&(d=new XDomainRequest);else{for(var e=0,f=s.ACTIVEX_VERSIONS.length;f>e;e++){{s.ACTIVEX_VERSIONS[e]}try{d=new ActiveXObject(axVersions);break}catch(g){}}if(null==d)return!1}null==a.mimeType&&createjs.RequestUtils.isText(a.type)&&(a.mimeType="text/plain; charset=utf-8"),a.mimeType&&d.overrideMimeType&&d.overrideMimeType(a.mimeType),this._xhrLevel="string"==typeof d.responseType?2:1;var h=null;if(h=a.method==createjs.AbstractLoader.GET?createjs.RequestUtils.buildPath(a.src,a.values):a.src,d.open(a.method||createjs.AbstractLoader.GET,h,!0),b&&d instanceof XMLHttpRequest&&1==this._xhrLevel&&(c.Origin=location.origin),a.values&&a.method==createjs.AbstractLoader.POST&&(c["Content-Type"]="application/x-www-form-urlencoded"),b||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest"),a.headers)for(var i in a.headers)c[i]=a.headers[i];for(i in c)d.setRequestHeader(i,c[i]);return d instanceof XMLHttpRequest&&void 0!==a.withCredentials&&(d.withCredentials=a.withCredentials),this._request=d,!0},a._clean=function(){clearTimeout(this._loadTimeout),this._request.removeEventListener("loadstart",this._handleLoadStartProxy),this._request.removeEventListener("progress",this._handleProgressProxy),this._request.removeEventListener("abort",this._handleAbortProxy),this._request.removeEventListener("error",this._handleErrorProxy),this._request.removeEventListener("timeout",this._handleTimeoutProxy),this._request.removeEventListener("load",this._handleLoadProxy),this._request.removeEventListener("readystatechange",this._handleReadyStateChangeProxy)},a.toString=function(){return"[PreloadJS XHRRequest]"},createjs.XHRRequest=createjs.promote(XHRRequest,"AbstractRequest")}(),this.createjs=this.createjs||{},function(){"use strict";function SoundLoader(a,b){this.AbstractMediaLoader_constructor(a,b,createjs.AbstractLoader.SOUND),createjs.RequestUtils.isAudioTag(a)?this._tag=a:createjs.RequestUtils.isAudioTag(a.src)?this._tag=a:createjs.RequestUtils.isAudioTag(a.tag)&&(this._tag=createjs.RequestUtils.isAudioTag(a)?a:a.src),null!=this._tag&&(this._preferXHR=!1)}var a=createjs.extend(SoundLoader,createjs.AbstractMediaLoader),b=SoundLoader;b.canLoadItem=function(a){return a.type==createjs.AbstractLoader.SOUND},a._createTag=function(a){var b=document.createElement("audio");return b.autoplay=!1,b.preload="none",b.src=a,b},createjs.SoundLoader=createjs.promote(SoundLoader,"AbstractMediaLoader")}(),this.createjs=this.createjs||{},function(){"use strict";var PlayPropsConfig=function(){this.interrupt=null,this.delay=null,this.offset=null,this.loop=null,this.volume=null,this.pan=null,this.startTime=null,this.duration=null},a=PlayPropsConfig.prototype={},b=PlayPropsConfig;b.create=function(a){if(a instanceof b||a instanceof Object){var c=new createjs.PlayPropsConfig;return c.set(a),c}throw new Error("Type not recognized.")},a.set=function(a){for(var b in a)this[b]=a[b];return this},a.toString=function(){return"[PlayPropsConfig]"},createjs.PlayPropsConfig=b}(),this.createjs=this.createjs||{},function(){"use strict";function Sound(){throw"Sound cannot be instantiated"}function a(a,b){this.init(a,b)}var b=Sound;b.INTERRUPT_ANY="any",b.INTERRUPT_EARLY="early",b.INTERRUPT_LATE="late",b.INTERRUPT_NONE="none",b.PLAY_INITED="playInited",b.PLAY_SUCCEEDED="playSucceeded",b.PLAY_INTERRUPTED="playInterrupted",b.PLAY_FINISHED="playFinished",b.PLAY_FAILED="playFailed",b.SUPPORTED_EXTENSIONS=["mp3","ogg","opus","mpeg","wav","m4a","mp4","aiff","wma","mid"],b.EXTENSION_MAP={m4a:"mp4"},b.FILE_PATTERN=/^(?:(\w+:)\/{2}(\w+(?:\.\w+)*\/?))?([/.]*?(?:[^?]+)?\/)?((?:[^/?]+)\.(\w+))(?:\?(\S+)?)?$/,b.defaultInterruptBehavior=b.INTERRUPT_NONE,b.alternateExtensions=[],b.activePlugin=null,b._masterVolume=1,Object.defineProperty(b,"volume",{get:function(){return this._masterVolume},set:function(a){if(null==Number(a))return!1;if(a=Math.max(0,Math.min(1,a)),b._masterVolume=a,!this.activePlugin||!this.activePlugin.setVolume||!this.activePlugin.setVolume(a))for(var c=this._instances,d=0,e=c.length;e>d;d++)c[d].setMasterVolume(a)}}),b._masterMute=!1,Object.defineProperty(b,"muted",{get:function(){return this._masterMute},set:function(a){if(null==a)return!1;if(this._masterMute=a,!this.activePlugin||!this.activePlugin.setMute||!this.activePlugin.setMute(a))for(var b=this._instances,c=0,d=b.length;d>c;c++)b[c].setMasterMute(a);return!0}}),Object.defineProperty(b,"capabilities",{get:function(){return null==b.activePlugin?null:b.activePlugin._capabilities},set:function(){return!1}}),b._pluginsRegistered=!1,b._lastID=0,b._instances=[],b._idHash={},b._preloadHash={},b._defaultPlayPropsHash={},b.addEventListener=null,b.removeEventListener=null,b.removeAllEventListeners=null,b.dispatchEvent=null,b.hasEventListener=null,b._listeners=null,createjs.EventDispatcher.initialize(b),b.getPreloadHandlers=function(){return{callback:createjs.proxy(b.initLoad,b),types:["sound"],extensions:b.SUPPORTED_EXTENSIONS}},b._handleLoadComplete=function(a){var c=a.target.getItem().src;if(b._preloadHash[c])for(var d=0,e=b._preloadHash[c].length;e>d;d++){var f=b._preloadHash[c][d];if(b._preloadHash[c][d]=!0,b.hasEventListener("fileload")){var a=new createjs.Event("fileload");a.src=f.src,a.id=f.id,a.data=f.data,a.sprite=f.sprite,b.dispatchEvent(a)}}},b._handleLoadError=function(a){var c=a.target.getItem().src;if(b._preloadHash[c])for(var d=0,e=b._preloadHash[c].length;e>d;d++){var f=b._preloadHash[c][d];if(b._preloadHash[c][d]=!1,b.hasEventListener("fileerror")){var a=new createjs.Event("fileerror");a.src=f.src,a.id=f.id,a.data=f.data,a.sprite=f.sprite,b.dispatchEvent(a)}}},b._registerPlugin=function(a){return a.isSupported()?(b.activePlugin=new a,!0):!1},b.registerPlugins=function(a){b._pluginsRegistered=!0;for(var c=0,d=a.length;d>c;c++)if(b._registerPlugin(a[c]))return!0;return!1},b.initializeDefaultPlugins=function(){return null!=b.activePlugin?!0:b._pluginsRegistered?!1:b.registerPlugins([createjs.WebAudioPlugin,createjs.HTMLAudioPlugin])?!0:!1},b.isReady=function(){return null!=b.activePlugin},b.getCapabilities=function(){return null==b.activePlugin?null:b.activePlugin._capabilities},b.getCapability=function(a){return null==b.activePlugin?null:b.activePlugin._capabilities[a]},b.initLoad=function(a){return b._registerSound(a)},b._registerSound=function(c){if(!b.initializeDefaultPlugins())return!1;var d;if(c.src instanceof Object?(d=b._parseSrc(c.src),d.src=c.path+d.src):d=b._parsePath(c.src),null==d)return!1;c.src=d.src,c.type="sound";var e=c.data,f=null;if(null!=e&&(isNaN(e.channels)?isNaN(e)||(f=parseInt(e)):f=parseInt(e.channels),e.audioSprite))for(var g,h=e.audioSprite.length;h--;)g=e.audioSprite[h],b._idHash[g.id]={src:c.src,startTime:parseInt(g.startTime),duration:parseInt(g.duration)},g.defaultPlayProps&&(b._defaultPlayPropsHash[g.id]=createjs.PlayPropsConfig.create(g.defaultPlayProps));null!=c.id&&(b._idHash[c.id]={src:c.src});var i=b.activePlugin.register(c);return a.create(c.src,f),null!=e&&isNaN(e)?c.data.channels=f||a.maxPerChannel():c.data=f||a.maxPerChannel(),i.type&&(c.type=i.type),c.defaultPlayProps&&(b._defaultPlayPropsHash[c.src]=createjs.PlayPropsConfig.create(c.defaultPlayProps)),i},b.registerSound=function(a,c,d,e,f){var g={src:a,id:c,data:d,defaultPlayProps:f};a instanceof Object&&a.src&&(e=c,g=a),g=createjs.LoadItem.create(g),g.path=e,null==e||g.src instanceof Object||(g.src=e+a);var h=b._registerSound(g);if(!h)return!1;if(b._preloadHash[g.src]||(b._preloadHash[g.src]=[]),b._preloadHash[g.src].push(g),1==b._preloadHash[g.src].length)h.on("complete",createjs.proxy(this._handleLoadComplete,this)),h.on("error",createjs.proxy(this._handleLoadError,this)),b.activePlugin.preload(h);else if(1==b._preloadHash[g.src][0])return!0;return g},b.registerSounds=function(a,b){var c=[];a.path&&(b?b+=a.path:b=a.path,a=a.manifest);for(var d=0,e=a.length;e>d;d++)c[d]=createjs.Sound.registerSound(a[d].src,a[d].id,a[d].data,b,a[d].defaultPlayProps);return c},b.removeSound=function(c,d){if(null==b.activePlugin)return!1;c instanceof Object&&c.src&&(c=c.src);var e;if(c instanceof Object?e=b._parseSrc(c):(c=b._getSrcById(c).src,e=b._parsePath(c)),null==e)return!1;c=e.src,null!=d&&(c=d+c);for(var f in b._idHash)b._idHash[f].src==c&&delete b._idHash[f];return a.removeSrc(c),delete b._preloadHash[c],b.activePlugin.removeSound(c),!0},b.removeSounds=function(a,b){var c=[];a.path&&(b?b+=a.path:b=a.path,a=a.manifest);for(var d=0,e=a.length;e>d;d++)c[d]=createjs.Sound.removeSound(a[d].src,b);return c},b.removeAllSounds=function(){b._idHash={},b._preloadHash={},a.removeAll(),b.activePlugin&&b.activePlugin.removeAllSounds()},b.loadComplete=function(a){if(!b.isReady())return!1;var c=b._parsePath(a);return a=c?b._getSrcById(c.src).src:b._getSrcById(a).src,void 0==b._preloadHash[a]?!1:1==b._preloadHash[a][0]},b._parsePath=function(a){"string"!=typeof a&&(a=a.toString());var c=a.match(b.FILE_PATTERN);if(null==c)return!1;for(var d=c[4],e=c[5],f=b.capabilities,g=0;!f[e];)if(e=b.alternateExtensions[g++],g>b.alternateExtensions.length)return null;a=a.replace("."+c[5],"."+e);var h={name:d,src:a,extension:e};return h},b._parseSrc=function(a){var c={name:void 0,src:void 0,extension:void 0},d=b.capabilities;for(var e in a)if(a.hasOwnProperty(e)&&d[e]){c.src=a[e],c.extension=e;break}if(!c.src)return!1;var f=c.src.lastIndexOf("/");return c.name=-1!=f?c.src.slice(f+1):c.src,c},b.play=function(a,c,d,e,f,g,h,i,j){var k;k=createjs.PlayPropsConfig.create(c instanceof Object||c instanceof createjs.PlayPropsConfig?c:{interrupt:c,delay:d,offset:e,loop:f,volume:g,pan:h,startTime:i,duration:j});var l=b.createInstance(a,k.startTime,k.duration),m=b._playInstance(l,k);return m||l._playFailed(),l},b.createInstance=function(c,d,e){if(!b.initializeDefaultPlugins())return new createjs.DefaultSoundInstance(c,d,e);var f=b._defaultPlayPropsHash[c];c=b._getSrcById(c);var g=b._parsePath(c.src),h=null;return null!=g&&null!=g.src?(a.create(g.src),null==d&&(d=c.startTime),h=b.activePlugin.create(g.src,d,e||c.duration),f=f||b._defaultPlayPropsHash[g.src],f&&h.applyPlayProps(f)):h=new createjs.DefaultSoundInstance(c,d,e),h.uniqueId=b._lastID++,h},b.stop=function(){for(var a=this._instances,b=a.length;b--;)a[b].stop()},b.setVolume=function(a){if(null==Number(a))return!1;if(a=Math.max(0,Math.min(1,a)),b._masterVolume=a,!this.activePlugin||!this.activePlugin.setVolume||!this.activePlugin.setVolume(a))for(var c=this._instances,d=0,e=c.length;e>d;d++)c[d].setMasterVolume(a)},b.getVolume=function(){return this._masterVolume},b.setMute=function(a){if(null==a)return!1;if(this._masterMute=a,!this.activePlugin||!this.activePlugin.setMute||!this.activePlugin.setMute(a))for(var b=this._instances,c=0,d=b.length;d>c;c++)b[c].setMasterMute(a);return!0},b.getMute=function(){return this._masterMute},b.setDefaultPlayProps=function(a,c){a=b._getSrcById(a),b._defaultPlayPropsHash[b._parsePath(a.src).src]=createjs.PlayPropsConfig.create(c)},b.getDefaultPlayProps=function(a){return a=b._getSrcById(a),b._defaultPlayPropsHash[b._parsePath(a.src).src]},b._playInstance=function(a,c){var d=b._defaultPlayPropsHash[a.src]||{};if(null==c.interrupt&&(c.interrupt=d.interrupt||b.defaultInterruptBehavior),null==c.delay&&(c.delay=d.delay||0),null==c.offset&&(c.offset=a.getPosition()),null==c.loop&&(c.loop=a.loop),null==c.volume&&(c.volume=a.volume),null==c.pan&&(c.pan=a.pan),0==c.delay){var e=b._beginPlaying(a,c); +this.createjs=this.createjs||{},function(){var a=createjs.SoundJS=createjs.SoundJS||{};a.version="NEXT",a.buildDate="Wed, 27 May 2015 18:12:38 GMT"}(),this.createjs=this.createjs||{},createjs.extend=function(a,b){"use strict";function c(){this.constructor=a}return c.prototype=b.prototype,a.prototype=new c},this.createjs=this.createjs||{},createjs.promote=function(a,b){"use strict";var c=a.prototype,d=Object.getPrototypeOf&&Object.getPrototypeOf(c)||c.__proto__;if(d){c[(b+="_")+"constructor"]=d.constructor;for(var e in d)c.hasOwnProperty(e)&&"function"==typeof d[e]&&(c[b+e]=d[e])}return a},this.createjs=this.createjs||{},createjs.indexOf=function(a,b){"use strict";for(var c=0,d=a.length;d>c;c++)if(b===a[c])return c;return-1},this.createjs=this.createjs||{},function(){"use strict";createjs.proxy=function(a,b){var c=Array.prototype.slice.call(arguments,2);return function(){return a.apply(b,Array.prototype.slice.call(arguments,0).concat(c))}}}(),this.createjs=this.createjs||{},function(){"use strict";function BrowserDetect(){throw"BrowserDetect cannot be instantiated"}var a=BrowserDetect.agent=window.navigator.userAgent;BrowserDetect.isWindowPhone=a.indexOf("IEMobile")>-1||a.indexOf("Windows Phone")>-1,BrowserDetect.isFirefox=a.indexOf("Firefox")>-1,BrowserDetect.isOpera=null!=window.opera,BrowserDetect.isChrome=a.indexOf("Chrome")>-1,BrowserDetect.isIOS=(a.indexOf("iPod")>-1||a.indexOf("iPhone")>-1||a.indexOf("iPad")>-1)&&!BrowserDetect.isWindowPhone,BrowserDetect.isAndroid=a.indexOf("Android")>-1&&!BrowserDetect.isWindowPhone,BrowserDetect.isBlackberry=a.indexOf("Blackberry")>-1,createjs.BrowserDetect=BrowserDetect}(),this.createjs=this.createjs||{},function(){"use strict";function EventDispatcher(){this._listeners=null,this._captureListeners=null}var a=EventDispatcher.prototype;EventDispatcher.initialize=function(b){b.addEventListener=a.addEventListener,b.on=a.on,b.removeEventListener=b.off=a.removeEventListener,b.removeAllEventListeners=a.removeAllEventListeners,b.hasEventListener=a.hasEventListener,b.dispatchEvent=a.dispatchEvent,b._dispatchEvent=a._dispatchEvent,b.willTrigger=a.willTrigger},a.addEventListener=function(a,b,c){var d;d=c?this._captureListeners=this._captureListeners||{}:this._listeners=this._listeners||{};var e=d[a];return e&&this.removeEventListener(a,b,c),e=d[a],e?e.push(b):d[a]=[b],b},a.on=function(a,b,c,d,e,f){return b.handleEvent&&(c=c||b,b=b.handleEvent),c=c||this,this.addEventListener(a,function(a){b.call(c,a,e),d&&a.remove()},f)},a.removeEventListener=function(a,b,c){var d=c?this._captureListeners:this._listeners;if(d){var e=d[a];if(e)for(var f=0,g=e.length;g>f;f++)if(e[f]==b){1==g?delete d[a]:e.splice(f,1);break}}},a.off=a.removeEventListener,a.removeAllEventListeners=function(a){a?(this._listeners&&delete this._listeners[a],this._captureListeners&&delete this._captureListeners[a]):this._listeners=this._captureListeners=null},a.dispatchEvent=function(a){if("string"==typeof a){var b=this._listeners;if(!b||!b[a])return!1;a=new createjs.Event(a)}else a.target&&a.clone&&(a=a.clone());try{a.target=this}catch(c){}if(a.bubbles&&this.parent){for(var d=this,e=[d];d.parent;)e.push(d=d.parent);var f,g=e.length;for(f=g-1;f>=0&&!a.propagationStopped;f--)e[f]._dispatchEvent(a,1+(0==f));for(f=1;g>f&&!a.propagationStopped;f++)e[f]._dispatchEvent(a,3)}else this._dispatchEvent(a,2);return a.defaultPrevented},a.hasEventListener=function(a){var b=this._listeners,c=this._captureListeners;return!!(b&&b[a]||c&&c[a])},a.willTrigger=function(a){for(var b=this;b;){if(b.hasEventListener(a))return!0;b=b.parent}return!1},a.toString=function(){return"[EventDispatcher]"},a._dispatchEvent=function(a,b){var c,d=1==b?this._captureListeners:this._listeners;if(a&&d){var e=d[a.type];if(!e||!(c=e.length))return;try{a.currentTarget=this}catch(f){}try{a.eventPhase=b}catch(f){}a.removed=!1,e=e.slice();for(var g=0;c>g&&!a.immediatePropagationStopped;g++){var h=e[g];h.handleEvent?h.handleEvent(a):h(a),a.removed&&(this.off(a.type,h,1==b),a.removed=!1)}}},createjs.EventDispatcher=EventDispatcher}(),this.createjs=this.createjs||{},function(){"use strict";function Event(a,b,c){this.type=a,this.target=null,this.currentTarget=null,this.eventPhase=0,this.bubbles=!!b,this.cancelable=!!c,this.timeStamp=(new Date).getTime(),this.defaultPrevented=!1,this.propagationStopped=!1,this.immediatePropagationStopped=!1,this.removed=!1}var a=Event.prototype;a.preventDefault=function(){this.defaultPrevented=this.cancelable&&!0},a.stopPropagation=function(){this.propagationStopped=!0},a.stopImmediatePropagation=function(){this.immediatePropagationStopped=this.propagationStopped=!0},a.remove=function(){this.removed=!0},a.clone=function(){return new Event(this.type,this.bubbles,this.cancelable)},a.set=function(a){for(var b in a)this[b]=a[b];return this},a.toString=function(){return"[Event (type="+this.type+")]"},createjs.Event=Event}(),this.createjs=this.createjs||{},function(){"use strict";function ErrorEvent(a,b,c){this.Event_constructor("error"),this.title=a,this.message=b,this.data=c}var a=createjs.extend(ErrorEvent,createjs.Event);a.clone=function(){return new createjs.ErrorEvent(this.title,this.message,this.data)},createjs.ErrorEvent=createjs.promote(ErrorEvent,"Event")}(),this.createjs=this.createjs||{},function(){"use strict";function ProgressEvent(a,b){this.Event_constructor("progress"),this.loaded=a,this.total=null==b?1:b,this.progress=0==b?0:this.loaded/this.total}var a=createjs.extend(ProgressEvent,createjs.Event);a.clone=function(){return new createjs.ProgressEvent(this.loaded,this.total)},createjs.ProgressEvent=createjs.promote(ProgressEvent,"Event")}(window),this.createjs=this.createjs||{},function(){"use strict";function LoadItem(){this.src=null,this.type=null,this.id=null,this.maintainOrder=!1,this.callback=null,this.data=null,this.method=createjs.LoadItem.GET,this.values=null,this.headers=null,this.withCredentials=!1,this.mimeType=null,this.crossOrigin=null,this.loadTimeout=b.LOAD_TIMEOUT_DEFAULT}var a=LoadItem.prototype={},b=LoadItem;b.LOAD_TIMEOUT_DEFAULT=8e3,b.create=function(a){if("string"==typeof a){var c=new LoadItem;return c.src=a,c}if(a instanceof b)return a;if(a instanceof Object&&a.src)return null==a.loadTimeout&&(a.loadTimeout=b.LOAD_TIMEOUT_DEFAULT),a;throw new Error("Type not recognized.")},a.set=function(a){for(var b in a)this[b]=a[b];return this},createjs.LoadItem=b}(),function(){var a={};a.ABSOLUTE_PATT=/^(?:\w+:)?\/{2}/i,a.RELATIVE_PATT=/^[./]*?\//i,a.EXTENSION_PATT=/\/?[^/]+\.(\w{1,5})$/i,a.parseURI=function(b){var c={absolute:!1,relative:!1};if(null==b)return c;var d=b.indexOf("?");d>-1&&(b=b.substr(0,d));var e;return a.ABSOLUTE_PATT.test(b)?c.absolute=!0:a.RELATIVE_PATT.test(b)&&(c.relative=!0),(e=b.match(a.EXTENSION_PATT))&&(c.extension=e[1].toLowerCase()),c},a.formatQueryString=function(a,b){if(null==a)throw new Error("You must specify data.");var c=[];for(var d in a)c.push(d+"="+escape(a[d]));return b&&(c=c.concat(b)),c.join("&")},a.buildPath=function(a,b){if(null==b)return a;var c=[],d=a.indexOf("?");if(-1!=d){var e=a.slice(d+1);c=c.concat(e.split("&"))}return-1!=d?a.slice(0,d)+"?"+this._formatQueryString(b,c):a+"?"+this._formatQueryString(b,c)},a.isCrossDomain=function(a){var b=document.createElement("a");b.href=a.src;var c=document.createElement("a");c.href=location.href;var d=""!=b.hostname&&(b.port!=c.port||b.protocol!=c.protocol||b.hostname!=c.hostname);return d},a.isLocal=function(a){var b=document.createElement("a");return b.href=a.src,""==b.hostname&&"file:"==b.protocol},a.isBinary=function(a){switch(a){case createjs.AbstractLoader.IMAGE:case createjs.AbstractLoader.BINARY:return!0;default:return!1}},a.isImageTag=function(a){return a instanceof HTMLImageElement},a.isAudioTag=function(a){return window.HTMLAudioElement?a instanceof HTMLAudioElement:!1},a.isVideoTag=function(a){return window.HTMLVideoElement?a instanceof HTMLVideoElement:!1},a.isText=function(a){switch(a){case createjs.AbstractLoader.TEXT:case createjs.AbstractLoader.JSON:case createjs.AbstractLoader.MANIFEST:case createjs.AbstractLoader.XML:case createjs.AbstractLoader.CSS:case createjs.AbstractLoader.SVG:case createjs.AbstractLoader.JAVASCRIPT:case createjs.AbstractLoader.SPRITESHEET:return!0;default:return!1}},a.getTypeByExtension=function(a){if(null==a)return createjs.AbstractLoader.TEXT;switch(a.toLowerCase()){case"jpeg":case"jpg":case"gif":case"png":case"webp":case"bmp":return createjs.AbstractLoader.IMAGE;case"ogg":case"mp3":case"webm":return createjs.AbstractLoader.SOUND;case"mp4":case"webm":case"ts":return createjs.AbstractLoader.VIDEO;case"json":return createjs.AbstractLoader.JSON;case"xml":return createjs.AbstractLoader.XML;case"css":return createjs.AbstractLoader.CSS;case"js":return createjs.AbstractLoader.JAVASCRIPT;case"svg":return createjs.AbstractLoader.SVG;default:return createjs.AbstractLoader.TEXT}},createjs.RequestUtils=a}(),this.createjs=this.createjs||{},function(){"use strict";function AbstractLoader(a,b,c){this.EventDispatcher_constructor(),this.loaded=!1,this.canceled=!1,this.progress=0,this.type=c,this.resultFormatter=null,this._item=a?createjs.LoadItem.create(a):null,this._preferXHR=b,this._result=null,this._rawResult=null,this._loadedItems=null,this._tagSrcAttribute=null,this._tag=null}var a=createjs.extend(AbstractLoader,createjs.EventDispatcher),b=AbstractLoader;b.POST="POST",b.GET="GET",b.BINARY="binary",b.CSS="css",b.IMAGE="image",b.JAVASCRIPT="javascript",b.JSON="json",b.JSONP="jsonp",b.MANIFEST="manifest",b.SOUND="sound",b.VIDEO="video",b.SPRITESHEET="spritesheet",b.SVG="svg",b.TEXT="text",b.XML="xml",a.getItem=function(){return this._item},a.getResult=function(a){return a?this._rawResult:this._result},a.getTag=function(){return this._tag},a.setTag=function(a){this._tag=a},a.load=function(){this._createRequest(),this._request.on("complete",this,this),this._request.on("progress",this,this),this._request.on("loadStart",this,this),this._request.on("abort",this,this),this._request.on("timeout",this,this),this._request.on("error",this,this);var a=new createjs.Event("initialize");a.loader=this._request,this.dispatchEvent(a),this._request.load()},a.cancel=function(){this.canceled=!0,this.destroy()},a.destroy=function(){this._request&&(this._request.removeAllEventListeners(),this._request.destroy()),this._request=null,this._item=null,this._rawResult=null,this._result=null,this._loadItems=null,this.removeAllEventListeners()},a.getLoadedItems=function(){return this._loadedItems},a._createRequest=function(){this._request=this._preferXHR?new createjs.XHRRequest(this._item):new createjs.TagRequest(this._item,this._tag||this._createTag(),this._tagSrcAttribute)},a._createTag=function(){return null},a._sendLoadStart=function(){this._isCanceled()||this.dispatchEvent("loadstart")},a._sendProgress=function(a){if(!this._isCanceled()){var b=null;"number"==typeof a?(this.progress=a,b=new createjs.ProgressEvent(this.progress)):(b=a,this.progress=a.loaded/a.total,b.progress=this.progress,(isNaN(this.progress)||1/0==this.progress)&&(this.progress=0)),this.hasEventListener("progress")&&this.dispatchEvent(b)}},a._sendComplete=function(){if(!this._isCanceled()){this.loaded=!0;var a=new createjs.Event("complete");a.rawResult=this._rawResult,null!=this._result&&(a.result=this._result),this.dispatchEvent(a)}},a._sendError=function(a){!this._isCanceled()&&this.hasEventListener("error")&&(null==a&&(a=new createjs.ErrorEvent("PRELOAD_ERROR_EMPTY")),this.dispatchEvent(a))},a._isCanceled=function(){return null==window.createjs||this.canceled?!0:!1},a.resultFormatter=null,a.handleEvent=function(a){switch(a.type){case"complete":this._rawResult=a.target._response;var b=this.resultFormatter&&this.resultFormatter(this),c=this;b instanceof Function?b(function(a){c._result=a,c._sendComplete()}):(this._result=b||this._rawResult,this._sendComplete());break;case"progress":this._sendProgress(a);break;case"error":this._sendError(a);break;case"loadstart":this._sendLoadStart();break;case"abort":case"timeout":this._isCanceled()||this.dispatchEvent(a.type)}},a.buildPath=function(a,b){return createjs.RequestUtils.buildPath(a,b)},a.toString=function(){return"[PreloadJS AbstractLoader]"},createjs.AbstractLoader=createjs.promote(AbstractLoader,"EventDispatcher")}(),this.createjs=this.createjs||{},function(){"use strict";function AbstractMediaLoader(a,b,c){this.AbstractLoader_constructor(a,b,c),this.resultFormatter=this._formatResult,this._tagSrcAttribute="src"}var a=createjs.extend(AbstractMediaLoader,createjs.AbstractLoader);a.load=function(){this._tag||(this._tag=this._createTag(this._item.src)),this._tag.preload="auto",this._tag.load(),this.AbstractLoader_load()},a._createTag=function(){},a._createRequest=function(){this._request=this._preferXHR?new createjs.XHRRequest(this._item):new createjs.MediaTagRequest(this._item,this._tag||this._createTag(),this._tagSrcAttribute)},a._formatResult=function(a){return this._tag.removeEventListener&&this._tag.removeEventListener("canplaythrough",this._loadedHandler),this._tag.onstalled=null,this._preferXHR&&(a.getTag().src=a.getResult(!0)),a.getTag()},createjs.AbstractMediaLoader=createjs.promote(AbstractMediaLoader,"AbstractLoader")}(),this.createjs=this.createjs||{},function(){"use strict";var AbstractRequest=function(a){this._item=a},a=createjs.extend(AbstractRequest,createjs.EventDispatcher);a.load=function(){},a.destroy=function(){},a.cancel=function(){},createjs.AbstractRequest=createjs.promote(AbstractRequest,"EventDispatcher")}(),this.createjs=this.createjs||{},function(){"use strict";function TagRequest(a,b,c){this.AbstractRequest_constructor(a),this._tag=b,this._tagSrcAttribute=c,this._loadedHandler=createjs.proxy(this._handleTagComplete,this),this._addedToDOM=!1,this._startTagVisibility=null}var a=createjs.extend(TagRequest,createjs.AbstractRequest);a.load=function(){this._tag.onload=createjs.proxy(this._handleTagComplete,this),this._tag.onreadystatechange=createjs.proxy(this._handleReadyStateChange,this),this._tag.onerror=createjs.proxy(this._handleError,this);var a=new createjs.Event("initialize");a.loader=this._tag,this.dispatchEvent(a),this._hideTag(),this._loadTimeout=setTimeout(createjs.proxy(this._handleTimeout,this),this._item.loadTimeout),this._tag[this._tagSrcAttribute]=this._item.src,null==this._tag.parentNode&&(window.document.body.appendChild(this._tag),this._addedToDOM=!0)},a.destroy=function(){this._clean(),this._tag=null,this.AbstractRequest_destroy()},a._handleReadyStateChange=function(){clearTimeout(this._loadTimeout);var a=this._tag;("loaded"==a.readyState||"complete"==a.readyState)&&this._handleTagComplete()},a._handleError=function(){this._clean(),this.dispatchEvent("error")},a._handleTagComplete=function(){this._rawResult=this._tag,this._result=this.resultFormatter&&this.resultFormatter(this)||this._rawResult,this._clean(),this._showTag(),this.dispatchEvent("complete")},a._handleTimeout=function(){this._clean(),this.dispatchEvent(new createjs.Event("timeout"))},a._clean=function(){this._tag.onload=null,this._tag.onreadystatechange=null,this._tag.onerror=null,this._addedToDOM&&null!=this._tag.parentNode&&this._tag.parentNode.removeChild(this._tag),clearTimeout(this._loadTimeout)},a._hideTag=function(){this._startTagVisibility=this._tag.style.visibility,this._tag.style.visibility="hidden"},a._showTag=function(){this._tag.style.visibility=this._startTagVisibility},a._handleStalled=function(){},createjs.TagRequest=createjs.promote(TagRequest,"AbstractRequest")}(),this.createjs=this.createjs||{},function(){"use strict";function MediaTagRequest(a,b,c){this.AbstractRequest_constructor(a),this._tag=b,this._tagSrcAttribute=c,this._loadedHandler=createjs.proxy(this._handleTagComplete,this)}var a=createjs.extend(MediaTagRequest,createjs.TagRequest);a.load=function(){var a=createjs.proxy(this._handleStalled,this);this._stalledCallback=a;var b=createjs.proxy(this._handleProgress,this);this._handleProgress=b,this._tag.addEventListener("stalled",a),this._tag.addEventListener("progress",b),this._tag.addEventListener&&this._tag.addEventListener("canplaythrough",this._loadedHandler,!1),this.TagRequest_load()},a._handleReadyStateChange=function(){clearTimeout(this._loadTimeout);var a=this._tag;("loaded"==a.readyState||"complete"==a.readyState)&&this._handleTagComplete()},a._handleStalled=function(){},a._handleProgress=function(a){if(a&&!(a.loaded>0&&0==a.total)){var b=new createjs.ProgressEvent(a.loaded,a.total);this.dispatchEvent(b)}},a._clean=function(){this._tag.removeEventListener&&this._tag.removeEventListener("canplaythrough",this._loadedHandler),this._tag.removeEventListener("stalled",this._stalledCallback),this._tag.removeEventListener("progress",this._progressCallback),this.TagRequest__clean()},createjs.MediaTagRequest=createjs.promote(MediaTagRequest,"TagRequest")}(),this.createjs=this.createjs||{},function(){"use strict";function XHRRequest(a){this.AbstractRequest_constructor(a),this._request=null,this._loadTimeout=null,this._xhrLevel=1,this._response=null,this._rawResponse=null,this._canceled=!1,this._handleLoadStartProxy=createjs.proxy(this._handleLoadStart,this),this._handleProgressProxy=createjs.proxy(this._handleProgress,this),this._handleAbortProxy=createjs.proxy(this._handleAbort,this),this._handleErrorProxy=createjs.proxy(this._handleError,this),this._handleTimeoutProxy=createjs.proxy(this._handleTimeout,this),this._handleLoadProxy=createjs.proxy(this._handleLoad,this),this._handleReadyStateChangeProxy=createjs.proxy(this._handleReadyStateChange,this),!this._createXHR(a)}var a=createjs.extend(XHRRequest,createjs.AbstractRequest);XHRRequest.ACTIVEX_VERSIONS=["Msxml2.XMLHTTP.6.0","Msxml2.XMLHTTP.5.0","Msxml2.XMLHTTP.4.0","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP"],a.getResult=function(a){return a&&this._rawResponse?this._rawResponse:this._response},a.cancel=function(){this.canceled=!0,this._clean(),this._request.abort()},a.load=function(){if(null==this._request)return void this._handleError();this._request.addEventListener("loadstart",this._handleLoadStartProxy,!1),this._request.addEventListener("progress",this._handleProgressProxy,!1),this._request.addEventListener("abort",this._handleAbortProxy,!1),this._request.addEventListener("error",this._handleErrorProxy,!1),this._request.addEventListener("timeout",this._handleTimeoutProxy,!1),this._request.addEventListener("load",this._handleLoadProxy,!1),this._request.addEventListener("readystatechange",this._handleReadyStateChangeProxy,!1),1==this._xhrLevel&&(this._loadTimeout=setTimeout(createjs.proxy(this._handleTimeout,this),this._item.loadTimeout));try{this._item.values&&this._item.method!=createjs.AbstractLoader.GET?this._item.method==createjs.AbstractLoader.POST&&this._request.send(createjs.RequestUtils.formatQueryString(this._item.values)):this._request.send()}catch(a){this.dispatchEvent(new createjs.ErrorEvent("XHR_SEND",null,a))}},a.setResponseType=function(a){this._request.responseType=a},a.getAllResponseHeaders=function(){return this._request.getAllResponseHeaders instanceof Function?this._request.getAllResponseHeaders():null},a.getResponseHeader=function(a){return this._request.getResponseHeader instanceof Function?this._request.getResponseHeader(a):null},a._handleProgress=function(a){if(a&&!(a.loaded>0&&0==a.total)){var b=new createjs.ProgressEvent(a.loaded,a.total);this.dispatchEvent(b)}},a._handleLoadStart=function(){clearTimeout(this._loadTimeout),this.dispatchEvent("loadstart")},a._handleAbort=function(a){this._clean(),this.dispatchEvent(new createjs.ErrorEvent("XHR_ABORTED",null,a))},a._handleError=function(a){this._clean(),this.dispatchEvent(new createjs.ErrorEvent(a.message))},a._handleReadyStateChange=function(){4==this._request.readyState&&this._handleLoad()},a._handleLoad=function(){if(!this.loaded){this.loaded=!0;var a=this._checkError();if(a)return void this._handleError(a);this._response=this._getResponse(),this._clean(),this.dispatchEvent(new createjs.Event("complete"))}},a._handleTimeout=function(a){this._clean(),this.dispatchEvent(new createjs.ErrorEvent("PRELOAD_TIMEOUT",null,a))},a._checkError=function(){var a=parseInt(this._request.status);switch(a){case 404:case 0:return new Error(a)}return null},a._getResponse=function(){if(null!=this._response)return this._response;if(null!=this._request.response)return this._request.response;try{if(null!=this._request.responseText)return this._request.responseText}catch(a){}try{if(null!=this._request.responseXML)return this._request.responseXML}catch(a){}return null},a._createXHR=function(a){var b=createjs.RequestUtils.isCrossDomain(a),c={},d=null;if(window.XMLHttpRequest)d=new XMLHttpRequest,b&&void 0===d.withCredentials&&window.XDomainRequest&&(d=new XDomainRequest);else{for(var e=0,f=s.ACTIVEX_VERSIONS.length;f>e;e++){{s.ACTIVEX_VERSIONS[e]}try{d=new ActiveXObject(axVersions);break}catch(g){}}if(null==d)return!1}null==a.mimeType&&createjs.RequestUtils.isText(a.type)&&(a.mimeType="text/plain; charset=utf-8"),a.mimeType&&d.overrideMimeType&&d.overrideMimeType(a.mimeType),this._xhrLevel="string"==typeof d.responseType?2:1;var h=null;if(h=a.method==createjs.AbstractLoader.GET?createjs.RequestUtils.buildPath(a.src,a.values):a.src,d.open(a.method||createjs.AbstractLoader.GET,h,!0),b&&d instanceof XMLHttpRequest&&1==this._xhrLevel&&(c.Origin=location.origin),a.values&&a.method==createjs.AbstractLoader.POST&&(c["Content-Type"]="application/x-www-form-urlencoded"),b||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest"),a.headers)for(var i in a.headers)c[i]=a.headers[i];for(i in c)d.setRequestHeader(i,c[i]);return d instanceof XMLHttpRequest&&void 0!==a.withCredentials&&(d.withCredentials=a.withCredentials),this._request=d,!0},a._clean=function(){clearTimeout(this._loadTimeout),this._request.removeEventListener("loadstart",this._handleLoadStartProxy),this._request.removeEventListener("progress",this._handleProgressProxy),this._request.removeEventListener("abort",this._handleAbortProxy),this._request.removeEventListener("error",this._handleErrorProxy),this._request.removeEventListener("timeout",this._handleTimeoutProxy),this._request.removeEventListener("load",this._handleLoadProxy),this._request.removeEventListener("readystatechange",this._handleReadyStateChangeProxy)},a.toString=function(){return"[PreloadJS XHRRequest]"},createjs.XHRRequest=createjs.promote(XHRRequest,"AbstractRequest")}(),this.createjs=this.createjs||{},function(){"use strict";function SoundLoader(a,b){this.AbstractMediaLoader_constructor(a,b,createjs.AbstractLoader.SOUND),createjs.RequestUtils.isAudioTag(a)?this._tag=a:createjs.RequestUtils.isAudioTag(a.src)?this._tag=a:createjs.RequestUtils.isAudioTag(a.tag)&&(this._tag=createjs.RequestUtils.isAudioTag(a)?a:a.src),null!=this._tag&&(this._preferXHR=!1)}var a=createjs.extend(SoundLoader,createjs.AbstractMediaLoader),b=SoundLoader;b.canLoadItem=function(a){return a.type==createjs.AbstractLoader.SOUND},a._createTag=function(a){var b=document.createElement("audio");return b.autoplay=!1,b.preload="none",b.src=a,b},createjs.SoundLoader=createjs.promote(SoundLoader,"AbstractMediaLoader")}(),this.createjs=this.createjs||{},function(){"use strict";var PlayPropsConfig=function(){this.interrupt=null,this.delay=null,this.offset=null,this.loop=null,this.volume=null,this.pan=null,this.startTime=null,this.duration=null},a=PlayPropsConfig.prototype={},b=PlayPropsConfig;b.create=function(a){if(a instanceof b||a instanceof Object){var c=new createjs.PlayPropsConfig;return c.set(a),c}throw new Error("Type not recognized.")},a.set=function(a){for(var b in a)this[b]=a[b];return this},a.toString=function(){return"[PlayPropsConfig]"},createjs.PlayPropsConfig=b}(),this.createjs=this.createjs||{},function(){"use strict";function Sound(){throw"Sound cannot be instantiated"}function a(a,b){this.init(a,b)}var b=Sound;b.INTERRUPT_ANY="any",b.INTERRUPT_EARLY="early",b.INTERRUPT_LATE="late",b.INTERRUPT_NONE="none",b.PLAY_INITED="playInited",b.PLAY_SUCCEEDED="playSucceeded",b.PLAY_INTERRUPTED="playInterrupted",b.PLAY_FINISHED="playFinished",b.PLAY_FAILED="playFailed",b.SUPPORTED_EXTENSIONS=["mp3","ogg","opus","mpeg","wav","m4a","mp4","aiff","wma","mid"],b.EXTENSION_MAP={m4a:"mp4"},b.FILE_PATTERN=/^(?:(\w+:)\/{2}(\w+(?:\.\w+)*\/?))?([/.]*?(?:[^?]+)?\/)?((?:[^/?]+)\.(\w+))(?:\?(\S+)?)?$/,b.defaultInterruptBehavior=b.INTERRUPT_NONE,b.alternateExtensions=[],b.activePlugin=null,b._masterVolume=1,Object.defineProperty(b,"volume",{get:function(){return this._masterVolume},set:function(a){if(null==Number(a))return!1;if(a=Math.max(0,Math.min(1,a)),b._masterVolume=a,!this.activePlugin||!this.activePlugin.setVolume||!this.activePlugin.setVolume(a))for(var c=this._instances,d=0,e=c.length;e>d;d++)c[d].setMasterVolume(a)}}),b._masterMute=!1,Object.defineProperty(b,"muted",{get:function(){return this._masterMute},set:function(a){if(null==a)return!1;if(this._masterMute=a,!this.activePlugin||!this.activePlugin.setMute||!this.activePlugin.setMute(a))for(var b=this._instances,c=0,d=b.length;d>c;c++)b[c].setMasterMute(a);return!0}}),Object.defineProperty(b,"capabilities",{get:function(){return null==b.activePlugin?null:b.activePlugin._capabilities},set:function(){return!1}}),b._pluginsRegistered=!1,b._lastID=0,b._instances=[],b._idHash={},b._preloadHash={},b._defaultPlayPropsHash={},b.addEventListener=null,b.removeEventListener=null,b.removeAllEventListeners=null,b.dispatchEvent=null,b.hasEventListener=null,b._listeners=null,createjs.EventDispatcher.initialize(b),b.getPreloadHandlers=function(){return{callback:createjs.proxy(b.initLoad,b),types:["sound"],extensions:b.SUPPORTED_EXTENSIONS}},b._handleLoadComplete=function(a){var c=a.target.getItem().src;if(b._preloadHash[c])for(var d=0,e=b._preloadHash[c].length;e>d;d++){var f=b._preloadHash[c][d];if(b._preloadHash[c][d]=!0,b.hasEventListener("fileload")){var a=new createjs.Event("fileload");a.src=f.src,a.id=f.id,a.data=f.data,a.sprite=f.sprite,b.dispatchEvent(a)}}},b._handleLoadError=function(a){var c=a.target.getItem().src;if(b._preloadHash[c])for(var d=0,e=b._preloadHash[c].length;e>d;d++){var f=b._preloadHash[c][d];if(b._preloadHash[c][d]=!1,b.hasEventListener("fileerror")){var a=new createjs.Event("fileerror");a.src=f.src,a.id=f.id,a.data=f.data,a.sprite=f.sprite,b.dispatchEvent(a)}}},b._registerPlugin=function(a){return a.isSupported()?(b.activePlugin=new a,!0):!1},b.registerPlugins=function(a){b._pluginsRegistered=!0;for(var c=0,d=a.length;d>c;c++)if(b._registerPlugin(a[c]))return!0;return!1},b.initializeDefaultPlugins=function(){return null!=b.activePlugin?!0:b._pluginsRegistered?!1:b.registerPlugins([createjs.WebAudioPlugin,createjs.HTMLAudioPlugin])?!0:!1},b.isReady=function(){return null!=b.activePlugin},b.getCapabilities=function(){return null==b.activePlugin?null:b.activePlugin._capabilities},b.getCapability=function(a){return null==b.activePlugin?null:b.activePlugin._capabilities[a]},b.initLoad=function(a){return b._registerSound(a)},b._registerSound=function(c){if(!b.initializeDefaultPlugins())return!1;var d;if(c.src instanceof Object?(d=b._parseSrc(c.src),d.src=c.path+d.src):d=b._parsePath(c.src),null==d)return!1;c.src=d.src,c.type="sound";var e=c.data,f=null;if(null!=e&&(isNaN(e.channels)?isNaN(e)||(f=parseInt(e)):f=parseInt(e.channels),e.audioSprite))for(var g,h=e.audioSprite.length;h--;)g=e.audioSprite[h],b._idHash[g.id]={src:c.src,startTime:parseInt(g.startTime),duration:parseInt(g.duration)},g.defaultPlayProps&&(b._defaultPlayPropsHash[g.id]=createjs.PlayPropsConfig.create(g.defaultPlayProps));null!=c.id&&(b._idHash[c.id]={src:c.src});var i=b.activePlugin.register(c);return a.create(c.src,f),null!=e&&isNaN(e)?c.data.channels=f||a.maxPerChannel():c.data=f||a.maxPerChannel(),i.type&&(c.type=i.type),c.defaultPlayProps&&(b._defaultPlayPropsHash[c.src]=createjs.PlayPropsConfig.create(c.defaultPlayProps)),i},b.registerSound=function(a,c,d,e,f){var g={src:a,id:c,data:d,defaultPlayProps:f};a instanceof Object&&a.src&&(e=c,g=a),g=createjs.LoadItem.create(g),g.path=e,null==e||g.src instanceof Object||(g.src=e+a);var h=b._registerSound(g);if(!h)return!1;if(b._preloadHash[g.src]||(b._preloadHash[g.src]=[]),b._preloadHash[g.src].push(g),1==b._preloadHash[g.src].length)h.on("complete",createjs.proxy(this._handleLoadComplete,this)),h.on("error",createjs.proxy(this._handleLoadError,this)),b.activePlugin.preload(h);else if(1==b._preloadHash[g.src][0])return!0;return g},b.registerSounds=function(a,b){var c=[];a.path&&(b?b+=a.path:b=a.path,a=a.manifest);for(var d=0,e=a.length;e>d;d++)c[d]=createjs.Sound.registerSound(a[d].src,a[d].id,a[d].data,b,a[d].defaultPlayProps);return c},b.removeSound=function(c,d){if(null==b.activePlugin)return!1;c instanceof Object&&c.src&&(c=c.src);var e;if(c instanceof Object?e=b._parseSrc(c):(c=b._getSrcById(c).src,e=b._parsePath(c)),null==e)return!1;c=e.src,null!=d&&(c=d+c);for(var f in b._idHash)b._idHash[f].src==c&&delete b._idHash[f];return a.removeSrc(c),delete b._preloadHash[c],b.activePlugin.removeSound(c),!0},b.removeSounds=function(a,b){var c=[];a.path&&(b?b+=a.path:b=a.path,a=a.manifest);for(var d=0,e=a.length;e>d;d++)c[d]=createjs.Sound.removeSound(a[d].src,b);return c},b.removeAllSounds=function(){b._idHash={},b._preloadHash={},a.removeAll(),b.activePlugin&&b.activePlugin.removeAllSounds()},b.loadComplete=function(a){if(!b.isReady())return!1;var c=b._parsePath(a);return a=c?b._getSrcById(c.src).src:b._getSrcById(a).src,void 0==b._preloadHash[a]?!1:1==b._preloadHash[a][0]},b._parsePath=function(a){"string"!=typeof a&&(a=a.toString());var c=a.match(b.FILE_PATTERN);if(null==c)return!1;for(var d=c[4],e=c[5],f=b.capabilities,g=0;!f[e];)if(e=b.alternateExtensions[g++],g>b.alternateExtensions.length)return null;a=a.replace("."+c[5],"."+e);var h={name:d,src:a,extension:e};return h},b._parseSrc=function(a){var c={name:void 0,src:void 0,extension:void 0},d=b.capabilities;for(var e in a)if(a.hasOwnProperty(e)&&d[e]){c.src=a[e],c.extension=e;break}if(!c.src)return!1;var f=c.src.lastIndexOf("/");return c.name=-1!=f?c.src.slice(f+1):c.src,c},b.play=function(a,c,d,e,f,g,h,i,j){var k;k=createjs.PlayPropsConfig.create(c instanceof Object||c instanceof createjs.PlayPropsConfig?c:{interrupt:c,delay:d,offset:e,loop:f,volume:g,pan:h,startTime:i,duration:j});var l=b.createInstance(a,k.startTime,k.duration),m=b._playInstance(l,k);return m||l._playFailed(),l},b.createInstance=function(c,d,e){if(!b.initializeDefaultPlugins())return new createjs.DefaultSoundInstance(c,d,e);var f=b._defaultPlayPropsHash[c];c=b._getSrcById(c);var g=b._parsePath(c.src),h=null;return null!=g&&null!=g.src?(a.create(g.src),null==d&&(d=c.startTime),h=b.activePlugin.create(g.src,d,e||c.duration),f=f||b._defaultPlayPropsHash[g.src],f&&h.applyPlayProps(f)):h=new createjs.DefaultSoundInstance(c,d,e),h.uniqueId=b._lastID++,h},b.stop=function(){for(var a=this._instances,b=a.length;b--;)a[b].stop()},b.setVolume=function(a){if(null==Number(a))return!1;if(a=Math.max(0,Math.min(1,a)),b._masterVolume=a,!this.activePlugin||!this.activePlugin.setVolume||!this.activePlugin.setVolume(a))for(var c=this._instances,d=0,e=c.length;e>d;d++)c[d].setMasterVolume(a)},b.getVolume=function(){return this._masterVolume},b.setMute=function(a){if(null==a)return!1;if(this._masterMute=a,!this.activePlugin||!this.activePlugin.setMute||!this.activePlugin.setMute(a))for(var b=this._instances,c=0,d=b.length;d>c;c++)b[c].setMasterMute(a);return!0},b.getMute=function(){return this._masterMute},b.setDefaultPlayProps=function(a,c){a=b._getSrcById(a),b._defaultPlayPropsHash[b._parsePath(a.src).src]=createjs.PlayPropsConfig.create(c)},b.getDefaultPlayProps=function(a){return a=b._getSrcById(a),b._defaultPlayPropsHash[b._parsePath(a.src).src]},b._playInstance=function(a,c){var d=b._defaultPlayPropsHash[a.src]||{};if(null==c.interrupt&&(c.interrupt=d.interrupt||b.defaultInterruptBehavior),null==c.delay&&(c.delay=d.delay||0),null==c.offset&&(c.offset=a.getPosition()),null==c.loop&&(c.loop=a.loop),null==c.volume&&(c.volume=a.volume),null==c.pan&&(c.pan=a.pan),0==c.delay){var e=b._beginPlaying(a,c); if(!e)return!1}else{var f=setTimeout(function(){b._beginPlaying(a,c)},c.delay);a.delayTimeoutId=f}return this._instances.push(a),!0},b._beginPlaying=function(b,c){if(!a.add(b,c.interrupt))return!1;var d=b._beginPlaying(c);if(!d){var e=createjs.indexOf(this._instances,b);return e>-1&&this._instances.splice(e,1),!1}return!0},b._getSrcById=function(a){return b._idHash[a]||{src:a}},b._playFinished=function(b){a.remove(b);var c=createjs.indexOf(this._instances,b);c>-1&&this._instances.splice(c,1)},createjs.Sound=Sound,a.channels={},a.create=function(b,c){var d=a.get(b);return null==d?(a.channels[b]=new a(b,c),!0):!1},a.removeSrc=function(b){var c=a.get(b);return null==c?!1:(c._removeAll(),delete a.channels[b],!0)},a.removeAll=function(){for(var b in a.channels)a.channels[b]._removeAll();a.channels={}},a.add=function(b,c){var d=a.get(b.src);return null==d?!1:d._add(b,c)},a.remove=function(b){var c=a.get(b.src);return null==c?!1:(c._remove(b),!0)},a.maxPerChannel=function(){return c.maxDefault},a.get=function(b){return a.channels[b]};var c=a.prototype;c.constructor=a,c.src=null,c.max=null,c.maxDefault=100,c.length=0,c.init=function(a,b){this.src=a,this.max=b||this.maxDefault,-1==this.max&&(this.max=this.maxDefault),this._instances=[]},c._get=function(a){return this._instances[a]},c._add=function(a,b){return this._getSlot(b,a)?(this._instances.push(a),this.length++,!0):!1},c._remove=function(a){var b=createjs.indexOf(this._instances,a);return-1==b?!1:(this._instances.splice(b,1),this.length--,!0)},c._removeAll=function(){for(var a=this.length-1;a>=0;a--)this._instances[a].stop()},c._getSlot=function(a){var b,c;if(a!=Sound.INTERRUPT_NONE&&(c=this._get(0),null==c))return!0;for(var d=0,e=this.max;e>d;d++){if(b=this._get(d),null==b)return!0;if(b.playState==Sound.PLAY_FINISHED||b.playState==Sound.PLAY_INTERRUPTED||b.playState==Sound.PLAY_FAILED){c=b;break}a!=Sound.INTERRUPT_NONE&&(a==Sound.INTERRUPT_EARLY&&b.getPosition()c.getPosition())&&(c=b)}return null!=c?(c._interrupt(),this._remove(c),!0):!1},c.toString=function(){return"[Sound SoundChannel]"}}(),this.createjs=this.createjs||{},function(){"use strict";var AbstractSoundInstance=function(a,b,c,d){this.EventDispatcher_constructor(),this.src=a,this.uniqueId=-1,this.playState=null,this.delayTimeoutId=null,this._volume=1,Object.defineProperty(this,"volume",{get:this.getVolume,set:this.setVolume}),this._pan=0,Object.defineProperty(this,"pan",{get:this.getPan,set:this.setPan}),this._startTime=Math.max(0,b||0),Object.defineProperty(this,"startTime",{get:this.getStartTime,set:this.setStartTime}),this._duration=Math.max(0,c||0),Object.defineProperty(this,"duration",{get:this.getDuration,set:this.setDuration}),this._playbackResource=null,Object.defineProperty(this,"playbackResource",{get:this.getPlaybackResource,set:this.setPlaybackResource}),d!==!1&&d!==!0&&this.setPlaybackResource(d),this._position=0,Object.defineProperty(this,"position",{get:this.getPosition,set:this.setPosition}),this._loop=0,Object.defineProperty(this,"loop",{get:this.getLoop,set:this.setLoop}),this._muted=!1,Object.defineProperty(this,"muted",{get:this.getMuted,set:this.setMuted}),this._paused=!1,Object.defineProperty(this,"paused",{get:this.getPaused,set:this.setPaused})},a=createjs.extend(AbstractSoundInstance,createjs.EventDispatcher);a.play=function(a,b,c,d,e,f){var g;return g=createjs.PlayPropsConfig.create(a instanceof Object||a instanceof createjs.PlayPropsConfig?a:{interrupt:a,delay:b,offset:c,loop:d,volume:e,pan:f}),this.playState==createjs.Sound.PLAY_SUCCEEDED?(this.applyPlayProps(g),void(this._paused&&this.setPaused(!1))):(this._cleanUp(),createjs.Sound._playInstance(this,g),this)},a.stop=function(){return this._position=0,this._paused=!1,this._handleStop(),this._cleanUp(),this.playState=createjs.Sound.PLAY_FINISHED,this},a.destroy=function(){this._cleanUp(),this.src=null,this.playbackResource=null,this.removeAllEventListeners()},a.applyPlayProps=function(a){return null!=a.offset&&this.setPosition(a.offset),null!=a.loop&&this.setLoop(a.loop),null!=a.volume&&this.setVolume(a.volume),null!=a.pan&&this.setPan(a.pan),null!=a.startTime&&(this.setStartTime(a.startTime),this.setDuration(a.duration)),this},a.toString=function(){return"[AbstractSoundInstance]"},a.getPaused=function(){return this._paused},a.setPaused=function(a){return a!==!0&&a!==!1||this._paused==a||1==a&&this.playState!=createjs.Sound.PLAY_SUCCEEDED?void 0:(this._paused=a,a?this._pause():this._resume(),clearTimeout(this.delayTimeoutId),this)},a.setVolume=function(a){return a==this._volume?this:(this._volume=Math.max(0,Math.min(1,a)),this._muted||this._updateVolume(),this)},a.getVolume=function(){return this._volume},a.setMuted=function(a){return a===!0||a===!1?(this._muted=a,this._updateVolume(),this):void 0},a.getMuted=function(){return this._muted},a.setPan=function(a){return a==this._pan?this:(this._pan=Math.max(-1,Math.min(1,a)),this._updatePan(),this)},a.getPan=function(){return this._pan},a.getPosition=function(){return this._paused||this.playState!=createjs.Sound.PLAY_SUCCEEDED||(this._position=this._calculateCurrentPosition()),this._position},a.setPosition=function(a){return this._position=Math.max(0,a),this.playState==createjs.Sound.PLAY_SUCCEEDED&&this._updatePosition(),this},a.getStartTime=function(){return this._startTime},a.setStartTime=function(a){return a==this._startTime?this:(this._startTime=Math.max(0,a||0),this._updateStartTime(),this)},a.getDuration=function(){return this._duration},a.setDuration=function(a){return a==this._duration?this:(this._duration=Math.max(0,a||0),this._updateDuration(),this)},a.setPlaybackResource=function(a){return this._playbackResource=a,0==this._duration&&this._setDurationFromSource(),this},a.getPlaybackResource=function(){return this._playbackResource},a.getLoop=function(){return this._loop},a.setLoop=function(a){null!=this._playbackResource&&(0!=this._loop&&0==a?this._removeLooping(a):0==this._loop&&0!=a&&this._addLooping(a)),this._loop=a},a._sendEvent=function(a){var b=new createjs.Event(a);this.dispatchEvent(b)},a._cleanUp=function(){clearTimeout(this.delayTimeoutId),this._handleCleanUp(),this._paused=!1,createjs.Sound._playFinished(this)},a._interrupt=function(){this._cleanUp(),this.playState=createjs.Sound.PLAY_INTERRUPTED,this._sendEvent("interrupted")},a._beginPlaying=function(a){return this.setPosition(a.offset),this.setLoop(a.loop),this.setVolume(a.volume),this.setPan(a.pan),null!=a.startTime&&(this.setStartTime(a.startTime),this.setDuration(a.duration)),null!=this._playbackResource&&this._positionc;c++){var e=this._soundInstances[b][c];e.setPlaybackResource(this._audioSources[b])}},a._handlePreloadError=function(){},a._updateVolume=function(){},createjs.AbstractPlugin=AbstractPlugin}(),this.createjs=this.createjs||{},function(){"use strict";function a(a){this.AbstractLoader_constructor(a,!0,createjs.AbstractLoader.SOUND)}var b=createjs.extend(a,createjs.AbstractLoader);a.context=null,b.toString=function(){return"[WebAudioLoader]"},b._createRequest=function(){this._request=new createjs.XHRRequest(this._item,!1),this._request.setResponseType("arraybuffer")},b._sendComplete=function(){a.context.decodeAudioData(this._rawResult,createjs.proxy(this._handleAudioDecoded,this),createjs.proxy(this._sendError,this))},b._handleAudioDecoded=function(a){this._result=a,this.AbstractLoader__sendComplete()},createjs.WebAudioLoader=createjs.promote(a,"AbstractLoader")}(),this.createjs=this.createjs||{},function(){"use strict";function WebAudioSoundInstance(a,c,d,e){this.AbstractSoundInstance_constructor(a,c,d,e),this.gainNode=b.context.createGain(),this.panNode=b.context.createPanner(),this.panNode.panningModel=b._panningModel,this.panNode.connect(this.gainNode),this.sourceNode=null,this._soundCompleteTimeout=null,this._sourceNodeNext=null,this._playbackStartTime=0,this._endedHandler=createjs.proxy(this._handleSoundComplete,this)}var a=createjs.extend(WebAudioSoundInstance,createjs.AbstractSoundInstance),b=WebAudioSoundInstance;b.context=null,b.destinationNode=null,b._panningModel="equalpower",a.destroy=function(){this.AbstractSoundInstance_destroy(),this.panNode.disconnect(0),this.panNode=null,this.gainNode.disconnect(0),this.gainNode=null},a.toString=function(){return"[WebAudioSoundInstance]"},a._updatePan=function(){this.panNode.setPosition(this._pan,0,-.5)},a._removeLooping=function(){this._sourceNodeNext=this._cleanUpAudioNode(this._sourceNodeNext)},a._addLooping=function(){this.playState==createjs.Sound.PLAY_SUCCEEDED&&(this._sourceNodeNext=this._createAndPlayAudioNode(this._playbackStartTime,0))},a._setDurationFromSource=function(){this._duration=1e3*this.playbackResource.duration},a._handleCleanUp=function(){this.sourceNode&&this.playState==createjs.Sound.PLAY_SUCCEEDED&&(this.sourceNode=this._cleanUpAudioNode(this.sourceNode),this._sourceNodeNext=this._cleanUpAudioNode(this._sourceNodeNext)),0!=this.gainNode.numberOfOutputs&&this.gainNode.disconnect(0),clearTimeout(this._soundCompleteTimeout),this._playbackStartTime=0},a._cleanUpAudioNode=function(a){return a&&(a.stop(0),a.disconnect(0),a=null),a},a._handleSoundReady=function(){this.gainNode.connect(b.destinationNode);var a=.001*this._duration,c=.001*this._position;c>a&&(c=a),this.sourceNode=this._createAndPlayAudioNode(b.context.currentTime-a,c),this._playbackStartTime=this.sourceNode.startTime-c,this._soundCompleteTimeout=setTimeout(this._endedHandler,1e3*(a-c)),0!=this._loop&&(this._sourceNodeNext=this._createAndPlayAudioNode(this._playbackStartTime,0))},a._createAndPlayAudioNode=function(a,c){var d=b.context.createBufferSource();d.buffer=this.playbackResource,d.connect(this.panNode);var e=.001*this._duration;return d.startTime=a+e,d.start(d.startTime,c+.001*this._startTime,e-c),d},a._pause=function(){this._position=1e3*(b.context.currentTime-this._playbackStartTime),this.sourceNode=this._cleanUpAudioNode(this.sourceNode),this._sourceNodeNext=this._cleanUpAudioNode(this._sourceNodeNext),0!=this.gainNode.numberOfOutputs&&this.gainNode.disconnect(0),clearTimeout(this._soundCompleteTimeout)},a._resume=function(){this._handleSoundReady()},a._updateVolume=function(){var a=this._muted?0:this._volume;a!=this.gainNode.gain.value&&(this.gainNode.gain.value=a)},a._calculateCurrentPosition=function(){return 1e3*(b.context.currentTime-this._playbackStartTime)},a._updatePosition=function(){this.sourceNode=this._cleanUpAudioNode(this.sourceNode),this._sourceNodeNext=this._cleanUpAudioNode(this._sourceNodeNext),clearTimeout(this._soundCompleteTimeout),this._paused||this._handleSoundReady()},a._handleLoop=function(){this._cleanUpAudioNode(this.sourceNode),this.sourceNode=this._sourceNodeNext,this._playbackStartTime=this.sourceNode.startTime,this._sourceNodeNext=this._createAndPlayAudioNode(this._playbackStartTime,0),this._soundCompleteTimeout=setTimeout(this._endedHandler,this._duration)},a._updateDuration=function(){this.playState==createjs.Sound.PLAY_SUCCEEDED&&(this._pause(),this._resume())},createjs.WebAudioSoundInstance=createjs.promote(WebAudioSoundInstance,"AbstractSoundInstance")}(),this.createjs=this.createjs||{},function(){"use strict";function WebAudioPlugin(){this.AbstractPlugin_constructor(),this._panningModel=b._panningModel,this.context=b.context,this.dynamicsCompressorNode=this.context.createDynamicsCompressor(),this.dynamicsCompressorNode.connect(this.context.destination),this.gainNode=this.context.createGain(),this.gainNode.connect(this.dynamicsCompressorNode),createjs.WebAudioSoundInstance.destinationNode=this.gainNode,this._capabilities=b._capabilities,this._loaderClass=createjs.WebAudioLoader,this._soundInstanceClass=createjs.WebAudioSoundInstance,this._addPropsToClasses()}var a=createjs.extend(WebAudioPlugin,createjs.AbstractPlugin),b=WebAudioPlugin;b._capabilities=null,b._panningModel="equalpower",b.context=null,b.isSupported=function(){var a=createjs.BrowserDetect.isIOS||createjs.BrowserDetect.isAndroid||createjs.BrowserDetect.isBlackberry;return"file:"!=location.protocol||a||this._isFileXHRSupported()?(b._generateCapabilities(),null==b.context?!1:!0):!1},b.playEmptySound=function(){if(null!=b.context){var a=b.context.createBufferSource();a.buffer=b.context.createBuffer(1,1,22050),a.connect(b.context.destination),a.start(0,0,0)}},b._isFileXHRSupported=function(){var a=!0,b=new XMLHttpRequest;try{b.open("GET","WebAudioPluginTest.fail",!1)}catch(c){return a=!1}b.onerror=function(){a=!1},b.onload=function(){a=404==this.status||200==this.status||0==this.status&&""!=this.response};try{b.send()}catch(c){a=!1}return a},b._generateCapabilities=function(){if(null==b._capabilities){var a=document.createElement("audio");if(null==a.canPlayType)return null;if(null==b.context)if(window.AudioContext)b.context=new AudioContext;else{if(!window.webkitAudioContext)return null;b.context=new webkitAudioContext}b._compatibilitySetUp(),b.playEmptySound(),b._capabilities={panning:!0,volume:!0,tracks:-1};for(var c=createjs.Sound.SUPPORTED_EXTENSIONS,d=createjs.Sound.EXTENSION_MAP,e=0,f=c.length;f>e;e++){var g=c[e],h=d[g]||g;b._capabilities[g]="no"!=a.canPlayType("audio/"+g)&&""!=a.canPlayType("audio/"+g)||"no"!=a.canPlayType("audio/"+h)&&""!=a.canPlayType("audio/"+h)}b.context.destination.numberOfChannels<2&&(b._capabilities.panning=!1)}},b._compatibilitySetUp=function(){if(b._panningModel="equalpower",!b.context.createGain){b.context.createGain=b.context.createGainNode;var a=b.context.createBufferSource();a.__proto__.start=a.__proto__.noteGrainOn,a.__proto__.stop=a.__proto__.noteOff,b._panningModel=0}},a.toString=function(){return"[WebAudioPlugin]"},a._addPropsToClasses=function(){var a=this._soundInstanceClass;a.context=this.context,a.destinationNode=this.gainNode,a._panningModel=this._panningModel,this._loaderClass.context=this.context},a._updateVolume=function(){var a=createjs.Sound._masterMute?0:this._volume;a!=this.gainNode.gain.value&&(this.gainNode.gain.value=a)},createjs.WebAudioPlugin=createjs.promote(WebAudioPlugin,"AbstractPlugin")}(),this.createjs=this.createjs||{},function(){"use strict";function HTMLAudioTagPool(){throw"HTMLAudioTagPool cannot be instantiated"}function a(){this._tags=[]}var b=HTMLAudioTagPool;b._tags={},b._tagPool=new a,b._tagUsed={},b.get=function(a){var c=b._tags[a];return null==c?(c=b._tags[a]=b._tagPool.get(),c.src=a):b._tagUsed[a]?(c=b._tagPool.get(),c.src=a):b._tagUsed[a]=!0,c},b.set=function(a,c){c==b._tags[a]?b._tagUsed[a]=!1:b._tagPool.set(c)},b.remove=function(a){var c=b._tags[a];return null==c?!1:(b._tagPool.set(c),delete b._tags[a],delete b._tagUsed[a],!0)},b.getDuration=function(a){var c=b._tags[a];return null==c?0:1e3*c.duration},createjs.HTMLAudioTagPool=HTMLAudioTagPool;var c=a.prototype;c.constructor=a,c.get=function(){var a;return a=0==this._tags.length?this._createTag():this._tags.pop(),null==a.parentNode&&document.body.appendChild(a),a},c.set=function(a){var b=createjs.indexOf(this._tags,a);-1==b&&(this._tags.src=null,this._tags.push(a))},c.toString=function(){return"[TagPool]"},c._createTag=function(){var a=document.createElement("audio");return a.autoplay=!1,a.preload="none",a}}(),this.createjs=this.createjs||{},function(){"use strict";function HTMLAudioSoundInstance(a,b,c,d){this.AbstractSoundInstance_constructor(a,b,c,d),this._audioSpriteStopTime=null,this._delayTimeoutId=null,this._endedHandler=createjs.proxy(this._handleSoundComplete,this),this._readyHandler=createjs.proxy(this._handleTagReady,this),this._stalledHandler=createjs.proxy(this._playFailed,this),this._audioSpriteEndHandler=createjs.proxy(this._handleAudioSpriteLoop,this),this._loopHandler=createjs.proxy(this._handleSoundComplete,this),c?this._audioSpriteStopTime=.001*(b+c):this._duration=createjs.HTMLAudioTagPool.getDuration(this.src)}var a=createjs.extend(HTMLAudioSoundInstance,createjs.AbstractSoundInstance);a.setMasterVolume=function(){this._updateVolume()},a.setMasterMute=function(){this._updateVolume()},a.toString=function(){return"[HTMLAudioSoundInstance]"},a._removeLooping=function(){null!=this._playbackResource&&(this._playbackResource.loop=!1,this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._loopHandler,!1))},a._addLooping=function(){null==this._playbackResource||this._audioSpriteStopTime||(this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._loopHandler,!1),this._playbackResource.loop=!0)},a._handleCleanUp=function(){var a=this._playbackResource;if(null!=a){a.pause(),a.loop=!1,a.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED,this._endedHandler,!1),a.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_READY,this._readyHandler,!1),a.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_STALLED,this._stalledHandler,!1),a.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._loopHandler,!1),a.removeEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE,this._audioSpriteEndHandler,!1);try{a.currentTime=this._startTime}catch(b){}createjs.HTMLAudioTagPool.set(this.src,a),this._playbackResource=null}},a._beginPlaying=function(a){return this._playbackResource=createjs.HTMLAudioTagPool.get(this.src),this.AbstractSoundInstance__beginPlaying(a)},a._handleSoundReady=function(){if(4!==this._playbackResource.readyState){var a=this._playbackResource;return a.addEventListener(createjs.HTMLAudioPlugin._AUDIO_READY,this._readyHandler,!1),a.addEventListener(createjs.HTMLAudioPlugin._AUDIO_STALLED,this._stalledHandler,!1),a.preload="auto",void a.load()}this._updateVolume(),this._playbackResource.currentTime=.001*(this._startTime+this._position),this._audioSpriteStopTime?this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE,this._audioSpriteEndHandler,!1):(this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED,this._endedHandler,!1),0!=this._loop&&(this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._loopHandler,!1),this._playbackResource.loop=!0)),this._playbackResource.play()},a._handleTagReady=function(){this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_READY,this._readyHandler,!1),this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_STALLED,this._stalledHandler,!1),this._handleSoundReady()},a._pause=function(){this._playbackResource.pause()},a._resume=function(){this._playbackResource.play()},a._updateVolume=function(){if(null!=this._playbackResource){var a=this._muted||createjs.Sound._masterMute?0:this._volume*createjs.Sound._masterVolume;a!=this._playbackResource.volume&&(this._playbackResource.volume=a)}},a._calculateCurrentPosition=function(){return 1e3*this._playbackResource.currentTime-this._startTime},a._updatePosition=function(){this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._loopHandler,!1),this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._handleSetPositionSeek,!1);try{this._playbackResource.currentTime=.001*(this._position+this._startTime)}catch(a){this._handleSetPositionSeek(null)}},a._handleSetPositionSeek=function(){null!=this._playbackResource&&(this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._handleSetPositionSeek,!1),this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._loopHandler,!1))},a._handleAudioSpriteLoop=function(){this._playbackResource.currentTime<=this._audioSpriteStopTime||(this._playbackResource.pause(),0==this._loop?this._handleSoundComplete(null):(this._position=0,this._loop--,this._playbackResource.currentTime=.001*this._startTime,this._paused||this._playbackResource.play(),this._sendEvent("loop")))},a._handleLoop=function(){0==this._loop&&(this._playbackResource.loop=!1,this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED,this._loopHandler,!1))},a._updateStartTime=function(){this._audioSpriteStopTime=.001*(this._startTime+this._duration),this.playState==createjs.Sound.PLAY_SUCCEEDED&&(this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED,this._endedHandler,!1),this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE,this._audioSpriteEndHandler,!1))},a._updateDuration=function(){this._audioSpriteStopTime=.001*(this._startTime+this._duration),this.playState==createjs.Sound.PLAY_SUCCEEDED&&(this._playbackResource.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED,this._endedHandler,!1),this._playbackResource.addEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE,this._audioSpriteEndHandler,!1))},createjs.HTMLAudioSoundInstance=createjs.promote(HTMLAudioSoundInstance,"AbstractSoundInstance")}(),this.createjs=this.createjs||{},function(){"use strict";function HTMLAudioPlugin(){this.AbstractPlugin_constructor(),this.defaultNumChannels=2,this._capabilities=b._capabilities,this._loaderClass=createjs.SoundLoader,this._soundInstanceClass=createjs.HTMLAudioSoundInstance}var a=createjs.extend(HTMLAudioPlugin,createjs.AbstractPlugin),b=HTMLAudioPlugin;b.MAX_INSTANCES=30,b._AUDIO_READY="canplaythrough",b._AUDIO_ENDED="ended",b._AUDIO_SEEKED="seeked",b._AUDIO_STALLED="stalled",b._TIME_UPDATE="timeupdate",b._capabilities=null,b.isSupported=function(){return b._generateCapabilities(),null!=b._capabilities},b._generateCapabilities=function(){if(null==b._capabilities){var a=document.createElement("audio");if(null==a.canPlayType)return null;b._capabilities={panning:!1,volume:!0,tracks:-1};for(var c=createjs.Sound.SUPPORTED_EXTENSIONS,d=createjs.Sound.EXTENSION_MAP,e=0,f=c.length;f>e;e++){var g=c[e],h=d[g]||g;b._capabilities[g]="no"!=a.canPlayType("audio/"+g)&&""!=a.canPlayType("audio/"+g)||"no"!=a.canPlayType("audio/"+h)&&""!=a.canPlayType("audio/"+h)}}},a.register=function(a){var b=createjs.HTMLAudioTagPool.get(a.src),c=this.AbstractPlugin_register(a);return c.setTag(b),c},a.removeSound=function(a){this.AbstractPlugin_removeSound(a),createjs.HTMLAudioTagPool.remove(a)},a.create=function(a,b,c){var d=this.AbstractPlugin_create(a,b,c);return d.setPlaybackResource(null),d},a.toString=function(){return"[HTMLAudioPlugin]"},a.setVolume=a.getVolume=a.setMute=null,createjs.HTMLAudioPlugin=createjs.promote(HTMLAudioPlugin,"AbstractPlugin")}(); \ No newline at end of file