add current song
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user