feat(radio): PLS/M3U playlist resolution + ICY metadata for playlist streams

Resolves PLS and M3U/M3U8 playlist URLs to their first direct stream URL
before playback and ICY metadata fetching. Stations configured with a
.pls or .m3u URL (e.g. SomaFM, schizoid.in) now play correctly and
report track metadata via ICY headers.

- Rust: parse_pls_stream_url / parse_m3u_stream_url helpers
- Rust: resolve_playlist_url (shared) + resolve_stream_url Tauri command
- fetch_icy_metadata: auto-resolves playlist URLs before connecting
- playerStore: playRadio() awaits resolve_stream_url before setting audio src

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-04-10 23:18:53 +02:00
parent 549677ffd4
commit c142e9e983
4 changed files with 84 additions and 3 deletions
+6 -2
View File
@@ -764,7 +764,7 @@ export const usePlayerStore = create<PlayerState>()(
},
// ── playRadio ────────────────────────────────────────────────────────────
playRadio: (station) => {
playRadio: async (station) => {
const { volume } = get();
++playGeneration;
isAudioPaused = false;
@@ -774,8 +774,12 @@ export const usePlayerStore = create<PlayerState>()(
if (seekDebounce) { clearTimeout(seekDebounce); seekDebounce = null; } seekTarget = null;
// Stop Rust engine in case a regular track was playing.
invoke('audio_stop').catch(() => {});
// Resolve PLS/M3U playlist URLs to the actual stream URL before handing
// to HTML5 <audio> — the browser cannot play playlist files directly.
const streamUrl = await invoke<string>('resolve_stream_url', { url: station.streamUrl })
.catch(() => station.streamUrl);
// Play via HTML5 audio — browser handles reconnects, codec negotiation, buffering.
radioAudio.src = station.streamUrl;
radioAudio.src = streamUrl;
radioAudio.volume = volume;
radioAudio.play().catch((err: unknown) => {
console.error('[psysonic] radio HTML5 play failed:', err);