From 2bbda206e7336088dbfdbf13f599cee770ed4f4b Mon Sep 17 00:00:00 2001 From: Scott Erickson <sderickson@gmail.com> Date: Wed, 7 May 2014 10:59:12 -0700 Subject: [PATCH 1/2] Updated createjs. --- vendor/scripts/SpriteContainer.js | 1 + vendor/scripts/SpriteStage.js | 1 + vendor/scripts/easeljs-NEXT.combined.js | 171 ++- vendor/scripts/preloadjs-NEXT.combined.js | 126 ++- vendor/scripts/soundjs-NEXT.combined.js | 1222 +++++++++------------ vendor/scripts/tweenjs-NEXT.combined.js | 12 +- 6 files changed, 722 insertions(+), 811 deletions(-) diff --git a/vendor/scripts/SpriteContainer.js b/vendor/scripts/SpriteContainer.js index 49f31a26f..885fa6946 100644 --- a/vendor/scripts/SpriteContainer.js +++ b/vendor/scripts/SpriteContainer.js @@ -41,6 +41,7 @@ this.createjs = this.createjs||{}; * - all children (with the exception of DOMElement) MUST use the same spriteSheet. * * <h4>Example</h4> + * * var data = { * images: ["sprites.jpg"], * frames: {width:50, height:50}, diff --git a/vendor/scripts/SpriteStage.js b/vendor/scripts/SpriteStage.js index 926de4a71..df88567fa 100644 --- a/vendor/scripts/SpriteStage.js +++ b/vendor/scripts/SpriteStage.js @@ -403,6 +403,7 @@ var p = SpriteStage.prototype = new createjs.Stage(); * Children also MUST have either an image or spriteSheet defined on them (unless it's a DOMElement). * * <h4>Example</h4> + * * addChildAt(child1, index); * * You can also add multiple children, such as: diff --git a/vendor/scripts/easeljs-NEXT.combined.js b/vendor/scripts/easeljs-NEXT.combined.js index 9e51cf98c..84543cfe7 100644 --- a/vendor/scripts/easeljs-NEXT.combined.js +++ b/vendor/scripts/easeljs-NEXT.combined.js @@ -31,6 +31,7 @@ * files of each library and are available on the createsjs namespace directly. * * <h4>Example</h4> + * * myObject.addEventListener("change", createjs.proxy(myMethod, scope)); * * @module CreateJS @@ -240,6 +241,18 @@ var p = Event.prototype; 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. @@ -862,6 +875,7 @@ this.createjs = this.createjs||{}; * should not be instantiated. * * <h4>Example</h4> + * * createjs.Ticker.addEventListener("tick", handleTick); * function handleTick(event) { * // Actions carried out each frame @@ -937,6 +951,7 @@ var Ticker = function() { * {{#crossLink "Ticker/setPaused"}}{{/crossLink}}. * * <h4>Example</h4> + * * createjs.Ticker.addEventListener("tick", handleTick); * function handleTick(event) { * console.log("Paused:", event.paused, event.delta); @@ -1229,6 +1244,7 @@ var Ticker = function() { * callback when Ticker was paused. This is no longer the case. * * <h4>Example</h4> + * * createjs.Ticker.addEventListener("tick", handleTick); * createjs.Ticker.setPaused(true); * function handleTick(event) { @@ -1251,6 +1267,7 @@ var Ticker = function() { * callback when Ticker was paused. This is no longer the case. * * <h4>Example</h4> + * * createjs.Ticker.addEventListener("tick", handleTick); * createjs.Ticker.setPaused(true); * function handleTick(event) { @@ -2279,8 +2296,9 @@ this.createjs = this.createjs||{}; * Represents a point on a 2 dimensional x / y coordinate system. * * <h4>Example</h4> - * var point = new Point(0, 100); - * + * + * var point = new createjs.Point(0, 100); + * * @class Point * @param {Number} [x=0] X position. * @param {Number} [y=0] Y position. @@ -2393,7 +2411,8 @@ this.createjs = this.createjs||{}; /** * Represents a rectangle as defined by the points (x, y) and (x+width, y+height). * - * @example + * <h4>Example</h4> + * * var rect = new createjs.Rectangle(0, 0, 100, 100); * * @class Rectangle @@ -2789,6 +2808,7 @@ this.createjs = this.createjs||{}; * via its <code>shadow</code> property. * * <h4>Example</h4> + * * myImage.shadow = new createjs.Shadow("#000000", 5, 5, 10); * * @class Shadow @@ -3427,6 +3447,7 @@ Command.prototype.exec = function(scope) { this.f.apply(scope, this.params); }; * context of an Easel display list. * * <h4>Example</h4> + * * var g = new createjs.Graphics(); * g.setStrokeStyle(1); * g.beginStroke(createjs.Graphics.getRGB(0,0,0)); @@ -3760,7 +3781,7 @@ var p = Graphics.prototype; /** * Draws only the path described for this Graphics instance, skipping any non-path instructions, including fill and - * stroke descriptions. Used by <code>DisplayObject.clippingPath</code> to draw the clipping path, for example. + * stroke descriptions. Used for <code>DisplayObject.mask</code> to draw the clipping path, for example. * @method drawAsPath * @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. **/ @@ -5785,9 +5806,8 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); }; /** - * Tests whether the display object intersects the specified local point (ie. draws a pixel with alpha > 0 at - * the specified position). This ignores the alpha, shadow and compositeOperation of the display object, and all - * transform properties including regX/Y. + * Tests whether the display object intersects the specified point in local coordinates (ie. draws a pixel with alpha > 0 at + * the specified position). This ignores the alpha, shadow, hitArea, mask, and compositeOperation of the display object. * * <h4>Example</h4> * @@ -5804,7 +5824,7 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); * local Point. */ p.hitTest = function(x, y) { - // TODO: update with support for .hitArea and update hitArea docs? + // TODO: update with support for .hitArea & .mask and update hitArea / mask docs? var ctx = DisplayObject._hitTestContext; ctx.setTransform(1, 0, 0, 1, -x, -y); this.draw(ctx); @@ -5816,7 +5836,7 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); }; /** - * Provides a chainable shortcut method for setting a number of properties on a DisplayObject instance. + * Provides a chainable shortcut method for setting a number of properties on the instance. * * <h4>Example</h4> * @@ -5826,7 +5846,7 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); * * @method set * @param {Object} props A generic object containing properties to copy to the DisplayObject instance. - * @return {DisplayObject} Returns The DisplayObject instance the method is called on (useful for chaining calls.) + * @return {DisplayObject} 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]; } @@ -6002,17 +6022,16 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); /** * @method _tick - * @param {Array} params Parameters to pass on to any listeners of the tick function. This will usually include the + * @param {Object} props Props to copy to the tick event object. This will usually include the * properties from the {{#crossLink "Ticker"}}{{/crossLink}} "tick" event, such as `delta` and `paused`, but may * be undefined or contain other values depending on the usage by the application. * @protected **/ - p._tick = function(params) { + p._tick = function(props) { // because tick can be really performance sensitive, we'll inline some of the dispatchEvent work. var ls = this._listeners; if (ls && ls["tick"]) { - var evt = new createjs.Event("tick"); - evt.params = params; + var evt = new createjs.Event("tick").set(props); this._dispatchEvent(evt, this, 2); } }; @@ -6180,6 +6199,7 @@ this.createjs = this.createjs||{}; * Containers have some overhead, so you generally shouldn't create a Container to hold a single child. * * <h4>Example</h4> + * * var container = new createjs.Container(); * container.addChild(bitmapInstance, shapeInstance); * container.x = 100; @@ -6299,6 +6319,7 @@ var p = Container.prototype = new createjs.DisplayObject(); * Adds a child to the top of the display list. * * <h4>Example</h4> + * * container.addChild(bitmapInstance); * * You can also add multiple children at once: @@ -6327,6 +6348,7 @@ var p = Container.prototype = new createjs.DisplayObject(); * setting its parent to this Container. * * <h4>Example</h4> + * * addChildAt(child1, index); * * You can also add multiple children, such as: @@ -6364,6 +6386,7 @@ var p = Container.prototype = new createjs.DisplayObject(); * already known. * * <h4>Example</h4> + * * container.removeChild(child); * * You can also remove multiple children: @@ -6422,6 +6445,7 @@ var p = Container.prototype = new createjs.DisplayObject(); * Removes all children from the display list. * * <h4>Example</h4> + * * container.removeAlLChildren(); * * @method removeAllChildren @@ -6435,6 +6459,7 @@ var p = Container.prototype = new createjs.DisplayObject(); * Returns the child at the specified index. * * <h4>Example</h4> + * * container.getChildAt(2); * * @method getChildAt @@ -6483,6 +6508,7 @@ var p = Container.prototype = new createjs.DisplayObject(); * Returns the index of the specified child in the display list, or -1 if it is not in the display list. * * <h4>Example</h4> + * * var index = container.getChildIndex(child); * * @method getChildIndex @@ -6590,6 +6616,8 @@ var p = Container.prototype = new createjs.DisplayObject(); * of visual depth, with the top-most display object at index 0. This uses shape based hit detection, and can be an * expensive operation to run, so it is best to use it carefully. For example, if testing for objects under the * mouse, test on tick (instead of on mousemove), and only if the mouse's position has changed. + * + * Accounts for both {{#crossLink "DisplayObject/hitArea:property"}}{{/crossLink}} and {{#crossLink "DisplayObject/mask:property"}}{{/crossLink}}. * @method getObjectsUnderPoint * @param {Number} x The x position in the container to test. * @param {Number} y The y position in the container to test. @@ -6679,18 +6707,18 @@ var p = Container.prototype = new createjs.DisplayObject(); /** * @method _tick - * @param {Array} params Parameters to pass onto the DisplayObject {{#crossLink "DisplayObject/tick"}}{{/crossLink}} + * @param {Object} props Properties to copy to the DisplayObject {{#crossLink "DisplayObject/tick"}}{{/crossLink}} event object. * function. * @protected **/ - p._tick = function(params) { + p._tick = function(props) { if (this.tickChildren) { for (var i=this.children.length-1; i>=0; i--) { var child = this.children[i]; - if (child.tickEnabled && child._tick) { child._tick(params); } + if (child.tickEnabled && child._tick) { child._tick(props); } } } - this.DisplayObject__tick(params); + this.DisplayObject__tick(props); }; /** @@ -6713,8 +6741,23 @@ var p = Container.prototype = new createjs.DisplayObject(); var l = children.length; for (var i=l-1; i>=0; i--) { var child = children[i]; - var hitArea = child.hitArea; + var hitArea = child.hitArea, mask = child.mask; if (!child.visible || (!hitArea && !child.isVisible()) || (mouse && !child.mouseEnabled)) { continue; } + if (!hitArea && mask && mask.graphics && !mask.graphics.isEmpty()) { + var maskMtx = mask.getMatrix(mask._matrix).prependMatrix(this.getConcatenatedMatrix(mtx)); + ctx.setTransform(maskMtx.a, maskMtx.b, maskMtx.c, maskMtx.d, maskMtx.tx-x, maskMtx.ty-y); + + // draw the mask as a solid fill: + mask.graphics.drawAsPath(ctx); + ctx.fillStyle = "#000"; + ctx.fill(); + + // if we don't hit the mask, then no need to keep looking at this DO: + if (!this._testHit(ctx)) { continue; } + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.clearRect(0, 0, 2, 2); + } + // if a child container has a hitArea then we only need to check its hitArea, so we can treat it as a normal DO: if (!hitArea && child instanceof Container) { var result = child._getObjectsUnderPoint(x, y, arr, mouse, activeListener); @@ -7138,30 +7181,19 @@ var p = Stage.prototype = new createjs.Container(); // public methods: /** - * Each time the update method is called, the stage will tick all descendants (see: {{#crossLink "DisplayObject/tick"}}{{/crossLink}}) - * and then render the display list to the canvas. Any parameters passed to `update()` will be passed on to any - * {{#crossLink "DisplayObject/tick:event"}}{{/crossLink}} event handlers. - * - * Some time-based features in EaselJS (for example {{#crossLink "Sprite/framerate"}}{{/crossLink}} require that - * a tick event object (or equivalent) be passed as the first parameter to update(). For example: - * - * Ticker.addEventListener("tick", handleTick); - * function handleTick(evtObj) { - * // do some work here, then update the stage, passing through the event object: - * myStage.update(evtObj); - * } + * Each time the update method is called, the stage will call {{#crossLink "Stage/tick"}}{{/crossLink}} + * unless {{#crossLink "Stage/tickOnUpdate:property"}}{{/crossLink}} is set to false, + * and then render the display list to the canvas. * * @method update - * @param {*} [params]* Params to include when ticking descendants. The first param should usually be a tick event. + * @param {*} [params]* Params to pass to .tick() if .tickOnUpdate is true. **/ p.update = function(params) { if (!this.canvas) { return; } - if (this.tickOnUpdate) { - this.dispatchEvent("tickstart"); // TODO: make cancellable? - this.tickEnabled&&this._tick((arguments.length ? arguments : null)); - this.dispatchEvent("tickend"); + if (this.tickOnUpdate) { // update this logic in SpriteStage when necessary + this.tick.apply(this, arguments); } - this.dispatchEvent("drawstart"); // TODO: make cancellable? + this.dispatchEvent("drawstart"); //TODO: make cancellable? createjs.DisplayObject._snapToPixelEnabled = this.snapToPixelEnabled; if (this.autoClear) { this.clear(); } var ctx = this.canvas.getContext("2d"); @@ -7171,6 +7203,47 @@ var p = Stage.prototype = new createjs.Container(); ctx.restore(); this.dispatchEvent("drawend"); }; + + /** + * Propagates a tick event through the display list. This is automatically called by {{#crossLink "Stage/update"}}{{/crossLink}} + * unless {{#crossLink "Stage/tickOnUpdate:property"}}{{/crossLink}} is set to false. + * + * Any parameters passed to `tick()` will be included as an array in the "param" property of the event object dispatched + * to {{#crossLink "DisplayObject/tick:event"}}{{/crossLink}} event handlers. Additionally, if the first parameter + * is a {{#crossLink "Ticker/tick:event"}}{{/crossLink}} event object (or has equivalent properties), then the delta, + * time, runTime, and paused properties will be copied to the event object. + * + * Some time-based features in EaselJS (for example {{#crossLink "Sprite/framerate"}}{{/crossLink}} require that + * a {{#crossLink "Ticker/tick:event"}}{{/crossLink}} event object (or equivalent) be passed as the first parameter + * to tick(). For example: + * + * Ticker.on("tick", handleTick); + * function handleTick(evtObj) { + * // do some work here, then update the stage, passing through the tick event object as the first param + * // and some custom data as the second and third param: + * myStage.update(evtObj, "hello", 2014); + * } + * + * // ... + * myDisplayObject.on("tick", handleDisplayObjectTick); + * function handleDisplayObjectTick(evt) { + * console.log(evt.params[0]); // the original tick evtObj + * console.log(evt.delta, evt.paused); // ex. "17 false" + * console.log(evt.params[1], evt.params[2]); // "hello 2014" + * } + * + * @method tick + * @param {*} [params]* Params to include when ticking descendants. The first param should usually be a tick event. + **/ + p.tick = function(params) { + this.dispatchEvent("tickstart"); //TODO: make cancellable? + var args = arguments.length ? Array.prototype.slice.call(arguments,0) : null; + var evt = args&&args[0]; + var props = evt&&(evt.delta != null) ? {delta:evt.delta, paused:evt.paused, time:evt.time, runTime:evt.runTime } : {}; + props.params = args; + this.tickEnabled&&this._tick(props); + this.dispatchEvent("tickend"); + }; /** * Default event handler that calls the Stage {{#crossLink "Stage/update"}}{{/crossLink}} method when a {{#crossLink "DisplayObject/tick:event"}}{{/crossLink}} @@ -7263,6 +7336,7 @@ var p = Stage.prototype = new createjs.Container(); * independently of mouse move events via the optional `frequency` parameter. * * <h4>Example</h4> + * * var stage = new createjs.Stage("canvasId"); * stage.enableMouseOver(10); // 10 updates per second * @@ -7687,6 +7761,7 @@ this.createjs = this.createjs||{}; * HTML element, or a string. * * <h4>Example</h4> + * * var bitmap = new createjs.Bitmap("imagePath.jpg"); * * <strong>Notes:</strong> @@ -7924,6 +7999,7 @@ this.createjs = this.createjs||{}; * See the {{#crossLink "SpriteSheet"}}{{/crossLink}} class for more information on setting up frames and animations. * * <h4>Example</h4> + * * var instance = new createjs.Sprite(spriteSheet); * instance.gotoAndStop("frameName"); * @@ -7935,7 +8011,7 @@ this.createjs = this.createjs||{}; * @constructor * @param {SpriteSheet} spriteSheet The SpriteSheet instance to play back. This includes the source image(s), frame * dimensions, and frame data. See {{#crossLink "SpriteSheet"}}{{/crossLink}} for more information. - * @param {String|Number} frameOrAnimation The frame number or animation to play initially. + * @param {String|Number} [frameOrAnimation] The frame number or animation to play initially. **/ var Sprite = function(spriteSheet, frameOrAnimation) { this.initialize(spriteSheet, frameOrAnimation); @@ -8254,14 +8330,15 @@ var p = Sprite.prototype = new createjs.DisplayObject(); /** * Advances the <code>currentFrame</code> if paused is not true. This is called automatically when the {{#crossLink "Stage"}}{{/crossLink}} * ticks. + * @param {Object} props Properties to copy to the DisplayObject {{#crossLink "DisplayObject/tick"}}{{/crossLink}} event object. * @protected * @method _tick **/ - p._tick = function(params) { + p._tick = function(props) { if (!this.paused) { - this.advance(params&¶ms[0]&¶ms[0].delta); + this.advance(props&&props.delta); } - this.DisplayObject__tick(params); + this.DisplayObject__tick(props); }; @@ -8338,7 +8415,7 @@ var p = Sprite.prototype = new createjs.DisplayObject(); /** * @method cloneProps - * @param {Text} o + * @param {Sprite} o * @protected **/ p.cloneProps = function(o) { @@ -8477,6 +8554,7 @@ this.createjs = this.createjs||{}; * rendering cost. * * <h4>Example</h4> + * * var graphics = new createjs.Graphics().beginFill("#ff0000").drawRect(0, 0, 100, 100); * var shape = new createjs.Shape(graphics); * @@ -8629,6 +8707,7 @@ this.createjs = this.createjs||{}; * multiple font styles, you will need to create multiple text instances, and position them manually. * * <h4>Example</h4> + * * var text = new createjs.Text("Hello World", "20px Arial", "#ff7700"); * text.x = 100; * text.textBaseline = "alphabetic"; @@ -10341,14 +10420,14 @@ var p = DOMElement.prototype = new createjs.DisplayObject(); /** * @method _tick - * @param {Array} params Parameters to pass onto the DisplayObject {{#crossLink "DisplayObject/tick"}}{{/crossLink}} + * @param {Object} props Properties to copy to the DisplayObject {{#crossLink "DisplayObject/tick"}}{{/crossLink}} event object. * function. * @protected */ - p._tick = function(params) { + p._tick = function(props) { var stage = this.getStage(); stage&&stage.on("drawend", this._handleDrawEnd, this, true); - this.DisplayObject__tick(params); + this.DisplayObject__tick(props); }; /** @@ -10427,6 +10506,7 @@ this.createjs = this.createjs||{}; * {{#crossLink "DisplayObject/updateCache"}}{{/crossLink}}. Note that the filters must be applied before caching. * * <h4>Example</h4> + * * myInstance.filters = [ * new createjs.ColorFilter(0, 0, 0, 1, 255, 0, 0), * new createjs.BlurFilter(5, 5, 10) @@ -11428,6 +11508,7 @@ this.createjs = this.createjs||{}; * chained calls. * * <h4>Example</h4> + * * myColorMatrix.adjustHue(20).adjustBrightness(50); * * See {{#crossLink "Filter"}}{{/crossLink}} for an example of how to apply filters, or {{#crossLink "ColorMatrixFilter"}}{{/crossLink}} diff --git a/vendor/scripts/preloadjs-NEXT.combined.js b/vendor/scripts/preloadjs-NEXT.combined.js index 039c8aa61..94e9f4e28 100644 --- a/vendor/scripts/preloadjs-NEXT.combined.js +++ b/vendor/scripts/preloadjs-NEXT.combined.js @@ -27,7 +27,7 @@ this.createjs = this.createjs||{}; * @type String * @static **/ - s.buildDate = /*date*/"Thu, 06 Mar 2014 22:58:10 GMT"; // injected by build process + s.buildDate = /*date*/"Wed, 02 Apr 2014 17:54:19 GMT"; // injected by build process })(); /* @@ -909,24 +909,32 @@ this.createjs = this.createjs||{}; var s = AbstractLoader; /** - * The RegExp pattern to use 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 relativePath:$3 path:$4 file:$5 extension:$6 query:$7. - * @property FILE_PATTERN - * @type {RegExp} + * The Regular Expression used to test file URLS for an absolute path. + * @property ABSOLUTE_PATH * @static - * @protected + * @type {RegExp} + * @since 0.4.2 */ - s.FILE_PATTERN = /^(?:(\w+:)\/{2}(\w+(?:\.\w+)*\/?)|(.{0,2}\/{1}))?([/.]*?(?:[^?]+)?\/)?((?:[^/?]+)\.(\w+))(?:\?(\S+)?)?$/; + s.ABSOLUTE_PATT = /^(?:\w+:)?\/{2}/i; /** - * The RegExp pattern to use to parse path URIs. This supports protocols, relative files, and paths. The resulting - * match is: protocol:$1 relativePath:$2 path$3. - * @property PATH_PATTERN - * @type {RegExp} + * The Regular Expression used to test file URLS for an absolute path. + * @property RELATIVE_PATH * @static - * @protected + * @type {RegExp} + * @since 0.4.2 */ - s.PATH_PATTERN = /^(?:(\w+:)\/{2})|(.{0,2}\/{1})?([/.]*?(?:[^?]+)?\/?)?$/; + s.RELATIVE_PATT = (/^[./]*?\//i); + + /** + * The Regular Expression used to test file URLS for an extension. Note that URIs must already have the query string + * removed. + * @property EXTENSION_PATT + * @static + * @type {RegExp} + * @since 0.4.2 + */ + s.EXTENSION_PATT = /\/?[^/]+\.(\w{1,5})$/i; /** * If the loader has completed loading. This provides a quick check, but also ensures that the different approaches @@ -1164,29 +1172,49 @@ this.createjs = this.createjs||{}; }; /** - * Parse a file URI using the {{#crossLink "AbstractLoader/FILE_PATTERN:property"}}{{/crossLink}} RegExp pattern. * @method _parseURI - * @param {String} path The file path to parse. - * @return {Array} The matched file contents. Please see the FILE_PATTERN property for details on the return value. - * This will return null if it does not match. - * @protected + * Parse a file path to determine the information we need to work with it. Currently, PreloadJS needs to know: + * <ul> + * <li>If the path is absolute. Absolute paths start with a protocol (such as `http://`, `file://`, or + * `//networkPath`)</li> + * <li>If the path is relative. Relative paths start with `../` or `/path` (or similar)</li> + * <li>The file extension. This is determined by the filename with an extension. Query strings are dropped, and + * the file path is expected to follow the format `name.ext`.</li> + * </ul> + * + * <strong>Note:</strong> This has changed from earlier versions, which used a single, complicated Regular Expression, which + * was difficult to maintain, and over-aggressive in determining all file properties. It has been simplified to + * only pull out what it needs. + * @param path + * @returns {Object} An Object with an `absolute` and `relative` Boolean, as well as an optional 'extension` String + * property, which is the lowercase extension. + * @private */ p._parseURI = function(path) { - if (!path) { return null; } - return path.match(s.FILE_PATTERN); - }; + var info = { absolute: false, relative:false }; + if (path == null) { return info; }; - /** - * Parse a file URI using the {{#crossLink "AbstractLoader/PATH_PATTERN"}}{{/crossLink}} RegExp pattern. - * @method _parsePath - * @param {String} path The file path to parse. - * @return {Array} The matched path contents. Please see the PATH_PATTERN property for details on the return value. - * This will return null if it does not match. - * @protected - */ - p._parsePath = function(path) { - if (!path) { return null; } - return path.match(s.PATH_PATTERN); + // Drop the query string + var queryIndex = path.indexOf("?"); + if (queryIndex > -1) { + path = path.substr(0,queryIndex); + } + + // Absolute + var match; + if (s.ABSOLUTE_PATT.test(path)) { + info.absolute = true; + + // Relative + } else if (s.RELATIVE_PATT.test(path)) { + info.relative = true; + } + + // Extension + if (match = path.match(s.EXTENSION_PATT)) { + info.extension = match[1].toLowerCase(); + } + return info; }; /** @@ -2593,7 +2621,7 @@ TODO: WINDOWS ISSUES // Determine Extension, etc. var match = this._parseURI(item.src); - if (match != null) { item.ext = match[6]; } + if (match.extension) { item.ext = match.extension; } if (item.type == null) { item.type = this._getTypeByExtension(item.ext); } @@ -2602,13 +2630,13 @@ TODO: WINDOWS ISSUES var bp = ""; // Store the generated basePath var useBasePath = basePath || this._basePath; var autoId = item.src; - if (match && match[1] == null && match[3] == null) { + if (!match.absolute && !match.relative) { if (path) { bp = path; - var pathMatch = this._parsePath(path); + var pathMatch = this._parseURI(path); autoId = path + autoId; // Also append basePath - if (useBasePath != null && pathMatch && pathMatch[1] == null && pathMatch[2] == null) { + if (useBasePath != null && !pathMatch.absolute && !pathMatch.relative) { bp = useBasePath + bp; } } else if (useBasePath != null) { @@ -2666,8 +2694,8 @@ TODO: WINDOWS ISSUES // Update the extension in case the type changed: match = this._parseURI(item.src); - if (match != null && match[6] != null) { - item.ext = match[6].toLowerCase(); + if (match.extension != null) { + item.ext = match.extension; } } } @@ -3396,7 +3424,16 @@ this.createjs = this.createjs||{}; item.type == createjs.LoadQueue.CSS) { this._startTagVisibility = tag.style.visibility; tag.style.visibility = "hidden"; - (document.body || document.getElementsByTagName("body")[0]).appendChild(tag); + var node = document.body || document.getElementsByTagName("body")[0]; + if (node == null) { + if (item.type == createjs.LoadQueue.SVG) { + this._handleSVGError(); + return; + } else { + node = document.head || document.getElementsByTagName("head"); + } + } + node.appendChild(tag); } // Note: Previous versions didn't seem to work when we called load() for OGG tags in Firefox. Seems fixed in 15.0.1 @@ -3405,6 +3442,13 @@ this.createjs = this.createjs||{}; } }; + p._handleSVGError = function() { + this._clean(); + var event = new createjs.Event("error"); + event.text = "SVG_NO_BODY"; + this._sendError(event); + }; + p._handleJSONPLoad = function(data) { this._jsonResult = data; }; @@ -3488,8 +3532,8 @@ this.createjs = this.createjs||{}; // case createjs.LoadQueue.CSS: //LM: We may need to remove CSS tags loaded using a LINK tag.style.visibility = this._startTagVisibility; - (document.body || document.getElementsByTagName("body")[0]).removeChild(tag); - break; + tag.parentNode && tag.parentNode.contains(tag) && tag.parentNode.removeChild(tag); + break; default: } diff --git a/vendor/scripts/soundjs-NEXT.combined.js b/vendor/scripts/soundjs-NEXT.combined.js index 09e8920c7..42d6571a9 100644 --- a/vendor/scripts/soundjs-NEXT.combined.js +++ b/vendor/scripts/soundjs-NEXT.combined.js @@ -856,6 +856,72 @@ this.createjs = this.createjs||{}; }; } +}());/* +* 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. @@ -920,8 +986,8 @@ this.createjs = this.createjs || {}; * } * * <h4>Browser Support</h4> - * Audio will work in browsers which support HTMLAudioElement (<a href="http://caniuse.com/audio">http://caniuse.com/audio</a>) - * or WebAudio (<a href="http://caniuse.com/audio-api">http://caniuse.com/audio-api</a>). A Flash fallback can be added + * 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 @@ -931,11 +997,6 @@ this.createjs = this.createjs || {}; "use strict"; - //TODO: Interface to validate plugins and throw warnings - //TODO: Determine if methods exist on a plugin before calling // OJR this is only an issue if something breaks or user changes something - //TODO: Interface to validate instances and throw warnings - //TODO: Surface errors on audio from all plugins - //TODO: Timeouts // OJR for? /** * 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. @@ -945,7 +1006,7 @@ this.createjs = this.createjs || {}; * 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>, this is handled for you when the sound is + * <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. * @@ -982,11 +1043,12 @@ this.createjs = this.createjs || {}; * * 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 not play immediately the first time play is called. Use the + * 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. * - * createjs.PreloadJS.installPlugin(createjs.Sound); + * var queue = new createjs.LoadQueue(); + * queue.installPlugin(createjs.Sound); * * <b>Mobile Safe Approach</b><br /> * Mobile devices require sounds to be played inside of a user initiated event (touch/click) in varying degrees. @@ -1009,6 +1071,7 @@ this.createjs = this.createjs || {}; * 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> * @@ -1044,19 +1107,16 @@ this.createjs = this.createjs || {}; var s = Sound; + // TODO DEPRECATED /** - * DEPRECATED - * This approach has is being replaced by {{#crossLink "Sound/alternateExtensions:property"}}{{/crossLink}}, and - * support will be removed in the next version. - * - * The character (or characters) that are used to split multiple paths from an audio source. + * REMOVED + * Use {{#crossLink "Sound/alternateExtensions:property"}}{{/crossLink}} instead * @property DELIMITER * @type {String} * @default | * @static * @deprecated */ - s.DELIMITER = "|"; /** * The interrupt value to interrupt any currently playing instance with the same source, if the maximum number of @@ -1159,7 +1219,7 @@ this.createjs = this.createjs || {}; * @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"]; // OJR FlashPlugin does not currently support + 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 @@ -1291,7 +1351,7 @@ this.createjs = this.createjs || {}; s._instances = []; /** - * An object hash storing sound sources via there corresponding ID. + * An object hash storing objects with sound sources, startTime, and duration via there corresponding ID. * @property _idHash * @type {Object} * @protected @@ -1346,16 +1406,6 @@ this.createjs = this.createjs || {}; * @since 0.4.1 */ - //TODO: Deprecated - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "Sound/fileload:event"}}{{/crossLink}} - * event. - * @property onLoadComplete - * @type {Function} - * @deprecated Use addEventListener and the fileload event. - * @since 0.4.0 - */ - /** * Used by external plugins to dispatch file load events. * @method _sendFileLoadEvent @@ -1365,9 +1415,8 @@ this.createjs = this.createjs || {}; * @since 0.4.1 */ s._sendFileLoadEvent = function (src) { - if (!s._preloadHash[src]) { - return; - } + 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; @@ -1378,6 +1427,7 @@ this.createjs = this.createjs || {}; event.src = item.src; event.id = item.id; event.data = item.data; + event.sprite = item.sprite; s.dispatchEvent(event); } @@ -1406,33 +1456,7 @@ this.createjs = this.createjs || {}; }; /** - * Deprecated in favor of {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} with a single argument. - * createjs.Sound.registerPlugins([createjs.WebAudioPlugin]); - * - * @method registerPlugin - * @param {Object} plugin The plugin class to install. - * @return {Boolean} Whether the plugin was successfully initialized. - * @static - * @deprecated - */ - s.registerPlugin = function (plugin) { - try { - console.log("createjs.Sound.registerPlugin has been deprecated. Please use registerPlugins."); - } catch (err) { - // you are in IE with the console closed, you monster - } - return s._registerPlugin(plugin); - }; - - /** - * Register a Sound plugin. Plugins handle the actual playback of audio. The default plugins are - * ({{#crossLink "WebAudioPlugin"}}{{/crossLink}} followed by {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}), - * and are installed if no other plugins are present when the user attempts to start playback or register sound. - * <h4>Example</h4> - * createjs.FlashPlugin.swfPath = "../src/SoundJS/"; - * createjs.Sound._registerPlugin(createjs.FlashPlugin); - * - * To register multiple plugins, use {{#crossLink "Sound/registerPlugins"}}{{/crossLink}}. + * Used by {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} to register a Sound plugin. * * @method _registerPlugin * @param {Object} plugin The plugin class to install. @@ -1441,14 +1465,9 @@ this.createjs = this.createjs || {}; * @private */ s._registerPlugin = function (plugin) { - s._pluginsRegistered = true; - if (plugin == null) { - return false; - } // 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(); - //TODO: Check error on initialization return true; } return false; @@ -1467,9 +1486,9 @@ this.createjs = this.createjs || {}; * @static */ s.registerPlugins = function (plugins) { + s._pluginsRegistered = true; for (var i = 0, l = plugins.length; i < l; i++) { - var plugin = plugins[i]; - if (s._registerPlugin(plugin)) { + if (s._registerPlugin(plugins[i])) { return true; } } @@ -1489,15 +1508,9 @@ this.createjs = this.createjs || {}; * @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; - } + if (s.activePlugin != null) {return true;} + if (s._pluginsRegistered) {return false;} + if (s.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin])) {return true;} return false; }; @@ -1544,9 +1557,7 @@ this.createjs = this.createjs || {}; * @static */ s.getCapabilities = function () { - if (s.activePlugin == null) { - return null; - } + if (s.activePlugin == null) {return null;} return s.activePlugin._capabilities; }; @@ -1564,9 +1575,7 @@ this.createjs = this.createjs || {}; * @see getCapabilities */ s.getCapability = function (key) { - if (s.activePlugin == null) { - return null; - } + if (s.activePlugin == null) {return null;} return s.activePlugin._capabilities[key]; }; @@ -1580,20 +1589,79 @@ this.createjs = this.createjs || {}; * @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 default to 1 if no value is found. - * @param {String} [path] A combined basepath and subPath from PreloadJS that has already been prepended to src. + * 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, path) { - // remove path from src so we can continue to support "|" splitting of src files // TODO remove this when "|" is removed - src = src.replace(path, ""); - var details = s.registerSound(src, id, data, false, path); - if (details == null) { - return false; + 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; }; @@ -1613,8 +1681,11 @@ this.createjs = this.createjs || {}; * @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. - * @param {Boolean} [preload=true] If the sound should be internally preloaded so that it can be played back - * without an external preloader. This is currently used by PreloadJS when loading sounds to disable internal preloading. + * 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. @@ -1622,90 +1693,26 @@ this.createjs = this.createjs || {}; * @static * @since 0.4.0 */ - s.registerSound = function (src, id, data, preload, basePath) { - if (!s.initializeDefaultPlugins()) { - return false; - } - + s.registerSound = function (src, id, data, basePath) { if (src instanceof Object) { - basePath = id; //this assumes preload has not be passed in as a property // OJR check if arguments == 3 would be less fragile - //?? preload = src.preload; - // OJR refactor how data is passed in to make the parameters work better + basePath = id; id = src.id; data = src.data; src = src.src; } - // branch to different parse based on alternate formats setting - if (s.alternateExtensions.length) { - var details = s._parsePath2(src, "sound", id, data); + 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 { - var details = s._parsePath(src, "sound", id, data); - } - if (details == null) { - return false; - } - if (basePath != null) { - src = basePath + src; - details.src = basePath + details.src; - } - - if (id != null) { - s._idHash[id] = details.src; - } - - var numChannels = null; // null tells SoundChannel to set this to it's internal maxDefault - if (data != null) { - if (!isNaN(data.channels)) { - numChannels = parseInt(data.channels); - } - else if (!isNaN(data)) { - numChannels = parseInt(data); - } - } - var loader = s.activePlugin.register(details.src, numChannels); // Note only HTML audio uses numChannels - - if (loader != null) { // all plugins currently return a loader - if (loader.numChannels != null) { - numChannels = loader.numChannels; - } // currently only HTMLAudio returns this - 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)) { - data = details.data = numChannels || SoundChannel.maxPerChannel(); - } else { - data.channels = details.data.channels = numChannels || SoundChannel.maxPerChannel(); - } - - // If the loader returns a tag, return it instead for preloading. - // OJR all loaders currently use tags? - if (loader.tag != null) { - details.tag = loader.tag; - } else if (loader.src) { - details.src = loader.src; - } - // If the loader returns a complete handler, pass it on to the prelaoder. - if (loader.completeHandler != null) { - details.completeHandler = loader.completeHandler; - } - if (loader.type) { - details.type = loader.type; - } - } - - if (preload != false) { - if (!s._preloadHash[details.src]) { - s._preloadHash[details.src] = []; - } // we do this so we can store multiple id's and data if needed - s._preloadHash[details.src].push({src:src, id:id, data:data}); // keep this data so we can return it in fileload event - if (s._preloadHash[details.src].length == 1) { - // if already loaded once, don't load a second time // OJR note this will disallow reloading a sound if loading fails or the source changes - s.activePlugin.preload(details.src, loader); - } else { - // if src already loaded successfully, return true - if (s._preloadHash[details.src][0] == true) {return true;} - } + if (s._preloadHash[details.src][0] == true) {return true;} } return details; @@ -1741,8 +1748,8 @@ this.createjs = this.createjs || {}; 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, manifest[i].preload, basePath); - } // OJR consider removing .preload from args, as it is only used by PreloadJS + returnValues[i] = createjs.Sound.registerSound(manifest[i].src, manifest[i].id, manifest[i].data, basePath); + } return returnValues; }; @@ -1764,29 +1771,18 @@ this.createjs = this.createjs || {}; * @since 0.4.1 */ s.removeSound = function(src, basePath) { - if (s.activePlugin == null) { - return false; - } + if (s.activePlugin == null) {return false;} - if (src instanceof Object) { - src = src.src; - } - src = s._getSrcById(src); + if (src instanceof Object) {src = src.src;} + src = s._getSrcById(src).src; + if (basePath != null) {src = basePath + src;} - if (s.alternateExtensions.length) { - var details = s._parsePath2(src); - } else { - var details = s._parsePath(src); - } - if (details == null) { - return false; - } - if (basePath != null) {details.src = basePath + details.src;} + var details = s._parsePath(src); + if (details == null) {return false;} src = details.src; - // remove src from _idHash // Note "for in" can be a slow operation for(var prop in s._idHash){ - if(s._idHash[prop] == src) { + if(s._idHash[prop].src == src) { delete(s._idHash[prop]); } } @@ -1794,10 +1790,8 @@ this.createjs = this.createjs || {}; // clear from SoundChannel, which also stops and deletes all instances SoundChannel.removeSrc(src); - // remove src from _preloadHash delete(s._preloadHash[src]); - // activePlugin cleanup s.activePlugin.removeSound(src); return true; @@ -1850,7 +1844,7 @@ this.createjs = this.createjs || {}; s._idHash = {}; s._preloadHash = {}; SoundChannel.removeAll(); - s.activePlugin.removeAllSounds(); + if (s.activePlugin) {s.activePlugin.removeAllSounds();} }; /** @@ -1869,89 +1863,41 @@ this.createjs = this.createjs || {}; * @since 0.4.0 */ s.loadComplete = function (src) { - if (s.alternateExtensions.length) { - var details = s._parsePath2(src, "sound"); - } else { - var details = s._parsePath(src, "sound"); - } + var details = s._parsePath(src); if (details) { - src = s._getSrcById(details.src); + src = s._getSrcById(details.src).src; } else { - src = s._getSrcById(src); + 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. Manifest items support single file paths, as well as - * composite paths using {{#crossLink "Sound/DELIMITER:property"}}{{/crossLink}}, which defaults to "|". The first path supported by the - * current browser/plugin will be used. - * NOTE the "|" approach is deprecated and will be removed in the next version + * 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. - * @param {String} [type] The type of path. This will typically be "sound" or null. - * @param {String} [id] The user-specified sound ID. This may be null, in which case the src will be used instead. - * @param {Number | String | Boolean | Object} [data] Arbitrary data appended to the sound, usually denoting the - * number of channels for the sound. This method doesn't currently do anything with the data property. * @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, type, id, data) { - if (typeof(value) != "string") {value = value.toString();} - var sounds = value.split(s.DELIMITER); - if (sounds.length > 1) { - try { - console.log("createjs.Sound.DELIMITER \"|\" loading approach has been deprecated. Please use the new alternateExtensions property."); - } catch (err) { - // you are in IE with the console closed, you monster - } - } - var ret = {type:type || "sound", id:id, data:data}; - var c = s.getCapabilities(); - for (var i = 0, l = sounds.length; i < l; i++) { - var sound = sounds[i]; - - var match = sound.match(s.FILE_PATTERN); - if (match == null) { - return false; - } - var name = match[4]; - var ext = match[5]; - - if (c[ext] && createjs.indexOf(s.SUPPORTED_EXTENSIONS, ext) > -1) { - ret.name = name; - ret.src = sound; - ret.extension = ext; - return ret; - } - } - return null; - }; - - // new approach, when old approach is deprecated this will become _parsePath - s._parsePath2 = function (value, type, id, data) { + s._parsePath = function (value) { if (typeof(value) != "string") {value = value.toString();} var match = value.match(s.FILE_PATTERN); - if (match == null) { - return false; - } + 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 = {type:type || "sound", id:id, data:data}; - ret.name = name; - ret.src = value; - ret.extension = ext; + + var ret = {name:name, src:value, extension:ext}; return ret; }; @@ -1976,6 +1922,9 @@ this.createjs = this.createjs || {}; * 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, @@ -1983,7 +1932,7 @@ this.createjs = this.createjs || {}; * 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, and pan (see the above code sample). + * 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 @@ -1991,16 +1940,26 @@ this.createjs = this.createjs || {}; * @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) { - var instance = s.createInstance(src); + 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 ok = s._playInstance(instance, interrupt, delay, offset, loop, volume, pan); - if (!ok) { - instance.playFailed(); } + var instance = s.createInstance(src, startTime, duration); + var ok = s._playInstance(instance, interrupt, delay, offset, loop, volume, pan); + if (!ok) {instance.playFailed();} return instance; }; @@ -2019,34 +1978,30 @@ this.createjs = this.createjs || {}; * 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) { - if (!s.initializeDefaultPlugins()) { - return s._defaultSoundInstance; - } + s.createInstance = function (src, startTime, duration) { + if (!s.initializeDefaultPlugins()) {return s._defaultSoundInstance;} src = s._getSrcById(src); - if (s.alternateExtensions.length) { - var details = s._parsePath2(src, "sound"); - } else { - var details = s._parsePath(src, "sound"); - } + var details = s._parsePath(src.src); var instance = null; if (details != null && details.src != null) { - // make sure that we have a sound channel (sound is registered or previously played) SoundChannel.create(details.src); - instance = s.activePlugin.create(details.src); + if (startTime == null) {startTime = src.startTime;} + instance = s.activePlugin.create(details.src, startTime, duration || src.duration); } else { - // the src is not supported, so give back a dummy instance. - // This can happen if PreloadJS fails because the plugin does not support the ext, and was passed an id which - // will not get added to the _idHash. instance = Sound._defaultSoundInstance; } @@ -2068,13 +2023,11 @@ this.createjs = this.createjs || {}; * @static */ s.setVolume = function (value) { - if (Number(value) == null) { - return false; - } + 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; // OJR does this impact garbage collection more than it helps performance? + var instances = this._instances; for (var i = 0, l = instances.length; i < l; i++) { instances[i].setMasterVolume(value); } @@ -2096,14 +2049,6 @@ this.createjs = this.createjs || {}; return s._masterVolume; }; - /** - * REMOVED. Please see {{#crossLink "Sound/setMute"}}{{/crossLink}}. - * @method mute - * @param {Boolean} value Whether the audio should be muted or not. - * @static - * @deprecated This function has been deprecated. Please use setMute instead. - */ - /** * 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 @@ -2119,9 +2064,7 @@ this.createjs = this.createjs || {}; * @since 0.4.0 */ s.setMute = function (value) { - if (value == null || value == undefined) { - return false; - } + if (value == null) {return false;} this._masterMute = value; if (!this.activePlugin || !this.activePlugin.setMute || !this.activePlugin.setMute(value)) { @@ -2205,15 +2148,13 @@ this.createjs = this.createjs || {}; interrupt = interrupt || s.defaultInterruptBehavior; if (delay == null) {delay = 0;} if (offset == null) {offset = instance.getPosition();} - if (loop == null) {loop = 0;} + if (loop == null) {loop = 0;} // OJR consider using instance._remainingLoops 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; - } + 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 @@ -2251,11 +2192,8 @@ this.createjs = this.createjs || {}; } var result = instance._beginPlaying(offset, loop, volume, pan); if (!result) { - //LM: Should we remove this from the SoundChannel (see finishedPlaying) var index = createjs.indexOf(this._instances, instance); - if (index > -1) { - this._instances.splice(index, 1); - } + if (index > -1) {this._instances.splice(index, 1);} return false; } return true; @@ -2266,15 +2204,12 @@ this.createjs = this.createjs || {}; * instead. * @method _getSrcById * @param {String} value The ID the sound was registered with. - * @return {String} The source of the sound. Returns null if src has been registered with this id. + * @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) { - if (s._idHash == null || s._idHash[value] == null) { - return value; - } - return s._idHash[value]; + return s._idHash[value] || {src: value}; }; /** @@ -2289,9 +2224,7 @@ this.createjs = this.createjs || {}; s._playFinished = function (instance) { SoundChannel.remove(instance); var index = createjs.indexOf(this._instances, instance); - if (index > -1) { - this._instances.splice(index, 1); - } + 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; @@ -2352,10 +2285,8 @@ this.createjs = this.createjs || {}; */ SoundChannel.removeSrc = function (src) { var channel = SoundChannel.get(src); - if (channel == null) { - return false; - } - channel.removeAll(); // this stops and removes all active instances + if (channel == null) {return false;} + channel._removeAll(); // this stops and removes all active instances delete(SoundChannel.channels[src]); return true; }; @@ -2366,7 +2297,7 @@ this.createjs = this.createjs || {}; */ SoundChannel.removeAll = function () { for(var channel in SoundChannel.channels) { - SoundChannel.channels[channel].removeAll(); // this stops and removes all active instances + SoundChannel.channels[channel]._removeAll(); // this stops and removes all active instances } SoundChannel.channels = {}; }; @@ -2381,10 +2312,8 @@ this.createjs = this.createjs || {}; */ SoundChannel.add = function (instance, interrupt) { var channel = SoundChannel.get(instance.src); - if (channel == null) { - return false; - } - return channel.add(instance, interrupt); + if (channel == null) {return false;} + return channel._add(instance, interrupt); }; /** * Remove an instance from the channel. @@ -2395,10 +2324,8 @@ this.createjs = this.createjs || {}; */ SoundChannel.remove = function (instance) { var channel = SoundChannel.get(instance.src); - if (channel == null) { - return false; - } - channel.remove(instance); + if (channel == null) {return false;} + channel._remove(instance); return true; }; /** @@ -2461,9 +2388,7 @@ this.createjs = this.createjs || {}; p.init = function (src, max) { this.src = src; this.max = max || this.maxDefault; - if (this.max == -1) { - this.max = this.maxDefault; - } + if (this.max == -1) {this.max = this.maxDefault;} this._instances = []; }; @@ -2473,7 +2398,7 @@ this.createjs = this.createjs || {}; * @param {Number} index The index to return. * @return {SoundInstance} The SoundInstance at a specific instance. */ - p.get = function (index) { + p._get = function (index) { return this._instances[index]; }; @@ -2483,10 +2408,8 @@ this.createjs = this.createjs || {}; * @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; - } + p._add = function (instance, interrupt) { + if (!this._getSlot(interrupt, instance)) {return false;} this._instances.push(instance); this.length++; return true; @@ -2499,11 +2422,9 @@ this.createjs = this.createjs || {}; * @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) { + p._remove = function (instance) { var index = createjs.indexOf(this._instances, instance); - if (index == -1) { - return false; - } + if (index == -1) {return false;} this._instances.splice(index, 1); this.length--; return true; @@ -2513,8 +2434,8 @@ this.createjs = this.createjs || {}; * 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, but we don't want to assume that. + 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(); } @@ -2528,11 +2449,11 @@ this.createjs = this.createjs || {}; * @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) { + p._getSlot = function (interrupt, instance) { var target, replacement; for (var i = 0, l = this.max; i < l; i++) { - target = this.get(i); + target = this._get(i); // Available Space if (target == null) { @@ -2554,16 +2475,15 @@ this.createjs = this.createjs || {}; replacement = target; // Audio is a better candidate than the current target, according to playhead - } else if ( - (interrupt == Sound.INTERRUPT_EARLY && target.getPosition() < replacement.getPosition()) || - (interrupt == Sound.INTERRUPT_LATE && target.getPosition() > replacement.getPosition())) { + } else 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); + this._remove(replacement); return true; } return false; @@ -2720,9 +2640,7 @@ this.createjs = this.createjs || {}; // 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; - } + if (s.context == null) {return false;} return true; }; @@ -2740,7 +2658,7 @@ this.createjs = this.createjs || {}; var xhr = new XMLHttpRequest(); try { - xhr.open("GET", "fail.fail", false); // loading non-existant file triggers 404 only if it could load (synchronous call) + 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; @@ -2767,28 +2685,19 @@ this.createjs = this.createjs || {}; * @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, - // therefore tag is still required for the capabilities check + 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 (t.canPlayType == null) { - return null; - } - - // This check is first because it's what is currently used, but the spec calls for it to be AudioContext so this - // will probably change in time - if (window.webkitAudioContext) { - s.context = new webkitAudioContext(); - } else if (window.AudioContext) { + if (window.AudioContext) { s.context = new AudioContext(); + } else if (window.webkitAudioContext) { + s.context = new webkitAudioContext(); } else { return null; } - // this handles if only deprecated Web Audio API calls are supported s._compatibilitySetUp(); // playing this inside of a touch event will enable audio on iOS, which starts muted @@ -2814,12 +2723,6 @@ this.createjs = this.createjs || {}; if (s.context.destination.numberOfChannels < 2) { s._capabilities.panning = false; } - - // set up AudioNodes that all of our source audio will connect to - s.dynamicsCompressorNode = s.context.createDynamicsCompressor(); - s.dynamicsCompressorNode.connect(s.context.destination); - s.gainNode = s.context.createGain(); - s.gainNode.connect(s.dynamicsCompressorNode); }; /** @@ -2829,10 +2732,12 @@ this.createjs = this.createjs || {}; * 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; } @@ -2845,7 +2750,7 @@ this.createjs = this.createjs || {}; audioNode.__proto__.stop = audioNode.__proto__.noteOff; // panningModel - this._panningModel = 0; + s._panningModel = 0; }; /** @@ -2855,24 +2760,18 @@ this.createjs = this.createjs || {}; * for example). * * <h4>Example</h4> - * * function handleTouch(event) { * createjs.WebAudioPlugin.playEmptySound(); * } * * @method playEmptySound + * @static * @since 0.4.1 */ s.playEmptySound = function() { - // create empty buffer - var buffer = this.context.createBuffer(1, 1, 22050); - var source = this.context.createBufferSource(); - source.buffer = buffer; - - // connect to output (your speakers) - source.connect(this.context.destination); - - // play the file + var source = s.context.createBufferSource(); + source.buffer = s.context.createBuffer(1, 1, 22050); + source.connect(s.context.destination); source.start(0, 0, 0); }; @@ -2888,7 +2787,6 @@ this.createjs = this.createjs || {}; * @default 1 * @protected */ - // TODO refactor Sound.js so we can use getter setter for volume p._volume = 1; /** @@ -2910,20 +2808,24 @@ this.createjs = this.createjs || {}; /** * 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}}. + * 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 + * 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. @@ -2943,8 +2845,13 @@ this.createjs = this.createjs || {}; this._arrayBuffers = {}; this.context = s.context; - this.gainNode = s.gainNode; - this.dynamicsCompressorNode = s.dynamicsCompressorNode; + 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); }; /** @@ -2958,11 +2865,9 @@ this.createjs = this.createjs || {}; * @return {Object} A result object, containing a "tag" for preloading purposes. */ p.register = function (src, instances) { - this._arrayBuffers[src] = true; // This is needed for PreloadJS - var tag = new createjs.WebAudioPlugin.Loader(src, this); - return { - tag:tag - }; + this._arrayBuffers[src] = true; + var loader = {tag: new createjs.WebAudioPlugin.Loader(src, this)}; + return loader; }; /** @@ -3021,19 +2926,18 @@ this.createjs = this.createjs || {}; * @method _handlePreloadComplete * @protected */ - p._handlePreloadComplete = function () { - //LM: I would recommend having the Loader include an "event" in the onload, and properly binding this callback. - createjs.Sound._sendFileLoadEvent(this.src); // fire event or callback on Sound - // note "this" will reference Loader object + 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} instance Not used in this plugin. + * @param {Object} tag Not used in this plugin. */ - p.preload = function (src, instance) { + p.preload = function (src, tag) { this._arrayBuffers[src] = true; var loader = new createjs.WebAudioPlugin.Loader(src, this); loader.onload = this._handlePreloadComplete; @@ -3044,13 +2948,13 @@ this.createjs = this.createjs || {}; * 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) { - if (!this.isPreloadStarted(src)) { - this.preload(src); - } - return new createjs.WebAudioPlugin.SoundInstance(src, this); + p.create = function (src, startTime, duration) { + if (!this.isPreloadStarted(src)) {this.preload(src);} + return new createjs.WebAudioPlugin.SoundInstance(src, startTime, duration, this); }; /** @@ -3116,7 +3020,6 @@ this.createjs = this.createjs || {}; * 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 @@ -3144,12 +3047,14 @@ this.createjs = this.createjs || {}; * * @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, owner) { - this._init(src, owner); + function SoundInstance(src, startTime, duration, owner) { + this._init(src, startTime, duration, owner); } var p = SoundInstance.prototype = new createjs.EventDispatcher(); @@ -3199,14 +3104,12 @@ this.createjs = this.createjs || {}; p._offset = 0; /** - * The time in milliseconds before the sound starts. - * Note this is handled by {{#crossLink "Sound"}}{{/crossLink}}. - * @property _delay + * Audio sprite property used to determine the starting offset. * @type {Number} - * @default 0 + * @default null * @protected */ - p._delay = 0; // OJR remove this property from SoundInstance as it is not used here? + p._startTime = 0; /** * The volume of the sound, between 0 and 1. @@ -3221,8 +3124,7 @@ this.createjs = this.createjs || {}; * @default 1 */ p._volume = 1; - // IE8 has Object.defineProperty, but only for DOM objects, so check if fails to suppress errors - try { + if (createjs.definePropertySupported) { Object.defineProperty(p, "volume", { get: function() { return this._volume; @@ -3234,9 +3136,7 @@ this.createjs = this.createjs || {}; this._updateVolume(); } }); - } catch (e) { - // dispatch message or error? - }; + } /** * The pan of the sound, between -1 (left) and 1 (right). Note that pan is not supported by HTML Audio. @@ -3250,8 +3150,7 @@ this.createjs = this.createjs || {}; * @default 0 */ p._pan = 0; - // IE8 has Object.defineProperty, but only for DOM objects, so check if fails to suppress errors - try { + if (createjs.definePropertySupported) { Object.defineProperty(p, "pan", { get: function() { return this._pan; @@ -3265,10 +3164,7 @@ this.createjs = this.createjs || {}; this.panNode.setPosition(value, 0, -0.5); // z need to be -0.5 otherwise the sound only plays in left, right, or center } }); - } catch (e) { - // dispatch message or error? - }; - + } /** * The length of the audio clip, in milliseconds. @@ -3291,7 +3187,7 @@ this.createjs = this.createjs || {}; /** * A Timeout created by {{#crossLink "Sound"}}{{/crossLink}} when this SoundInstance is played with a delay. - * This allows SoundInstance to remove the delay if stop or pause or cleanup are called before playback begins. + * This allows SoundInstance to remove the delay if stop, pause, or cleanup are called before playback begins. * @property _delayTimeoutId * @type {timeoutVariable} * @default null @@ -3376,13 +3272,13 @@ this.createjs = this.createjs || {}; /** * WebAudioPlugin only. * Time audio started playback, in seconds. Used to handle set position, get position, and resuming from paused. - * @property _startTime + * @property _playbackStartTime * @type {Number} * @default 0 * @protected * @since 0.4.0 */ - p._startTime = 0; + p._playbackStartTime = 0; // Proxies, make removing listeners easier. p._endedHandler = null; @@ -3432,43 +3328,6 @@ this.createjs = this.createjs || {}; * @since 0.4.0 */ - //TODO: Deprecated - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "SoundInstance/succeeded:event"}}{{/crossLink}} - * event. - * @property onPlaySucceeded - * @type {Function} - * @deprecated Use addEventListener and the "succeeded" event. - */ - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "SoundInstance/interrupted:event"}}{{/crossLink}} - * event. - * @property onPlayInterrupted - * @type {Function} - * @deprecated Use addEventListener and the "interrupted" event. - */ - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "SoundInstance/failed:event"}}{{/crossLink}} - * event. - * @property onPlayFailed - * @type {Function} - * @deprecated Use addEventListener and the "failed" event. - */ - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "SoundInstance/complete:event"}}{{/crossLink}} - * event. - * @property onComplete - * @type {Function} - * @deprecated Use addEventListener and the "complete" event. - */ - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "SoundInstance/loop:event"}}{{/crossLink}} - * event. - * @property onLoop - * @type {Function} - * @deprecated Use addEventListener and the "loop" event. - */ - /** * A helper method that dispatches all events for SoundInstance. * @method _sendEvent @@ -3485,22 +3344,24 @@ this.createjs = this.createjs || {}; * 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, owner) { - this._owner = owner; + 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(); //TODO test how this affects when we have mono audio + 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._owner._arrayBuffers[this.src].duration * 1000; - } + if (this._owner.isPreloadComplete(this.src) && !this._duration) {this._duration = this._owner._arrayBuffers[this.src].duration * 1000;} this._endedHandler = createjs.proxy(this._handleSoundComplete, this); }; @@ -3516,19 +3377,14 @@ this.createjs = this.createjs || {}; this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); } - if (this.gainNode.numberOfOutputs != 0) { - this.gainNode.disconnect(0); - } // this works because we only have one connection, and it returns 0 if we've already disconnected it. + 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._startTime = 0; // This is used by getPosition + this._playbackStartTime = 0; // This is used by getPosition - if (window.createjs == null) { - return; - } createjs.Sound._playFinished(this); }; @@ -3544,7 +3400,7 @@ this.createjs = this.createjs || {}; if(audioNode) { audioNode.stop(0); audioNode.disconnect(this.panNode); - audioNode = null; // release reference so Web Audio can handle removing references and garbage collection + audioNode = null; } return audioNode; }; @@ -3567,11 +3423,8 @@ this.createjs = this.createjs || {}; * @protected */ p._handleSoundReady = function (event) { - if (window.createjs == null) { - return; - } - - if ((this._offset*1000) > this.getDuration()) { // converting offset to ms + 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 @@ -3583,15 +3436,14 @@ this.createjs = this.createjs || {}; 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._owner._arrayBuffers[this.src].duration; + var dur = this._duration * 0.001; this.sourceNode = this._createAndPlayAudioNode((this._owner.context.currentTime - dur), this._offset); - this._duration = dur * 1000; // NOTE *1000 because WebAudio reports everything in seconds but js uses milliseconds - this._startTime = this.sourceNode.startTime - 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._startTime, 0); + this._sourceNodeNext = this._createAndPlayAudioNode(this._playbackStartTime, 0); } }; @@ -3608,9 +3460,9 @@ this.createjs = this.createjs || {}; var audioNode = this._owner.context.createBufferSource(); audioNode.buffer = this._owner._arrayBuffers[this.src]; audioNode.connect(this.panNode); - var currentTime = this._owner.context.currentTime; - audioNode.startTime = startTime + audioNode.buffer.duration; //currentTime + audioNode.buffer.duration - (currentTime - startTime); - audioNode.start(audioNode.startTime, offset, audioNode.buffer.duration - offset); + var dur = this._duration * 0.001; + audioNode.startTime = startTime + dur; + audioNode.start(audioNode.startTime, offset+this._startTime, dur - offset); return audioNode; }; @@ -3654,15 +3506,7 @@ this.createjs = this.createjs || {}; * @protected */ p._beginPlaying = function (offset, loop, volume, pan) { - if (window.createjs == null) { - return; - } - - if (!this.src) { - return; - } - - this._offset = offset / 1000; //convert ms to sec + this._offset = offset * 0.001; //convert ms to sec this._remainingLoops = loop; this.volume = volume; this.pan = pan; @@ -3689,22 +3533,19 @@ this.createjs = this.createjs || {}; * @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) { - this.paused = this._paused = true; + if (this._paused || this.playState != createjs.Sound.PLAY_SUCCEEDED) {return false;} - this._offset = this._owner.context.currentTime - this._startTime; // 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); + this.paused = this._paused = true; - if (this.gainNode.numberOfOutputs != 0) { - this.gainNode.disconnect(); - } // this works because we only have one connection, and it returns 0 if we've already disconnected it. + 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); - clearTimeout(this._delayTimeoutId); // clear timeout that plays delayed sound - clearTimeout(this._soundCompleteTimeout); // clear timeout that triggers sound complete - return true; - } - return false; + if (this.gainNode.numberOfOutputs != 0) {this.gainNode.disconnect();} + + clearTimeout(this._delayTimeoutId); + clearTimeout(this._soundCompleteTimeout); + return true; }; /** @@ -3721,10 +3562,8 @@ this.createjs = this.createjs || {}; * @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(null); + if (!this._paused) {return false;} + this._handleSoundReady(); return true; }; @@ -3764,23 +3603,20 @@ this.createjs = this.createjs || {}; */ p.setVolume = function (value) { this.volume = value; - return true; // This is always true because even if the volume is not updated, the value is set + 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 - * @return {Boolean} if the volume was updated. * @protected */ p._updateVolume = function () { var newVolume = this._muted ? 0 : this._volume; if (newVolume != this.gainNode.gain.value) { this.gainNode.gain.value = newVolume; - return true; } - return false; }; /** @@ -3810,9 +3646,7 @@ this.createjs = this.createjs || {}; * @since 0.4.0 */ p.setMute = function (value) { - if (value == null || value == undefined) { - return false; - } + if (value == null) {return false;} this._muted = value; this._updateVolume(); @@ -3852,6 +3686,7 @@ this.createjs = this.createjs || {}; 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; }; /** @@ -3885,7 +3720,7 @@ this.createjs = this.createjs || {}; if (this._paused || this.sourceNode == null) { var pos = this._offset; } else { - var pos = this._owner.context.currentTime - this._startTime; + var pos = this._owner.context.currentTime - this._playbackStartTime; } return pos * 1000; // pos in seconds * 1000 to give milliseconds @@ -3903,7 +3738,7 @@ this.createjs = this.createjs || {}; * @param {Number} value The position to place the playhead, in milliseconds. */ p.setPosition = function (value) { - this._offset = value / 1000; // convert milliseconds to seconds + 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 @@ -3912,9 +3747,7 @@ this.createjs = this.createjs || {}; 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(null); - } + if (!this._paused && this.playState == createjs.Sound.PLAY_SUCCEEDED) {this._handleSoundReady();} return true; }; @@ -3955,21 +3788,18 @@ this.createjs = this.createjs || {}; 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._startTime = this.sourceNode.startTime; - this._sourceNodeNext = this._createAndPlayAudioNode(this._startTime, 0); + this._playbackStartTime = this.sourceNode.startTime; + this._sourceNodeNext = this._createAndPlayAudioNode(this._playbackStartTime, 0); this._soundCompleteTimeout = setTimeout(this._endedHandler, this._duration); } else { - this._handleSoundReady(null); + this._handleSoundReady(); } this._sendEvent("loop"); return; } - if (window.createjs == null) { - return; - } this._cleanUp(); this.playState = createjs.Sound.PLAY_FINISHED; this._sendEvent("complete"); @@ -3977,9 +3807,6 @@ this.createjs = this.createjs || {}; // Play has failed, which can happen for a variety of reasons. p.playFailed = function () { - if (window.createjs == null) { - return; - } this._cleanUp(); this.playState = createjs.Sound.PLAY_FAILED; this._sendEvent("failed"); @@ -4023,13 +3850,6 @@ this.createjs = this.createjs || {}; */ p.src = null; - /** - * The original source of the sound, before it is altered with a basePath. - * #property src - * @type {String} - */ - p.originalSrc = null; - /** * The decoded AudioBuffer array that is returned when loading is complete. * #property result @@ -4054,17 +3874,16 @@ this.createjs = this.createjs || {}; p.onprogress = null; /** - * The callback that fires if the load hits an error. - * #property onError + * The callback that fires if the load hits an error. This follows HTML tag naming. + * #property onerror * @type {Method} * @protected */ - p.onError = null; + p.onerror = null; // constructor p._init = function (src, owner) { this.src = src; - this.originalSrc = src; this.owner = owner; }; @@ -4074,16 +3893,13 @@ this.createjs = this.createjs || {}; * @param {String} src The path to the sound. */ p.load = function (src) { - if (src != null) { - // TODO does this need to set this.originalSrc - this.src = 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.onerror = createjs.proxy(this.handleError, this); this.request.onprogress = createjs.proxy(this.handleProgress, this); this.request.send(); @@ -4101,7 +3917,7 @@ this.createjs = this.createjs || {}; */ p.handleProgress = function (loaded, total) { this.progress = loaded / total; - this.onprogress != null && this.onprogress({loaded:loaded, total:total, progress:this.progress}); + this.onprogress && this.onprogress({loaded:loaded, total:total, progress:this.progress}); }; /** @@ -4123,9 +3939,8 @@ this.createjs = this.createjs || {}; p.handleAudioDecoded = function (decodedAudio) { this.progress = 1; this.result = decodedAudio; - this.src = this.originalSrc; this.owner.addPreloadResults(this.src, this.result); - this.onload && this.onload(); + this.onload && this.onload(this); }; /** @@ -4138,6 +3953,23 @@ this.createjs = this.createjs || {}; 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]"; }; @@ -4197,12 +4029,13 @@ this.createjs = this.createjs || {}; * 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 9 html limitations</b><br /> + * <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.</li></ul> * @@ -4291,6 +4124,17 @@ this.createjs = this.createjs || {}; */ 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 @@ -4325,14 +4169,9 @@ this.createjs = this.createjs || {}; * @static */ s.isSupported = function () { - if (createjs.Sound.BrowserDetect.isIOS && !s.enableIOS) { - return false; - } + if (createjs.Sound.BrowserDetect.isIOS && !s.enableIOS) {return false;} s._generateCapabilities(); - var t = s.tag; // OJR do we still need this check, when cap will already be null if this is the case - if (t == null || s._capabilities == null) { - return false; - } + if (s._capabilities == null) {return false;} return true; }; @@ -4344,13 +4183,9 @@ this.createjs = this.createjs || {}; * @protected */ s._generateCapabilities = function () { - if (s._capabilities != null) { - return; - } - var t = s.tag = document.createElement("audio"); - if (t.canPlayType == null) { - return null; - } + if (s._capabilities != null) {return;} + var t = document.createElement("audio"); + if (t.canPlayType == null) {return null;} s._capabilities = { panning:true, @@ -4383,7 +4218,7 @@ this.createjs = this.createjs || {}; p._audioSources = null; /** - * The default number of instances to allow. Passed back to {{#crossLink "Sound"}}{{/crossLink}} when a source + * 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. * @@ -4395,9 +4230,6 @@ this.createjs = this.createjs || {}; */ p.defaultNumChannels = 2; - // Proxies, make removing listeners easier. - p.loadedHandler = null; - /** * An initialization function run by the constructor * @method _init @@ -4422,52 +4254,17 @@ this.createjs = this.createjs || {}; 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 || this.defaultNumChannels; - for (var i = 0; i < l; i++) { // OJR should we be enforcing s.MAX_INSTANCES here? Does the chrome bug still exist, or can we change this code? + var l = instances; + for (var i = 0; i < l; i++) { tag = this._createTag(src); channel.add(tag); } - tag.id = src; // co-opting id as we need a way to store original src in case it is changed before loading - this.loadedHandler = createjs.proxy(this._handleTagLoad, this); // we need this bind to be able to remove event listeners - tag.addEventListener && tag.addEventListener("canplaythrough", this.loadedHandler); - if(tag.onreadystatechange == null) { - tag.onreadystatechange = this.loadedHandler; - } else { - var f = tag.onreadystatechange; - // OJR will this lose scope? - tag.onreadystatechange = function() { - f(); - this.loadedHandler(); - } - } - return { - tag:tag, // Return one instance for preloading purposes - numChannels:l // The default number of channels to make for this Sound or the passed in value + tag:tag // Return one instance for preloading purposes }; }; - // TODO remove this when | approach is removed - /** - * Deprecated as this will not be required with new approach to basePath. - * Checks if src was changed on tag used to create instances in TagPool before loading - * Currently PreloadJS does this when a basePath is set, so we are replicating that behavior for internal preloading. - * @method _handleTagLoad - * @param event - * @protected - * @deprecated - */ - p._handleTagLoad = function(event) { - // cleanup and so we don't send the event more than once - event.target.removeEventListener && event.target.removeEventListener("canplaythrough", this.loadedHandler); - event.target.onreadystatechange = null; - - if (event.target.src == event.target.id) { return; } - // else src has changed before loading, and we need to make the change to TagPool because we pre create tags - createjs.HTMLAudioPlugin.TagPool.checkSrc(event.target.id); - }; - /** * Create an HTML audio tag. * @method _createTag @@ -4503,7 +4300,7 @@ this.createjs = this.createjs || {}; * @since 0.4.1 */ p.removeAllSounds = function () { - this._audioSources = {}; // this drops all references, in theory freeing them for garbage collection + this._audioSources = {}; createjs.HTMLAudioPlugin.TagPool.removeAll(); }; @@ -4511,9 +4308,11 @@ this.createjs = this.createjs || {}; * 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) { + 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); @@ -4523,7 +4322,7 @@ this.createjs = this.createjs || {}; this.preload(src, {tag:tag}); } - return new createjs.HTMLAudioPlugin.SoundInstance(src, this); + return new createjs.HTMLAudioPlugin.SoundInstance(src, startTime, duration, this); }; /** @@ -4541,12 +4340,12 @@ this.createjs = this.createjs || {}; * Internally preload a sound. * @method preload * @param {String} src The sound URI to load. - * @param {Object} instance An object containing a tag property that is an HTML audio tag used to load src. + * @param {Object} tag An HTML audio tag used to load src. * @since 0.4.0 */ - p.preload = function (src, instance) { + p.preload = function (src, tag) { this._audioSources[src] = true; - new createjs.HTMLAudioPlugin.Loader(src, instance.tag); + new createjs.HTMLAudioPlugin.Loader(src, tag); }; p.toString = function () { @@ -4563,8 +4362,8 @@ this.createjs = this.createjs || {}; // NOTE Documentation for the SoundInstance class in WebAudioPlugin file. Each plugin generates a SoundInstance that // follows the same interface. - function SoundInstance(src, owner) { - this._init(src, owner); + function SoundInstance(src, startTime, duration, owner) { + this._init(src, startTime, duration, owner); } var p = SoundInstance.prototype = new createjs.EventDispatcher(); @@ -4575,10 +4374,9 @@ this.createjs = this.createjs || {}; p._owner = null; p.loaded = false; p._offset = 0; - p._delay = 0; + p._startTime = 0; p._volume = 1; - // IE8 has Object.defineProperty, but only for DOM objects, so check if fails to suppress errors - try { + if (createjs.definePropertySupported) { Object.defineProperty(p, "volume", { get: function() { return this._volume; @@ -4590,11 +4388,10 @@ this.createjs = this.createjs || {}; this._updateVolume(); } }); - } catch (e) { - // dispatch message or error? - }; + } p.pan = 0; p._duration = 0; + p._audioSpriteStopTime = null; // HTMLAudioPlugin only p._remainingLoops = 0; p._delayTimeoutId = null; p.tag = null; @@ -4606,16 +4403,25 @@ this.createjs = this.createjs || {}; p._endedHandler = null; p._readyHandler = null; p._stalledHandler = null; + p._audioSpriteEndHandler = null; p.loopHandler = null; // Constructor - p._init = function (src, owner) { + 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); }; @@ -4628,11 +4434,14 @@ this.createjs = this.createjs || {}; 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 = 0; + tag.currentTime = this._startTime; } catch (e) { } // Reset Position createjs.HTMLAudioPlugin.TagPool.setInstance(this.src, tag); @@ -4640,16 +4449,11 @@ this.createjs = this.createjs || {}; } clearTimeout(this._delayTimeoutId); - if (window.createjs == null) { - return; - } createjs.Sound._playFinished(this); }; p._interrupt = function () { - if (this.tag == null) { - return; - } + if (this.tag == null) {return;} this.playState = createjs.Sound.PLAY_INTERRUPTED; this._cleanUp(); this.paused = this._paused = false; @@ -4658,14 +4462,11 @@ this.createjs = this.createjs || {}; // Public API p.play = function (interrupt, delay, offset, loop, volume, pan) { - this._cleanUp(); //LM: Is this redundant? + this._cleanUp(); createjs.Sound._playInstance(this, interrupt, delay, offset, loop, volume, pan); }; p._beginPlaying = function (offset, loop, volume, pan) { - if (window.createjs == null) { - return -1; - } var tag = this.tag = createjs.HTMLAudioPlugin.TagPool.getInstance(this.src); if (tag == null) { this.playFailed(); @@ -4677,8 +4478,7 @@ this.createjs = this.createjs || {}; // Reset this instance. this._offset = offset; this.volume = volume; - this.pan = pan; // not pan has no effect - this._updateVolume(); // note this will set for mute and _masterMute + this._updateVolume(); this._remainingLoops = loop; if (tag.readyState !== 4) { @@ -4697,55 +4497,49 @@ this.createjs = this.createjs || {}; // 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 NOTE this will stop playback, and I think we should remove this and let the developer decide how to handle stalled instances + 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) { - if (window.createjs == null) { - return; - } - - // OJR would like a cleaner way to do this in _init, discuss with LM - this._duration = this.tag.duration * 1000; // need this for setPosition on stopped sounds - 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(); // OJR: throw error? + this.playFailed(); return; - } else if (this._offset > 0) { - this.tag.currentTime = this._offset * 0.001; } - if (this._remainingLoops == -1) { - this.tag.loop = true; - } - if(this._remainingLoops != 0) { - this.tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); - this.tag.loop = true; + 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 == -1) { + this.tag.loop = true; + } 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; - // Note: when paused by user, we hold a reference to our tag. We do not release it until stopped. this.tag.pause(); - clearTimeout(this._delayTimeoutId); - return true; } return false; }; p.resume = function () { - if (!this._paused || this.tag == null) { - return false; - } + if (!this._paused || this.tag == null) {return false;} this.paused = this._paused = false; this.tag.play(); return true; @@ -4761,7 +4555,6 @@ this.createjs = this.createjs || {}; p.setMasterVolume = function (value) { this._updateVolume(); - return true; }; p.setVolume = function (value) { @@ -4772,12 +4565,7 @@ this.createjs = this.createjs || {}; 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; - } - return true; - } else { - return false; + if (newVolume != this.tag.volume) {this.tag.volume = newVolume;} } }; @@ -4787,14 +4575,10 @@ this.createjs = this.createjs || {}; p.setMasterMute = function (isMuted) { this._updateVolume(); - return true; }; p.setMute = function (isMuted) { - if (isMuted == null || isMuted == undefined) { - return false; - } - + if (isMuted == null) {return false;} this._muted = isMuted; this._updateVolume(); return true; @@ -4804,7 +4588,7 @@ this.createjs = this.createjs || {}; return this._muted; }; - // Can not set pan in HTML + // Can not set pan in HTML audio p.setPan = function (value) { return false; }; @@ -4814,10 +4598,8 @@ this.createjs = this.createjs || {}; }; p.getPosition = function () { - if (this.tag == null) { - return this._offset; - } - return this.tag.currentTime * 1000; + if (this.tag == null) {return this._offset;} + return (this.tag.currentTime * 1000) - this._startTime; }; p.setPosition = function (value) { @@ -4826,6 +4608,7 @@ this.createjs = this.createjs || {}; } else { this.tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); try { + value = value + this._startTime; this.tag.currentTime = value * 0.001; } catch (error) { // Out of range return false; @@ -4835,27 +4618,37 @@ this.createjs = this.createjs || {}; return true; }; - p.getDuration = function () { // NOTE this will always return 0 until sound has been played. + 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; - - if (window.createjs == null) { - return; - } this.playState = createjs.Sound.PLAY_FINISHED; this._cleanUp(); this._sendEvent("complete"); }; - // handles looping functionality + // 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; @@ -4865,9 +4658,6 @@ this.createjs = this.createjs || {}; }; p.playFailed = function () { - if (window.createjs == null) { - return; - } this.playState = createjs.Sound.PLAY_FAILED; this._cleanUp(); this._sendEvent("failed"); @@ -4946,12 +4736,12 @@ this.createjs = this.createjs || {}; 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); // OJR not 100% sure we need this, just copied from PreloadJS + 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); // OJR not 100% sure we need this, just copied from PreloadJS + this.tag.onreadystatechange = createjs.proxy(this.sendLoadedEvent, this); } } @@ -4994,6 +4784,7 @@ this.createjs = this.createjs || {}; 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 @@ -5056,9 +4847,7 @@ this.createjs = this.createjs || {}; */ s.remove = function (src) { var channel = s.tags[src]; - if (channel == null) { - return false; - } + if (channel == null) {return false;} channel.removeAll(); delete(s.tags[src]); return true; @@ -5085,9 +4874,7 @@ this.createjs = this.createjs || {}; */ s.getInstance = function (src) { var channel = s.tags[src]; - if (channel == null) { - return null; - } + if (channel == null) {return null;} return channel.get(); }; @@ -5101,27 +4888,20 @@ this.createjs = this.createjs || {}; */ s.setInstance = function (src, tag) { var channel = s.tags[src]; - if (channel == null) { - return null; - } + if (channel == null) {return null;} return channel.set(tag); }; /** - * A function to check if src has changed in the loaded audio tag. - * This is required because PreloadJS appends a basePath to the src before loading. - * Note this is currently only called when a change is detected - * #method checkSrc - * @param src the unaltered src that is used to store the channel. - * @static - * @protected + * 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.checkSrc = function (src) { + s.getDuration= function (src) { var channel = s.tags[src]; - if (channel == null) { - return null; - } - channel.checkSrcChange(); + if (channel == null) {return 0;} + return channel.getDuration(); }; var p = TagPool.prototype; @@ -5161,6 +4941,15 @@ this.createjs = this.createjs || {}; */ 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; @@ -5183,8 +4972,12 @@ this.createjs = this.createjs || {}; * #method removeAll */ p.removeAll = function () { - // This may not be neccessary + 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; @@ -5197,14 +4990,10 @@ this.createjs = this.createjs || {}; * @return {HTMLAudioElement} An HTML audio tag. */ p.get = function () { - if (this.tags.length == 0) { - return null; - } + 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); - } + if (tag.parentNode == null) {document.body.appendChild(tag);} return tag; }; @@ -5215,26 +5004,19 @@ this.createjs = this.createjs || {}; */ p.set = function (tag) { var index = createjs.indexOf(this.tags, tag); - if (index == -1) { - this.tags.push(tag); - } + if (index == -1) {this.tags.push(tag);} this.available = this.tags.length; }; /** - * Make sure the src of all other tags is correct after load. - * This is needed because PreloadJS appends a basePath to src before loading. - * #method checkSrcChange + * 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.checkSrcChange = function () { - // the last tag always has the latest src after loading - //var i = this.length-1; // this breaks in Firefox because it is not correctly removing an event listener - var i = this.tags.length - 1; - if(i == -1) return; // CodeCombat addition; sometimes errors in IE without this... - var newSrc = this.tags[i].src; - while(i--) { - this.tags[i].src = newSrc; - } + 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 () { diff --git a/vendor/scripts/tweenjs-NEXT.combined.js b/vendor/scripts/tweenjs-NEXT.combined.js index 179548b27..851373235 100644 --- a/vendor/scripts/tweenjs-NEXT.combined.js +++ b/vendor/scripts/tweenjs-NEXT.combined.js @@ -909,9 +909,10 @@ var p = Tween.prototype = new createjs.EventDispatcher(); if (!target.tweenjs_count) { return; } var tweens = Tween._tweens; for (var i=tweens.length-1; i>=0; i--) { - if (tweens[i]._target == target) { - tweens[i]._paused = true; - tweens.splice(i,1); + var tween = tweens[i]; + if (tween._target == target) { + tween._paused = true; + tweens.splice(i, 1); } } target.tweenjs_count = 0; @@ -927,7 +928,7 @@ var p = Tween.prototype = new createjs.EventDispatcher(); var tweens = Tween._tweens; for (var i= 0, l=tweens.length; i<l; i++) { var tween = tweens[i]; - tween.paused = true; + tween._paused = true; tween.target.tweenjs_count = 0; } tweens.length = 0; @@ -1376,6 +1377,7 @@ var p = Tween.prototype = new createjs.EventDispatcher(); * @return {Tween} This tween instance (for chaining calls) */ p.setPaused = function(value) { + if (this._paused === !!value) { return this; } this._paused = !!value; Tween._register(this, !value); return this; @@ -2755,6 +2757,6 @@ this.createjs = this.createjs || {}; * @type String * @static **/ - s.buildDate = /*date*/"Thu, 12 Dec 2013 23:37:07 GMT"; // injected by build process + s.buildDate = /*date*/"Wed, 02 Apr 2014 20:57:09 GMT"; // injected by build process })(); From 09a47cc5d822c5ecd582c8edaaa3c9a03c35ec1e Mon Sep 17 00:00:00 2001 From: Scott Erickson <sderickson@gmail.com> Date: Wed, 7 May 2014 11:11:22 -0700 Subject: [PATCH 2/2] Fixed #951. The parser was removing the shadow shape but not the tween of the shadow. --- app/lib/sprites/SpriteParser.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/lib/sprites/SpriteParser.coffee b/app/lib/sprites/SpriteParser.coffee index 3138bb47b..0de48c013 100644 --- a/app/lib/sprites/SpriteParser.coffee +++ b/app/lib/sprites/SpriteParser.coffee @@ -379,7 +379,9 @@ module.exports = class SpriteParser argsSource = argsSource.replace(/cjs(.+)\)/, '"createjs$1)"') # turns cjs.Ease.get(0.5) args = eval "[#{argsSource}]" - if args[0]?.state?[0]?.t?.search?("shape") is 0 and not _.find(localShapes, bn: args[0].state[0].t) + shadowTween = args[0]?.search?('shape') is 0 and not _.find(localShapes, bn: args[0]) + shadowTween = shadowTween or args[0]?.state?[0]?.t?.search?("shape") is 0 and not _.find(localShapes, bn: args[0].state[0].t) + if shadowTween console.log "Skipping tween", name, argsSource, args, "from localShapes", localShapes, "presumably because it's a shadow we skipped." return callExpressions.push {n: name, a: args}