Refine player control-plane flow

This commit is contained in:
2026-08-07 13:10:16 +01:00
parent 74318eb34e
commit 433be06bc7
35 changed files with 1604 additions and 233 deletions
+27 -30
View File
@@ -263,20 +263,33 @@ function renderSlideMarkup(markup, shouldFade) {
});
}
function schedulePostRenderSetup(root, delayMs) {
if (!root) {
return;
}
if (root.isConnected === false) {
return;
}
if (typeof syncRtmpRegions === 'function') {
syncRtmpRegions(root);
}
if (!isThumbnailPreview()) {
initializeRegionInstances(root);
}
initializeRenderedVideoPlayback(root, delayMs);
if (typeof playRegionAnimations === 'function') {
playRegionAnimations(root, 'intro');
}
}
if (!shouldFade) {
app.innerHTML = markup;
if (typeof syncRtmpRegions === 'function') {
syncRtmpRegions(app);
}
if (!isThumbnailPreview()) {
initializeRegionInstances(app);
}
initializeRenderedVideoPlayback(app);
if (typeof playRegionAnimations === 'function') {
window.requestAnimationFrame(function () {
playRegionAnimations(app, 'intro');
});
}
schedulePostRenderSetup(app, 0);
return app.firstElementChild;
}
@@ -325,11 +338,7 @@ function renderSlideMarkup(markup, shouldFade) {
app.innerHTML = '';
nextShell.style.opacity = '1';
app.appendChild(nextShell);
if (typeof syncRtmpRegions === 'function') {
syncRtmpRegions(nextShell);
}
initializeRegionInstances(nextShell);
initializeRenderedVideoPlayback(nextShell, slideFadeDurationMs / 2);
schedulePostRenderSetup(nextShell, slideFadeDurationMs / 2);
return nextShell;
}
@@ -340,24 +349,12 @@ function renderSlideMarkup(markup, shouldFade) {
pauseRenderedVideoPlayback(previousShell, slideFadeDurationMs / 2);
app.appendChild(nextShell);
void nextShell.offsetHeight;
window.requestAnimationFrame(function () {
nextShell.style.opacity = '1';
previousShell.style.opacity = '0';
if (typeof playRegionAnimations === 'function') {
playRegionAnimations(nextShell, 'intro');
}
schedulePostRenderSetup(nextShell, slideFadeDurationMs / 2);
});
if (typeof syncRtmpRegions === 'function') {
syncRtmpRegions(nextShell);
}
if (!isThumbnailPreview()) {
initializeRegionInstances(nextShell);
}
initializeRenderedVideoPlayback(nextShell, slideFadeDurationMs / 2);
slideTransitionTimer = window.setTimeout(function () {
if (previousShell && previousShell.parentNode) {
previousShell.parentNode.removeChild(previousShell);
@@ -54,6 +54,9 @@ async function renderSlideAtIndex(sourceSlides, targetIndex) {
index = currentIndex;
var markup = buildSlideMarkup(slide);
renderSlideMarkup(markup, currentPlaylistFadeBetweenSlides);
if (typeof scheduleSlideMarkupPreload === 'function') {
scheduleSlideMarkupPreload(availableSlides, currentIndex);
}
sendCommandState(slide);
if (!isPaused) {
scheduleSlideAdvance(getSlideHoldDelay(Math.max(1, Number(slide.duration_seconds || 10)) * 1000));
@@ -90,6 +93,9 @@ function showCurrent() {
if (typeof syncRtmpWarmups === 'function') {
syncRtmpWarmups(activeSlides, index);
}
if (typeof scheduleSlideMarkupPreload === 'function') {
scheduleSlideMarkupPreload(activeSlides, index);
}
if (!activeSlides.length) {
renderEmpty(slides.length ? 'No slides are scheduled for this time.' : 'No slides assigned to this screen yet.');
lastRenderedViewKey = getCurrentRenderKey(activeSlides);
@@ -211,6 +217,9 @@ function refresh() {
if (typeof syncRtmpWarmups === 'function') {
syncRtmpWarmups(nextActiveSlides, index);
}
if (typeof scheduleSlideMarkupPreload === 'function') {
scheduleSlideMarkupPreload(nextActiveSlides, index);
}
if (nextActiveSlides.length < 2) {
pendingPlaylistUpdate = {
slides: nextSlides,
+58 -6
View File
@@ -84,7 +84,7 @@ function getCurrentRenderKey(activeSlides) {
return [currentPlaylistSignature || '', viewportKey, 'slide', slide && slide.id ? slide.id : ''].join('|');
}
// Pick the current slide and the next slide for webpage preloading.
// Pick the next slide for webpage preloading.
function getWebpagePreloadSlides(sourceSlides, targetIndex) {
const availableSlides = Array.isArray(sourceSlides) ? sourceSlides : [];
if (!availableSlides.length) {
@@ -97,19 +97,71 @@ function getWebpagePreloadSlides(sourceSlides, targetIndex) {
}
const preloadSlides = [];
const currentSlide = availableSlides[normalizedIndex];
const nextSlide = availableSlides[normalizedIndex + 1];
if (currentSlide) {
preloadSlides.push(currentSlide);
}
if (nextSlide && nextSlide !== currentSlide) {
if (nextSlide) {
preloadSlides.push(nextSlide);
}
return preloadSlides;
}
// Pick the next slide to warm its markup before it becomes visible.
function getSlideMarkupPreloadSlides(sourceSlides, targetIndex) {
const availableSlides = Array.isArray(sourceSlides) ? sourceSlides : [];
if (!availableSlides.length) {
return [];
}
let normalizedIndex = Number(targetIndex || 0);
if (!Number.isFinite(normalizedIndex) || normalizedIndex < 0 || normalizedIndex >= availableSlides.length) {
normalizedIndex = 0;
}
const preloadSlides = [];
const nextSlide = availableSlides[normalizedIndex + 1];
if (nextSlide) {
preloadSlides.push(nextSlide);
}
return preloadSlides;
}
function scheduleSlideMarkupPreload(sourceSlides, targetIndex) {
if (window.__pulseThumbnailPreview) {
return;
}
const preloadSlides = getSlideMarkupPreloadSlides(sourceSlides, targetIndex);
if (!preloadSlides.length || typeof primeSlideMarkup !== 'function') {
return;
}
const slide = preloadSlides[0];
const preloadSignature = [
currentPlaylistSignature || '',
slide && slide.id ? slide.id : '',
slide && slide.template_id ? slide.template_id : '',
slide && slide.modified_at ? slide.modified_at : '',
window.innerWidth + 'x' + window.innerHeight,
videoRegionRenderVersion || 0
].join('|');
if (scheduleSlideMarkupPreload.signature === preloadSignature) {
return;
}
scheduleSlideMarkupPreload.signature = preloadSignature;
window.setTimeout(function () {
if (scheduleSlideMarkupPreload.signature !== preloadSignature) {
return;
}
primeSlideMarkup(slide);
}, 0);
}
// Mount hidden iframe preloads for the chosen webpage URLs.
function syncWebpagePreloads(sourceSlides, targetIndex) {
const urls = getWebpageUrls(getWebpagePreloadSlides(sourceSlides, targetIndex));
+49 -3
View File
@@ -893,6 +893,45 @@ function getSlideMarkupCacheKey(slide) {
return [currentPlaylistSignature || '', slide && slide.id ? slide.id : '', slide && slide.template_id ? slide.template_id : '', slide && slide.modified_at ? slide.modified_at : '', viewportKey, videoRegionRenderVersion || 0].join('|');
}
function restorePlayerCanvasDimensions(width, height) {
if (!document || !document.documentElement) {
return;
}
var style = document.documentElement.style;
if (width) {
style.setProperty('--player-canvas-width', width);
} else {
style.removeProperty('--player-canvas-width');
}
if (height) {
style.setProperty('--player-canvas-height', height);
} else {
style.removeProperty('--player-canvas-height');
}
}
function primeSlideMarkup(slide) {
if (!slide) {
return '';
}
var cachedMarkup = getCachedSlideMarkup(slide);
if (cachedMarkup) {
return cachedMarkup;
}
var previousWidth = document && document.documentElement && document.documentElement.style ? String(document.documentElement.style.getPropertyValue('--player-canvas-width') || '') : '';
var previousHeight = document && document.documentElement && document.documentElement.style ? String(document.documentElement.style.getPropertyValue('--player-canvas-height') || '') : '';
try {
return buildSlideMarkupForState(slide, false);
} finally {
restorePlayerCanvasDimensions(previousWidth.trim(), previousHeight.trim());
}
}
function notifyVideoRegionSourceReady() {
if (typeof videoRegionRenderVersion === 'number') {
videoRegionRenderVersion += 1;
@@ -919,9 +958,12 @@ function setCachedSlideMarkup(slide, markup) {
// Slide rendering and markup cache helpers.
// Choose the right slide renderer and cache the result.
function buildSlideMarkup(slide) {
lastRenderedSlide = slide || null;
syncBlackoutState();
function buildSlideMarkupForState(slide, updateCurrentState) {
if (updateCurrentState) {
lastRenderedSlide = slide || null;
syncBlackoutState();
}
var cachedMarkup = getCachedSlideMarkup(slide);
if (cachedMarkup) {
return cachedMarkup;
@@ -938,3 +980,7 @@ function buildSlideMarkup(slide) {
setCachedSlideMarkup(slide, markup);
return markup;
}
function buildSlideMarkup(slide) {
return buildSlideMarkupForState(slide, true);
}
+5 -10
View File
@@ -304,13 +304,9 @@ function getRtmpWarmupSlides(sourceSlides, targetIndex) {
}
var warmupSlides = [];
var currentSlide = availableSlides[normalizedIndex];
var nextSlide = availableSlides[normalizedIndex + 1];
if (currentSlide) {
warmupSlides.push(currentSlide);
}
if (nextSlide && nextSlide !== currentSlide) {
if (nextSlide) {
warmupSlides.push(nextSlide);
}
@@ -693,12 +689,11 @@ function startRtmpPlayback(video, sourceUrl, disableAudio, skipUnavailable, plac
if (window.Hls && window.Hls.isSupported && window.Hls.isSupported()) {
var hls = new window.Hls({
enableWorker: true,
lowLatencyMode: true,
liveSyncDurationCount: 4,
liveMaxLatencyDurationCount: 8,
maxBufferLength: 20,
liveSyncDurationCount: 6,
liveMaxLatencyDurationCount: 12,
maxBufferLength: 30,
maxLiveSyncPlaybackRate: 1,
backBufferLength: 30
backBufferLength: 60
});
video.__rtmpHls = hls;
hls.attachMedia(video);
+3 -3
View File
@@ -122,7 +122,7 @@ function renderVideoRegion(region, regionContent) {
videoRegionLastGoodSrcCache[regionKey] = requestedSrc;
}
setVideoSourceAvailability(requestedSrc, true);
return '<div class="template-region video" style="' + region.baseStyle + '"><video src="' + escapeHtml(requestedSrcVersioned) + '" title="' + escapeHtml(region.label) + '" data-disable-audio="' + (disableAudio ? '1' : '0') + '" autoplay' + (disableAudio ? ' muted' : '') + ' loop playsinline preload="auto" disablepictureinpicture oncanplay="this.play().catch(function () {})" onloadedmetadata="this.play().catch(function () {})"></video></div>';
return '<div class="template-region video" style="' + region.baseStyle + '"><video src="' + escapeHtml(requestedSrcVersioned) + '" title="' + escapeHtml(region.label) + '" data-disable-audio="' + (disableAudio ? '1' : '0') + '" autoplay' + (disableAudio ? ' muted' : '') + ' loop playsinline preload="auto" disablepictureinpicture oncanplay="this.play().catch(function () {})"></video></div>';
}
var requestedState = getVideoSourceAvailability(requestedSrc);
@@ -132,7 +132,7 @@ function renderVideoRegion(region, regionContent) {
if (regionKey) {
videoRegionLastGoodSrcCache[regionKey] = requestedSrc;
}
return '<div class="template-region video" style="' + region.baseStyle + '"><video src="' + escapeHtml(requestedSrcVersioned) + '" title="' + escapeHtml(region.label) + '" data-disable-audio="' + (disableAudio ? '1' : '0') + '" autoplay' + (disableAudio ? ' muted' : '') + ' loop playsinline preload="auto" disablepictureinpicture oncanplay="this.play().catch(function () {})" onloadedmetadata="this.play().catch(function () {})"></video></div>';
return '<div class="template-region video" style="' + region.baseStyle + '"><video src="' + escapeHtml(requestedSrcVersioned) + '" title="' + escapeHtml(region.label) + '" data-disable-audio="' + (disableAudio ? '1' : '0') + '" autoplay' + (disableAudio ? ' muted' : '') + ' loop playsinline preload="auto" disablepictureinpicture oncanplay="this.play().catch(function () {})"></video></div>';
}
scheduleVideoSourceProbe(regionKey, requestedSrc, false);
@@ -141,7 +141,7 @@ function renderVideoRegion(region, regionContent) {
if (cachedSrc !== requestedSrc) {
logVideoRegionStatus('Keeping the previous playable video until the new mirrored file finishes transferring.', 'region=' + regionKey + ' old=' + cachedSrc + ' new=' + requestedSrc);
}
return '<div class="template-region video" style="' + region.baseStyle + '"><video src="' + escapeHtml(cachedSrcVersioned) + '" title="' + escapeHtml(region.label) + '" data-disable-audio="' + (disableAudio ? '1' : '0') + '" autoplay' + (disableAudio ? ' muted' : '') + ' loop playsinline preload="auto" disablepictureinpicture oncanplay="this.play().catch(function () {})" onloadedmetadata="this.play().catch(function () {})"></video></div>';
return '<div class="template-region video" style="' + region.baseStyle + '"><video src="' + escapeHtml(cachedSrcVersioned) + '" title="' + escapeHtml(region.label) + '" data-disable-audio="' + (disableAudio ? '1' : '0') + '" autoplay' + (disableAudio ? ' muted' : '') + ' loop playsinline preload="auto" disablepictureinpicture oncanplay="this.play().catch(function () {})"></video></div>';
}
return '';