Source: tiny/loaders/Loader.js

import ResourceLoader from '../../libs/resource-loader';
import EventEmitter from 'eventemitter3';
import { DATA_URI, isObject, isString } from '../../utils';
import textureParser from './middlewares/textureParser';
import spritesheetParser from './middlewares/spritesheetParser';
import JSONObjectParser from './middlewares/JSONObjectParser';

/**
 *
 * 继承自 Resource Loader 的 Loader 类。
 *
 * ```js
 * const loader = new Tiny.loaders.Loader(); // 你也可以通过 `Loader` 类自己实例化一个
 * // 建议使用 Tiny.js 提供静态方法来获取 Loader 实例化对象
 * // const loader = Tiny.Loader;
 *
 * // 声明资源文件
 * var resources = [
 *   'https://zos.alipayobjects.com/rmsportal/nJBojwdMJfUqpCWvwyoA.png',
 *   'https://zos.alipayobjects.com/rmsportal/kkroUtIawGrWrqOLRmjq.jpg',
 *   'https://zos.alipayobjects.com/rmsportal/jkgwjYSFHRkorsKaZbng.jpeg',
 *   'https://zos.alipayobjects.com/rmsportal/HAacythTETlcsPxEePbk.webp',
 *   'https://os.alipayobjects.com/rmsportal/atrIuwPurrBiNEyWNdQA.ogg'
 * ];
 * //执行加载
 * loader.run({
 *   resources: resources,
 *   // 加载进度
 *   onProgress: function(per){
 *       console.log('percent:', per + '%');
 *   },
 *   // 单个资源加载完成后的回调
 *   onComplete: function(resourceLoader, resource){
 *       console.log(resource.url);
 *   },
 *   // 单个资源加载失败后的回调
 *   onError: function(errorMsg, resourceLoader, resource){
 *       console.log(errorMsg);
 *   },
 *   // 所有资源加载完成后的回调
 *   onAllComplete: function (resourceLoader, object) {
 *       console.log('all complete');
 *   }
 * });
 * ```
 *
 * @class
 * @extends Tiny.ResourceLoader
 * @memberof Tiny.loaders
 */
export default class Loader extends ResourceLoader {
  /**
   * @param {string} [baseUrl=''] - The base url for all resources loaded by this loader.
   * @param {number} [concurrency=10] - The number of resources to load concurrently.
   */
  constructor(baseUrl, concurrency) {
    super(baseUrl, concurrency);
    EventEmitter.call(this);

    for (let i = 0; i < Loader._tinyPreMiddleware.length; ++i) {
      this.pre(Loader._tinyPreMiddleware[i]());
    }

    for (let i = 0; i < Loader._tinyMiddleware.length; ++i) {
      this.use(Loader._tinyMiddleware[i]());
    }

    this.onStart.add((l) => this.emit('start', l));
    this.onProgress.add((l, r) => this.emit('progress', l, r));
    this.onError.add((e, l, r) => this.emit('error', e, l, r));
    this.onLoad.add((l, r) => this.emit('load', l, r));
    this.onComplete.add((l, r) => this.emit('complete', l, r));
  }

  /**
   * Adds a default middleware to the Tiny loader.
   *
   * @static
   * @param {function} fn - The middleware to add.
   */
  static addTinyMiddleware(fn) {
    Loader._tinyMiddleware.push(fn);
  }

  /**
   * Adds a default middleware pre the Tiny loader.
   *
   * @static
   * @version 1.1.7
   * @param {function} fn - The middleware to add pre.
   */
  static addTinyPreMiddleware(fn) {
    Loader._tinyPreMiddleware.push(fn);
  }

  /**
   * 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: 'key1'
   *         url: 'data:image/png;base64,iV...Jggg=='
   *     }, function () {})
   *     .add({
   *         url: 'http://...'
   *     }, function () {})
   *     .add({
   *         name: 'key3',
   *         url: 'http://...'
   *         onComplete: function () {}
   *     })
   *     .add({
   *         url: 'https://...',
   *         onComplete: function () {},
   *         crossOrigin: true
   *     })
   *
   *     // you can pass JSONObject
   *     .add({
   *         url: 'key4.json', // the name can be anything, but the extname must be .json
   *         metadata: {
   *           JSONObject: {"meta":{"image":"https://...","size":{"w":1730,"h":1158},"scale":"1"},"frames":{}},
   *           fallback: function () {}
   *         },
   *         xhrType: Tiny.loaders.Resource.XHR_RESPONSE_TYPE.JSONOBJECT,
   *     })
   *
   *     // version >= 1.3.0
   *     // you can pass useCompressedTexture=true to use compressed texture
   *     .add('./res/x-0.json', {
   *        metadata: { useCompressedTexture: true }
   *     })
   *
   *     // version >= 1.5.0
   *     // you can pass pixelFormat to use custom pixels
   *     .add('./res/xxx.png', {
   *        metadata: { pixelFormat: Tiny.TYPES.UNSIGNED_SHORT_4_4_4_4 }
   *     })
   *     // and pass imageMetadata with pixelFormat on json type
   *     .add('./res/x-0.json', {
   *        metadata: {
   *          imageMetadata: { pixelFormat: Tiny.TYPES.UNSIGNED_SHORT_4_4_4_4 }
   *        }
   *     })
   *
   *     // you can also pass an array of objects or urls or both
   *     .add([
   *         { name: 'key5', url: 'http://...', onComplete: function () {} },
   *         { url: 'http://...', onComplete: function () {} },
   *         'http://...',
   *         'data:image/png;base64,iV...Jggg=='
   *     ])
   *
   *     // and you can use both params and options
   *     .add('key6', 'http://...', { crossOrigin: true }, function () {})
   *     .add('http://...', { crossOrigin: true }, function () {});
   * ```
   *
   * @param {string} [name] - The name of the resource to load, if not passed the url is used.
   * @param {string} [url] - The url for this resource, relative to the baseUrl of this loader.
   * @param {object} [options] - The options for the load.
   * @param {boolean} [options.crossOrigin] - Is this request cross-origin? Default is to determine automatically.
   * @param {Tiny.loaders.Resource.LOAD_TYPE} [options.loadType=XHR] - How should this resource be loaded?「`XHR, IMAGE, AUDIO, VIDEO`」
   * @param {Tiny.loaders.Resource.XHR_RESPONSE_TYPE} [options.xhrType=DEFAULT] - How should the data being loaded be interpreted when using XH?「`BLOB, BUFFER, DEFAULT, DOCUMENT, JSON, JSONOBJECT, TEXT`」
   * @param {object} [options.metadata] - Extra configuration for middleware and the Resource object.
   * @param {boolean} [options.metadata.useCompressedTexture=false] - 是否使用压缩纹理,会自动按照加载的图片链接加载当前设备支持的纹理格式,替换 `.png` 为 `.astc.ktx` 或 `.pvr.ktx`
   * @param {Tiny.TYPES} [options.metadata.pixelFormat] - 是否使用特定格式的像素源作为纹理
   * @param {HTMLImageElement|HTMLAudioElement|HTMLVideoElement} [options.metadata.loadElement=null] - The element to use for loading, instead of creating one.
   * @param {boolean} [options.metadata.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.
   * @param {object} [options.metadata.JSONObject] - 加载的资源如果是一个 JSON 对象,将对象在这里传入,同时传入 `url` 的格式为:`*.json`,`xhrType` 的格式为:`JSONOBJECT`
   * @param {function} [options.metadata.fallback] - Spritesheet 解析识别时的兜底回调
   * @param {function} [cb] - Function to call when this specific resource completes loading.
   * @version 1.1.7
   * @return {Tiny.loaders.Loader} Returns itself.
   */
  add(name, url, options, cb) {
    // @version 1.1.7 @2018-04-12 解决 iOS < 10.2.1 的跨域报错(错误信息:Cross-origin image load denied by Cross-Origin Resource Sharing policy.)
    // @version 1.2.4 @2019-01-21 移除默认 crossOrigin 的添加
    if (isObject(name)) {
      if (DATA_URI.test(name.url)) {
        name['crossOrigin'] = false;
      }
    } else if (isString(name)) {
      if (DATA_URI.test(name) || DATA_URI.test(url)) {
        isObject(url) && (url['crossOrigin'] = false);
        isObject(options) && (options['crossOrigin'] = false);
      }
    }
    super.add(name, url, options, cb);
    return this;
  }

  /**
   * 执行加载
   *
   * @param {array} [opts.resources] - 资源集合
   * @param {function} [opts.onProgress] - 加载进度回调函数
   * @param {function} [opts.onComplete] - 单个资源加载完成回调函数
   * @param {function} [opts.onError] - 单个资源加载失败回调函数
   * @param {function} [opts.onAllComplete] - 所有资源加载完成回调函数
   */
  run(opts) {
    if (!opts) {
      opts = {};
    }
    const res = opts.resources;

    if (Array.isArray(res)) {
      this.add(res);
    } else {
      throw new Error('The param [resources] must be array');
    }

    //单个文件加载出错时调用
    const onError = function(error, resourceLoader, resource) {
      const errorMsg = `${error}, url: ${resource.url}`;
      (opts.onError || function() {
        throw errorMsg;
      })(errorMsg, resourceLoader, resource);
    };

    const onProgress = function(resourceLoader, resource) {
      (opts.onProgress || function() {})(Number(resourceLoader.progress.toFixed(2)), resource);
    };

    //单个资源文件加载完成调用
    const onComplete = function(resourceLoader, resource) {
      (opts.onComplete || function() {})(resourceLoader, resource);
    };

    const onAllComplete = function(resourceLoader, object) {
      (opts.onAllComplete || function() {})(resourceLoader, object);
    };

    this.on('load', onComplete);

    //单个文件加载失败
    this.on('error', onError);

    //全部加载完成
    this.on('complete', onAllComplete);

    this.on('progress', onProgress);

    this.load();
  }

  /**
   * Destroy the loader, removes references.
   *
   * @version 1.2.0
   */
  destroy() {
    this.removeAllListeners();
    this.reset();
  }
}

// Copy EE3 prototype (mixin)
for (const i in EventEmitter.prototype) {
  Loader.prototype[i] = EventEmitter.prototype[i];
}

Loader._tinyMiddleware = [
  // parse any Image objects into textures
  textureParser,
  // parse any spritesheet data into multiple textures
  spritesheetParser,
];

Loader._tinyPreMiddleware = [
  JSONObjectParser,
];

// Add custom extentions
const Resource = ResourceLoader.Resource;

Resource.setExtensionXhrType('fnt', Resource.XHR_RESPONSE_TYPE.DOCUMENT);
Resource.XHR_RESPONSE_TYPE.JSONOBJECT = 'JSONObject';
Documentation generated by JSDoc 3.4.3 on Fri Jul 09 2021 19:32:25 GMT+0800 (CST)