/* * LoadQueue * Visit http://createjs.com/ for documentation, updates and examples. * * * Copyright (c) 2012 gskinner.com, inc. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ /** * PreloadJS provides a consistent way to preload content for use in HTML applications. Preloading can be done using * HTML tags, as well as XHR. * * By default, PreloadJS will try and load content using XHR, since it provides better support for progress and * completion events, <b>however due to cross-domain issues, it may still be preferable to use tag-based loading * instead</b>. Note that some content requires XHR to work (plain text, web audio), and some requires tags (HTML audio). * Note this is handled automatically where possible. * * PreloadJS currently supports all modern browsers, and we have done our best to include support for most older * browsers. If you find an issue with any specific OS/browser combination, please visit http://community.createjs.com/ * and report it. * * <h4>Getting Started</h4> * To get started, check out the {{#crossLink "LoadQueue"}}{{/crossLink}} class, which includes a quick overview of how * to load files and process results. * * <h4>Example</h4> * * var queue = new createjs.LoadQueue(); * queue.installPlugin(createjs.Sound); * queue.on("complete", handleComplete, this); * queue.loadFile({id:"sound", src:"http://path/to/sound.mp3"}); * queue.loadManifest([ * {id: "myImage", src:"path/to/myImage.jpg"} * ]); * function handleComplete() { * createjs.Sound.play("sound"); * var image = queue.getResult("myImage"); * document.body.appendChild(image); * } * * <b>Important note on plugins:</b> Plugins must be installed <i>before</i> items are added to the queue, otherwise * they will not be processed, even if the load has not actually kicked off yet. Plugin functionality is handled when * the items are added to the LoadQueue. * * <h4>Browser Support</h4> * PreloadJS is partially supported in all browsers, and fully supported in all modern browsers. Known exceptions: * <ul><li>XHR loading of any content will not work in many older browsers (See a matrix here: <a href="http://caniuse.com/xhr2" target="_blank">http://caniuse.com/xhr2</a>). * In many cases, you can fall back on tag loading (images, audio, CSS, scripts, and SVG). Text and * WebAudio will only work with XHR.</li> * <li>Some formats have poor support for complete events in IE 6, 7, and 8 (SVG, tag loading of scripts, XML/JSON)</li> * <li>Opera has poor support for SVG loading with XHR</li> * <li>CSS loading in Android and Safari will not work with tags (currently, a workaround is in progress)</li> * <li>Local loading is not permitted with XHR, which is required by some file formats. When testing local content * use either a local server, or enable tag loading, which is supported for most formats. See {{#crossLink "LoadQueue/setPreferXHR"}}{{/crossLink}} * for more information.</li> * </ul> * * <h4>Cross-domain Loading</h4> * Most content types can be loaded cross-domain, as long as the server supports CORS. PreloadJS also has internal * support for images served from a CORS-enabled server, via the `crossOrigin` argument on the {{#crossLink "LoadQueue"}}{{/crossLink}} * constructor. If set to a string value (such as "Anonymous"), the "crossOrigin" property of images generated by * PreloadJS is set to that value. Please note that setting a `crossOrigin` value on an image that is served from a * server without CORS will cause other errors. For more info on CORS, visit https://en.wikipedia.org/wiki/Cross-origin_resource_sharing. * * @module PreloadJS * @main PreloadJS */ // namespace: this.createjs = this.createjs || {}; /* TODO: WINDOWS ISSUES * No error for HTML audio in IE 678 * SVG no failure error in IE 67 (maybe 8) TAGS AND XHR * No script complete handler in IE 67 TAGS (XHR is fine) * No XML/JSON in IE6 TAGS * Need to hide loading SVG in Opera TAGS * No CSS onload/readystatechange in Safari or Android TAGS (requires rule checking) * SVG no load or failure in Opera XHR * Reported issues with IE7/8 */ (function () { "use strict"; // constructor /** * The LoadQueue class is the main API for preloading content. LoadQueue is a load manager, which can preload either * a single file, or queue of files. * * <b>Creating a Queue</b><br /> * To use LoadQueue, create a LoadQueue instance. If you want to force tag loading where possible, set the preferXHR * argument to false. * * var queue = new createjs.LoadQueue(true); * * <b>Listening for Events</b><br /> * Add any listeners you want to the queue. Since PreloadJS 0.3.0, the {{#crossLink "EventDispatcher"}}{{/crossLink}} * lets you add as many listeners as you want for events. You can subscribe to the following events:<ul> * <li>{{#crossLink "AbstractLoader/complete:event"}}{{/crossLink}}: fired when a queue completes loading all * files</li> * <li>{{#crossLink "AbstractLoader/error:event"}}{{/crossLink}}: fired when the queue encounters an error with * any file.</li> * <li>{{#crossLink "AbstractLoader/progress:event"}}{{/crossLink}}: Progress for the entire queue has * changed.</li> * <li>{{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}}: A single file has completed loading.</li> * <li>{{#crossLink "LoadQueue/fileprogress:event"}}{{/crossLink}}: Progress for a single file has changes. Note * that only files loaded with XHR (or possibly by plugins) will fire progress events other than 0 or 100%.</li> * </ul> * * queue.on("fileload", handleFileLoad, this); * queue.on("complete", handleComplete, this); * * <b>Adding files and manifests</b><br /> * Add files you want to load using {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} or add multiple files at a * time using a list or a manifest definition using {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}}. Files are * appended to the end of the active queue, so you can use these methods as many times as you like, whenever you * like. * * queue.loadFile("filePath/file.jpg"); * queue.loadFile({id:"image", src:"filePath/file.jpg"}); * queue.loadManifest(["filePath/file.jpg", {id:"image", src:"filePath/file.jpg"}]); * * // Use an external manifest * queue.loadManifest("path/to/manifest.json"); * queue.loadManifest({src:"manifest.json", type:"manifest"}); * * If you pass `false` as the `loadNow` parameter, the queue will not kick of the load of the files, but it will not * stop if it has already been started. Call the {{#crossLink "AbstractLoader/load"}}{{/crossLink}} method to begin * a paused queue. Note that a paused queue will automatically resume when new files are added to it with a * `loadNow` argument of `true`. * * queue.load(); * * <b>File Types</b><br /> * The file type of a manifest item is auto-determined by the file extension. The pattern matching in PreloadJS * should handle the majority of standard file and url formats, and works with common file extensions. If you have * either a non-standard file extension, or are serving the file using a proxy script, then you can pass in a * <code>type</code> property with any manifest item. * * queue.loadFile({src:"path/to/myFile.mp3x", type:createjs.Types.SOUND}); * * // Note that PreloadJS will not read a file extension from the query string * queue.loadFile({src:"http://server.com/proxy?file=image.jpg", type:createjs.Types.IMAGE}); * * Supported types are defined on the {{#crossLink "AbstractLoader"}}{{/crossLink}} class, and include: * <ul> * <li>{{#crossLink "Types/BINARY:property"}}{{/crossLink}}: Raw binary data via XHR</li> * <li>{{#crossLink "Types/CSS:property"}}{{/crossLink}}: CSS files</li> * <li>{{#crossLink "Types/IMAGE:property"}}{{/crossLink}}: Common image formats</li> * <li>{{#crossLink "Types/JAVASCRIPT:property"}}{{/crossLink}}: JavaScript files</li> * <li>{{#crossLink "Types/JSON:property"}}{{/crossLink}}: JSON data</li> * <li>{{#crossLink "Types/JSONP:property"}}{{/crossLink}}: JSON files cross-domain</li> * <li>{{#crossLink "Types/MANIFEST:property"}}{{/crossLink}}: A list of files to load in JSON format, see * {{#crossLink "AbstractLoader/loadManifest"}}{{/crossLink}}</li> * <li>{{#crossLink "Types/SOUND:property"}}{{/crossLink}}: Audio file formats</li> * <li>{{#crossLink "Types/SPRITESHEET:property"}}{{/crossLink}}: JSON SpriteSheet definitions. This * will also load sub-images, and provide a {{#crossLink "SpriteSheet"}}{{/crossLink}} instance.</li> * <li>{{#crossLink "Types/SVG:property"}}{{/crossLink}}: SVG files</li> * <li>{{#crossLink "Types/TEXT:property"}}{{/crossLink}}: Text files - XHR only</li> * <li>{{#crossLink "Types/VIDEO:property"}}{{/crossLink}}: Video objects</li> * <li>{{#crossLink "Types/XML:property"}}{{/crossLink}}: XML data</li> * </ul> * * <em>Note: Loader types used to be defined on LoadQueue, but have been moved to the Types class</em> * * <b>Handling Results</b><br /> * When a file is finished downloading, a {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}} event is * dispatched. In an example above, there is an event listener snippet for fileload. Loaded files are usually a * formatted object that can be used immediately, including: * <ul> * <li>Binary: The binary loaded result</li> * <li>CSS: A <link /> tag</li> * <li>Image: An <img /> tag</li> * <li>JavaScript: A <script /> tag</li> * <li>JSON/JSONP: A formatted JavaScript Object</li> * <li>Manifest: A JavaScript object. * <li>Sound: An <audio /> tag</a> * <li>SpriteSheet: A {{#crossLink "SpriteSheet"}}{{/crossLink}} instance, containing loaded images. * <li>SVG: An <object /> tag</li> * <li>Text: Raw text</li> * <li>Video: A Video DOM node</li> * <li>XML: An XML DOM node</li> * </ul> * * function handleFileLoad(event) { * var item = event.item; // A reference to the item that was passed in to the LoadQueue * var type = item.type; * * // Add any images to the page body. * if (type == createjs.Types.IMAGE) { * document.body.appendChild(event.result); * } * } * * At any time after the file has been loaded (usually after the queue has completed), any result can be looked up * via its "id" using {{#crossLink "LoadQueue/getResult"}}{{/crossLink}}. If no id was provided, then the * "src" or file path can be used instead, including the `path` defined by a manifest, but <strong>not including</strong> * a base path defined on the LoadQueue. It is recommended to always pass an id if you want to look up content. * * var image = queue.getResult("image"); * document.body.appendChild(image); * * Raw loaded content can be accessed using the <code>rawResult</code> property of the {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}} * event, or can be looked up using {{#crossLink "LoadQueue/getResult"}}{{/crossLink}}, passing `true` as the 2nd * argument. This is only applicable for content that has been parsed for the browser, specifically: JavaScript, * CSS, XML, SVG, and JSON objects, or anything loaded with XHR. * * var image = queue.getResult("image", true); // load the binary image data loaded with XHR. * * <b>Plugins</b><br /> * LoadQueue has a simple plugin architecture to help process and preload content. For example, to preload audio, * make sure to install the <a href="http://soundjs.com">SoundJS</a> Sound class, which will help load HTML audio, * Flash audio, and WebAudio files. This should be installed <strong>before</strong> loading any audio files. * * queue.installPlugin(createjs.Sound); * * <h4>Known Browser Issues</h4> * <ul> * <li>Browsers without audio support can not load audio files.</li> * <li>Safari on Mac OS X can only play HTML audio if QuickTime is installed</li> * <li>HTML Audio tags will only download until their <code>canPlayThrough</code> event is fired. Browsers other * than Chrome will continue to download in the background.</li> * <li>When loading scripts using tags, they are automatically added to the document.</li> * <li>Scripts loaded via XHR may not be properly inspectable with browser tools.</li> * <li>IE6 and IE7 (and some other browsers) may not be able to load XML, Text, or JSON, since they require * XHR to work.</li> * <li>Content loaded via tags will not show progress, and will continue to download in the background when * canceled, although no events will be dispatched.</li> * </ul> * * @class LoadQueue * @param {Boolean} [preferXHR=true] Determines whether the preload instance will favor loading with XHR (XML HTTP * Requests), or HTML tags. When this is `false`, the queue will use tag loading when possible, and fall back on XHR * when necessary. * @param {String} [basePath=""] A path that will be prepended on to the source parameter of all items in the queue * before they are loaded. Sources beginning with a protocol such as `http://` or a relative path such as `../` * will not receive a base path. * @param {String|Boolean} [crossOrigin=""] An optional flag to support images loaded from a CORS-enabled server. To * use it, set this value to `true`, which will default the crossOrigin property on images to "Anonymous". Any * string value will be passed through, but only "" and "Anonymous" are recommended. <strong>Note: The crossOrigin * parameter is deprecated. Use LoadItem.crossOrigin instead</strong> * * @constructor * @extends AbstractLoader */ function LoadQueue (preferXHR, basePath, crossOrigin) { this.AbstractLoader_constructor(); /** * An array of the plugins registered using {{#crossLink "LoadQueue/installPlugin"}}{{/crossLink}}. * @property _plugins * @type {Array} * @private * @since 0.6.1 */ this._plugins = []; /** * An object hash of callbacks that are fired for each file type before the file is loaded, giving plugins the * ability to override properties of the load. Please see the {{#crossLink "LoadQueue/installPlugin"}}{{/crossLink}} * method for more information. * @property _typeCallbacks * @type {Object} * @private */ this._typeCallbacks = {}; /** * An object hash of callbacks that are fired for each file extension before the file is loaded, giving plugins the * ability to override properties of the load. Please see the {{#crossLink "LoadQueue/installPlugin"}}{{/crossLink}} * method for more information. * @property _extensionCallbacks * @type {null} * @private */ this._extensionCallbacks = {}; /** * The next preload queue to process when this one is complete. If an error is thrown in the current queue, and * {{#crossLink "LoadQueue/stopOnError:property"}}{{/crossLink}} is `true`, the next queue will not be processed. * @property next * @type {LoadQueue} * @default null */ this.next = null; /** * Ensure loaded scripts "complete" in the order they are specified. Loaded scripts are added to the document head * once they are loaded. Scripts loaded via tags will load one-at-a-time when this property is `true`, whereas * scripts loaded using XHR can load in any order, but will "finish" and be added to the document in the order * specified. * * Any items can be set to load in order by setting the {{#crossLink "maintainOrder:property"}}{{/crossLink}} * property on the load item, or by ensuring that only one connection can be open at a time using * {{#crossLink "LoadQueue/setMaxConnections"}}{{/crossLink}}. Note that when the `maintainScriptOrder` property * is set to `true`, scripts items are automatically set to `maintainOrder=true`, and changing the * `maintainScriptOrder` to `false` during a load will not change items already in a queue. * * <h4>Example</h4> * * var queue = new createjs.LoadQueue(); * queue.setMaxConnections(3); // Set a higher number to load multiple items at once * queue.maintainScriptOrder = true; // Ensure scripts are loaded in order * queue.loadManifest([ * "script1.js", * "script2.js", * "image.png", // Load any time * {src: "image2.png", maintainOrder: true} // Will wait for script2.js * "image3.png", * "script3.js" // Will wait for image2.png before loading (or completing when loading with XHR) * ]); * * @property maintainScriptOrder * @type {Boolean} * @default true */ this.maintainScriptOrder = true; /** * Determines if the LoadQueue will stop processing the current queue when an error is encountered. * @property stopOnError * @type {Boolean} * @default false */ this.stopOnError = false; /** * The number of maximum open connections that a loadQueue tries to maintain. Please see * {{#crossLink "LoadQueue/setMaxConnections"}}{{/crossLink}} for more information. * @property _maxConnections * @type {Number} * @default 1 * @private */ this._maxConnections = 1; /** * An internal list of all the default Loaders that are included with PreloadJS. Before an item is loaded, the * available loader list is iterated, in the order they are included, and as soon as a loader indicates it can * handle the content, it will be selected. The default loader, ({{#crossLink "TextLoader"}}{{/crossLink}} is * last in the list, so it will be used if no other match is found. Typically, loaders will match based on the * {{#crossLink "LoadItem/type"}}{{/crossLink}}, which is automatically determined using the file extension of * the {{#crossLink "LoadItem/src:property"}}{{/crossLink}}. * * Loaders can be removed from PreloadJS by simply not including them. * * Custom loaders installed using {{#crossLink "registerLoader"}}{{/crossLink}} will be prepended to this list * so that they are checked first. * @property _availableLoaders * @type {Array} * @private * @since 0.6.0 */ this._availableLoaders = [ createjs.FontLoader, createjs.ImageLoader, createjs.JavaScriptLoader, createjs.CSSLoader, createjs.JSONLoader, createjs.JSONPLoader, createjs.SoundLoader, createjs.ManifestLoader, createjs.SpriteSheetLoader, createjs.XMLLoader, createjs.SVGLoader, createjs.BinaryLoader, createjs.VideoLoader, createjs.TextLoader ]; /** * The number of built in loaders, so they can't be removed by {{#crossLink "unregisterLoader"}}{{/crossLink}. * @property _defaultLoaderLength * @type {Number} * @private * @since 0.6.0 */ this._defaultLoaderLength = this._availableLoaders.length; this.init(preferXHR, basePath, crossOrigin); } var p = createjs.extend(LoadQueue, createjs.AbstractLoader); var s = LoadQueue; // Remove these @deprecated properties after 1.0 try { Object.defineProperties(s, { POST: { get: createjs.deprecate(function() { return createjs.Methods.POST; }, "AbstractLoader.POST") }, GET: { get: createjs.deprecate(function() { return createjs.Methods.GET; }, "AbstractLoader.GET") }, BINARY: { get: createjs.deprecate(function() { return createjs.Types.BINARY; }, "AbstractLoader.BINARY") }, CSS: { get: createjs.deprecate(function() { return createjs.Types.CSS; }, "AbstractLoader.CSS") }, FONT: { get: createjs.deprecate(function() { return createjs.Types.FONT; }, "AbstractLoader.FONT") }, FONTCSS: { get: createjs.deprecate(function() { return createjs.Types.FONTCSS; }, "AbstractLoader.FONTCSS") }, IMAGE: { get: createjs.deprecate(function() { return createjs.Types.IMAGE; }, "AbstractLoader.IMAGE") }, JAVASCRIPT: { get: createjs.deprecate(function() { return createjs.Types.JAVASCRIPT; }, "AbstractLoader.JAVASCRIPT") }, JSON: { get: createjs.deprecate(function() { return createjs.Types.JSON; }, "AbstractLoader.JSON") }, JSONP: { get: createjs.deprecate(function() { return createjs.Types.JSONP; }, "AbstractLoader.JSONP") }, MANIFEST: { get: createjs.deprecate(function() { return createjs.Types.MANIFEST; }, "AbstractLoader.MANIFEST") }, SOUND: { get: createjs.deprecate(function() { return createjs.Types.SOUND; }, "AbstractLoader.SOUND") }, VIDEO: { get: createjs.deprecate(function() { return createjs.Types.VIDEO; }, "AbstractLoader.VIDEO") }, SPRITESHEET: { get: createjs.deprecate(function() { return createjs.Types.SPRITESHEET; }, "AbstractLoader.SPRITESHEET") }, SVG: { get: createjs.deprecate(function() { return createjs.Types.SVG; }, "AbstractLoader.SVG") }, TEXT: { get: createjs.deprecate(function() { return createjs.Types.TEXT; }, "AbstractLoader.TEXT") }, XML: { get: createjs.deprecate(function() { return createjs.Types.XML; }, "AbstractLoader.XML") } }); } catch (e) {} /** * An internal initialization method, which is used for initial set up, but also to reset the LoadQueue. * @method init * @param preferXHR * @param basePath * @param crossOrigin * @private */ p.init = function (preferXHR, basePath, crossOrigin) { // public properties /** * Try and use XMLHttpRequest (XHR) when possible. Note that LoadQueue will default to tag loading or XHR * loading depending on the requirements for a media type. For example, HTML audio can not be loaded with XHR, * and plain text can not be loaded with tags, so it will default the the correct type instead of using the * user-defined type. * @type {Boolean} * @default true * @since 0.6.0 */ this.preferXHR = true; //TODO: Get/Set this._preferXHR = true; this.setPreferXHR(preferXHR); // protected properties /** * Whether the queue is currently paused or not. * @property _paused * @type {boolean} * @private */ this._paused = false; /** * A path that will be prepended on to the item's {{#crossLink "LoadItem/src:property"}}{{/crossLink}}. The * `_basePath` property will only be used if an item's source is relative, and does not include a protocol such * as `http://`, or a relative path such as `../`. * @property _basePath * @type {String} * @private * @since 0.3.1 */ this._basePath = basePath; /** * An optional flag to set on images that are loaded using PreloadJS, which enables CORS support. Images loaded * cross-domain by servers that support CORS require the crossOrigin flag to be loaded and interacted with by * a canvas. When loading locally, or with a server with no CORS support, this flag can cause other security issues, * so it is recommended to only set it if you are sure the server supports it. Currently, supported values are "" * and "Anonymous". * @property _crossOrigin * @type {String} * @default "" * @private * @since 0.4.1 */ this._crossOrigin = crossOrigin; /** * Determines if the loadStart event was dispatched already. This event is only fired one time, when the first * file is requested. * @property _loadStartWasDispatched * @type {Boolean} * @default false * @private */ this._loadStartWasDispatched = false; /** * Determines if there is currently a script loading. This helps ensure that only a single script loads at once when * using a script tag to do preloading. * @property _currentlyLoadingScript * @type {Boolean} * @private */ this._currentlyLoadingScript = null; /** * An array containing the currently downloading files. * @property _currentLoads * @type {Array} * @private */ this._currentLoads = []; /** * An array containing the queued items that have not yet started downloading. * @property _loadQueue * @type {Array} * @private */ this._loadQueue = []; /** * An array containing downloads that have not completed, so that the LoadQueue can be properly reset. * @property _loadQueueBackup * @type {Array} * @private */ this._loadQueueBackup = []; /** * An object hash of items that have finished downloading, indexed by the {{#crossLink "LoadItem"}}{{/crossLink}} * id. * @property _loadItemsById * @type {Object} * @private */ this._loadItemsById = {}; /** * An object hash of items that have finished downloading, indexed by {{#crossLink "LoadItem"}}{{/crossLink}} * source. * @property _loadItemsBySrc * @type {Object} * @private */ this._loadItemsBySrc = {}; /** * An object hash of loaded items, indexed by the ID of the {{#crossLink "LoadItem"}}{{/crossLink}}. * @property _loadedResults * @type {Object} * @private */ this._loadedResults = {}; /** * An object hash of un-parsed loaded items, indexed by the ID of the {{#crossLink "LoadItem"}}{{/crossLink}}. * @property _loadedRawResults * @type {Object} * @private */ this._loadedRawResults = {}; /** * The number of items that have been requested. This helps manage an overall progress without knowing how large * the files are before they are downloaded. This does not include items inside of loaders such as the * {{#crossLink "ManifestLoader"}}{{/crossLink}}. * @property _numItems * @type {Number} * @default 0 * @private */ this._numItems = 0; /** * The number of items that have completed loaded. This helps manage an overall progress without knowing how large * the files are before they are downloaded. * @property _numItemsLoaded * @type {Number} * @default 0 * @private */ this._numItemsLoaded = 0; /** * A list of scripts in the order they were requested. This helps ensure that scripts are "completed" in the right * order. * @property _scriptOrder * @type {Array} * @private */ this._scriptOrder = []; /** * A list of scripts that have been loaded. Items are added to this list as <code>null</code> when they are * requested, contain the loaded item if it has completed, but not been dispatched to the user, and <code>true</true> * once they are complete and have been dispatched. * @property _loadedScripts * @type {Array} * @private */ this._loadedScripts = []; /** * The last progress amount. This is used to suppress duplicate progress events. * @property _lastProgress * @type {Number} * @private * @since 0.6.0 */ this._lastProgress = NaN; }; // static properties // events /** * This event is fired when an individual file has loaded, and been processed. * @event fileload * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @param {Object} item The file item which was specified in the {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} * or {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}} call. If only a string path or tag was specified, the * object will contain that value as a `src` property. * @param {Object} result The HTML tag or parsed result of the loaded item. * @param {Object} rawResult The unprocessed result, usually the raw text or binary data before it is converted * to a usable object. * @since 0.3.0 */ /** * This {{#crossLink "ProgressEvent"}}{{/crossLink}} that is fired when an an individual file's progress changes. * @event fileprogress * @since 0.3.0 */ /** * This event is fired when an individual file starts to load. * @event filestart * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @param {Object} item The file item which was specified in the {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} * or {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}} call. If only a string path or tag was specified, the * object will contain that value as a property. */ /** * Although it extends {{#crossLink "AbstractLoader"}}{{/crossLink}}, the `initialize` event is never fired from * a LoadQueue instance. * @event initialize * @private */ // public methods /** * Register a custom loaders class. New loaders are given precedence over loaders added earlier and default loaders. * It is recommended that loaders extend {{#crossLink "AbstractLoader"}}{{/crossLink}}. Loaders can only be added * once, and will be prepended to the list of available loaders. * @method registerLoader * @param {Function|AbstractLoader} loader The AbstractLoader class to add. * @since 0.6.0 */ p.registerLoader = function (loader) { if (!loader || !loader.canLoadItem) { throw new Error("loader is of an incorrect type."); } else if (this._availableLoaders.indexOf(loader) != -1) { throw new Error("loader already exists."); //LM: Maybe just silently fail here } this._availableLoaders.unshift(loader); }; /** * Remove a custom loader added using {{#crossLink "registerLoader"}}{{/crossLink}}. Only custom loaders can be * unregistered, the default loaders will always be available. * @method unregisterLoader * @param {Function|AbstractLoader} loader The AbstractLoader class to remove */ p.unregisterLoader = function (loader) { var idx = this._availableLoaders.indexOf(loader); if (idx != -1 && idx < this._defaultLoaderLength - 1) { this._availableLoaders.splice(idx, 1); } }; /** * Change the {{#crossLink "preferXHR:property"}}{{/crossLink}} value. Note that if this is set to `true`, it may * fail, or be ignored depending on the browser's capabilities and the load type. * @method setPreferXHR * @param {Boolean} value * @returns {Boolean} The value of {{#crossLink "preferXHR"}}{{/crossLink}} that was successfully set. * @since 0.6.0 */ p.setPreferXHR = function (value) { // Determine if we can use XHR. XHR defaults to TRUE, but the browser may not support it. //TODO: Should we be checking for the other XHR types? Might have to do a try/catch on the different types similar to createXHR. this.preferXHR = (value != false && window.XMLHttpRequest != null); return this.preferXHR; }; /** * Stops all queued and loading items, and clears the queue. This also removes all internal references to loaded * content, and allows the queue to be used again. * @method removeAll * @since 0.3.0 */ p.removeAll = function () { this.remove(); }; /** * Stops an item from being loaded, and removes it from the queue. If nothing is passed, all items are removed. * This also removes internal references to loaded item(s). * * <h4>Example</h4> * * queue.loadManifest([ * {src:"test.png", id:"png"}, * {src:"test.jpg", id:"jpg"}, * {src:"test.mp3", id:"mp3"} * ]); * queue.remove("png"); // Single item by ID * queue.remove("png", "test.jpg"); // Items as arguments. Mixed id and src. * queue.remove(["test.png", "jpg"]); // Items in an Array. Mixed id and src. * * @method remove * @param {String | Array} idsOrUrls* The id or ids to remove from this queue. You can pass an item, an array of * items, or multiple items as arguments. * @since 0.3.0 */ p.remove = function (idsOrUrls) { var args = null; if (idsOrUrls && !Array.isArray(idsOrUrls)) { args = [idsOrUrls]; } else if (idsOrUrls) { args = idsOrUrls; } else if (arguments.length > 0) { return; } var itemsWereRemoved = false; // Destroy everything if (!args) { this.close(); for (var n in this._loadItemsById) { this._disposeItem(this._loadItemsById[n]); } this.init(this.preferXHR, this._basePath); // Remove specific items } else { while (args.length) { var item = args.pop(); var r = this.getResult(item); //Remove from the main load Queue for (i = this._loadQueue.length - 1; i >= 0; i--) { loadItem = this._loadQueue[i].getItem(); if (loadItem.id == item || loadItem.src == item) { this._loadQueue.splice(i, 1)[0].cancel(); break; } } //Remove from the backup queue for (i = this._loadQueueBackup.length - 1; i >= 0; i--) { loadItem = this._loadQueueBackup[i].getItem(); if (loadItem.id == item || loadItem.src == item) { this._loadQueueBackup.splice(i, 1)[0].cancel(); break; } } if (r) { this._disposeItem(this.getItem(item)); } else { for (var i = this._currentLoads.length - 1; i >= 0; i--) { var loadItem = this._currentLoads[i].getItem(); if (loadItem.id == item || loadItem.src == item) { this._currentLoads.splice(i, 1)[0].cancel(); itemsWereRemoved = true; break; } } } } // If this was called during a load, try to load the next item. if (itemsWereRemoved) { this._loadNext(); } } }; /** * Stops all open loads, destroys any loaded items, and resets the queue, so all items can * be reloaded again by calling {{#crossLink "AbstractLoader/load"}}{{/crossLink}}. Items are not removed from the * queue. To remove items use the {{#crossLink "LoadQueue/remove"}}{{/crossLink}} or * {{#crossLink "LoadQueue/removeAll"}}{{/crossLink}} method. * @method reset * @since 0.3.0 */ p.reset = function () { this.close(); for (var n in this._loadItemsById) { this._disposeItem(this._loadItemsById[n]); } //Reset the queue to its start state var a = []; for (var i = 0, l = this._loadQueueBackup.length; i < l; i++) { a.push(this._loadQueueBackup[i].getItem()); } this.loadManifest(a, false); }; /** * Register a plugin. Plugins can map to load types (sound, image, etc), or specific extensions (png, mp3, etc). * Currently, only one plugin can exist per type/extension. * * When a plugin is installed, a <code>getPreloadHandlers()</code> method will be called on it. For more information * on this method, check out the {{#crossLink "SamplePlugin/getPreloadHandlers"}}{{/crossLink}} method in the * {{#crossLink "SamplePlugin"}}{{/crossLink}} class. * * Before a file is loaded, a matching plugin has an opportunity to modify the load. If a `callback` is returned * from the {{#crossLink "SamplePlugin/getPreloadHandlers"}}{{/crossLink}} method, it will be invoked first, and its * result may cancel or modify the item. The callback method can also return a `completeHandler` to be fired when * the file is loaded, or a `tag` object, which will manage the actual download. For more information on these * methods, check out the {{#crossLink "SamplePlugin/preloadHandler"}}{{/crossLink}} and {{#crossLink "SamplePlugin/fileLoadHandler"}}{{/crossLink}} * methods on the {{#crossLink "SamplePlugin"}}{{/crossLink}}. * * @method installPlugin * @param {Function} plugin The plugin class to install. */ p.installPlugin = function (plugin) { if (plugin == null) { return; } if (plugin.getPreloadHandlers != null) { this._plugins.push(plugin); var map = plugin.getPreloadHandlers(); map.scope = plugin; if (map.types != null) { for (var i = 0, l = map.types.length; i < l; i++) { this._typeCallbacks[map.types[i]] = map; } } if (map.extensions != null) { for (i = 0, l = map.extensions.length; i < l; i++) { this._extensionCallbacks[map.extensions[i]] = map; } } } }; /** * Set the maximum number of concurrent connections. Note that browsers and servers may have a built-in maximum * number of open connections, so any additional connections may remain in a pending state until the browser * opens the connection. When loading scripts using tags, and when {{#crossLink "LoadQueue/maintainScriptOrder:property"}}{{/crossLink}} * is `true`, only one script is loaded at a time due to browser limitations. * * <h4>Example</h4> * * var queue = new createjs.LoadQueue(); * queue.setMaxConnections(10); // Allow 10 concurrent loads * * @method setMaxConnections * @param {Number} value The number of concurrent loads to allow. By default, only a single connection per LoadQueue * is open at any time. */ p.setMaxConnections = function (value) { this._maxConnections = value; if (!this._paused && this._loadQueue.length > 0) { this._loadNext(); } }; /** * Load a single file. To add multiple files at once, use the {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}} * method. * * Files are always appended to the current queue, so this method can be used multiple times to add files. * To clear the queue first, use the {{#crossLink "AbstractLoader/close"}}{{/crossLink}} method. * @method loadFile * @param {LoadItem|Object|String} file The file object or path to load. A file can be either * <ul> * <li>A {{#crossLink "LoadItem"}}{{/crossLink}} instance</li> * <li>An object containing properties defined by {{#crossLink "LoadItem"}}{{/crossLink}}</li> * <li>OR A string path to a resource. Note that this kind of load item will be converted to a {{#crossLink "LoadItem"}}{{/crossLink}} * in the background.</li> * </ul> * @param {Boolean} [loadNow=true] Kick off an immediate load (true) or wait for a load call (false). The default * value is true. If the queue is paused using {{#crossLink "LoadQueue/setPaused"}}{{/crossLink}}, and the value is * `true`, the queue will resume automatically. * @param {String} [basePath] A base path that will be prepended to each file. The basePath argument overrides the * path specified in the constructor. Note that if you load a manifest using a file of type {{#crossLink "Types/MANIFEST:property"}}{{/crossLink}}, * its files will <strong>NOT</strong> use the basePath parameter. <strong>The basePath parameter is deprecated.</strong> * This parameter will be removed in a future version. Please either use the `basePath` parameter in the LoadQueue * constructor, or a `path` property in a manifest definition. */ p.loadFile = function (file, loadNow, basePath) { if (file == null) { var event = new createjs.ErrorEvent("PRELOAD_NO_FILE"); this._sendError(event); return; } this._addItem(file, null, basePath); if (loadNow !== false) { this.setPaused(false); } else { this.setPaused(true); } }; /** * Load an array of files. To load a single file, use the {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} method. * The files in the manifest are requested in the same order, but may complete in a different order if the max * connections are set above 1 using {{#crossLink "LoadQueue/setMaxConnections"}}{{/crossLink}}. Scripts will load * in the right order as long as {{#crossLink "LoadQueue/maintainScriptOrder"}}{{/crossLink}} is true (which is * default). * * Files are always appended to the current queue, so this method can be used multiple times to add files. * To clear the queue first, use the {{#crossLink "AbstractLoader/close"}}{{/crossLink}} method. * @method loadManifest * @param {Array|String|Object} manifest An list of files to load. The loadManifest call supports four types of * manifests: * <ol> * <li>A string path, which points to a manifest file, which is a JSON file that contains a "manifest" property, * which defines the list of files to load, and can optionally contain a "path" property, which will be * prepended to each file in the list.</li> * <li>An object which defines a "src", which is a JSON or JSONP file. A "callback" can be defined for JSONP * file. The JSON/JSONP file should contain a "manifest" property, which defines the list of files to load, * and can optionally contain a "path" property, which will be prepended to each file in the list.</li> * <li>An object which contains a "manifest" property, which defines the list of files to load, and can * optionally contain a "path" property, which will be prepended to each file in the list.</li> * <li>An Array of files to load.</li> * </ol> * * Each "file" in a manifest can be either: * <ul> * <li>A {{#crossLink "LoadItem"}}{{/crossLink}} instance</li> * <li>An object containing properties defined by {{#crossLink "LoadItem"}}{{/crossLink}}</li> * <li>OR A string path to a resource. Note that this kind of load item will be converted to a {{#crossLink "LoadItem"}}{{/crossLink}} * in the background.</li> * </ul> * * @param {Boolean} [loadNow=true] Kick off an immediate load (true) or wait for a load call (false). The default * value is true. If the queue is paused using {{#crossLink "LoadQueue/setPaused"}}{{/crossLink}} and this value is * `true`, the queue will resume automatically. * @param {String} [basePath] A base path that will be prepended to each file. The basePath argument overrides the * path specified in the constructor. Note that if you load a manifest using a file of type {{#crossLink "LoadQueue/MANIFEST:property"}}{{/crossLink}}, * its files will <strong>NOT</strong> use the basePath parameter. <strong>The basePath parameter is deprecated.</strong> * This parameter will be removed in a future version. Please either use the `basePath` parameter in the LoadQueue * constructor, or a `path` property in a manifest definition. */ p.loadManifest = function (manifest, loadNow, basePath) { var fileList = null; var path = null; // Array-based list of items if (Array.isArray(manifest)) { if (manifest.length == 0) { var event = new createjs.ErrorEvent("PRELOAD_MANIFEST_EMPTY"); this._sendError(event); return; } fileList = manifest; // String-based. Only file manifests can be specified this way. Any other types will cause an error when loaded. } else if (typeof(manifest) === "string") { fileList = [ { src: manifest, type: s.MANIFEST } ]; } else if (typeof(manifest) == "object") { // An object that defines a manifest path if (manifest.src !== undefined) { if (manifest.type == null) { manifest.type = s.MANIFEST; } else if (manifest.type != s.MANIFEST) { var event = new createjs.ErrorEvent("PRELOAD_MANIFEST_TYPE"); this._sendError(event); } fileList = [manifest]; // An object that defines a manifest } else if (manifest.manifest !== undefined) { fileList = manifest.manifest; path = manifest.path; } // Unsupported. This will throw an error. } else { var event = new createjs.ErrorEvent("PRELOAD_MANIFEST_NULL"); this._sendError(event); return; } for (var i = 0, l = fileList.length; i < l; i++) { this._addItem(fileList[i], path, basePath); } if (loadNow !== false) { this.setPaused(false); } else { this.setPaused(true); } }; /** * Start a LoadQueue that was created, but not automatically started. * @method load */ p.load = function () { this.setPaused(false); }; /** * Look up a {{#crossLink "LoadItem"}}{{/crossLink}} using either the "id" or "src" that was specified when loading it. Note that if no "id" was * supplied with the load item, the ID will be the "src", including a `path` property defined by a manifest. The * `basePath` will not be part of the ID. * @method getItem * @param {String} value The <code>id</code> or <code>src</code> of the load item. * @return {Object} The load item that was initially requested using {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} * or {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}}. This object is also returned via the {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}} * event as the `item` parameter. */ p.getItem = function (value) { return this._loadItemsById[value] || this._loadItemsBySrc[value]; }; /** * Look up a loaded result using either the "id" or "src" that was specified when loading it. Note that if no "id" * was supplied with the load item, the ID will be the "src", including a `path` property defined by a manifest. The * `basePath` will not be part of the ID. * @method getResult * @param {String} value The <code>id</code> or <code>src</code> of the load item. * @param {Boolean} [rawResult=false] Return a raw result instead of a formatted result. This applies to content * loaded via XHR such as scripts, XML, CSS, and Images. If there is no raw result, the formatted result will be * returned instead. * @return {Object} A result object containing the content that was loaded, such as: * <ul> * <li>An image tag (<image />) for images</li> * <li>A script tag for JavaScript (<script />). Note that scripts are automatically added to the HTML * DOM.</li> * <li>A style tag for CSS (<style /> or <link >)</li> * <li>Raw text for TEXT</li> * <li>A formatted JavaScript object defined by JSON</li> * <li>An XML document</li> * <li>A binary arraybuffer loaded by XHR</li> * <li>An audio tag (<audio >) for HTML audio. Note that it is recommended to use SoundJS APIs to play * loaded audio. Specifically, audio loaded by Flash and WebAudio will return a loader object using this method * which can not be used to play audio back.</li> * </ul> * This object is also returned via the {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}} event as the 'item` * parameter. Note that if a raw result is requested, but not found, the result will be returned instead. */ p.getResult = function (value, rawResult) { var item = this._loadItemsById[value] || this._loadItemsBySrc[value]; if (item == null) { return null; } var id = item.id; if (rawResult && this._loadedRawResults[id]) { return this._loadedRawResults[id]; } return this._loadedResults[id]; }; /** * Generate an list of items loaded by this queue. * @method getItems * @param {Boolean} loaded Determines if only items that have been loaded should be returned. If false, in-progress * and failed load items will also be included. * @returns {Array} A list of objects that have been loaded. Each item includes the {{#crossLink "LoadItem"}}{{/crossLink}}, * result, and rawResult. * @since 0.6.0 */ p.getItems = function (loaded) { var arr = []; for (var n in this._loadItemsById) { var item = this._loadItemsById[n]; var result = this.getResult(n); if (loaded === true && result == null) { continue; } arr.push({ item: item, result: result, rawResult: this.getResult(n, true) }); } return arr; }; /** * Pause or resume the current load. Active loads will not be cancelled, but the next items in the queue will not * be processed when active loads complete. LoadQueues are not paused by default. * * Note that if new items are added to the queue using {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} or * {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}}, a paused queue will be resumed, unless the `loadNow` * argument is `false`. * @method setPaused * @param {Boolean} value Whether the queue should be paused or not. */ p.setPaused = function (value) { this._paused = value; if (!this._paused) { this._loadNext(); } }; /** * Close the active queue. Closing a queue completely empties the queue, and prevents any remaining items from * starting to download. Note that currently any active loads will remain open, and events may be processed. * * To stop and restart a queue, use the {{#crossLink "LoadQueue/setPaused"}}{{/crossLink}} method instead. * @method close */ p.close = function () { while (this._currentLoads.length) { this._currentLoads.pop().cancel(); } this._scriptOrder.length = 0; this._loadedScripts.length = 0; this.loadStartWasDispatched = false; this._itemCount = 0; this._lastProgress = NaN; }; // protected methods /** * Add an item to the queue. Items are formatted into a usable object containing all the properties necessary to * load the content. The load queue is populated with the loader instance that handles preloading, and not the load * item that was passed in by the user. To look up the load item by id or src, use the {{#crossLink "LoadQueue.getItem"}}{{/crossLink}} * method. * @method _addItem * @param {String|Object} value The item to add to the queue. * @param {String} [path] An optional path prepended to the `src`. The path will only be prepended if the src is * relative, and does not start with a protocol such as `http://`, or a path like `../`. If the LoadQueue was * provided a {{#crossLink "_basePath"}}{{/crossLink}}, then it will optionally be prepended after. * @param {String} [basePath] <strong>Deprecated</strong>An optional basePath passed into a {{#crossLink "LoadQueue/loadManifest"}}{{/crossLink}} * or {{#crossLink "LoadQueue/loadFile"}}{{/crossLink}} call. This parameter will be removed in a future tagged * version. * @private */ p._addItem = function (value, path, basePath) { var item = this._createLoadItem(value, path, basePath); // basePath and manifest path are added to the src. if (item == null) { return; } // Sometimes plugins or types should be skipped. var loader = this._createLoader(item); if (loader != null) { if ("plugins" in loader) { loader.plugins = this._plugins; } item._loader = loader; this._loadQueue.push(loader); this._loadQueueBackup.push(loader); this._numItems++; this._updateProgress(); // Only worry about script order when using XHR to load scripts. Tags are only loading one at a time. if ((this.maintainScriptOrder && item.type == createjs.Types.JAVASCRIPT //&& loader instanceof createjs.XHRLoader //NOTE: Have to track all JS files this way ) || item.maintainOrder === true) { this._scriptOrder.push(item); this._loadedScripts.push(null); } } }; /** * Create a refined {{#crossLink "LoadItem"}}{{/crossLink}}, which contains all the required properties. The type of * item is determined by browser support, requirements based on the file type, and developer settings. For example, * XHR is only used for file types that support it in new browsers. * * Before the item is returned, any plugins registered to handle the type or extension will be fired, which may * alter the load item. * @method _createLoadItem * @param {String | Object | HTMLAudioElement | HTMLImageElement} value The item that needs to be preloaded. * @param {String} [path] A path to prepend to the item's source. Sources beginning with http:// or similar will * not receive a path. Since PreloadJS 0.4.1, the src will be modified to include the `path` and {{#crossLink "LoadQueue/_basePath:property"}}{{/crossLink}} * when it is added. * @param {String} [basePath] <strong>Deprectated</strong> A base path to prepend to the items source in addition to * the path argument. * @return {Object} The loader instance that will be used. * @private */ p._createLoadItem = function (value, path, basePath) { var item = createjs.LoadItem.create(value); if (item == null) { return null; } var bp = ""; // Store the generated basePath var useBasePath = basePath || this._basePath; if (item.src instanceof Object) { if (!item.type) { return null; } // the the src is an object, type is required to pass off to plugin if (path) { bp = path; var pathMatch = createjs.URLUtils.parseURI(path); // Also append basePath if (useBasePath != null && !pathMatch.absolute && !pathMatch.relative) { bp = useBasePath + bp; } } else if (useBasePath != null) { bp = useBasePath; } } else { // Determine Extension, etc. var match = createjs.URLUtils.parseURI(item.src); if (match.extension) { item.ext = match.extension; } if (item.type == null) { item.type = createjs.RequestUtils.getTypeByExtension(item.ext); } // Inject path & basePath var autoId = item.src; if (!match.absolute && !match.relative) { if (path) { bp = path; var pathMatch = createjs.URLUtils.parseURI(path); autoId = path + autoId; // Also append basePath if (useBasePath != null && !pathMatch.absolute && !pathMatch.relative) { bp = useBasePath + bp; } } else if (useBasePath != null) { bp = useBasePath; } } item.src = bp + item.src; } item.path = bp; // If there's no id, set one now. if (item.id === undefined || item.id === null || item.id === "") { item.id = autoId; } // Give plugins a chance to modify the loadItem: var customHandler = this._typeCallbacks[item.type] || this._extensionCallbacks[item.ext]; if (customHandler) { // Plugins are now passed both the full source, as well as a combined path+basePath (appropriately) var result = customHandler.callback.call(customHandler.scope, item, this); // The plugin will handle the load, or has canceled it. Ignore it. if (result === false) { return null; // Load as normal: } else if (result === true) { // Do Nothing // Result is a loader class: } else if (result != null) { item._loader = result; } // Update the extension in case the type changed: match = createjs.URLUtils.parseURI(item.src); if (match.extension != null) { item.ext = match.extension; } } // Store the item for lookup. This also helps clean-up later. this._loadItemsById[item.id] = item; this._loadItemsBySrc[item.src] = item; if (item.crossOrigin == null) { item.crossOrigin = this._crossOrigin; } return item; }; /** * Create a loader for a load item. * @method _createLoader * @param {Object} item A formatted load item that can be used to generate a loader. * @return {AbstractLoader} A loader that can be used to load content. * @private */ p._createLoader = function (item) { if (item._loader != null) { // A plugin already specified a loader return item._loader; } // Initially, try and use the provided/supported XHR mode: var preferXHR = this.preferXHR; for (var i = 0; i < this._availableLoaders.length; i++) { var loader = this._availableLoaders[i]; if (loader && loader.canLoadItem(item)) { return new loader(item, preferXHR); } } // TODO: Log error (requires createjs.log) return null; }; /** * Load the next item in the queue. If the queue is empty (all items have been loaded), then the complete event * is processed. The queue will "fill up" any empty slots, up to the max connection specified using * {{#crossLink "LoadQueue.setMaxConnections"}}{{/crossLink}} method. The only exception is scripts that are loaded * using tags, which have to be loaded one at a time to maintain load order. * @method _loadNext * @private */ p._loadNext = function () { if (this._paused) { return; } // Only dispatch loadstart event when the first file is loaded. if (!this._loadStartWasDispatched) { this._sendLoadStart(); this._loadStartWasDispatched = true; } // The queue has completed. if (this._numItems == this._numItemsLoaded) { this.loaded = true; this._sendComplete(); // Load the next queue, if it has been defined. if (this.next && this.next.load) { this.next.load(); } } else { this.loaded = false; } // Must iterate forwards to load in the right order. for (var i = 0; i < this._loadQueue.length; i++) { if (this._currentLoads.length >= this._maxConnections) { break; } var loader = this._loadQueue[i]; // Determine if we should be only loading one tag-script at a time: // Note: maintainOrder items don't do anything here because we can hold onto their loaded value if (!this._canStartLoad(loader)) { continue; } this._loadQueue.splice(i, 1); i--; this._loadItem(loader); } }; /** * Begin loading an item. Event listeners are not added to the loaders until the load starts. * @method _loadItem * @param {AbstractLoader} loader The loader instance to start. Currently, this will be an XHRLoader or TagLoader. * @private */ p._loadItem = function (loader) { loader.on("fileload", this._handleFileLoad, this); loader.on("progress", this._handleProgress, this); loader.on("complete", this._handleFileComplete, this); loader.on("error", this._handleError, this); loader.on("fileerror", this._handleFileError, this); this._currentLoads.push(loader); this._sendFileStart(loader.getItem()); loader.load(); }; /** * The callback that is fired when a loader loads a file. This enables loaders like {{#crossLink "ManifestLoader"}}{{/crossLink}} * to maintain internal queues, but for this queue to dispatch the {{#crossLink "fileload:event"}}{{/crossLink}} * events. * @param {Event} event The {{#crossLink "AbstractLoader/fileload:event"}}{{/crossLink}} event from the loader. * @private * @since 0.6.0 */ p._handleFileLoad = function (event) { event.target = null; this.dispatchEvent(event); }; /** * The callback that is fired when a loader encounters an error from an internal file load operation. This enables * loaders like M * @param event * @private */ p._handleFileError = function (event) { var newEvent = new createjs.ErrorEvent("FILE_LOAD_ERROR", null, event.item); this._sendError(newEvent); }; /** * The callback that is fired when a loader encounters an error. The queue will continue loading unless {{#crossLink "LoadQueue/stopOnError:property"}}{{/crossLink}} * is set to `true`. * @method _handleError * @param {ErrorEvent} event The error event, containing relevant error information. * @private */ p._handleError = function (event) { var loader = event.target; this._numItemsLoaded++; this._finishOrderedItem(loader, true); this._updateProgress(); var newEvent = new createjs.ErrorEvent("FILE_LOAD_ERROR", null, loader.getItem()); // TODO: Propagate actual error message. this._sendError(newEvent); if (!this.stopOnError) { this._removeLoadItem(loader); this._cleanLoadItem(loader); this._loadNext(); } else { this.setPaused(true); } }; /** * An item has finished loading. We can assume that it is totally loaded, has been parsed for immediate use, and * is available as the "result" property on the load item. The raw text result for a parsed item (such as JSON, XML, * CSS, JavaScript, etc) is available as the "rawResult" property, and can also be looked up using {{#crossLink "LoadQueue/getResult"}}{{/crossLink}}. * @method _handleFileComplete * @param {Event} event The event object from the loader. * @private */ p._handleFileComplete = function (event) { var loader = event.target; var item = loader.getItem(); var result = loader.getResult(); this._loadedResults[item.id] = result; var rawResult = loader.getResult(true); if (rawResult != null && rawResult !== result) { this._loadedRawResults[item.id] = rawResult; } this._saveLoadedItems(loader); // Remove the load item this._removeLoadItem(loader); if (!this._finishOrderedItem(loader)) { // The item was NOT managed, so process it now this._processFinishedLoad(item, loader); } // Clean up the load item this._cleanLoadItem(loader); }; /** * Some loaders might load additional content, other than the item they were passed (such as {{#crossLink "ManifestLoader"}}{{/crossLink}}). * Any items exposed by the loader using {{#crossLink "AbstractLoader/getLoadItems"}}{{/crossLink}} are added to the * LoadQueue's look-ups, including {{#crossLink "getItem"}}{{/crossLink}} and {{#crossLink "getResult"}}{{/crossLink}} * methods. * @method _saveLoadedItems * @param {AbstractLoader} loader * @protected * @since 0.6.0 */ p._saveLoadedItems = function (loader) { // TODO: Not sure how to handle this. Would be nice to expose the items. // Loaders may load sub-items. This adds them to this queue var list = loader.getLoadedItems(); if (list === null) { return; } for (var i = 0; i < list.length; i++) { var item = list[i].item; // Store item lookups this._loadItemsBySrc[item.src] = item; this._loadItemsById[item.id] = item; // Store loaded content this._loadedResults[item.id] = list[i].result; this._loadedRawResults[item.id] = list[i].rawResult; } }; /** * Flag an item as finished. If the item's order is being managed, then ensure that it is allowed to finish, and if * so, trigger prior items to trigger as well. * @method _finishOrderedItem * @param {AbstractLoader} loader * @param {Boolean} loadFailed * @return {Boolean} If the item's order is being managed. This allows the caller to take an alternate * behaviour if it is. * @private */ p._finishOrderedItem = function (loader, loadFailed) { var item = loader.getItem(); if ((this.maintainScriptOrder && item.type == createjs.Types.JAVASCRIPT) || item.maintainOrder) { //TODO: Evaluate removal of the _currentlyLoadingScript if (loader instanceof createjs.JavaScriptLoader) { this._currentlyLoadingScript = false; } var index = createjs.indexOf(this._scriptOrder, item); if (index == -1) { return false; } // This loader no longer exists this._loadedScripts[index] = (loadFailed === true) ? true : item; this._checkScriptLoadOrder(); return true; } return false; }; /** * Ensure the scripts load and dispatch in the correct order. When using XHR, scripts are stored in an array in the * order they were added, but with a "null" value. When they are completed, the value is set to the load item, * and then when they are processed and dispatched, the value is set to `true`. This method simply * iterates the array, and ensures that any loaded items that are not preceded by a `null` value are * dispatched. * @method _checkScriptLoadOrder * @private */ p._checkScriptLoadOrder = function () { var l = this._loadedScripts.length; for (var i = 0; i < l; i++) { var item = this._loadedScripts[i]; if (item === null) { break; } // This is still loading. Do not process further. if (item === true) { continue; } // This has completed, and been processed. Move on. var loadItem = this._loadedResults[item.id]; if (item.type == createjs.Types.JAVASCRIPT) { // Append script tags to the head automatically. createjs.DomUtils.appendToHead(loadItem); } var loader = item._loader; this._processFinishedLoad(item, loader); this._loadedScripts[i] = true; } }; /** * A file has completed loading, and the LoadQueue can move on. This triggers the complete event, and kick-starts * the next item. * @method _processFinishedLoad * @param {LoadItem|Object} item * @param {AbstractLoader} loader * @protected */ p._processFinishedLoad = function (item, loader) { this._numItemsLoaded++; // Since LoadQueue needs maintain order, we can't append scripts in the loader. // So we do it here instead. Or in _checkScriptLoadOrder(); if (!this.maintainScriptOrder && item.type == createjs.Types.JAVASCRIPT) { var tag = loader.getTag(); createjs.DomUtils.appendToHead(tag); } this._updateProgress(); this._sendFileComplete(item, loader); this._loadNext(); }; /** * Ensure items with `maintainOrder=true` that are before the specified item have loaded. This only applies to * JavaScript items that are being loaded with a TagLoader, since they have to be loaded and completed <strong>before</strong> * the script can even be started, since it exist in the DOM while loading. * @method _canStartLoad * @param {AbstractLoader} loader The loader for the item * @return {Boolean} Whether the item can start a load or not. * @private */ p._canStartLoad = function (loader) { if (!this.maintainScriptOrder || loader.preferXHR) { return true; } var item = loader.getItem(); if (item.type != createjs.Types.JAVASCRIPT) { return true; } if (this._currentlyLoadingScript) { return false; } var index = this._scriptOrder.indexOf(item); var i = 0; while (i < index) { var checkItem = this._loadedScripts[i]; if (checkItem == null) { return false; } i++; } this._currentlyLoadingScript = true; return true; }; /** * A load item is completed or was canceled, and needs to be removed from the LoadQueue. * @method _removeLoadItem * @param {AbstractLoader} loader A loader instance to remove. * @private */ p._removeLoadItem = function (loader) { var l = this._currentLoads.length; for (var i = 0; i < l; i++) { if (this._currentLoads[i] == loader) { this._currentLoads.splice(i, 1); break; } } }; /** * Remove unneeded references from a loader. * * @param loader * @private */ p._cleanLoadItem = function(loader) { var item = loader.getItem(); loader.removeAllEventListeners(); if (item) { delete item._loader; } } /** * An item has dispatched progress. Propagate that progress, and update the LoadQueue's overall progress. * @method _handleProgress * @param {ProgressEvent} event The progress event from the item. * @private */ p._handleProgress = function (event) { var loader = event.target; this._sendFileProgress(loader.getItem(), loader.progress); this._updateProgress(); }; /** * Overall progress has changed, so determine the new progress amount and dispatch it. This changes any time an * item dispatches progress or completes. Note that since we don't always know the actual filesize of items before * they are loaded. In this case, we define a "slot" for each item (1 item in 10 would get 10%), and then append * loaded progress on top of the already-loaded items. * * For example, if 5/10 items have loaded, and item 6 is 20% loaded, the total progress would be: * <ul> * <li>5/10 of the items in the queue (50%)</li> * <li>plus 20% of item 6's slot (2%)</li> * <li>equals 52%</li> * </ul> * @method _updateProgress * @private */ p._updateProgress = function () { var loaded = this._numItemsLoaded / this._numItems; // Fully Loaded Progress var remaining = this._numItems - this._numItemsLoaded; if (remaining > 0) { var chunk = 0; for (var i = 0, l = this._currentLoads.length; i < l; i++) { chunk += this._currentLoads[i].progress; } loaded += (chunk / remaining) * (remaining / this._numItems); } if (this._lastProgress != loaded) { this._sendProgress(loaded); this._lastProgress = loaded; } }; /** * Clean out item results, to free them from memory. Mainly, the loaded item and results are cleared from internal * hashes. * @method _disposeItem * @param {LoadItem|Object} item The item that was passed in for preloading. * @private */ p._disposeItem = function (item) { delete this._loadedResults[item.id]; delete this._loadedRawResults[item.id]; delete this._loadItemsById[item.id]; delete this._loadItemsBySrc[item.src]; }; /** * Dispatch a "fileprogress" {{#crossLink "Event"}}{{/crossLink}}. Please see the LoadQueue {{#crossLink "LoadQueue/fileprogress:event"}}{{/crossLink}} * event for details on the event payload. * @method _sendFileProgress * @param {LoadItem|Object} item The item that is being loaded. * @param {Number} progress The amount the item has been loaded (between 0 and 1). * @protected */ p._sendFileProgress = function (item, progress) { if (this._isCanceled() || this._paused) { return; } if (!this.hasEventListener("fileprogress")) { return; } //LM: Rework ProgressEvent to support this? var event = new createjs.Event("fileprogress"); event.progress = progress; event.loaded = progress; event.total = 1; event.item = item; this.dispatchEvent(event); }; /** * Dispatch a fileload {{#crossLink "Event"}}{{/crossLink}}. Please see the {{#crossLink "LoadQueue/fileload:event"}}{{/crossLink}} event for * details on the event payload. * @method _sendFileComplete * @param {LoadItemObject} item The item that is being loaded. * @param {AbstractLoader} loader * @protected */ p._sendFileComplete = function (item, loader) { if (this._isCanceled() || this._paused) { return; } var event = new createjs.Event("fileload"); event.loader = loader; event.item = item; event.result = this._loadedResults[item.id]; event.rawResult = this._loadedRawResults[item.id]; // This calls a handler specified on the actual load item. Currently, the SoundJS plugin uses this. if (item.completeHandler) { item.completeHandler(event); } this.hasEventListener("fileload") && this.dispatchEvent(event); }; /** * Dispatch a filestart {{#crossLink "Event"}}{{/crossLink}} immediately before a file starts to load. Please see * the {{#crossLink "LoadQueue/filestart:event"}}{{/crossLink}} event for details on the event payload. * @method _sendFileStart * @param {LoadItem|Object} item The item that is being loaded. * @protected */ p._sendFileStart = function (item) { var event = new createjs.Event("filestart"); event.item = item; this.hasEventListener("filestart") && this.dispatchEvent(event); }; p.toString = function () { return "[PreloadJS LoadQueue]"; }; createjs.LoadQueue = createjs.promote(LoadQueue, "AbstractLoader"); }());