// Announcement overlay polling and rendering helpers. (function () { if (window.__pulseThumbnailPreview) { return; } var announcementLayer = null; var announcementRefreshTimer = null; var announcementPollTimer = null; var announcementSocket = null; var announcementReconnectTimer = null; var announcementRequestId = 0; var announcementEtag = ''; var announcementHideTimer = null; var announcementRevealTimer = null; var announcementFadeDurationMs = 220; var announcementSocketPath = '/ws/screens/' + encodeURIComponent(window.slug || '') + '/announcements'; function normalizeAnnouncementType(value) { var normalized = String(value || '').trim().toLowerCase(); if (normalized === 'center-card') { return 'fullscreen'; } if (['lower-third', 'fullscreen', 'top-banner'].indexOf(normalized) !== -1) { return normalized; } return 'lower-third'; } function getAnnouncementLayer() { if (announcementLayer) { return announcementLayer; } announcementLayer = document.getElementById('player-announcement-layer'); if (!announcementLayer) { announcementLayer = document.createElement('div'); announcementLayer.id = 'player-announcement-layer'; announcementLayer.className = 'player-announcement-layer'; announcementLayer.setAttribute('aria-live', 'polite'); announcementLayer.setAttribute('aria-atomic', 'true'); document.body.appendChild(announcementLayer); } return announcementLayer; } function clearAnnouncementHideTimer() { if (announcementHideTimer) { window.clearTimeout(announcementHideTimer); announcementHideTimer = null; } } function clearAnnouncementRevealTimer() { if (announcementRevealTimer) { window.cancelAnimationFrame(announcementRevealTimer); announcementRevealTimer = null; } } function activateInjectedScripts(layer) { if (!layer) { return; } var scripts = layer.querySelectorAll('script'); scripts.forEach(function (originalScript) { var replacement = document.createElement('script'); Array.prototype.slice.call(originalScript.attributes || []).forEach(function (attribute) { replacement.setAttribute(attribute.name, attribute.value); }); replacement.text = originalScript.textContent || ''; originalScript.parentNode.replaceChild(replacement, originalScript); }); } function hideAnnouncement() { var layer = getAnnouncementLayer(); clearAnnouncementRevealTimer(); clearAnnouncementHideTimer(); layer.classList.add('is-closing'); layer.classList.remove('is-visible'); announcementHideTimer = window.setTimeout(function () { layer.innerHTML = ''; layer.hidden = true; layer.classList.remove('is-closing'); announcementHideTimer = null; }, announcementFadeDurationMs); } function renderAnnouncement(announcement) { var layer = getAnnouncementLayer(); if (!announcement || !announcement.message) { hideAnnouncement(); return; } var type = normalizeAnnouncementType(announcement.announcement_type); clearAnnouncementRevealTimer(); clearAnnouncementHideTimer(); layer.hidden = false; layer.className = 'player-announcement-layer player-announcement-layer--' + type; layer.classList.remove('is-closing'); layer.innerHTML = window.renderPlayerAnnouncementMarkup ? window.renderPlayerAnnouncementMarkup(announcement) : ''; activateInjectedScripts(layer); announcementRevealTimer = window.requestAnimationFrame(function () { announcementRevealTimer = null; layer.classList.add('is-visible'); }); } function scheduleAnnouncementRefresh(delayMs) { if (announcementRefreshTimer) { window.clearTimeout(announcementRefreshTimer); } announcementRefreshTimer = window.setTimeout(function () { announcementRefreshTimer = null; void refreshAnnouncementOverlay(); }, Math.max(0, Number(delayMs || 0))); } async function refreshAnnouncementOverlay() { var requestId = ++announcementRequestId; var request = new XMLHttpRequest(); var url = window.location.origin + '/api/screens/' + encodeURIComponent(window.slug || '') + '/announcement?ts=' + Date.now(); request.open('GET', url, true); request.timeout = 2500; if (window.__pulsePageAuthToken) { request.setRequestHeader('x-pulse-page-auth', window.__pulsePageAuthToken); } if (announcementEtag) { request.setRequestHeader('If-None-Match', announcementEtag); } request.onreadystatechange = function () { if (request.readyState !== 4 || requestId !== announcementRequestId) { return; } if (request.status === 304) { return; } if (request.status < 200 || request.status >= 300) { scheduleAnnouncementRefresh(15000); return; } try { var responseEtag = String(request.getResponseHeader('ETag') || '').trim(); var data = JSON.parse(request.responseText || '{}'); var announcement = data && data.announcement ? data.announcement : null; if (responseEtag) { announcementEtag = responseEtag; } if (!announcement) { hideAnnouncement(); } else { renderAnnouncement(announcement); } } catch (_error) { scheduleAnnouncementRefresh(15000); } }; request.onerror = function () { scheduleAnnouncementRefresh(15000); }; request.ontimeout = function () { scheduleAnnouncementRefresh(15000); }; request.send(); } function scheduleAnnouncementPolling() { if (announcementPollTimer) { window.clearInterval(announcementPollTimer); } announcementPollTimer = window.setInterval(function () { void refreshAnnouncementOverlay(); }, 15000); } function scheduleAnnouncementReconnect() { if (announcementReconnectTimer) { return; } announcementReconnectTimer = window.setTimeout(function () { announcementReconnectTimer = null; connectAnnouncementSocket(); }, 5000); } function handleAnnouncementSocketMessage(rawMessage) { var payload; try { payload = JSON.parse(String(rawMessage || '')); } catch (_error) { return; } if (!payload || payload.type !== 'announcement-refresh') { return; } void refreshAnnouncementOverlay(); } function connectAnnouncementSocket() { if (!window.WebSocket) { return; } if (announcementSocket && (announcementSocket.readyState === WebSocket.OPEN || announcementSocket.readyState === WebSocket.CONNECTING)) { return; } var socket = new WebSocket(new URL(announcementSocketPath, window.location.origin).toString()); announcementSocket = socket; socket.onopen = function () { void refreshAnnouncementOverlay(); }; socket.onmessage = function (event) { handleAnnouncementSocketMessage(event.data); }; socket.onclose = function () { announcementSocket = null; scheduleAnnouncementReconnect(); }; socket.onerror = function () { try { socket.close(); } catch (_error) { // ignore socket close errors } }; } window.refreshAnnouncementOverlay = refreshAnnouncementOverlay; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function () { void refreshAnnouncementOverlay(); scheduleAnnouncementPolling(); connectAnnouncementSocket(); }, { once: true }); } else { void refreshAnnouncementOverlay(); scheduleAnnouncementPolling(); connectAnnouncementSocket(); } window.addEventListener('focus', function () { void refreshAnnouncementOverlay(); }); })();