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

@@ -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