import ResourceLoader from 'resource-loader';
import { blobMiddlewareFactory } from 'resource-loader/lib/middlewares/parsing/blob';
import EventEmitter from 'eventemitter3';
import * as utils from '../core/utils';
import { DATA_URI } from '../core/const';
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');
* }
* });
* ```
*
* @see https://github.com/englercj/resource-loader
*
* @class
* @extends module:resource-loader.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,
* })
*
* // 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 {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) {
// 解决 iOS < 10.2.1 的跨域报错
// 错误信息:Cross-origin image load denied by Cross-Origin Resource Sharing policy.
const crossOrigin = 'Anonymous';
if (utils.isObject(name)) {
name[ 'crossOrigin' ] = crossOrigin;
if (DATA_URI.test(name.url)) {
name[ 'crossOrigin' ] = false;
}
} else if (utils.isString(name)) {
utils.isObject(url) && (url[ 'crossOrigin' ] = crossOrigin);
utils.isObject(options) && (options[ 'crossOrigin' ] = crossOrigin);
if (DATA_URI.test(name) || DATA_URI.test(url)) {
utils.isObject(url) && (url[ 'crossOrigin' ] = false);
utils.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 (utils.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();
}
}
// Copy EE3 prototype (mixin)
for (const i in EventEmitter.prototype) {
Loader.prototype[ i ] = EventEmitter.prototype[ i ];
}
Loader._tinyMiddleware = [
// parse any blob into more usable objects (e.g. Image)
blobMiddlewareFactory,
// 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';