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:
2026-01-13 07:21:51 +01:00
parent abb7cafaed
commit 694f335408
50 changed files with 1128 additions and 6186 deletions

View File

@@ -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); }