async function waitForSlideImages(markup, renderRequestId) { if (typeof Image !== 'function' || typeof document === 'undefined') return markup; var template = document.createElement('template'); template.innerHTML = markup; var sources = Array.from(new Set(Array.from(template.content.querySelectorAll('img[src]')).map(function (image) { return image.getAttribute('src'); }).filter(Boolean))); if (!sources.length) return markup; if (!lastRenderedViewKey && !app.querySelector('.player-media-loading')) { var loading = document.createElement('div'); loading.className = 'empty player-media-loading'; loading.setAttribute('role', 'status'); loading.setAttribute('aria-live', 'polite'); loading.innerHTML = 'Loading media...'; app.appendChild(loading); } async function loadSource(source) { var attempt = 0; var loadedUrl = source; while (renderRequestId === slideRenderRequestId) { var ready = await new Promise(function (resolve) { var image = new Image(); var settled = false; var timeout; function finish(value) { if (settled) return; settled = true; window.clearTimeout(timeout); image.onload = null; image.onerror = null; resolve(value); } image.onload = async function () { try { if (typeof image.decode === 'function') await image.decode(); loadedUrl = image.src; finish(image.naturalWidth > 0); } catch (_error) { finish(false); } }; image.onerror = function () { finish(false); }; timeout = window.setTimeout(function () { finish(false); }, 10000); var url = source; if (attempt) { try { var retryUrl = new URL(source, window.location.href); if (retryUrl.origin === window.location.origin && retryUrl.pathname.startsWith('/media/')) { retryUrl.searchParams.set('_mediaReady', String(Date.now())); url = retryUrl.toString(); } } catch (_error) {} } image.src = url; }); if (ready) { if (loadedUrl !== source) template.content.querySelectorAll('img[src]').forEach(function (element) { if (element.getAttribute('src') === source) element.setAttribute('src', loadedUrl); }); return renderRequestId === slideRenderRequestId; } if (renderRequestId !== slideRenderRequestId) return false; attempt += 1; await new Promise(function (resolve) { window.setTimeout(resolve, Math.min(1000 * attempt, 5000)); }); } return false; } var results = await Promise.all(sources.map(loadSource)); return renderRequestId === slideRenderRequestId && results.every(Boolean) ? template.innerHTML : false; } // Slide media startup and teardown helpers. var renderedVideoPlaybackCleanup = new WeakMap(); function cancelRenderedVideoPlayback(video) { var cleanup = renderedVideoPlaybackCleanup.get(video); if (cleanup) { cleanup(); renderedVideoPlaybackCleanup.delete(video); } } function initializeRenderedVideoPlayback(root, delayMs) { if (!root) { return; } var startDelayMs = Math.max(0, Number(delayMs || 0)); var videos = root.querySelectorAll('.template-region.video video'); Array.prototype.forEach.call(videos, function (video) { if (!video) { return; } cancelRenderedVideoPlayback(video); var playbackScheduled = false; var playbackCancelled = false; var playbackTimer = null; function removeReadinessListeners() { video.removeEventListener('canplay', schedulePlaybackStart); video.removeEventListener('loadedmetadata', onLoadedMetadata); } // A cancelled callback can already be queued for dispatch, so it also checks this flag. renderedVideoPlaybackCleanup.set(video, function () { playbackCancelled = true; if (playbackTimer !== null) { window.clearTimeout(playbackTimer); playbackTimer = null; } removeReadinessListeners(); }); // Script-controlled startup aligns playback with the incoming slide's fade timing. video.autoplay = false; video.loop = !(video.dataset && video.dataset.loop === '0'); video.muted = !(video.dataset && video.dataset.disableAudio === '0'); video.playsInline = true; function startPlayback() { playbackTimer = null; if (playbackCancelled || video.isConnected === false || root.isConnected === false) { return; } var playPromise = video.play && video.play(); if (playPromise && typeof playPromise.catch === 'function') { playPromise.catch(function () { return null; }); } } function schedulePlaybackStart() { // Metadata and canplay can both fire; schedule only one start for this video. if (playbackCancelled || playbackScheduled) { return; } playbackScheduled = true; removeReadinessListeners(); if (startDelayMs > 0) { playbackTimer = window.setTimeout(startPlayback, startDelayMs); return; } startPlayback(); } function onLoadedMetadata() { if (video.readyState >= 2) { schedulePlaybackStart(); } } // HAVE_CURRENT_DATA means a frame is available, not just the video's metadata. if (video.readyState >= 2) { schedulePlaybackStart(); return; } video.addEventListener('canplay', schedulePlaybackStart, { once: true }); video.addEventListener('loadedmetadata', onLoadedMetadata, { once: true }); }); } function pauseRenderedVideoPlayback(root, delayMs) { if (!root) { return; } var pauseDelayMs = Math.max(0, Number(delayMs || 0)); var videos = root.querySelectorAll('.template-region.video video, .slide-media video'); Array.prototype.forEach.call(videos, function (video) { if (!video || typeof video.pause !== 'function') { return; } window.setTimeout(function () { try { video.pause(); } catch (_error) { return null; } }, pauseDelayMs); }); } function initializeSlideMedia(root, delayMs) { if (!root || root.isConnected === false) { return; } if (typeof syncRtmpRegions === 'function') { syncRtmpRegions(root); } initializeRenderedVideoPlayback(root, delayMs); } function destroySlideMedia(root, options) { if (!root) { return; } var preserveVideoFrame = Boolean(options && options.preserveVideoFrame); var videos = root.querySelectorAll('.template-region.video video, .slide-media video'); Array.prototype.forEach.call(videos, function (video) { cancelRenderedVideoPlayback(video); if (typeof video.pause === 'function') { video.pause(); } // Keep the paused frame through a crossfade; release decoder/network resources at removal. if (!preserveVideoFrame) { video.removeAttribute('src'); if (typeof video.load === 'function') { video.load(); } } }); // RTMP players hold independent HLS resources beyond the lifetime of their DOM nodes. if (typeof destroyRtmpRegions === 'function') { destroyRtmpRegions(root); } }