Files
pulse-signage/src/player/player-page.script.html
T
lzstealth 8c0c22156e
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m15s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 34s
Release v2.10.6
2026-08-29 14:59:06 +01:00

201 lines
6.3 KiB
HTML

<!-- Player page bootstrap and browser-side playback lifecycle. -->
<script>
const slug = {{SLUG_JSON}};
let initialData = {{INITIAL_DATA_JSON}};
window.slug = slug;
window.initialData = initialData;
const app = document.getElementById('app');
let slides = Array.isArray(initialData && initialData.slides) ? initialData.slides.map(normalizeSlide) : [];
let currentPlaylistSignature = '';
let currentPlaylistEtag = '';
let currentPlaylistFadeBetweenSlides = false;
let currentPlaylistSkipUnavailableRtmp = false;
let pendingPlaylistUpdate = null;
let activeSlidesCacheKey = '';
let activeSlidesCacheValue = [];
let slideMarkupCache = Object.create(null);
let templateLayoutCache = Object.create(null);
let templateRenderPlanCache = Object.create(null);
let videoRegionRenderVersion = 0;
let renderCacheViewportKey = '';
let index = 0;
let timer = null;
let slideTransitionTimer = null;
let slideRenderRequestId = 0;
let preloadContainer = null;
let preloadSignature = '';
let commandSocket = null;
let commandReconnectTimer = null;
let commandStateTimer = null;
let viewportRenderTimer = null;
let refreshRetryTimer = null;
let refreshRetryDelayMs = 0;
let playlistRefreshTimer = null;
let commandClientId = null;
let screenWakeLock = null;
let screenWakeLockRequestPromise = null;
let screenWakeLockRetryTimer = null;
let lastRenderedSlide = null;
let lastRenderedViewKey = '';
let isPaused = false;
let isBlackout = false;
let pausedRemainingMs = null;
let slideExpiresAt = null;
const slideFadeLengthMs = 560;
const slideFadeOffsetMs = slideFadeLengthMs / 2;
const commandSocketPath = '/ws/screens/' + encodeURIComponent(slug);
const commandClientStorageKey = 'pulse-signage-player-client-id';
const playlistSnapshotStorageKey = 'pulse-signage-player-playlist-snapshot:' + slug;
const initialPlaylistSnapshot = loadPlaylistSnapshot();
let offlineBanner = null;
let offlineBannerVisible = false;
{{{REGION_SCRIPTS}}}
// Escape text before inserting it into HTML.
function escapeHtml(value) {
return String(value ?? '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function logDebug(message, details, level) {
var logger = level === 'error' ? console.error : console.info;
var timestamp = new Date().toISOString();
var timestampedMessage = '[' + timestamp + '] ' + String(message || '');
if (details) {
logger(timestampedMessage, details);
} else {
logger(timestampedMessage);
}
}
// Render the empty-state message into the player root.
function renderEmpty(message) {
var markup = '<div class="empty">' + escapeHtml(message) + '</div>';
if (currentPlaylistFadeBetweenSlides && typeof renderSlideMarkup === 'function') {
renderSlideMarkup(markup, true);
return;
}
app.innerHTML = markup;
}
// Announce the player to the command websocket.
function sendPlayerBootstrapState(socket) {
if (!socket || socket.readyState !== WebSocket.OPEN) {
return;
}
var clientName = getOnboardingClientName();
socket.send(JSON.stringify({
type: 'state',
clientId: getCommandClientId(),
clientName: clientName || null,
deviceId: getOnboardingDeviceId() || null,
userAgent: window.navigator.userAgent || '',
page: window.location.href,
viewport: getCurrentViewport(),
paused: isPaused,
blackout: isBlackout,
currentSlide: null
}));
}
window.addEventListener('error', function (event) {
logDebug('Player script error.', String(event.message || event.error || 'unknown error'), 'error');
});
window.addEventListener('unhandledrejection', function (event) {
logDebug('Player promise error.', String(event.reason || 'unknown error'), 'error');
});
window.addEventListener('beforeunload', function () {
if (commandSocket) {
try {
commandSocket.close();
} catch (_error) {
// ignore shutdown errors
}
}
});
window.addEventListener('resize', function () {
scheduleViewportRenderUpdate();
scheduleCommandStateUpdate();
});
window.addEventListener('orientationchange', function () {
scheduleViewportRenderUpdate();
scheduleCommandStateUpdate();
});
window.addEventListener('online', function () {
syncOfflineBanner();
markRefreshHealthy();
refresh();
});
window.addEventListener('offline', function () {
syncOfflineBanner();
scheduleRefreshRetry();
});
document.addEventListener('visibilitychange', function () {
syncOfflineBanner();
syncScreenWakeLock();
if (document.visibilityState === 'visible') {
markRefreshHealthy();
refresh();
}
});
window.addEventListener('pageshow', function () {
syncScreenWakeLock();
});
window.addEventListener('pagehide', function () {
releaseScreenWakeLock();
});
if (slides.length) {
if (!currentPlaylistSignature) {
currentPlaylistSignature = getPlaylistRevision(initialData && initialData.slides ? initialData : { slides: slides });
}
if (initialData && initialData.playlist) {
currentPlaylistFadeBetweenSlides = Boolean(initialData.playlist.fade_between_slides);
currentPlaylistSkipUnavailableRtmp = Boolean(initialData.playlist.skip_unavailable_rtmp);
}
savePlaylistSnapshot({
slides: slides,
signature: currentPlaylistSignature,
fadeBetweenSlides: currentPlaylistFadeBetweenSlides,
skipUnavailableRtmp: currentPlaylistSkipUnavailableRtmp,
etag: currentPlaylistEtag
});
syncWebpagePreloads(getActiveSlidesFrom(slides), index);
if (typeof syncRtmpWarmups === 'function') {
syncRtmpWarmups(getActiveSlidesFrom(slides), index);
}
syncBlackoutState();
showCurrent();
refresh();
} else {
if (initialPlaylistSnapshot && applyPlaylistSnapshot(initialPlaylistSnapshot)) {
currentPlaylistSkipUnavailableRtmp = Boolean(initialPlaylistSnapshot.skipUnavailableRtmp);
showCurrent();
}
syncBlackoutState();
refresh();
}
syncOfflineBanner();
syncScreenWakeLock();
playlistRefreshTimer = window.setInterval(function () {
if (document.visibilityState !== 'hidden') {
refresh();
}
}, 60 * 1000);
connectCommandSocket();
</script>