preloader.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. var Preloader = /** @constructor */ function() {
  2. var DOWNLOAD_ATTEMPTS_MAX = 4;
  3. var progressFunc = null;
  4. var lastProgress = { loaded: 0, total: 0 };
  5. var loadingFiles = {};
  6. this.preloadedFiles = [];
  7. function loadXHR(resolve, reject, file, tracker) {
  8. var xhr = new XMLHttpRequest;
  9. xhr.open('GET', file);
  10. if (!file.endsWith('.js')) {
  11. xhr.responseType = 'arraybuffer';
  12. }
  13. ['loadstart', 'progress', 'load', 'error', 'abort'].forEach(function(ev) {
  14. xhr.addEventListener(ev, onXHREvent.bind(xhr, resolve, reject, file, tracker));
  15. });
  16. xhr.send();
  17. }
  18. function onXHREvent(resolve, reject, file, tracker, ev) {
  19. if (this.status >= 400) {
  20. if (this.status < 500 || ++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
  21. reject(new Error("Failed loading file '" + file + "': " + this.statusText));
  22. this.abort();
  23. return;
  24. } else {
  25. setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
  26. }
  27. }
  28. switch (ev.type) {
  29. case 'loadstart':
  30. if (tracker[file] === undefined) {
  31. tracker[file] = {
  32. total: ev.total,
  33. loaded: ev.loaded,
  34. attempts: 0,
  35. final: false,
  36. };
  37. }
  38. break;
  39. case 'progress':
  40. tracker[file].loaded = ev.loaded;
  41. tracker[file].total = ev.total;
  42. break;
  43. case 'load':
  44. tracker[file].final = true;
  45. resolve(this);
  46. break;
  47. case 'error':
  48. if (++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
  49. tracker[file].final = true;
  50. reject(new Error("Failed loading file '" + file + "'"));
  51. } else {
  52. setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
  53. }
  54. break;
  55. case 'abort':
  56. tracker[file].final = true;
  57. reject(new Error("Loading file '" + file + "' was aborted."));
  58. break;
  59. }
  60. }
  61. this.loadPromise = function(file) {
  62. return new Promise(function(resolve, reject) {
  63. loadXHR(resolve, reject, file, loadingFiles);
  64. });
  65. }
  66. this.preload = function(pathOrBuffer, destPath) {
  67. if (pathOrBuffer instanceof ArrayBuffer) {
  68. pathOrBuffer = new Uint8Array(pathOrBuffer);
  69. } else if (ArrayBuffer.isView(pathOrBuffer)) {
  70. pathOrBuffer = new Uint8Array(pathOrBuffer.buffer);
  71. }
  72. if (pathOrBuffer instanceof Uint8Array) {
  73. this.preloadedFiles.push({
  74. path: destPath,
  75. buffer: pathOrBuffer
  76. });
  77. return Promise.resolve();
  78. } else if (typeof pathOrBuffer === 'string') {
  79. var me = this;
  80. return this.loadPromise(pathOrBuffer).then(function(xhr) {
  81. me.preloadedFiles.push({
  82. path: destPath || pathOrBuffer,
  83. buffer: xhr.response
  84. });
  85. return Promise.resolve();
  86. });
  87. } else {
  88. throw Promise.reject("Invalid object for preloading");
  89. }
  90. };
  91. var animateProgress = function() {
  92. var loaded = 0;
  93. var total = 0;
  94. var totalIsValid = true;
  95. var progressIsFinal = true;
  96. Object.keys(loadingFiles).forEach(function(file) {
  97. const stat = loadingFiles[file];
  98. if (!stat.final) {
  99. progressIsFinal = false;
  100. }
  101. if (!totalIsValid || stat.total === 0) {
  102. totalIsValid = false;
  103. total = 0;
  104. } else {
  105. total += stat.total;
  106. }
  107. loaded += stat.loaded;
  108. });
  109. if (loaded !== lastProgress.loaded || total !== lastProgress.total) {
  110. lastProgress.loaded = loaded;
  111. lastProgress.total = total;
  112. if (typeof progressFunc === 'function')
  113. progressFunc(loaded, total);
  114. }
  115. if (!progressIsFinal)
  116. requestAnimationFrame(animateProgress);
  117. }
  118. this.animateProgress = animateProgress; // Also exposed to start it.
  119. this.setProgressFunc = function(callback) {
  120. progressFunc = callback;
  121. }
  122. };