263 lines
7.8 KiB
JavaScript
263 lines
7.8 KiB
JavaScript
// 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 (typeof getCommandClientId === 'function') {
|
|
request.setRequestHeader('x-pulse-client-id', getCommandClientId());
|
|
}
|
|
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();
|
|
});
|
|
})(); |