codecombat/vendor/scripts/soundjs-NEXT.combined.js

5180 lines
179 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @module SoundJS
*/
this.createjs = this.createjs || {};
(function () {
/**
* Static class holding library specific information such as the version and buildDate of the library.
* The SoundJS class has been renamed {{#crossLink "Sound"}}{{/crossLink}}. Please see {{#crossLink "Sound"}}{{/crossLink}}
* for information on using sound.
* @class SoundJS
**/
var s = createjs.SoundJS = createjs.SoundJS || {};
/**
* The version string for this release.
* @property version
* @type String
* @static
**/
s.version = /*version*/"NEXT"; // injected by build process
/**
* The build date for this release in UTC format.
* @property buildDate
* @type String
* @static
**/
s.buildDate = /*date*/"Mon, 27 Oct 2014 20:40:07 GMT"; // 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.
*/
/**
* @module CreateJS
*/
// namespace:
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;
/**
* Static initializer to mix EventDispatcher methods into a target object or prototype.
*
* EventDispatcher.initialize(MyClass.prototype); // add to the prototype of the class
* EventDispatcher.initialize(myObject); // add to a specific instance
*
* @method initialize
* @static
* @param {Object} target The target object to inject EventDispatcher methods into. This can be an instance or a
* prototype.
**/
EventDispatcher.initialize = function(target) {
target.addEventListener = p.addEventListener;
target.on = p.on;
target.removeEventListener = target.off = p.removeEventListener;
target.removeAllEventListeners = p.removeAllEventListeners;
target.hasEventListener = p.hasEventListener;
target.dispatchEvent = p.dispatchEvent;
target._dispatchEvent = p._dispatchEvent;
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:
/**
* Adds the specified event listener. Note that adding multiple listeners to the same function will result in
* multiple callbacks getting fired.
*
* <h4>Example</h4>
*
* displayObject.addEventListener("click", handleClick);
* function handleClick(event) {
* // Click happened.
* }
*
* @method addEventListener
* @param {String} type The string type of the event.
* @param {Function | Object} listener An object with a handleEvent method, or a function that will be called when
* the event is dispatched.
* @param {Boolean} [useCapture] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase.
* @return {Function | Object} Returns the listener for chaining or assignment.
**/
p.addEventListener = function(type, listener, useCapture) {
var listeners;
if (useCapture) {
listeners = this._captureListeners = this._captureListeners||{};
} else {
listeners = this._listeners = this._listeners||{};
}
var arr = listeners[type];
if (arr) { this.removeEventListener(type, listener, useCapture); }
arr = listeners[type]; // remove may have deleted the array
if (!arr) { listeners[type] = [listener]; }
else { arr.push(listener); }
return listener;
};
/**
* A shortcut method for using addEventListener that makes it easier to specify an execution scope, have a listener
* only run once, associate arbitrary data with the listener, and remove the listener.
*
* This method works by creating an anonymous wrapper function and subscribing it with addEventListener.
* The created anonymous function is returned for use with .removeEventListener (or .off).
*
* <h4>Example</h4>
*
* var listener = myBtn.on("click", handleClick, null, false, {count:3});
* function handleClick(evt, data) {
* data.count -= 1;
* console.log(this == myBtn); // true - scope defaults to the dispatcher
* if (data.count == 0) {
* alert("clicked 3 times!");
* myBtn.off("click", listener);
* // alternately: evt.remove();
* }
* }
*
* @method on
* @param {String} type The string type of the event.
* @param {Function | Object} listener An object with a handleEvent method, or a function that will be called when
* the event is dispatched.
* @param {Object} [scope] The scope to execute the listener in. Defaults to the dispatcher/currentTarget for function listeners, and to the listener itself for object listeners (ie. using handleEvent).
* @param {Boolean} [once=false] If true, the listener will remove itself after the first time it is triggered.
* @param {*} [data] Arbitrary data that will be included as the second parameter when the listener is called.
* @param {Boolean} [useCapture=false] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase.
* @return {Function} Returns the anonymous function that was created and assigned as the listener. This is needed to remove the listener later using .removeEventListener.
**/
p.on = function(type, listener, scope, once, data, useCapture) {
if (listener.handleEvent) {
scope = scope||listener;
listener = listener.handleEvent;
}
scope = scope||this;
return this.addEventListener(type, function(evt) {
listener.call(scope, evt, data);
once&&evt.remove();
}, useCapture);
};
/**
* Removes the specified event listener.
*
* <b>Important Note:</b> that you must pass the exact function reference used when the event was added. If a proxy
* function, or function closure is used as the callback, the proxy/closure reference must be used - a new proxy or
* closure will not work.
*
* <h4>Example</h4>
*
* displayObject.removeEventListener("click", handleClick);
*
* @method removeEventListener
* @param {String} type The string type of the event.
* @param {Function | Object} listener The listener function or object.
* @param {Boolean} [useCapture] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase.
**/
p.removeEventListener = function(type, listener, useCapture) {
var listeners = useCapture ? this._captureListeners : this._listeners;
if (!listeners) { return; }
var arr = listeners[type];
if (!arr) { return; }
for (var i=0,l=arr.length; i<l; i++) {
if (arr[i] == listener) {
if (l==1) { delete(listeners[type]); } // allows for faster checks.
else { arr.splice(i,1); }
break;
}
}
};
/**
* A shortcut to the removeEventListener method, with the same parameters and return value. This is a companion to the
* .on method.
*
* @method off
* @param {String} type The string type of the event.
* @param {Function | Object} listener The listener function or object.
* @param {Boolean} [useCapture] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase.
**/
p.off = p.removeEventListener;
/**
* Removes all listeners for the specified type, or all listeners of all types.
*
* <h4>Example</h4>
*
* // Remove all listeners
* displayObject.removeAllEventListeners();
*
* // Remove all click listeners
* displayObject.removeAllEventListeners("click");
*
* @method removeAllEventListeners
* @param {String} [type] The string type of the event. If omitted, all listeners for all types will be removed.
**/
p.removeAllEventListeners = function(type) {
if (!type) { this._listeners = this._captureListeners = null; }
else {
if (this._listeners) { delete(this._listeners[type]); }
if (this._captureListeners) { delete(this._captureListeners[type]); }
}
};
/**
* Dispatches the specified event to all listeners.
*
* <h4>Example</h4>
*
* // Use a string event
* this.dispatchEvent("complete");
*
* // Use an Event instance
* var event = new createjs.Event("progress");
* this.dispatchEvent(event);
*
* @method dispatchEvent
* @param {Object | String | Event} eventObj An object with a "type" property, or a string type.
* While a generic object will work, it is recommended to use a CreateJS Event instance. If a string is used,
* dispatchEvent will construct an Event instance with the specified type.
* @return {Boolean} Returns the value of eventObj.defaultPrevented.
**/
p.dispatchEvent = function(eventObj) {
if (typeof eventObj == "string") {
// won't bubble, so skip everything if there's no listeners:
var listeners = this._listeners;
if (!listeners || !listeners[eventObj]) { return false; }
eventObj = new createjs.Event(eventObj);
} else if (eventObj.target && eventObj.clone) {
// redispatching an active event object, so clone it:
eventObj = eventObj.clone();
}
try { eventObj.target = this; } catch (e) {} // try/catch allows redispatching of native events
if (!eventObj.bubbles || !this.parent) {
this._dispatchEvent(eventObj, 2);
} else {
var top=this, list=[top];
while (top.parent) { list.push(top = top.parent); }
var i, l=list.length;
// capture & atTarget
for (i=l-1; i>=0 && !eventObj.propagationStopped; i--) {
list[i]._dispatchEvent(eventObj, 1+(i==0));
}
// bubbling
for (i=1; i<l && !eventObj.propagationStopped; i++) {
list[i]._dispatchEvent(eventObj, 3);
}
}
return eventObj.defaultPrevented;
};
/**
* Indicates whether there is at least one listener for the specified event type.
* @method hasEventListener
* @param {String} type The string type of the event.
* @return {Boolean} Returns true if there is at least one listener for the specified event.
**/
p.hasEventListener = function(type) {
var listeners = this._listeners, captureListeners = this._captureListeners;
return !!((listeners && listeners[type]) || (captureListeners && captureListeners[type]));
};
/**
* Indicates whether there is at least one listener for the specified event type on this object or any of its
* ancestors (parent, parent's parent, etc). A return value of true indicates that if a bubbling event of the
* specified type is dispatched from this object, it will trigger at least one listener.
*
* This is similar to {{#crossLink "EventDispatcher/hasEventListener"}}{{/crossLink}}, but it searches the entire
* event flow for a listener, not just this object.
* @method willTrigger
* @param {String} type The string type of the event.
* @return {Boolean} Returns `true` if there is at least one listener for the specified event.
**/
p.willTrigger = function(type) {
var o = this;
while (o) {
if (o.hasEventListener(type)) { return true; }
o = o.parent;
}
return false;
};
/**
* @method toString
* @return {String} a string representation of the instance.
**/
p.toString = function() {
return "[EventDispatcher]";
};
// private methods:
/**
* @method _dispatchEvent
* @param {Object | String | Event} eventObj
* @param {Object} eventPhase
* @protected
**/
p._dispatchEvent = function(eventObj, eventPhase) {
var l, listeners = (eventPhase==1) ? this._captureListeners : this._listeners;
if (eventObj && listeners) {
var arr = listeners[eventObj.type];
if (!arr||!(l=arr.length)) { return; }
try { eventObj.currentTarget = this; } catch (e) {}
try { eventObj.eventPhase = eventPhase; } catch (e) {}
eventObj.removed = false;
arr = arr.slice(); // to avoid issues with items being removed or added during the dispatch
for (var i=0; i<l && !eventObj.immediatePropagationStopped; i++) {
var o = arr[i];
if (o.handleEvent) { o.handleEvent(eventObj); }
else { o(eventObj); }
if (eventObj.removed) {
this.off(eventObj.type, o, eventPhase==1);
eventObj.removed = false;
}
}
}
};
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
*/
// 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
* @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
**/
p.initialize = function(type, bubbles, cancelable) {
this.type = type;
this.bubbles = bubbles;
this.cancelable = cancelable;
this.timeStamp = (new Date()).getTime();
};
// public methods:
/**
* Sets {{#crossLink "Event/defaultPrevented"}}{{/crossLink}} to true.
* Mirrors the DOM event standard.
* @method preventDefault
**/
p.preventDefault = function() {
this.defaultPrevented = true;
};
/**
* Sets {{#crossLink "Event/propagationStopped"}}{{/crossLink}} to true.
* Mirrors the DOM event standard.
* @method stopPropagation
**/
p.stopPropagation = function() {
this.propagationStopped = true;
};
/**
* Sets {{#crossLink "Event/propagationStopped"}}{{/crossLink}} and
* {{#crossLink "Event/immediatePropagationStopped"}}{{/crossLink}} to true.
* Mirrors the DOM event standard.
* @method stopImmediatePropagation
**/
p.stopImmediatePropagation = function() {
this.immediatePropagationStopped = this.propagationStopped = true;
};
/**
* Causes the active listener to be removed via removeEventListener();
*
* myBtn.addEventListener("click", function(evt) {
* // do stuff...
* evt.remove(); // removes this listener.
* });
*
* @method remove
**/
p.remove = function() {
this.removed = true;
};
/**
* Returns a clone of the Event instance.
* @method clone
* @return {Event} a clone of the Event instance.
**/
p.clone = function() {
return new Event(this.type, this.bubbles, this.cancelable);
};
/**
* Provides a chainable shortcut method for setting a number of properties on the instance.
*
* @method set
* @param {Object} props A generic object containing properties to copy to the instance.
* @return {Event} Returns the instance the method is called on (useful for chaining calls.)
*/
p.set = function(props) {
for (var n in props) { this[n] = props[n]; }
return this;
};
/**
* Returns a string representation of this object.
* @method toString
* @return {String} a string representation of the instance.
**/
p.toString = function() {
return "[Event (type="+this.type+")]";
};
createjs.Event = Event;
}());
/*
* 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
*/
// 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 () {
"use strict";
/**
* The Sound class is the public API for creating sounds, controlling the overall sound levels, and managing plugins.
* All Sound APIs on this class are static.
*
* <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
* 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
* preloaded. It is recommended to preload sounds either internally using the register functions or externally using
* PreloadJS so they are ready when you want to use them.
*
* <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.
*
* <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
* 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.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 source path or event.src.
* instance.addEventListener("complete", createjs.proxy(this.handleComplete, this));
* instance.volume = 0.5;
* }
*
* The maximum number of concurrently playing instances of the same sound can be specified in the "data" argument
* of {{#crossLink "Sound/registerSound"}}{{/crossLink}}. Note that if not specified, the active plugin will apply
* a default limit. Currently HTMLAudioPlugin sets a default limit of 2, while WebAudioPlugin and FlashPlugin set a
* default limit of 100.
*
* createjs.Sound.registerSound("sound.mp3", "soundId", 4);
*
* Sound can be used as a plugin with PreloadJS to help preload audio properly. Audio preloaded with PreloadJS is
* automatically registered with the Sound class. When audio is not preloaded, Sound will do an automatic internal
* load. As a result, it may fail to play the first time play is called if the audio is not finished loading. Use the
* {{#crossLink "Sound/fileload"}}{{/crossLink}} event to determine when a sound has finished internally preloading.
* It is recommended that all audio is preloaded before it is played.
*
* var queue = new createjs.LoadQueue();
* queue.installPlugin(createjs.Sound);
*
* <b>Audio Sprites</b><br />
* SoundJS has added support for Audio Sprites, available as of version 0.5.3.
* For those unfamiliar with audio sprites, they are much like CSS sprites or sprite sheets: multiple audio assets
* grouped into a single file.
*
* Benefits of Audio Sprites
* <ul><li>More robust support for older browsers and devices that only allow a single audio instance, such as iOS 5.</li>
* <li>They provide a work around for the Internet Explorer 9 audio tag limit, which until now restricted how many
* different sounds we could load at once.</li>
* <li>Faster loading by only requiring a single network request for several sounds, especially on mobile devices
* where the network round trip for each file can add significant latency.</li></ul>
*
* Drawbacks of Audio Sprites
* <ul><li>No guarantee of smooth looping when using HTML or Flash audio. If you have a track that needs to loop
* smoothly and you are supporting non-web audio browsers, do not use audio sprites for that sound if you can avoid it.</li>
* <li>No guarantee that HTML audio will play back immediately, especially the first time. In some browsers (Chrome!),
* HTML audio will only load enough to play through so we rely on the “canplaythrough” event to determine if the audio is loaded.
* Since audio sprites must jump ahead to play specific sounds, the audio may not yet have downloaded.</li>
* <li>Audio sprites share the same core source, so if you have a sprite with 5 sounds and are limited to 2
* concurrently playing instances, that means you can only play 2 of the sounds at the same time.</li></ul>
*
* <h4>Example</h4>
* createjs.Sound.initializeDefaultPlugins();
* var assetsPath = "./assets/";
* var manifest = [{
* src:"MyAudioSprite.ogg", data: {
* audioSprite: [
* {id:"sound1", startTime:0, duration:500},
* {id:"sound2", startTime:1000, duration:400},
* {id:"sound3", startTime:1700, duration: 1000}
* ]}
* }
* ];
* createjs.Sound.alternateExtensions = ["mp3"];
* createjs.Sound.addEventListener("fileload", loadSound);
* createjs.Sound.registerManifest(manifest, 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.
*
* createjs.Sound.play("MyAudioSprite", {startTime: 1000, duration: 400});
*
* <b>Mobile Safe Approach</b><br />
* Mobile devices require sounds to be played inside of a user initiated event (touch/click) in varying degrees.
* As of SoundJS 0.4.1, you can launch a site inside of a user initiated event and have audio playback work. To
* enable as broadly as possible, the site needs to setup the Sound plugin in its initialization (for example via
* <code>createjs.Sound.initializeDefaultPlugins();</code>), and all sounds need to be played in the scope of the
* application. See the MobileSafe demo for a working example.
*
* <h4>Example</h4>
* document.getElementById("status").addEventListener("click", handleTouch, false); // works on Android and iPad
* function handleTouch(event) {
* document.getElementById("status").removeEventListener("click", handleTouch, false); // remove the listener
* var thisApp = new myNameSpace.MyApp(); // launch the app
* }
*
* <h4>Known Browser and OS issues</h4>
* <b>IE 9 HTML Audio 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, particularly in Internet Explorer. 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.</li></ul>
*
* <b>Firefox 25 Web Audio limitations</b>
* <ul><li>mp3 audio files do not load properly on all windows machines, reported
* <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=929969" target="_blank">here</a>. </br>
* For this reason it is recommended to pass another FF supported type (ie ogg) first until this bug is resolved, if possible.</li></ul>
* <b>Safari limitations</b><br />
* <ul><li>Safari requires Quicktime to be installed for audio playback.</li></ul>
*
* <b>iOS 6 Web Audio limitations</b><br />
* <ul><li>Sound is initially muted and will only unmute through play being called inside a user initiated event
* (touch/click).</li>
* <li>A bug exists that will distort un-cached web audio when a video element is present in the DOM that has audio at a different sampleRate.</li>
* <li>Note HTMLAudioPlugin is not supported on iOS by default. See {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}
* for more details.</li>
* </ul>
*
* <b>Android HTML Audio 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>
*
*
* @class Sound
* @static
* @uses EventDispatcher
*/
function Sound() {
throw "Sound cannot be instantiated";
}
var s = Sound;
// TODO DEPRECATED
/**
* REMOVED
* Use {{#crossLink "Sound/alternateExtensions:property"}}{{/crossLink}} instead
* @property DELIMITER
* @type {String}
* @default |
* @static
* @deprecated
*/
/**
* The interrupt value to interrupt any currently playing instance with the same source, if the maximum number of
* instances of the sound are already playing.
* @property INTERRUPT_ANY
* @type {String}
* @default any
* @static
*/
s.INTERRUPT_ANY = "any";
/**
* The interrupt value to interrupt the earliest currently playing instance with the same source that progressed the
* least distance in the audio track, if the maximum number of instances of the sound are already playing.
* @property INTERRUPT_EARLY
* @type {String}
* @default early
* @static
*/
s.INTERRUPT_EARLY = "early";
/**
* The interrupt value to interrupt the currently playing instance with the same source that progressed the most
* distance in the audio track, if the maximum number of instances of the sound are already playing.
* @property INTERRUPT_LATE
* @type {String}
* @default late
* @static
*/
s.INTERRUPT_LATE = "late";
/**
* The interrupt value to not interrupt any currently playing instances with the same source, if the maximum number of
* instances of the sound are already playing.
* @property INTERRUPT_NONE
* @type {String}
* @default none
* @static
*/
s.INTERRUPT_NONE = "none";
// The playState in plugins should be implemented with these values.
/**
* Defines the playState of an instance that is still initializing.
* @property PLAY_INITED
* @type {String}
* @default playInited
* @static
*/
s.PLAY_INITED = "playInited";
/**
* Defines the playState of an instance that is currently playing or paused.
* @property PLAY_SUCCEEDED
* @type {String}
* @default playSucceeded
* @static
*/
s.PLAY_SUCCEEDED = "playSucceeded";
/**
* Defines the playState of an instance that was interrupted by another instance.
* @property PLAY_INTERRUPTED
* @type {String}
* @default playInterrupted
* @static
*/
s.PLAY_INTERRUPTED = "playInterrupted";
/**
* Defines the playState of an instance that completed playback.
* @property PLAY_FINISHED
* @type {String}
* @default playFinished
* @static
*/
s.PLAY_FINISHED = "playFinished";
/**
* Defines the playState of an instance that failed to play. This is usually caused by a lack of available channels
* when the interrupt mode was "INTERRUPT_NONE", the playback stalled, or the sound could not be found.
* @property PLAY_FAILED
* @type {String}
* @default playFailed
* @static
*/
s.PLAY_FAILED = "playFailed";
/**
* A list of the default supported extensions that Sound will <i>try</i> to play. Plugins will check if the browser
* can play these types, so modifying this list before a plugin is initialized will allow the plugins to try to
* support additional media types.
*
* NOTE this does not currently work for {{#crossLink "FlashPlugin"}}{{/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>.
* @property SUPPORTED_EXTENSIONS
* @type {Array[String]}
* @default ["mp3", "ogg", "mpeg", "wav", "m4a", "mp4", "aiff", "wma", "mid"]
* @since 0.4.0
*/
s.SUPPORTED_EXTENSIONS = ["mp3", "ogg", "mpeg", "wav", "m4a", "mp4", "aiff", "wma", "mid"];
/**
* Some extensions use another type of extension support to play (one of them is a codex). This allows you to map
* that support so plugins can accurately determine if an extension is supported. Adding to this list can help
* plugins determine more accurately if an extension is supported.
*
* A useful list of extensions for each format can be found at <a href="http://html5doctor.com/html5-audio-the-state-of-play/" target="_blank">http://html5doctor.com/html5-audio-the-state-of-play/</a>.
* @property EXTENSION_MAP
* @type {Object}
* @since 0.4.0
* @default {m4a:"mp4"}
*/
s.EXTENSION_MAP = {
m4a:"mp4"
};
/**
* The RegExp pattern used to parse file URIs. This supports simple file names, as well as full domain URIs with
* query strings. The resulting match is: protocol:$1 domain:$2 path:$3 file:$4 extension:$5 query:$6.
* @property FILE_PATTERN
* @type {RegExp}
* @static
* @protected
*/
s.FILE_PATTERN = /^(?:(\w+:)\/{2}(\w+(?:\.\w+)*\/?))?([/.]*?(?:[^?]+)?\/)?((?:[^/?]+)\.(\w+))(?:\?(\S+)?)?$/;
/**
* Determines the default behavior for interrupting other currently playing instances with the same source, if the
* maximum number of instances of the sound are already playing. Currently the default is {{#crossLink "Sound/INTERRUPT_NONE:property"}}{{/crossLink}}
* but this can be set and will change playback behavior accordingly. This is only used when {{#crossLink "Sound/play"}}{{/crossLink}}
* is called without passing a value for interrupt.
* @property defaultInterruptBehavior
* @type {String}
* @default Sound.INTERRUPT_NONE, or "none"
* @static
* @since 0.4.0
*/
s.defaultInterruptBehavior = s.INTERRUPT_NONE; // OJR does s.INTERRUPT_ANY make more sense as default? Needs game dev testing to see which case makes more sense.
/**
* An array of extensions to attempt to use when loading sound, if the default is unsupported by the active plugin.
* These are applied in order, so if you try to Load Thunder.ogg in a browser that does not support ogg, and your
* extensions array is ["mp3", "m4a", "wav"] it will check mp3 support, then m4a, then wav. The audio files need
* to exist in the same location, as only the extension is altered.
*
* Note that regardless of which file is loaded, you can call {{#crossLink "Sound/createInstance"}}{{/crossLink}}
* and {{#crossLink "Sound/play"}}{{/crossLink}} using the same id or full source path passed for loading.
* <h4>Example</h4>
* var manifest = [
* {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.play("myPath/mySound.ogg"); // works regardless of what extension is supported. Note calling with ID is a better approach
*
* @property alternateExtensions
* @type {Array}
* @since 0.5.2
*/
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
* {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}.
* @property activePlugin
* @type {Object}
* @static
*/
s.activePlugin = null;
/**
* Determines if the plugins have been registered. If false, the first call to play() will instantiate the default
* plugins ({{#crossLink "WebAudioPlugin"}}{{/crossLink}}, followed by {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}).
* If plugins have been registered, but none are applicable, then sound playback will fail.
* @property _pluginsRegistered
* @type {Boolean}
* @default false
* @static
* @protected
*/
s._pluginsRegistered = false;
/**
* 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.
* @property _masterVolume
* @type {Number}
* @default 1
* @protected
* @since 0.4.0
*/
s._masterVolume = 1;
/**
* The master mute value, which affects all sounds. This is applies to all sound instances. This value can be set
* through {{#crossLink "Sound/setMute"}}{{/crossLink}} and accessed via {{#crossLink "Sound/getMute"}}{{/crossLink}}.
* @property _masterMute
* @type {Boolean}
* @default false
* @protected
* @static
* @since 0.4.0
*/
s._masterMute = false;
/**
* An array containing all currently playing instances. This allows Sound to control the volume, mute, and playback of
* all instances when using static APIs like {{#crossLink "Sound/stop"}}{{/crossLink}} and {{#crossLink "Sound/setVolume"}}{{/crossLink}}.
* When an instance has finished playback, it gets removed via the {{#crossLink "Sound/finishedPlaying"}}{{/crossLink}}
* method. If the user replays an instance, it gets added back in via the {{#crossLink "Sound/_beginPlaying"}}{{/crossLink}}
* method.
* @property _instances
* @type {Array}
* @protected
* @static
*/
s._instances = [];
/**
* An object hash storing objects with sound sources, startTime, and duration via there corresponding ID.
* @property _idHash
* @type {Object}
* @protected
* @static
*/
s._idHash = {};
/**
* An object hash that stores preloading sound sources via the parsed source that is passed to the plugin. Contains the
* source, id, and data that was passed in by the user. Parsed sources can contain multiple instances of source, id,
* and data.
* @property _preloadHash
* @type {Object}
* @protected
* @static
*/
s._preloadHash = {};
/**
* An object 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:
s.addEventListener = null;
s.removeEventListener = null;
s.removeAllEventListeners = null;
s.dispatchEvent = null;
s.hasEventListener = null;
s._listeners = null;
createjs.EventDispatcher.initialize(s); // inject EventDispatcher methods.
// Events
/**
* This event is fired when a file finishes loading internally. This event is fired for each loaded sound,
* so any handler methods should look up the <code>event.src</code> to handle a particular sound.
* @event fileload
* @param {Object} target The object that dispatched the event.
* @param {String} type The event type.
* @param {String} src The source of the sound that was loaded.
* @param {String} [id] The id passed in when the sound was registered. If one was not provided, it will be null.
* @param {Number|Object} [data] Any additional data associated with the item. If not provided, it will be undefined.
* @since 0.4.1
*/
/**
* 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
*/
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);
}
};
/**
* 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
* object, which is potentially modified by Sound. This helps when determining the correct path, as well as
* registering the audio instance(s) with Sound. This method should not be called, except by PreloadJS.
* @method getPreloadHandlers
* @return {Object} An object containing:
* <ul><li>callback: A preload callback that is fired when a file is added to PreloadJS, which provides
* Sound a mechanism to modify the load parameters, select the correct file format, register the sound, etc.</li>
* <li>types: A list of file types that are supported by Sound (currently supports "sound").</li>
* <li>extensions: A list of file extensions that are supported by Sound (see {{#crossLink "Sound.SUPPORTED_EXTENSIONS"}}{{/crossLink}}).</li></ul>
* @static
* @protected
*/
s.getPreloadHandlers = function () {
return {
callback:createjs.proxy(s.initLoad, s),
types:["sound"],
extensions:s.SUPPORTED_EXTENSIONS
};
};
/**
* Used by {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} to register a Sound plugin.
*
* @method _registerPlugin
* @param {Object} plugin The plugin class to install.
* @return {Boolean} Whether the plugin was successfully initialized.
* @static
* @private
*/
s._registerPlugin = function (plugin) {
// Note: Each plugin is passed in as a class reference, but we store the activePlugin as an instance
if (plugin.isSupported()) {
s.activePlugin = new plugin();
return true;
}
return false;
};
/**
* Register a list of Sound plugins, in order of precedence. To register a single plugin, pass a single element in the array.
*
* <h4>Example</h4>
* createjs.FlashPlugin.swfPath = "../src/SoundJS/";
* createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin, createjs.FlashPlugin]);
*
* @method registerPlugins
* @param {Array} plugins An array of plugins classes to install.
* @return {Boolean} Whether a plugin was successfully initialized.
* @static
*/
s.registerPlugins = function (plugins) {
s._pluginsRegistered = true;
for (var i = 0, l = plugins.length; i < l; i++) {
if (s._registerPlugin(plugins[i])) {
return true;
}
}
return false;
};
/**
* Initialize the default plugins. This method is automatically called when any audio is played or registered before
* the user has manually registered plugins, and enables Sound to work without manual plugin setup. Currently, the
* default plugins are {{#crossLink "WebAudioPlugin"}}{{/crossLink}} followed by {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}.
*
* <h4>Example</h4>
* if (!createjs.initializeDefaultPlugins()) { return; }
*
* @method initializeDefaultPlugins
* @returns {Boolean} True if a plugin was initialized, false otherwise.
* @since 0.4.0
*/
s.initializeDefaultPlugins = function () {
if (s.activePlugin != null) {return true;}
if (s._pluginsRegistered) {return false;}
if (s.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin])) {return true;}
return false;
};
/**
* Determines if Sound has been initialized, and a plugin has been activated.
*
* <h4>Example</h4>
* 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]);
* }
*
* @method isReady
* @return {Boolean} If Sound has initialized a plugin.
* @static
*/
s.isReady = function () {
return (s.activePlugin != null);
};
/**
* Get the active plugins capabilities, which help determine if a plugin can be used in the current environment,
* or if the plugin supports a specific feature. Capabilities include:
* <ul>
* <li><b>panning:</b> If the plugin can pan audio from left to right</li>
* <li><b>volume;</b> If the plugin can control audio volume.</li>
* <li><b>tracks:</b> The maximum number of audio tracks that can be played back at a time. This will be -1
* if there is no known limit.</li>
* <br />An entry for each file type in {{#crossLink "Sound/SUPPORTED_EXTENSIONS:property"}}{{/crossLink}}:
* <li><b>mp3:</b> If MP3 audio is supported.</li>
* <li><b>ogg:</b> If OGG audio is supported.</li>
* <li><b>wav:</b> If WAV audio is supported.</li>
* <li><b>mpeg:</b> If MPEG audio is supported.</li>
* <li><b>m4a:</b> If M4A audio is supported.</li>
* <li><b>mp4:</b> If MP4 audio is supported.</li>
* <li><b>aiff:</b> If aiff audio is supported.</li>
* <li><b>wma:</b> If wma audio is supported.</li>
* <li><b>mid:</b> If mid audio is supported.</li>
* </ul>
* @method getCapabilities
* @return {Object} An object containing the capabilities of the active plugin.
* @static
*/
s.getCapabilities = function () {
if (s.activePlugin == null) {return null;}
return s.activePlugin._capabilities;
};
/**
* Get a specific capability of the active plugin. See {{#crossLink "Sound/getCapabilities"}}{{/crossLink}} for a
* full list of capabilities.
*
* <h4>Example</h4>
* var maxAudioInstances = createjs.Sound.getCapability("tracks");
*
* @method getCapability
* @param {String} key The capability to retrieve
* @return {Number|Boolean} The value of the capability.
* @static
* @see getCapabilities
*/
s.getCapability = function (key) {
if (s.activePlugin == null) {return null;}
return s.activePlugin._capabilities[key];
};
/**
* Process manifest items from <a href="http://preloadjs.com" target="_blank">PreloadJS</a>. This method is intended
* for usage by a plugin, and not for direct interaction.
* @method initLoad
* @param {String | Object} src The src or object to load. This is usually a string path, but can also be an
* HTMLAudioElement or similar audio playback object.
* @param {String} [type] The type of object. Will likely be "sound" or null.
* @param {String} [id] An optional user-specified id that is used to play sounds.
* @param {Number|String|Boolean|Object} [data] Data associated with the item. Sound uses the data parameter as the
* number of channels for an audio instance, however a "channels" property can be appended to the data object if
* this property is used for other information. The audio channels will set a default based on plugin if no value is found.
* @return {Boolean|Object} An object with the modified values of those that were passed in, or false if the active
* plugin can not play the audio type.
* @protected
* @static
*/
s.initLoad = function (src, type, id, data) {
return s._registerSound(src, id, data);
};
/**
* Internal method for loading sounds. This should not be called directly.
*
* @method _registerSound
* @param {String | Object} src The source to load.
* @param {String} [id] An id specified by the user to play the sound later.
* @param {Number | Object} [data] Data associated with the item. Sound uses the data parameter as the number of
* channels for an audio instance, however a "channels" property can be appended to the data object if it is used
* for other information. The audio channels will set a default based on plugin if no value is found.
* Sound also uses the data property to hold an audioSprite array of objects in the following format {id, startTime, duration}.<br/>
* id used to play the sound later, in the same manner as a sound src with an id.<br/>
* startTime is the initial offset to start playback and loop from, in milliseconds.<br/>
* duration is the amount of time to play the clip for, in milliseconds.<br/>
* This allows Sound to support audio sprites that are played back by id.
* @return {Object} An object with the modified values that were passed in, which defines the sound.
* Returns false if the source cannot be parsed or no plugins can be initialized.
* Returns true if the source is already loaded.
* @static
* @private
* @since 0.5.3
*/
s._registerSound = function (src, id, data) {
if (!s.initializeDefaultPlugins()) {return false;}
var details = s._parsePath(src);
if (details == null) {return false;}
details.type = "sound";
details.id = id;
details.data = data;
var numChannels = s.activePlugin.defaultNumChannels || null;
if (data != null) {
if (!isNaN(data.channels)) {
numChannels = parseInt(data.channels);
} else if (!isNaN(data)) {
numChannels = parseInt(data);
}
if(data.audioSprite) {
var sp;
for(var i = data.audioSprite.length; i--; ) {
sp = data.audioSprite[i];
s._idHash[sp.id] = {src: details.src, startTime: parseInt(sp.startTime), duration: parseInt(sp.duration)};
}
}
}
if (id != null) {s._idHash[id] = {src: details.src}};
var loader = s.activePlugin.register(details.src, numChannels); // Note only HTML audio uses numChannels
SoundChannel.create(details.src, numChannels);
// return the number of instances to the user. This will also be returned in the load event.
if (data == null || !isNaN(data)) {
details.data = numChannels || SoundChannel.maxPerChannel();
} else {
details.data.channels = numChannels || SoundChannel.maxPerChannel();
}
details.tag = loader.tag;
if (loader.completeHandler) {details.completeHandler = loader.completeHandler;}
if (loader.type) {details.type = loader.type;}
return details;
};
/**
* Register an audio file for loading and future playback in Sound. This is automatically called when using
* <a href="http://preloadjs.com" target="_blank">PreloadJS</a>. 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>
* createjs.Sound.alternateExtensions = ["mp3"];
* createjs.Sound.addEventListener("fileload", handleLoad); // add an event listener for when load is completed
* createjs.Sound.registerSound("myAudioPath/mySound.ogg", "myID", 3);
*
* @method registerSound
* @param {String | Object} src The source or an Object with a "src" property
* @param {String} [id] An id specified by the user to play the sound later.
* @param {Number | Object} [data] Data associated with the item. Sound uses the data parameter as the number of
* channels for an audio instance, however a "channels" property can be appended to the data object if it is used
* for other information. The audio channels will set a default based on plugin if no value is found.
* Sound also uses the data property to hold an audioSprite array of objects in the following format {id, startTime, duration}.<br/>
* id used to play the sound later, in the same manner as a sound src with an id.<br/>
* startTime is the initial offset to start playback and loop from, in milliseconds.<br/>
* duration is the amount of time to play the clip for, in milliseconds.<br/>
* This allows Sound to support audio sprites that are played back by id.
* @param {string} basePath Set a path that will be prepended to src for loading.
* @return {Object} An object with the modified values that were passed in, which defines the sound.
* Returns false if the source cannot be parsed or no plugins can be initialized.
* Returns true if the source is already loaded.
* @static
* @since 0.4.0
*/
s.registerSound = function (src, id, data, basePath) {
if (src instanceof Object) {
basePath = id;
id = src.id;
data = src.data;
src = src.src;
}
if (basePath != null) {src = basePath + src;}
var details = s._registerSound(src, id, data);
if(!details) {return false;}
if (!s._preloadHash[details.src]) { s._preloadHash[details.src] = [];}
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);
} else {
if (s._preloadHash[details.src][0] == true) {return true;}
}
return details;
};
/**
* Register a manifest 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 = [
* {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);
*
* @method registerManifest
* @param {Array} manifest 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
* 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.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;
};
/**
* Remove a sound that has been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or
* {{#crossLink "Sound/registerManifest"}}{{/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.
*
* <h4>Example</h4>
* createjs.Sound.removeSound("myAudioBasePath/mySound.ogg");
* createjs.Sound.removeSound("myID");
*
* @method removeSound
* @param {String | Object} src The src or ID of the audio, or an Object with a "src" property
* @param {string} basePath Set a path that will be prepended to each src when removing.
* @return {Boolean} True if sound is successfully removed.
* @static
* @since 0.4.1
*/
s.removeSound = function(src, basePath) {
if (s.activePlugin == null) {return false;}
if (src instanceof Object) {src = src.src;}
src = s._getSrcById(src).src;
if (basePath != null) {src = basePath + src;}
var details = s._parsePath(src);
if (details == null) {return false;}
src = details.src;
for(var prop in s._idHash){
if(s._idHash[prop].src == src) {
delete(s._idHash[prop]);
}
}
// clear from SoundChannel, which also stops and deletes all instances
SoundChannel.removeSrc(src);
delete(s._preloadHash[src]);
s.activePlugin.removeSound(src);
return true;
};
/**
* Remove a manifest of audio files that have been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or
* {{#crossLink "Sound/registerManifest"}}{{/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 = [
* {src:"asset0.ogg", id:"example"},
* {src:"asset1.ogg", id:"1", data:6},
* {src:"asset2.mp3", id:"works"}
* ];
* createjs.Sound.removeManifest(manifest, assetPath);
*
* @method removeManifest
* @param {Array} manifest An array of objects to remove. Objects are expected to be in the format needed for
* {{#crossLink "Sound/removeSound"}}{{/crossLink}}: <code>{srcOrID:srcURIorID}</code>
* @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 in manifest was
* successfully removed.
* @static
* @since 0.4.1
*/
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;
};
/**
* Remove all sounds that have been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or
* {{#crossLink "Sound/registerManifest"}}{{/crossLink}}.
* <br />Note this will stop playback on all active sound instances before deleting them.
*
* <h4>Example</h4>
* createjs.Sound.removeAllSounds();
*
* @method removeAllSounds
* @static
* @since 0.4.1
*/
s.removeAllSounds = function() {
s._idHash = {};
s._preloadHash = {};
SoundChannel.removeAll();
if (s.activePlugin) {s.activePlugin.removeAllSounds();}
};
/**
* Check if a source has been loaded by internal preloaders. This is necessary to ensure that sounds that are
* not completed preloading will not kick off a new internal preload if they are played.
*
* <h4>Example</h4>
* var mySound = "assetPath/asset0.ogg";
* if(createjs.Sound.loadComplete(mySound) {
* createjs.Sound.play(mySound);
* }
*
* @method loadComplete
* @param {String} src The src or id that is being loaded.
* @return {Boolean} If the src is already loaded.
* @since 0.4.0
*/
s.loadComplete = function (src) {
if (!s.isReady()) { return false; }
var details = s._parsePath(src);
if (details) {
src = s._getSrcById(details.src).src;
} else {
src = s._getSrcById(src).src;
}
return (s._preloadHash[src][0] == true); // src only loads once, so if it's true for the first it's true for all
};
/**
* Parse the path of a sound, usually from a manifest item. alternate extensions will be attempted in order if the
* current extension is not supported
* @method _parsePath
* @param {String} value The path to an audio source.
* @return {Object} A formatted object that can be registered with the {{#crossLink "Sound/activePlugin:property"}}{{/crossLink}}
* and returned to a preloader like <a href="http://preloadjs.com" target="_blank">PreloadJS</a>.
* @protected
*/
s._parsePath = function (value) {
if (typeof(value) != "string") {value = value.toString();}
var match = value.match(s.FILE_PATTERN);
if (match == null) {return false;}
var name = match[4];
var ext = match[5];
var c = s.getCapabilities();
var i = 0;
while (!c[ext]) {
ext = s.alternateExtensions[i++];
if (i > s.alternateExtensions.length) { return null;} // no extensions are supported
}
value = value.replace("."+match[5], "."+ext);
var ret = {name:name, src:value, extension:ext};
return ret;
};
/* ---------------
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}},
* 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.
*
* <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
* 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);
* }
*
* NOTE to create an audio sprite that has not already been registered, both startTime and duration need to be set.
* This is only when creating a new audio sprite, not when playing using the id of an already registered audio sprite.
*
* @method play
* @param {String} src The src or ID of the audio.
* @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: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, pan, startTime, and duration (see the above code sample).
* @param {Number} [delay=0] The amount of time to delay the start of audio playback, in milliseconds.
* @param {Number} [offset=0] The offset from the start of the audio to begin playback, in milliseconds.
* @param {Number} [loop=0] How many times the audio loops when it reaches the end of playback. The default is 0 (no
* loops), and -1 can be used for infinite playback.
* @param {Number} [volume=1] The volume of the sound, between 0 and 1. Note that the master volume is applied
* against the individual volume.
* @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.
* @static
*/
s.play = function (src, interrupt, delay, offset, loop, volume, pan, startTime, duration) {
if (interrupt instanceof Object) {
delay = interrupt.delay;
offset = interrupt.offset;
loop = interrupt.loop;
volume = interrupt.volume;
pan = interrupt.pan;
startTime = interrupt.startTime;
duration = interrupt.duration;
interrupt = interrupt.interrupt;
}
var instance = s.createInstance(src, startTime, duration);
var ok = s._playInstance(instance, interrupt, delay, offset, loop, volume, pan);
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
* called safely but does nothing.
*
* <h4>Example</h4>
* var myInstance = null;
* createjs.Sound.addEventListener("fileload", handleLoad);
* createjs.Sound.registerSound("myAudioPath/mySound.mp3", "myID", 3);
* function handleLoad(event) {
* myInstance = createjs.Sound.createInstance("myID");
* // alternately we could call the following
* myInstance = createjs.Sound.createInstance("myAudioPath/mySound.mp3");
* }
*
* NOTE to create an audio sprite that has not already been registered, both startTime and duration need to be set.
* This is only when creating a new audio sprite, not when playing using the id of an already registered audio sprite.
*
* @method createInstance
* @param {String} src The src or ID of the audio.
* @param {Number} [startTime=null] To create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds.
* @param {Number} [duration=null] To create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds.
* @return {SoundInstance} A {{#crossLink "SoundInstance"}}{{/crossLink}} that can be controlled after it is created.
* Unsupported extensions will return the default SoundInstance.
* @since 0.4.0
*/
s.createInstance = function (src, startTime, duration) {
if (!s.initializeDefaultPlugins()) {return s._defaultSoundInstance;}
src = s._getSrcById(src);
var details = s._parsePath(src.src);
var instance = null;
if (details != null && details.src != null) {
SoundChannel.create(details.src);
if (startTime == null) {startTime = src.startTime;}
instance = s.activePlugin.create(details.src, startTime, duration || src.duration);
} else {
instance = Sound._defaultSoundInstance;
}
instance.uniqueId = s._lastID++;
return instance;
};
/**
* 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.
*
* <h4>Example</h4>
* createjs.Sound.setVolume(0.5);
*
* @method setVolume
* @param {Number} value The master volume value. The acceptable range is 0-1.
* @static
*/
s.setVolume = function (value) {
if (Number(value) == null) {return false;}
value = Math.max(0, Math.min(1, value));
s._masterVolume = value;
if (!this.activePlugin || !this.activePlugin.setVolume || !this.activePlugin.setVolume(value)) {
var instances = this._instances;
for (var i = 0, l = instances.length; i < l; i++) {
instances[i].setMasterVolume(value);
}
}
};
/**
* 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.
*
* <h4>Example</h4>
* var masterVolume = createjs.Sound.getVolume();
*
* @method getVolume
* @return {Number} The master volume, in a range of 0-1.
* @static
*/
s.getVolume = function () {
return s._masterVolume;
};
/**
* 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.
*
* <h4>Example</h4>
* createjs.Sound.setMute(true);
*
* @method setMute
* @param {Boolean} value Whether the audio should be muted or not.
* @return {Boolean} If the mute was set.
* @static
* @since 0.4.0
*/
s.setMute = function (value) {
if (value == null) {return false;}
this._masterMute = value;
if (!this.activePlugin || !this.activePlugin.setMute || !this.activePlugin.setMute(value)) {
var instances = this._instances;
for (var i = 0, l = instances.length; i < l; i++) {
instances[i].setMasterMute(value);
}
}
return true;
};
/**
* Returns the global mute value. To get the mute value of an individual instance, use SoundInstance
* {{#crossLink "SoundInstance/getMute"}}{{/crossLink}} instead.
*
* <h4>Example</h4>
* var muted = createjs.Sound.getMute();
*
* @method getMute
* @return {Boolean} The mute value of Sound.
* @static
* @since 0.4.0
*/
s.getMute = function () {
return this._masterMute;
};
/**
* 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}}.
*
* <h4>Example</h4>
* createjs.Sound.stop();
*
* @method stop
* @static
*/
s.stop = function () {
var instances = this._instances;
for (var i = instances.length; i--; ) {
instances[i].stop(); // NOTE stop removes instance from this._instances
}
};
/* ---------------
Internal methods
--------------- */
/**
* Play an instance. This is called by the static API, as well as from plugins. This allows the core class to
* control delays.
* @method _playInstance
* @param {SoundInstance} instance The {{#crossLink "SoundInstance"}}{{/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}}.
* <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] Time in milliseconds before playback begins.
* @param {Number} [offset=instance.offset] Time into the sound to begin playback in milliseconds. Defaults to the
* current value on the instance.
* @param {Number} [loop=0] The number of times to loop the audio. Use 0 for no loops, and -1 for an infinite loop.
* @param {Number} [volume] The volume of the sound between 0 and 1. Defaults to current instance value.
* @param {Number} [pan] The pan of the sound between -1 and 1. Defaults to current instance value.
* @return {Boolean} If the sound can start playing. Sounds that fail immediately will return false. Sounds that
* have a delay will return true, but may still fail to play.
* @protected
* @static
*/
s._playInstance = function (instance, interrupt, delay, offset, loop, volume, pan) {
if (interrupt instanceof Object) {
delay = interrupt.delay;
offset = interrupt.offset;
loop = interrupt.loop;
volume = interrupt.volume;
pan = interrupt.pan;
interrupt = interrupt.interrupt;
}
interrupt = interrupt || s.defaultInterruptBehavior;
if (delay == null) {delay = 0;}
if (offset == null) {offset = instance.getPosition();}
if (loop == null) {loop = instance.loop;}
if (volume == null) {volume = instance.volume;}
if (pan == null) {pan = instance.pan;}
if (delay == 0) {
var ok = s._beginPlaying(instance, interrupt, offset, loop, volume, pan);
if (!ok) {return false;}
} else {
//Note that we can't pass arguments to proxy OR setTimeout (IE only), so just wrap the function call.
// OJR WebAudio may want to handle this differently, so it might make sense to move this functionality into the plugins in the future
var delayTimeoutId = setTimeout(function () {
s._beginPlaying(instance, interrupt, offset, loop, volume, pan);
}, delay);
instance._delayTimeoutId = delayTimeoutId;
}
this._instances.push(instance);
return true;
};
/**
* Begin playback. This is called immediately or after delay by {{#crossLink "Sound/playInstance"}}{{/crossLink}}.
* @method _beginPlaying
* @param {SoundInstance} instance A {{#crossLink "SoundInstance"}}{{/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.
* @param {Number} [offset] Time in milliseconds into the sound to begin playback. Defaults to the current value on
* the instance.
* @param {Number} [loop=0] The number of times to loop the audio. Use 0 for no loops, and -1 for an infinite loop.
* @param {Number} [volume] The volume of the sound between 0 and 1. Defaults to the current value on the instance.
* @param {Number} [pan=instance.pan] The pan of the sound between -1 and 1. Defaults to current instance value.
* @return {Boolean} If the sound can start playing. If there are no available channels, or the instance fails to
* start, this will return false.
* @protected
* @static
*/
s._beginPlaying = function (instance, interrupt, offset, loop, volume, pan) {
if (!SoundChannel.add(instance, interrupt)) {
return false;
}
var result = instance._beginPlaying(offset, loop, volume, pan);
if (!result) {
var index = createjs.indexOf(this._instances, instance);
if (index > -1) {this._instances.splice(index, 1);}
return false;
}
return true;
};
/**
* Get the source of a sound via the ID passed in with a register call. If no ID is found the value is returned
* instead.
* @method _getSrcById
* @param {String} value The ID the sound was registered with.
* @return {String} The source of the sound if it has been registered with this ID or the value that was passed in.
* @protected
* @static
*/
s._getSrcById = function (value) {
return s._idHash[value] || {src: value};
};
/**
* A sound has completed playback, been interrupted, failed, or been stopped. This method removes the instance from
* Sound management. It will be added again, if the sound re-plays. Note that this method is called from the
* instances themselves.
* @method _playFinished
* @param {SoundInstance} instance The instance that finished playback.
* @protected
* @static
*/
s._playFinished = function (instance) {
SoundChannel.remove(instance);
var index = createjs.indexOf(this._instances, instance);
if (index > -1) {this._instances.splice(index, 1);} // OJR this will always be > -1, there is no way for an instance to exist without being added to this._instances
};
createjs.Sound = Sound;
/**
* An internal class that manages the number of active {{#crossLink "SoundInstance"}}{{/crossLink}} instances for
* each sound type. This method is only used internally by the {{#crossLink "Sound"}}{{/crossLink}} class.
*
* The number of sounds is artificially limited by Sound in order to prevent over-saturation of a
* single sound, as well as to stay within hardware limitations, although the latter may disappear with better
* browser support.
*
* When a sound is played, this class ensures that there is an available instance, or interrupts an appropriate
* sound that is already playing.
* #class SoundChannel
* @param {String} src The source of the instances
* @param {Number} [max=1] The number of instances allowed
* @constructor
* @protected
*/
function SoundChannel(src, max) {
this.init(src, max);
}
/* ------------
Static API
------------ */
/**
* A hash of channel instances indexed by source.
* #property channels
* @type {Object}
* @static
*/
SoundChannel.channels = {};
/**
* Create a sound channel. Note that if the sound channel already exists, this will fail.
* #method create
* @param {String} src The source for the channel
* @param {Number} max The maximum amount this channel holds. The default is {{#crossLink "SoundChannel.maxDefault"}}{{/crossLink}}.
* @return {Boolean} If the channels were created.
* @static
*/
SoundChannel.create = function (src, max) {
var channel = SoundChannel.get(src);
if (channel == null) {
SoundChannel.channels[src] = new SoundChannel(src, max);
return true;
}
return false;
};
/**
* Delete a sound channel, stop and delete all related instances. Note that if the sound channel does not exist, this will fail.
* #method remove
* @param {String} src The source for the channel
* @return {Boolean} If the channels were deleted.
* @static
*/
SoundChannel.removeSrc = function (src) {
var channel = SoundChannel.get(src);
if (channel == null) {return false;}
channel._removeAll(); // this stops and removes all active instances
delete(SoundChannel.channels[src]);
return true;
};
/**
* Delete all sound channels, stop and delete all related instances.
* #method removeAll
* @static
*/
SoundChannel.removeAll = function () {
for(var channel in SoundChannel.channels) {
SoundChannel.channels[channel]._removeAll(); // this stops and removes all active instances
}
SoundChannel.channels = {};
};
/**
* Add an instance to a sound channel.
* #method add
* @param {SoundInstance} instance The instance to add to the channel
* @param {String} interrupt The interrupt value to use. Please see the {{#crossLink "Sound/play"}}{{/crossLink}}
* for details on interrupt modes.
* @return {Boolean} The success of the method call. If the channel is full, it will return false.
* @static
*/
SoundChannel.add = function (instance, interrupt) {
var channel = SoundChannel.get(instance.src);
if (channel == null) {return false;}
return channel._add(instance, interrupt);
};
/**
* Remove an instance from the channel.
* #method remove
* @param {SoundInstance} instance The instance to remove from the channel
* @return The success of the method call. If there is no channel, it will return false.
* @static
*/
SoundChannel.remove = function (instance) {
var channel = SoundChannel.get(instance.src);
if (channel == null) {return false;}
channel._remove(instance);
return true;
};
/**
* Get the maximum number of sounds you can have in a channel.
* #method maxPerChannel
* @return {Number} The maximum number of sounds you can have in a channel.
*/
SoundChannel.maxPerChannel = function () {
return p.maxDefault;
};
/**
* Get a channel instance by its src.
* #method get
* @param {String} src The src to use to look up the channel
* @static
*/
SoundChannel.get = function (src) {
return SoundChannel.channels[src];
};
var p = SoundChannel.prototype;
p.constructor = SoundChannel;
/**
* The source of the channel.
* #property src
* @type {String}
*/
p.src = null;
/**
* The maximum number of instances in this channel. -1 indicates no limit
* #property max
* @type {Number}
*/
p.max = null;
/**
* The default value to set for max, if it isn't passed in. Also used if -1 is passed.
* #property maxDefault
* @type {Number}
* @default 100
* @since 0.4.0
*/
p.maxDefault = 100;
/**
* The current number of active instances.
* #property length
* @type {Number}
*/
p.length = 0;
/**
* Initialize the channel.
* #method init
* @param {String} src The source of the channel
* @param {Number} max The maximum number of instances in the channel
* @protected
*/
p.init = function (src, max) {
this.src = src;
this.max = max || this.maxDefault;
if (this.max == -1) {this.max = this.maxDefault;}
this._instances = [];
};
/**
* Get an instance by index.
* #method get
* @param {Number} index The index to return.
* @return {SoundInstance} The SoundInstance at a specific instance.
*/
p._get = function (index) {
return this._instances[index];
};
/**
* Add a new instance to the channel.
* #method add
* @param {SoundInstance} instance The instance to add.
* @return {Boolean} The success of the method call. If the channel is full, it will return false.
*/
p._add = function (instance, interrupt) {
if (!this._getSlot(interrupt, instance)) {return false;}
this._instances.push(instance);
this.length++;
return true;
};
/**
* Remove an instance from the channel, either when it has finished playing, or it has been interrupted.
* #method remove
* @param {SoundInstance} instance The instance to remove
* @return {Boolean} The success of the remove call. If the instance is not found in this channel, it will
* return false.
*/
p._remove = function (instance) {
var index = createjs.indexOf(this._instances, instance);
if (index == -1) {return false;}
this._instances.splice(index, 1);
this.length--;
return true;
};
/**
* Stop playback and remove all instances from the channel. Usually in response to a delete call.
* #method removeAll
*/
p._removeAll = function () {
// Note that stop() removes the item from the list
for (var i=this.length-1; i>=0; i--) {
this._instances[i].stop();
}
};
/**
* Get an available slot depending on interrupt value and if slots are available.
* #method getSlot
* @param {String} interrupt The interrupt value to use.
* @param {SoundInstance} 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.
*/
p._getSlot = function (interrupt, instance) {
var target, replacement;
if (interrupt != Sound.INTERRUPT_NONE) {
// First replacement candidate
replacement = this._get(0);
if (replacement == null) {
return true;
}
}
for (var i = 0, l = this.max; i < l; i++) {
target = this._get(i);
// Available Space
if (target == null) {
return true;
}
// Audio is complete or not playing
if (target.playState == Sound.PLAY_FINISHED ||
target.playState == Sound.PLAY_INTERRUPTED ||
target.playState == Sound.PLAY_FAILED) {
replacement = target;
break;
}
if (interrupt == Sound.INTERRUPT_NONE) {
continue;
}
// Audio is a better candidate than the current target, according to playhead
if ((interrupt == Sound.INTERRUPT_EARLY && target.getPosition() < replacement.getPosition()) ||
(interrupt == Sound.INTERRUPT_LATE && target.getPosition() > replacement.getPosition())) {
replacement = target;
}
}
if (replacement != null) {
replacement._interrupt();
this._remove(replacement);
return true;
}
return false;
};
p.toString = function () {
return "[Sound SoundChannel]";
};
// do not add SoundChannel to namespace
// 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 () {
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();
/**
* 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);
};
BrowserDetect.init();
createjs.Sound.BrowserDetect = BrowserDetect;
}());
/*
* 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.
*/
/**
* @module SoundJS
*/
// namespace:
this.createjs = this.createjs || {};
(function () {
"use strict";
/**
* Play sounds using Web Audio in the browser. The WebAudioPlugin is currently the default plugin, and will be used
* anywhere that it is supported. To change plugin priority, check out the Sound API
* {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} method.
* <h4>Known Browser and OS issues for Web Audio</h4>
* <b>Firefox 25</b>
* <ul><li>mp3 audio files do not load properly on all windows machines, reported
* <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=929969" target="_blank">here</a>. </br>
* For this reason it is recommended to pass another FF supported type (ie ogg) first until this bug is resolved, if possible.</li></ul>
* <br />
* <b>Webkit (Chrome and Safari)</b>
* <ul><li>AudioNode.disconnect does not always seem to work. This can cause the file size to grow over time if you
* are playing a lot of audio files.</li></ul>
* <br />
* <b>iOS 6 limitations</b>
* <ul><li>Sound is initially muted and will only unmute through play being called inside a user initiated event (touch/click).</li>
* <li>A bug exists that will distort uncached audio when a video element is present in the DOM. You can avoid this bug
* by ensuring the audio and video audio share the same sampleRate.</li>
* </ul>
* @class WebAudioPlugin
* @constructor
* @since 0.4.0
*/
function WebAudioPlugin() {
this._init();
}
var s = WebAudioPlugin;
/**
* The capabilities of the plugin. This is generated via the {{#crossLink "WebAudioPlugin/_generateCapabilities:method"}}{{/crossLink}}
* method and is used internally.
* @property _capabilities
* @type {Object}
* @default null
* @protected
* @static
*/
s._capabilities = null;
/**
* Determine if the plugin can be used in the current browser/OS.
* @method isSupported
* @return {Boolean} If the plugin can be initialized.
* @static
*/
s.isSupported = function () {
// check if this is some kind of mobile device, Web Audio works with local protocol under PhoneGap and it is unlikely someone is trying to run a local file
var isMobilePhoneGap = createjs.Sound.BrowserDetect.isIOS || createjs.Sound.BrowserDetect.isAndroid || createjs.Sound.BrowserDetect.isBlackberry;
// OJR isMobile may be redundant with _isFileXHRSupported available. Consider removing.
if (location.protocol == "file:" && !isMobilePhoneGap && !this._isFileXHRSupported()) { return false; } // Web Audio requires XHR, which is not usually available locally
s._generateCapabilities();
if (s.context == null) {return false;}
return true;
};
/**
* Determine if XHR is supported, which is necessary for web audio.
* @method _isFileXHRSupported
* @return {Boolean} If XHR is supported.
* @since 0.4.2
* @protected
* @static
*/
s._isFileXHRSupported = function() {
// it's much easier to detect when something goes wrong, so let's start optimistically
var supported = true;
var xhr = new XMLHttpRequest();
try {
xhr.open("GET", "WebAudioPluginTest.fail", false); // loading non-existant file triggers 404 only if it could load (synchronous call)
} catch (error) {
// catch errors in cases where the onerror is passed by
supported = false;
return supported;
}
xhr.onerror = function() { supported = false; }; // cause irrelevant
// with security turned off, we can get empty success results, which is actually a failed read (status code 0?)
xhr.onload = function() { supported = this.status == 404 || (this.status == 200 || (this.status == 0 && this.response != "")); };
try {
xhr.send();
} catch (error) {
// catch errors in cases where the onerror is passed by
supported = false;
}
return supported;
};
/**
* Determine the capabilities of the plugin. Used internally. Please see the Sound API {{#crossLink "Sound/getCapabilities"}}{{/crossLink}}
* method for an overview of plugin capabilities.
* @method _generateCapabilities
* @static
* @protected
*/
s._generateCapabilities = function () {
if (s._capabilities != null) {return;}
// Web Audio can be in any formats supported by the audio element, from http://www.w3.org/TR/webaudio/#AudioContext-section
var t = document.createElement("audio");
if (t.canPlayType == null) {return null;}
if (window.AudioContext) {
s.context = new AudioContext();
} else if (window.webkitAudioContext) {
s.context = new webkitAudioContext();
} else {
return null;
}
s._compatibilitySetUp();
// playing this inside of a touch event will enable audio on iOS, which starts muted
s.playEmptySound();
s._capabilities = {
panning:true,
volume:true,
tracks:-1
};
// determine which extensions our browser supports for this plugin by iterating through Sound.SUPPORTED_EXTENSIONS
var supportedExtensions = createjs.Sound.SUPPORTED_EXTENSIONS;
var extensionMap = createjs.Sound.EXTENSION_MAP;
for (var i = 0, l = supportedExtensions.length; i < l; i++) {
var ext = supportedExtensions[i];
var playType = extensionMap[ext] || ext;
s._capabilities[ext] = (t.canPlayType("audio/" + ext) != "no" && t.canPlayType("audio/" + ext) != "") || (t.canPlayType("audio/" + playType) != "no" && t.canPlayType("audio/" + playType) != "");
} // OJR another way to do this might be canPlayType:"m4a", codex: mp4
// 0=no output, 1=mono, 2=stereo, 4=surround, 6=5.1 surround.
// See http://www.w3.org/TR/webaudio/#AudioChannelSplitter for more details on channels.
if (s.context.destination.numberOfChannels < 2) {
s._capabilities.panning = false;
}
};
/**
* Set up compatibility if only deprecated web audio calls are supported.
* See http://www.w3.org/TR/webaudio/#DeprecationNotes
* Needed so we can support new browsers that don't support deprecated calls (Firefox) as well as old browsers that
* don't support new calls.
*
* @method _compatibilitySetUp
* @static
* @protected
* @since 0.4.2
*/
s._compatibilitySetUp = function() {
s._panningModel = "equalpower";
//assume that if one new call is supported, they all are
if (s.context.createGain) { return; }
// simple name change, functionality the same
s.context.createGain = s.context.createGainNode;
// source node, add to prototype
var audioNode = s.context.createBufferSource();
audioNode.__proto__.start = audioNode.__proto__.noteGrainOn; // note that noteGrainOn requires all 3 parameters
audioNode.__proto__.stop = audioNode.__proto__.noteOff;
// panningModel
s._panningModel = 0;
};
/**
* 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);
};
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
*/
p._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}
*/
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);
};
/**
* 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.
* @method _updateVolume
* @protected
*/
p._updateVolume = function () {
var newVolume = createjs.Sound._masterMute ? 0 : this._volume;
if (newVolume != this.gainNode.gain.value) {
this.gainNode.gain.value = newVolume;
}
};
/**
* 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;
}());
(function () {
"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 || {};
(function () {
"use strict";
/**
* Play sounds using HTML &lt;audio&gt; 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 &lt;audio&gt; 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 &lt;audio&gt; 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
* @param {String} src The source of the channel.
* @protected
*/
function TagPool(src) {
this._init(src);
}
var s = TagPool;
/**
* A hash lookup of each sound channel, indexed by the audio source.
* #property tags
* @static
* @protected
*/
s.tags = {};
/**
* Get a tag pool. If the pool doesn't exist, create it.
* #method get
* @param {String} src The source file used by the audio tag.
* @static
* @protected
*/
s.get = function (src) {
var channel = s.tags[src];
if (channel == null) {
channel = s.tags[src] = new TagPool(src);
}
return channel;
};
/**
* Delete a TagPool and all related tags. Note that if the TagPool does not exist, this will fail.
* #method remove
* @param {String} src The source for the tag
* @return {Boolean} If the TagPool was deleted.
* @static
*/
s.remove = function (src) {
var channel = s.tags[src];
if (channel == null) {return false;}
channel.removeAll();
delete(s.tags[src]);
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
* @param {String} src The source file used by the audio tag.
* @static
* @protected
*/
s.getInstance = function (src) {
var channel = s.tags[src];
if (channel == null) {return null;}
return channel.get();
};
/**
* Return a tag instance. This is a shortcut method.
* #method setInstance
* @param {String} src The source file used by the audio tag.
* @param {HTMLElement} tag Audio tag to set.
* @static
* @protected
*/
s.setInstance = function (src, tag) {
var channel = s.tags[src];
if (channel == null) {return null;}
return channel.set(tag);
};
/**
* Gets the duration of the src audio in milliseconds
* #method getDuration
* @param {String} src The source file used by the audio tag.
* @return {Number} Duration of src in milliseconds
*/
s.getDuration= function (src) {
var channel = s.tags[src];
if (channel == null) {return 0;}
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 = [];
};
/**
* Add an HTMLAudio tag into the pool.
* #method add
* @param {HTMLAudioElement} tag A tag to be used for playback.
*/
p.add = function (tag) {
this.tags.push(tag);
this.length++;
this.available++;
};
/**
* Remove all tags from the channel. Usually in response to a delete call.
* #method removeAll
*/
p.removeAll = function () {
var tag;
while(this.length--) {
tag = this.tags[this.length];
if(tag.parentNode) {
tag.parentNode.removeChild(tag);
}
delete(this.tags[this.length]); // NOTE that the audio playback is already stopped by this point
}
this.src = null;
this.tags.length = 0;
};
/**
* Get an HTMLAudioElement for immediate playback. This takes it out of the pool.
* #method get
* @return {HTMLAudioElement} An HTML audio tag.
*/
p.get = function () {
if (this.tags.length == 0) {return null;}
this.available = this.tags.length;
var tag = this.tags.pop();
if (tag.parentNode == null) {document.body.appendChild(tag);}
return tag;
};
/**
* Put an HTMLAudioElement back in the pool for use.
* #method set
* @param {HTMLAudioElement} tag HTML audio tag
*/
p.set = function (tag) {
var index = createjs.indexOf(this.tags, tag);
if (index == -1) {this.tags.push(tag);}
this.available = this.tags.length;
};
/**
* Gets the duration for the src audio and on first call stores it to this.duration
* #method getDuration
* @return {Number} Duration of the src in milliseconds
*/
p.getDuration = function () {
// this will work because this will be only be run the first time a sound instance is created and before any tags are taken from the pool
if (!this.duration) {this.duration = this.tags[this.tags.length - 1].duration * 1000;}
return this.duration;
};
p.toString = function () {
return "[HTMLAudioPlugin TagPool]";
};
createjs.HTMLAudioPlugin.TagPool = TagPool;
}());