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);
}
}