Release 2.21.0: non-destructive media and scoped player sync
This commit is contained in:
1 parent
24df245ea1
commit
7cff39dc76
35 files changed
+2009
-949
No files matched your search
@@ -1,3 +1,70 @@
|
||||
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 = '<span class="player-media-loading-spinner" aria-hidden="true"></span><span>Loading media...</span>';
|
||||
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();
|
||||
|
||||
@@ -56,6 +56,11 @@ async function renderSlideAtIndex(sourceSlides, targetIndex, options) {
|
||||
|
||||
index = currentIndex;
|
||||
var markup = buildSlideMarkup(slide);
|
||||
if (typeof waitForSlideImages === 'function') {
|
||||
var readyMarkup = await waitForSlideImages(markup, renderRequestId);
|
||||
if (readyMarkup === false || renderRequestId !== slideRenderRequestId) return false;
|
||||
if (typeof readyMarkup === 'string') markup = readyMarkup;
|
||||
}
|
||||
var shouldFade = !(options && options.skipFade) && currentPlaylistFadeBetweenSlides;
|
||||
var mediaDelayMs = slide && slide.use_video_duration ? 0 : undefined;
|
||||
renderSlideMarkup(markup, shouldFade, mediaDelayMs);
|
||||
@@ -159,6 +164,17 @@ function handleRtmpPlaybackFailure(message, options) {
|
||||
|
||||
// Fetch and queue data updates; rendering and pause/resume own the slide timer.
|
||||
function refresh(applyImmediately, bypassDebounce) {
|
||||
if (applyImmediately && typeof document !== 'undefined' && document.querySelectorAll) {
|
||||
document.querySelectorAll('img').forEach(function (image) {
|
||||
if (!image.complete || image.naturalWidth > 0) return;
|
||||
try {
|
||||
var mediaUrl = new URL(image.currentSrc || image.src, window.location.href);
|
||||
if (mediaUrl.origin !== window.location.origin || !mediaUrl.pathname.startsWith('/media/')) return;
|
||||
mediaUrl.searchParams.set('_mediaRefresh', String(Date.now()));
|
||||
image.src = mediaUrl.toString();
|
||||
} catch (_error) {}
|
||||
});
|
||||
}
|
||||
if (playlistRefreshInFlight) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ function startPlayerWatchdog() {
|
||||
}
|
||||
|
||||
playerWatchdogTimer = window.setInterval(function () {
|
||||
if (document.visibilityState === 'hidden' || isPaused || !slides.length || !lastRenderedSlide) {
|
||||
if (document.visibilityState === 'hidden' || isPaused || !slides.length || !lastRenderedSlide || (document.querySelector && document.querySelector('.player-media-loading'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user