add current song

This commit is contained in:
2026-01-02 16:58:58 +01:00
parent e36bb1ab55
commit a2753bcf66
9 changed files with 596 additions and 38 deletions

View File

@@ -100,11 +100,12 @@
</section>
<section class="track-info">
<h2 id="station-name">Radio 1 MB</h2>
<p id="station-subtitle">Live Stream</p>
<h2 id="station-name"></h2>
<p id="now-playing" class="hidden" aria-live="polite"></p>
<p id="station-subtitle"></p>
<div id="status-indicator" class="status-indicator-wrap" aria-hidden="true">
<span class="status-dot"></span>
<span id="status-text">Ready</span>
<span id="status-text"></span>
</div>
</section>

View File

@@ -12,6 +12,7 @@ const audio = new Audio();
// UI Elements
const stationNameEl = document.getElementById('station-name');
const stationSubtitleEl = document.getElementById('station-subtitle');
const nowPlayingEl = document.getElementById('now-playing');
const statusTextEl = document.getElementById('status-text');
const statusDotEl = document.querySelector('.status-dot');
const playBtn = document.getElementById('play-btn');
@@ -43,13 +44,47 @@ const usIndex = document.getElementById('us_index');
// Init
async function init() {
restoreSavedVolume();
await loadStations();
setupEventListeners();
updateUI();
}
// Volume persistence
function saveVolumeToStorage(val) {
try {
localStorage.setItem('volume', String(val));
} catch (e) { /* ignore */ }
}
function getSavedVolume() {
try {
const v = localStorage.getItem('volume');
if (!v) return null;
const n = Number(v);
if (Number.isFinite(n) && n >= 0 && n <= 100) return n;
return null;
} catch (e) { return null; }
}
function restoreSavedVolume() {
const saved = getSavedVolume();
if (saved !== null && volumeSlider) {
volumeSlider.value = String(saved);
volumeValue.textContent = `${saved}%`;
const decimals = saved / 100;
audio.volume = decimals;
// If currently in cast mode and a device is selected, propagate volume
if (currentMode === 'cast' && currentCastDevice) {
invoke('cast_set_volume', { deviceName: currentCastDevice, volume: decimals }).catch(()=>{});
}
}
}
async function loadStations() {
try {
// stop any existing pollers before reloading stations
stopCurrentSongPollers();
const resp = await fetch('stations.json');
const raw = await resp.json();
@@ -108,6 +143,8 @@ async function loadStations() {
}
loadStation(currentIndex);
// start polling for currentSong endpoints (if any)
startCurrentSongPollers();
}
} catch (e) {
console.error('Failed to load stations', e);
@@ -115,6 +152,83 @@ async function loadStations() {
}
}
// --- Current Song Polling ---
const currentSongPollers = new Map(); // stationId -> intervalId
function stopCurrentSongPollers() {
for (const id of currentSongPollers.values()) {
clearInterval(id);
}
currentSongPollers.clear();
}
function startCurrentSongPollers() {
// Clear existing
stopCurrentSongPollers();
stations.forEach((s, idx) => {
const url = s.raw && s.raw.currentSong;
if (url && typeof url === 'string' && url.length > 0) {
// fetch immediately and then every 10s
fetchAndStoreCurrentSong(s, idx, url);
const iid = setInterval(() => fetchAndStoreCurrentSong(s, idx, url), 10000);
currentSongPollers.set(s.id || idx, iid);
}
});
}
async function fetchAndStoreCurrentSong(station, idx, url) {
try {
let data = null;
try {
const resp = await fetch(url, { cache: 'no-store' });
const ct = resp.headers.get('content-type') || '';
if (ct.includes('application/json')) {
data = await resp.json();
} else {
const txt = await resp.text();
try { data = JSON.parse(txt); } catch (e) { data = null; }
}
} catch (fetchErr) {
// Possibly blocked by CORS — fall back to backend fetch via Tauri invoke
try {
const body = await invoke('fetch_url', { url });
try { data = JSON.parse(body); } catch (e) { data = null; }
} catch (invokeErr) {
console.debug('Both fetch and backend fetch failed for', url, fetchErr, invokeErr);
data = null;
}
}
if (data && (data.artist || data.title)) {
station.currentSongInfo = { artist: data.artist || '', title: data.title || '' };
// update UI if this is the currently loaded station
if (idx === currentIndex) updateNowPlayingUI();
}
} catch (e) {
// ignore fetch errors silently (network/CORS) but keep console for debugging
console.debug('currentSong fetch failed for', url, e.message || e);
}
}
function updateNowPlayingUI() {
const station = stations[currentIndex];
if (!station) return;
if (nowPlayingEl) {
if (station.currentSongInfo && station.currentSongInfo.artist && station.currentSongInfo.title) {
nowPlayingEl.textContent = `${station.currentSongInfo.artist}${station.currentSongInfo.title}`;
nowPlayingEl.classList.remove('hidden');
} else {
nowPlayingEl.textContent = '';
nowPlayingEl.classList.add('hidden');
}
}
// keep subtitle for mode/status
stationSubtitleEl.textContent = currentMode === 'cast' ? `Casting to ${currentCastDevice}` : 'Live Stream';
}
// --- User Stations (localStorage) ---
function loadUserStations() {
try {
@@ -304,6 +418,11 @@ function loadStation(index) {
stationNameEl.textContent = station.name;
stationSubtitleEl.textContent = currentMode === 'cast' ? `Casting to ${currentCastDevice}` : 'Live Stream';
// clear now playing when loading a new station; will be updated by poller if available
if (nowPlayingEl) {
nowPlayingEl.textContent = '';
nowPlayingEl.classList.add('hidden');
}
// Update Logo Text (First letter or number)
// Simple heuristic: if name has a number, use it, else first letter
@@ -491,6 +610,8 @@ function handleVolumeInput() {
} else if (currentMode === 'cast' && currentCastDevice) {
invoke('cast_set_volume', { deviceName: currentCastDevice, volume: decimals });
}
// persist volume for next sessions
saveVolumeToStorage(Number(val));
}
// Cast Logic

View File

@@ -9,6 +9,7 @@
"poster": "",
"lastSongs": "http://data.radio.si/api/lastsongsxml/radio1/json",
"epg": "http://spored.radio.si/api/now/radio1",
"currentSong": "https://radio1.si/?handler=CurrentSong",
"defaultText": "www.radio1.si",
"www": "https://www.radio1.si",
"mountPoints": [
@@ -200,6 +201,7 @@
"liveAudio": "http://live.radio.si/Radio80",
"liveVideo": null,
"poster": null,
"currentSong": "https://radio80.si/?handler=CurrentSong",
"lastSongs": "http://data.radio.si/api/lastsongsxml/radio80/json",
"epg": "http://spored.radio.si/api/now/radio80",
"defaultText": "www.radio80.si",

View File

@@ -407,6 +407,13 @@ body {
text-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.now-playing {
margin: 6px 0 0;
color: var(--text-main);
font-size: 0.95rem;
font-weight: 600;
}
.track-info p {
margin: 6px 0 0;
color: var(--text-muted);
@@ -543,6 +550,12 @@ body {
flex: 1;
}
/* Make slider interactive when the parent card is draggable */
.slider-container,
input[type=range] {
-webkit-app-region: no-drag;
}
input[type=range] {
width: 100%;
background: transparent;