Files
live2d/live2d/node_modules/@pixi/loaders/dist/browser/loaders.js

2009 lines
83 KiB
JavaScript

/*!
* @pixi/loaders - v6.5.10
* Compiled Thu, 06 Jul 2023 15:25:11 UTC
*
* @pixi/loaders is licensed under the MIT License.
* http://www.opensource.org/licenses/mit-license
*/
this.PIXI = this.PIXI || {};
var _pixi_loaders = (function (exports, core, utils) {
'use strict';
/* jshint -W097 */
/**
* @memberof PIXI
*/
var SignalBinding = /** @class */ (function () {
/**
* SignalBinding constructor.
* @constructs SignalBinding
* @param {Function} fn - Event handler to be called.
* @param {boolean} [once=false] - Should this listener be removed after dispatch
* @param {object} [thisArg] - The context of the callback function.
* @api private
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
function SignalBinding(fn, once, thisArg) {
if (once === void 0) { once = false; }
this._fn = fn;
this._once = once;
this._thisArg = thisArg;
this._next = this._prev = this._owner = null;
}
SignalBinding.prototype.detach = function () {
if (this._owner === null)
{ return false; }
this._owner.detach(this);
return true;
};
return SignalBinding;
}());
/**
* @param self
* @param node
* @private
*/
function _addSignalBinding(self, node) {
if (!self._head) {
self._head = node;
self._tail = node;
}
else {
self._tail._next = node;
node._prev = self._tail;
self._tail = node;
}
node._owner = self;
return node;
}
/**
* @memberof PIXI
*/
var Signal = /** @class */ (function () {
/**
* MiniSignal constructor.
* @example
* let mySignal = new Signal();
* let binding = mySignal.add(onSignal);
* mySignal.dispatch('foo', 'bar');
* mySignal.detach(binding);
*/
function Signal() {
this._head = this._tail = undefined;
}
/**
* Return an array of attached SignalBinding.
* @param {boolean} [exists=false] - We only need to know if there are handlers.
* @returns {PIXI.SignalBinding[] | boolean} Array of attached SignalBinding or Boolean if called with exists = true
* @api public
*/
Signal.prototype.handlers = function (exists) {
if (exists === void 0) { exists = false; }
var node = this._head;
if (exists)
{ return !!node; }
var ee = [];
while (node) {
ee.push(node);
node = node._next;
}
return ee;
};
/**
* Return true if node is a SignalBinding attached to this MiniSignal
* @param {PIXI.SignalBinding} node - Node to check.
* @returns {boolean} True if node is attache to mini-signal
*/
Signal.prototype.has = function (node) {
if (!(node instanceof SignalBinding)) {
throw new Error('MiniSignal#has(): First arg must be a SignalBinding object.');
}
return node._owner === this;
};
/**
* Dispaches a signal to all registered listeners.
* @param {...any} args
* @returns {boolean} Indication if we've emitted an event.
*/
Signal.prototype.dispatch = function () {
var arguments$1 = arguments;
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments$1[_i];
}
var node = this._head;
if (!node)
{ return false; }
while (node) {
if (node._once)
{ this.detach(node); }
node._fn.apply(node._thisArg, args);
node = node._next;
}
return true;
};
/**
* Register a new listener.
* @param {Function} fn - Callback function.
* @param {object} [thisArg] - The context of the callback function.
* @returns {PIXI.SignalBinding} The SignalBinding node that was added.
*/
Signal.prototype.add = function (fn, thisArg) {
if (thisArg === void 0) { thisArg = null; }
if (typeof fn !== 'function') {
throw new Error('MiniSignal#add(): First arg must be a Function.');
}
return _addSignalBinding(this, new SignalBinding(fn, false, thisArg));
};
/**
* Register a new listener that will be executed only once.
* @param {Function} fn - Callback function.
* @param {object} [thisArg] - The context of the callback function.
* @returns {PIXI.SignalBinding} The SignalBinding node that was added.
*/
Signal.prototype.once = function (fn, thisArg) {
if (thisArg === void 0) { thisArg = null; }
if (typeof fn !== 'function') {
throw new Error('MiniSignal#once(): First arg must be a Function.');
}
return _addSignalBinding(this, new SignalBinding(fn, true, thisArg));
};
/**
* Remove binding object.
* @param {PIXI.SignalBinding} node - The binding node that will be removed.
* @returns {Signal} The instance on which this method was called.
@api public */
Signal.prototype.detach = function (node) {
if (!(node instanceof SignalBinding)) {
throw new Error('MiniSignal#detach(): First arg must be a SignalBinding object.');
}
if (node._owner !== this)
{ return this; } // todo: or error?
if (node._prev)
{ node._prev._next = node._next; }
if (node._next)
{ node._next._prev = node._prev; }
if (node === this._head) { // first node
this._head = node._next;
if (node._next === null) {
this._tail = null;
}
}
else if (node === this._tail) { // last node
this._tail = node._prev;
this._tail._next = null;
}
node._owner = null;
return this;
};
/**
* Detach all listeners.
* @returns {Signal} The instance on which this method was called.
*/
Signal.prototype.detachAll = function () {
var node = this._head;
if (!node)
{ return this; }
this._head = this._tail = null;
while (node) {
node._owner = null;
node = node._next;
}
return this;
};
return Signal;
}());
/**
* function from npm package `parseUri`, converted to TS to avoid leftpad incident
* @param {string} str
* @param [opts] - options
* @param {boolean} [opts.strictMode] - type of parser
*/
function parseUri(str, opts) {
opts = opts || {};
var o = {
// eslint-disable-next-line max-len
key: ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor'],
q: {
name: 'queryKey',
parser: /(?:^|&)([^&=]*)=?([^&]*)/g
},
parser: {
// eslint-disable-next-line max-len
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
// eslint-disable-next-line max-len
loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
}
};
var m = o.parser[opts.strictMode ? 'strict' : 'loose'].exec(str);
var uri = {};
var i = 14;
while (i--)
{ uri[o.key[i]] = m[i] || ''; }
uri[o.q.name] = {};
uri[o.key[12]].replace(o.q.parser, function (_t0, t1, t2) {
if (t1)
{ uri[o.q.name][t1] = t2; }
});
return uri;
}
// tests if CORS is supported in XHR, if not we need to use XDR
var useXdr;
var tempAnchor = null;
// some status constants
var STATUS_NONE = 0;
var STATUS_OK = 200;
var STATUS_EMPTY = 204;
var STATUS_IE_BUG_EMPTY = 1223;
var STATUS_TYPE_OK = 2;
// noop
function _noop$1() { }
/**
* Quick helper to set a value on one of the extension maps. Ensures there is no
* dot at the start of the extension.
* @ignore
* @param map - The map to set on.
* @param extname - The extension (or key) to set.
* @param val - The value to set.
*/
function setExtMap(map, extname, val) {
if (extname && extname.indexOf('.') === 0) {
extname = extname.substring(1);
}
if (!extname) {
return;
}
map[extname] = val;
}
/**
* Quick helper to get string xhr type.
* @ignore
* @param xhr - The request to check.
* @returns The type.
*/
function reqType(xhr) {
return xhr.toString().replace('object ', '');
}
/**
* Manages the state and loading of a resource and all child resources.
*
* Can be extended in `GlobalMixins.LoaderResource`.
* @memberof PIXI
*/
exports.LoaderResource = /** @class */ (function () {
/**
* @param {string} name - The name of the resource to load.
* @param {string|string[]} url - The url for this resource, for audio/video loads you can pass
* an array of sources.
* @param {object} [options] - The options for the load.
* @param {string|boolean} [options.crossOrigin] - Is this request cross-origin? Default is to
* determine automatically.
* @param {number} [options.timeout=0] - A timeout in milliseconds for the load. If the load takes
* longer than this time it is cancelled and the load is considered a failure. If this value is
* set to `0` then there is no explicit timeout.
* @param {PIXI.LoaderResource.LOAD_TYPE} [options.loadType=LOAD_TYPE.XHR] - How should this resource
* be loaded?
* @param {PIXI.LoaderResource.XHR_RESPONSE_TYPE} [options.xhrType=XHR_RESPONSE_TYPE.DEFAULT] - How
* should the data being loaded be interpreted when using XHR?
* @param {PIXI.LoaderResource.IMetadata} [options.metadata] - Extra configuration for middleware
* and the Resource object.
*/
function LoaderResource(name, url, options) {
/**
* The `dequeue` method that will be used a storage place for the async queue dequeue method
* used privately by the loader.
* @private
* @member {Function}
*/
this._dequeue = _noop$1;
/**
* Used a storage place for the on load binding used privately by the loader.
* @private
* @member {Function}
*/
this._onLoadBinding = null;
/**
* The timer for element loads to check if they timeout.
* @private
*/
this._elementTimer = 0;
/**
* The `complete` function bound to this resource's context.
* @private
* @type {Function}
*/
this._boundComplete = null;
/**
* The `_onError` function bound to this resource's context.
* @private
* @type {Function}
*/
this._boundOnError = null;
/**
* The `_onProgress` function bound to this resource's context.
* @private
* @type {Function}
*/
this._boundOnProgress = null;
/**
* The `_onTimeout` function bound to this resource's context.
* @private
* @type {Function}
*/
this._boundOnTimeout = null;
this._boundXhrOnError = null;
this._boundXhrOnTimeout = null;
this._boundXhrOnAbort = null;
this._boundXhrOnLoad = null;
if (typeof name !== 'string' || typeof url !== 'string') {
throw new Error('Both name and url are required for constructing a resource.');
}
options = options || {};
this._flags = 0;
// set data url flag, needs to be set early for some _determineX checks to work.
this._setFlag(LoaderResource.STATUS_FLAGS.DATA_URL, url.indexOf('data:') === 0);
this.name = name;
this.url = url;
this.extension = this._getExtension();
this.data = null;
this.crossOrigin = options.crossOrigin === true ? 'anonymous' : options.crossOrigin;
this.timeout = options.timeout || 0;
this.loadType = options.loadType || this._determineLoadType();
// The type used to load the resource via XHR. If unset, determined automatically.
this.xhrType = options.xhrType;
// Extra info for middleware, and controlling specifics about how the resource loads.
// Note that if you pass in a `loadElement`, the Resource class takes ownership of it.
// Meaning it will modify it as it sees fit.
this.metadata = options.metadata || {};
// The error that occurred while loading (if any).
this.error = null;
// The XHR object that was used to load this resource. This is only set
// when `loadType` is `LoaderResource.LOAD_TYPE.XHR`.
this.xhr = null;
// The child resources this resource owns.
this.children = [];
// The resource type.
this.type = LoaderResource.TYPE.UNKNOWN;
// The progress chunk owned by this resource.
this.progressChunk = 0;
// The `dequeue` method that will be used a storage place for the async queue dequeue method
// used privately by the loader.
this._dequeue = _noop$1;
// Used a storage place for the on load binding used privately by the loader.
this._onLoadBinding = null;
// The timer for element loads to check if they timeout.
this._elementTimer = 0;
this._boundComplete = this.complete.bind(this);
this._boundOnError = this._onError.bind(this);
this._boundOnProgress = this._onProgress.bind(this);
this._boundOnTimeout = this._onTimeout.bind(this);
// xhr callbacks
this._boundXhrOnError = this._xhrOnError.bind(this);
this._boundXhrOnTimeout = this._xhrOnTimeout.bind(this);
this._boundXhrOnAbort = this._xhrOnAbort.bind(this);
this._boundXhrOnLoad = this._xhrOnLoad.bind(this);
// Dispatched when the resource beings to load.
this.onStart = new Signal();
// Dispatched each time progress of this resource load updates.
// Not all resources types and loader systems can support this event
// so sometimes it may not be available. If the resource
// is being loaded on a modern browser, using XHR, and the remote server
// properly sets Content-Length headers, then this will be available.
this.onProgress = new Signal();
// Dispatched once this resource has loaded, if there was an error it will
// be in the `error` property.
this.onComplete = new Signal();
// Dispatched after this resource has had all the *after* middleware run on it.
this.onAfterMiddleware = new Signal();
}
/**
* Sets the load type to be used for a specific extension.
* @static
* @param {string} extname - The extension to set the type for, e.g. "png" or "fnt"
* @param {PIXI.LoaderResource.LOAD_TYPE} loadType - The load type to set it to.
*/
LoaderResource.setExtensionLoadType = function (extname, loadType) {
setExtMap(LoaderResource._loadTypeMap, extname, loadType);
};
/**
* Sets the load type to be used for a specific extension.
* @static
* @param {string} extname - The extension to set the type for, e.g. "png" or "fnt"
* @param {PIXI.LoaderResource.XHR_RESPONSE_TYPE} xhrType - The xhr type to set it to.
*/
LoaderResource.setExtensionXhrType = function (extname, xhrType) {
setExtMap(LoaderResource._xhrTypeMap, extname, xhrType);
};
Object.defineProperty(LoaderResource.prototype, "isDataUrl", {
/**
* When the resource starts to load.
* @memberof PIXI.LoaderResource
* @callback OnStartSignal
* @param {PIXI.Resource} resource - The resource that the event happened on.
*/
/**
* When the resource reports loading progress.
* @memberof PIXI.LoaderResource
* @callback OnProgressSignal
* @param {PIXI.Resource} resource - The resource that the event happened on.
* @param {number} percentage - The progress of the load in the range [0, 1].
*/
/**
* When the resource finishes loading.
* @memberof PIXI.LoaderResource
* @callback OnCompleteSignal
* @param {PIXI.Resource} resource - The resource that the event happened on.
*/
/**
* @memberof PIXI.LoaderResource
* @typedef {object} IMetadata
* @property {HTMLImageElement|HTMLAudioElement|HTMLVideoElement} [loadElement=null] - The
* element to use for loading, instead of creating one.
* @property {boolean} [skipSource=false] - Skips adding source(s) to the load element. This
* is useful if you want to pass in a `loadElement` that you already added load sources to.
* @property {string|string[]} [mimeType] - The mime type to use for the source element
* of a video/audio elment. If the urls are an array, you can pass this as an array as well
* where each index is the mime type to use for the corresponding url index.
*/
/**
* Stores whether or not this url is a data url.
* @readonly
* @member {boolean}
*/
get: function () {
return this._hasFlag(LoaderResource.STATUS_FLAGS.DATA_URL);
},
enumerable: false,
configurable: true
});
Object.defineProperty(LoaderResource.prototype, "isComplete", {
/**
* Describes if this resource has finished loading. Is true when the resource has completely
* loaded.
* @readonly
* @member {boolean}
*/
get: function () {
return this._hasFlag(LoaderResource.STATUS_FLAGS.COMPLETE);
},
enumerable: false,
configurable: true
});
Object.defineProperty(LoaderResource.prototype, "isLoading", {
/**
* Describes if this resource is currently loading. Is true when the resource starts loading,
* and is false again when complete.
* @readonly
* @member {boolean}
*/
get: function () {
return this._hasFlag(LoaderResource.STATUS_FLAGS.LOADING);
},
enumerable: false,
configurable: true
});
/** Marks the resource as complete. */
LoaderResource.prototype.complete = function () {
this._clearEvents();
this._finish();
};
/**
* Aborts the loading of this resource, with an optional message.
* @param {string} message - The message to use for the error
*/
LoaderResource.prototype.abort = function (message) {
// abort can be called multiple times, ignore subsequent calls.
if (this.error) {
return;
}
// store error
this.error = new Error(message);
// clear events before calling aborts
this._clearEvents();
// abort the actual loading
if (this.xhr) {
this.xhr.abort();
}
else if (this.xdr) {
this.xdr.abort();
}
else if (this.data) {
// single source
if (this.data.src) {
this.data.src = LoaderResource.EMPTY_GIF;
}
// multi-source
else {
while (this.data.firstChild) {
this.data.removeChild(this.data.firstChild);
}
}
}
// done now.
this._finish();
};
/**
* Kicks off loading of this resource. This method is asynchronous.
* @param {PIXI.LoaderResource.OnCompleteSignal} [cb] - Optional callback to call once the resource is loaded.
*/
LoaderResource.prototype.load = function (cb) {
var _this = this;
if (this.isLoading) {
return;
}
if (this.isComplete) {
if (cb) {
setTimeout(function () { return cb(_this); }, 1);
}
return;
}
else if (cb) {
this.onComplete.once(cb);
}
this._setFlag(LoaderResource.STATUS_FLAGS.LOADING, true);
this.onStart.dispatch(this);
// if unset, determine the value
if (this.crossOrigin === false || typeof this.crossOrigin !== 'string') {
this.crossOrigin = this._determineCrossOrigin(this.url);
}
switch (this.loadType) {
case LoaderResource.LOAD_TYPE.IMAGE:
this.type = LoaderResource.TYPE.IMAGE;
this._loadElement('image');
break;
case LoaderResource.LOAD_TYPE.AUDIO:
this.type = LoaderResource.TYPE.AUDIO;
this._loadSourceElement('audio');
break;
case LoaderResource.LOAD_TYPE.VIDEO:
this.type = LoaderResource.TYPE.VIDEO;
this._loadSourceElement('video');
break;
case LoaderResource.LOAD_TYPE.XHR:
/* falls through */
default:
if (typeof useXdr === 'undefined') {
useXdr = !!(globalThis.XDomainRequest && !('withCredentials' in (new XMLHttpRequest())));
}
if (useXdr && this.crossOrigin) {
this._loadXdr();
}
else {
this._loadXhr();
}
break;
}
};
/**
* Checks if the flag is set.
* @param flag - The flag to check.
* @returns True if the flag is set.
*/
LoaderResource.prototype._hasFlag = function (flag) {
return (this._flags & flag) !== 0;
};
/**
* (Un)Sets the flag.
* @param flag - The flag to (un)set.
* @param value - Whether to set or (un)set the flag.
*/
LoaderResource.prototype._setFlag = function (flag, value) {
this._flags = value ? (this._flags | flag) : (this._flags & ~flag);
};
/** Clears all the events from the underlying loading source. */
LoaderResource.prototype._clearEvents = function () {
clearTimeout(this._elementTimer);
if (this.data && this.data.removeEventListener) {
this.data.removeEventListener('error', this._boundOnError, false);
this.data.removeEventListener('load', this._boundComplete, false);
this.data.removeEventListener('progress', this._boundOnProgress, false);
this.data.removeEventListener('canplaythrough', this._boundComplete, false);
}
if (this.xhr) {
if (this.xhr.removeEventListener) {
this.xhr.removeEventListener('error', this._boundXhrOnError, false);
this.xhr.removeEventListener('timeout', this._boundXhrOnTimeout, false);
this.xhr.removeEventListener('abort', this._boundXhrOnAbort, false);
this.xhr.removeEventListener('progress', this._boundOnProgress, false);
this.xhr.removeEventListener('load', this._boundXhrOnLoad, false);
}
else {
this.xhr.onerror = null;
this.xhr.ontimeout = null;
this.xhr.onprogress = null;
this.xhr.onload = null;
}
}
};
/** Finalizes the load. */
LoaderResource.prototype._finish = function () {
if (this.isComplete) {
throw new Error('Complete called again for an already completed resource.');
}
this._setFlag(LoaderResource.STATUS_FLAGS.COMPLETE, true);
this._setFlag(LoaderResource.STATUS_FLAGS.LOADING, false);
this.onComplete.dispatch(this);
};
/**
* Loads this resources using an element that has a single source,
* like an HTMLImageElement.
* @private
* @param type - The type of element to use.
*/
LoaderResource.prototype._loadElement = function (type) {
if (this.metadata.loadElement) {
this.data = this.metadata.loadElement;
}
else if (type === 'image' && typeof globalThis.Image !== 'undefined') {
this.data = new Image();
}
else {
this.data = document.createElement(type);
}
if (this.crossOrigin) {
this.data.crossOrigin = this.crossOrigin;
}
if (!this.metadata.skipSource) {
this.data.src = this.url;
}
this.data.addEventListener('error', this._boundOnError, false);
this.data.addEventListener('load', this._boundComplete, false);
this.data.addEventListener('progress', this._boundOnProgress, false);
if (this.timeout) {
this._elementTimer = setTimeout(this._boundOnTimeout, this.timeout);
}
};
/**
* Loads this resources using an element that has multiple sources,
* like an HTMLAudioElement or HTMLVideoElement.
* @param type - The type of element to use.
*/
LoaderResource.prototype._loadSourceElement = function (type) {
if (this.metadata.loadElement) {
this.data = this.metadata.loadElement;
}
else if (type === 'audio' && typeof globalThis.Audio !== 'undefined') {
this.data = new Audio();
}
else {
this.data = document.createElement(type);
}
if (this.data === null) {
this.abort("Unsupported element: " + type);
return;
}
if (this.crossOrigin) {
this.data.crossOrigin = this.crossOrigin;
}
if (!this.metadata.skipSource) {
// support for CocoonJS Canvas+ runtime, lacks document.createElement('source')
if (navigator.isCocoonJS) {
this.data.src = Array.isArray(this.url) ? this.url[0] : this.url;
}
else if (Array.isArray(this.url)) {
var mimeTypes = this.metadata.mimeType;
for (var i = 0; i < this.url.length; ++i) {
this.data.appendChild(this._createSource(type, this.url[i], Array.isArray(mimeTypes) ? mimeTypes[i] : mimeTypes));
}
}
else {
var mimeTypes = this.metadata.mimeType;
this.data.appendChild(this._createSource(type, this.url, Array.isArray(mimeTypes) ? mimeTypes[0] : mimeTypes));
}
}
this.data.addEventListener('error', this._boundOnError, false);
this.data.addEventListener('load', this._boundComplete, false);
this.data.addEventListener('progress', this._boundOnProgress, false);
this.data.addEventListener('canplaythrough', this._boundComplete, false);
this.data.load();
if (this.timeout) {
this._elementTimer = setTimeout(this._boundOnTimeout, this.timeout);
}
};
/** Loads this resources using an XMLHttpRequest. */
LoaderResource.prototype._loadXhr = function () {
// if unset, determine the value
if (typeof this.xhrType !== 'string') {
this.xhrType = this._determineXhrType();
}
var xhr = this.xhr = new XMLHttpRequest();
// send credentials when crossOrigin with credentials requested
if (this.crossOrigin === 'use-credentials') {
xhr.withCredentials = true;
}
// set the request type and url
xhr.open('GET', this.url, true);
xhr.timeout = this.timeout;
// load json as text and parse it ourselves. We do this because some browsers
// *cough* safari *cough* can't deal with it.
if (this.xhrType === LoaderResource.XHR_RESPONSE_TYPE.JSON
|| this.xhrType === LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT) {
xhr.responseType = LoaderResource.XHR_RESPONSE_TYPE.TEXT;
}
else {
xhr.responseType = this.xhrType;
}
xhr.addEventListener('error', this._boundXhrOnError, false);
xhr.addEventListener('timeout', this._boundXhrOnTimeout, false);
xhr.addEventListener('abort', this._boundXhrOnAbort, false);
xhr.addEventListener('progress', this._boundOnProgress, false);
xhr.addEventListener('load', this._boundXhrOnLoad, false);
xhr.send();
};
/** Loads this resources using an XDomainRequest. This is here because we need to support IE9 (gross). */
LoaderResource.prototype._loadXdr = function () {
// if unset, determine the value
if (typeof this.xhrType !== 'string') {
this.xhrType = this._determineXhrType();
}
var xdr = this.xhr = new globalThis.XDomainRequest(); // eslint-disable-line no-undef
// XDomainRequest has a few quirks. Occasionally it will abort requests
// A way to avoid this is to make sure ALL callbacks are set even if not used
// More info here: http://stackoverflow.com/questions/15786966/xdomainrequest-aborts-post-on-ie-9
xdr.timeout = this.timeout || 5000; // XDR needs a timeout value or it breaks in IE9
xdr.onerror = this._boundXhrOnError;
xdr.ontimeout = this._boundXhrOnTimeout;
xdr.onprogress = this._boundOnProgress;
xdr.onload = this._boundXhrOnLoad;
xdr.open('GET', this.url, true);
// Note: The xdr.send() call is wrapped in a timeout to prevent an
// issue with the interface where some requests are lost if multiple
// XDomainRequests are being sent at the same time.
// Some info here: https://github.com/photonstorm/phaser/issues/1248
setTimeout(function () { return xdr.send(); }, 1);
};
/**
* Creates a source used in loading via an element.
* @param type - The element type (video or audio).
* @param url - The source URL to load from.
* @param [mime] - The mime type of the video
* @returns The source element.
*/
LoaderResource.prototype._createSource = function (type, url, mime) {
if (!mime) {
mime = type + "/" + this._getExtension(url);
}
var source = document.createElement('source');
source.src = url;
source.type = mime;
return source;
};
/**
* Called if a load errors out.
* @param event - The error event from the element that emits it.
*/
LoaderResource.prototype._onError = function (event) {
this.abort("Failed to load element using: " + event.target.nodeName);
};
/**
* Called if a load progress event fires for an element or xhr/xdr.
* @param event - Progress event.
*/
LoaderResource.prototype._onProgress = function (event) {
if (event && event.lengthComputable) {
this.onProgress.dispatch(this, event.loaded / event.total);
}
};
/** Called if a timeout event fires for an element. */
LoaderResource.prototype._onTimeout = function () {
this.abort("Load timed out.");
};
/** Called if an error event fires for xhr/xdr. */
LoaderResource.prototype._xhrOnError = function () {
var xhr = this.xhr;
this.abort(reqType(xhr) + " Request failed. Status: " + xhr.status + ", text: \"" + xhr.statusText + "\"");
};
/** Called if an error event fires for xhr/xdr. */
LoaderResource.prototype._xhrOnTimeout = function () {
var xhr = this.xhr;
this.abort(reqType(xhr) + " Request timed out.");
};
/** Called if an abort event fires for xhr/xdr. */
LoaderResource.prototype._xhrOnAbort = function () {
var xhr = this.xhr;
this.abort(reqType(xhr) + " Request was aborted by the user.");
};
/** Called when data successfully loads from an xhr/xdr request. */
LoaderResource.prototype._xhrOnLoad = function () {
var xhr = this.xhr;
var text = '';
var status = typeof xhr.status === 'undefined' ? STATUS_OK : xhr.status; // XDR has no `.status`, assume 200.
// responseText is accessible only if responseType is '' or 'text' and on older browsers
if (xhr.responseType === '' || xhr.responseType === 'text' || typeof xhr.responseType === 'undefined') {
text = xhr.responseText;
}
// status can be 0 when using the `file://` protocol so we also check if a response is set.
// If it has a response, we assume 200; otherwise a 0 status code with no contents is an aborted request.
if (status === STATUS_NONE && (text.length > 0 || xhr.responseType === LoaderResource.XHR_RESPONSE_TYPE.BUFFER)) {
status = STATUS_OK;
}
// handle IE9 bug: http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request
else if (status === STATUS_IE_BUG_EMPTY) {
status = STATUS_EMPTY;
}
var statusType = (status / 100) | 0;
if (statusType === STATUS_TYPE_OK) {
// if text, just return it
if (this.xhrType === LoaderResource.XHR_RESPONSE_TYPE.TEXT) {
this.data = text;
this.type = LoaderResource.TYPE.TEXT;
}
// if json, parse into json object
else if (this.xhrType === LoaderResource.XHR_RESPONSE_TYPE.JSON) {
try {
this.data = JSON.parse(text);
this.type = LoaderResource.TYPE.JSON;
}
catch (e) {
this.abort("Error trying to parse loaded json: " + e);
return;
}
}
// if xml, parse into an xml document or div element
else if (this.xhrType === LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT) {
try {
if (globalThis.DOMParser) {
var domparser = new DOMParser();
this.data = domparser.parseFromString(text, 'text/xml');
}
else {
var div = document.createElement('div');
div.innerHTML = text;
this.data = div;
}
this.type = LoaderResource.TYPE.XML;
}
catch (e$1) {
this.abort("Error trying to parse loaded xml: " + e$1);
return;
}
}
// other types just return the response
else {
this.data = xhr.response || text;
}
}
else {
this.abort("[" + xhr.status + "] " + xhr.statusText + ": " + xhr.responseURL);
return;
}
this.complete();
};
/**
* Sets the `crossOrigin` property for this resource based on if the url
* for this resource is cross-origin. If crossOrigin was manually set, this
* function does nothing.
* @private
* @param url - The url to test.
* @param [loc=globalThis.location] - The location object to test against.
* @returns The crossOrigin value to use (or empty string for none).
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
LoaderResource.prototype._determineCrossOrigin = function (url, loc) {
// data: and javascript: urls are considered same-origin
if (url.indexOf('data:') === 0) {
return '';
}
// A sandboxed iframe without the 'allow-same-origin' attribute will have a special
// origin designed not to match globalThis.location.origin, and will always require
// crossOrigin requests regardless of whether the location matches.
if (globalThis.origin !== globalThis.location.origin) {
return 'anonymous';
}
// default is globalThis.location
loc = loc || globalThis.location;
if (!tempAnchor) {
tempAnchor = document.createElement('a');
}
// let the browser determine the full href for the url of this resource and then
// parse with the node url lib, we can't use the properties of the anchor element
// because they don't work in IE9 :(
tempAnchor.href = url;
var parsedUrl = parseUri(tempAnchor.href, { strictMode: true });
var samePort = (!parsedUrl.port && loc.port === '') || (parsedUrl.port === loc.port);
var protocol = parsedUrl.protocol ? parsedUrl.protocol + ":" : '';
// if cross origin
if (parsedUrl.host !== loc.hostname || !samePort || protocol !== loc.protocol) {
return 'anonymous';
}
return '';
};
/**
* Determines the responseType of an XHR request based on the extension of the
* resource being loaded.
* @private
* @returns {PIXI.LoaderResource.XHR_RESPONSE_TYPE} The responseType to use.
*/
LoaderResource.prototype._determineXhrType = function () {
return LoaderResource._xhrTypeMap[this.extension] || LoaderResource.XHR_RESPONSE_TYPE.TEXT;
};
/**
* Determines the loadType of a resource based on the extension of the
* resource being loaded.
* @private
* @returns {PIXI.LoaderResource.LOAD_TYPE} The loadType to use.
*/
LoaderResource.prototype._determineLoadType = function () {
return LoaderResource._loadTypeMap[this.extension] || LoaderResource.LOAD_TYPE.XHR;
};
/**
* Extracts the extension (sans '.') of the file being loaded by the resource.
* @param [url] - url to parse, `this.url` by default.
* @returns The extension.
*/
LoaderResource.prototype._getExtension = function (url) {
if (url === void 0) { url = this.url; }
var ext = '';
if (this.isDataUrl) {
var slashIndex = url.indexOf('/');
ext = url.substring(slashIndex + 1, url.indexOf(';', slashIndex));
}
else {
var queryStart = url.indexOf('?');
var hashStart = url.indexOf('#');
var index = Math.min(queryStart > -1 ? queryStart : url.length, hashStart > -1 ? hashStart : url.length);
url = url.substring(0, index);
ext = url.substring(url.lastIndexOf('.') + 1);
}
return ext.toLowerCase();
};
/**
* Determines the mime type of an XHR request based on the responseType of
* resource being loaded.
* @param type - The type to get a mime type for.
* @private
* @returns The mime type to use.
*/
LoaderResource.prototype._getMimeFromXhrType = function (type) {
switch (type) {
case LoaderResource.XHR_RESPONSE_TYPE.BUFFER:
return 'application/octet-binary';
case LoaderResource.XHR_RESPONSE_TYPE.BLOB:
return 'application/blob';
case LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT:
return 'application/xml';
case LoaderResource.XHR_RESPONSE_TYPE.JSON:
return 'application/json';
case LoaderResource.XHR_RESPONSE_TYPE.DEFAULT:
case LoaderResource.XHR_RESPONSE_TYPE.TEXT:
/* falls through */
default:
return 'text/plain';
}
};
return LoaderResource;
}());
// eslint-disable-next-line @typescript-eslint/no-namespace
(function (LoaderResource) {
/**
* The types of resources a resource could represent.
* @static
* @readonly
* @enum {number}
* @memberof PIXI.LoaderResource
*/
var STATUS_FLAGS;
(function (STATUS_FLAGS) {
/** None */
STATUS_FLAGS[STATUS_FLAGS["NONE"] = 0] = "NONE";
/** Data URL */
STATUS_FLAGS[STATUS_FLAGS["DATA_URL"] = 1] = "DATA_URL";
/** Complete */
STATUS_FLAGS[STATUS_FLAGS["COMPLETE"] = 2] = "COMPLETE";
/** Loading */
STATUS_FLAGS[STATUS_FLAGS["LOADING"] = 4] = "LOADING";
})(STATUS_FLAGS = LoaderResource.STATUS_FLAGS || (LoaderResource.STATUS_FLAGS = {}));
/**
* The types of resources a resource could represent.
* @static
* @readonly
* @enum {number}
* @memberof PIXI.LoaderResource
*/
var TYPE;
(function (TYPE) {
/** Unknown */
TYPE[TYPE["UNKNOWN"] = 0] = "UNKNOWN";
/** JSON */
TYPE[TYPE["JSON"] = 1] = "JSON";
/** XML */
TYPE[TYPE["XML"] = 2] = "XML";
/** Image */
TYPE[TYPE["IMAGE"] = 3] = "IMAGE";
/** Audio */
TYPE[TYPE["AUDIO"] = 4] = "AUDIO";
/** Video */
TYPE[TYPE["VIDEO"] = 5] = "VIDEO";
/** Plain text */
TYPE[TYPE["TEXT"] = 6] = "TEXT";
})(TYPE = LoaderResource.TYPE || (LoaderResource.TYPE = {}));
/**
* The types of loading a resource can use.
* @static
* @readonly
* @enum {number}
* @memberof PIXI.LoaderResource
*/
var LOAD_TYPE;
(function (LOAD_TYPE) {
/** Uses XMLHttpRequest to load the resource. */
LOAD_TYPE[LOAD_TYPE["XHR"] = 1] = "XHR";
/** Uses an `Image` object to load the resource. */
LOAD_TYPE[LOAD_TYPE["IMAGE"] = 2] = "IMAGE";
/** Uses an `Audio` object to load the resource. */
LOAD_TYPE[LOAD_TYPE["AUDIO"] = 3] = "AUDIO";
/** Uses a `Video` object to load the resource. */
LOAD_TYPE[LOAD_TYPE["VIDEO"] = 4] = "VIDEO";
})(LOAD_TYPE = LoaderResource.LOAD_TYPE || (LoaderResource.LOAD_TYPE = {}));
/**
* The XHR ready states, used internally.
* @static
* @readonly
* @enum {string}
* @memberof PIXI.LoaderResource
*/
var XHR_RESPONSE_TYPE;
(function (XHR_RESPONSE_TYPE) {
/** string */
XHR_RESPONSE_TYPE["DEFAULT"] = "text";
/** ArrayBuffer */
XHR_RESPONSE_TYPE["BUFFER"] = "arraybuffer";
/** Blob */
XHR_RESPONSE_TYPE["BLOB"] = "blob";
/** Document */
XHR_RESPONSE_TYPE["DOCUMENT"] = "document";
/** Object */
XHR_RESPONSE_TYPE["JSON"] = "json";
/** String */
XHR_RESPONSE_TYPE["TEXT"] = "text";
})(XHR_RESPONSE_TYPE = LoaderResource.XHR_RESPONSE_TYPE || (LoaderResource.XHR_RESPONSE_TYPE = {}));
LoaderResource._loadTypeMap = {
// images
gif: LoaderResource.LOAD_TYPE.IMAGE,
png: LoaderResource.LOAD_TYPE.IMAGE,
bmp: LoaderResource.LOAD_TYPE.IMAGE,
jpg: LoaderResource.LOAD_TYPE.IMAGE,
jpeg: LoaderResource.LOAD_TYPE.IMAGE,
tif: LoaderResource.LOAD_TYPE.IMAGE,
tiff: LoaderResource.LOAD_TYPE.IMAGE,
webp: LoaderResource.LOAD_TYPE.IMAGE,
tga: LoaderResource.LOAD_TYPE.IMAGE,
avif: LoaderResource.LOAD_TYPE.IMAGE,
svg: LoaderResource.LOAD_TYPE.IMAGE,
'svg+xml': LoaderResource.LOAD_TYPE.IMAGE,
// audio
mp3: LoaderResource.LOAD_TYPE.AUDIO,
ogg: LoaderResource.LOAD_TYPE.AUDIO,
wav: LoaderResource.LOAD_TYPE.AUDIO,
// videos
mp4: LoaderResource.LOAD_TYPE.VIDEO,
webm: LoaderResource.LOAD_TYPE.VIDEO,
};
LoaderResource._xhrTypeMap = {
// xml
xhtml: LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT,
html: LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT,
htm: LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT,
xml: LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT,
tmx: LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT,
svg: LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT,
// This was added to handle Tiled Tileset XML, but .tsx is also a TypeScript React Component.
// Since it is way less likely for people to be loading TypeScript files instead of Tiled files,
// this should probably be fine.
tsx: LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT,
// images
gif: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
png: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
bmp: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
jpg: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
jpeg: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
tif: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
tiff: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
webp: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
tga: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
avif: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
// json
json: LoaderResource.XHR_RESPONSE_TYPE.JSON,
// text
text: LoaderResource.XHR_RESPONSE_TYPE.TEXT,
txt: LoaderResource.XHR_RESPONSE_TYPE.TEXT,
// fonts
ttf: LoaderResource.XHR_RESPONSE_TYPE.BUFFER,
otf: LoaderResource.XHR_RESPONSE_TYPE.BUFFER,
};
// We can't set the `src` attribute to empty string, so on abort we set it to this 1px transparent gif
LoaderResource.EMPTY_GIF = 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==';
})(exports.LoaderResource || (exports.LoaderResource = {}));
/**
* Smaller version of the async library constructs.
* @ignore
*/
function _noop() {
}
/**
* Ensures a function is only called once.
* @ignore
* @param {Function} fn - The function to wrap.
* @returns {Function} The wrapping function.
*/
function onlyOnce(fn) {
return function onceWrapper() {
var arguments$1 = arguments;
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments$1[_i];
}
if (fn === null) {
throw new Error('Callback was already called.');
}
var callFn = fn;
fn = null;
callFn.apply(this, args);
};
}
/**
* @private
* @memberof PIXI
*/
var AsyncQueueItem = /** @class */ (function () {
/**
* @param data
* @param callback
* @private
*/
function AsyncQueueItem(data, callback) {
this.data = data;
this.callback = callback;
}
return AsyncQueueItem;
}());
/**
* @private
* @memberof PIXI
*/
var AsyncQueue = /** @class */ (function () {
/**
* @param worker
* @param concurrency
* @private
*/
function AsyncQueue(worker, concurrency) {
var _this = this;
if (concurrency === void 0) { concurrency = 1; }
this.workers = 0;
this.saturated = _noop;
this.unsaturated = _noop;
this.empty = _noop;
this.drain = _noop;
this.error = _noop;
this.started = false;
this.paused = false;
this._tasks = [];
this._insert = function (data, insertAtFront, callback) {
if (callback && typeof callback !== 'function') {
throw new Error('task callback must be a function');
}
_this.started = true;
// eslint-disable-next-line no-eq-null,eqeqeq
if (data == null && _this.idle()) {
// call drain immediately if there are no tasks
setTimeout(function () { return _this.drain(); }, 1);
return;
}
var item = new AsyncQueueItem(data, typeof callback === 'function' ? callback : _noop);
if (insertAtFront) {
_this._tasks.unshift(item);
}
else {
_this._tasks.push(item);
}
setTimeout(_this.process, 1);
};
this.process = function () {
while (!_this.paused && _this.workers < _this.concurrency && _this._tasks.length) {
var task = _this._tasks.shift();
if (_this._tasks.length === 0) {
_this.empty();
}
_this.workers += 1;
if (_this.workers === _this.concurrency) {
_this.saturated();
}
_this._worker(task.data, onlyOnce(_this._next(task)));
}
};
this._worker = worker;
if (concurrency === 0) {
throw new Error('Concurrency must not be zero');
}
this.concurrency = concurrency;
this.buffer = concurrency / 4.0;
}
/**
* @param task
* @private
*/
AsyncQueue.prototype._next = function (task) {
var _this = this;
return function () {
var arguments$1 = arguments;
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments$1[_i];
}
_this.workers -= 1;
task.callback.apply(task, args);
// eslint-disable-next-line no-eq-null,eqeqeq
if (args[0] != null) {
_this.error(args[0], task.data);
}
if (_this.workers <= (_this.concurrency - _this.buffer)) {
_this.unsaturated();
}
if (_this.idle()) {
_this.drain();
}
_this.process();
};
};
// That was in object
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
AsyncQueue.prototype.push = function (data, callback) {
this._insert(data, false, callback);
};
AsyncQueue.prototype.kill = function () {
this.workers = 0;
this.drain = _noop;
this.started = false;
this._tasks = [];
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
AsyncQueue.prototype.unshift = function (data, callback) {
this._insert(data, true, callback);
};
AsyncQueue.prototype.length = function () {
return this._tasks.length;
};
AsyncQueue.prototype.running = function () {
return this.workers;
};
AsyncQueue.prototype.idle = function () {
return this._tasks.length + this.workers === 0;
};
AsyncQueue.prototype.pause = function () {
if (this.paused === true) {
return;
}
this.paused = true;
};
AsyncQueue.prototype.resume = function () {
if (this.paused === false) {
return;
}
this.paused = false;
// Need to call this.process once per concurrent
// worker to preserve full concurrency after pause
for (var w = 1; w <= this.concurrency; w++) {
this.process();
}
};
/**
* Iterates an array in series.
* @param {Array.<*>} array - Array to iterate.
* @param {Function} iterator - Function to call for each element.
* @param {Function} callback - Function to call when done, or on error.
* @param {boolean} [deferNext=false] - Break synchronous each loop by calling next with a setTimeout of 1.
*/
AsyncQueue.eachSeries = function (array, iterator, callback, deferNext) {
var i = 0;
var len = array.length;
function next(err) {
if (err || i === len) {
if (callback) {
callback(err);
}
return;
}
if (deferNext) {
setTimeout(function () {
iterator(array[i++], next);
}, 1);
}
else {
iterator(array[i++], next);
}
}
next();
};
/**
* Async queue implementation,
* @param {Function} worker - The worker function to call for each task.
* @param {number} concurrency - How many workers to run in parrallel.
* @returns {*} The async queue object.
*/
AsyncQueue.queue = function (worker, concurrency) {
return new AsyncQueue(worker, concurrency);
};
return AsyncQueue;
}());
// some constants
var MAX_PROGRESS = 100;
var rgxExtractUrlHash = /(#[\w-]+)?$/;
/**
* The new loader, forked from Resource Loader by Chad Engler: https://github.com/englercj/resource-loader
*
* ```js
* const loader = PIXI.Loader.shared; // PixiJS exposes a premade instance for you to use.
* // or
* const loader = new PIXI.Loader(); // You can also create your own if you want
*
* const sprites = {};
*
* // Chainable `add` to enqueue a resource
* loader.add('bunny', 'data/bunny.png')
* .add('spaceship', 'assets/spritesheet.json');
* loader.add('scoreFont', 'assets/score.fnt');
*
* // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource.
* // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc).
* loader.pre(cachingMiddleware);
*
* // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource.
* // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc).
* loader.use(parsingMiddleware);
*
* // The `load` method loads the queue of resources, and calls the passed in callback called once all
* // resources have loaded.
* loader.load((loader, resources) => {
* // resources is an object where the key is the name of the resource loaded and the value is the resource object.
* // They have a couple default properties:
* // - `url`: The URL that the resource was loaded from
* // - `error`: The error that happened when trying to load (if any)
* // - `data`: The raw data that was loaded
* // also may contain other properties based on the middleware that runs.
* sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture);
* sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture);
* sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture);
* });
*
* // throughout the process multiple signals can be dispatched.
* loader.onProgress.add(() => {}); // called once per loaded/errored file
* loader.onError.add(() => {}); // called once per errored file
* loader.onLoad.add(() => {}); // called once per loaded file
* loader.onComplete.add(() => {}); // called once when the queued resources all load.
* ```
* @memberof PIXI
*/
var Loader = /** @class */ (function () {
/**
* @param baseUrl - The base url for all resources loaded by this loader.
* @param concurrency - The number of resources to load concurrently.
*/
function Loader(baseUrl, concurrency) {
var _this = this;
if (baseUrl === void 0) { baseUrl = ''; }
if (concurrency === void 0) { concurrency = 10; }
/** The progress percent of the loader going through the queue. */
this.progress = 0;
/** Loading state of the loader, true if it is currently loading resources. */
this.loading = false;
/**
* A querystring to append to every URL added to the loader.
*
* This should be a valid query string *without* the question-mark (`?`). The loader will
* also *not* escape values for you. Make sure to escape your parameters with
* [`encodeURIComponent`](https://mdn.io/encodeURIComponent) before assigning this property.
* @example
* const loader = new Loader();
*
* loader.defaultQueryString = 'user=me&password=secret';
*
* // This will request 'image.png?user=me&password=secret'
* loader.add('image.png').load();
*
* loader.reset();
*
* // This will request 'image.png?v=1&user=me&password=secret'
* loader.add('iamge.png?v=1').load();
*/
this.defaultQueryString = '';
/** The middleware to run before loading each resource. */
this._beforeMiddleware = [];
/** The middleware to run after loading each resource. */
this._afterMiddleware = [];
/** The tracks the resources we are currently completing parsing for. */
this._resourcesParsing = [];
/**
* The `_loadResource` function bound with this object context.
* @param r - The resource to load
* @param d - The dequeue function
*/
this._boundLoadResource = function (r, d) { return _this._loadResource(r, d); };
/** All the resources for this loader keyed by name. */
this.resources = {};
this.baseUrl = baseUrl;
this._beforeMiddleware = [];
this._afterMiddleware = [];
this._resourcesParsing = [];
this._boundLoadResource = function (r, d) { return _this._loadResource(r, d); };
this._queue = AsyncQueue.queue(this._boundLoadResource, concurrency);
this._queue.pause();
this.resources = {};
this.onProgress = new Signal();
this.onError = new Signal();
this.onLoad = new Signal();
this.onStart = new Signal();
this.onComplete = new Signal();
for (var i = 0; i < Loader._plugins.length; ++i) {
var plugin = Loader._plugins[i];
var pre = plugin.pre, use = plugin.use;
if (pre) {
this.pre(pre);
}
if (use) {
this.use(use);
}
}
this._protected = false;
}
/**
* Same as add, params have strict order
* @private
* @param name - The name of the resource to load.
* @param url - The url for this resource, relative to the baseUrl of this loader.
* @param options - The options for the load.
* @param callback - Function to call when this specific resource completes loading.
* @returns The loader itself.
*/
Loader.prototype._add = function (name, url, options, callback) {
// if loading already you can only add resources that have a parent.
if (this.loading && (!options || !options.parentResource)) {
throw new Error('Cannot add resources while the loader is running.');
}
// check if resource already exists.
if (this.resources[name]) {
throw new Error("Resource named \"" + name + "\" already exists.");
}
// add base url if this isn't an absolute url
url = this._prepareUrl(url);
// create the store the resource
this.resources[name] = new exports.LoaderResource(name, url, options);
if (typeof callback === 'function') {
this.resources[name].onAfterMiddleware.once(callback);
}
// if actively loading, make sure to adjust progress chunks for that parent and its children
if (this.loading) {
var parent = options.parentResource;
var incompleteChildren = [];
for (var i = 0; i < parent.children.length; ++i) {
if (!parent.children[i].isComplete) {
incompleteChildren.push(parent.children[i]);
}
}
var fullChunk = parent.progressChunk * (incompleteChildren.length + 1); // +1 for parent
var eachChunk = fullChunk / (incompleteChildren.length + 2); // +2 for parent & new child
parent.children.push(this.resources[name]);
parent.progressChunk = eachChunk;
for (var i = 0; i < incompleteChildren.length; ++i) {
incompleteChildren[i].progressChunk = eachChunk;
}
this.resources[name].progressChunk = eachChunk;
}
// add the resource to the queue
this._queue.push(this.resources[name]);
return this;
};
/* eslint-enable require-jsdoc,valid-jsdoc */
/**
* Sets up a middleware function that will run *before* the
* resource is loaded.
* @param fn - The middleware function to register.
* @returns The loader itself.
*/
Loader.prototype.pre = function (fn) {
this._beforeMiddleware.push(fn);
return this;
};
/**
* Sets up a middleware function that will run *after* the
* resource is loaded.
* @param fn - The middleware function to register.
* @returns The loader itself.
*/
Loader.prototype.use = function (fn) {
this._afterMiddleware.push(fn);
return this;
};
/**
* Resets the queue of the loader to prepare for a new load.
* @returns The loader itself.
*/
Loader.prototype.reset = function () {
this.progress = 0;
this.loading = false;
this._queue.kill();
this._queue.pause();
// abort all resource loads
for (var k in this.resources) {
var res = this.resources[k];
if (res._onLoadBinding) {
res._onLoadBinding.detach();
}
if (res.isLoading) {
res.abort('loader reset');
}
}
this.resources = {};
return this;
};
/**
* Starts loading the queued resources.
* @param cb - Optional callback that will be bound to the `complete` event.
* @returns The loader itself.
*/
Loader.prototype.load = function (cb) {
utils.deprecation('6.5.0', '@pixi/loaders is being replaced with @pixi/assets in the next major release.');
// register complete callback if they pass one
if (typeof cb === 'function') {
this.onComplete.once(cb);
}
// if the queue has already started we are done here
if (this.loading) {
return this;
}
if (this._queue.idle()) {
this._onStart();
this._onComplete();
}
else {
// distribute progress chunks
var numTasks = this._queue._tasks.length;
var chunk = MAX_PROGRESS / numTasks;
for (var i = 0; i < this._queue._tasks.length; ++i) {
this._queue._tasks[i].data.progressChunk = chunk;
}
// notify we are starting
this._onStart();
// start loading
this._queue.resume();
}
return this;
};
Object.defineProperty(Loader.prototype, "concurrency", {
/**
* The number of resources to load concurrently.
* @default 10
*/
get: function () {
return this._queue.concurrency;
},
set: function (concurrency) {
this._queue.concurrency = concurrency;
},
enumerable: false,
configurable: true
});
/**
* Prepares a url for usage based on the configuration of this object
* @param url - The url to prepare.
* @returns The prepared url.
*/
Loader.prototype._prepareUrl = function (url) {
var parsedUrl = parseUri(url, { strictMode: true });
var result;
// absolute url, just use it as is.
if (parsedUrl.protocol || !parsedUrl.path || url.indexOf('//') === 0) {
result = url;
}
// if baseUrl doesn't end in slash and url doesn't start with slash, then add a slash inbetween
else if (this.baseUrl.length
&& this.baseUrl.lastIndexOf('/') !== this.baseUrl.length - 1
&& url.charAt(0) !== '/') {
result = this.baseUrl + "/" + url;
}
else {
result = this.baseUrl + url;
}
// if we need to add a default querystring, there is a bit more work
if (this.defaultQueryString) {
var hash = rgxExtractUrlHash.exec(result)[0];
result = result.slice(0, result.length - hash.length);
if (result.indexOf('?') !== -1) {
result += "&" + this.defaultQueryString;
}
else {
result += "?" + this.defaultQueryString;
}
result += hash;
}
return result;
};
/**
* Loads a single resource.
* @param resource - The resource to load.
* @param dequeue - The function to call when we need to dequeue this item.
*/
Loader.prototype._loadResource = function (resource, dequeue) {
var _this = this;
resource._dequeue = dequeue;
// run before middleware
AsyncQueue.eachSeries(this._beforeMiddleware, function (fn, next) {
fn.call(_this, resource, function () {
// if the before middleware marks the resource as complete,
// break and don't process any more before middleware
next(resource.isComplete ? {} : null);
});
}, function () {
if (resource.isComplete) {
_this._onLoad(resource);
}
else {
resource._onLoadBinding = resource.onComplete.once(_this._onLoad, _this);
resource.load();
}
}, true);
};
/** Called once loading has started. */
Loader.prototype._onStart = function () {
this.progress = 0;
this.loading = true;
this.onStart.dispatch(this);
};
/** Called once each resource has loaded. */
Loader.prototype._onComplete = function () {
this.progress = MAX_PROGRESS;
this.loading = false;
this.onComplete.dispatch(this, this.resources);
};
/**
* Called each time a resources is loaded.
* @param resource - The resource that was loaded
*/
Loader.prototype._onLoad = function (resource) {
var _this = this;
resource._onLoadBinding = null;
// remove this resource from the async queue, and add it to our list of resources that are being parsed
this._resourcesParsing.push(resource);
resource._dequeue();
// run all the after middleware for this resource
AsyncQueue.eachSeries(this._afterMiddleware, function (fn, next) {
fn.call(_this, resource, next);
}, function () {
resource.onAfterMiddleware.dispatch(resource);
_this.progress = Math.min(MAX_PROGRESS, _this.progress + resource.progressChunk);
_this.onProgress.dispatch(_this, resource);
if (resource.error) {
_this.onError.dispatch(resource.error, _this, resource);
}
else {
_this.onLoad.dispatch(_this, resource);
}
_this._resourcesParsing.splice(_this._resourcesParsing.indexOf(resource), 1);
// do completion check
if (_this._queue.idle() && _this._resourcesParsing.length === 0) {
_this._onComplete();
}
}, true);
};
/** Destroy the loader, removes references. */
Loader.prototype.destroy = function () {
if (!this._protected) {
this.reset();
}
};
Object.defineProperty(Loader, "shared", {
/** A premade instance of the loader that can be used to load resources. */
get: function () {
var shared = Loader._shared;
if (!shared) {
shared = new Loader();
shared._protected = true;
Loader._shared = shared;
}
return shared;
},
enumerable: false,
configurable: true
});
/**
* Use the {@link PIXI.extensions.add} API to register plugins.
* @deprecated since 6.5.0
* @param plugin - The plugin to add
* @returns Reference to PIXI.Loader for chaining
*/
Loader.registerPlugin = function (plugin) {
utils.deprecation('6.5.0', 'Loader.registerPlugin() is deprecated, use extensions.add() instead.');
core.extensions.add({
type: core.ExtensionType.Loader,
ref: plugin,
});
return Loader;
};
Loader._plugins = [];
return Loader;
}());
core.extensions.handleByList(core.ExtensionType.Loader, Loader._plugins);
Loader.prototype.add = function add(name, url, options, callback) {
// special case of an array of objects or urls
if (Array.isArray(name)) {
for (var i = 0; i < name.length; ++i) {
this.add(name[i]);
}
return this;
}
// if an object is passed instead of params
if (typeof name === 'object') {
options = name;
callback = url || options.callback || options.onComplete;
url = options.url;
name = options.name || options.key || options.url;
}
// case where no name is passed shift all args over by one.
if (typeof url !== 'string') {
callback = options;
options = url;
url = name;
}
// now that we shifted make sure we have a proper url.
if (typeof url !== 'string') {
throw new Error('No url passed to add resource to loader.');
}
// options are optional so people might pass a function and no options
if (typeof options === 'function') {
callback = options;
options = null;
}
return this._add(name, url, options, callback);
};
/**
* Application plugin for supporting loader option. Installing the LoaderPlugin
* is not necessary if using **pixi.js** or **pixi.js-legacy**.
* @example
* import {AppLoaderPlugin} from '@pixi/loaders';
* import {extensions} from '@pixi/core';
* extensions.add(AppLoaderPlugin);
* @memberof PIXI
*/
var AppLoaderPlugin = /** @class */ (function () {
function AppLoaderPlugin() {
}
/**
* Called on application constructor
* @param options
* @private
*/
AppLoaderPlugin.init = function (options) {
options = Object.assign({
sharedLoader: false,
}, options);
this.loader = options.sharedLoader ? Loader.shared : new Loader();
};
/**
* Called when application destroyed
* @private
*/
AppLoaderPlugin.destroy = function () {
if (this.loader) {
this.loader.destroy();
this.loader = null;
}
};
/** @ignore */
AppLoaderPlugin.extension = core.ExtensionType.Application;
return AppLoaderPlugin;
}());
/**
* Loader plugin for handling Texture resources.
* @memberof PIXI
*/
var TextureLoader = /** @class */ (function () {
function TextureLoader() {
}
/** Handle SVG elements a text, render with SVGResource. */
TextureLoader.add = function () {
exports.LoaderResource.setExtensionLoadType('svg', exports.LoaderResource.LOAD_TYPE.XHR);
exports.LoaderResource.setExtensionXhrType('svg', exports.LoaderResource.XHR_RESPONSE_TYPE.TEXT);
};
/**
* Called after a resource is loaded.
* @see PIXI.Loader.loaderMiddleware
* @param resource
* @param {Function} next
*/
TextureLoader.use = function (resource, next) {
// create a new texture if the data is an Image object
if (resource.data && (resource.type === exports.LoaderResource.TYPE.IMAGE || resource.extension === 'svg')) {
var data = resource.data, url = resource.url, name = resource.name, metadata = resource.metadata;
core.Texture.fromLoader(data, url, name, metadata).then(function (texture) {
resource.texture = texture;
next();
})
// TODO: handle errors in Texture.fromLoader
// so we can pass them to the Loader
.catch(next);
}
else {
next();
}
};
/** @ignore */
TextureLoader.extension = core.ExtensionType.Loader;
return TextureLoader;
}());
// a simple in-memory cache for resources
var cache = {};
/**
* A simple in-memory cache for resource.
* @ignore
* @function caching
* @example
* import { Loader, middleware } from 'resource-loader';
* const loader = new Loader();
* loader.use(middleware.caching);
* @param resource - Current Resource
* @param next - Callback when complete
*/
function caching(resource, next) {
// if cached, then set data and complete the resource
if (cache[resource.url]) {
resource.data = cache[resource.url];
resource.complete(); // marks resource load complete and stops processing before middlewares
}
// if not cached, wait for complete and store it in the cache.
else {
resource.onComplete.once(function doCache() { cache[this.url] = this.data; });
}
next();
}
var _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
/**
* Encodes binary into base64.
* @function encodeBinary
* @param {string} input - The input data to encode.
* @returns {string} The encoded base64 string
*/
function encodeBinary(input) {
var output = '';
var inx = 0;
while (inx < input.length) {
// Fill byte buffer array
var bytebuffer = [0, 0, 0];
var encodedCharIndexes = [0, 0, 0, 0];
for (var jnx = 0; jnx < bytebuffer.length; ++jnx) {
if (inx < input.length) {
// throw away high-order byte, as documented at:
// https://developer.mozilla.org/En/Using_XMLHttpRequest#Handling_binary_data
bytebuffer[jnx] = input.charCodeAt(inx++) & 0xff;
}
else {
bytebuffer[jnx] = 0;
}
}
// Get each encoded character, 6 bits at a time
// index 1: first 6 bits
encodedCharIndexes[0] = bytebuffer[0] >> 2;
// index 2: second 6 bits (2 least significant bits from input byte 1 + 4 most significant bits from byte 2)
encodedCharIndexes[1] = ((bytebuffer[0] & 0x3) << 4) | (bytebuffer[1] >> 4);
// index 3: third 6 bits (4 least significant bits from input byte 2 + 2 most significant bits from byte 3)
encodedCharIndexes[2] = ((bytebuffer[1] & 0x0f) << 2) | (bytebuffer[2] >> 6);
// index 3: forth 6 bits (6 least significant bits from input byte 3)
encodedCharIndexes[3] = bytebuffer[2] & 0x3f;
// Determine whether padding happened, and adjust accordingly
var paddingBytes = inx - (input.length - 1);
switch (paddingBytes) {
case 2:
// Set last 2 characters to padding char
encodedCharIndexes[3] = 64;
encodedCharIndexes[2] = 64;
break;
case 1:
// Set last character to padding char
encodedCharIndexes[3] = 64;
break;
default:
break; // No padding - proceed
}
// Now we will grab each appropriate character out of our keystring
// based on our index array and append it to the output string
for (var jnx = 0; jnx < encodedCharIndexes.length; ++jnx) {
output += _keyStr.charAt(encodedCharIndexes[jnx]);
}
}
return output;
}
/**
* A middleware for transforming XHR loaded Blobs into more useful objects
* @ignore
* @function parsing
* @example
* import { Loader, middleware } from 'resource-loader';
* const loader = new Loader();
* loader.use(middleware.parsing);
* @param resource - Current Resource
* @param next - Callback when complete
*/
function parsing(resource, next) {
if (!resource.data) {
next();
return;
}
// if this was an XHR load of a blob
if (resource.xhr && resource.xhrType === exports.LoaderResource.XHR_RESPONSE_TYPE.BLOB) {
// if there is no blob support we probably got a binary string back
if (!self.Blob || typeof resource.data === 'string') {
var type = resource.xhr.getResponseHeader('content-type');
// this is an image, convert the binary string into a data url
if (type && type.indexOf('image') === 0) {
resource.data = new Image();
resource.data.src = "data:" + type + ";base64," + encodeBinary(resource.xhr.responseText);
resource.type = exports.LoaderResource.TYPE.IMAGE;
// wait until the image loads and then callback
resource.data.onload = function () {
resource.data.onload = null;
next();
};
// next will be called on load
return;
}
}
// if content type says this is an image, then we should transform the blob into an Image object
else if (resource.data.type.indexOf('image') === 0) {
var Url_1 = globalThis.URL || globalThis.webkitURL;
var src_1 = Url_1.createObjectURL(resource.data);
resource.blob = resource.data;
resource.data = new Image();
resource.data.src = src_1;
resource.type = exports.LoaderResource.TYPE.IMAGE;
// cleanup the no longer used blob after the image loads
// TODO: Is this correct? Will the image be invalid after revoking?
resource.data.onload = function () {
Url_1.revokeObjectURL(src_1);
resource.data.onload = null;
next();
};
// next will be called on load.
return;
}
}
next();
}
/**
* Parse any blob into more usable objects (e.g. Image).
* @memberof PIXI
*/
var ParsingLoader = /** @class */ (function () {
function ParsingLoader() {
}
/** @ignore */
ParsingLoader.extension = core.ExtensionType.Loader;
ParsingLoader.use = parsing;
return ParsingLoader;
}());
core.extensions.add(TextureLoader, ParsingLoader);
exports.AppLoaderPlugin = AppLoaderPlugin;
exports.Loader = Loader;
exports.TextureLoader = TextureLoader;
Object.defineProperty(exports, '__esModule', { value: true });
return exports;
})({}, PIXI, PIXI.utils);
Object.assign(this.PIXI, _pixi_loaders);
//# sourceMappingURL=loaders.js.map