/*!
 * resource-loader - v3.0.1
 * https://github.com/pixijs/pixi-sound
 * Compiled Tue, 02 Jul 2019 14:06:18 UTC
 *
 * resource-loader is licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license
 */
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }

var parseUri = _interopDefault(require('parse-uri'));
var Signal = _interopDefault(require('mini-signals'));

/**
 * Smaller version of the async library constructs.
 *
 * @namespace async
 */

/**
 * Noop function
 *
 * @ignore
 * @function
 * @memberof async
 */
function _noop() {}
/* empty */

/**
 * Iterates an array in series.
 *
 * @memberof async
 * @function eachSeries
 * @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.
 */


function eachSeries(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);
    }
  })();
}
/**
 * Ensures a function is only called once.
 *
 * @ignore
 * @memberof async
 * @param {function} fn - The function to wrap.
 * @return {function} The wrapping function.
 */

function onlyOnce(fn) {
  return function onceWrapper() {
    if (fn === null) {
      throw new Error('Callback was already called.');
    }

    var callFn = fn;
    fn = null;
    callFn.apply(this, arguments);
  };
}
/**
 * Async queue implementation,
 *
 * @memberof async
 * @function queue
 * @param {function} worker - The worker function to call for each task.
 * @param {number} concurrency - How many workers to run in parrallel.
 * @return {*} The async queue object.
 */


function queue(worker, concurrency) {
  if (concurrency == null) {
    // eslint-disable-line no-eq-null,eqeqeq
    concurrency = 1;
  } else if (concurrency === 0) {
    throw new Error('Concurrency must not be zero');
  }

  var workers = 0;
  var q = {
    _tasks: [],
    concurrency: concurrency,
    saturated: _noop,
    unsaturated: _noop,
    buffer: concurrency / 4,
    empty: _noop,
    drain: _noop,
    error: _noop,
    started: false,
    paused: false,
    push: function push(data, callback) {
      _insert(data, false, callback);
    },
    kill: function kill() {
      workers = 0;
      q.drain = _noop;
      q.started = false;
      q._tasks = [];
    },
    unshift: function unshift(data, callback) {
      _insert(data, true, callback);
    },
    process: function process() {
      while (!q.paused && workers < q.concurrency && q._tasks.length) {
        var task = q._tasks.shift();

        if (q._tasks.length === 0) {
          q.empty();
        }

        workers += 1;

        if (workers === q.concurrency) {
          q.saturated();
        }

        worker(task.data, onlyOnce(_next(task)));
      }
    },
    length: function length() {
      return q._tasks.length;
    },
    running: function running() {
      return workers;
    },
    idle: function idle() {
      return q._tasks.length + workers === 0;
    },
    pause: function pause() {
      if (q.paused === true) {
        return;
      }

      q.paused = true;
    },
    resume: function resume() {
      if (q.paused === false) {
        return;
      }

      q.paused = false; // Need to call q.process once per concurrent
      // worker to preserve full concurrency after pause

      for (var w = 1; w <= q.concurrency; w++) {
        q.process();
      }
    }
  };

  function _insert(data, insertAtFront, callback) {
    if (callback != null && typeof callback !== 'function') {
      // eslint-disable-line no-eq-null,eqeqeq
      throw new Error('task callback must be a function');
    }

    q.started = true;

    if (data == null && q.idle()) {
      // eslint-disable-line no-eq-null,eqeqeq
      // call drain immediately if there are no tasks
      setTimeout(function () {
        return q.drain();
      }, 1);
      return;
    }

    var item = {
      data: data,
      callback: typeof callback === 'function' ? callback : _noop
    };

    if (insertAtFront) {
      q._tasks.unshift(item);
    } else {
      q._tasks.push(item);
    }

    setTimeout(function () {
      return q.process();
    }, 1);
  }

  function _next(task) {
    return function next() {
      workers -= 1;
      task.callback.apply(task, arguments);

      if (arguments[0] != null) {
        // eslint-disable-line no-eq-null,eqeqeq
        q.error(arguments[0], task.data);
      }

      if (workers <= q.concurrency - q.buffer) {
        q.unsaturated();
      }

      if (q.idle()) {
        q.drain();
      }

      q.process();
    };
  }

  return q;
}

var async = ({
    eachSeries: eachSeries,
    queue: queue
});

// a simple in-memory cache for resources
var cache = {};
/**
 * A simple in-memory cache for resource.
 *
 * @memberof middleware
 * @function caching
 * @example
 * import { Loader, middleware } from 'resource-loader';
 * const loader = new Loader();
 * loader.use(middleware.caching);
 * @param {Resource} resource - Current Resource
 * @param {function} next - Callback when complete
 */

function caching(resource, next) {
  var _this = this;

  // 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 () {
        return cache[_this.url] = _this.data;
      });
    }

  next();
}

function _defineProperties(target, props) {
  for (var i = 0; i < props.length; i++) {
    var descriptor = props[i];
    descriptor.enumerable = descriptor.enumerable || false;
    descriptor.configurable = true;
    if ("value" in descriptor) descriptor.writable = true;
    Object.defineProperty(target, descriptor.key, descriptor);
  }
}

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  return Constructor;
}

var useXdr = !!(window.XDomainRequest && !('withCredentials' in new XMLHttpRequest()));
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() {}
/* empty */

/**
 * Manages the state and loading of a resource and all child resources.
 *
 * @class
 */


var Resource =
/*#__PURE__*/
function () {
  /**
   * 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 {Resource.LOAD_TYPE} loadType - The load type to set it to.
   */
  Resource.setExtensionLoadType = function setExtensionLoadType(extname, loadType) {
    setExtMap(Resource._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 {Resource.XHR_RESPONSE_TYPE} xhrType - The xhr type to set it to.
   */
  ;

  Resource.setExtensionXhrType = function setExtensionXhrType(extname, xhrType) {
    setExtMap(Resource._xhrTypeMap, extname, xhrType);
  }
  /**
   * @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 {Resource.LOAD_TYPE} [options.loadType=Resource.LOAD_TYPE.XHR] - How should this resource
   *      be loaded?
   * @param {Resource.XHR_RESPONSE_TYPE} [options.xhrType=Resource.XHR_RESPONSE_TYPE.DEFAULT] - How
   *      should the data being loaded be interpreted when using XHR?
   * @param {Resource.IMetadata} [options.metadata] - Extra configuration for middleware and the Resource object.
   */
  ;

  function Resource(name, url, options) {
    if (typeof name !== 'string' || typeof url !== 'string') {
      throw new Error('Both name and url are required for constructing a resource.');
    }

    options = options || {};
    /**
     * The state flags of this resource.
     *
     * @private
     * @member {number}
     */

    this._flags = 0; // set data url flag, needs to be set early for some _determineX checks to work.

    this._setFlag(Resource.STATUS_FLAGS.DATA_URL, url.indexOf('data:') === 0);
    /**
     * The name of this resource.
     *
     * @readonly
     * @member {string}
     */


    this.name = name;
    /**
     * The url used to load this resource.
     *
     * @readonly
     * @member {string}
     */

    this.url = url;
    /**
     * The extension used to load this resource.
     *
     * @readonly
     * @member {string}
     */

    this.extension = this._getExtension();
    /**
     * The data that was loaded by the resource.
     *
     * @member {any}
     */

    this.data = null;
    /**
     * Is this request cross-origin? If unset, determined automatically.
     *
     * @member {string}
     */

    this.crossOrigin = options.crossOrigin === true ? 'anonymous' : options.crossOrigin;
    /**
     * 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.
     *
     * @member {number}
     */

    this.timeout = options.timeout || 0;
    /**
     * The method of loading to use for this resource.
     *
     * @member {Resource.LOAD_TYPE}
     */

    this.loadType = options.loadType || this._determineLoadType();
    /**
     * The type used to load the resource via XHR. If unset, determined automatically.
     *
     * @member {string}
     */

    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.
     *
     * @member {Resource.IMetadata}
     */

    this.metadata = options.metadata || {};
    /**
     * The error that occurred while loading (if any).
     *
     * @readonly
     * @member {Error}
     */

    this.error = null;
    /**
     * The XHR object that was used to load this resource. This is only set
     * when `loadType` is `Resource.LOAD_TYPE.XHR`.
     *
     * @readonly
     * @member {XMLHttpRequest}
     */

    this.xhr = null;
    /**
     * The child resources this resource owns.
     *
     * @readonly
     * @member {Resource[]}
     */

    this.children = [];
    /**
     * The resource type.
     *
     * @readonly
     * @member {Resource.TYPE}
     */

    this.type = Resource.TYPE.UNKNOWN;
    /**
     * The progress chunk owned by this resource.
     *
     * @readonly
     * @member {number}
     */

    this.progressChunk = 0;
    /**
     * 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
     * @member {number}
     */

    this._elementTimer = 0;
    /**
     * The `complete` function bound to this resource's context.
     *
     * @private
     * @member {function}
     */

    this._boundComplete = this.complete.bind(this);
    /**
     * The `_onError` function bound to this resource's context.
     *
     * @private
     * @member {function}
     */

    this._boundOnError = this._onError.bind(this);
    /**
     * The `_onProgress` function bound to this resource's context.
     *
     * @private
     * @member {function}
     */

    this._boundOnProgress = this._onProgress.bind(this);
    /**
     * The `_onTimeout` function bound to this resource's context.
     *
     * @private
     * @member {function}
     */

    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.
     *
     * The callback looks like {@link Resource.OnStartSignal}.
     *
     * @member {Signal<Resource.OnStartSignal>}
     */

    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.
     *
     * The callback looks like {@link Resource.OnProgressSignal}.
     *
     * @member {Signal<Resource.OnProgressSignal>}
     */

    this.onProgress = new Signal();
    /**
     * Dispatched once this resource has loaded, if there was an error it will
     * be in the `error` property.
     *
     * The callback looks like {@link Resource.OnCompleteSignal}.
     *
     * @member {Signal<Resource.OnCompleteSignal>}
     */

    this.onComplete = new Signal();
    /**
     * Dispatched after this resource has had all the *after* middleware run on it.
     *
     * The callback looks like {@link Resource.OnCompleteSignal}.
     *
     * @member {Signal<Resource.OnCompleteSignal>}
     */

    this.onAfterMiddleware = new Signal();
  }
  /**
   * When the resource starts to load.
   *
   * @memberof Resource
   * @callback OnStartSignal
   * @param {Resource} resource - The resource that the event happened on.
   */

  /**
   * When the resource reports loading progress.
   *
   * @memberof Resource
   * @callback OnProgressSignal
   * @param {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 Resource
   * @callback OnCompleteSignal
   * @param {Resource} resource - The resource that the event happened on.
   */

  /**
   * @memberof Resource
   * @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}
   */


  var _proto = Resource.prototype;

  /**
   * Marks the resource as complete.
   *
   */
  _proto.complete = function complete() {
    this._clearEvents();

    this._finish();
  }
  /**
   * Aborts the loading of this resource, with an optional message.
   *
   * @param {string} message - The message to use for the error
   */
  ;

  _proto.abort = function abort(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 = Resource.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 {Resource.OnCompleteSignal} [cb] - Optional callback to call once the resource is loaded.
   */
  ;

  _proto.load = function load(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(Resource.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 Resource.LOAD_TYPE.IMAGE:
        this.type = Resource.TYPE.IMAGE;

        this._loadElement('image');

        break;

      case Resource.LOAD_TYPE.AUDIO:
        this.type = Resource.TYPE.AUDIO;

        this._loadSourceElement('audio');

        break;

      case Resource.LOAD_TYPE.VIDEO:
        this.type = Resource.TYPE.VIDEO;

        this._loadSourceElement('video');

        break;

      case Resource.LOAD_TYPE.XHR:
      /* falls through */

      default:
        if (useXdr && this.crossOrigin) {
          this._loadXdr();
        } else {
          this._loadXhr();
        }

        break;
    }
  }
  /**
   * Checks if the flag is set.
   *
   * @private
   * @param {number} flag - The flag to check.
   * @return {boolean} True if the flag is set.
   */
  ;

  _proto._hasFlag = function _hasFlag(flag) {
    return (this._flags & flag) !== 0;
  }
  /**
   * (Un)Sets the flag.
   *
   * @private
   * @param {number} flag - The flag to (un)set.
   * @param {boolean} value - Whether to set or (un)set the flag.
   */
  ;

  _proto._setFlag = function _setFlag(flag, value) {
    this._flags = value ? this._flags | flag : this._flags & ~flag;
  }
  /**
   * Clears all the events from the underlying loading source.
   *
   * @private
   */
  ;

  _proto._clearEvents = function _clearEvents() {
    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.
   *
   * @private
   */
  ;

  _proto._finish = function _finish() {
    if (this.isComplete) {
      throw new Error('Complete called again for an already completed resource.');
    }

    this._setFlag(Resource.STATUS_FLAGS.COMPLETE, true);

    this._setFlag(Resource.STATUS_FLAGS.LOADING, false);

    this.onComplete.dispatch(this);
  }
  /**
   * Loads this resources using an element that has a single source,
   * like an HTMLImageElement.
   *
   * @private
   * @param {string} type - The type of element to use.
   */
  ;

  _proto._loadElement = function _loadElement(type) {
    if (this.metadata.loadElement) {
      this.data = this.metadata.loadElement;
    } else if (type === 'image' && typeof window.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.
   *
   * @private
   * @param {string} type - The type of element to use.
   */
  ;

  _proto._loadSourceElement = function _loadSourceElement(type) {
    if (this.metadata.loadElement) {
      this.data = this.metadata.loadElement;
    } else if (type === 'audio' && typeof window.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.
   *
   * @private
   */
  ;

  _proto._loadXhr = function _loadXhr() {
    // if unset, determine the value
    if (typeof this.xhrType !== 'string') {
      this.xhrType = this._determineXhrType();
    }

    var xhr = this.xhr = new XMLHttpRequest(); // 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 === Resource.XHR_RESPONSE_TYPE.JSON || this.xhrType === Resource.XHR_RESPONSE_TYPE.DOCUMENT) {
      xhr.responseType = Resource.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).
   *
   * @private
   */
  ;

  _proto._loadXdr = function _loadXdr() {
    // if unset, determine the value
    if (typeof this.xhrType !== 'string') {
      this.xhrType = this._determineXhrType();
    }

    var xdr = this.xhr = new 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.
   *
   * @private
   * @param {string} type - The element type (video or audio).
   * @param {string} url - The source URL to load from.
   * @param {string} [mime] - The mime type of the video
   * @return {HTMLSourceElement} The source element.
   */
  ;

  _proto._createSource = function _createSource(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} event - The error event from the element that emits it.
   * @private
   */
  ;

  _proto._onError = function _onError(event) {
    this.abort("Failed to load element using: " + event.target.nodeName);
  }
  /**
   * Called if a load progress event fires for an element or xhr/xdr.
   *
   * @private
   * @param {XMLHttpRequestProgressEvent|Event} event - Progress event.
   */
  ;

  _proto._onProgress = function _onProgress(event) {
    if (event && event.lengthComputable) {
      this.onProgress.dispatch(this, event.loaded / event.total);
    }
  }
  /**
   * Called if a timeout event fires for an element.
   *
   * @private
   */
  ;

  _proto._onTimeout = function _onTimeout() {
    this.abort("Load timed out.");
  }
  /**
   * Called if an error event fires for xhr/xdr.
   *
   * @private
   */
  ;

  _proto._xhrOnError = function _xhrOnError() {
    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.
   *
   * @private
   */
  ;

  _proto._xhrOnTimeout = function _xhrOnTimeout() {
    var xhr = this.xhr;
    this.abort(reqType(xhr) + " Request timed out.");
  }
  /**
   * Called if an abort event fires for xhr/xdr.
   *
   * @private
   */
  ;

  _proto._xhrOnAbort = function _xhrOnAbort() {
    var xhr = this.xhr;
    this.abort(reqType(xhr) + " Request was aborted by the user.");
  }
  /**
   * Called when data successfully loads from an xhr/xdr request.
   *
   * @private
   * @param {XMLHttpRequestLoadEvent|Event} event - Load event
   */
  ;

  _proto._xhrOnLoad = function _xhrOnLoad() {
    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 === Resource.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 === Resource.XHR_RESPONSE_TYPE.TEXT) {
        this.data = text;
        this.type = Resource.TYPE.TEXT;
      } // if json, parse into json object
      else if (this.xhrType === Resource.XHR_RESPONSE_TYPE.JSON) {
          try {
            this.data = JSON.parse(text);
            this.type = Resource.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 === Resource.XHR_RESPONSE_TYPE.DOCUMENT) {
            try {
              if (window.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 = Resource.TYPE.XML;
            } catch (e) {
              this.abort("Error trying to parse loaded xml: " + e);
              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 {string} url - The url to test.
   * @param {object} [loc=window.location] - The location object to test against.
   * @return {string} The crossOrigin value to use (or empty string for none).
   */
  ;

  _proto._determineCrossOrigin = function _determineCrossOrigin(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 window.location.origin, and will always require
    // crossOrigin requests regardless of whether the location matches.


    if (window.origin !== window.location.origin) {
      return 'anonymous';
    } // default is window.location


    loc = loc || window.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;
    url = parseUri(tempAnchor.href, {
      strictMode: true
    });
    var samePort = !url.port && loc.port === '' || url.port === loc.port;
    var protocol = url.protocol ? url.protocol + ":" : ''; // if cross origin

    if (url.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
   * @return {Resource.XHR_RESPONSE_TYPE} The responseType to use.
   */
  ;

  _proto._determineXhrType = function _determineXhrType() {
    return Resource._xhrTypeMap[this.extension] || Resource.XHR_RESPONSE_TYPE.TEXT;
  }
  /**
   * Determines the loadType of a resource based on the extension of the
   * resource being loaded.
   *
   * @private
   * @return {Resource.LOAD_TYPE} The loadType to use.
   */
  ;

  _proto._determineLoadType = function _determineLoadType() {
    return Resource._loadTypeMap[this.extension] || Resource.LOAD_TYPE.XHR;
  }
  /**
   * Extracts the extension (sans '.') of the file being loaded by the resource.
   *
   * @private
   * @return {string} The extension.
   */
  ;

  _proto._getExtension = function _getExtension() {
    var 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.
   *
   * @private
   * @param {Resource.XHR_RESPONSE_TYPE} type - The type to get a mime type for.
   * @return {string} The mime type to use.
   */
  ;

  _proto._getMimeFromXhrType = function _getMimeFromXhrType(type) {
    switch (type) {
      case Resource.XHR_RESPONSE_TYPE.BUFFER:
        return 'application/octet-binary';

      case Resource.XHR_RESPONSE_TYPE.BLOB:
        return 'application/blob';

      case Resource.XHR_RESPONSE_TYPE.DOCUMENT:
        return 'application/xml';

      case Resource.XHR_RESPONSE_TYPE.JSON:
        return 'application/json';

      case Resource.XHR_RESPONSE_TYPE.DEFAULT:
      case Resource.XHR_RESPONSE_TYPE.TEXT:
      /* falls through */

      default:
        return 'text/plain';
    }
  };

  _createClass(Resource, [{
    key: "isDataUrl",
    get: function get() {
      return this._hasFlag(Resource.STATUS_FLAGS.DATA_URL);
    }
    /**
     * Describes if this resource has finished loading. Is true when the resource has completely
     * loaded.
     *
     * @readonly
     * @member {boolean}
     */

  }, {
    key: "isComplete",
    get: function get() {
      return this._hasFlag(Resource.STATUS_FLAGS.COMPLETE);
    }
    /**
     * Describes if this resource is currently loading. Is true when the resource starts loading,
     * and is false again when complete.
     *
     * @readonly
     * @member {boolean}
     */

  }, {
    key: "isLoading",
    get: function get() {
      return this._hasFlag(Resource.STATUS_FLAGS.LOADING);
    }
  }]);

  return Resource;
}();
/**
 * The types of resources a resource could represent.
 *
 * @static
 * @readonly
 * @enum {number}
 */


Resource.STATUS_FLAGS = {
  NONE: 0,
  DATA_URL: 1 << 0,
  COMPLETE: 1 << 1,
  LOADING: 1 << 2
};
/**
 * The types of resources a resource could represent.
 *
 * @static
 * @readonly
 * @enum {number}
 */

Resource.TYPE = {
  UNKNOWN: 0,
  JSON: 1,
  XML: 2,
  IMAGE: 3,
  AUDIO: 4,
  VIDEO: 5,
  TEXT: 6
};
/**
 * The types of loading a resource can use.
 *
 * @static
 * @readonly
 * @enum {number}
 */

Resource.LOAD_TYPE = {
  /** Uses XMLHttpRequest to load the resource. */
  XHR: 1,

  /** Uses an `Image` object to load the resource. */
  IMAGE: 2,

  /** Uses an `Audio` object to load the resource. */
  AUDIO: 3,

  /** Uses a `Video` object to load the resource. */
  VIDEO: 4
};
/**
 * The XHR ready states, used internally.
 *
 * @static
 * @readonly
 * @enum {string}
 */

Resource.XHR_RESPONSE_TYPE = {
  /** string */
  DEFAULT: 'text',

  /** ArrayBuffer */
  BUFFER: 'arraybuffer',

  /** Blob */
  BLOB: 'blob',

  /** Document */
  DOCUMENT: 'document',

  /** Object */
  JSON: 'json',

  /** String */
  TEXT: 'text'
};
Resource._loadTypeMap = {
  // images
  gif: Resource.LOAD_TYPE.IMAGE,
  png: Resource.LOAD_TYPE.IMAGE,
  bmp: Resource.LOAD_TYPE.IMAGE,
  jpg: Resource.LOAD_TYPE.IMAGE,
  jpeg: Resource.LOAD_TYPE.IMAGE,
  tif: Resource.LOAD_TYPE.IMAGE,
  tiff: Resource.LOAD_TYPE.IMAGE,
  webp: Resource.LOAD_TYPE.IMAGE,
  tga: Resource.LOAD_TYPE.IMAGE,
  svg: Resource.LOAD_TYPE.IMAGE,
  'svg+xml': Resource.LOAD_TYPE.IMAGE,
  // for SVG data urls
  // audio
  mp3: Resource.LOAD_TYPE.AUDIO,
  ogg: Resource.LOAD_TYPE.AUDIO,
  wav: Resource.LOAD_TYPE.AUDIO,
  // videos
  mp4: Resource.LOAD_TYPE.VIDEO,
  webm: Resource.LOAD_TYPE.VIDEO
};
Resource._xhrTypeMap = {
  // xml
  xhtml: Resource.XHR_RESPONSE_TYPE.DOCUMENT,
  html: Resource.XHR_RESPONSE_TYPE.DOCUMENT,
  htm: Resource.XHR_RESPONSE_TYPE.DOCUMENT,
  xml: Resource.XHR_RESPONSE_TYPE.DOCUMENT,
  tmx: Resource.XHR_RESPONSE_TYPE.DOCUMENT,
  svg: Resource.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: Resource.XHR_RESPONSE_TYPE.DOCUMENT,
  // images
  gif: Resource.XHR_RESPONSE_TYPE.BLOB,
  png: Resource.XHR_RESPONSE_TYPE.BLOB,
  bmp: Resource.XHR_RESPONSE_TYPE.BLOB,
  jpg: Resource.XHR_RESPONSE_TYPE.BLOB,
  jpeg: Resource.XHR_RESPONSE_TYPE.BLOB,
  tif: Resource.XHR_RESPONSE_TYPE.BLOB,
  tiff: Resource.XHR_RESPONSE_TYPE.BLOB,
  webp: Resource.XHR_RESPONSE_TYPE.BLOB,
  tga: Resource.XHR_RESPONSE_TYPE.BLOB,
  // json
  json: Resource.XHR_RESPONSE_TYPE.JSON,
  // text
  text: Resource.XHR_RESPONSE_TYPE.TEXT,
  txt: Resource.XHR_RESPONSE_TYPE.TEXT,
  // fonts
  ttf: Resource.XHR_RESPONSE_TYPE.BUFFER,
  otf: Resource.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

Resource.EMPTY_GIF = 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==';
/**
 * 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 {object} map - The map to set on.
 * @param {string} extname - The extension (or key) to set.
 * @param {number} 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 {XMLHttpRequest|XDomainRequest} xhr - The request to check.
 * @return {string} The type.
 */


function reqType(xhr) {
  return xhr.toString().replace('object ', '');
}

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;
}

var Url = window.URL || window.webkitURL;
/**
 * A middleware for transforming XHR loaded Blobs into more useful objects
 *
 * @memberof middleware
 * @function parsing
 * @example
 * import { Loader, middleware } from 'resource-loader';
 * const loader = new Loader();
 * loader.use(middleware.parsing);
 * @param {Resource} resource - Current Resource
 * @param {function} 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 === Resource.XHR_RESPONSE_TYPE.BLOB) {
    // if there is no blob support we probably got a binary string back
    if (!window.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 = Resource.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 src = Url.createObjectURL(resource.data);
        resource.blob = resource.data;
        resource.data = new Image();
        resource.data.src = src;
        resource.type = Resource.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.revokeObjectURL(src);
          resource.data.onload = null;
          next();
        }; // next will be called on load.


        return;
      }
  }

  next();
}

/**
 * @namespace middleware
 */

var index = ({
    caching: caching,
    parsing: parsing
});

var MAX_PROGRESS = 100;
var rgxExtractUrlHash = /(#[\w-]+)?$/;
/**
 * Manages the state and loading of multiple resources to load.
 *
 * @class
 */

var Loader =
/*#__PURE__*/
function () {
  /**
   * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader.
   * @param {number} [concurrency=10] - 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 base url for all resources loaded by this loader.
     *
     * @member {string}
     */
    this.baseUrl = baseUrl;
    /**
     * The progress percent of the loader going through the queue.
     *
     * @member {number}
     * @default 0
     */

    this.progress = 0;
    /**
     * Loading state of the loader, true if it is currently loading resources.
     *
     * @member {boolean}
     * @default false
     */

    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();
     *
     * @member {string}
     * @default ''
     */

    this.defaultQueryString = '';
    /**
     * The middleware to run before loading each resource.
     *
     * @private
     * @member {function[]}
     */

    this._beforeMiddleware = [];
    /**
     * The middleware to run after loading each resource.
     *
     * @private
     * @member {function[]}
     */

    this._afterMiddleware = [];
    /**
     * The tracks the resources we are currently completing parsing for.
     *
     * @private
     * @member {Resource[]}
     */

    this._resourcesParsing = [];
    /**
     * The `_loadResource` function bound with this object context.
     *
     * @private
     * @member {function}
     * @param {Resource} r - The resource to load
     * @param {Function} d - The dequeue function
     * @return {undefined}
     */

    this._boundLoadResource = function (r, d) {
      return _this._loadResource(r, d);
    };
    /**
     * The resources waiting to be loaded.
     *
     * @private
     * @member {Resource[]}
     */


    this._queue = queue(this._boundLoadResource, concurrency);

    this._queue.pause();
    /**
     * All the resources for this loader keyed by name.
     *
     * @member {object<string, Resource>}
     */


    this.resources = {};
    /**
     * Dispatched once per loaded or errored resource.
     *
     * The callback looks like {@link Loader.OnProgressSignal}.
     *
     * @member {Signal<Loader.OnProgressSignal>}
     */

    this.onProgress = new Signal();
    /**
     * Dispatched once per errored resource.
     *
     * The callback looks like {@link Loader.OnErrorSignal}.
     *
     * @member {Signal<Loader.OnErrorSignal>}
     */

    this.onError = new Signal();
    /**
     * Dispatched once per loaded resource.
     *
     * The callback looks like {@link Loader.OnLoadSignal}.
     *
     * @member {Signal<Loader.OnLoadSignal>}
     */

    this.onLoad = new Signal();
    /**
     * Dispatched when the loader begins to process the queue.
     *
     * The callback looks like {@link Loader.OnStartSignal}.
     *
     * @member {Signal<Loader.OnStartSignal>}
     */

    this.onStart = new Signal();
    /**
     * Dispatched when the queued resources all load.
     *
     * The callback looks like {@link Loader.OnCompleteSignal}.
     *
     * @member {Signal<Loader.OnCompleteSignal>}
     */

    this.onComplete = new Signal(); // Add default before middleware

    for (var i = 0; i < Loader._defaultBeforeMiddleware.length; ++i) {
      this.pre(Loader._defaultBeforeMiddleware[i]);
    } // Add default after middleware


    for (var _i = 0; _i < Loader._defaultAfterMiddleware.length; ++_i) {
      this.use(Loader._defaultAfterMiddleware[_i]);
    }
  }
  /**
   * When the progress changes the loader and resource are disaptched.
   *
   * @memberof Loader
   * @callback OnProgressSignal
   * @param {Loader} loader - The loader the progress is advancing on.
   * @param {Resource} resource - The resource that has completed or failed to cause the progress to advance.
   */

  /**
   * When an error occurrs the loader and resource are disaptched.
   *
   * @memberof Loader
   * @callback OnErrorSignal
   * @param {Loader} loader - The loader the error happened in.
   * @param {Resource} resource - The resource that caused the error.
   */

  /**
   * When a load completes the loader and resource are disaptched.
   *
   * @memberof Loader
   * @callback OnLoadSignal
   * @param {Loader} loader - The loader that laoded the resource.
   * @param {Resource} resource - The resource that has completed loading.
   */

  /**
   * When the loader starts loading resources it dispatches this callback.
   *
   * @memberof Loader
   * @callback OnStartSignal
   * @param {Loader} loader - The loader that has started loading resources.
   */

  /**
   * When the loader completes loading resources it dispatches this callback.
   *
   * @memberof Loader
   * @callback OnCompleteSignal
   * @param {Loader} loader - The loader that has finished loading resources.
   */

  /**
   * Options for a call to `.add()`.
   *
   * @see Loader#add
   *
   * @typedef {object} IAddOptions
   * @property {string} [name] - The name of the resource to load, if not passed the url is used.
   * @property {string} [key] - Alias for `name`.
   * @property {string} [url] - The url for this resource, relative to the baseUrl of this loader.
   * @property {string|boolean} [crossOrigin] - Is this request cross-origin? Default is to
   *      determine automatically.
   * @property {number} [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.
   * @property {Resource.LOAD_TYPE} [loadType=Resource.LOAD_TYPE.XHR] - How should this resource
   *      be loaded?
   * @property {Resource.XHR_RESPONSE_TYPE} [xhrType=Resource.XHR_RESPONSE_TYPE.DEFAULT] - How
   *      should the data being loaded be interpreted when using XHR?
   * @property {Resource.OnCompleteSignal} [onComplete] - Callback to add an an onComplete signal istener.
   * @property {Resource.OnCompleteSignal} [callback] - Alias for `onComplete`.
   * @property {Resource.IMetadata} [metadata] - Extra configuration for middleware and the Resource object.
   */

  /* eslint-disable require-jsdoc,valid-jsdoc */

  /**
   * Adds a resource (or multiple resources) to the loader queue.
   *
   * This function can take a wide variety of different parameters. The only thing that is always
   * required the url to load. All the following will work:
   *
   * ```js
   * loader
   *     // normal param syntax
   *     .add('key', 'http://...', function () {})
   *     .add('http://...', function () {})
   *     .add('http://...')
   *
   *     // object syntax
   *     .add({
   *         name: 'key2',
   *         url: 'http://...'
   *     }, function () {})
   *     .add({
   *         url: 'http://...'
   *     }, function () {})
   *     .add({
   *         name: 'key3',
   *         url: 'http://...'
   *         onComplete: function () {}
   *     })
   *     .add({
   *         url: 'https://...',
   *         onComplete: function () {},
   *         crossOrigin: true
   *     })
   *
   *     // you can also pass an array of objects or urls or both
   *     .add([
   *         { name: 'key4', url: 'http://...', onComplete: function () {} },
   *         { url: 'http://...', onComplete: function () {} },
   *         'http://...'
   *     ])
   *
   *     // and you can use both params and options
   *     .add('key', 'http://...', { crossOrigin: true }, function () {})
   *     .add('http://...', { crossOrigin: true }, function () {});
   * ```
   *
   * @function
   * @variation 1
   * @param {string} name - The name of the resource to load.
   * @param {string} url - The url for this resource, relative to the baseUrl of this loader.
   * @param {Resource.OnCompleteSignal} [callback] - Function to call when this specific resource completes loading.
   * @return {this} Returns itself.
   */

  /**
  * @function
  * @variation 2
  * @param {string} name - The name of the resource to load.
  * @param {string} url - The url for this resource, relative to the baseUrl of this loader.
  * @param {IAddOptions} [options] - The options for the load.
  * @param {Resource.OnCompleteSignal} [callback] - Function to call when this specific resource completes loading.
  * @return {this} Returns itself.
  */

  /**
  * @function
  * @variation 3
  * @param {string} url - The url for this resource, relative to the baseUrl of this loader.
  * @param {Resource.OnCompleteSignal} [callback] - Function to call when this specific resource completes loading.
  * @return {this} Returns itself.
  */

  /**
  * @function
  * @variation 4
  * @param {string} url - The url for this resource, relative to the baseUrl of this loader.
  * @param {IAddOptions} [options] - The options for the load.
  * @param {Resource.OnCompleteSignal} [callback] - Function to call when this specific resource completes loading.
  * @return {this} Returns itself.
  */

  /**
  * @function
  * @variation 5
  * @param {IAddOptions} options - The options for the load. This object must contain a `url` property.
  * @param {Resource.OnCompleteSignal} [callback] - Function to call when this specific resource completes loading.
  * @return {this} Returns itself.
  */

  /**
  * @function
  * @variation 6
  * @param {Array<IAddOptions|string>} resources - An array of resources to load, where each is
  *      either an object with the options or a string url. If you pass an object, it must contain a `url` property.
  * @param {Resource.OnCompleteSignal} [callback] - Function to call when this specific resource completes loading.
  * @return {this} Returns itself.
  */


  var _proto = Loader.prototype;

  _proto.add = function add(name, url, options, cb) {
    // 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') {
      cb = url || name.callback || name.onComplete;
      options = name;
      url = name.url;
      name = name.name || name.key || name.url;
    } // case where no name is passed shift all args over by one.


    if (typeof url !== 'string') {
      cb = 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') {
      cb = options;
      options = null;
    } // 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 Resource(name, url, options);

    if (typeof cb === 'function') {
      this.resources[name].onAfterMiddleware.once(cb);
    } // 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 _i2 = 0; _i2 < parent.children.length; ++_i2) {
        if (!parent.children[_i2].isComplete) {
          incompleteChildren.push(parent.children[_i2]);
        }
      }

      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 _i3 = 0; _i3 < incompleteChildren.length; ++_i3) {
        incompleteChildren[_i3].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 {function} fn - The middleware function to register.
   * @return {this} Returns itself.
   */
  ;

  _proto.pre = function pre(fn) {
    this._beforeMiddleware.push(fn);

    return this;
  }
  /**
   * Sets up a middleware function that will run *after* the
   * resource is loaded.
   *
   * @param {function} fn - The middleware function to register.
   * @return {this} Returns itself.
   */
  ;

  _proto.use = function use(fn) {
    this._afterMiddleware.push(fn);

    return this;
  }
  /**
   * Resets the queue of the loader to prepare for a new load.
   *
   * @return {this} Returns itself.
   */
  ;

  _proto.reset = function reset() {
    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();
      }
    }

    this.resources = {};
    return this;
  }
  /**
   * Starts loading the queued resources.
   *
   * @param {function} [cb] - Optional callback that will be bound to the `complete` event.
   * @return {this} Returns itself.
   */
  ;

  _proto.load = function load(cb) {
    // 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;
  }
  /**
   * The number of resources to load concurrently.
   *
   * @member {number}
   * @default 10
   */
  ;

  /**
   * Prepares a url for usage based on the configuration of this object
   *
   * @private
   * @param {string} url - The url to prepare.
   * @return {string} The prepared url.
   */
  _proto._prepareUrl = function _prepareUrl(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.substr(0, result.length - hash.length);

      if (result.indexOf('?') !== -1) {
        result += "&" + this.defaultQueryString;
      } else {
        result += "?" + this.defaultQueryString;
      }

      result += hash;
    }

    return result;
  }
  /**
   * Loads a single resource.
   *
   * @private
   * @param {Resource} resource - The resource to load.
   * @param {function} dequeue - The function to call when we need to dequeue this item.
   */
  ;

  _proto._loadResource = function _loadResource(resource, dequeue) {
    var _this2 = this;

    resource._dequeue = dequeue; // run before middleware

    eachSeries(this._beforeMiddleware, function (fn, next) {
      fn.call(_this2, 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) {
        _this2._onLoad(resource);
      } else {
        resource._onLoadBinding = resource.onComplete.once(_this2._onLoad, _this2);
        resource.load();
      }
    }, true);
  }
  /**
   * Called once loading has started.
   *
   * @private
   */
  ;

  _proto._onStart = function _onStart() {
    this.progress = 0;
    this.loading = true;
    this.onStart.dispatch(this);
  }
  /**
   * Called once each resource has loaded.
   *
   * @private
   */
  ;

  _proto._onComplete = function _onComplete() {
    this.progress = MAX_PROGRESS;
    this.loading = false;
    this.onComplete.dispatch(this, this.resources);
  }
  /**
   * Called each time a resources is loaded.
   *
   * @private
   * @param {Resource} resource - The resource that was loaded
   */
  ;

  _proto._onLoad = function _onLoad(resource) {
    var _this3 = 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


    eachSeries(this._afterMiddleware, function (fn, next) {
      fn.call(_this3, resource, next);
    }, function () {
      resource.onAfterMiddleware.dispatch(resource);
      _this3.progress = Math.min(MAX_PROGRESS, _this3.progress + resource.progressChunk);

      _this3.onProgress.dispatch(_this3, resource);

      if (resource.error) {
        _this3.onError.dispatch(resource.error, _this3, resource);
      } else {
        _this3.onLoad.dispatch(_this3, resource);
      }

      _this3._resourcesParsing.splice(_this3._resourcesParsing.indexOf(resource), 1); // do completion check


      if (_this3._queue.idle() && _this3._resourcesParsing.length === 0) {
        _this3._onComplete();
      }
    }, true);
  };

  _createClass(Loader, [{
    key: "concurrency",
    get: function get() {
      return this._queue.concurrency;
    } // eslint-disable-next-line require-jsdoc
    ,
    set: function set(concurrency) {
      this._queue.concurrency = concurrency;
    }
  }]);

  return Loader;
}();
/**
 * A default array of middleware to run before loading each resource.
 * Each of these middlewares are added to any new Loader instances when they are created.
 *
 * @private
 * @member {function[]}
 */


Loader._defaultBeforeMiddleware = [];
/**
 * A default array of middleware to run after loading each resource.
 * Each of these middlewares are added to any new Loader instances when they are created.
 *
 * @private
 * @member {function[]}
 */

Loader._defaultAfterMiddleware = [];
/**
 * Sets up a middleware function that will run *before* the
 * resource is loaded.
 *
 * @static
 * @param {function} fn - The middleware function to register.
 * @return {Loader} Returns itself.
 */

Loader.pre = function LoaderPreStatic(fn) {
  Loader._defaultBeforeMiddleware.push(fn);

  return Loader;
};
/**
 * Sets up a middleware function that will run *after* the
 * resource is loaded.
 *
 * @static
 * @param {function} fn - The middleware function to register.
 * @return {Loader} Returns itself.
 */


Loader.use = function LoaderUseStatic(fn) {
  Loader._defaultAfterMiddleware.push(fn);

  return Loader;
};

exports.Loader = Loader;
exports.Resource = Resource;
exports.async = async;
exports.encodeBinary = encodeBinary;
exports.middleware = index;
//# sourceMappingURL=resource-loader.cjs.js.map
