Release v2.2.0
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
// Announcement overlay polling and rendering helpers.
|
||||
|
||||
(function () {
|
||||
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 socketUrl = new URL(announcementSocketPath, window.location.origin);
|
||||
if (window.__pulsePageAuthToken) {
|
||||
socketUrl.searchParams.set('auth', window.__pulsePageAuthToken);
|
||||
}
|
||||
|
||||
var socket = new WebSocket(socketUrl.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();
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user