show.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. 'use strict';
  2. (() => {
  3. function ShowFile(file, delay, latency) {
  4. this.file = file;
  5. this.delay = delay;
  6. this.latency = latency;
  7. }
  8. ShowFile.prototype.Delay = function() {
  9. return this.latency ? this.latency * (this.delay ? this.delay : 1) : this.delay;
  10. }
  11. function Show(parent, files, delay) {
  12. this.index = 0;
  13. this.elapsed = 0;
  14. this.playing = true;
  15. this.list = [];
  16. this.preloaded = {};
  17. this.updated = Date.now();
  18. this.delay = !isNaN(delay) ? delay : 3000;
  19. this.parent = parent;
  20. this.parent.innerHTML = '';
  21. this.parent.appendChild(new Image());
  22. (() => {
  23. this.parse(files);
  24. window.requestAnimationFrame(() => { this.render(); });
  25. window.addEventListener('keyup', (e) => {
  26. if (!e.keyCode) return;
  27. if (e.keyCode === 32) { this.toggle(); }
  28. else if (e.keyCode === 37) { this.prev(); }
  29. else if (e.keyCode === 39) { this.next(); }
  30. }, false);
  31. })();
  32. };
  33. Show.prototype.toggle = function() { this.playing = !this.playing; };
  34. Show.prototype.next = function() {
  35. if (++this.index >= this.list.length) this.index = 0;
  36. this.elapsed = 0;
  37. };
  38. Show.prototype.prev = function() {
  39. if (--this.index < 0) this.index = this.list.length-1;
  40. this.elapsed = 0;
  41. };
  42. Show.prototype.format = function(f, a) {
  43. f = f.replaceAll('%s', a);
  44. for (let start = f.indexOf('%'); start != -1 && f.length > start+3 && f[start+3] === 's'; start = f.indexOf('%')) {
  45. let c = String(f[start+1]);
  46. let w = parseInt(f[start+2]);
  47. let r = (String(a).length >= w) ? a : (new Array(w).join(c)).concat(a).slice(-w);
  48. f = f.replace(f.slice(start, start+4), r);
  49. }
  50. return f
  51. };
  52. Show.prototype.add = function(file, delay, latency) {
  53. let o = new ShowFile(file, delay, latency);
  54. // let o = {file: file, delay: delay, latency: latency};
  55. this.list.push(o);
  56. }
  57. Show.prototype.parse = function(files) {
  58. if (!(files instanceof Array)) return;
  59. files.map((o) => {
  60. if (typeof o === 'string') {
  61. this.add(o);
  62. } else if (o instanceof Object && !(o instanceof Array) && o !== null && typeof o.file !== 'undefined' && typeof o.file === 'string') {
  63. if (typeof o.range !== 'undefined' && o.range instanceof Array) {
  64. if (o.range.length === 2 && !(isNaN(o.range[2]) && isNaN(o.range[1])) && parseInt(o.range[0]) < parseInt(o.range[1])) {
  65. for (let i = parseInt(o.range[0]); i <= parseInt(o.range[1]); i++) {
  66. this.add(this.format(o.file, i), o.delay, o.latency);
  67. }
  68. } else {
  69. for (let i of o.range) {
  70. this.add(this.format(o.file, i), o.delay, o.latency);
  71. }
  72. }
  73. } else {
  74. this.add(o.file, o.delay, o.latency);
  75. }
  76. }
  77. });
  78. };
  79. Show.prototype.gifDuration = async (el, o) => {
  80. try {
  81. let response = await fetch(o.file);
  82. let data = await response.blob();
  83. let f = new FileReader();
  84. f.readAsArrayBuffer(data);
  85. f.onload = (event) => {
  86. let arr = new Uint8Array(f.result);
  87. let d = 0;
  88. for (var i = 0; i < arr.length; i++) {
  89. if (arr[i] == 0x21
  90. && arr[i + 1] == 0xF9
  91. && arr[i + 2] == 0x04
  92. && arr[i + 7] == 0x00) {
  93. const delay = (arr[i + 5] << 8) | (arr[i + 4] & 0xFF)
  94. d += delay < 2 ? 10 : delay;
  95. }
  96. }
  97. o.latency = d*10;
  98. }
  99. } catch(e) {}
  100. }
  101. Show.prototype.generate = function(idx) {
  102. let o = this.list[idx];
  103. let el = new Image();
  104. if (o.file.endsWith('.mp4') || o.file.endsWith('.webm') || o.file.endsWith('.ogg')) {
  105. el = document.createElement('video');
  106. el.addEventListener('canplay', () => { o.latency = (isNaN(o.delay) ? 1 : o.delay) * Math.trunc(el.duration * 1000); }, {once: true});
  107. el.muted = true;
  108. el.loop = true;
  109. } else if (o.file.endsWith('.gif')) {
  110. el.addEventListener('load', () => { this.gifDuration(el, o); }, {once: true});
  111. }
  112. el.src = o.file;
  113. el.addEventListener('error', () => { this.list.splice(this.list.indexOf(o), 1); }, {once: true});
  114. return el;
  115. }
  116. Show.prototype.preload = function() {
  117. let partial = Math.min(.4 * this.list.length << 0, 5);
  118. let position = partial > this.index ? this.list.length - Math.abs(partial - this.index) : this.index - partial;
  119. for (let i = 0; i <= partial*2; i++) {
  120. let l = (position+i)%this.list.length;
  121. let o = this.list[l];
  122. if (!this.preloaded.hasOwnProperty(o.file)) {
  123. this.preloaded[o.file] = this.generate(l);
  124. setTimeout(() => { delete this.preloaded[o.file]; }, partial*o.delay);
  125. }
  126. }
  127. }
  128. Show.prototype.resize = function() {
  129. let el = this.parent.firstChild;
  130. let vid = (el.tagName == 'VIDEO') ? 'video' : 'natural';
  131. this.parent.firstChild.className = (el[vid+'Width'] / el[vid+'Height'] < this.parent.clientWidth / this.parent.clientHeight) ? 'fillheight' : 'fillwidth';
  132. }
  133. Show.prototype.render = function() {
  134. if (!this.list.length) return;
  135. this.resize();
  136. let d = Date.now();
  137. let o = this.list[this.index];
  138. if (this.current != this.index) {
  139. this.current = this.index;
  140. this.preload();
  141. let el = this.preloaded[o.file];
  142. if (el.tagName !== this.parent.firstChild.tagName) this.parent.replaceChild(el.cloneNode(), this.parent.firstChild);
  143. if (el.tagName == 'VIDEO') {
  144. if (this.parent.firstChild.src !== el.src) this.parent.firstChild.src = el.src;
  145. this.parent.firstChild.currentTime = 0;
  146. this.parent.firstChild.loop = true;
  147. this.parent.firstChild.muted = true;
  148. this.parent.firstChild.play();
  149. } else {
  150. this.parent.firstChild.src = el.src;
  151. }
  152. }
  153. if (this.playing && document.hasFocus()) this.elapsed += (d - this.updated);
  154. if (this.elapsed >= (o.Delay() | this.delay)) this.next();
  155. s.updated = d;
  156. window.requestAnimationFrame(() => { this.render(); });
  157. }
  158. window.Show = Show;
  159. })();