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