webapp
This commit is contained in:
@@ -6,6 +6,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>RadioPlayer</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<meta name="theme-color" content="#1f1f2e">
|
||||
<link rel="apple-touch-icon" href="assets/favicon_io/apple-touch-icon.png">
|
||||
<script src="main.js" defer type="module"></script>
|
||||
</head>
|
||||
|
||||
|
||||
35
src/main.js
35
src/main.js
@@ -106,7 +106,7 @@ async function loadStations() {
|
||||
try {
|
||||
// stop any existing pollers before reloading stations
|
||||
stopCurrentSongPollers();
|
||||
const resp = await fetch('stations.json');
|
||||
const resp = await fetch('/stations.json');
|
||||
const raw = await resp.json();
|
||||
|
||||
// Normalize station objects so the rest of the app can rely on `name` and `url`.
|
||||
@@ -152,6 +152,11 @@ async function loadStations() {
|
||||
// Append user stations after file stations
|
||||
stations = stations.concat(userNormalized);
|
||||
|
||||
// Debug: report how many stations we have after loading
|
||||
try {
|
||||
console.debug('loadStations: loaded stations count:', stations.length);
|
||||
} catch (e) {}
|
||||
|
||||
if (stations.length > 0) {
|
||||
// Try to restore last selected station by id
|
||||
const lastId = getLastStationId();
|
||||
@@ -163,6 +168,7 @@ async function loadStations() {
|
||||
currentIndex = 0;
|
||||
}
|
||||
|
||||
console.debug('loadStations: loading station index', currentIndex);
|
||||
loadStation(currentIndex);
|
||||
// start polling for currentSong endpoints (if any)
|
||||
startCurrentSongPollers();
|
||||
@@ -414,10 +420,9 @@ function updateNowPlayingUI() {
|
||||
if (!station) return;
|
||||
|
||||
if (nowPlayingEl && nowArtistEl && nowTitleEl) {
|
||||
// Show now-playing if we have either an artist or a title (some stations only provide title)
|
||||
if (station.currentSongInfo && (station.currentSongInfo.artist || station.currentSongInfo.title)) {
|
||||
nowArtistEl.textContent = station.currentSongInfo.artist || '';
|
||||
nowTitleEl.textContent = station.currentSongInfo.title || '';
|
||||
if (station.currentSongInfo && station.currentSongInfo.artist && station.currentSongInfo.title) {
|
||||
nowArtistEl.textContent = station.currentSongInfo.artist;
|
||||
nowTitleEl.textContent = station.currentSongInfo.title;
|
||||
nowPlayingEl.classList.remove('hidden');
|
||||
} else {
|
||||
nowArtistEl.textContent = '';
|
||||
@@ -680,13 +685,14 @@ function loadStation(index) {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Fallback: show the full station name when no logo is provided
|
||||
// Fallback to single-letter/logo text
|
||||
logoImgEl.src = '';
|
||||
logoImgEl.classList.add('hidden');
|
||||
try {
|
||||
logoTextEl.textContent = (station.name || '').trim();
|
||||
} catch (e) {
|
||||
logoTextEl.textContent = '';
|
||||
const numberMatch = station.name.match(/\d+/);
|
||||
if (numberMatch) {
|
||||
logoTextEl.textContent = numberMatch[0];
|
||||
} else {
|
||||
logoTextEl.textContent = station.name.charAt(0).toUpperCase();
|
||||
}
|
||||
logoTextEl.classList.remove('hidden');
|
||||
}
|
||||
@@ -922,6 +928,15 @@ async function selectCastDevice(deviceName) {
|
||||
|
||||
window.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
// Register Service Worker for PWA installation (non-disruptive)
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('sw.js')
|
||||
.then((reg) => console.log('ServiceWorker registered:', reg.scope))
|
||||
.catch((err) => console.debug('ServiceWorker registration failed:', err));
|
||||
});
|
||||
}
|
||||
|
||||
// Open overlay and show list of stations (used by menu/hamburger)
|
||||
async function openStationsOverlay() {
|
||||
castOverlay.classList.remove('hidden');
|
||||
|
||||
22
src/manifest.json
Normal file
22
src/manifest.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "RadioPlayer",
|
||||
"short_name": "Radio",
|
||||
"description": "RadioPlayer — stream radio stations from the web",
|
||||
"start_url": ".",
|
||||
"scope": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#1f1f2e",
|
||||
"theme_color": "#1f1f2e",
|
||||
"icons": [
|
||||
{
|
||||
"src": "assets/favicon_io/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "assets/favicon_io/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
48
src/sw.js
Normal file
48
src/sw.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const CACHE_NAME = 'radiocast-core-v1';
|
||||
const CORE_ASSETS = [
|
||||
'.',
|
||||
'index.html',
|
||||
'main.js',
|
||||
'styles.css',
|
||||
'stations.json',
|
||||
'assets/favicon_io/android-chrome-192x192.png',
|
||||
'assets/favicon_io/android-chrome-512x512.png',
|
||||
'assets/favicon_io/apple-touch-icon.png'
|
||||
];
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME).then((cache) => cache.addAll(CORE_ASSETS))
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(
|
||||
caches.keys().then((keys) => Promise.all(
|
||||
keys.map((k) => { if (k !== CACHE_NAME) return caches.delete(k); return null; })
|
||||
))
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', (event) => {
|
||||
// Only handle GET requests
|
||||
if (event.request.method !== 'GET') return;
|
||||
|
||||
event.respondWith(
|
||||
caches.match(event.request).then((cached) => {
|
||||
if (cached) return cached;
|
||||
return fetch(event.request).then((networkResp) => {
|
||||
// Optionally cache new resources (best-effort)
|
||||
try {
|
||||
const respClone = networkResp.clone();
|
||||
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, respClone)).catch(()=>{});
|
||||
} catch (e) {}
|
||||
return networkResp;
|
||||
}).catch(() => {
|
||||
// If offline and HTML navigation, return cached index.html
|
||||
if (event.request.mode === 'navigate') return caches.match('index.html');
|
||||
return new Response('', { status: 503, statusText: 'Service Unavailable' });
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user