Release v2.14.1
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m53s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 32s

This commit is contained in:
lzstealth committed 2026-10-11 18:54:43 +01:00
1 parent 635e1a9942
commit c3299abaac
174 files changed
+4483 -1687

No files matched your search

@@ -2,15 +2,7 @@
var ANNOUNCEMENT_TYPES = ['lower-third', 'fullscreen', 'top-banner'];
var ANNOUNCEMENT_ICONS = Array.isArray(window.pulseAnnouncementIconKeys) ? window.pulseAnnouncementIconKeys : [];
var DEFAULT_ANNOUNCEMENT_ICON = String(window.pulseAnnouncementDefaultIconKey || ANNOUNCEMENT_ICONS[0] || '').trim().toLowerCase();
function escapeHtml(value) {
return String(value ?? '')
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
var escapeHtml = window.pulsePlayerRenderingShared.escapeHtml;
function normalizeAnnouncementType(value) {
var normalized = String(value || '').trim().toLowerCase();
+23 -143
View File
@@ -7,6 +7,7 @@ function getCurrentViewport() {
}
var commandHeartbeatTimer = null;
var commandReconnectDelayMs = 0;
// Command websocket and player-state helpers.
// Send the current playback state to the command websocket.
@@ -61,137 +62,6 @@ function scheduleViewportRenderUpdate() {
}, 150);
}
function getPlayerRegionModules() {
if (!window.pulsePlayerRegionTypes || typeof window.pulsePlayerRegionTypes.list !== 'function') {
return [];
}
return window.pulsePlayerRegionTypes.list();
}
function runRegionLifecycle(root, lifecycleName) {
if (!root) {
return;
}
getPlayerRegionModules().forEach(function (entry) {
var module = entry && entry.definition ? entry.definition : null;
if (!module || typeof module[lifecycleName] !== 'function') {
return;
}
try {
module[lifecycleName](root);
} catch (_error) {
// Keep slide rendering resilient if a region lifecycle hook fails.
}
});
}
function destroyRegionInstances(root) {
runRegionLifecycle(root, 'destroyRegion');
}
function initializeRegionInstances(root) {
runRegionLifecycle(root, 'initRegion');
}
// Swap slide markup with optional fade animation.
function renderSlideMarkup(markup, shouldFade, mediaDelayMs) {
clearSlideTransitionTimer();
destroyRegionInstances(app);
if (typeof destroySlideMedia === 'function') {
destroySlideMedia(app);
}
function schedulePostRenderSetup(root, delayMs) {
if (!root) {
return;
}
if (root.isConnected === false) {
return;
}
initializeRegionInstances(root);
if (typeof initializeSlideMedia === 'function') {
initializeSlideMedia(root, delayMs);
}
if (typeof playRegionAnimations === 'function') {
playRegionAnimations(root, 'intro');
}
}
if (!shouldFade) {
app.innerHTML = markup;
schedulePostRenderSetup(app, 0);
return app.firstElementChild;
}
var topLevelChildren = Array.prototype.slice.call(app.children || []);
var existingShells = topLevelChildren.filter(function (child) {
return child && child.classList && child.classList.contains('slide-shell');
});
var previousShell = existingShells.length ? existingShells[existingShells.length - 1] : app.firstElementChild;
if (existingShells.length > 1) {
existingShells.slice(0, -1).forEach(function (shell) {
if (shell && shell.parentNode) {
shell.parentNode.removeChild(shell);
}
});
}
var nextShell = document.createElement('div');
nextShell.className = 'slide-shell slide-shell-entering';
nextShell.style.zIndex = '0';
nextShell.style.opacity = '0';
nextShell.innerHTML = markup;
if (!previousShell || (previousShell.classList && previousShell.classList.contains('empty'))) {
app.innerHTML = '';
nextShell.style.opacity = '1';
nextShell.classList.remove('slide-shell-entering');
nextShell.classList.add('is-visible');
app.appendChild(nextShell);
schedulePostRenderSetup(nextShell, mediaDelayMs === undefined ? slideFadeOffsetMs : mediaDelayMs);
return nextShell;
}
if (!previousShell.classList.contains('slide-shell')) {
previousShell.classList.add('slide-shell');
}
previousShell.classList.remove('is-visible');
previousShell.classList.add('is-exiting');
previousShell.style.zIndex = '1';
previousShell.style.opacity = '1';
app.appendChild(nextShell);
window.requestAnimationFrame(function () {
nextShell.style.opacity = '1';
previousShell.style.opacity = '0';
nextShell.classList.remove('slide-shell-entering');
nextShell.classList.add('is-visible');
schedulePostRenderSetup(nextShell, mediaDelayMs === undefined ? slideFadeOffsetMs : mediaDelayMs);
});
slideTransitionTimer = window.setTimeout(function () {
if (previousShell && previousShell.parentNode) {
previousShell.parentNode.removeChild(previousShell);
}
if (nextShell) {
nextShell.style.zIndex = '1';
nextShell.style.opacity = '1';
nextShell.classList.remove('slide-shell-entering');
nextShell.classList.add('is-visible');
}
slideTransitionTimer = null;
}, slideFadeLengthMs);
return nextShell;
}
// Mirror blackout state onto the document body.
function syncBlackoutState() {
document.body.classList.toggle('screen-blackout', isBlackout);
@@ -207,11 +77,13 @@ function setPaused(nextPaused) {
pausedRemainingMs = slideExpiresAt ? Math.max(0, slideExpiresAt - Date.now()) : null;
isPaused = true;
clearSlideTimer();
savePlaybackState();
sendCommandState(lastRenderedSlide);
return;
}
isPaused = false;
savePlaybackState();
sendCommandState(lastRenderedSlide);
if (!slides.length || !lastRenderedSlide) {
return;
@@ -230,6 +102,7 @@ function setBlackout(nextBlackout) {
}
isBlackout = normalized;
syncBlackoutState();
savePlaybackState();
sendCommandState(lastRenderedSlide);
}
@@ -357,10 +230,6 @@ function handleCommandMessage(rawMessage) {
} catch (_error) {
return;
}
if (payload && payload.type === 'client-id-conflict') {
handleClientIdConflict();
return;
}
if (payload && payload.type === 'client-name-updated') {
if (payload.clientName) {
applyOnboardingClientName(payload.clientName, commandSocket);
@@ -437,23 +306,31 @@ function handleCommandMessage(rawMessage) {
}
}
function handleClientIdConflict() {
var replacementClientId = regenerateCommandClientId();
var onboardingUrl = new URL('/', window.location.origin);
window.location.replace(onboardingUrl.toString());
}
// Retry the command websocket after a disconnect.
function scheduleCommandReconnect() {
if (commandReconnectTimer) {
return;
}
var baseDelayMs = commandReconnectDelayMs
? Math.min(commandReconnectDelayMs * 2, 30000)
: 1000;
commandReconnectDelayMs = baseDelayMs;
var jitteredDelayMs = Math.round(baseDelayMs * (0.75 + Math.random() * 0.5));
commandReconnectTimer = window.setTimeout(function () {
commandReconnectTimer = null;
connectCommandSocket();
}, 5000);
}, jitteredDelayMs);
}
window.addEventListener('online', function () {
if (commandReconnectTimer) {
window.clearTimeout(commandReconnectTimer);
commandReconnectTimer = null;
}
commandReconnectDelayMs = 0;
connectCommandSocket();
});
// Open and wire the command websocket connection.
function connectCommandSocket() {
if (!window.WebSocket) {
@@ -466,6 +343,7 @@ function connectCommandSocket() {
commandSocket = socket;
socket.onopen = function () {
commandReconnectDelayMs = 0;
if (commandHeartbeatTimer) {
window.clearInterval(commandHeartbeatTimer);
}
@@ -495,7 +373,9 @@ function connectCommandSocket() {
}
commandSocket = null;
if (event && event.code === 4009) {
handleClientIdConflict();
regenerateCommandClientId();
commandReconnectDelayMs = 0;
scheduleCommandReconnect();
return;
}
scheduleCommandReconnect();
+67 -8
View File
@@ -1,5 +1,15 @@
// 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;
@@ -12,14 +22,37 @@ function initializeRenderedVideoPlayback(root, delayMs) {
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 () {
@@ -29,28 +62,33 @@ function initializeRenderedVideoPlayback(root, delayMs) {
}
function schedulePlaybackStart() {
if (playbackScheduled) {
// Metadata and canplay can both fire; schedule only one start for this video.
if (playbackCancelled || playbackScheduled) {
return;
}
playbackScheduled = true;
removeReadinessListeners();
if (startDelayMs > 0) {
window.setTimeout(startPlayback, startDelayMs);
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', function () {
if (video.readyState >= 2) {
schedulePlaybackStart();
}
}, { once: true });
video.addEventListener('loadedmetadata', onLoadedMetadata, { once: true });
});
}
@@ -87,7 +125,28 @@ function initializeSlideMedia(root, delayMs) {
initializeRenderedVideoPlayback(root, delayMs);
}
function destroySlideMedia(root) {
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);
}
+33 -54
View File
@@ -1,5 +1,8 @@
var playlistRefreshInFlight = false;
// Render the slide at the requested index within the active set.
async function renderSlideAtIndex(sourceSlides, targetIndex, options) {
var renderRequestId = ++slideRenderRequestId;
const availableSlides = Array.isArray(sourceSlides) ? sourceSlides : [];
if (!availableSlides.length) {
renderEmpty(slides.length ? 'No slides are scheduled for this time.' : 'No slides assigned to this screen yet.');
@@ -8,14 +11,14 @@ async function renderSlideAtIndex(sourceSlides, targetIndex, options) {
}
let normalizedIndex = Number(targetIndex || 0);
if (!Number.isFinite(normalizedIndex) || normalizedIndex < 0 || normalizedIndex >= availableSlides.length) {
if (!Number.isInteger(normalizedIndex) || normalizedIndex < 0 || normalizedIndex >= availableSlides.length) {
normalizedIndex = 0;
}
var slideCount = availableSlides.length;
var currentIndex = normalizedIndex;
var slide = null;
var attempts = 0;
var renderRequestId = ++slideRenderRequestId;
while (attempts < slideCount) {
slide = availableSlides[currentIndex];
@@ -35,7 +38,7 @@ async function renderSlideAtIndex(sourceSlides, targetIndex, options) {
}
}
if (renderRequestId !== slideRenderRequestId) {
break;
return false;
}
break;
}
@@ -56,6 +59,13 @@ async function renderSlideAtIndex(sourceSlides, targetIndex, options) {
var shouldFade = !(options && options.skipFade) && currentPlaylistFadeBetweenSlides;
var mediaDelayMs = slide && slide.use_video_duration ? 0 : undefined;
renderSlideMarkup(markup, shouldFade, mediaDelayMs);
if (typeof syncSlideMediaPreloads === 'function') {
syncSlideMediaPreloads(availableSlides, currentIndex);
}
if (typeof markPlayerPlaybackProgress === 'function') {
markPlayerPlaybackProgress();
}
savePlaybackState();
if (typeof scheduleSlideMarkupPreload === 'function') {
scheduleSlideMarkupPreload(availableSlides, currentIndex);
}
@@ -67,20 +77,6 @@ async function renderSlideAtIndex(sourceSlides, targetIndex, options) {
return true;
}
// Calculate the configured time from slide entry to the next transition.
function getSlideAdvanceDelay(slide) {
var durationMs = Math.max(1, Number(slide && slide.duration_seconds || 10)) * 1000;
if (slide && slide.use_video_duration) {
return currentPlaylistFadeBetweenSlides
? getSlideHoldDelay(Math.max(1, durationMs - slideFadeLengthMs))
: getSlideHoldDelay(durationMs);
}
if (currentPlaylistFadeBetweenSlides) {
return getSlideHoldDelay(Math.max(1, durationMs - slideFadeOffsetMs));
}
return getSlideHoldDelay(durationMs);
}
// Promote a deferred playlist update at the next safe point.
function applyPendingPlaylistUpdate() {
if (!pendingPlaylistUpdate) {
@@ -117,15 +113,12 @@ function showCurrent(options) {
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);
return;
}
void renderSlideAtIndex(activeSlides, index, options);
lastRenderedViewKey = getCurrentRenderKey(activeSlides);
}
var lastPlaylistRefreshAt = 0;
function isSlideInList(slide, slideList) {
if (!slide || !Array.isArray(slideList)) {
return false;
@@ -163,8 +156,16 @@ function handleRtmpPlaybackFailure(message, options) {
return true;
}
// Fetch the latest playlist and queue any updates.
function refresh(applyImmediately) {
// Fetch and queue data updates; rendering and pause/resume own the slide timer.
function refresh(applyImmediately, bypassDebounce) {
if (playlistRefreshInFlight) {
return;
}
if (!bypassDebounce && Date.now() - lastPlaylistRefreshAt < 1000) {
return;
}
playlistRefreshInFlight = true;
lastPlaylistRefreshAt = Date.now();
var request = new XMLHttpRequest();
var url = window.location.origin + '/api/screens/' + encodeURIComponent(slug) + '/playlist?ts=' + Date.now();
request.open('GET', url, true);
@@ -175,25 +176,26 @@ function refresh(applyImmediately) {
if (typeof getCommandClientId === 'function') {
request.setRequestHeader('x-pulse-client-id', getCommandClientId());
}
if (currentPlaylistEtag) {
if (currentPlaylistEtag
&& initialData
&& Array.isArray(initialData.apiSources)
&& Array.isArray(initialData.weatherLocations)) {
request.setRequestHeader('If-None-Match', currentPlaylistEtag);
}
request.onreadystatechange = function () {
if (request.readyState !== 4) {
return;
}
playlistRefreshInFlight = false;
if (request.status === 304) {
if (!initialData || !Array.isArray(initialData.apiSources) || !Array.isArray(initialData.weatherLocations)) {
currentPlaylistEtag = '';
refresh(applyImmediately);
refresh(applyImmediately, true);
return;
}
logDebug('Playlist refresh completed with no changes.');
markRefreshHealthy();
setOfflineBannerVisible(false);
if (lastRenderedSlide && getCurrentActiveSlides().length < 2) {
scheduleSlideAdvance(getSlideAdvanceDelay(lastRenderedSlide));
}
return;
}
if (request.status < 200 || request.status >= 300) {
@@ -249,7 +251,6 @@ function refresh(applyImmediately) {
});
markRefreshHealthy();
setOfflineBannerVisible(false);
const currentActiveSlides = getCurrentActiveSlides();
if (responseEtag) {
currentPlaylistEtag = responseEtag;
}
@@ -268,9 +269,6 @@ function refresh(applyImmediately) {
showCurrent({ skipFade: true });
return;
}
if (currentActiveSlides.length < 2 && lastRenderedSlide) {
scheduleSlideAdvance(getSlideAdvanceDelay(lastRenderedSlide));
}
return;
}
syncWebpagePreloads(nextActiveSlides, index);
@@ -287,16 +285,6 @@ function refresh(applyImmediately) {
fadeBetweenSlides: nextFadeBetweenSlides,
skipUnavailableRtmp: nextSkipUnavailableRtmp
};
if (applyImmediately) {
applyPendingPlaylistUpdate();
showCurrent();
return;
}
if (!isSlideInList(lastRenderedSlide, nextSlides)) {
applyPendingPlaylistUpdate();
showCurrent();
return;
}
logDebug('Playlist update detected; applying on next slide transition.');
return;
}
@@ -306,11 +294,6 @@ function refresh(applyImmediately) {
fadeBetweenSlides: nextFadeBetweenSlides,
skipUnavailableRtmp: nextSkipUnavailableRtmp
};
if (applyImmediately) {
applyPendingPlaylistUpdate();
showCurrent();
return;
}
logDebug('Playlist update detected; applying on next slide transition.');
} catch (_error) {
logDebug(
@@ -323,6 +306,7 @@ function refresh(applyImmediately) {
}
};
request.onerror = function () {
playlistRefreshInFlight = false;
logDebug(
'Unable to load screen playlist.',
['URL: ' + url, 'Network error during request.'].join(' | '),
@@ -330,11 +314,9 @@ function refresh(applyImmediately) {
);
setOfflineBannerVisible(true);
scheduleRefreshRetry();
if (lastRenderedSlide && getCurrentActiveSlides().length < 2) {
scheduleSlideAdvance(getSlideAdvanceDelay(lastRenderedSlide));
}
};
request.ontimeout = function () {
playlistRefreshInFlight = false;
logDebug(
'Playlist refresh timed out.',
['URL: ' + url, 'Timeout after ' + request.timeout + 'ms'].join(' | '),
@@ -342,9 +324,6 @@ function refresh(applyImmediately) {
);
setOfflineBannerVisible(true);
scheduleRefreshRetry();
if (lastRenderedSlide && getCurrentActiveSlides().length < 2) {
scheduleSlideAdvance(getSlideAdvanceDelay(lastRenderedSlide));
}
};
request.send();
}
@@ -158,6 +158,148 @@ function scheduleSlideMarkupPreload(sourceSlides, targetIndex) {
}, 0);
}
function deferVideoSourcesForPreload(markup) {
return String(markup || '').replace(/(<video\b[^>]*?)\s+src=("([^"]*)"|'([^']*)')/gi, function (_match, openingTag, quote, doubleQuoted, singleQuoted) {
const source = doubleQuoted !== undefined ? doubleQuoted : singleQuoted;
return openingTag + ' data-pulse-video-src=' + quote + source + quote;
});
}
function clearSlideMediaPreloads() {
mediaPreloadElements.forEach(function (element) {
if (!element || element.tagName !== 'VIDEO' || element.parentNode) {
return;
}
if (typeof element.pause === 'function') {
element.pause();
}
if (typeof element.removeAttribute === 'function') {
element.removeAttribute('src');
}
if (typeof element.load === 'function') {
element.load();
}
});
mediaPreloadElements = [];
mediaPreloadSignature = '';
}
function promotePreloadedVideoElements(root, slideId) {
if (!root || typeof root.querySelectorAll !== 'function') {
return 0;
}
var normalizedSlideId = String(slideId || '');
var promotedElements = [];
Array.prototype.forEach.call(root.querySelectorAll('.template-region.video video, .slide-media video'), function (activeVideo) {
var activeSource = String(activeVideo && (activeVideo.getAttribute('data-pulse-video-src') || activeVideo.getAttribute('src') || activeVideo.src) || '').trim();
if (!activeSource || !activeVideo.parentNode) {
return;
}
var absoluteSource = new URL(activeSource, window.location.origin).toString();
var preloadedVideo = mediaPreloadElements.find(function (element) {
return element && element.tagName === 'VIDEO'
&& String(element.__pulsePreloadSlideId || '') === normalizedSlideId
&& String(element.src || '').trim() === absoluteSource;
});
if (!preloadedVideo) {
if (activeVideo.getAttribute('data-pulse-video-src')) {
activeVideo.removeAttribute('data-pulse-video-src');
activeVideo.setAttribute('src', activeSource);
}
return;
}
Array.prototype.forEach.call(activeVideo.attributes || [], function (attribute) {
var attributeName = String(attribute.name || '').toLowerCase();
if (attributeName !== 'src' && attributeName !== 'data-pulse-video-src') {
preloadedVideo.setAttribute(attribute.name, attribute.value);
}
});
preloadedVideo.preload = activeVideo.preload || 'auto';
preloadedVideo.muted = Boolean(activeVideo.muted);
preloadedVideo.loop = Boolean(activeVideo.loop);
preloadedVideo.playsInline = Boolean(activeVideo.playsInline);
preloadedVideo.autoplay = Boolean(activeVideo.autoplay);
preloadedVideo.removeAttribute('data-pulse-video-src');
if (String(preloadedVideo.src || '').trim() !== absoluteSource) {
preloadedVideo.src = absoluteSource;
}
activeVideo.parentNode.replaceChild(preloadedVideo, activeVideo);
promotedElements.push(preloadedVideo);
});
if (promotedElements.length) {
mediaPreloadElements = mediaPreloadElements.filter(function (element) {
return promotedElements.indexOf(element) === -1;
});
}
return promotedElements.length;
}
function syncSlideMediaPreloads(sourceSlides, targetIndex) {
var availableSlides = Array.isArray(sourceSlides) ? sourceSlides : [];
if (availableSlides.length < 2) {
clearSlideMediaPreloads();
return;
}
var currentIndex = Math.min(availableSlides.length - 1, Math.max(0, Number(targetIndex || 0)));
var nextSlide = availableSlides[(currentIndex + 1) % availableSlides.length];
if (!nextSlide || typeof primeSlideMarkup !== 'function' || typeof document === 'undefined') {
clearSlideMediaPreloads();
return;
}
var signature = [
currentPlaylistSignature || '',
nextSlide.id || '',
nextSlide.modified_at || '',
window.innerWidth + 'x' + window.innerHeight
].join('|');
if (signature === mediaPreloadSignature) {
return;
}
clearSlideMediaPreloads();
mediaPreloadSignature = signature;
var holder = document.createElement('div');
holder.innerHTML = deferVideoSourcesForPreload(primeSlideMarkup(nextSlide) || '');
var mediaUrls = [];
Array.prototype.forEach.call(holder.querySelectorAll('img[src], video[data-pulse-video-src]'), function (element) {
var isVideo = element.tagName === 'VIDEO';
var src = String(element.getAttribute(isVideo ? 'data-pulse-video-src' : 'src') || '').trim();
if (!src || mediaUrls.indexOf(src) !== -1) {
return;
}
mediaUrls.push(src);
var absoluteUrl = new URL(src, window.location.origin).toString();
if (element.tagName === 'VIDEO') {
var video = document.createElement('video');
Array.prototype.forEach.call(element.attributes || [], function (attribute) {
if (String(attribute.name || '').toLowerCase() !== 'src') {
video.setAttribute(attribute.name, attribute.value);
}
});
video.preload = 'auto';
video.muted = true;
video.playsInline = true;
video.__pulsePreloadSlideId = String(nextSlide.id || '');
video.src = absoluteUrl;
if (typeof video.load === 'function') {
video.load();
}
mediaPreloadElements.push(video);
return;
}
var image = new Image();
image.decoding = 'async';
image.src = absoluteUrl;
mediaPreloadElements.push(image);
});
}
// Mount hidden iframe preloads for the chosen webpage URLs.
function syncWebpagePreloads(sourceSlides, targetIndex) {
const urls = getWebpageUrls(getWebpagePreloadSlides(sourceSlides, targetIndex));
@@ -247,6 +389,7 @@ function savePlaylistSnapshot(data) {
slides: Array.isArray(data && data.slides) ? data.slides : [],
signature: String(data && data.signature || ''),
fadeBetweenSlides: Boolean(data && data.fadeBetweenSlides),
skipUnavailableRtmp: Boolean(data && data.skipUnavailableRtmp),
etag: String(data && data.etag || ''),
savedAt: new Date().toISOString()
}));
@@ -263,6 +406,7 @@ function applyPlaylistSnapshot(data) {
slides = data.slides.map(normalizeSlide);
currentPlaylistSignature = String(data.signature || '');
currentPlaylistFadeBetweenSlides = Boolean(data.fadeBetweenSlides);
currentPlaylistSkipUnavailableRtmp = Boolean(data.skipUnavailableRtmp);
currentPlaylistEtag = String(data.etag || '');
pendingPlaylistUpdate = null;
clearActiveSlidesCache();
@@ -272,4 +416,62 @@ function applyPlaylistSnapshot(data) {
renderCacheViewportKey = '';
index = 0;
return true;
}
function loadPlaybackState() {
try {
var raw = window.localStorage.getItem(playbackStateStorageKey);
if (!raw) {
return null;
}
var parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== 'object') {
return null;
}
return {
signature: String(parsed.signature || ''),
slideId: parsed.slideId === null || parsed.slideId === undefined ? null : String(parsed.slideId),
index: Number.isFinite(Number(parsed.index)) ? Number(parsed.index) : 0,
paused: Boolean(parsed.paused),
blackout: Boolean(parsed.blackout)
};
} catch (_error) {
return null;
}
}
function savePlaybackState() {
try {
window.localStorage.setItem(playbackStateStorageKey, JSON.stringify({
signature: String(currentPlaylistSignature || ''),
slideId: lastRenderedSlide && lastRenderedSlide.id !== undefined ? String(lastRenderedSlide.id) : null,
index: Number(index || 0),
paused: Boolean(isPaused),
blackout: Boolean(isBlackout),
savedAt: new Date().toISOString()
}));
} catch (_error) {
// Ignore storage errors so playback remains unaffected.
}
}
function restorePlaybackState() {
var state = loadPlaybackState();
if (!state || state.signature !== String(currentPlaylistSignature || '')) {
return false;
}
var restoredIndex = state.index;
var matchingSlideIndex = slides.findIndex(function (slide) {
return slide && state.slideId !== null && String(slide.id) === state.slideId;
});
if (matchingSlideIndex >= 0) {
restoredIndex = matchingSlideIndex;
}
if (slides.length) {
index = Math.min(Math.max(0, Number(restoredIndex) || 0), slides.length - 1);
}
isPaused = state.paused;
isBlackout = state.blackout;
return true;
}
+33 -311
View File
@@ -1,28 +1,21 @@
// General sanitization and sizing helpers.
// Strip unsupported characters from a font family string.
function sanitizeFontFamily(value) {
return String(value || '').replace(/[^a-zA-Z0-9 ,\"-]/g, '').trim();
}
function normalizeStyleAttributeValue(value) {
return String(value || '')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/&amp;quot;/g, '"')
.replace(/&amp;#39;/g, "'");
}
function sanitizeFontSize(value) {
return Math.max(8, Number(value || 0) || 24);
}
function sanitizeTextColor(value, fallback) {
var raw = String(value || '').trim();
if (/^#[0-9a-fA-F]{6}$/.test(raw) || /^#[0-9a-fA-F]{3}$/.test(raw)) {
return raw;
}
return fallback || '#000000';
}
var playerRenderingShared = window.pulsePlayerRenderingShared;
var escapeHtml = playerRenderingShared.escapeHtml;
var normalizeStyleAttributeValue = playerRenderingShared.normalizeStyleAttributeValue;
var sanitizeFontFamily = playerRenderingShared.sanitizeFontFamily;
var sanitizeFontSize = playerRenderingShared.sanitizeFontSize;
var sanitizeTextColor = playerRenderingShared.sanitizeTextColor;
var sanitizeRichTextAttributes = playerRenderingShared.sanitizeRichTextAttributes;
var sanitizeRichText = playerRenderingShared.sanitizeRichText;
var renderEditorJsBlock = playerRenderingShared.renderEditorJsBlock;
var renderEditorJsListItem = playerRenderingShared.renderEditorJsListItem;
var renderEditorJsTable = playerRenderingShared.renderEditorJsTable;
var renderEditorJsContent = playerRenderingShared.renderEditorJsContent;
var parseMaybeJson = playerRenderingShared.parseMaybeJson;
var normalizeContentValue = playerRenderingShared.normalizeContentValue;
var fitCanvasSize = playerRenderingShared.fitCanvasSize;
var decorateRegionMarkup = playerRenderingShared.decorateRegionMarkup;
function getTemplateCanvasSize(template) {
return {
@@ -31,6 +24,17 @@ function getTemplateCanvasSize(template) {
};
}
function selectPlayerMediaSource(originalSource, variants, targetDimension) {
var original = String(originalSource || '').trim();
var target = Math.max(1, Number(targetDimension || 0) || 1);
var candidates = (Array.isArray(variants) ? variants : []).filter(function (variant) {
return variant && String(variant.url || '').trim() && Number(variant.width) >= target;
}).sort(function (left, right) {
return Number(left.width) - Number(right.width);
});
return candidates.length ? String(candidates[0].url) : original;
}
function getPlaylistRevision(data) {
if (data && data.revision) {
return String(data.revision);
@@ -44,36 +48,6 @@ function getPlaylistRevision(data) {
return String(Date.now());
}
function fitCanvasSize(canvasWidth, canvasHeight, maxWidth, maxHeight) {
var width = Math.max(1, Number(canvasWidth || 0) || 1920);
var height = Math.max(1, Number(canvasHeight || 0) || 1080);
var viewportWidth = Math.max(1, Number(maxWidth || 0) || width);
var viewportHeight = Math.max(1, Number(maxHeight || 0) || height);
var scale = Math.min(viewportWidth / width, viewportHeight / height);
return {
width: Math.round(width * scale),
height: Math.round(height * scale)
};
}
function hasPlayerAnimation(config) {
return Boolean(config && ['intro', 'outro', 'loop'].some(function (stepName) {
var preset = String((config[stepName] && config[stepName].preset) || '').trim();
return preset && preset !== 'none';
}));
}
function decorateRegionMarkup(markup, region) {
if (!markup || !region || !hasPlayerAnimation(region.animationConfig)) {
return markup;
}
var animationJson = escapeHtml(JSON.stringify(region.animationConfig));
return markup.replace(/^(\s*)<([a-z0-9-]+)(\s[^>]*)?>/i, function (match, leadingWhitespace, tagName, attributes) {
return leadingWhitespace + '<' + tagName + (attributes || '') + ' data-animation-json="' + animationJson + '">';
});
}
function setPlayerCanvasDimensions(canvasWidth, canvasHeight) {
if (!document || !document.documentElement) {
return;
@@ -85,264 +59,9 @@ function setPlayerCanvasDimensions(canvasWidth, canvasHeight) {
document.documentElement.style.setProperty('--player-canvas-height', height + 'px');
}
const ALLOWED_RICH_TEXT_TAGS = ['a', 'b', 'blockquote', 'br', 'code', 'col', 'colgroup', 'div', 'em', 'figure', 'figcaption', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'li', 'ol', 'p', 'pre', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'th', 'thead', 'tr', 'u', 'ul'];
function sanitizeRichTextAttributes(tagName, attrText) {
const allowedAttributes = {
a: ['href', 'title', 'target', 'rel', 'class', 'style'],
blockquote: ['class', 'style'],
div: ['class', 'style'],
figure: ['class', 'style'],
figcaption: ['class', 'style'],
h1: ['class', 'style'],
h2: ['class', 'style'],
h3: ['class', 'style'],
h4: ['class', 'style'],
h5: ['class', 'style'],
h6: ['class', 'style'],
img: ['src', 'alt', 'title', 'width', 'height', 'class', 'style', 'loading', 'decoding'],
i: ['class', 'style', 'aria-hidden'],
col: ['class', 'style', 'span', 'width'],
colgroup: ['class', 'style', 'span'],
li: ['class', 'style'],
ol: ['class', 'style', 'start'],
p: ['class', 'style'],
pre: ['class', 'style'],
span: ['class', 'style'],
table: ['class', 'style'],
tbody: ['class', 'style'],
td: ['class', 'style', 'colspan', 'rowspan'],
th: ['class', 'style', 'colspan', 'rowspan', 'scope'],
thead: ['class', 'style'],
tr: ['class', 'style'],
ul: ['class', 'style']
};
const allowed = allowedAttributes[tagName] || [];
if (!allowed.length) {
return '';
}
if (tagName === 'img') {
const srcMatch = String(attrText || '').match(/\bsrc\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>/=`]+))/i);
const srcValue = srcMatch ? String(srcMatch[2] !== undefined ? srcMatch[2] : srcMatch[3] !== undefined ? srcMatch[3] : srcMatch[4] !== undefined ? srcMatch[4] : '').trim() : '';
if (!srcValue || !/^(?:https?:\/\/|\/media\/|\/assets\/|\/[^/]|data:image\/)/i.test(srcValue)) {
return '';
}
}
const attrs = [];
String(attrText || '').replace(/([a-zA-Z0-9:-]+)(?:\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>/=`]+)))?/g, (_full, key, _valuePart, doubleQuoted, singleQuoted, bareValue) => {
const lowerKey = String(key || '').toLowerCase();
if (!allowed.includes(lowerKey)) {
return '';
}
const value = doubleQuoted !== undefined ? doubleQuoted : singleQuoted !== undefined ? singleQuoted : bareValue !== undefined ? bareValue : '';
if (lowerKey === 'href' && /^(?:\s*javascript:|\s*data:)/i.test(String(value || ''))) {
return '';
}
if (lowerKey === 'style' && /(?:expression\s*\(|javascript:|url\s*\()/i.test(String(value || ''))) {
return '';
}
if (lowerKey === 'target') {
const targetValue = String(value || '').trim();
if (targetValue === '_blank') {
attrs.push(' target="_blank"');
if (!attrs.includes(' rel="noreferrer noopener"')) {
attrs.push(' rel="noreferrer noopener"');
}
return '';
}
}
attrs.push(' ' + lowerKey + '="' + escapeHtml(lowerKey === 'style' ? normalizeStyleAttributeValue(value) : value) + '"');
return '';
});
return attrs.join('');
}
// Remove unsafe markup while preserving richer rich-text formatting.
function sanitizeRichText(html) {
var output = String(html || '');
output = output.replace(/<script[\s\S]*?<\/script>/gi, '');
output = output.replace(/<style[\s\S]*?<\/style>/gi, '');
return output.replace(/<[^>]+>/g, function (tag) {
var match = tag.match(/^<\s*(\/?)\s*([a-z0-9]+)([\s\S]*?)(\/?)>$/i);
if (!match) {
return '';
}
var closing = Boolean(match[1]);
var name = String(match[2] || '').toLowerCase();
var attrText = String(match[3] || '');
if (ALLOWED_RICH_TEXT_TAGS.indexOf(name) === -1) {
return '';
}
if (closing) {
return '</' + name + '>';
}
return '<' + name + sanitizeRichTextAttributes(name, attrText) + '>';
});
}
// Render a single Editor.js block to HTML.
function renderEditorJsBlock(block) {
if (!block || !block.type || !block.data) {
return '';
}
if (block.type === 'header') {
var level = Math.max(1, Math.min(6, Number(block.data.level || 2)));
return '<h' + level + '>' + sanitizeRichText(block.data.text || '') + '</h' + level + '>';
}
if (block.type === 'list') {
var tag = block.data.style === 'ordered' ? 'ol' : 'ul';
var items = Array.isArray(block.data.items) ? block.data.items : [];
return '<' + tag + ' style="list-style-type:' + (tag === 'ol' ? 'decimal' : 'disc') + ';padding-left:1.4em;">' + items.map(function (item) { return renderEditorJsListItem(item, tag); }).join('') + '</' + tag + '>';
}
if (block.type === 'delimiter') {
return '<hr />';
}
if (block.type === 'code') {
return '<pre><code>' + escapeHtml(block.data.code || '') + '</code></pre>';
}
if (block.type === 'table') {
return renderEditorJsTable(block.data);
}
if (block.type === 'paragraph') {
return '<p>' + sanitizeRichText(block.data.text || '') + '</p>';
}
return '';
}
// Render a list item and any nested sub-items.
function renderEditorJsListItem(item, tag) {
if (item && typeof item === 'object') {
var content = item.content !== undefined ? item.content : (item.text !== undefined ? item.text : item.value !== undefined ? item.value : '');
var children = Array.isArray(item.items) ? item.items : Array.isArray(item.subItems) ? item.subItems : Array.isArray(item.children) ? item.children : [];
var nested = children.length ? '<' + tag + ' style="list-style-type:' + (tag === 'ol' ? 'decimal' : 'disc') + ';padding-left:1.4em;">' + children.map(function (child) { return renderEditorJsListItem(child, tag); }).join('') + '</' + tag + '>' : '';
return '<li>' + sanitizeRichText(content || '') + nested + '</li>';
}
return '<li>' + sanitizeRichText(item || '') + '</li>';
}
// Render an Editor.js table block.
function renderEditorJsTable(data) {
var rows = Array.isArray(data.content) ? data.content : Array.isArray(data.rows) ? data.rows : [];
if (!rows.length) {
return '';
}
var hasHeadings = Boolean(data.withHeadings);
var tableRows = rows.map(function (row, rowIndex) {
var cells = Array.isArray(row) ? row : [];
var cellTag = hasHeadings && rowIndex === 0 ? 'th' : 'td';
var cellAttrs = cellTag === 'th' ? ' scope="col"' : '';
return '<tr>' + cells.map(function (cell) {
return '<' + cellTag + cellAttrs + '>' + sanitizeRichText(cell || '') + '</' + cellTag + '>';
}).join('') + '</tr>';
}).join('');
return '<table class="ck-content-table">' + tableRows + '</table>';
}
function wrapRichTextParagraph(html) {
var raw = String(html || '').trim();
if (!raw) {
return '';
}
if (/^<\s*(?:p|div|h[1-6]|ul|ol|pre|table|blockquote|figure|hr|li)\b/i.test(raw)) {
return raw;
}
return '<p>' + raw + '</p>';
}
// Render Editor.js JSON or plain content safely.
function renderEditorJsContent(value) {
if (value && typeof value === 'object') {
if (Array.isArray(value.blocks)) {
return value.blocks.map(renderEditorJsBlock).join('');
}
if (value.value !== undefined) {
return renderEditorJsContent(value.value);
}
}
var raw = String(value || '');
try {
var parsed = JSON.parse(raw);
if (parsed && Array.isArray(parsed.blocks)) {
return parsed.blocks.map(renderEditorJsBlock).join('');
}
} catch (_error) {
// fall through to legacy HTML rendering
}
return wrapRichTextParagraph(sanitizeRichText(raw));
}
// Parse string values that look like JSON.
function parseMaybeJson(value) {
if (typeof value !== 'string') {
return value;
}
var raw = value.trim();
if (!raw) {
return value;
}
if (raw.charAt(0) !== '{' && raw.charAt(0) !== '[') {
return value;
}
try {
return JSON.parse(raw);
} catch (_error) {
return value;
}
}
// Normalize a slide region's stored content value.
function normalizeContentValue(value) {
var normalized;
if (!value || typeof value !== 'object') {
return {
type: 'text',
value: parseMaybeJson(value)
};
}
normalized = {};
Object.keys(value).forEach(function (key) {
normalized[key] = value[key];
});
if (normalized.value !== undefined) {
normalized.value = parseMaybeJson(normalized.value);
}
if (normalized.font_family !== undefined && normalized.font_family !== null) {
normalized.font_family = sanitizeFontFamily(normalized.font_family);
}
if (normalized.font_size !== undefined && normalized.font_size !== null) {
normalized.font_size = sanitizeFontSize(normalized.font_size);
}
if (normalized.font_color !== undefined && normalized.font_color !== null) {
normalized.font_color = sanitizeTextColor(normalized.font_color);
}
if (!normalized.type) {
normalized.type = 'text';
}
return normalized;
}
// Normalize a slide and its nested region content.
function normalizeSlide(slide) {
var normalized = {};
var content;
Object.keys(slide || {}).forEach(function (key) {
normalized[key] = slide[key];
});
normalized.content = {};
content = slide && slide.content && typeof slide.content === 'object' ? slide.content : {};
Object.keys(content).forEach(function (regionKey) {
normalized.content[regionKey] = normalizeContentValue(content[regionKey]);
});
var normalized = playerRenderingShared.normalizeSlide(slide);
if (normalized.use_video_duration && normalized.template && Array.isArray(normalized.template.regions)) {
normalized.template.regions.forEach(function (region) {
if (region && region.region_type === 'video' && normalized.content[region.region_key]) {
@@ -520,6 +239,8 @@ function getTemplateLayout(template) {
baseStyle: baseStyle,
pixelWidth: pixelWidth,
pixelHeight: pixelHeight,
canvasWidth: templateCanvas.width,
canvasHeight: templateCanvas.height,
fontFamily: region.font_family || null,
fontSize: region.font_size || null,
fontColor: region.font_color || null,
@@ -530,7 +251,7 @@ function getTemplateLayout(template) {
var layout = {
canvasWidth: canvasSize.width,
canvasHeight: canvasSize.height,
background: template.background_image_path ? '<img class="template-background" src="' + escapeHtml(template.background_image_path) + '" alt="" />' : '',
background: template.background_image_path ? '<img class="template-background" src="' + escapeHtml(selectPlayerMediaSource(template.background_image_path, template.background_image_variants, Math.max(templateCanvas.width, templateCanvas.height))) + '" alt="" />' : '',
backgroundColor: template.background_color || '#111111',
backgroundGradient: template.background_gradient || '',
regions: regions
@@ -641,7 +362,8 @@ function renderTemplateSlideMarkup(slide) {
animationConfig: normalizePlayerAnimationConfig(region.animationJson)
});
}).join('') : '';
const stageStyle = layout ? buildBackdropStyle(layout.backgroundColor || '#111111', template.background_image_path, layout.backgroundGradient) : '';
const backgroundPath = layout ? selectPlayerMediaSource(template.background_image_path, template.background_image_variants, Math.max(layout.canvasWidth, layout.canvasHeight)) : '';
const stageStyle = layout ? buildBackdropStyle(layout.backgroundColor || '#111111', backgroundPath, layout.backgroundGradient) : '';
if (layout) {
setPlayerCanvasDimensions(layout.canvasWidth, layout.canvasHeight);
}
+187 -2
View File
@@ -1,6 +1,56 @@
// Slide transition timing, cancellation, and outgoing animation coordination.
var slideOutroTimers = [];
var slideTransitionRequestId = 0;
var playerWatchdogTimer = null;
var playerLastProgressAt = Date.now();
var playerWatchdogRecoveryKey = 'pulse-signage-player-watchdog-recoveries';
function markPlayerPlaybackProgress() {
playerLastProgressAt = Date.now();
try {
window.sessionStorage.removeItem(playerWatchdogRecoveryKey);
} catch (_error) {
// Session storage may be unavailable in restricted kiosk profiles.
}
}
function startPlayerWatchdog() {
if (playerWatchdogTimer || typeof window.setInterval !== 'function') {
return;
}
playerWatchdogTimer = window.setInterval(function () {
if (document.visibilityState === 'hidden' || isPaused || !slides.length || !lastRenderedSlide) {
return;
}
var expectedDurationMs = Math.max(10000, Number(getSlideAdvanceDelay(lastRenderedSlide) || 10000));
if (Date.now() - playerLastProgressAt <= (expectedDurationMs * 2) + 15000) {
return;
}
var recoveryCount = 0;
try {
recoveryCount = Number(window.sessionStorage.getItem(playerWatchdogRecoveryKey) || 0);
} catch (_error) {
recoveryCount = 0;
}
if (recoveryCount >= 2) {
return;
}
try {
window.sessionStorage.setItem(playerWatchdogRecoveryKey, String(recoveryCount + 1));
} catch (_error) {
// Continue with the reload if session storage is unavailable.
}
if (typeof logDebug === 'function') {
logDebug('Player watchdog is reloading a stalled playback page.', 'No slide progress detected.', 'error');
}
window.location.reload();
}, 30000);
}
// Cancel the current slide-advance timer.
function clearSlideTimer() {
@@ -92,13 +142,148 @@ function getSlideHoldDelay(delayMs) {
// Calculate the configured time from slide entry to the next transition.
function getSlideAdvanceDelay(slide) {
return getSlideHoldDelay(Math.max(1, Number(slide && slide.duration_seconds || 10)) * 1000);
var durationMs = Math.max(1, Number(slide && slide.duration_seconds || 10)) * 1000;
// Video-duration slides start immediately; other slides start midway through the fade.
if (slide && slide.use_video_duration) {
return currentPlaylistFadeBetweenSlides
? getSlideHoldDelay(Math.max(1, durationMs - slideFadeLengthMs))
: getSlideHoldDelay(durationMs);
}
if (currentPlaylistFadeBetweenSlides) {
return getSlideHoldDelay(Math.max(1, durationMs - slideFadeOffsetMs));
}
return getSlideHoldDelay(durationMs);
}
// Cancel any pending fade-transition cleanup.
// Cancel fade cleanup and invalidate callbacks already queued for an older transition.
function clearSlideTransitionTimer() {
slideTransitionRequestId += 1;
if (slideTransitionTimer) {
window.clearTimeout(slideTransitionTimer);
slideTransitionTimer = null;
}
}
// Swap slide markup with optional fade animation.
function renderSlideMarkup(markup, shouldFade, mediaDelayMs) {
clearSlideTransitionTimer();
var transitionRequestId = slideTransitionRequestId;
if (typeof deferVideoSourcesForPreload === 'function') {
markup = deferVideoSourcesForPreload(markup);
}
// Stop outgoing region activity before overlapping its shell with the incoming slide.
destroyRegionInstances(app);
if (typeof destroySlideMedia === 'function') {
destroySlideMedia(app, { preserveVideoFrame: shouldFade });
}
function schedulePostRenderSetup(root, delayMs) {
if (!root) {
return;
}
if (root.isConnected === false) {
return;
}
initializeRegionInstances(root);
if (typeof initializeSlideMedia === 'function') {
initializeSlideMedia(root, delayMs);
}
if (typeof playRegionAnimations === 'function') {
playRegionAnimations(root, 'intro');
}
}
if (!shouldFade) {
app.innerHTML = markup;
if (typeof promotePreloadedVideoElements === 'function') {
promotePreloadedVideoElements(app, lastRenderedSlide && lastRenderedSlide.id);
}
schedulePostRenderSetup(app, 0);
return app.firstElementChild;
}
var topLevelChildren = Array.prototype.slice.call(app.children || []);
var existingShells = topLevelChildren.filter(function (child) {
return child && child.classList && child.classList.contains('slide-shell');
});
var previousShell = existingShells.length ? existingShells[existingShells.length - 1] : app.firstElementChild;
if (existingShells.length > 1) {
// Rapid navigation can interrupt cleanup, leaving older crossfade shells behind.
existingShells.slice(0, -1).forEach(function (shell) {
if (shell && shell.parentNode) {
if (typeof destroySlideMedia === 'function') {
destroySlideMedia(shell);
}
shell.parentNode.removeChild(shell);
}
});
}
var nextShell = document.createElement('div');
nextShell.className = 'slide-shell slide-shell-entering';
nextShell.style.zIndex = '0';
nextShell.style.opacity = '0';
nextShell.innerHTML = markup;
if (typeof promotePreloadedVideoElements === 'function') {
promotePreloadedVideoElements(nextShell, lastRenderedSlide && lastRenderedSlide.id);
}
if (!previousShell || (previousShell.classList && previousShell.classList.contains('empty'))) {
if (previousShell && typeof destroySlideMedia === 'function') {
destroySlideMedia(app);
}
app.innerHTML = '';
nextShell.style.opacity = '1';
nextShell.classList.remove('slide-shell-entering');
nextShell.classList.add('is-visible');
app.appendChild(nextShell);
schedulePostRenderSetup(nextShell, mediaDelayMs === undefined ? slideFadeOffsetMs : mediaDelayMs);
return nextShell;
}
if (!previousShell.classList.contains('slide-shell')) {
previousShell.classList.add('slide-shell');
}
previousShell.classList.remove('is-visible');
previousShell.classList.add('is-exiting');
previousShell.style.zIndex = '1';
previousShell.style.opacity = '1';
app.appendChild(nextShell);
// Commit the initial opacity before starting the crossfade on the next paint frame.
window.requestAnimationFrame(function () {
if (transitionRequestId !== slideTransitionRequestId) {
return;
}
nextShell.style.opacity = '1';
previousShell.style.opacity = '0';
nextShell.classList.remove('slide-shell-entering');
nextShell.classList.add('is-visible');
schedulePostRenderSetup(nextShell, mediaDelayMs === undefined ? slideFadeOffsetMs : mediaDelayMs);
});
slideTransitionTimer = window.setTimeout(function () {
if (transitionRequestId !== slideTransitionRequestId) {
return;
}
if (previousShell && previousShell.parentNode) {
if (typeof destroySlideMedia === 'function') {
destroySlideMedia(previousShell);
}
previousShell.parentNode.removeChild(previousShell);
}
if (nextShell) {
nextShell.style.zIndex = '1';
nextShell.style.opacity = '1';
nextShell.classList.remove('slide-shell-entering');
nextShell.classList.add('is-visible');
}
slideTransitionTimer = null;
}, slideFadeLengthMs);
return nextShell;
}
+36 -1
View File
@@ -31,4 +31,39 @@
get: get,
list: list
};
}());
}());
function getPlayerRegionModules() {
if (!window.pulsePlayerRegionTypes || typeof window.pulsePlayerRegionTypes.list !== 'function') {
return [];
}
return window.pulsePlayerRegionTypes.list();
}
function runRegionLifecycle(root, lifecycleName) {
if (!root) {
return;
}
getPlayerRegionModules().forEach(function (entry) {
var module = entry && entry.definition ? entry.definition : null;
if (!module || typeof module[lifecycleName] !== 'function') {
return;
}
try {
module[lifecycleName](root);
} catch (_error) {
// Keep slide rendering resilient if a region lifecycle hook fails.
}
});
}
function destroyRegionInstances(root) {
runRegionLifecycle(root, 'destroyRegion');
}
function initializeRegionInstances(root) {
runRegionLifecycle(root, 'initRegion');
}