169 lines
5.0 KiB
JavaScript
169 lines
5.0 KiB
JavaScript
// Show or hide the offline status banner.
|
|
function setOfflineBannerVisible(visible, message) {
|
|
if (window.__pulseThumbnailPreview) {
|
|
return;
|
|
}
|
|
var normalizedVisible = Boolean(visible);
|
|
var bannerMessage = String(message || 'Offline mode: using cached playlist.').trim();
|
|
if (normalizedVisible) {
|
|
if (!offlineBanner) {
|
|
offlineBanner = document.createElement('div');
|
|
offlineBanner.className = 'player-offline-banner';
|
|
offlineBanner.style.position = 'fixed';
|
|
offlineBanner.style.right = '0';
|
|
offlineBanner.style.bottom = '0';
|
|
offlineBanner.style.left = 'auto';
|
|
offlineBanner.style.top = 'auto';
|
|
offlineBanner.style.width = '1.25rem';
|
|
offlineBanner.style.height = '1.25rem';
|
|
offlineBanner.style.zIndex = '9999';
|
|
offlineBanner.style.background = 'linear-gradient(135deg, #ff4d4f 0%, #b00020 100%)';
|
|
offlineBanner.style.clipPath = 'circle(100% at 100% 100%)';
|
|
offlineBanner.style.webkitClipPath = 'circle(100% at 100% 100%)';
|
|
offlineBanner.style.boxShadow = '0 0 0 1px rgba(0, 0, 0, 0.16), 0 4px 12px rgba(0, 0, 0, 0.18)';
|
|
offlineBanner.style.pointerEvents = 'none';
|
|
document.body.appendChild(offlineBanner);
|
|
}
|
|
offlineBanner.textContent = '';
|
|
offlineBanner.setAttribute('aria-label', bannerMessage);
|
|
offlineBanner.setAttribute('role', 'img');
|
|
offlineBanner.title = bannerMessage;
|
|
offlineBannerVisible = true;
|
|
return;
|
|
}
|
|
|
|
offlineBannerVisible = false;
|
|
if (offlineBanner && offlineBanner.parentNode) {
|
|
offlineBanner.parentNode.removeChild(offlineBanner);
|
|
}
|
|
offlineBanner = null;
|
|
}
|
|
|
|
// Update the offline banner based on connectivity or playlist availability.
|
|
function syncOfflineBanner() {
|
|
if (window.__pulseThumbnailPreview) {
|
|
return;
|
|
}
|
|
if (!window.navigator.onLine) {
|
|
setOfflineBannerVisible(true, 'Offline mode: using cached playlist.');
|
|
return;
|
|
}
|
|
if (offlineBannerVisible) {
|
|
setOfflineBannerVisible(false);
|
|
}
|
|
}
|
|
|
|
// Clear any pending playlist refresh retry.
|
|
function clearRefreshRetry() {
|
|
if (refreshRetryTimer) {
|
|
window.clearTimeout(refreshRetryTimer);
|
|
refreshRetryTimer = null;
|
|
}
|
|
}
|
|
|
|
// Retry playlist refresh with a short backoff while the player is offline.
|
|
function scheduleRefreshRetry() {
|
|
if (window.__pulseThumbnailPreview) {
|
|
return;
|
|
}
|
|
if (refreshRetryTimer) {
|
|
return;
|
|
}
|
|
if (window.navigator.onLine === false) {
|
|
refreshRetryDelayMs = refreshRetryDelayMs ? Math.min(refreshRetryDelayMs * 2, 15000) : 3000;
|
|
} else {
|
|
refreshRetryDelayMs = refreshRetryDelayMs ? Math.min(refreshRetryDelayMs * 2, 8000) : 3000;
|
|
}
|
|
refreshRetryTimer = window.setTimeout(function () {
|
|
refreshRetryTimer = null;
|
|
refresh();
|
|
}, refreshRetryDelayMs);
|
|
}
|
|
|
|
function clearScreenWakeLockRetry() {
|
|
if (screenWakeLockRetryTimer) {
|
|
window.clearTimeout(screenWakeLockRetryTimer);
|
|
screenWakeLockRetryTimer = null;
|
|
}
|
|
}
|
|
|
|
function supportsScreenWakeLock() {
|
|
return Boolean(window.navigator && window.navigator.wakeLock && typeof window.navigator.wakeLock.request === 'function');
|
|
}
|
|
|
|
function scheduleScreenWakeLockRetry() {
|
|
if (screenWakeLockRetryTimer) {
|
|
return;
|
|
}
|
|
if (!supportsScreenWakeLock() || document.visibilityState !== 'visible') {
|
|
return;
|
|
}
|
|
screenWakeLockRetryTimer = window.setTimeout(function () {
|
|
screenWakeLockRetryTimer = null;
|
|
acquireScreenWakeLock();
|
|
}, 2000);
|
|
}
|
|
|
|
function releaseScreenWakeLock() {
|
|
if (screenWakeLock && typeof screenWakeLock.release === 'function') {
|
|
try {
|
|
screenWakeLock.release();
|
|
} catch (_error) {
|
|
// ignore wake lock release errors
|
|
}
|
|
}
|
|
screenWakeLock = null;
|
|
screenWakeLockRequestPromise = null;
|
|
clearScreenWakeLockRetry();
|
|
}
|
|
|
|
function acquireScreenWakeLock() {
|
|
if (!supportsScreenWakeLock() || document.visibilityState !== 'visible') {
|
|
return Promise.resolve(null);
|
|
}
|
|
if (screenWakeLockRequestPromise) {
|
|
return screenWakeLockRequestPromise;
|
|
}
|
|
if (screenWakeLock && screenWakeLock.released === false) {
|
|
return Promise.resolve(screenWakeLock);
|
|
}
|
|
|
|
screenWakeLockRequestPromise = window.navigator.wakeLock.request('screen').then(function (sentinel) {
|
|
screenWakeLock = sentinel;
|
|
screenWakeLock.addEventListener('release', function () {
|
|
screenWakeLock = null;
|
|
if (document.visibilityState === 'visible') {
|
|
scheduleScreenWakeLockRetry();
|
|
}
|
|
});
|
|
clearScreenWakeLockRetry();
|
|
return screenWakeLock;
|
|
}).catch(function (error) {
|
|
screenWakeLock = null;
|
|
if (error && error.name !== 'NotAllowedError') {
|
|
scheduleScreenWakeLockRetry();
|
|
}
|
|
return null;
|
|
}).finally(function () {
|
|
screenWakeLockRequestPromise = null;
|
|
});
|
|
|
|
return screenWakeLockRequestPromise;
|
|
}
|
|
|
|
function syncScreenWakeLock() {
|
|
if (!supportsScreenWakeLock()) {
|
|
return;
|
|
}
|
|
if (document.visibilityState === 'visible') {
|
|
acquireScreenWakeLock();
|
|
return;
|
|
}
|
|
releaseScreenWakeLock();
|
|
}
|
|
|
|
// Clear the retry cadence after a successful refresh.
|
|
function markRefreshHealthy() {
|
|
refreshRetryDelayMs = 0;
|
|
clearRefreshRetry();
|
|
} |