198 lines
6.1 KiB
HTML
198 lines
6.1 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 slideFadeDurationMs = 560;
|
|
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, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
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) {
|
|
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> |