This commit is contained in:
2026-04-29 07:39:29 +02:00
parent 41a5472582
commit b866845b6a
40 changed files with 71735 additions and 111 deletions
+54 -25
View File
@@ -89,6 +89,9 @@ const stationLibraryPagePrevBtn = document.getElementById('station-library-page-
const stationLibraryPageNextBtn = document.getElementById('station-library-page-next');
const stationLibraryPageInfo = document.getElementById('station-library-page-info');
const stationTabBtns = document.querySelectorAll('[data-station-tab]');
const installPromptBannerEl = document.getElementById('install-prompt-banner');
const installPromptActionBtn = document.getElementById('install-prompt-action');
const installPromptDismissBtn = document.getElementById('install-prompt-dismiss');
const radioCountryCodeByName = new Map(radioCountries.map((country) => [country.name, country.code]));
const radioCountryNameByCode = new Map(radioCountries.map((country) => [country.code, country.name]));
@@ -124,6 +127,47 @@ function prefersReducedMotion() {
}
}
function isMobileViewport() {
return window.matchMedia('(max-width: 760px)').matches;
}
function isStandalonePwa() {
return window.matchMedia('(display-mode: standalone)').matches
|| window.matchMedia('(display-mode: fullscreen)').matches
|| window.navigator.standalone === true;
}
async function lockPortraitOrientation() {
if (!isMobileViewport()) return;
if (!isStandalonePwa()) return;
if (!screen?.orientation?.lock) return;
try {
await screen.orientation.lock('portrait');
} catch (e) {
// Some browsers require a user gesture or simply do not allow locking.
console.debug('Portrait orientation lock not available:', e);
}
}
function isMobileInstallViewport() {
return window.matchMedia('(max-width: 760px)').matches;
}
function hideInstallPromptUI() {
installAppBtn?.classList.add('hidden');
installPromptBannerEl?.classList.add('hidden');
}
function showInstallPromptUI() {
if (!deferredInstallPrompt) return;
if (isMobileInstallViewport()) {
installPromptBannerEl?.classList.remove('hidden');
} else {
installAppBtn?.classList.remove('hidden');
}
}
function clearArtworkShineTimers() {
if (artworkShineTimeoutId) {
clearTimeout(artworkShineTimeoutId);
@@ -2252,6 +2296,8 @@ function setupEventListeners() {
artworkPlaceholder?.addEventListener('click', openStationLibrary);
castOutputBtn?.addEventListener('click', toggleCastBothMode);
installPromptActionBtn?.addEventListener('click', promptInstallApp);
installPromptDismissBtn?.addEventListener('click', hideInstallPromptUI);
window.addEventListener('resize', () => {
updateCoverflowTransforms();
updateStationTitleLayout(getStationTitle(stations[currentIndex]));
@@ -2283,7 +2329,7 @@ async function promptInstallApp() {
deferredInstallPrompt.prompt();
try { await deferredInstallPrompt.userChoice; } catch (e) { /* ignore */ }
deferredInstallPrompt = null;
installAppBtn?.classList.add('hidden');
hideInstallPromptUI();
}
// ── Media Session API (for OS media controls / lock screen) ──────────────────
@@ -2325,6 +2371,7 @@ async function init() {
restoreLastStationCountry();
await loadStations();
setupEventListeners();
lockPortraitOrientation();
ensureArtworkPointerFallback();
scheduleArtworkShine();
updateUI();
@@ -2337,37 +2384,19 @@ async function init() {
}
}
// ── Service Worker registration (PWA) ────────────────────────────────────────
if ('serviceWorker' in navigator && import.meta.env.DEV) {
window.addEventListener('load', () => {
navigator.serviceWorker.getRegistrations()
.then((registrations) => Promise.all(registrations.map((reg) => reg.unregister())))
.catch((err) => console.debug('ServiceWorker dev cleanup failed:', err));
if ('caches' in window) {
caches.keys()
.then((keys) => Promise.all(keys.map((key) => caches.delete(key))))
.catch((err) => console.debug('Cache dev cleanup failed:', err));
}
});
} else if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register(`${import.meta.env.BASE_URL}sw.js`)
.then((reg) => console.log('ServiceWorker registered:', reg.scope))
.catch((err) => console.debug('ServiceWorker registration failed:', err));
});
}
window.addEventListener('beforeinstallprompt', (event) => {
event.preventDefault();
deferredInstallPrompt = event;
installAppBtn?.classList.remove('hidden');
showInstallPromptUI();
});
window.addEventListener('appinstalled', () => {
deferredInstallPrompt = null;
installAppBtn?.classList.add('hidden');
hideInstallPromptUI();
});
window.addEventListener('orientationchange', () => {
lockPortraitOrientation();
});
export function initPlayer() {