Files
pulse-signage/src/player/player-page.script.html
T
2026-07-25 15:11:41 +01:00

182 lines
5.5 KiB
HTML

<script>
const slug = {{SLUG_JSON}};
const initialData = {{INITIAL_DATA_JSON}};
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 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 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 slideFadeDurationMs = 560;
const commandSocketPath = '/ws/screens/' + encodeURIComponent(slug);
const commandClientStorageKey = 'pulse-signage-player-client-id:' + slug;
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;
if (details) {
logger(message, details);
} else {
logger(message);
}
}
// Render the empty-state message into the player root.
function renderEmpty(message) {
app.innerHTML = '<div class="empty">' + escapeHtml(message) + '</div>';
}
// Announce the player to the command websocket.
function sendCommandHello(socket) {
if (!socket || socket.readyState !== WebSocket.OPEN) {
return;
}
var clientName = getOnboardingClientName();
socket.send(JSON.stringify({
type: 'hello',
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 (initialPlaylistSnapshot && initialPlaylistSnapshot.slides.length) {
applyPlaylistSnapshot(initialPlaylistSnapshot);
}
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 {
syncBlackoutState();
refresh();
}
syncOfflineBanner();
syncScreenWakeLock();
connectCommandSocket();
</script>