140 lines
3.4 KiB
JavaScript
140 lines
3.4 KiB
JavaScript
|
var Preloader = /** @constructor */ function() {
|
||
|
|
||
|
var DOWNLOAD_ATTEMPTS_MAX = 4;
|
||
|
var progressFunc = null;
|
||
|
var lastProgress = { loaded: 0, total: 0 };
|
||
|
|
||
|
var loadingFiles = {};
|
||
|
this.preloadedFiles = [];
|
||
|
|
||
|
function loadXHR(resolve, reject, file, tracker) {
|
||
|
var xhr = new XMLHttpRequest;
|
||
|
xhr.open('GET', file);
|
||
|
if (!file.endsWith('.js')) {
|
||
|
xhr.responseType = 'arraybuffer';
|
||
|
}
|
||
|
['loadstart', 'progress', 'load', 'error', 'abort'].forEach(function(ev) {
|
||
|
xhr.addEventListener(ev, onXHREvent.bind(xhr, resolve, reject, file, tracker));
|
||
|
});
|
||
|
xhr.send();
|
||
|
}
|
||
|
|
||
|
function onXHREvent(resolve, reject, file, tracker, ev) {
|
||
|
|
||
|
if (this.status >= 400) {
|
||
|
|
||
|
if (this.status < 500 || ++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
|
||
|
reject(new Error("Failed loading file '" + file + "': " + this.statusText));
|
||
|
this.abort();
|
||
|
return;
|
||
|
} else {
|
||
|
setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch (ev.type) {
|
||
|
case 'loadstart':
|
||
|
if (tracker[file] === undefined) {
|
||
|
tracker[file] = {
|
||
|
total: ev.total,
|
||
|
loaded: ev.loaded,
|
||
|
attempts: 0,
|
||
|
final: false,
|
||
|
};
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'progress':
|
||
|
tracker[file].loaded = ev.loaded;
|
||
|
tracker[file].total = ev.total;
|
||
|
break;
|
||
|
|
||
|
case 'load':
|
||
|
tracker[file].final = true;
|
||
|
resolve(this);
|
||
|
break;
|
||
|
|
||
|
case 'error':
|
||
|
if (++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
|
||
|
tracker[file].final = true;
|
||
|
reject(new Error("Failed loading file '" + file + "'"));
|
||
|
} else {
|
||
|
setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'abort':
|
||
|
tracker[file].final = true;
|
||
|
reject(new Error("Loading file '" + file + "' was aborted."));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.loadPromise = function(file) {
|
||
|
return new Promise(function(resolve, reject) {
|
||
|
loadXHR(resolve, reject, file, loadingFiles);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
this.preload = function(pathOrBuffer, destPath) {
|
||
|
if (pathOrBuffer instanceof ArrayBuffer) {
|
||
|
pathOrBuffer = new Uint8Array(pathOrBuffer);
|
||
|
} else if (ArrayBuffer.isView(pathOrBuffer)) {
|
||
|
pathOrBuffer = new Uint8Array(pathOrBuffer.buffer);
|
||
|
}
|
||
|
if (pathOrBuffer instanceof Uint8Array) {
|
||
|
this.preloadedFiles.push({
|
||
|
path: destPath,
|
||
|
buffer: pathOrBuffer
|
||
|
});
|
||
|
return Promise.resolve();
|
||
|
} else if (typeof pathOrBuffer === 'string') {
|
||
|
var me = this;
|
||
|
return this.loadPromise(pathOrBuffer).then(function(xhr) {
|
||
|
me.preloadedFiles.push({
|
||
|
path: destPath || pathOrBuffer,
|
||
|
buffer: xhr.response
|
||
|
});
|
||
|
return Promise.resolve();
|
||
|
});
|
||
|
} else {
|
||
|
throw Promise.reject("Invalid object for preloading");
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var animateProgress = function() {
|
||
|
|
||
|
var loaded = 0;
|
||
|
var total = 0;
|
||
|
var totalIsValid = true;
|
||
|
var progressIsFinal = true;
|
||
|
|
||
|
Object.keys(loadingFiles).forEach(function(file) {
|
||
|
const stat = loadingFiles[file];
|
||
|
if (!stat.final) {
|
||
|
progressIsFinal = false;
|
||
|
}
|
||
|
if (!totalIsValid || stat.total === 0) {
|
||
|
totalIsValid = false;
|
||
|
total = 0;
|
||
|
} else {
|
||
|
total += stat.total;
|
||
|
}
|
||
|
loaded += stat.loaded;
|
||
|
});
|
||
|
if (loaded !== lastProgress.loaded || total !== lastProgress.total) {
|
||
|
lastProgress.loaded = loaded;
|
||
|
lastProgress.total = total;
|
||
|
if (typeof progressFunc === 'function')
|
||
|
progressFunc(loaded, total);
|
||
|
}
|
||
|
if (!progressIsFinal)
|
||
|
requestAnimationFrame(animateProgress);
|
||
|
}
|
||
|
this.animateProgress = animateProgress; // Also exposed to start it.
|
||
|
|
||
|
this.setProgressFunc = function(callback) {
|
||
|
progressFunc = callback;
|
||
|
}
|
||
|
};
|