add current song
This commit is contained in:
121
src/main.js
121
src/main.js
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user