- Introduced a shared HLS configuration in hlsConfig.ts to standardize playback settings across players.

- Implemented a one-shot live catch-up mechanism in `liveCatchup.ts` to enhance user experience during live streaming.
- Created a global radio state management system in `radioState.ts` to maintain playback continuity and metadata across different components and tabs.
- Bumped version 1.0 -> 1.1
This commit is contained in:
2026-02-27 10:37:03 -05:00
parent 8263d582a6
commit 4c93a51cc7
16 changed files with 3449 additions and 430 deletions

View File

@@ -21,8 +21,8 @@ const envBadge = ENVIRONMENT === 'Dev' ? 'DEV' : null;
{!whitelabel && <RandomMsg client:only="react" />}
<div class="footer-version" data-build-time={buildTime} title={`Built: ${buildTime}`}>
<span class="version-pill">
{envBadge && <span class="env-dot" title="Development build"></span>}
{!envBadge && <span class="version-dot"></span>}
{envBadge && <span class="env-dot api-status-dot" title="Development build"></span>}
{!envBadge && <span class="version-dot api-status-dot"></span>}
<span class="version-text">{versionDisplay}:{buildNumber}</span>
<span class="build-time-text" aria-hidden="true"></span>
</span>
@@ -30,6 +30,34 @@ const envBadge = ENVIRONMENT === 'Dev' ? 'DEV' : null;
</div>
<script>
function applyApiStatus(reachable) {
const dot = document.querySelector('.api-status-dot');
if (!dot) return;
dot.classList.toggle('api-offline', reachable === false);
}
function bindApiStatusListener() {
const guard = window as any;
if (guard.__footerApiStatusBound) return;
guard.__footerApiStatusBound = true;
// Restore last known state across Astro client navigation
try {
const cached = sessionStorage.getItem('api-reachable');
if (cached === '0') applyApiStatus(false);
if (cached === '1') applyApiStatus(true);
} catch {}
window.addEventListener('api:status', (event) => {
const customEvent = event as CustomEvent<{ reachable?: boolean }>;
const reachable = Boolean(customEvent?.detail?.reachable);
applyApiStatus(reachable);
try {
sessionStorage.setItem('api-reachable', reachable ? '1' : '0');
} catch {}
});
}
function initBuildTooltip() {
const el = document.querySelector('.footer-version[data-build-time]');
if (!el) return;
@@ -59,6 +87,8 @@ const envBadge = ENVIRONMENT === 'Dev' ? 'DEV' : null;
});
}
}
bindApiStatusListener();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initBuildTooltip);
@@ -144,6 +174,11 @@ const envBadge = ENVIRONMENT === 'Dev' ? 'DEV' : null;
box-shadow: 0 0 4px rgba(34, 197, 94, 0.4);
}
.api-status-dot.api-offline {
background: #ef4444 !important;
box-shadow: 0 0 4px rgba(239, 68, 68, 0.45) !important;
}
.env-dot {
width: 6px;
height: 6px;