'use strict'; (() => { function ShowFile(file, delay, latency) { this.file = file; this.delay = delay; this.latency = latency; } ShowFile.prototype.Delay = function() { return this.latency ? this.latency * (this.delay ? this.delay : 1) : this.delay; } function Show(parent, files, delay) { this.index = 0; this.elapsed = 0; this.playing = true; this.list = []; this.preloaded = {}; this.updated = Date.now(); this.delay = !isNaN(delay) ? delay : 3000; this.parent = parent; this.parent.innerHTML = ''; this.parent.appendChild(new Image()); (() => { this.parse(files); window.requestAnimationFrame(() => { this.render(); }); window.addEventListener('keyup', (e) => { if (!e.keyCode) return; if (e.keyCode === 32) { this.toggle(); } else if (e.keyCode === 37) { this.prev(); } else if (e.keyCode === 39) { this.next(); } }, false); })(); }; Show.prototype.toggle = function() { this.playing = !this.playing; }; Show.prototype.next = function() { if (++this.index >= this.list.length) this.index = 0; this.elapsed = 0; }; Show.prototype.prev = function() { if (--this.index < 0) this.index = this.list.length-1; this.elapsed = 0; }; Show.prototype.format = function(f, a) { f = f.replaceAll('%s', a); for (let start = f.indexOf('%'); start != -1 && f.length > start+3 && f[start+3] === 's'; start = f.indexOf('%')) { let c = String(f[start+1]); let w = parseInt(f[start+2]); let r = (String(a).length >= w) ? a : (new Array(w).join(c)).concat(a).slice(-w); f = f.replace(f.slice(start, start+4), r); } return f }; Show.prototype.add = function(file, delay, latency) { let o = new ShowFile(file, delay, latency); this.list.push(o); } Show.prototype.parse = function(files) { if (!(files instanceof Array)) return; files.map((o) => { if (typeof o === 'string') { this.add(o); } else if (o instanceof Object && !(o instanceof Array) && o !== null && typeof o.file !== 'undefined' && typeof o.file === 'string') { if (typeof o.range !== 'undefined' && o.range instanceof Array) { if (o.range.length === 2 && !(isNaN(o.range[2]) && isNaN(o.range[1])) && parseInt(o.range[0]) < parseInt(o.range[1])) { for (let i = parseInt(o.range[0]); i <= parseInt(o.range[1]); i++) { this.add(this.format(o.file, i), o.delay, o.latency); } } else { for (let i of o.range) { this.add(this.format(o.file, i), o.delay, o.latency); } } } else { this.add(o.file, o.delay, o.latency); } } }); }; Show.prototype.gifDuration = async (el, o) => { try { let response = await fetch(o.file); let data = await response.blob(); let f = new FileReader(); f.readAsArrayBuffer(data); f.onload = (event) => { let arr = new Uint8Array(f.result); let d = 0; for (var i = 0; i < arr.length; i++) { if (arr[i] == 0x21 && arr[i + 1] == 0xF9 && arr[i + 2] == 0x04 && arr[i + 7] == 0x00) { const delay = (arr[i + 5] << 8) | (arr[i + 4] & 0xFF) d += delay < 2 ? 10 : delay; } } o.latency = d*10; } } catch(e) {} } Show.prototype.generate = function(idx) { let o = this.list[idx]; let el = new Image(); if (o.file.endsWith('.mp4') || o.file.endsWith('.webm') || o.file.endsWith('.ogg')) { el = document.createElement('video'); el.addEventListener('canplay', () => { o.latency = Math.trunc(el.duration * 1000); }, {once: true}); el.muted = true; el.loop = true; } else if (o.file.endsWith('.gif')) { el.addEventListener('load', () => { this.gifDuration(el, o); }, {once: true}); } el.src = o.file; el.addEventListener('error', () => { this.list.splice(this.list.indexOf(o), 1); }, {once: true}); return el; } Show.prototype.preload = function() { let partial = Math.min(.4 * this.list.length << 0, 5); let position = partial > this.index ? this.list.length - Math.abs(partial - this.index) : this.index - partial; for (let i = 0; i <= partial*2; i++) { let l = (position+i)%this.list.length; let o = this.list[l]; if (!this.preloaded.hasOwnProperty(o.file)) { this.preloaded[o.file] = this.generate(l); setTimeout(() => { delete this.preloaded[o.file]; }, partial*o.delay); } } } Show.prototype.resize = function() { let el = this.parent.firstChild; let vid = (el.tagName == 'VIDEO') ? 'video' : 'natural'; this.parent.firstChild.className = (el[vid+'Width'] / el[vid+'Height'] < this.parent.clientWidth / this.parent.clientHeight) ? 'fillheight' : 'fillwidth'; } Show.prototype.render = function() { if (!this.list.length) return; this.resize(); let d = Date.now(); let o = this.list[this.index]; if (this.current != this.index) { this.current = this.index; this.preload(); let el = this.preloaded[o.file]; if (el.tagName !== this.parent.firstChild.tagName) this.parent.replaceChild(el.cloneNode(), this.parent.firstChild); if (el.tagName == 'VIDEO') { if (this.parent.firstChild.src !== el.src) this.parent.firstChild.src = el.src; this.parent.firstChild.currentTime = 0; this.parent.firstChild.loop = true; this.parent.firstChild.muted = true; this.parent.firstChild.play(); } else { this.parent.firstChild.src = el.src; } } if (this.playing && document.hasFocus()) this.elapsed += (d - this.updated); if (this.elapsed >= (o.Delay() | this.delay)) this.next(); s.updated = d; window.requestAnimationFrame(() => { this.render(); }); } window.Show = Show; })();