(function () { var items = []; var current = -1; var timer = null; var overlay = null; var container = null; var elementMap = {}; var elements = []; var lastRefresh = Date.now(); var refreshInterval = 30 * 60 * 1000; var fadeDuration = 500; function analyzeVideoElement(el) { return { readyState: el.readyState, duration: el.duration, seekableRanges: el.seekable.length, }; } function maybeReload() { // console.log('Checking if page needs to be reloaded...'); var now = Date.now(); if (now - lastRefresh >= refreshInterval) { // console.log('Reloading page...'); lastRefresh = now; try { localStorage.setItem('lastRefresh', String(now)); } catch (e) {} location.reload(); } } function showItem(index) { // console.log('Showing item at index:', index); if (!elements.length) return; var el = elements[index]; if (!el) return; if (timer) { clearTimeout(timer); timer = null; } if (current !== index && elements[current]) { var cur = elements[current]; if (cur.tagName === 'VIDEO') { cur.pause(); cur.currentTime = 0; } cur.style.display = 'none'; } // Lógica por tipo if (el.tagName === 'VIDEO') { el.style.display = 'block'; el.currentTime = 0; // 1) Listener nativo 'ended' el.addEventListener('ended', function onEnd() { // console.log('Video ended, moving to next item...'); clearTimeout(el._fallbackTimer); next(); }, false); // 2) Fallback con timeout tras loadedmetadata o inmediato function scheduleFallback() { // console.log('Scheduling fallback for video... duration: ' + el.duration); el._fallbackTimer = setTimeout(function(){ console.log('Fallback triggered, moving to next item...'); next(); }, (el.duration * 1000) + 3000); } if (el.readyState >= 1 && !isNaN(el.duration)) { scheduleFallback(); } else { el.addEventListener('loadedmetadata', function onMeta() { // console.log('Video metadata loaded, scheduling fallback...'); el.removeEventListener('loadedmetadata', onMeta); scheduleFallback(); }, false); } // 3) Supervisión con timeupdate el.addEventListener('timeupdate', function onTime() { if (el.currentTime >= el.duration - 0.2) { el.removeEventListener('timeupdate', onTime); clearTimeout(el._fallbackTimer); next(); } }, false); // 4) Arranca reproducción // console.log('analyzeVideoElement: ' + JSON.stringify(analyzeVideoElement(el))); el.load(); el.play(); } else { // Lógica para imágenes el.style.display = 'block'; // Cada imagen se muestra 3 s antes de llamar a next() timer = setTimeout(next, 30000); } current = index; } function fadeAndShow(index) { // console.log('Fading and showing item at index:', index); overlay.style.opacity = '1'; setTimeout(function () { showItem(index); overlay.style.opacity = '0'; }, fadeDuration); } function next() { // console.log('Moving to next item...'); var idx = items.length ? (current + 1) % items.length : 0; if (items.length && idx === 0) { maybeReload(); } fadeAndShow(idx); } function findFolder(nodes, path) { // console.log('Finding folder with path:', path); if (!path.length) return { children: nodes }; var head = path[0]; var rest = path.slice(1); for (var i = 0; i < nodes.length; i++) { var n = nodes[i]; if (n.type === 'directory' && n.name === head) { if (!rest.length) return n; return findFolder(n.children || [], rest); } } return null; } function collectMedia(node, base, out) { // console.log('Collecting media from node:', node.name); if (node.type === 'file') { var ext = node.name.split('.').pop().toLowerCase(); var path = base.concat([node.name]).map(encodeURIComponent).join('/'); if (['jpg','jpeg','png','gif','bmp','webp'].indexOf(ext) !== -1) { out.push({ url: '/api/media/' + path, type: 'image' }); } else if (['mp4','webm','ogg'].indexOf(ext) !== -1) { out.push({ url: '/api/media/' + path, type: 'video' }); } } else { var children = node.children || []; for (var i = 0; i < children.length; i++) { collectMedia(children[i], base.concat([node.name]), out); } } } function arraysEqual(a, b) { if (a.length !== b.length) return false; for (var i = 0; i < a.length; i++) { if (a[i].url !== b[i].url || a[i].type !== b[i].type) return false; } return true; } function createElement(item) { // console.log('Creating element for:', item.url); var el; if (item.type === 'image') { el = document.createElement('img'); el.src = item.url; } else if (item.type === 'video') { el = document.createElement('video'); el.src = item.url; el.muted = true; el.preload = 'auto'; el.setAttribute('preload', 'auto'); el.load(); //el.onended = next; el.addEventListener('ended', next, false); } else { return null; } el.dataset.type = item.type; el.style.display = 'none'; container.insertBefore(el, overlay); return el; } function updateElements(newItems) { // console.log('Updating elements with new items...'); var newMap = {}; var newElements = []; for (var i = 0; i < newItems.length; i++) { var it = newItems[i]; var el = elementMap[it.url]; if (!el) { el = createElement(it); } newMap[it.url] = el; newElements.push(el); } for (var url in elementMap) { if (!newMap[url]) { var removeEl = elementMap[url]; if (removeEl.tagName === 'VIDEO') { removeEl.pause(); } if (removeEl.parentNode) { removeEl.parentNode.removeChild(removeEl); } } } elementMap = newMap; elements = newElements; items = newItems; } function preloadElements(callback) { // console.log('Preloading elements and measuring size…'); var remaining = elements.length; if (!remaining) { callback({}); return; } // Guarda aquí los tamaños var sizes = {}; function done(el) { // Buscar la entrada en performance var entries = performance.getEntriesByType('resource') .filter(r => r.name === el.src); if (entries.length) { // Tomamos la última entrada var entry = entries[entries.length - 1]; sizes[el.src] = entry.transferSize; // console.log(`Measured ${el.src}: ${entry.transferSize} bytes`); } else { sizes[el.src] = null; console.warn(`No timing entry for ${el.src}`); } remaining--; if (remaining === 0) { // Limpiar el buffer si quieres, para no saturar más tarde // performance.clearResourceTimings(); callback(sizes); } } elements.forEach(function(el) { if (el.tagName === 'VIDEO') { if (el.readyState >= 3) { done(el); } else { var h = function() { el.removeEventListener('canplaythrough', h); done(el); }; el.addEventListener('canplaythrough', h, false); try { el.load(); } catch (e) {} } } else { if (el.complete) { done(el); } else { var hi = function() { el.removeEventListener('load', hi); done(el); }; el.addEventListener('load', hi, false); } } }); } function fetchList(folder, callback) { // console.log('Fetching media list for folder:', folder); var listReq = new XMLHttpRequest(); listReq.open('GET', '/api/media-list', true); listReq.onload = function () { if (listReq.status >= 200 && listReq.status < 300) { try { var listData = JSON.parse(listReq.responseText); var segments = folder.split('/').filter(function(s){ return s; }); var folderNode = findFolder(listData.files, segments); if (!folderNode) return; var newItems = []; var children = folderNode.children || []; for (var j = 0; j < children.length; j++) { collectMedia(children[j], segments, newItems); } if (!arraysEqual(newItems, items)) { updateElements(newItems); preloadElements(function (sizes) { // console.log('Preloading complete, showing first item...'); console.table(sizes); current = -1; fadeAndShow(0); }); } } catch (e) { console.error(e); } } }; listReq.onerror = function (e) { console.error(e); }; listReq.send(null); } function fetchData() { // console.log('Fetching schedule data...'); var schedReq = new XMLHttpRequest(); schedReq.open('GET', '/api/schedule', true); schedReq.onload = function () { if (schedReq.status >= 200 && schedReq.status < 300) { try { var schedData = JSON.parse(schedReq.responseText); var now = new Date(); var folder = ''; var events = schedData.events || []; for (var i = 0; i < events.length; i++) { var ev = events[i]; var start = new Date(ev.start.dateTime || ev.start.date || ''); var end = new Date(ev.end.dateTime || ev.end.date || ''); if (now >= start && now < end) { folder = ev.summary || ''; break; } } if (!folder) return; fetchList(folder); } catch (e) { console.error(e); } } }; schedReq.onerror = function (e) { console.error(e); }; schedReq.send(null); } window.addEventListener('DOMContentLoaded', function () { container = document.getElementById('container'); overlay = document.getElementById('overlay'); var saved = parseInt(localStorage.getItem('lastRefresh') || '0', 10); if (saved) { lastRefresh = saved; } else { try { localStorage.setItem('lastRefresh', String(lastRefresh)); } catch (e) {} } fetchData(); setInterval(fetchData, 5 * 60 * 1000); }); })();