diff --git a/vendor/scripts/soundjs-NEXT.combined.js b/vendor/scripts/soundjs-NEXT.combined.js index 346983e77..0af1f4246 100644 --- a/vendor/scripts/soundjs-NEXT.combined.js +++ b/vendor/scripts/soundjs-NEXT.combined.js @@ -1,6 +1,9 @@ -/** - * @module SoundJS - */ + + +//############################################################################## +// version.js +//############################################################################## + this.createjs = this.createjs || {}; (function () { @@ -19,7 +22,7 @@ this.createjs = this.createjs || {}; * @type String * @static **/ - s.version = /*version*/"NEXT"; // injected by build process + s.version = /*=version*/""; // injected by build process /** * The build date for this release in UTC format. @@ -27,103 +30,366 @@ this.createjs = this.createjs || {}; * @type String * @static **/ - s.buildDate = /*date*/"Mon, 27 Oct 2014 20:40:07 GMT"; // injected by build process + s.buildDate = /*=date*/""; // injected by build process })(); -/* -* EventDispatcher -* 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. -*/ + +//############################################################################## +// extend.js +//############################################################################## + +this.createjs = this.createjs||{}; /** - * @module CreateJS + * @class Utility Methods */ -// namespace: +/** + * 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 extends + * @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; +}; + +//############################################################################## +// 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; +}; + +//############################################################################## +// 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. + * + * <h4>Example</h4> + * + * 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. + * + * <h4>Example</h4> + * + * 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)); + }; + } + +}()); + +//############################################################################## +// definePropertySupported.js +//############################################################################## + +this.createjs = this.createjs||{}; + +/** + * @class Utility Methods + */ +(function() { + "use strict"; + + /** + * Boolean value indicating if Object.defineProperty is supported. + * + * <h4>Example</h4> + * + * if (createjs.definePropertySupported) { // add getter / setter} + * + * @property definePropertySupported + * @type {Boolean} + * @default true + */ + var t = Object.defineProperty ? true : false; + + // IE8 has Object.defineProperty, but only for DOM objects, so check if fails to suppress errors + var foo = {}; + try { + Object.defineProperty(foo, "bar", { + get: function () { + return this._bar; + }, + set: function (value) { + this._bar = value; + } + }); + } catch (e) { + t = false; + } + + createjs.definePropertySupported = t; +}()); + +//############################################################################## +// 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. + * + * <h4>Example</h4> + * + * 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; + +}()); + +//############################################################################## +// EventDispatcher.js +//############################################################################## + this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * EventDispatcher provides methods for managing queues of event listeners and dispatching events. - * - * You can either extend EventDispatcher or mix its methods into an existing prototype or instance by using the - * EventDispatcher {{#crossLink "EventDispatcher/initialize"}}{{/crossLink}} method. - * - * Together with the CreateJS Event class, EventDispatcher provides an extended event model that is based on the - * DOM Level 2 event model, including addEventListener, removeEventListener, and dispatchEvent. It supports - * bubbling / capture, preventDefault, stopPropagation, stopImmediatePropagation, and handleEvent. - * - * EventDispatcher also exposes a {{#crossLink "EventDispatcher/on"}}{{/crossLink}} method, which makes it easier - * to create scoped listeners, listeners that only run once, and listeners with associated arbitrary data. The - * {{#crossLink "EventDispatcher/off"}}{{/crossLink}} method is merely an alias to - * {{#crossLink "EventDispatcher/removeEventListener"}}{{/crossLink}}. - * - * Another addition to the DOM Level 2 model is the {{#crossLink "EventDispatcher/removeAllEventListeners"}}{{/crossLink}} - * method, which can be used to listeners for all events, or listeners for a specific event. The Event object also - * includes a {{#crossLink "Event/remove"}}{{/crossLink}} method which removes the active listener. - * - * <h4>Example</h4> - * Add EventDispatcher capabilities to the "MyClass" class. - * - * EventDispatcher.initialize(MyClass.prototype); - * - * Add an event (see {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}}). - * - * instance.addEventListener("eventName", handlerMethod); - * function handlerMethod(event) { - * console.log(event.target + " Was Clicked"); - * } - * - * <b>Maintaining proper scope</b><br /> - * Scope (ie. "this") can be be a challenge with events. Using the {{#crossLink "EventDispatcher/on"}}{{/crossLink}} - * method to subscribe to events simplifies this. - * - * instance.addEventListener("click", function(event) { - * console.log(instance == this); // false, scope is ambiguous. - * }); - * - * instance.on("click", function(event) { - * console.log(instance == this); // true, "on" uses dispatcher scope by default. - * }); - * - * If you want to use addEventListener instead, you may want to use function.bind() or a similar proxy to manage scope. - * - * - * @class EventDispatcher - * @constructor - **/ -var EventDispatcher = function() { -/* this.initialize(); */ // not needed. -}; -var p = EventDispatcher.prototype; -EventDispatcher.prototype.constructor = EventDispatcher; + +// constructor: + /** + * EventDispatcher provides methods for managing queues of event listeners and dispatching events. + * + * You can either extend EventDispatcher or mix its methods into an existing prototype or instance by using the + * EventDispatcher {{#crossLink "EventDispatcher/initialize"}}{{/crossLink}} method. + * + * Together with the CreateJS Event class, EventDispatcher provides an extended event model that is based on the + * DOM Level 2 event model, including addEventListener, removeEventListener, and dispatchEvent. It supports + * bubbling / capture, preventDefault, stopPropagation, stopImmediatePropagation, and handleEvent. + * + * EventDispatcher also exposes a {{#crossLink "EventDispatcher/on"}}{{/crossLink}} method, which makes it easier + * to create scoped listeners, listeners that only run once, and listeners with associated arbitrary data. The + * {{#crossLink "EventDispatcher/off"}}{{/crossLink}} method is merely an alias to + * {{#crossLink "EventDispatcher/removeEventListener"}}{{/crossLink}}. + * + * Another addition to the DOM Level 2 model is the {{#crossLink "EventDispatcher/removeAllEventListeners"}}{{/crossLink}} + * method, which can be used to listeners for all events, or listeners for a specific event. The Event object also + * includes a {{#crossLink "Event/remove"}}{{/crossLink}} method which removes the active listener. + * + * <h4>Example</h4> + * Add EventDispatcher capabilities to the "MyClass" class. + * + * EventDispatcher.initialize(MyClass.prototype); + * + * Add an event (see {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}}). + * + * instance.addEventListener("eventName", handlerMethod); + * function handlerMethod(event) { + * console.log(event.target + " Was Clicked"); + * } + * + * <b>Maintaining proper scope</b><br /> + * Scope (ie. "this") can be be a challenge with events. Using the {{#crossLink "EventDispatcher/on"}}{{/crossLink}} + * method to subscribe to events simplifies this. + * + * instance.addEventListener("click", function(event) { + * console.log(instance == this); // false, scope is ambiguous. + * }); + * + * instance.on("click", function(event) { + * console.log(instance == this); // true, "on" uses dispatcher scope by default. + * }); + * + * If you want to use addEventListener instead, you may want to use function.bind() or a similar proxy to manage scope. + * + * + * @class EventDispatcher + * @constructor + **/ + function EventDispatcher() { + + + // private properties: + /** + * @protected + * @property _listeners + * @type Object + **/ + this._listeners = null; + + /** + * @protected + * @property _captureListeners + * @type Object + **/ + this._captureListeners = null; + } + var p = EventDispatcher.prototype; +// static public methods: /** * Static initializer to mix EventDispatcher methods into a target object or prototype. * @@ -146,30 +412,6 @@ EventDispatcher.prototype.constructor = EventDispatcher; target.willTrigger = p.willTrigger; }; -// constructor: - -// private properties: - /** - * @protected - * @property _listeners - * @type Object - **/ - p._listeners = null; - - /** - * @protected - * @property _captureListeners - * @type Object - **/ - p._captureListeners = null; - -// constructor: - /** - * Initialization method. - * @method initialize - * @protected - **/ - p.initialize = function() {}; // public methods: /** @@ -398,6 +640,7 @@ EventDispatcher.prototype.constructor = EventDispatcher; return "[EventDispatcher]"; }; + // private methods: /** * @method _dispatchEvent @@ -427,209 +670,154 @@ EventDispatcher.prototype.constructor = EventDispatcher; }; -createjs.EventDispatcher = EventDispatcher; + createjs.EventDispatcher = EventDispatcher; }()); -/* -* Event -* 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. -*/ -/** - * A collection of Classes that are shared across all the CreateJS libraries. The classes are included in the minified - * files of each library and are available on the createsjs namespace directly. - * - * <h4>Example</h4> - * - * myObject.addEventListener("change", createjs.proxy(myMethod, scope)); - * - * @module CreateJS - * @main CreateJS - */ +//############################################################################## +// Event.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * 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 - **/ -var Event = function(type, bubbles, cancelable) { - this.initialize(type, bubbles, cancelable); -}; -var p = Event.prototype; -Event.prototype.constructor = Event; - -// events: - -// public properties: - - /** - * The type of event. - * @property type - * @type String - **/ - p.type = null; - - /** - * The object that generated an event. - * @property target - * @type Object - * @default null - * @readonly - */ - p.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 - */ - p.currentTarget = null; - - /** - * For bubbling events, this indicates the current event phase:<OL> - * <LI> capture phase: starting from the top parent to the target</LI> - * <LI> at target phase: currently being dispatched from the target</LI> - * <LI> bubbling phase: from the target to the top parent</LI> - * </OL> - * @property eventPhase - * @type Number - * @default 0 - * @readonly - */ - p.eventPhase = 0; - - /** - * Indicates whether the event will bubble through the display list. - * @property bubbles - * @type Boolean - * @default false - * @readonly - */ - p.bubbles = false; - - /** - * 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 - */ - p.cancelable = false; - - /** - * The epoch time at which this event was created. - * @property timeStamp - * @type Number - * @default 0 - * @readonly - */ - p.timeStamp = 0; - - /** - * Indicates if {{#crossLink "Event/preventDefault"}}{{/crossLink}} has been called - * on this event. - * @property defaultPrevented - * @type Boolean - * @default false - * @readonly - */ - p.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 - */ - p.propagationStopped = false; - - /** - * Indicates if {{#crossLink "Event/stopImmediatePropagation"}}{{/crossLink}} has been called - * on this event. - * @property immediatePropagationStopped - * @type Boolean - * @default false - * @readonly - */ - p.immediatePropagationStopped = false; - - /** - * Indicates if {{#crossLink "Event/remove"}}{{/crossLink}} has been called on this event. - * @property removed - * @type Boolean - * @default false - * @readonly - */ - p.removed = false; - // constructor: /** - * Initialization method. - * @method initialize + * 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. - * @protected + * @constructor **/ - p.initialize = function(type, bubbles, cancelable) { + function Event(type, bubbles, cancelable) { + + + // public properties: + /** + * The type of event. + * @property type + * @type String + **/ this.type = type; - this.bubbles = bubbles; - this.cancelable = cancelable; + + /** + * 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:<OL> + * <LI> capture phase: starting from the top parent to the target</LI> + * <LI> at target phase: currently being dispatched from the target</LI> + * <LI> bubbling phase: from the target to the top parent</LI> + * </OL> + * @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; + // public methods: - /** * Sets {{#crossLink "Event/defaultPrevented"}}{{/crossLink}} to true. * Mirrors the DOM event standard. * @method preventDefault **/ p.preventDefault = function() { - this.defaultPrevented = true; + this.defaultPrevented = this.cancelable&&true; }; /** @@ -695,321 +883,1908 @@ Event.prototype.constructor = Event; return "[Event (type="+this.type+")]"; }; -createjs.Event = Event; + createjs.Event = Event; }()); -/* -* IndexOf -* 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. -*/ -/** - * @module CreateJS - */ +//############################################################################## +// ProgressEvent.js +//############################################################################## -// namespace: -this.createjs = this.createjs||{}; - -/** - * @class Utility Methods - */ -(function() { - "use strict"; - - /* - * Employs Duff's Device to make a more performant implementation of indexOf. - * see http://jsperf.com/duffs-indexof/2 - * #method indexOf - * @param {Array} array Array to search for searchElement - * @param searchElement Element to search array for. - * @return {Number} The position of the first occurrence of a specified value searchElement in the passed in array ar. - * @constructor - */ - /* replaced with simple for loop for now, perhaps will be researched further - createjs.indexOf = function (ar, searchElement) { - var l = ar.length; - - var n = (l * 0.125) ^ 0; // 0.125 == 1/8, using multiplication because it's faster in some browsers // ^0 floors result - for (var i = 0; i < n; i++) { - if(searchElement === ar[i*8]) { return (i*8);} - if(searchElement === ar[i*8+1]) { return (i*8+1);} - if(searchElement === ar[i*8+2]) { return (i*8+2);} - if(searchElement === ar[i*8+3]) { return (i*8+3);} - if(searchElement === ar[i*8+4]) { return (i*8+4);} - if(searchElement === ar[i*8+5]) { return (i*8+5);} - if(searchElement === ar[i*8+6]) { return (i*8+6);} - if(searchElement === ar[i*8+7]) { return (i*8+7);} - } - - var n = l % 8; - for (var i = 0; i < n; i++) { - if (searchElement === ar[l-n+i]) { - return l-n+i; - } - } - - return -1; - } - */ - - /** - * 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){ - for (var i = 0,l=array.length; i < l; i++) { - if (searchElement === array[i]) { - return i; - } - } - return -1; - } - -}());/* -* Proxy -* 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. -*/ - -/** - * @module CreateJS - */ - -// namespace: -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: - * - * <h4>Example</h4> - * 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. - * - * <h4>Example</h4> - * 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)); - }; - } - -}());/* -* defineProperty -* 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. -*/ - -/** - * @module CreateJS - */ - -// namespace: -this.createjs = this.createjs||{}; - -/** - * @class Utility Methods - */ -(function() { - "use strict"; - - /** - * Boolean value indicating if Object.defineProperty is supported. - * - * @property definePropertySupported - * @type {Boolean} - * @default true - */ - var t = Object.defineProperty ? true : false; - - // IE8 has Object.defineProperty, but only for DOM objects, so check if fails to suppress errors - var foo = {}; - try { - Object.defineProperty(foo, "bar", { - get: function() { - return this._bar; - }, - set: function(value) { - this._bar = value; - } - }); - } catch (e) { - t = false; - }; - - createjs.definePropertySupported = t; -}());/* - * Sound - * Visit http://createjs.com/ for documentation, updates and examples. - * - * - * Copyright (c) 2012 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. - */ - - -// namespace: this.createjs = this.createjs || {}; -/** - * The SoundJS library manages the playback of audio on the web. It works via plugins which abstract the actual audio - * implementation, so playback is possible on any platform without specific knowledge of what mechanisms are necessary - * to play sounds. - * - * To use SoundJS, use the public API on the {{#crossLink "Sound"}}{{/crossLink}} class. This API is for: - * <ul><li>Installing audio playback Plugins</li> - * <li>Registering (and preloading) sounds</li> - * <li>Creating and playing sounds</li> - * <li>Master volume, mute, and stop controls for all sounds at once</li> - * </ul> - * - * <b>Controlling Sounds</b><br /> - * Playing sounds creates {{#crossLink "SoundInstance"}}{{/crossLink}} instances, which can be controlled individually. - * <ul><li>Pause, resume, seek, and stop sounds</li> - * <li>Control a sound's volume, mute, and pan</li> - * <li>Listen for events on sound instances to get notified when they finish, loop, or fail</li> - * </ul> - * - * <h4>Feature Set Example</h4> - * createjs.Sound.alternateExtensions = ["mp3"]; - * createjs.Sound.addEventListener("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 sourcepath or event.src. - * instance.addEventListener("complete", createjs.proxy(this.handleComplete, this)); - * instance.volume = 0.5; - * } - * - * <h4>Browser Support</h4> - * Audio will work in browsers which support WebAudio (<a href="http://caniuse.com/audio-api">http://caniuse.com/audio-api</a>) - * or HTMLAudioElement (<a href="http://caniuse.com/audio">http://caniuse.com/audio</a>). A Flash fallback can be added - * as well, which will work in any browser that supports the Flash player. - * @module SoundJS - * @main SoundJS - */ +(function (scope) { + "use strict"; + + /** + * A createjs Event 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] The total amount that will load. This will default to 0, so does not need to be passed in, + * as long as the loaded value is a progress value (between 0 and 1). + * @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. + * @oroperty size + * @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 : loaded / 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"; + + /** + * @class LoadItem + * + * @constructor + */ + function LoadItem() { + /** + * The source of the file that is being loaded. This property is <b>required</b>. The source can + * either be a string (recommended), or an HTML tag.</li> + * + * @type {null} + */ + this.src = null; + + /** + * The source of the file that is being loaded. This property is <b>required</b>. The source can + * either be a string (recommended), or an HTML tag. + * + * Check {{#crossLink "DataTypes"}}DataTypes{{/crossLink}} for the full list of supported types. + * + * @type {String|HTMLMediaElement|HTMLImageElement|HTMLLinkElement} + */ + this.type = createjs.AbstractLoader.TEXT; + + /** + * A string identifier which can be used to reference the loaded object. + * + * @type {String|Number} + */ + this.id = null; + + /** + * Set to `true` to ensure this asset loads in the order defined in the manifest. This + * will happen when the max connections has been set above 1 (using {{#crossLink "LoadQueue/setMaxConnections"}}{{/crossLink}}), + * and will only affect other assets also defined as `maintainOrder`. Everything else 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`. + * + * @type {boolean} + */ + this.maintainOrder = false; + + /** + * Optional, used for JSONP requests, to define what method to call when the JSONP is loaded. + * + * @type {String} + */ + this.callback = null; + + /** + * An arbitrary data object, which is included with the loaded object + * + * @type {Object} + */ + this.data = null; + + /** + * uUsed to define if this request uses GET or POST when sending data to the server. The default value is "GET" + * + * @type {String} + */ + this.method = createjs.LoadItem.GET; + + /** + * Optional object of name/value pairs to send to the server. + * + * @type {Object} + */ + this.values = null; + + /** + * Optional 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 if needed. + * + * @type {Object} + */ + this.headers = null; + + /** + * Default false; Set to true if you need to enable credentials for XHR requests. + * + * @type {boolean} + */ + this.withCredentials = false; + + /** + * String, Default for text bases files (json, xml, text, css, js) "text/plain; charset=utf-8"; Sets the mime type of XHR. + * + * @type {String} + */ + this.mimeType = null; + + /** + * Sets the crossorigin attribute on images. + * + * @default Anonymous + * + * @type {boolean} + */ + this.crossOrigin = "Anonymous"; + + /** + * how long before we stop a request. Only applies to Tag loading and XHR level one loading. + * + * @type {number} + */ + this.loadTimeout = 8000; + }; + + var p = LoadItem.prototype = {}; + var s = LoadItem; + + 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) { // Don't modify object, allows users to attach random data to the item. + return value; + } else { + throw new Error("Type not recognized."); + } + }; + + /** + * Provides a chainable shortcut method for setting a number of properties on the instance. + * + * <h4>Example</h4> + * + * 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 () { + var s = {}; + + /** + * The Regular Expression used to test file URLS for an absolute path. + * @property ABSOLUTE_PATH + * @static + * @type {RegExp} + * @since 0.4.2 + */ + s.ABSOLUTE_PATT = /^(?:\w+:)?\/{2}/i; + + /** + * The Regular Expression used to test file URLS for an absolute path. + * @property RELATIVE_PATH + * @static + * @type {RegExp} + * @since 0.4.2 + */ + 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 + * @static + * @type {RegExp} + * @since 0.4.2 + */ + s.EXTENSION_PATT = /\/?[^/]+\.(\w{1,5})$/i; + + /** + * @method _parseURI + * Parse a file path to determine the information we need to work with it. Currently, PreloadJS needs to know: + * <ul> + * <li>If the path is absolute. Absolute paths start with a protocol (such as `http://`, `file://`, or + * `//networkPath`)</li> + * <li>If the path is relative. Relative paths start with `../` or `/path` (or similar)</li> + * <li>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`.</li> + * </ul> + * + * <strong>Note:</strong> This has changed from earlier versions, which used a single, complicated Regular Expression, which + * was difficult to maintain, and over-aggressive in determining all file properties. It has been simplified to + * only pull out what it needs. + * @param path + * @returns {Object} An Object with an `absolute` and `relative` Boolean, as well as an optional 'extension` String + * property, which is the lowercase extension. + * @private + */ + 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. + * @private + */ + 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. All + * of the loaders in PreloadJS use this method to compile paths when loading. + * @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. + * @since 0.3.1 + */ + 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 {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. + * @private + */ + 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 {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. + * @private + */ + 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 <b>not</b> 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. + * @method isBinary + * @param {String} type The item type. + * @return {Boolean} If the specified type is binary. + * @private + */ + s.isBinary = function (type) { + switch (type) { + case createjs.AbstractLoader.IMAGE: + case createjs.AbstractLoader.BINARY: + return true; + default: + 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. + * @private + */ + 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.HTML: + case createjs.AbstractLoader.CSS: + case createjs.AbstractLoader.SVG: + case createjs.AbstractLoader.JAVASCRIPT: + 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. + * @param {String} extension The file extension to use to determine the load type. + * @return {String} The determined load type (for example, <code>AbstractLoader.IMAGE</code> or null if it can not be + * determined by the extension. + * @private + */ + 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 callbacks and events. All loaders extend this class, including the + * {{#crossLink "LoadQueue"}}{{/crossLink}}. + * @class AbstractLoader + * @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 <code>complete</code> event. + * @property loaded + * @type {Boolean} + * @default false + */ + this.loaded = false; + + /** + * Determine if the loader was canceled. Canceled loads will not fire complete events. Note that + * {{#crossLink "LoadQueue"}}{{/crossLink}} queues should be closed using {{#crossLink "AbstractLoader/close"}}{{/crossLink}} + * instead of setting this property. + * @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. + * + * <h4>Example</h4> + * + * 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 this item. + * See {{#crossLink}}DataTypes{{/crossLink}} for a full list of supported types. + * + * @type {null} + */ + this.type = type; + + // protected properties + /** + * The item 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 {Object} + * @private + */ + if (loadItem) { + this._item = createjs.LoadItem.create(loadItem); + } else { + this._item = null; + } + + this._preferXHR = preferXHR; + + 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 subitems include the {{#crossLink "SpriteSheetLoader"}}{{/crossLink}} and + * {{#crossLink "ManifestLoader"}}{{/crossLink}}. + * @property _loadItems + * @type {null} + * @protected + */ + this._loadedItems = null; + }; + + var p = createjs.extend(AbstractLoader, createjs.EventDispatcher); + var s = AbstractLoader; + + /** + * Defines a POST request, use for a method value when loading data. + * + * @type {string} + */ + s.POST = 'POST'; + + /** + * Defines a GET request, use for a method value when loading data. + * + * @type {string} + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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.4.1 + */ + 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 + */ + 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 + */ + s.VIDEO = "video"; + + /** + * The preload type for SVG files. + * @property SVG + * @type {String} + * @default svg + * @static + */ + 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 + */ + s.TEXT = "text"; + + /** + * The preload type for xml files. XML is loaded into an XML document. + * @property XML + * @type {String} + * @default xml + * @static + */ + s.XML = "xml"; + +// Events + /** + * The event that is fired when the overall progress changes. + * @event progress + * @param {Object} target The object that dispatched the event. + * @param {String} type The event type. + * @param {Number} loaded The amount that has been loaded so far. Note that this is may just be a percentage of 1, + * since file sizes can not be determined before a load is kicked off, if at all. + * @param {Number} total The total number of bytes. Note that this may just be 1. + * @param {Number} progress The ratio that has been loaded between 0 and 1. + * @since 0.3.0 + */ + + /** + * The event 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 event 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 event 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. There may be additional properties such as the error reason on event + * objects. + * @event error + * @param {Object} target The object that dispatched the event. + * @param {String} type The event type. + * @param {Object} [item] The item that was being loaded that caused the error. The item 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 {String} [error] The error object or text. + * @since 0.3.0 + */ + + /** + * Dispatched after our XHRRequest is created, but before a load. + * Allows updates to the loader for specific loading needs (ex, Binary loading, or XHR image loading.) + * + * @event initialize + * @param {Object} target The object that dispatched the event. + * @param {String} type The event type. + * @param {AbstractLoader} loader The loader that has been initialized. + */ + + //TODO: Deprecated + /** + * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "AbstractLoader/progress:event"}}{{/crossLink}} + * event. + * @property onProgress + * @type {Function} + * @deprecated Use addEventListener and the "progress" event. + */ + /** + * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "AbstractLoader/loadstart:event"}}{{/crossLink}} + * event. + * @property onLoadStart + * @type {Function} + * @deprecated Use addEventListener and the "loadstart" event. + */ + /** + * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "AbstractLoader/complete:event"}}{{/crossLink}} + * event. + * @property onComplete + * @type {Function} + * @deprecated Use addEventListener and the "complete" event. + */ + /** + * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "AbstractLoader/error:event"}}{{/crossLink}} + * event. + * @property onError + * @type {Function} + * @deprecated Use addEventListener and the "error" event. + */ + + /** + * Get a reference to the manifest item that is loaded by this loader. In most 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 an Object created by the LoadQueue. + * @return {Object} The manifest item that this loader is responsible for loading. + */ + p.getItem = function () { + return this._item; + }; + + p.getResult = function (raw) { + return raw ? this._rawResult : this._result; + }; + + p.getTag = function () { + return this._tag; + }; + + p.setTag = function(tag) { + this._tag = tag; + }; + + /** + * Begin loading the queued items. This method can be called when a {{#crossLink "LoadQueue"}}{{/crossLink}} is set + * up but not started immediately. + * @example + * var queue = new createjs.LoadQueue(); + * queue.addEventListener("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(); + }; + + p._createRequest = function() { + if (!this._preferXHR) { + this._request = new createjs.TagRequest(this._item, false, this._tag || this._createTag(), this._tagSrcAttribute); + } else { + this._request = new createjs.XHRRequest(this._item, false); + } + }; + + /** + * Close the active queue. Closing a queue completely empties the queue, and prevents any remaining items from + * starting to download. Note that currently any active loads will remain open, and events may be processed. + * + * To stop and restart a queue, use the {{#crossLink "LoadQueue/setPaused"}}{{/crossLink}} method instead. + * @method close + */ + p.close = function () { + + }; + + /** + * + */ + p.cancel = function () { + this.canceled = true; + this.destroy(); + }; + + /** + * Remove all references to this loader. + * + */ + 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.removeAllEventListeners(); + }; + + /** + * Get any items loaded internally by the loader. + * @method getLoadedItems + * @returns {Array} A list of the items loaded by the loader. + */ + p.getLoadedItems = function () { + return this._loadedItems; + }; + +// Callback proxies + /** + * Dispatch a loadstart event. 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 progress event. Please see the {{#crossLink "AbstractLoader/progress:event"}}{{/crossLink}} event for + * details on the event payload. + * @method _sendProgress + * @param {Number | Object} value The progress of the loaded item, or an object containing <code>loaded</code> + * and <code>total</code> properties. + * @protected + */ + p._sendProgress = function (value) { + if (this._isCanceled()) { return; } + var event = null; + if (typeof(value) == "number") { + this.progress = value; + event = new createjs.ProgressEvent(); + event.loaded = this.progress; + event.total = 1; + } else { + event = value; + this.progress = value.loaded / value.total; + if (isNaN(this.progress) || this.progress == Infinity) { this.progress = 0; } + } + event.progress = this.progress; + this.hasEventListener("progress") && this.dispatchEvent(event); + }; + + /** + * Dispatch a complete event. Please see the {{#crossLink "AbstractLoader/complete:event"}}{{/crossLink}} event + * @method _sendComplete + * @protected + */ + p._sendComplete = function () { + if (this._isCanceled()) { return; } + + var event = new createjs.Event("complete"); + event.rawResult = this._rawResult; + + if (this._result != null) { + event.result = this._result; + } + + this.dispatchEvent(event); + }; + + /** + * Dispatch an error event. Please see the {{#crossLink "AbstractLoader/error:event"}}{{/crossLink}} event for + * details on the event payload. + * @method _sendError + * @param {Object} 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.Event("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; + }; + + /** + * Optional; Called just before a request dispatches its complete event. + * Allows plugins to set a custom result value. + * Will be passed a single loader parameter, which is the current loader in use. + * + * @type Function + * @returns {Object} + * @private + */ + p.resultFormatter = null; + + p.handleEvent = function (event) { + switch (event.type) { + case "complete": + this._rawResult = event.target._response; + this._result = this.resultFormatter && this.resultFormatter(this) || 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; + } + }; + + /** + * @deprecated Prefer RequestUtils.buildPath instead of this method. + */ + 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 class description goes here. + * + */ + function AbstractMediaLoader(loadItem, preferXHR, type) { + this.AbstractLoader_constructor(loadItem, preferXHR, type); + + // public properties + + // protected properties + this._tagSrcAttribute = "src"; + + /** + * Used to determine what type of tag to create, for example "audio" + * @property _tagType + * @type {string} + * @private + */ + this._tagType = type; + + this.resultFormatter = this._formatResult; + }; + + var p = createjs.extend(AbstractMediaLoader, createjs.AbstractLoader); + // static properties + + // public methods + + // protected 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(); + }; + + /** + * Abstract, create a new tag if none exist. + * + * @private + */ + p._createTag = function () { + + }; + + 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"; + + var AbstractRequest = function (item) { + this._item = item; + }; + + var p = createjs.extend(AbstractRequest, createjs.EventDispatcher); + var s = AbstractRequest; + + /** + * Abstract function. + * + */ + p.load = function() { + + }; + + p.destroy = function() { + + }; + + createjs.AbstractRequest = createjs.promote(AbstractRequest, "EventDispatcher"); + +}()); + +//############################################################################## +// TagRequest.js +//############################################################################## + +this.createjs = this.createjs || {}; + +(function () { + "use strict"; + + // constructor + /** + * The TagRequest class description goes here. + * + */ + function TagRequest(loadItem, preferXHR, tag, srcAttribute) { + this.AbstractRequest_constructor(loadItem, preferXHR); + + // public properties + + // protected properties + this._tag = tag; + this._tagSrcAttribute = srcAttribute; + + this._loadedHandler = createjs.proxy(this._handleTagComplete, this); + }; + + var p = createjs.extend(TagRequest, createjs.AbstractRequest); + var s = TagRequest; + + p.load = function () { + window.document.body.appendChild(this._tag); + + this._tag.onload = createjs.proxy(this._handleTagComplete, this); + this._tag.onreadystatechange = createjs.proxy(this._handleReadyStateChange, this); + + var evt = new createjs.Event("initialize"); + evt.loader = this._tag; + + this.dispatchEvent(evt); + + this._tag[this._tagSrcAttribute] = this._item.src; + }; + + p.destroy = function() { + this._tag.onreadystatechange = null; + this._tag.onload = null; + + this._tag = null; + + this.AbstractRequest_destory(); + }; + + /** + * Handle the readyStateChange event from a tag. We sometimes need this in place of the onload event (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(); + } + }; + + p._handleTagComplete = function () { + this._tag.onload = null; + this._tag.onreadystatechange = null; + this._rawResult = this._tag; + this._result = this.resultFormatter && this.resultFormatter(this) || this._rawResult; + + this.dispatchEvent("complete"); + }; + + /** + * Handle a stalled audio event. The main place we seem to get these is with HTMLAudio in Chrome when we try and + * playback 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 + /** + * The TagRequest class description goes here. + * + */ + function MediaTagRequest(loadItem, preferXHR, tag, srcAttribute) { + this.AbstractRequest_constructor(loadItem, preferXHR); + + // public properties + + // 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; + + p.load = function () { + this._tag.onstalled = createjs.proxy(this._handleStalled, this); + this._tag.onprogress = createjs.proxy(this._handleProgress, this); + + // 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); // canplaythrough callback doesn't work in Chrome, so we use an event. + + this.TagRequest_load(); + }; + + p.destroy = function() { + this._tag.addEventListener && this._tag.removeEventListener("canplaythrough", this._loadedHandler); + this._tag.onstalled = null; + this._tag.onprogress = null; + + this.TagRequest_destory(); + }; + + /** + * Handle the readyStateChange event from a tag. We sometimes need this in place of the onload event (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 a stalled audio event. The main place we seem to get these is with HTMLAudio in Chrome when we try and + * playback 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. + }; + + /** + * 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); + }; + + p._handleTagComplete = function () { + this._tag.removeEventListener && this._tag.removeEventListener("canplaythrough", this._loadedHandler); + this._tag.onstalled = null; + this._tag.onprogress = null; + this.TagRequest__handleTagComplete(); + }; + + + 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 <code>result</code> 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.AbstractLoader); + +// 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: + * <ul> + * <li>An image tag (<image />) for images</li> + * <li>A script tag for JavaScript (<script />). Note that scripts loaded with tags may be added to the + * HTML head.</li> + * <li>A style tag for CSS (<style />)</li> + * <li>Raw text for TEXT</li> + * <li>A formatted JavaScript object defined by JSON</li> + * <li>An XML document</li> + * <li>An binary arraybuffer loaded by XHR</li> + * </ul> + * 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 AbstractLoader + 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); + this._request.addEventListener("progress", this._handleProgressProxy); + this._request.addEventListener("abort", this._handleAbortProxy); + this._request.addEventListener("error",this._handleErrorProxy); + this._request.addEventListener("timeout", this._handleTimeoutProxy); + + // Note: We don't get onload in all browsers (earlier FF and IE). onReadyStateChange handles these. + this._request.addEventListener("load", this._handleLoadProxy); + this._request.addEventListener("readystatechange", this._handleReadyStateChangeProxy); + + // Set up a timeout if we don't have XHR2 + if (this._xhrLevel == 1) { + this._loadTimeout = setTimeout(createjs.proxy(this._handleTimeout, this), this.getItem().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) { + var event = new createjs.Event("error"); + event.error = error; + this._sendError(event); + } + }; + + p.setResponseType = function (type) { + this._request.responseType = type; + }; + + /** + * Get all the response headers from the XmlHttpRequest. + * + * <strong>From the docs:</strong> 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. + * + * <strong>From the docs:</strong> 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._sendLoadStart(); + }; + + /** + * The XHR request has reported an abort event. + * @method handleAbort + * @param {Object} event The XHR abort event. + * @private + */ + p._handleAbort = function (event) { + this._clean(); + var newEvent = new createjs.Event("error"); + newEvent.text = "XHR_ABORTED"; + this._sendError(newEvent); + }; + + /** + * The XHR request has reported an error event. + * @method _handleError + * @param {Object} event The XHR error event. + * @private + */ + p._handleError = function (event) { + this._clean(); + var newEvent = new createjs.Event("error"); + newEvent.error = event; + + this._sendError(newEvent); + }; + + /** + * 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 + * <code>request.readyState == 4</code>. 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; + + if (!this._checkError()) { + this._handleError(); + return; + } + + this._response = this._getResponse(); + this._clean(); + + this._sendComplete(); + }; + + /** + * The XHR request has timed out. This is called by the XHR request directly, or via a <code>setTimeout</code> + * 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(); + var newEvent = new createjs.Event("error"); + newEvent.text = "PRELOAD_TIMEOUT"; + newEvent.error = event; + + this._sendError(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 {Boolean} 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 false; + } + return true; + }; + + /** + * Validate the response. Different browsers have different approaches, some of which throw errors when accessed + * in other browsers. If there is no response, the <code>_response</code> 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. + * <ol><li>Some browsers get an <code>XDomainRequest</code> when loading cross-domain.</li> + * <li>XMLHttpRequest are created when available.</li> + * <li>ActiveX.XMLHTTP objects are used in older IE browsers.</li> + * <li>Text requests override the mime type if possible</li> + * <li>Origin headers are sent for crossdomain requests in some browsers.</li> + * <li>Binary loads set the response type to "arraybuffer"</li></ol> + * @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; } + } + + // 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 + /** + * The SoundLoader class description goes here. + * + */ + function SoundLoader(loadItem, preferXHR) { + this.AbstractMediaLoader_constructor(loadItem, preferXHR, createjs.AbstractLoader.SOUND); + + this._tagType = "audio"; + }; + + var p = createjs.extend(SoundLoader, createjs.AbstractMediaLoader); + var s = SoundLoader; + /** + * LoadQueue calls this when it creates loaders. + * Each loader has the option to say either yes (true) or no (false). + * + * @private + * @param item The LoadItem LoadQueue is trying to load. + * @returns {boolean} + */ + s.canLoadItem = function (item) { + return item.type == createjs.AbstractLoader.SOUND; + }; + + p._createRequest = function() { + if (!this._preferXHR) { + this._request = new createjs.MediaTagRequest(this._item, false, this._tag || this._createTag(), this._tagSrcAttribute); + } else { + this._request = new createjs.XHRRequest(this._item, false); + } + }; + + /** + * 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 (src) { + var tag = document.createElement(this._tagType); + 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"); + +}()); + +//############################################################################## +// Sound.js +//############################################################################## + +this.createjs = this.createjs || {}; + + + +(function () { "use strict"; /** @@ -1018,7 +2793,7 @@ this.createjs = this.createjs || {}; * * <b>Registering and Preloading</b><br /> * Before you can play a sound, it <b>must</b> be registered. You can do this with {{#crossLink "Sound/registerSound"}}{{/crossLink}}, - * or register multiple sounds using {{#crossLink "Sound/registerManifest"}}{{/crossLink}}. If you don't register a + * 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 * <a href="http://preloadjs.com" target="_blank">PreloadJS</a>, registration is handled for you when the sound is @@ -1027,18 +2802,18 @@ this.createjs = this.createjs || {}; * * <b>Playback</b><br /> * To play a sound once it's been registered and preloaded, use the {{#crossLink "Sound/play"}}{{/crossLink}} method. - * This method returns a {{#crossLink "SoundInstance"}}{{/crossLink}} which can be paused, resumed, muted, etc. - * Please see the {{#crossLink "SoundInstance"}}{{/crossLink}} documentation for more on the instance control APIs. + * 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. * * <b>Plugins</b><br /> * 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 "FlashPlugin"}}{{/crossLink}}). Please see the {{#crossLink "Sound"}}{{/crossLink}} API + * 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}}. * * <h4>Example</h4> - * createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.FlashPlugin]); + * createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.FlashAudioPlugin]); * createjs.Sound.alternateExtensions = ["mp3"]; * createjs.Sound.addEventListener("fileload", createjs.proxy(this.loadHandler, (this)); * createjs.Sound.registerSound("path/to/mySound.ogg", "sound"); @@ -1051,7 +2826,7 @@ this.createjs = this.createjs || {}; * * 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 FlashPlugin set a + * 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); @@ -1066,7 +2841,7 @@ this.createjs = this.createjs || {}; * queue.installPlugin(createjs.Sound); * * <b>Audio Sprites</b><br /> - * SoundJS has added support for Audio Sprites, available as of version 0.5.3. + * SoundJS has added support for Audio Sprites, 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. * @@ -1089,7 +2864,7 @@ this.createjs = this.createjs || {}; * <h4>Example</h4> * createjs.Sound.initializeDefaultPlugins(); * var assetsPath = "./assets/"; - * var manifest = [{ + * var sounds = [{ * src:"MyAudioSprite.ogg", data: { * audioSprite: [ * {id:"sound1", startTime:0, duration:500}, @@ -1100,11 +2875,11 @@ this.createjs = this.createjs || {}; * ]; * createjs.Sound.alternateExtensions = ["mp3"]; * createjs.Sound.addEventListener("fileload", loadSound); - * createjs.Sound.registerManifest(manifest, assetsPath); + * 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 SoundInstance. + * 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}); * @@ -1165,17 +2940,8 @@ this.createjs = this.createjs || {}; var s = Sound; - // TODO DEPRECATED - /** - * REMOVED - * Use {{#crossLink "Sound/alternateExtensions:property"}}{{/crossLink}} instead - * @property DELIMITER - * @type {String} - * @default | - * @static - * @deprecated - */ +// 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. @@ -1216,7 +2982,6 @@ this.createjs = this.createjs || {}; */ s.INTERRUPT_NONE = "none"; -// The playState in plugins should be implemented with these values. /** * Defines the playState of an instance that is still initializing. * @property PLAY_INITED @@ -1268,7 +3033,7 @@ this.createjs = this.createjs || {}; * 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 "FlashPlugin"}}{{/crossLink}}. + * NOTE this does not currently work for {{#crossLink "FlashAudioPlugin"}}{{/crossLink}}. * * More details on file formats can be found at <a href="http://en.wikipedia.org/wiki/Audio_file_format" target="_blank">http://en.wikipedia.org/wiki/Audio_file_format</a>.<br /> * A very detailed list of file formats can be found at <a href="http://www.fileinfo.com/filetypes/audio" target="_blank">http://www.fileinfo.com/filetypes/audio</a>. @@ -1304,6 +3069,8 @@ this.createjs = this.createjs || {}; */ 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}} @@ -1326,12 +3093,12 @@ this.createjs = this.createjs || {}; * 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. * <h4>Example</h4> - * var manifest = [ + * 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.addEventListener("fileload", handleLoad); // call handleLoad when each sound loads - * createjs.Sound.registerManifest(manifest, assetPath); + * 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 * @@ -1341,15 +3108,6 @@ this.createjs = this.createjs || {}; */ s.alternateExtensions = []; - /** - * Used internally to assign unique IDs to each SoundInstance. - * @property _lastID - * @type {Number} - * @static - * @protected - */ - s._lastID = 0; - /** * 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 @@ -1360,6 +3118,8 @@ this.createjs = this.createjs || {}; */ s.activePlugin = null; + +// 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}}). @@ -1372,6 +3132,15 @@ this.createjs = this.createjs || {}; */ s._pluginsRegistered = false; + /** + * Used internally to assign unique IDs to each AbstractSoundInstance. + * @property _lastID + * @type {Number} + * @static + * @protected + */ + s._lastID = 0; + /** * The master volume value, which affects all sounds. Use {{#crossLink "Sound/getVolume"}}{{/crossLink}} and * {{#crossLink "Sound/setVolume"}}{{/crossLink}} to modify the volume of all audio. @@ -1428,19 +3197,8 @@ this.createjs = this.createjs || {}; */ s._preloadHash = {}; - /** - * An object that stands in for audio that fails to play. This allows developers to continue to call methods - * on the failed instance without having to check if it is valid first. The instance is instantiated once, and - * shared to keep the memory footprint down. - * @property _defaultSoundInstance - * @type {Object} - * @protected - * @static - */ - s._defaultSoundInstance = null; -// mix-ins: - // EventDispatcher methods: +// EventDispatcher methods: s.addEventListener = null; s.removeEventListener = null; s.removeAllEventListeners = null; @@ -1465,32 +3223,19 @@ this.createjs = this.createjs || {}; */ /** - * Used by external plugins to dispatch file load events. - * @method _sendFileLoadEvent - * @param {String} src A sound file has completed loading, and should be dispatched. - * @protected - * @static - * @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 <code>event.src</code> 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 */ - s._sendFileLoadEvent = function (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); - } - }; +// Class Public Methods /** * Get the preload rules to allow Sound to be used as a plugin by <a href="http://preloadjs.com" target="_blank">PreloadJS</a>. * Any load calls that have the matching type or extension will fire the callback method, and use the resulting @@ -1513,6 +3258,60 @@ this.createjs = this.createjs || {}; }; }; + /** + * 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 + */ + 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. * @@ -1535,8 +3334,8 @@ this.createjs = this.createjs || {}; * Register a list of Sound plugins, in order of precedence. To register a single plugin, pass a single element in the array. * * <h4>Example</h4> - * createjs.FlashPlugin.swfPath = "../src/SoundJS/"; - * createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin, createjs.FlashPlugin]); + * 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. @@ -1579,8 +3378,8 @@ this.createjs = this.createjs || {}; * This example sets up a Flash fallback, but only if there is no plugin specified yet. * * if (!createjs.Sound.isReady()) { - * createjs.FlashPlugin.swfPath = "../src/SoundJS/"; - * createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin, createjs.FlashPlugin]); + * createjs.FlashAudioPlugin.swfPath = "../src/soundjs/flashaudio/"; + * createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin, createjs.FlashAudioPlugin]); * } * * @method isReady @@ -1676,7 +3475,7 @@ this.createjs = this.createjs || {}; * Returns true if the source is already loaded. * @static * @private - * @since 0.5.3 + * @since 0.6.0 */ s._registerSound = function (src, id, data) { @@ -1716,8 +3515,8 @@ this.createjs = this.createjs || {}; details.data.channels = numChannels || SoundChannel.maxPerChannel(); } - details.tag = loader.tag; - if (loader.completeHandler) {details.completeHandler = loader.completeHandler;} + details.loader = loader; + if (loader.onload) {details.completeHandler = loader.onload;} // used by preloadJS if (loader.type) {details.type = loader.type;} return details; @@ -1768,7 +3567,10 @@ this.createjs = this.createjs || {}; s._preloadHash[details.src].push({src:src, id:id, data:details.data}); if (s._preloadHash[details.src].length == 1) { // OJR note this will disallow reloading a sound if loading fails or the source changes - s.activePlugin.preload(details.src, details.tag); + var loader = details.loader; + loader.on("complete", createjs.proxy(this._handleLoadComplete, this)); + loader.on("error", createjs.proxy(this._handleLoadError, this)); + s.activePlugin.preload(details.loader); } else { if (s._preloadHash[details.src][0] == true) {return true;} } @@ -1777,22 +3579,52 @@ this.createjs = this.createjs || {}; }; /** - * Register a manifest of audio files for loading and future playback in Sound. It is recommended to register all + * 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. * * <h4>Example</h4> - * var manifest = [ + * var sounds = [ * {src:"asset0.ogg", id:"example"}, * {src:"asset1.ogg", id:"1", data:6}, * {src:"asset2.mp3", id:"works"} * ]; * createjs.Sound.alternateExtensions = ["mp3"]; // if the passed extension is not supported, try this extension * createjs.Sound.addEventListener("fileload", handleLoad); // call handleLoad when each sound loads - * createjs.Sound.registerManifest(manifest, assetPath); + * 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}}: <code>{src:srcURI, id:ID, data:Data}</code> + * with "id" and "data" being optional. You can also set an optional path property that will be prepended to the src of each object. + * @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; + } + } + 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); + } + return returnValues; + }; + + /** + * Deprecated. Please use {{#crossLink "Sound/registerSounds"}}{{/crossLink} instead. * * @method registerManifest - * @param {Array} manifest An array of objects to load. Objects are expected to be in the format needed for + * @param {Array} sounds An array of objects to load. Objects are expected to be in the format needed for * {{#crossLink "Sound/registerSound"}}{{/crossLink}}: <code>{src:srcURI, id:ID, data:Data}</code> * with "id" and "data" being optional. * @param {string} basePath Set a path that will be prepended to each src when loading. When creating, playing, or removing @@ -1800,20 +3632,21 @@ this.createjs = this.createjs || {}; * @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.4.0 - */ - s.registerManifest = function (manifest, basePath) { - var returnValues = []; - for (var i = 0, l = manifest.length; i < l; i++) { - returnValues[i] = createjs.Sound.registerSound(manifest[i].src, manifest[i].id, manifest[i].data, basePath); - } - return returnValues; + * @depreacted + */ + s.registerManifest = function(manifest, basePath) { + try { + console.log("createjs.Sound.registerManifest is deprecated, please use createjs.Sound.registerSounds.") + } catch (error) { + + }; + return this.registerSounds(manifest, basePath); }; /** * Remove a sound that has been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or - * {{#crossLink "Sound/registerManifest"}}{{/crossLink}}. + * {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. * <br />Note this will stop playback on active instances playing this sound before deleting them. * <br />Note if you passed in a basePath, you need to pass it or prepend it to the src here. * @@ -1856,18 +3689,46 @@ this.createjs = this.createjs || {}; }; /** - * Remove a manifest of audio files that have been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or - * {{#crossLink "Sound/registerManifest"}}{{/crossLink}}. + * Remove an array of audio files that have been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or + * {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. * <br />Note this will stop playback on active instances playing this audio before deleting them. * <br />Note if you passed in a basePath, you need to pass it or prepend it to the src here. * * <h4>Example</h4> - * var manifest = [ + * var sounds = [ * {src:"asset0.ogg", id:"example"}, * {src:"asset1.ogg", id:"1", data:6}, * {src:"asset2.mp3", id:"works"} * ]; - * createjs.Sound.removeManifest(manifest, assetPath); + * 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}}: <code>{srcOrID:srcURIorID}</code>. + * You can also set an optional path property that will be prepended to the src of each object. + * @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; + } + } + for (var i = 0, l = sounds.length; i < l; i++) { + returnValues[i] = createjs.Sound.removeSound(sounds[i].src, basePath); + } + return returnValues; + }; + + /** + * Deprecated. Please use {{#crossLink "Sound/removeSounds"}}{{/crossLink}} instead. * * @method removeManifest * @param {Array} manifest An array of objects to remove. Objects are expected to be in the format needed for @@ -1877,18 +3738,20 @@ this.createjs = this.createjs || {}; * successfully removed. * @static * @since 0.4.1 + * @deprecated */ s.removeManifest = function (manifest, basePath) { - var returnValues = []; - for (var i = 0, l = manifest.length; i < l; i++) { - returnValues[i] = createjs.Sound.removeSound(manifest[i].src, basePath); - } - return returnValues; + try { + console.log("createjs.Sound.removeManifest is deprecated, please use createjs.Sound.removeSounds."); + } catch (error) { + + }; + return s.removeSounds(manifest, basePath); }; /** * Remove all sounds that have been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or - * {{#crossLink "Sound/registerManifest"}}{{/crossLink}}. + * {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. * <br />Note this will stop playback on all active sound instances before deleting them. * * <h4>Example</h4> @@ -1932,7 +3795,7 @@ this.createjs = this.createjs || {}; }; /** - * Parse the path of a sound, usually from a manifest item. alternate extensions will be attempted in order if the + * 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. @@ -1964,18 +3827,18 @@ this.createjs = this.createjs || {}; Static API. --------------- */ /** - * Play a sound and get a {{#crossLink "SoundInstance"}}{{/crossLink}} to control. If the sound fails to play, a - * SoundInstance 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 SoundInstance {{#crossLink "SoundInstance/play"}}{{/crossLink}}, + * 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 SoundInstance will be returned which will not play any audio, but will not generate errors. + * if there is no available plugin, a default AbstractSoundInstance will be returned which will not play any audio, but will not generate errors. * * <h4>Example</h4> * createjs.Sound.addEventListener("fileload", handleLoad); * createjs.Sound.registerSound("myAudioPath/mySound.mp3", "myID", 3); * function handleLoad(event) { * createjs.Sound.play("myID"); - * // we can pass in options we want to set inside of an object, and store off SoundInstance for controlling + * // we can pass in options we want to set inside of an object, and store off AbstractSoundInstance for controlling * var myInstance = createjs.Sound.play("myID", {interrupt: createjs.Sound.INTERRUPT_ANY, loop:-1}); * // alternately, we can pass full source path and specify each argument individually * var myInstance = createjs.Sound.play("myAudioPath/mySound.mp3", createjs.Sound.INTERRUPT_ANY, 0, 0, -1, 1, 0); @@ -2001,7 +3864,7 @@ this.createjs = this.createjs || {}; * @param {Number} [pan=0] The left-right pan of the sound (if supported), between -1 (left) and 1 (right). * @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 {SoundInstance} A {{#crossLink "SoundInstance"}}{{/crossLink}} that can be controlled after it is created. + * @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) { @@ -2018,13 +3881,13 @@ this.createjs = this.createjs || {}; } var instance = s.createInstance(src, startTime, duration); var ok = s._playInstance(instance, interrupt, delay, offset, loop, volume, pan); - if (!ok) {instance.playFailed();} + if (!ok) {instance._playFailed();} return instance; }; /** - * Creates a {{#crossLink "SoundInstance"}}{{/crossLink}} using the passed in src. If the src does not have a - * supported extension or if there is no available plugin, a default SoundInstance will be returned that can be + * 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. * * <h4>Example</h4> @@ -2044,12 +3907,12 @@ this.createjs = this.createjs || {}; * @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 {SoundInstance} A {{#crossLink "SoundInstance"}}{{/crossLink}} that can be controlled after it is created. - * Unsupported extensions will return the default SoundInstance. + * @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 */ s.createInstance = function (src, startTime, duration) { - if (!s.initializeDefaultPlugins()) {return s._defaultSoundInstance;} + if (!s.initializeDefaultPlugins()) {return new createjs.DefaultSoundInstance(src, startTime, duration);} src = s._getSrcById(src); @@ -2061,7 +3924,7 @@ this.createjs = this.createjs || {}; if (startTime == null) {startTime = src.startTime;} instance = s.activePlugin.create(details.src, startTime, duration || src.duration); } else { - instance = Sound._defaultSoundInstance; + instance = new createjs.DefaultSoundInstance(src, startTime, duration);; } instance.uniqueId = s._lastID++; @@ -2072,7 +3935,7 @@ this.createjs = this.createjs || {}; /** * 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 SoundInstance {{#crossLink "SoundInstance/setVolume"}}{{/crossLink}} instead. + * sound volume, use AbstractSoundInstance {{#crossLink "AbstractSoundInstance/setVolume"}}{{/crossLink}} instead. * * <h4>Example</h4> * createjs.Sound.setVolume(0.5); @@ -2095,7 +3958,7 @@ this.createjs = this.createjs || {}; /** * Get the master volume of Sound. The master volume is multiplied against each sound's individual volume. - * To get individual sound volume, use SoundInstance {{#crossLink "SoundInstance/volume:property"}}{{/crossLink}} instead. + * To get individual sound volume, use AbstractSoundInstance {{#crossLink "AbstractSoundInstance/volume:property"}}{{/crossLink}} instead. * * <h4>Example</h4> * var masterVolume = createjs.Sound.getVolume(); @@ -2111,7 +3974,7 @@ this.createjs = this.createjs || {}; /** * 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 SoundInstance {{#crossLink "SoundInstance/setMute"}}{{/crossLink}} instead. + * instance, use AbstractSoundInstance {{#crossLink "AbstractSoundInstance/setMute"}}{{/crossLink}} instead. * * <h4>Example</h4> * createjs.Sound.setMute(true); @@ -2136,8 +3999,8 @@ this.createjs = this.createjs || {}; }; /** - * Returns the global mute value. To get the mute value of an individual instance, use SoundInstance - * {{#crossLink "SoundInstance/getMute"}}{{/crossLink}} instead. + * Returns the global mute value. To get the mute value of an individual instance, use AbstractSoundInstance + * {{#crossLink "AbstractSoundInstance/getMute"}}{{/crossLink}} instead. * * <h4>Example</h4> * var muted = createjs.Sound.getMute(); @@ -2153,7 +4016,7 @@ this.createjs = this.createjs || {}; /** * Stop all audio (global stop). Stopped audio is reset, and not paused. To play audio that has been stopped, - * call SoundInstance {{#crossLink "SoundInstance/play"}}{{/crossLink}}. + * call AbstractSoundInstance {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}}. * * <h4>Example</h4> * createjs.Sound.stop(); @@ -2176,7 +4039,7 @@ this.createjs = this.createjs || {}; * 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 {SoundInstance} instance The {{#crossLink "SoundInstance"}}{{/crossLink}} to start playing. + * @param {AbstractSoundInstance} instance The {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} to start playing. * @param {String | Object} [interrupt="none"|options] 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 <code>INTERRUPT_TYPE</code> * constants on the Sound class, with the default defined by {{#crossLink "Sound/defaultInterruptBehavior"}}{{/crossLink}}. @@ -2220,7 +4083,7 @@ this.createjs = this.createjs || {}; var delayTimeoutId = setTimeout(function () { s._beginPlaying(instance, interrupt, offset, loop, volume, pan); }, delay); - instance._delayTimeoutId = delayTimeoutId; + instance.delayTimeoutId = delayTimeoutId; } this._instances.push(instance); @@ -2231,7 +4094,7 @@ this.createjs = this.createjs || {}; /** * Begin playback. This is called immediately or after delay by {{#crossLink "Sound/playInstance"}}{{/crossLink}}. * @method _beginPlaying - * @param {SoundInstance} instance A {{#crossLink "SoundInstance"}}{{/crossLink}} to begin playback. + * @param {AbstractSoundInstance} instance A {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} to begin playback. * @param {String} [interrupt=none] How this sound interrupts other instances with the same source. Defaults to * {{#crossLink "Sound/INTERRUPT_NONE:property"}}{{/crossLink}}. Interrupts are defined as <code>INTERRUPT_TYPE</code> * constants on Sound. @@ -2276,7 +4139,7 @@ this.createjs = this.createjs || {}; * 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 {SoundInstance} instance The instance that finished playback. + * @param {AbstractSoundInstance} instance The instance that finished playback. * @protected * @static */ @@ -2289,7 +4152,7 @@ this.createjs = this.createjs || {}; createjs.Sound = Sound; /** - * An internal class that manages the number of active {{#crossLink "SoundInstance"}}{{/crossLink}} instances for + * 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 @@ -2363,7 +4226,7 @@ this.createjs = this.createjs || {}; /** * Add an instance to a sound channel. * #method add - * @param {SoundInstance} instance The instance to add to the channel + * @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. @@ -2377,7 +4240,7 @@ this.createjs = this.createjs || {}; /** * Remove an instance from the channel. * #method remove - * @param {SoundInstance} instance The instance to remove from the channel + * @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 */ @@ -2456,7 +4319,7 @@ this.createjs = this.createjs || {}; * Get an instance by index. * #method get * @param {Number} index The index to return. - * @return {SoundInstance} The SoundInstance at a specific instance. + * @return {AbstractSoundInstance} The AbstractSoundInstance at a specific instance. */ p._get = function (index) { return this._instances[index]; @@ -2465,7 +4328,7 @@ this.createjs = this.createjs || {}; /** * Add a new instance to the channel. * #method add - * @param {SoundInstance} instance The instance to 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) { @@ -2478,7 +4341,7 @@ this.createjs = this.createjs || {}; /** * Remove an instance from the channel, either when it has finished playing, or it has been interrupted. * #method remove - * @param {SoundInstance} instance The instance to 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. */ @@ -2505,9 +4368,9 @@ this.createjs = this.createjs || {}; * Get an available slot depending on interrupt value and if slots are available. * #method getSlot * @param {String} interrupt The interrupt value to use. - * @param {SoundInstance} instance The sound instance that will go in the channel if successful. + * @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 SoundInstance may be interrupted. If there are no slots, this method returns false. + * an existing AbstractSoundInstance may be interrupted. If there are no slots, this method returns false. */ p._getSlot = function (interrupt, instance) { var target, replacement; @@ -2558,95 +4421,1631 @@ this.createjs = this.createjs || {}; p.toString = function () { return "[Sound SoundChannel]"; }; - // do not add SoundChannel to namespace +}()); - // This is a dummy sound instance, which allows Sound to return something so developers don't need to check nulls. - function SoundInstance() { - this.isDefault = true; - this.addEventListener = this.on = this.off = this.removeEventListener = this.removeAllEventListeners = this.dispatchEvent = this.hasEventListener = this._listeners = this._interrupt = this._playFailed = this.pause = this.resume = this.play = this._beginPlaying = this._cleanUp = this.stop = this.setMasterVolume = this.setVolume = this.mute = this.setMute = this.getMute = this.setPan = this.getPosition = this.setPosition = this.playFailed = function () { +//############################################################################## +// 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. + * + * <h4>Example</h4> + * 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.addEventListener("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.addEventListener("complete", handleComplete); + * myInstance.addEventListener("loop", handleLoop); + * myInstance.addEventListener("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 + /** + * Audio sprite property used to determine the starting offset. + * @type {Number} + * @default null + * @protected + */ + this._startTime = Math.max(0, startTime || 0); + //TODO add a getter / setter for startTime? + + + // 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. + * <br />Note this uses a getter setter, which is not supported by Firefox versions 3.6 or lower and Opera versions 11.50 or lower, + * and Internet Explorer 8 or lower. Instead use {{#crossLink "AbstractSoundInstance/setVolume"}}{{/crossLink}} and {{#crossLink "AbstractSoundInstance/getVolume"}}{{/crossLink}}. + * + * The actual output volume of a sound can be calculated using: + * <code>myInstance.volume * createjs.Sound.getVolume();</code> + * + * @property volume + * @type {Number} + * @default 1 + */ + this._volume = 1; + if (createjs.definePropertySupported) { + 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. + * + * <br />Note this uses a getter setter, which is not supported by Firefox versions 3.6 or lower, Opera versions 11.50 or lower, + * and Internet Explorer 8 or lower. Instead use {{#crossLink "AbstractSoundInstance/setPan"}}{{/crossLink}} and {{#crossLink "AbstractSoundInstance/getPan"}}{{/crossLink}}. + * <br />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; + if (createjs.definePropertySupported) { + Object.defineProperty(this, "pan", { + get: this.getPan, + set: this.setPan + }); + } + + /** + * The length of the audio clip, in milliseconds. + * + * <br />Note this uses a getter setter, which is not supported by Firefox versions 3.6 or lower, Opera versions 11.50 or lower, + * and Internet Explorer 8 or lower. Instead use {{#crossLink "AbstractSoundInstance/setDuration"}}{{/crossLink}} and {{#crossLink "AbstractSoundInstance/getDuration"}}{{/crossLink}}. + * + * @property duration + * @type {Number} + * @default 0 + * @since 0.6.0 + */ + this._duration = Math.max(0, duration || 0); + if (createjs.definePropertySupported) { + 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; + if (createjs.definePropertySupported) { + 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. + * + * <br />Note this uses a getter setter, which is not supported by Firefox versions 3.6 or lower, Opera versions 11.50 or lower, + * and Internet Explorer 8 or lower. Instead use {{#crossLink "AbstractSoundInstance/setPosition"}}{{/crossLink}} and {{#crossLink "AbstractSoundInstance/getPosition"}}{{/crossLink}}. + * + * @property position + * @type {Number} + * @default 0 + * @since 0.6.0 + */ + this._position = 0; + if (createjs.definePropertySupported) { + Object.defineProperty(this, "position", { + get: this.getPosition, + set: this.setPosition + }); + } + + /** + * The number of play loops remaining. Negative values will loop infinitely. + * + * <br />Note this uses a getter setter, which is not supported by Firefox versions 3.6 or lower, Opera versions 11.50 or lower, + * and Internet Explorer 8 or lower. Instead use {{#crossLink "AbstractSoundInstance/setLoop"}}{{/crossLink}} and {{#crossLink "AbstractSoundInstance/getLoop"}}{{/crossLink}}. + * + * @property loop + * @type {Number} + * @default 0 + * @public + * @since 0.6.0 + */ + this._loop = 0; + if (createjs.definePropertySupported) { + Object.defineProperty(this, "loop", { + get: this.getLoop, + set: this.setLoop + }); + } + + /** + * Determines if the audio is currently muted. + * + * <br />Note this uses a getter setter, which is not supported by Firefox versions 3.6 or lower, Opera versions 11.50 or lower, + * and Internet Explorer 8 or lower. Instead use {{#crossLink "AbstractSoundInstance/setMute"}}{{/crossLink}} and {{#crossLink "AbstractSoundInstance/getMute"}}{{/crossLink}}. + * + * @property muted + * @type {Boolean} + * @default false + * @since 0.6.0 + */ + this._muted = false; + if (createjs.definePropertySupported) { + Object.defineProperty(this, "muted", { + get: this.getMuted, + set: this.setMuted + }); + } + + /** + * Tells you if the audio is currently paused. + * + * <br />Note this uses a getter setter, which is not supported by Firefox versions 3.6 or lower, Opera versions 11.50 or lower, + * and Internet Explorer 8 or lower. + * Use {{#crossLink "AbstractSoundInstance/pause:method"}}{{/crossLink}} and {{#crossLink "AbstractSoundInstance/resume:method"}}{{/crossLink}} to set. + * + * @property paused + * @type {Boolean} + */ + this._paused = false; + if (createjs.definePropertySupported) { + 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); + + +// 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}}). + * + * <h4>Example</h4> + * var myInstance = createjs.Sound.createInstance(mySrc); + * myInstance.play({offset:1, loop:2, pan:0.5}); // options as object properties + * myInstance.play(createjs.Sound.INTERRUPT_ANY); // options as parameters + * + * Note that if this sound is already playing, this call will do nothing. + * + * @method play + * @param {String | Object} [interrupt="none"|options] 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 <code>INTERRUPT_TYPE</code> + * constants on the Sound class, with the default defined by Sound {{#crossLink "Sound/defaultInterruptBehavior:property"}}{{/crossLink}}. + * <br /><strong>OR</strong><br /> + * This parameter can be an object that contains any or all optional properties by name, including: interrupt, + * delay, offset, loop, volume, and pan (see the above code sample). + * @param {Number} [delay=0] The delay in milliseconds before the sound starts + * @param {Number} [offset=0] How far into the sound to begin playback, in milliseconds. + * @param {Number} [loop=0] The number of times to loop the audio. Use -1 for infinite loops. + * @param {Number} [volume=1] The volume of the sound, between 0 and 1. + * @param {Number} [pan=0] The pan of the sound 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) { + if (this.playState == createjs.Sound.PLAY_SUCCEEDED) { + if (interrupt instanceof Object) { + offset = interrupt.offset; + loop = interrupt.loop; + volume = interrupt.volume; + pan = interrupt.pan; + } + if (offset != null) { this.setPosition(offset) } + if (loop != null) { this.setLoop(loop); } + if (volume != null) { this.setVolume(volume); } + if (pan != null) { this.setPan(pan); } + if (this._paused) { this.setPaused(false); } + return; + } + this._cleanUp(); + createjs.Sound._playInstance(this, interrupt, delay, offset, loop, volume, pan); // make this an event dispatch?? + return this; + }; + + /** + * Deprecated, please use {{#crossLink "AbstractSoundInstance/paused:property"}}{{/crossLink}} instead. + * + * @method pause + * @return {Boolean} If the pause call succeeds. This will return false if the sound isn't currently playing. + * @deprecated + */ + p.pause = function () { + if (this._paused || this.playState != createjs.Sound.PLAY_SUCCEEDED) {return false;} + this.setPaused(true); + return true; + }; + + /** + * Deprecated, please use {{#crossLink "AbstractSoundInstance/paused:property"}}{{/crossLink}} instead. + * + * @method resume + * @return {Boolean} If the resume call succeeds. This will return false if called on a sound that is not paused. + * @deprecated + */ + p.resume = function () { + if (!this._paused) {return false;} + this.setPaused(false); + return true; + }; + + /** + * 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}}. + * + * <h4>Example</h4> + * + * 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(); + }; + + p.toString = function () { + return "[AbstractSoundInstance]"; + }; + + +// get/set methods that allow support for IE8 + /** + * NOTE {{#crossLink "AbstractSoundInstance/paused:property"}}{{/crossLink}} can be accessed directly as a property, + * and getPaused remains to allow support for IE8 with FlashAudioPlugin. + * + * Returns true if the instance is currently paused. + * + * @method getPaused + * @returns {boolean} If the instance is currently paused + * @since 0.6.0 + */ + p.getPaused = function() { + return this._paused; + }; + + /** + * NOTE {{#crossLink "AbstractSoundInstance/paused:property"}}{{/crossLink}} can be accessed directly as a property, + * setPaused remains to allow support for IE8 with FlashAudioPlugin. + * + * Pause or resume the instance. Note you can also resume playback with {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}}. + * + * @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; + }; + + /** + * NOTE {{#crossLink "AbstractSoundInstance/volume:property"}}{{/crossLink}} can be accessed directly as a property, + * setVolume remains to allow support for IE8 with FlashAudioPlugin. + * + * Set the volume of the instance. + * + * <h4>Example</h4> + * myInstance.setVolume(0.5); + * + * Note that the master volume set using the Sound API method {{#crossLink "Sound/setVolume"}}{{/crossLink}} + * will be applied to the instance volume. + * + * @method setVolume + * @param 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; + }; + + /** + * NOTE {{#crossLink "AbstractSoundInstance/volume:property"}}{{/crossLink}} can be accessed directly as a property, + * getVolume remains to allow support for IE8 with FlashAudioPlugin. + * + * Get the volume of the instance. The actual output volume of a sound can be calculated using: + * <code>myInstance.getVolume() * createjs.Sound.getVolume();</code> + * + * @method getVolume + * @return The current volume of the sound instance. + */ + p.getVolume = function () { + return this._volume; + }; + + /** + * Deprecated, please use {{#crossLink "AbstractSoundInstance/muted:property"}}{{/crossLink}} instead. + * + * @method setMute + * @param {Boolean} value If the sound should be muted. + * @return {Boolean} If the mute call succeeds. + * @deprecated + */ + p.setMute = function (value) { + this.setMuted(value); + }; + + /** + * Deprecated, please use {{#crossLink "AbstractSoundInstance/muted:property"}}{{/crossLink}} instead. + * + * @method getMute + * @return {Boolean} If the sound is muted. + * @deprecated + */ + p.getMute = function () { + return this._muted; + }; + + /** + * NOTE {{#crossLink "AbstractSoundInstance/muted:property"}}{{/crossLink}} can be accessed directly as a property, + * setMuted exists to allow support for IE8 with FlashAudioPlugin. + * + * Mute and unmute the sound. Muted sounds will still play at 0 volume. Note that an unmuted sound may still be + * silent depending on {{#crossLink "Sound"}}{{/crossLink}} volume, instance volume, and Sound muted. + * + * <h4>Example</h4> + * myInstance.setMuted(true); + * + * @method setMute + * @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; + }; + + /** + * NOTE {{#crossLink "AbstractSoundInstance/muted:property"}}{{/crossLink}} can be accessed directly as a property, + * getMuted remains to allow support for IE8 with FlashAudioPlugin. + * + * Get the mute value of the instance. + * + * <h4>Example</h4> + * var isMuted = myInstance.getMuted(); + * + * @method getMute + * @return {Boolean} If the sound is muted. + * @since 0.6.0 + */ + p.getMuted = function () { + return this._muted; + }; + + /** + * NOTE {{#crossLink "AbstractSoundInstance/pan:property"}}{{/crossLink}} can be accessed directly as a property, + * getPan remains to allow support for IE8 with FlashAudioPlugin. + * + * Set the left(-1)/right(+1) pan of the instance. Note that {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}} does not + * support panning, and only simple left/right panning has been implemented for {{#crossLink "WebAudioPlugin"}}{{/crossLink}}. + * The default pan value is 0 (center). + * + * <h4>Example</h4> + * + * myInstance.setPan(-1); // to the left! + * + * @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; + }; + + /** + * NOTE {{#crossLink "AbstractSoundInstance/pan:property"}}{{/crossLink}} can be accessed directly as a property, + * getPan remains to allow support for IE8 with FlashAudioPlugin. + * + * Get the left/right pan of the instance. Note in WebAudioPlugin this only gives us the "x" value of what is + * actually 3D audio. + * + * <h4>Example</h4> + * + * var myPan = myInstance.getPan(); + * + * @method getPan + * @return {Number} The value of the pan, between -1 (left) and 1 (right). + */ + p.getPan = function () { + return this._pan; + }; + + /** + * NOTE {{#crossLink "AbstractSoundInstance/position:property"}}{{/crossLink}} can be accessed directly as a property, + * getPosition remains to allow support for IE8 with FlashAudioPlugin. + * + * Get the position of the playhead of the instance in milliseconds. + * + * <h4>Example</h4> + * var currentOffset = myInstance.getPosition(); + * + * @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) { + return this._calculateCurrentPosition(); // sets this._position + } + return this._position; + }; + + /** + * NOTE {{#crossLink "AbstractSoundInstance/position:property"}}{{/crossLink}} can be accessed directly as a property, + * setPosition remains to allow support for IE8 with FlashAudioPlugin. + * + * Set the position of the playhead in the instance. This can be set while a sound is playing, paused, or + * stopped. + * + * <h4>Example</h4> + * myInstance.setPosition(myInstance.getDuration()/2); // set audio to its halfway point. + * + * @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; + }; + + /** + * NOTE {{#crossLink "AbstractSoundInstance/duration:property"}}{{/crossLink}} can be accessed directly as a property, + * getDuration exists to allow support for IE8 with FlashAudioPlugin. + * + * Get the duration of the instance, in milliseconds. + * Note a sound needs to be loaded before it will have duration, unless it was set manually to create an audio sprite. + * + * <h4>Example</h4> + * var soundDur = myInstance.getDuration(); + * + * @method getDuration + * @return {Number} The duration of the sound instance in milliseconds. + */ + p.getDuration = function () { + return this._duration; + }; + + /** + * NOTE {{#crossLink "AbstractSoundInstance/duration:property"}}{{/crossLink}} can be accessed directly as a property, + * setDuration exists to allow support for IE8 with FlashAudioPlugin. + * + * Set the duration of the audio. Generally this is not called, but it can be used to create an audio sprite out of an existing AbstractSoundInstance. + * + * @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; + }; + + /** + * NOTE {{#crossLink "AbstractSoundInstance/playbackResource:property"}}{{/crossLink}} can be accessed directly as a property, + * setPlaybackResource exists to allow support for IE8 with FlashAudioPlugin. + * + * An object containing any resources needed for audio playback, set by the plugin. + * Only meant for use by advanced users. + * + * @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; + }; + + /** + * NOTE {{#crossLink "AbstractSoundInstance/playbackResource:property"}}{{/crossLink}} can be accessed directly as a property, + * getPlaybackResource exists to allow support for IE8 with FlashAudioPlugin. + * + * An object containing any resources needed for audio playback, usually set by the plugin. + * + * @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; + }; + + /** + * NOTE {{#crossLink "AbstractSoundInstance/loop:property"}}{{/crossLink}} can be accessed directly as a property, + * getLoop exists to allow support for IE8 with FlashAudioPlugin. + * + * The number of play loops remaining. Negative values will loop infinitely. + * + * @method getLoop + * @return {number} + * @since 0.6.0 + **/ + p.getLoop = function () { + return this._loop; + }; + + /** + * NOTE {{#crossLink "AbstractSoundInstance/loop:property"}}{{/crossLink}} can be accessed directly as a property, + * setLoop exists to allow support for IE8 with FlashAudioPlugin. + * + * Set the number of play loops remaining. + * + * @method setLoop + * @param {number} value The number of times to loop after play. + * @since 0.6.0 + */ + p.setLoop = function (value) { + // remove looping + if (this._loop != 0 && value == 0) { + this._removeLooping(value); + } + // add looping + 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 {Number} offset How far into the sound to begin playback, in milliseconds. + * @param {Number} loop The number of times to loop the audio. Use -1 for infinite loops. + * @param {Number} volume The volume of the sound, between 0 and 1. + * @param {Number} pan The pan of the sound between -1 (left) and 1 (right). Note that pan does not work for HTML Audio. + * @return {Boolean} If playback succeeded. + * @protected + */ + p._beginPlaying = function (offset, loop, volume, pan) { + this.setPosition(offset); + this.setLoop(loop); + this.setVolume(volume); + this.setPan(pan); + + 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; - }; - this.getVolume = this.getPan = this.getDuration = function () { - return 0; } - this.playState = Sound.PLAY_FAILED; - this.toString = function () { - return "[Sound Default Sound Instance]"; - } - } + }; - Sound._defaultSoundInstance = new SoundInstance(); + /** + * 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 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 it on this._position + * @method _updatePosition + * @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 + * @protected + * @since 0.6.0 + */ + p._removeLooping = function () { + // plugin specific code + }; + + /** + * Internal function called when looping is added during playback. + * @method _addLooping + * @protected + * @since 0.6.0 + */ + p._addLooping = function () { + // 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 _handleCleanUp + * @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 = {}; + + /** + * 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; + + +// Static Properties: +// NOTE THESE PROPERTIES NEED TO BE ADDED TO EACH PLUGIN + /** + * 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 + */ + 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 <code>Loader</code> instance, which <a href="http://preloadjs.com" target="_blank">PreloadJS</a> + * can use to assist with preloading. + * @method register + * @param {String} src The source of the audio + * @param {Number} instances The number of concurrently playing instances to allow for the channel at any time. + * Note that not every plugin will manage this value. + * @return {Object} A result object, containing a "tag" for preloading purposes. + */ + p.register = function (src, instances) { + this._audioSources[src] = true; + this._soundInstances[src] = []; + if(this._loaders[src]) {return this._loaders[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? + var loader = new this._loaderClass(src); + loader.on("complete", createjs.proxy(this._handlePreloadComplete, this)); + this._loaders[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) { + for (var i = this._soundInstances[src].length; i--; ) { + var item = this._soundInstances[src][i]; + item.destroy(); + } + delete(this._soundInstances[src]); + delete(this._audioSources[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; + }; + + // TODO Volume & mute Getter / Setter?? + // TODO change calls to return nothing or this for chaining?? + // 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 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.target.getResult(false); + 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} src The path to the sound + * @param {Object} flash The flash instance that will do the preloading. + * @extends XHRRequest + * @protected + */ + function Loader(src) { + var loaditem = createjs.LoadItem.create(src); + this.XHRRequest_constructor(loaditem, true, createjs.AbstractLoader.SOUND); + + this._request.responseType = "arraybuffer"; + }; + var p = createjs.extend(Loader, createjs.XHRRequest); + + /** + * 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._handleLoad = 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._request.response, + createjs.proxy(this._handleAudioDecoded, this), + createjs.proxy(this._handleError, this)); + }; /** - * An additional module to determine the current browser, version, operating system, and other environment - * variables. It is not publically documented. - * #class BrowserDetect - * @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 iPad). - * @param {Boolean} isAndroid True if our browser is Android. - * @param {Boolean} isBlackberry True if our browser is Blackberry. - * @constructor - * @static - */ - function BrowserDetect() { - } - - BrowserDetect.init = function () { - var 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.isBlackberry = (agent.indexOf("Blackberry") > -1); + * The audio has been decoded. + * @method handleAudioDecoded + * @param decoded + * @protected + */ + p._handleAudioDecoded = function (decodedAudio) { + this._response = decodedAudio; + this.XHRRequest__handleLoad(); }; - BrowserDetect.init(); - - createjs.Sound.BrowserDetect = BrowserDetect; - + createjs.WebAudioLoader = createjs.promote(Loader, "XHRRequest"); }()); -/* - * WebAudioPlugin - * Visit http://createjs.com/ for documentation, updates and examples. - * - * - * Copyright (c) 2012 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. - */ + +//############################################################################## +// WebAudioSoundInstance.js +//############################################################################## + +this.createjs = this.createjs || {}; /** - * @module SoundJS + * 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. + * <br />GainNode for controlling <code>WebAudioSoundInstance</code> 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. + * <br />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. + * <br />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; + + /** + * Note this is only intended for use by advanced users. + * <br />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. + * <br /> Audio node from WebAudioPlugin that sequences to <code>context.destination</code> + * @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() { + this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); + }; + + p._addLooping = function() { + 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; + 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 () { + this._pause(); + this._resume(); + }; + + createjs.WebAudioSoundInstance = createjs.promote(WebAudioSoundInstance, "AbstractSoundInstance"); +}()); + +//############################################################################## +// WebAudioPlugin.js +//############################################################################## -// namespace: this.createjs = this.createjs || {}; (function () { @@ -2674,16 +6073,74 @@ this.createjs = this.createjs || {}; * by ensuring the audio and video audio share the same sampleRate.</li> * </ul> * @class WebAudioPlugin + * @extends AbstractPlugin * @constructor * @since 0.4.0 */ function WebAudioPlugin() { - this._init(); + 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 internal master volume value of the plugin. + * @property _volume + * @type {Number} + * @default 1 + * @protected + */ + this._volume = 1; + + /** + * 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 <code>context.destination</code>. + * + * 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); +// 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. @@ -2695,6 +6152,30 @@ this.createjs = this.createjs || {}; */ 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 <b>must</b> 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 @@ -2703,7 +6184,7 @@ this.createjs = this.createjs || {}; */ 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.Sound.BrowserDetect.isIOS || createjs.Sound.BrowserDetect.isAndroid || createjs.Sound.BrowserDetect.isBlackberry; + 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(); @@ -2711,6 +6192,30 @@ this.createjs = this.createjs || {}; 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). + * + * <h4>Example</h4> + * function handleTouch(event) { + * createjs.WebAudioPlugin.playEmptySound(); + * } + * + * @method playEmptySound + * @static + * @since 0.4.1 + */ + s.playEmptySound = function() { + 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 @@ -2757,12 +6262,14 @@ this.createjs = this.createjs || {}; var t = document.createElement("audio"); if (t.canPlayType == null) {return null;} - if (window.AudioContext) { - s.context = new AudioContext(); - } else if (window.webkitAudioContext) { - s.context = new webkitAudioContext(); - } else { - 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(); @@ -2820,223 +6327,30 @@ this.createjs = this.createjs || {}; s._panningModel = 0; }; + +// Public Methods + p.toString = function () { + return "[WebAudioPlugin]"; + }; + + +// Private Methods /** - * 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). - * - * <h4>Example</h4> - * function handleTouch(event) { - * createjs.WebAudioPlugin.playEmptySound(); - * } - * - * @method playEmptySound + * Set up needed properties on supported classes WebAudioSoundInstance and WebAudioLoader. + * @method _addPropsToClasses * @static - * @since 0.4.1 - */ - s.playEmptySound = function() { - var source = s.context.createBufferSource(); - source.buffer = s.context.createBuffer(1, 1, 22050); - source.connect(s.context.destination); - source.start(0, 0, 0); - }; - - - var p = WebAudioPlugin.prototype; - p.constructor = WebAudioPlugin; - - p._capabilities = null; // doc'd above - - /** - * The internal master volume value of the plugin. - * @property _volume - * @type {Number} - * @default 1 * @protected + * @since 0.6.0 */ - p._volume = 1; + p._addPropsToClasses = function() { + var c = this._soundInstanceClass; + c.context = this.context; + c.destinationNode = this.gainNode; + c._panningModel = this._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} - */ - p.context = null; - - /** - * Value to set panning model to equal power for SoundInstance. Can be "equalpower" or 0 depending on browser implementation. - * @property _panningModel - * @type {Number / String} - * @protected - */ - p._panningModel = "equalpower"; - - /** - * A DynamicsCompressorNode, which is used to improve sound quality and prevent audio distortion. - * It is connected to <code>context.destination</code>. - * - * Can be accessed by advanced users through createjs.Sound.activePlugin.dynamicsCompressorNode. - * @property dynamicsCompressorNode - * @type {AudioNode} - */ - p.dynamicsCompressorNode = null; - - /** - * 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} - */ - p.gainNode = null; - - /** - * An object hash used internally to store ArrayBuffers, indexed by the source URI used to load it. This - * prevents having to load and decode audio files more than once. If a load has been started on a file, - * <code>arrayBuffers[src]</code> will be set to true. Once load is complete, it is set the the loaded - * ArrayBuffer instance. - * @property _arrayBuffers - * @type {Object} - * @protected - */ - p._arrayBuffers = null; - - /** - * An initialization function run by the constructor - * @method _init - * @protected - */ - p._init = function () { - this._capabilities = s._capabilities; - this._arrayBuffers = {}; - - this.context = s.context; - this._panningModel = s._panningModel; - - // set up AudioNodes that all of our source audio will connect to - this.dynamicsCompressorNode = this.context.createDynamicsCompressor(); - this.dynamicsCompressorNode.connect(this.context.destination); - this.gainNode = this.context.createGain(); - this.gainNode.connect(this.dynamicsCompressorNode); + this._loaderClass.context = this.context; }; - /** - * Pre-register a sound for preloading and setup. This is called by {{#crossLink "Sound"}}{{/crossLink}}. - * Note that WebAudio provides a <code>Loader</code> instance, which <a href="http://preloadjs.com" target="_blank">PreloadJS</a> - * can use to assist with preloading. - * @method register - * @param {String} src The source of the audio - * @param {Number} instances The number of concurrently playing instances to allow for the channel at any time. - * Note that the WebAudioPlugin does not manage this property. - * @return {Object} A result object, containing a "tag" for preloading purposes. - */ - p.register = function (src, instances) { - this._arrayBuffers[src] = true; - var loader = {tag: new createjs.WebAudioPlugin.Loader(src, this)}; - return loader; - }; - - /** - * 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._arrayBuffers[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._arrayBuffers[src] == null || this._arrayBuffers[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. - * @since 0.4.1 - */ - p.removeSound = function (src) { - delete(this._arrayBuffers[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. - * @since 0.4.1 - */ - p.removeAllSounds = function () { - this._arrayBuffers = {}; - }; - - /** - * Add loaded results to the preload object hash. - * @method addPreloadResults - * @param {String} src The sound URI to unload. - * @return {Boolean} - */ - p.addPreloadResults = function (src, result) { - this._arrayBuffers[src] = result; - }; - - /** - * Handles internal preload completion. - * @method _handlePreloadComplete - * @protected - */ - p._handlePreloadComplete = function (loader) { - createjs.Sound._sendFileLoadEvent(loader.src); - loader.cleanUp(); - }; - - /** - * Internally preload a sound. Loading uses XHR2 to load an array buffer for use with WebAudio. - * @method preload - * @param {String} src The sound URI to load. - * @param {Object} tag Not used in this plugin. - */ - p.preload = function (src, tag) { - this._arrayBuffers[src] = true; - var loader = new createjs.WebAudioPlugin.Loader(src, this); - loader.onload = this._handlePreloadComplete; - loader.load(); - }; - - /** - * 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 {SoundInstance} A sound instance for playback and control. - */ - p.create = function (src, startTime, duration) { - if (!this.isPreloadStarted(src)) {this.preload(src);} - return new createjs.WebAudioPlugin.SoundInstance(src, startTime, duration, this); - }; - - /** - * 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; - }; /** * Set the gain value for master audio. Should not be called externally. @@ -3050,1922 +6364,83 @@ this.createjs = this.createjs || {}; } }; - /** - * Get the master volume of the plugin, which affects all SoundInstances. - * @method getVolume - * @return 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; - }; - - p.toString = function () { - return "[WebAudioPlugin]"; - }; - - createjs.WebAudioPlugin = WebAudioPlugin; + createjs.WebAudioPlugin = createjs.promote(WebAudioPlugin, "AbstractPlugin"); }()); -(function () { +//############################################################################## +// HTMLAudioTagPool.js +//############################################################################## - "use strict"; - - /** - * A SoundInstance is created when any calls to the Sound API method {{#crossLink "Sound/play"}}{{/crossLink}} or - * {{#crossLink "Sound/createInstance"}}{{/crossLink}} are made. The SoundInstance is returned by the active plugin - * for control by the user. - * - * <h4>Example</h4> - * 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 SoundInstance is created, a reference can be stored that can be used to control the audio directly through - * the SoundInstance. If the reference is not stored, the SoundInstance 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 "SoundInstance/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.addEventListener("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.addEventListener("complete", handleComplete); - * myInstance.addEventListener("loop", handleLoop); - * myInstance.addEventListener("failed", handleFailed); - * - * - * @class SoundInstance - * @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} owner The plugin instance that created this SoundInstance. - * @extends EventDispatcher - * @constructor - */ - function SoundInstance(src, startTime, duration, owner) { - this._init(src, startTime, duration, owner); - } - - var p = SoundInstance.prototype = new createjs.EventDispatcher(); - p.constructor = SoundInstance; - - /** - * The source of the sound. - * @property src - * @type {String} - * @default null - */ - p.src = null; - - /** - * The unique ID of the instance. This is set by {{#crossLink "Sound"}}{{/crossLink}}. - * @property uniqueId - * @type {String} | Number - * @default -1 - */ - p.uniqueId = -1; - - /** - * The play state of the sound. Play states are defined as constants on {{#crossLink "Sound"}}{{/crossLink}}. - * @property playState - * @type {String} - * @default null - */ - p.playState = null; - - /** - * The plugin that created the instance - * @property _owner - * @type {WebAudioPlugin} - * @default null - * @protected - */ - p._owner = null; - - /** - * How far into the sound to begin playback in milliseconds. This is passed in when play is called and used by - * pause and setPosition to track where the sound should be at. - * Note this is converted from milliseconds to seconds for consistency with the WebAudio API. - * @property _offset - * @type {Number} - * @default 0 - * @protected - */ - p._offset = 0; - - /** - * Audio sprite property used to determine the starting offset. - * @type {Number} - * @default null - * @protected - */ - p._startTime = 0; - - /** - * The volume of the sound, between 0 and 1. - * <br />Note this uses a getter setter, which is not supported by Firefox versions 3.6 or lower and Opera versions 11.50 or lower, - * and Internet Explorer 8 or lower. Instead use {{#crossLink "SoundInstance/setVolume"}}{{/crossLink}} and {{#crossLink "SoundInstance/getVolume"}}{{/crossLink}}. - * - * The actual output volume of a sound can be calculated using: - * <code>myInstance.volume * createjs.Sound.getVolume();</code> - * - * @property volume - * @type {Number} - * @default 1 - */ - p._volume = 1; - if (createjs.definePropertySupported) { - Object.defineProperty(p, "volume", { - get: function() { - return this._volume; - }, - set: function(value) { - if (Number(value) == null) {return false} - value = Math.max(0, Math.min(1, value)); - this._volume = value; - this._updateVolume(); - } - }); - } - - /** - * The pan of the sound, between -1 (left) and 1 (right). Note that pan is not supported by HTML Audio. - * - * <br />Note this uses a getter setter, which is not supported by Firefox versions 3.6 or lower, Opera versions 11.50 or lower, - * and Internet Explorer 8 or lower. Instead use {{#crossLink "SoundInstance/setPan"}}{{/crossLink}} and {{#crossLink "SoundInstance/getPan"}}{{/crossLink}}. - * <br />Note in WebAudioPlugin this only gives us the "x" value of what is actually 3D audio. - * - * @property pan - * @type {Number} - * @default 0 - */ - p._pan = 0; - if (createjs.definePropertySupported) { - Object.defineProperty(p, "pan", { - get: function() { - return this._pan; - }, - set: function(value) { - if (!this._owner._capabilities.panning || Number(value) == null) {return false;} - - value = Math.max(-1, Math.min(1, value)); // force pan to stay in the -1 to 1 range - // Note that panning in WebAudioPlugin can support 3D audio, but our implementation does not. - this._pan = value; // Unfortunately panner does not give us a way to access this after it is set http://www.w3.org/TR/webaudio/#AudioPannerNode - this.panNode.setPosition(value, 0, -0.5); // z need to be -0.5 otherwise the sound only plays in left, right, or center - } - }); - } - -/** - * The length of the audio clip, in milliseconds. - * Use {{#crossLink "SoundInstance/getDuration:method"}}{{/crossLink}} to access. - * @property _duration - * @type {Number} - * @default 0 - * @protected - */ - p._duration = 0; - - /** - * The number of play loops remaining. Negative values will loop infinitely. - * - * @property loop - * @type {Number} - * @default 0 - * @public - */ - p._remainingLoops = 0; - if (createjs.definePropertySupported) { - Object.defineProperty(p, "loop", { - get: function() { - return this._remainingLoops; - }, - set: function(value) { - // remove looping - if (this._remainingLoops != 0 && value == 0) { - this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); - } - // add looping - if (this._remainingLoops == 0 && value != 0) { - this._sourceNodeNext = this._createAndPlayAudioNode(this._playbackStartTime, 0); - } - this._remainingLoops = value; - } - }); - } - - /** - * A Timeout created by {{#crossLink "Sound"}}{{/crossLink}} when this SoundInstance is played with a delay. - * This allows SoundInstance 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 - */ - p._delayTimeoutId = null; - - /** - * 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 - */ - p._soundCompleteTimeout = null; - - /** - * NOTE this only exists as a {{#crossLink "WebAudioPlugin"}}{{/crossLink}} property and is only intended for use by advanced users. - * <br />GainNode for controlling <code>SoundInstance</code> volume. Connected to the WebAudioPlugin {{#crossLink "WebAudioPlugin/gainNode:property"}}{{/crossLink}} - * that sequences to <code>context.destination</code>. - * @property gainNode - * @type {AudioGainNode} - * @since 0.4.0 - * - */ - p.gainNode = null; - - /** - * NOTE this only exists as a {{#crossLink "WebAudioPlugin"}}{{/crossLink}} property and is only intended for use by advanced users. - * <br />A panNode allowing left and right audio channel panning only. Connected to SoundInstance {{#crossLink "SoundInstance/gainNode:property"}}{{/crossLink}}. - * @property panNode - * @type {AudioPannerNode} - * @since 0.4.0 - */ - p.panNode = null; - - /** - * NOTE this only exists as a {{#crossLink "WebAudioPlugin"}}{{/crossLink}} property and is only intended for use by advanced users. - * <br />sourceNode is the audio source. Connected to SoundInstance {{#crossLink "SoundInstance/panNode:property"}}{{/crossLink}}. - * @property sourceNode - * @type {AudioNode} - * @since 0.4.0 - * - */ - p.sourceNode = null; - - /** - * NOTE this only exists as a {{#crossLink "WebAudioPlugin"}}{{/crossLink}} property and is only intended for use by 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 "SoundInstance/gainNode:property"}}{{/crossLink}}. - * @property _sourceNodeNext - * @type {AudioNode} - * @default null - * @protected - * @since 0.4.1 - * - */ - p._sourceNodeNext = null; - - /** - * Determines if the audio is currently muted. - * Use {{#crossLink "SoundInstance/getMute:method"}}{{/crossLink}} and {{#crossLink "SoundInstance/setMute:method"}}{{/crossLink}} to access. - * @property _muted - * @type {Boolean} - * @default false - * @protected - */ - p._muted = false; - - /** - * Read only value that tells you if the audio is currently paused. - * Use {{#crossLink "SoundInstance/pause:method"}}{{/crossLink}} and {{#crossLink "SoundInstance/resume:method"}}{{/crossLink}} to set. - * @property paused - * @type {Boolean} - */ - p.paused = false; // this value will not be used, and is only set - p._paused = false; // this value is used internally for setting paused - - /** - * WebAudioPlugin only. - * 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 - */ - p._playbackStartTime = 0; - - // Proxies, make removing listeners easier. - p._endedHandler = null; - -// 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 - */ - - /** - * A helper method that dispatches all events for SoundInstance. - * @method _sendEvent - * @param {String} type The event type - * @protected - */ - p._sendEvent = function (type) { - var event = new createjs.Event(type); - this.dispatchEvent(event); - }; - -// Constructor - /** - * Initialize the SoundInstance. This is called from the constructor. - * @method _init - * @param {string} src The source of the audio. - * @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 {Class} owner The plugin that created this instance. - * @protected - */ - p._init = function (src, startTime, duration, owner) { - this.src = src; - this._startTime = startTime * 0.001 || 0; // convert ms to s as web audio handles everything in seconds - this._duration = duration || 0; - this._owner = owner; - - this.gainNode = this._owner.context.createGain(); - - this.panNode = this._owner.context.createPanner(); - this.panNode.panningModel = this._owner._panningModel; - this.panNode.connect(this.gainNode); - - if (this._owner.isPreloadComplete(this.src) && !this._duration) {this._duration = this._owner._arrayBuffers[this.src].duration * 1000;} - - this._endedHandler = createjs.proxy(this._handleSoundComplete, this); - }; - - /** - * Clean up the instance. Remove references and clean up any additional properties such as timers. - * @method _cleanUp - * @protected - */ - p._cleanUp = 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._delayTimeoutId); // clear timeout that plays delayed sound - clearTimeout(this._soundCompleteTimeout); // clear timeout that triggers sound complete - - this._playbackStartTime = 0; // This is used by getPosition - - createjs.Sound._playFinished(this); - }; - - /** - * 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; - }; - - /** - * The sound has been interrupted. - * @method _interrupt - * @protected - */ - p._interrupt = function () { - this._cleanUp(); - this.playState = createjs.Sound.PLAY_INTERRUPTED; - this.paused = this._paused = false; - this._sendEvent("interrupted"); - }; - - /** - * Handles starting playback when the sound is ready for playing. - * @method _handleSoundReady - * @protected - */ - p._handleSoundReady = function (event) { - if (!this._duration) {this._duration = this._owner._arrayBuffers[this.src].duration * 1000;} // NOTE *1000 because WebAudio reports everything in seconds but js uses milliseconds - if ((this._offset*1000) > this._duration) { - this.playFailed(); - return; - } else if (this._offset < 0) { // may not need this check if play ignores negative values, this is not specified in the API http://www.w3.org/TR/webaudio/#AudioBufferSourceNode - this._offset = 0; - } - - this.playState = createjs.Sound.PLAY_SUCCEEDED; - this.paused = this._paused = false; - - this.gainNode.connect(this._owner.gainNode); // 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; - this.sourceNode = this._createAndPlayAudioNode((this._owner.context.currentTime - dur), this._offset); - this._playbackStartTime = this.sourceNode.startTime - this._offset; - - this._soundCompleteTimeout = setTimeout(this._endedHandler, (dur - this._offset) * 1000); - - if(this._remainingLoops != 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 = this._owner.context.createBufferSource(); - audioNode.buffer = this._owner._arrayBuffers[this.src]; - audioNode.connect(this.panNode); - var dur = this._duration * 0.001; - audioNode.startTime = startTime + dur; - audioNode.start(audioNode.startTime, offset+this._startTime, dur - offset); - return audioNode; - }; - - // Public API - /** - * 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}}). - * - * <h4>Example</h4> - * var myInstance = createjs.Sound.createInstance(mySrc); - * myInstance.play({offset:1, loop:2, pan:0.5}); // options as object properties - * myInstance.play(createjs.Sound.INTERRUPT_ANY); // options as parameters - * - * Note that if this sound is already playing, this call will do nothing. - * - * @method play - * @param {String | Object} [interrupt="none"|options] 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 <code>INTERRUPT_TYPE</code> - * constants on the Sound class, with the default defined by Sound {{#crossLink "Sound/defaultInterruptBehavior:property"}}{{/crossLink}}. - * <br /><strong>OR</strong><br /> - * This parameter can be an object that contains any or all optional properties by name, including: interrupt, - * delay, offset, loop, volume, and pan (see the above code sample). - * @param {Number} [delay=0] The delay in milliseconds before the sound starts - * @param {Number} [offset=0] How far into the sound to begin playback, in milliseconds. - * @param {Number} [loop=0] The number of times to loop the audio. Use -1 for infinite loops. - * @param {Number} [volume=1] The volume of the sound, between 0 and 1. - * @param {Number} [pan=0] The pan of the sound between -1 (left) and 1 (right). Note that pan is not supported - * for HTML Audio. - */ - p.play = function (interrupt, delay, offset, loop, volume, pan) { - if (this.playState == createjs.Sound.PLAY_SUCCEEDED) { - if (interrupt instanceof Object) { - offset = interrupt.offset; - loop = interrupt.loop; - volume = interrupt.volume; - pan = interrupt.pan; - } - if (offset != null) { this.setPosition(offset) } - if (loop != null) { this.loop = loop; } - if (volume != null) { this.setVolume(volume); } - if (pan != null) { this.setPan(pan); } - if (this._paused) { this.resume(); } - return; - } - this._cleanUp(); - createjs.Sound._playInstance(this, interrupt, delay, offset, loop, volume, pan); - }; - - /** - * 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 {Number} offset How far into the sound to begin playback, in milliseconds. - * @param {Number} loop The number of times to loop the audio. Use -1 for infinite loops. - * @param {Number} volume The volume of the sound, between 0 and 1. - * @param {Number} pan The pan of the sound between -1 (left) and 1 (right). Note that pan does not work for HTML Audio. - * @protected - */ - p._beginPlaying = function (offset, loop, volume, pan) { - this._offset = offset * 0.001; //convert ms to sec - this._remainingLoops = loop; - this.volume = volume; - this.pan = pan; - - if (this._owner.isPreloadComplete(this.src)) { - this._handleSoundReady(null); - this._sendEvent("succeeded"); - return 1; - } else { - this.playFailed(); - return; - } - }; - - /** - * Pause the instance. Paused audio will stop at the current time, and can be resumed using - * {{#crossLink "SoundInstance/resume"}}{{/crossLink}}. - * - * <h4>Example</h4> - * - * myInstance.pause(); - * - * @method pause - * @return {Boolean} If the pause call succeeds. This will return false if the sound isn't currently playing. - */ - p.pause = function () { - if (this._paused || this.playState != createjs.Sound.PLAY_SUCCEEDED) {return false;} - - this.paused = this._paused = true; - - this._offset = this._owner.context.currentTime - this._playbackStartTime; // this allows us to restart the sound at the same point in playback - this.sourceNode = this._cleanUpAudioNode(this.sourceNode); - this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); - - if (this.gainNode.numberOfOutputs != 0) {this.gainNode.disconnect(0);} - - clearTimeout(this._delayTimeoutId); - clearTimeout(this._soundCompleteTimeout); - return true; - }; - - /** - * Resume an instance that has been paused using {{#crossLink "SoundInstance/pause"}}{{/crossLink}}. Audio that - * has not been paused will not playback when this method is called. - * - * <h4>Example</h4> - * - * myInstance.pause(); - * // do some stuff - * myInstance.resume(); - * - * @method resume - * @return {Boolean} If the resume call succeeds. This will return false if called on a sound that is not paused. - */ - p.resume = function () { - if (!this._paused) {return false;} - this._handleSoundReady(); - return true; - }; - - /** - * Stop playback of the instance. Stopped sounds will reset their position to 0, and calls to {{#crossLink "SoundInstance/resume"}}{{/crossLink}} - * will fail. To start playback again, call {{#crossLink "SoundInstance/play"}}{{/crossLink}}. - * - * <h4>Example</h4> - * - * myInstance.stop(); - * - * @method stop - * @return {Boolean} If the stop call succeeds. - */ - p.stop = function () { - this.paused = this._paused = false; - this._cleanUp(); - this.playState = createjs.Sound.PLAY_FINISHED; - this._offset = 0; // set audio to start at the beginning - return true; - }; - - /** - * NOTE that you can set volume directly as a property, and setVolume remains to allow support for IE8 with FlashPlugin. - * Set the volume of the instance. You can retrieve the volume using {{#crossLink "SoundInstance/getVolume"}}{{/crossLink}}. - * - * <h4>Example</h4> - * - * myInstance.setVolume(0.5); - * - * Note that the master volume set using the Sound API method {{#crossLink "Sound/setVolume"}}{{/crossLink}} - * will be applied to the instance volume. - * - * @method setVolume - * @param value The volume to set, between 0 and 1. - * @return {Boolean} If the setVolume call succeeds. - */ - p.setVolume = function (value) { - this.volume = value; - return true; - }; - - /** - * 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 () { - var newVolume = this._muted ? 0 : this._volume; - if (newVolume != this.gainNode.gain.value) { - this.gainNode.gain.value = newVolume; - } - }; - - /** - * NOTE that you can access volume directly as a property, and getVolume remains to allow support for IE8 with FlashPlugin. - * - * Get the volume of the instance. The actual output volume of a sound can be calculated using: - * <code>myInstance.getVolume() * createjs.Sound.getVolume();</code> - * - * @method getVolume - * @return The current volume of the sound instance. - */ - p.getVolume = function () { - return this.volume; - }; - - /** - * Mute and unmute the sound. Muted sounds will still play at 0 volume. Note that an unmuted sound may still be - * silent depending on {{#crossLink "Sound"}}{{/crossLink}} volume, instance volume, and Sound mute. - * - * <h4>Example</h4> - * - * myInstance.setMute(true); - * - * @method setMute - * @param {Boolean} value If the sound should be muted. - * @return {Boolean} If the mute call succeeds. - * @since 0.4.0 - */ - p.setMute = function (value) { - if (value == null) {return false;} - - this._muted = value; - this._updateVolume(); - return true; - }; - - /** - * Get the mute value of the instance. - * - * <h4>Example</h4> - * - * var isMuted = myInstance.getMute(); - * - * @method getMute - * @return {Boolean} If the sound is muted. - * @since 0.4.0 - */ - p.getMute = function () { - return this._muted; - }; - - /** - * NOTE that you can set pan directly as a property, and getPan remains to allow support for IE8 with FlashPlugin. - * - * Set the left(-1)/right(+1) pan of the instance. Note that {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}} does not - * support panning, and only simple left/right panning has been implemented for {{#crossLink "WebAudioPlugin"}}{{/crossLink}}. - * The default pan value is 0 (center). - * - * <h4>Example</h4> - * - * myInstance.setPan(-1); // to the left! - * - * @method setPan - * @param {Number} value The pan value, between -1 (left) and 1 (right). - * @return {Number} If the setPan call succeeds. - */ - p.setPan = function (value) { - this.pan = value; // Unfortunately panner does not give us a way to access this after it is set http://www.w3.org/TR/webaudio/#AudioPannerNode - if(this.pan != value) {return false;} - return true; - }; - - /** - * NOTE that you can access pan directly as a property, and getPan remains to allow support for IE8 with FlashPlugin. - * - * Get the left/right pan of the instance. Note in WebAudioPlugin this only gives us the "x" value of what is - * actually 3D audio. - * - * <h4>Example</h4> - * - * var myPan = myInstance.getPan(); - * - * @method getPan - * @return {Number} The value of the pan, between -1 (left) and 1 (right). - */ - p.getPan = function () { - return this.pan; - }; - - /** - * Get the position of the playhead of the instance in milliseconds. - * - * <h4>Example</h4> - * - * var currentOffset = myInstance.getPosition(); - * - * @method getPosition - * @return {Number} The position of the playhead in the sound, in milliseconds. - */ - p.getPosition = function () { - if (this._paused || this.sourceNode == null) { - var pos = this._offset; - } else { - var pos = this._owner.context.currentTime - this._playbackStartTime; - } - - return pos * 1000; // pos in seconds * 1000 to give milliseconds - }; - - /** - * Set the position of the playhead in the instance. This can be set while a sound is playing, paused, or - * stopped. - * - * <h4>Example</h4> - * - * myInstance.setPosition(myInstance.getDuration()/2); // set audio to its halfway point. - * - * @method setPosition - * @param {Number} value The position to place the playhead, in milliseconds. - */ - p.setPosition = function (value) { - this._offset = value * 0.001; // convert milliseconds to seconds - - if (this.sourceNode && this.playState == createjs.Sound.PLAY_SUCCEEDED) { - // we need to stop this sound from continuing to play, as we need to recreate the sourceNode to change position - this.sourceNode = this._cleanUpAudioNode(this.sourceNode); - this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); - clearTimeout(this._soundCompleteTimeout); // clear timeout that triggers sound complete - } // NOTE we cannot just call cleanup because it also calls the Sound function _playFinished which releases this instance in SoundChannel - - if (!this._paused && this.playState == createjs.Sound.PLAY_SUCCEEDED) {this._handleSoundReady();} - - return true; - }; - - /** - * Get the duration of the instance, in milliseconds. Note in most cases, you need to play a sound using - * {{#crossLink "SoundInstance/play"}}{{/crossLink}} or the Sound API {{#crossLink "Sound/play"}}{{/crossLink}} - * method before its duration can be reported accurately. - * - * <h4>Example</h4> - * - * var soundDur = myInstance.getDuration(); - * - * @method getDuration - * @return {Number} The duration of the sound instance in milliseconds. - */ - p.getDuration = function () { - return this._duration; - }; - - /** - * Audio has finished playing. Manually loop it if required. - * @method _handleSoundComplete - * @param event - * @protected - */ - // called internally by _soundCompleteTimeout in WebAudioPlugin - p._handleSoundComplete = function (event) { - this._offset = 0; // have to set this as it can be set by pause during playback - - if (this._remainingLoops != 0) { - this._remainingLoops--; // NOTE this introduces a theoretical limit on loops = float max size x 2 - 1 - - // 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 and the reasons for - // using it are described in greater detail 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. - if(this._sourceNodeNext) { // this can be set to null, but this should not happen when looping - 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); - } - else { - this._handleSoundReady(); - } - - this._sendEvent("loop"); - return; - } - - this._cleanUp(); - this.playState = createjs.Sound.PLAY_FINISHED; - this._sendEvent("complete"); - }; - - // Play has failed, which can happen for a variety of reasons. - p.playFailed = function () { - this._cleanUp(); - this.playState = createjs.Sound.PLAY_FAILED; - this._sendEvent("failed"); - }; - - p.toString = function () { - return "[WebAudioPlugin SoundInstance]"; - }; - - createjs.WebAudioPlugin.SoundInstance = SoundInstance; -}()); - -(function () { - - "use strict"; - - /** - * An internal helper class that preloads web audio via XHR. Note that this class and its methods are not documented - * properly to avoid generating HTML documentation. - * #class Loader - * @param {String} src The source of the sound to load. - * @param {Object} owner A reference to the class that created this instance. - * @constructor - */ - function Loader(src, owner) { - this._init(src, owner); - } - - var p = Loader.prototype; - p.constructor = Loader; - - // the request object for or XHR2 request - p.request = null; - - p.owner = null; - p.progress = -1; - - /** - * The source of the sound to load. Used by callback functions when we return this class. - * #property src - * @type {String} - */ - p.src = null; - - /** - * The decoded AudioBuffer array that is returned when loading is complete. - * #property result - * @type {AudioBuffer} - * @protected - */ - p.result = null; - - // Calbacks - /** - * The callback that fires when the load completes. This follows HTML tag naming. - * #property onload - * @type {Method} - */ - p.onload = null; - - /** - * The callback that fires as the load progresses. This follows HTML tag naming. - * #property onprogress - * @type {Method} - */ - p.onprogress = null; - - /** - * The callback that fires if the load hits an error. This follows HTML tag naming. - * #property onerror - * @type {Method} - * @protected - */ - p.onerror = null; - - // constructor - p._init = function (src, owner) { - this.src = src; - this.owner = owner; - }; - - /** - * Begin loading the content. - * #method load - * @param {String} src The path to the sound. - */ - p.load = function (src) { - if (src != null) {this.src = src;} - - this.request = new XMLHttpRequest(); - this.request.open("GET", this.src, true); - this.request.responseType = "arraybuffer"; - this.request.onload = createjs.proxy(this.handleLoad, this); - this.request.onerror = createjs.proxy(this.handleError, this); - this.request.onprogress = createjs.proxy(this.handleProgress, this); - - this.request.send(); - }; - - /** - * The loader has reported progress. - * - * <strong>Note</strong>: this is not a public API, but is used to allow preloaders to subscribe to load - * progress as if this is an HTML audio tag. This reason is why this still uses a callback instead of an event. - * #method handleProgress - * @param {event} event Progress event that gives event.loaded and event.total if server is configured correctly - * @protected - */ - p.handleProgress = function (event) { - if (!event || event.loaded > 0 && event.total == 0) { - return; // Sometimes we get no "total", so just ignore the progress event. - } - this.progress = event.loaded / event.total; - this.onprogress && this.onprogress({loaded:event.loaded, total:event.total, progress:this.progress}); - }; - - /** - * The sound has completed loading. - * #method handleLoad - * @protected - */ - p.handleLoad = function () { - this.owner.context.decodeAudioData(this.request.response, - createjs.proxy(this.handleAudioDecoded, this), - createjs.proxy(this.handleError, this)); - }; - - /** - * The audio has been decoded. - * #method handleAudioDecoded - * @protected - */ - p.handleAudioDecoded = function (decodedAudio) { - this.progress = 1; - this.result = decodedAudio; - this.owner.addPreloadResults(this.src, this.result); - this.onload && this.onload(this); - }; - - /** - * Errors have been caused by the loader. - * #method handleError - * @protected - */ - p.handleError = function (evt) { - this.owner.removeSound(this.src); - this.onerror && this.onerror(evt); - }; - - /** - * Remove all external references from loader - * #method cleanUp - */ - p.cleanUp = function () { - if(!this.request) {return;} - this.src = null; - this.owner = null; - this.request.onload = null; - this.request.onerror = null; - this.request.onprogress = null; - this.request = null; - this.onload = null; - this.onprogress = null; - this.onerror = null; - }; - - p.toString = function () { - return "[WebAudioPlugin Loader]"; - }; - - createjs.WebAudioPlugin.Loader = Loader; - -}()); -/* - * HTMLAudioPlugin - * Visit http://createjs.com/ for documentation, updates and examples. - * - * - * Copyright (c) 2012 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. - */ - -/** - * @module SoundJS - */ - -// namespace: this.createjs = this.createjs || {}; +//TODO verify that tags no longer need to be precreated (mac and pc) +//TODO modify this now that tags do not need to be precreated (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 "FlashPlugin"}}{{/crossLink}}. - * - * <h4>Known Browser and OS issues for HTML Audio</h4> - * <b>All browsers</b><br /> - * 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. This will be seen as soon as you register sounds, as - * tags are precreated to allow Chrome to load them. Please use {{#crossLink "Sound.MAX_INSTANCES"}}{{/crossLink}} as - * a guide to how many total audio tags you can safely use in all browsers. - * - * <b>IE html limitations</b><br /> - * <ul><li>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.</li> - * <li>MP3 encoding will not always work for audio tags if it's not default. We've found default encoding with - * 64kbps works.</li> - * <li>Occasionally very short samples will get cut off.</li> - * <li>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. - * Note that audio sprites can be used as a solution to this issue.</li></ul> - * - * <b>Safari limitations</b><br /> - * <ul><li>Safari requires Quicktime to be installed for audio playback.</li></ul> - * - * <b>iOS 6 limitations</b><br /> - * <ul><li>Note it is recommended to use {{#crossLink "WebAudioPlugin"}}{{/crossLink}} for iOS (6+)</li> - * <li>HTML Audio is disabled by default because</li> - * <li>can only have one <audio> tag</li> - * <li>can not preload or autoplay the audio</li> - * <li>can not cache the audio</li> - * <li>can not play the audio except inside a user initiated event.</li> - * <li>audio sprites can be used to mitigate some of these issues and are strongly recommended on iOS</li> - * </ul> - * - * <b>Android Native Browser limitations</b><br /> - * <ul><li>We have no control over audio volume. Only the user can set volume on their device.</li> - * <li>We can only play audio inside a user event (touch/click). This currently means you cannot loop sound or use a delay.</li></ul> - * <b> Android Chrome 26.0.1410.58 specific limitations</b><br /> - * <ul><li>Chrome reports true when you run createjs.Sound.BrowserDetect.isChrome, but is a different browser - * with different abilities.</li> - * <li>Can only play 1 sound at a time.</li> - * <li>Sound is not cached.</li> - * <li>Sound can only be loaded in a user initiated touch/click event.</li> - * <li>There is a delay before a sound is played, presumably while the src is loaded.</li> - * </ul> - * - * See {{#crossLink "Sound"}}{{/crossLink}} for general notes on known issues. - * - * @class HTMLAudioPlugin - * @constructor - */ - function HTMLAudioPlugin() { - this._init(); - } - - var s = HTMLAudioPlugin; - - /** - * The maximum number of instances that can be loaded and 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. - * @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 the SoundInstance {{#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; - - /** - * Deprecated now that we have audio sprite support. Audio sprites are strongly recommend on iOS. - * <li>it can only have one <audio> tag</li> - * <li>can not preload or autoplay the audio</li> - * <li>can not cache the audio</li> - * <li>can not play the audio except inside a user initiated event</li> - * - * @property enableIOS - * @type {Boolean} - * @default false - * @deprecated - */ - s.enableIOS = false; - - /** - * 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(); - if (s._capabilities == null) {return false;} - return true; - }; - - /** - * 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: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 - } - - var p = HTMLAudioPlugin.prototype; - p.constructor = HTMLAudioPlugin; - - // doc'd above - p._capabilities = null; - - /** - * Object hash indexed by the source of each file to indicate if an audio source is loaded, or loading. - * @property _audioSources - * @type {Object} - * @protected - * @since 0.4.0 - */ - p._audioSources = null; - - /** - * The default number of instances to allow. Used by {{#crossLink "Sound"}}{{/crossLink}} when a source - * is registered using the {{#crossLink "Sound/register"}}{{/crossLink}} method. This is only used if - * a value is not provided. - * - * <b>NOTE this property only exists as a limitation of HTML audio.</b> - * @property defaultNumChannels - * @type {Number} - * @default 2 - * @since 0.4.0 - */ - p.defaultNumChannels = 2; - - /** - * An initialization function run by the constructor - * @method _init - * @protected - */ - p._init = function () { - this._capabilities = s._capabilities; - this._audioSources = {}; - }; - - /** - * Pre-register a sound instance when preloading/setup. This is called by {{#crossLink "Sound"}}{{/crossLink}}. - * Note that this provides an object containing a tag used for preloading purposes, which - * <a href="http://preloadjs.com" target="_blank">PreloadJS</a> can use to assist with preloading. - * @method register - * @param {String} src The source of the audio - * @param {Number} instances The number of concurrently playing instances to allow for the channel at any time. - * @return {Object} A result object, containing a tag for preloading purposes and a numChannels value for internally - * controlling how many instances of a source can be played by default. - */ - p.register = function (src, instances) { - this._audioSources[src] = true; // Note this does not mean preloading has started - var channel = createjs.HTMLAudioPlugin.TagPool.get(src); - var tag = null; - var l = instances; - for (var i = 0; i < l; i++) { - tag = this._createTag(src); - channel.add(tag); - } - - return { - tag:tag // Return one instance for preloading purposes - }; - }; - - /** - * 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 (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; - }; - - /** - * Remove a sound added using {{#crossLink "HTMLAudioPlugin/register"}}{{/crossLink}}. Note this does not cancel - * a preload. - * @method removeSound - * @param {String} src The sound URI to unload. - * @since 0.4.1 - */ - p.removeSound = function (src) { - delete(this._audioSources[src]); - createjs.HTMLAudioPlugin.TagPool.remove(src); - }; - - /** - * Remove all sounds added using {{#crossLink "HTMLAudioPlugin/register"}}{{/crossLink}}. Note this does not cancel a preload. - * @method removeAllSounds - * @param {String} src The sound URI to unload. - * @since 0.4.1 - */ - p.removeAllSounds = function () { - this._audioSources = {}; - createjs.HTMLAudioPlugin.TagPool.removeAll(); - }; - - /** - * 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 {SoundInstance} A sound instance for playback and control. - */ - p.create = function (src, startTime, duration) { - // if this sound has not be registered, create a tag and preload it - if (!this.isPreloadStarted(src)) { - var channel = createjs.HTMLAudioPlugin.TagPool.get(src); - var tag = this._createTag(src); - tag.id = src; - channel.add(tag); - this.preload(src, {tag:tag}); - } - - return new createjs.HTMLAudioPlugin.SoundInstance(src, startTime, duration, this); - }; - - /** - * Checks if preloading has started for a specific source. - * @method isPreloadStarted - * @param {String} src The sound URI to check. - * @return {Boolean} If the preload has started. - * @since 0.4.0 - */ - p.isPreloadStarted = function (src) { - return (this._audioSources[src] != null); - }; - - /** - * Internally preload a sound. - * @method preload - * @param {String} src The sound URI to load. - * @param {Object} tag An HTML audio tag used to load src. - * @since 0.4.0 - */ - p.preload = function (src, tag) { - this._audioSources[src] = true; - new createjs.HTMLAudioPlugin.Loader(src, tag); - }; - - p.toString = function () { - return "[HTMLAudioPlugin]"; - }; - - createjs.HTMLAudioPlugin = HTMLAudioPlugin; -}()); - - -(function () { - - "use strict"; - - // NOTE Documentation for the SoundInstance class in WebAudioPlugin file. Each plugin generates a SoundInstance that - // follows the same interface. - function SoundInstance(src, startTime, duration, owner) { - this._init(src, startTime, duration, owner); - } - - var p = SoundInstance.prototype = new createjs.EventDispatcher(); - p.constructor = SoundInstance; - - p.src = null; - p.uniqueId = -1; - p.playState = null; - p._owner = null; - p.loaded = false; - p._offset = 0; - p._startTime = 0; - p._volume = 1; - if (createjs.definePropertySupported) { - Object.defineProperty(p, "volume", { - get: function() { - return this._volume; - }, - set: function(value) { - if (Number(value) == null) {return;} - value = Math.max(0, Math.min(1, value)); - this._volume = value; - this._updateVolume(); - } - }); - } - p.pan = 0; - p._duration = 0; - p._audioSpriteStopTime = null; // HTMLAudioPlugin only - p._remainingLoops = 0; - if (createjs.definePropertySupported) { - Object.defineProperty(p, "loop", { - get: function() { - return this._remainingLoops; - }, - set: function(value) { - if (this.tag != null) { - // remove looping - if (this._remainingLoops != 0 && value == 0) { - this.tag.loop = false; - this.tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); - } - // add looping - if (this._remainingLoops == 0 && value != 0) { - this.tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); - this.tag.loop = true; - } - } - this._remainingLoops = value; - } - }); - } - p._delayTimeoutId = null; - p.tag = null; - p._muted = false; - p.paused = false; - p._paused = false; - - // Proxies, make removing listeners easier. - p._endedHandler = null; - p._readyHandler = null; - p._stalledHandler = null; - p._audioSpriteEndHandler = null; - p.loopHandler = null; - -// Constructor - p._init = function (src, startTime, duration, owner) { - this.src = src; - this._startTime = startTime || 0; // convert ms to s as web audio handles everything in seconds - if (duration) { - this._duration = duration; - this._audioSpriteStopTime = (startTime + duration) * 0.001; - } else { - this._duration = createjs.HTMLAudioPlugin.TagPool.getDuration(this.src); - } - this._owner = owner; - - this._endedHandler = createjs.proxy(this._handleSoundComplete, this); - this._readyHandler = createjs.proxy(this._handleSoundReady, this); - this._stalledHandler = createjs.proxy(this._handleSoundStalled, this); - this.__audioSpriteEndHandler = createjs.proxy(this._handleAudioSpriteLoop, this); - this.loopHandler = createjs.proxy(this.handleSoundLoop, this); - }; - - p._sendEvent = function (type) { - var event = new createjs.Event(type); - this.dispatchEvent(event); - }; - - p._cleanUp = function () { - var tag = this.tag; - if (tag != null) { - tag.pause(); - this.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_SEEKED, this.loopHandler, false); - tag.removeEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE, this.__audioSpriteEndHandler, false); - - try { - tag.currentTime = this._startTime; - } catch (e) { - } // Reset Position - createjs.HTMLAudioPlugin.TagPool.setInstance(this.src, tag); - this.tag = null; - } - - clearTimeout(this._delayTimeoutId); - createjs.Sound._playFinished(this); - }; - - p._interrupt = function () { - if (this.tag == null) {return;} - this.playState = createjs.Sound.PLAY_INTERRUPTED; - this._cleanUp(); - this.paused = this._paused = false; - this._sendEvent("interrupted"); - }; - -// Public API - p.play = function (interrupt, delay, offset, loop, volume, pan) { - if (this.playState == createjs.Sound.PLAY_SUCCEEDED) { - if (interrupt instanceof Object) { - offset = interrupt.offset; - loop = interrupt.loop; - volume = interrupt.volume; - pan = interrupt.pan; - } - if (offset != null) { this.setPosition(offset) } - if (loop != null) { this.loop = loop; } - if (volume != null) { this.setVolume(volume); } - if (pan != null) { this.setPan(pan); } - if (this._paused) { this.resume(); } - return; - } - this._cleanUp(); - createjs.Sound._playInstance(this, interrupt, delay, offset, loop, volume, pan); - }; - - p._beginPlaying = function (offset, loop, volume, pan) { - var tag = this.tag = createjs.HTMLAudioPlugin.TagPool.getInstance(this.src); - if (tag == null) { - this.playFailed(); - return -1; - } - - tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED, this._endedHandler, false); - - // Reset this instance. - this._offset = offset; - this.volume = volume; - this._updateVolume(); - this._remainingLoops = loop; - - if (tag.readyState !== 4) { - 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(); - } else { - this._handleSoundReady(null); - } - - this._sendEvent("succeeded"); - return 1; - }; - - // Note: Sounds stall when trying to begin playback of a new audio instance when the existing instances - // has not loaded yet. This doesn't mean the sound will not play. - p._handleSoundStalled = function (event) { - this._cleanUp(); // OJR this will stop playback, we could remove this and let the developer decide how to handle stalled instances - this._sendEvent("failed"); - }; - - p._handleSoundReady = function (event) { - this.playState = createjs.Sound.PLAY_SUCCEEDED; - this.paused = this._paused = false; - this.tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_READY, this._readyHandler, false); - this.tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); - - if (this._offset >= this.getDuration()) { - this.playFailed(); - return; - } - this.tag.currentTime = (this._startTime + this._offset) * 0.001; - - if (this._audioSpriteStopTime) { - this.tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED, this._endedHandler, false); - this.tag.addEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE, this.__audioSpriteEndHandler, false); - } else { - if(this._remainingLoops != 0) { - this.tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); - this.tag.loop = true; - } - } - - this.tag.play(); - }; - - p.pause = function () { - if (!this._paused && this.playState == createjs.Sound.PLAY_SUCCEEDED && this.tag != null) { - this.paused = this._paused = true; - this.tag.pause(); - clearTimeout(this._delayTimeoutId); - return true; - } - return false; - }; - - p.resume = function () { - if (!this._paused || this.tag == null) {return false;} - this.paused = this._paused = false; - this.tag.play(); - return true; - }; - - p.stop = function () { - this._offset = 0; - this.pause(); - this.playState = createjs.Sound.PLAY_FINISHED; - this._cleanUp(); - return true; - }; - - p.setMasterVolume = function (value) { - this._updateVolume(); - }; - - p.setVolume = function (value) { - this.volume = value; - return true; - }; - - p._updateVolume = function () { - if (this.tag != null) { - var newVolume = (this._muted || createjs.Sound._masterMute) ? 0 : this._volume * createjs.Sound._masterVolume; - if (newVolume != this.tag.volume) {this.tag.volume = newVolume;} - } - }; - - p.getVolume = function (value) { - return this.volume; - }; - - p.setMasterMute = function (isMuted) { - this._updateVolume(); - }; - - p.setMute = function (isMuted) { - if (isMuted == null) {return false;} - this._muted = isMuted; - this._updateVolume(); - return true; - }; - - p.getMute = function () { - return this._muted; - }; - - // Can not set pan in HTML audio - p.setPan = function (value) { - return false; - }; - - p.getPan = function () { - return 0; - }; - - p.getPosition = function () { - if (this.tag == null) {return this._offset;} - return (this.tag.currentTime * 1000) - this._startTime; - }; - - p.setPosition = function (value) { - if (this.tag == null) { - this._offset = value - } else { - this.tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); - this.tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._handleSetPositionSeek, false); - try { - value = value + this._startTime; - this.tag.currentTime = value * 0.001; - } catch (error) { // Out of range - this._handleSetPositionSeek(null); - return false; - } - } - return true; - }; - - p._handleSetPositionSeek = function(event) { - if (this.tag == null) { return; } - this.tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._handleSetPositionSeek, false); - this.tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); - }; - - p.getDuration = function () { // NOTE this will always return 0 until sound has been played unless it is set - return this._duration; - }; - - p._handleSoundComplete = function (event) { - this._offset = 0; - this.playState = createjs.Sound.PLAY_FINISHED; - this._cleanUp(); - this._sendEvent("complete"); - }; - - // 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 - p._handleAudioSpriteLoop = function (event) { - if(this.tag.currentTime <= this._audioSpriteStopTime) {return;} - this.tag.pause(); - if(this._remainingLoops == 0) { - this._handleSoundComplete(null); - } else { - this._offset = 0; - this._remainingLoops--; - this.tag.currentTime = this._startTime * 0.001; - if(!this._paused) {this.tag.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.handleSoundLoop = function (event) { - this._offset = 0; - this._remainingLoops--; - if(this._remainingLoops == 0) { - this.tag.loop = false; - this.tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); - } - this._sendEvent("loop"); - }; - - p.playFailed = function () { - this.playState = createjs.Sound.PLAY_FAILED; - this._cleanUp(); - this._sendEvent("failed"); - }; - - p.toString = function () { - return "[HTMLAudioPlugin SoundInstance]"; - }; - - createjs.HTMLAudioPlugin.SoundInstance = SoundInstance; - -}()); - - -(function () { - - "use strict"; - - /** - * An internal helper class that preloads html audio via HTMLAudioElement tags. Note that PreloadJS will NOT use - * this load class like it does Flash and WebAudio plugins. - * Note that this class and its methods are not documented properly to avoid generating HTML documentation. - * #class Loader - * @param {String} src The source of the sound to load. - * @param {HTMLAudioElement} tag The audio tag of the sound to load. - * @constructor - * @protected - * @since 0.4.0 - */ - function Loader(src, tag) { - this._init(src, tag); - }; - - var p = Loader.prototype; - p.constructor = Loader; - - /** - * The source to be loaded. - * #property src - * @type {String} - * @default null - * @protected - */ - p.src = null; - - /** - * The tag to load the source with / into. - * #property tag - * @type {AudioTag} - * @default null - * @protected - */ - p.tag = null; - - /** - * An interval used to give us progress. - * #property preloadTimer - * @type {String} - * @default null - * @protected - */ - p.preloadTimer = null; - - // Proxies, make removing listeners easier. - p.loadedHandler = null; - - // constructor - p._init = function (src, tag) { - this.src = src; - this.tag = tag; - - this.preloadTimer = setInterval(createjs.proxy(this.preloadTick, this), 200); - - // 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. - // Note that canplaythrough callback doesn't work in Chrome, we have to use the event. - this.loadedHandler = createjs.proxy(this.sendLoadedEvent, this); // we need this bind to be able to remove event listeners - this.tag.addEventListener && this.tag.addEventListener("canplaythrough", this.loadedHandler); - if(this.tag.onreadystatechange == null) { - this.tag.onreadystatechange = createjs.proxy(this.sendLoadedEvent, this); - } else { - var f = this.tag.onreadystatechange; - this.tag.onreadystatechange = function() { - f(); - this.tag.onreadystatechange = createjs.proxy(this.sendLoadedEvent, this); - } - } - - this.tag.preload = "auto"; - //this.tag.src = src; - this.tag.load(); - }; - - /** - * Allows us to have preloading progress and tell when its done. - * #method preloadTick - * @protected - */ - p.preloadTick = function () { - var buffered = this.tag.buffered; - var duration = this.tag.duration; - - if (buffered.length > 0) { - if (buffered.end(0) >= duration - 1) { - this.handleTagLoaded(); - } - } - }; - - /** - * Internal handler for when a tag is loaded. - * #method handleTagLoaded - * @protected - */ - p.handleTagLoaded = function () { - clearInterval(this.preloadTimer); - }; - - /** - * Communicates back to Sound that a load is complete. - * #method sendLoadedEvent - * @param {Object} evt The load Event - */ - p.sendLoadedEvent = function (evt) { - this.tag.removeEventListener && this.tag.removeEventListener("canplaythrough", this.loadedHandler); // cleanup and so we don't send the event more than once - this.tag.onreadystatechange = null; // cleanup and so we don't send the event more than once - createjs.Sound._sendFileLoadEvent(this.src); // fire event or callback on Sound - - }; - - // used for debugging - p.toString = function () { - return "[HTMLAudioPlugin Loader]"; - }; - - createjs.HTMLAudioPlugin.Loader = Loader; - -}()); - - -(function () { - "use strict"; /** * The TagPool is an object pool for HTMLAudio tag instances. In Chrome, we have to pre-create the number of HTML * audio tag instances that we are going to play before we load the data, otherwise the audio stalls. * (Note: This seems to be a bug in Chrome) - * #class TagPool + * @class HTMLAudioTagPool * @param {String} src The source of the channel. * @protected */ function TagPool(src) { - this._init(src); - } + +//Public Properties + /** + * The source of the tag pool. + * #property src + * @type {String} + * @protected + */ + this.src = src; + + /** + * The total number of HTMLAudio tags in this pool. This is the maximum number of instance of a certain sound + * that can play at one time. + * #property length + * @type {Number} + * @default 0 + * @protected + */ + this.length = 0; + + /** + * The number of unused HTMLAudio tags. + * #property available + * @type {Number} + * @default 0 + * @protected + */ + this.available = 0; + + /** + * A list of all available tags in the pool. + * #property tags + * @type {Array} + * @protected + */ + this.tags = []; + + /** + * The duration property of all audio tags, converted to milliseconds, which originally is only available on the + * last tag in the tags array because that is the one that is loaded. + * #property + * @type {Number} + * @protected + */ + this.duration = 0; + }; + + var p = TagPool.prototype; + p.constructor = TagPool; var s = TagPool; + +// Static Properties /** * A hash lookup of each sound channel, indexed by the audio source. * #property tags @@ -4974,6 +6449,8 @@ this.createjs = this.createjs || {}; */ s.tags = {}; + +// Static Methods /** * Get a tag pool. If the pool doesn't exist, create it. * #method get @@ -5004,18 +6481,6 @@ this.createjs = this.createjs || {}; return true; }; - /** - * Delete all TagPools and all related tags. - * #method removeAll - * @static - */ - s.removeAll = function () { - for(var channel in s.tags) { - s.tags[channel].removeAll(); // this stops and removes all active instances - } - s.tags = {}; - }; - /** * Get a tag instance. This is a shortcut method. * #method getInstance @@ -5055,59 +6520,8 @@ this.createjs = this.createjs || {}; return channel.getDuration(); }; - var p = TagPool.prototype; - p.constructor = TagPool; - - /** - * The source of the tag pool. - * #property src - * @type {String} - * @protected - */ - p.src = null; - - /** - * The total number of HTMLAudio tags in this pool. This is the maximum number of instance of a certain sound - * that can play at one time. - * #property length - * @type {Number} - * @default 0 - * @protected - */ - p.length = 0; - - /** - * The number of unused HTMLAudio tags. - * #property available - * @type {Number} - * @default 0 - * @protected - */ - p.available = 0; - - /** - * A list of all available tags in the pool. - * #property tags - * @type {Array} - * @protected - */ - p.tags = null; - - /** - * The duration property of all audio tags, converted to milliseconds, which originally is only available on the - * last tag in the tags array because that is the one that is loaded. - * #property - * @type {Number} - * @protected - */ - p.duration = 0; - - // constructor - p._init = function (src) { - this.src = src; - this.tags = []; - }; +// Public Methods /** * Add an HTMLAudio tag into the pool. * #method add @@ -5172,9 +6586,522 @@ this.createjs = this.createjs || {}; }; p.toString = function () { - return "[HTMLAudioPlugin TagPool]"; + return "[HTMLAudioTagPool]"; }; - createjs.HTMLAudioPlugin.TagPool = TagPool; - + createjs.HTMLAudioTagPool = TagPool; }()); + +//############################################################################## +// 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); + + +// 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.setInstance(this.src, tag); + this._playbackResource = null; + } + }; + + p._beginPlaying = function (offset, loop, volume, pan) { + this._playbackResource = createjs.HTMLAudioTagPool.getInstance(this.src); + return this.AbstractSoundInstance__beginPlaying(offset, loop, volume, pan); + }; + + 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._updateDuration = function () { + this._audioSpriteStopTime = (startTime + 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}}. + * + * <h4>Known Browser and OS issues for HTML Audio</h4> + * <b>All browsers</b><br /> + * 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. This will be seen as soon as you register sounds, as + * tags are precreated to allow Chrome to load them. Please use {{#crossLink "Sound.MAX_INSTANCES"}}{{/crossLink}} as + * a guide to how many total audio tags you can safely use in all browsers. + * + * <b>IE html limitations</b><br /> + * <ul><li>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.</li> + * <li>MP3 encoding will not always work for audio tags if it's not default. We've found default encoding with + * 64kbps works.</li> + * <li>Occasionally very short samples will get cut off.</li> + * <li>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. + * Note that audio sprites can be used as a solution to this issue.</li></ul> + * + * <b>Safari limitations</b><br /> + * <ul><li>Safari requires Quicktime to be installed for audio playback.</li></ul> + * + * <b>iOS 6 limitations</b><br /> + * <ul><li>Note it is recommended to use {{#crossLink "WebAudioPlugin"}}{{/crossLink}} for iOS (6+)</li> + * <li>HTML Audio is disabled by default because</li> + * <li>can only have one <audio> tag</li> + * <li>can not preload or autoplay the audio</li> + * <li>can not cache the audio</li> + * <li>can not play the audio except inside a user initiated event.</li> + * <li>audio sprites can be used to mitigate some of these issues and are strongly recommended on iOS</li> + * </ul> + * + * <b>Android Native Browser limitations</b><br /> + * <ul><li>We have no control over audio volume. Only the user can set volume on their device.</li> + * <li>We can only play audio inside a user event (touch/click). This currently means you cannot loop sound or use a delay.</li></ul> + * <b> Android Chrome 26.0.1410.58 specific limitations</b><br /> + * <ul> <li>Can only play 1 sound at a time.</li> + * <li>Sound is not cached.</li> + * <li>Sound can only be loaded in a user initiated touch/click event.</li> + * <li>There is a delay before a sound is played, presumably while the src is loaded.</li> + * </ul> + * + * See {{#crossLink "Sound"}}{{/crossLink}} for general notes on known issues. + * + * @class HTMLAudioPlugin + * @extends AbstractPlugin + * @constructor + */ + function HTMLAudioPlugin() { + this.AbstractPlugin_constructor(); + + + // Public Properties + /** + * The default number of instances to allow. Used by {{#crossLink "Sound"}}{{/crossLink}} when a source + * is registered using the {{#crossLink "Sound/register"}}{{/crossLink}} method. This is only used if + * a value is not provided. + * + * <b>NOTE this property only exists as a limitation of HTML audio.</b> + * @property defaultNumChannels + * @type {Number} + * @default 2 + * @since 0.4.0 + */ + 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; + + +// Static Properties + /** + * The maximum number of instances that can be loaded and 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; + + /** + * Deprecated now that we have audio sprite support. Audio sprites are strongly recommend on iOS for the following reasons: + * <li>it can only have one <audio> tag</li> + * <li>can not preload or autoplay the audio</li> + * <li>can not cache the audio</li> + * <li>can not play the audio except inside a user initiated event</li> + * + * @property enableIOS + * @type {Boolean} + * @default false + * @deprecated + */ + s.enableIOS = false; + + +// 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(); + if (s._capabilities == null) {return false;} + return true; + }; + + /** + * 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: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 + }; + + +// public methods + p.register = function (src, instances) { + var channel = createjs.HTMLAudioTagPool.get(src); + var tag = null; + for (var i = 0; i < instances; i++) { + tag = this._createTag(src); + channel.add(tag); + } + + var loader = this.AbstractPlugin_register(src, instances); + 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; + + +// 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 + */ + // TODO move this to tagpool when it changes to be a standard object pool + 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.HTMLAudioPlugin = createjs.promote(HTMLAudioPlugin, "AbstractPlugin"); +}()); \ No newline at end of file