tools: add sync-version.js to sync package.json -> Tauri files
- Add tools/sync-version.js script to read root package.json version and update src-tauri/tauri.conf.json and src-tauri/Cargo.toml. - Update only the [package] version line in Cargo.toml to preserve formatting. - Include JSON read/write helpers and basic error handling/reporting.
This commit is contained in:
163
src/main.js
163
src/main.js
@@ -11,6 +11,7 @@ let currentIndex = 0;
|
||||
let isPlaying = false;
|
||||
let currentMode = 'local'; // 'local' | 'cast'
|
||||
let currentCastDevice = null;
|
||||
let currentCastTransport = null; // 'tap' | 'proxy' | 'direct' | null
|
||||
|
||||
// Local playback is handled natively by the Tauri backend (player_* commands).
|
||||
// The WebView is a control surface only.
|
||||
@@ -158,12 +159,63 @@ const usIndex = document.getElementById('us_index');
|
||||
// Init
|
||||
async function init() {
|
||||
try {
|
||||
// Helpful debug information for release builds so we can compare parity with dev.
|
||||
console.group && console.group('RadioCast init');
|
||||
console.log('runningInTauri:', runningInTauri);
|
||||
try { console.log('location:', location.href); } catch (_) {}
|
||||
try { console.log('userAgent:', navigator.userAgent); } catch (_) {}
|
||||
try { console.log('platform:', navigator.platform); } catch (_) {}
|
||||
try { console.log('RADIO_DEBUG_DEVTOOLS flag:', localStorage.getItem('RADIO_DEBUG_DEVTOOLS')); } catch (_) {}
|
||||
|
||||
// Always try to read build stamp if present (bundled by build scripts).
|
||||
try {
|
||||
const resp = await fetch('/build-info.json', { cache: 'no-store' });
|
||||
if (resp && resp.ok) {
|
||||
const bi = await resp.json();
|
||||
console.log('build-info:', bi);
|
||||
} else {
|
||||
console.log('build-info: not present');
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('build-info: failed to read');
|
||||
}
|
||||
|
||||
restoreSavedVolume();
|
||||
await loadStations();
|
||||
try { console.log('stations loaded:', Array.isArray(stations) ? stations.length : typeof stations); } catch (_) {}
|
||||
setupEventListeners();
|
||||
ensureArtworkPointerFallback();
|
||||
updateUI();
|
||||
updateEngineBadge();
|
||||
|
||||
// Optionally open devtools in release builds for debugging parity with `tauri dev`.
|
||||
// Enable by setting `localStorage.setItem('RADIO_DEBUG_DEVTOOLS', '1')` or by creating
|
||||
// `src/build-info.json` with { debug: true } at build time (the `build:devlike` script does this).
|
||||
try {
|
||||
let shouldOpen = false;
|
||||
try { if (localStorage && localStorage.getItem && localStorage.getItem('RADIO_DEBUG_DEVTOOLS') === '1') shouldOpen = true; } catch (_) {}
|
||||
|
||||
// Build-time flag file (created by tools/write-build-flag.js when running `build`/`build:devlike`).
|
||||
try {
|
||||
const resp = await fetch('/build-info.json', { cache: 'no-store' });
|
||||
if (resp && resp.ok) {
|
||||
const bi = await resp.json();
|
||||
if (bi && bi.debug) shouldOpen = true;
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
if (shouldOpen) {
|
||||
try {
|
||||
const w = getCurrentWindow();
|
||||
if (w && typeof w.openDevTools === 'function') {
|
||||
w.openDevTools();
|
||||
console.log('Opened devtools via build-info/localStorage flag');
|
||||
}
|
||||
} catch (e) { console.warn('Failed to open devtools:', e); }
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
console.groupEnd && console.groupEnd();
|
||||
} catch (e) {
|
||||
console.error('Error during init', e);
|
||||
if (statusTextEl) statusTextEl.textContent = 'Init error: ' + (e && e.message ? e.message : String(e));
|
||||
@@ -1161,7 +1213,39 @@ async function play() {
|
||||
} else if (currentMode === 'cast' && currentCastDevice) {
|
||||
// Cast logic
|
||||
try {
|
||||
await invoke('cast_play', { deviceName: currentCastDevice, url: station.url });
|
||||
// UX guard: if native playback is currently decoding a different station,
|
||||
// stop it explicitly before starting the cast pipeline (which would otherwise
|
||||
// replace the decoder behind the scenes).
|
||||
try {
|
||||
const st = await invoke('player_get_state');
|
||||
const nativeActive = st && (st.status === 'playing' || st.status === 'buffering') && st.url;
|
||||
if (nativeActive && st.url !== station.url) {
|
||||
stopLocalPlayerStatePolling();
|
||||
await invoke('player_stop').catch(() => {});
|
||||
}
|
||||
} catch (_) {
|
||||
// Ignore: best-effort guard only.
|
||||
}
|
||||
|
||||
let castUrl = station.url;
|
||||
currentCastTransport = null;
|
||||
try {
|
||||
const res = await invoke('cast_proxy_start', { deviceName: currentCastDevice, url: station.url });
|
||||
if (res && typeof res === 'object') {
|
||||
castUrl = res.url || station.url;
|
||||
currentCastTransport = res.mode || 'proxy';
|
||||
} else {
|
||||
// Backward-compat (older backend returned string)
|
||||
castUrl = res || station.url;
|
||||
currentCastTransport = 'proxy';
|
||||
}
|
||||
} catch (e) {
|
||||
// If proxy cannot start (ffmpeg missing, firewall, etc), fall back to direct station URL.
|
||||
console.warn('Cast proxy start failed; falling back to direct URL', e);
|
||||
currentCastTransport = 'direct';
|
||||
}
|
||||
|
||||
await invoke('cast_play', { deviceName: currentCastDevice, url: castUrl });
|
||||
isPlaying = true;
|
||||
// Sync volume
|
||||
const vol = volumeSlider.value / 100;
|
||||
@@ -1169,8 +1253,10 @@ async function play() {
|
||||
updateUI();
|
||||
} catch (e) {
|
||||
console.error('Cast failed', e);
|
||||
statusTextEl.textContent = 'Cast Error';
|
||||
statusTextEl.textContent = 'Cast Error (check LAN/firewall)';
|
||||
await invoke('cast_proxy_stop').catch(() => {});
|
||||
currentMode = 'local'; // Fallback
|
||||
currentCastTransport = null;
|
||||
updateUI();
|
||||
}
|
||||
}
|
||||
@@ -1186,6 +1272,7 @@ async function stop() {
|
||||
}
|
||||
} else if (currentMode === 'cast' && currentCastDevice) {
|
||||
try {
|
||||
await invoke('cast_proxy_stop').catch(() => {});
|
||||
await invoke('cast_stop', { deviceName: currentCastDevice });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -1193,6 +1280,9 @@ async function stop() {
|
||||
}
|
||||
|
||||
isPlaying = false;
|
||||
if (currentMode !== 'cast') {
|
||||
currentCastTransport = null;
|
||||
}
|
||||
updateUI();
|
||||
}
|
||||
|
||||
@@ -1220,14 +1310,24 @@ function updateUI() {
|
||||
playBtn.classList.add('playing'); // Add pulsing ring animation
|
||||
statusTextEl.textContent = 'Playing';
|
||||
statusDotEl.style.backgroundColor = 'var(--success)';
|
||||
stationSubtitleEl.textContent = currentMode === 'cast' ? `Casting to ${currentCastDevice}` : 'Live Stream';
|
||||
if (currentMode === 'cast') {
|
||||
const t = currentCastTransport ? ` (${currentCastTransport})` : '';
|
||||
stationSubtitleEl.textContent = `Casting${t} to ${currentCastDevice}`;
|
||||
} else {
|
||||
stationSubtitleEl.textContent = 'Live Stream';
|
||||
}
|
||||
} else {
|
||||
iconPlay.classList.remove('hidden');
|
||||
iconStop.classList.add('hidden');
|
||||
playBtn.classList.remove('playing'); // Remove pulsing ring
|
||||
statusTextEl.textContent = 'Ready';
|
||||
statusDotEl.style.backgroundColor = 'var(--text-muted)';
|
||||
stationSubtitleEl.textContent = currentMode === 'cast' ? `Connected to ${currentCastDevice}` : 'Live Stream';
|
||||
if (currentMode === 'cast') {
|
||||
const t = currentCastTransport ? ` (${currentCastTransport})` : '';
|
||||
stationSubtitleEl.textContent = `Connected${t} to ${currentCastDevice}`;
|
||||
} else {
|
||||
stationSubtitleEl.textContent = 'Live Stream';
|
||||
}
|
||||
}
|
||||
|
||||
updateEngineBadge();
|
||||
@@ -1296,14 +1396,20 @@ async function selectCastDevice(deviceName) {
|
||||
await stop();
|
||||
}
|
||||
|
||||
// Best-effort cleanup: stop any lingering cast transport when changing device/mode.
|
||||
await invoke('cast_proxy_stop').catch(() => {});
|
||||
|
||||
if (deviceName) {
|
||||
currentMode = 'cast';
|
||||
currentCastDevice = deviceName;
|
||||
castBtn.style.color = 'var(--success)';
|
||||
// Transport mode gets set on play.
|
||||
currentCastTransport = currentCastTransport || null;
|
||||
} else {
|
||||
currentMode = 'local';
|
||||
currentCastDevice = null;
|
||||
castBtn.style.color = 'var(--text-main)';
|
||||
currentCastTransport = null;
|
||||
}
|
||||
|
||||
updateUI();
|
||||
@@ -1313,22 +1419,51 @@ async function selectCastDevice(deviceName) {
|
||||
// Let's prompt user to play.
|
||||
}
|
||||
|
||||
// Best-effort: stop any cast transport when leaving the window.
|
||||
window.addEventListener('beforeunload', () => {
|
||||
try { invoke('cast_proxy_stop'); } catch (_) {}
|
||||
});
|
||||
|
||||
window.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
// Service worker is useful for the PWA, but it can cause confusing caching during
|
||||
// Tauri development because it may serve an older cached `index.html`.
|
||||
if ('serviceWorker' in navigator) {
|
||||
if (runningInTauri) {
|
||||
// Best-effort cleanup so the desktop app always reflects local file changes.
|
||||
navigator.serviceWorker.getRegistrations()
|
||||
.then((regs) => Promise.all(regs.map((r) => r.unregister())))
|
||||
.catch(() => {});
|
||||
// Best-effort cleanup so the desktop app doesn't get stuck on an old cached UI.
|
||||
// If we clear anything, do a one-time reload to ensure the new bundled assets are used.
|
||||
(async () => {
|
||||
let changed = false;
|
||||
|
||||
if ('caches' in window) {
|
||||
caches.keys()
|
||||
.then((keys) => Promise.all(keys.map((k) => caches.delete(k))))
|
||||
.catch(() => {});
|
||||
}
|
||||
try {
|
||||
const regs = await navigator.serviceWorker.getRegistrations();
|
||||
if (regs && regs.length) {
|
||||
await Promise.all(regs.map((r) => r.unregister().catch(() => false)));
|
||||
changed = true;
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
if ('caches' in window) {
|
||||
try {
|
||||
const keys = await caches.keys();
|
||||
if (keys && keys.length) {
|
||||
await Promise.all(keys.map((k) => caches.delete(k).catch(() => false)));
|
||||
changed = true;
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
try {
|
||||
if (changed) {
|
||||
const k = '__radiocast_sw_cleared_once';
|
||||
const already = sessionStorage.getItem(k);
|
||||
if (!already) {
|
||||
sessionStorage.setItem(k, '1');
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
})();
|
||||
} else {
|
||||
// Register Service Worker for PWA installation (non-disruptive)
|
||||
window.addEventListener('load', () => {
|
||||
@@ -1400,7 +1535,9 @@ async function openStationsOverlay() {
|
||||
li.onclick = async () => {
|
||||
currentMode = 'local';
|
||||
currentCastDevice = null;
|
||||
currentCastTransport = null;
|
||||
castBtn.style.color = 'var(--text-main)';
|
||||
try { await invoke('cast_proxy_stop'); } catch (_) {}
|
||||
await setStationByIndex(idx);
|
||||
closeCastOverlay();
|
||||
try { await play(); } catch (e) { console.error('Failed to play station from grid', e); }
|
||||
|
||||
Reference in New Issue
Block a user