'use strict'; // ---- Radio: The Journey FM on top, your saved stations, and a search of thousands more (Radio Browser). The hub passes the stream on (and reads the song title // out of it), so stations that only offer plain http still play, and everything plays in the same player as Music. const rdoApi = (p, opts) => jfetch('/api/radio' + p, opts); const rdoOpt = (m, o) => ({ method: m, headers: { 'content-type': 'application/json' }, body: o ? JSON.stringify(o) : undefined }); const rdoIcon = s => s.favicon ? '/api/radio/icon?u=' + encodeURIComponent(s.favicon) : ''; const rdoTrack = s => ({ key: 'radio:' + s.id, title: s.name, artist: 'Live radio', album: [s.state, s.country].filter(Boolean).join(', '), album0: [s.state, s.country].filter(Boolean).join(', '), thumb: rdoIcon(s), src: '/api/radio/play/' + encodeURIComponent(s.id), nocache: true, dur: 0, station: s.name }); const rdoPlaying = () => { const t = cur(); return t && String(t.key).startsWith('radio:') ? t : null; }; const rdoMeta = s => [s.tags && s.tags.split(',').slice(0, 2).join(', '), s.state || s.country, s.codec && (s.codec + (s.bitrate ? ' ' + s.bitrate + 'k' : ''))].filter(Boolean).join(' · '); const rdoChips = [['Christian', 'christian'], ['Worship', 'worship'], ['Gospel', 'gospel'], ['Talk', 'talk'], ['News', 'news'], ['Classical', 'classical'], ['Jazz', 'jazz'], ['Country', 'country'], ['Oldies', 'oldies']]; let rdoSaved = [], rdoNow = {}; /** While a station plays, ask what song is on every 20 seconds and show it in the player. */ async function rdoPoll() { const t = rdoPlaying(); if (!t) return; const id = t.key.slice(6); try { const n = await rdoApi('/now/' + encodeURIComponent(id)); if (!rdoPlaying() || rdoPlaying().key !== t.key) return; rdoNow[id] = n; rdoApplyNow(t, n); const el = document.getElementById('rdonow'); if (el && el.dataset.id === id) rdoDrawNow(el, n); const sm = document.querySelector(`.rdorow[data-id="${id}"] small`); if (sm && n.title) sm.textContent = 'Now: ' + n.title + (n.artist ? ' \u2014 ' + n.artist : ''); } catch {} } /** What is on now goes into the player (the song as the title, the artist under it, the station as the album), the lock screen, and the lyrics if they are open. */ function rdoApplyNow(t, n) { const was = t.song ? t.song.title + '|' + t.song.artist : ''; t.song = n.title ? { title: n.title, artist: n.artist || '', startedAt: n.startedAt || 0 } : null; t.title = n.title || t.station; t.artist = n.title ? (n.artist || t.station) : 'Live radio'; t.album = n.title ? t.station : t.album0; const now = t.song ? t.song.title + '|' + t.song.artist : ''; if (was === now && t.shown) return; t.shown = true; for (const f of ['setMeta', 'renderMini', 'updateNow', 'renderNpCard', 'syncUI']) { try { if (typeof window[f] === 'function') (f === 'setMeta' ? window[f](t) : window[f]()); } catch {} } if (lyr.open && was !== now) { lyrShell(); lyrReload(); } } setInterval(rdoPoll, 10000); pl.a.addEventListener('playing', () => { if (rdoPlaying()) setTimeout(rdoPoll, 1500); }); function rdoDrawNow(el, n) { el.innerHTML = n && n.title ? `Now playing${esc(n.title)}${n.artist ? ' · ' + esc(n.artist) + '' : ''}` : 'Live'; } async function viewRadio() { const v = $('#view'); v.innerHTML = '
Loading…
${esc(e.message)}
${esc(rdoMeta(j))}
Searching…
' : 'No stations found.
'; bindRows($('#rdores'), id => S.items.find(x => x.id === id)); $('#rdomore').innerHTML = S.more && !S.busy ? '' : ''; if ($('#rdomb')) $('#rdomb').onclick = () => run(false); }; const drawAll = () => { drawMine(); drawRes(); }; const run = async reset => { const my = ++S.seq; if (reset) { S.off = 0; S.items = []; } S.busy = true; if (reset) drawRes(); try { const r = await rdoApi(`/search?q=${encodeURIComponent(S.q)}&tag=${encodeURIComponent(S.q ? '' : S.tag)}&offset=${S.off}`); if (my !== S.seq) return; S.items = S.items.concat(r.items); S.more = r.more; S.off += 30; } catch (e) { if (my === S.seq) { $('#rdores').innerHTML = `${esc(e.message)}
`; S.busy = false; return; } } if (my === S.seq) { S.busy = false; drawRes(); } }; $('#rdojp').onclick = () => playStation(j); $('#rdojl').onclick = () => { if (!rdoPlaying() || rdoPlaying().key !== 'radio:' + j.id) playStation(j); setTimeout(() => { if (typeof lyrOpen === 'function' && !lyr.open) lyrOpen(); }, 1200); }; $('#rdojr').onclick = async () => { const el = openSheet('Recently played', j.name, 'Loading…
'); try { const n = await rdoApi('/now/journeyfm'); el.querySelector('.empty').outerHTML = n.recent.length ? `Nothing to show right now.
'; } catch (e) { el.querySelector('.empty').outerHTML = `${esc(e.message)}
`; } }; $('#rdochips').querySelectorAll('a').forEach(a => a.onclick = e => { e.preventDefault(); S.tag = a.dataset.t; S.q = ''; $('#rdoq').value = ''; $('#rdochips').querySelectorAll('a').forEach(x => x.classList.toggle('on', x === a)); run(true); }); let t; $('#rdoq').oninput = e => { clearTimeout(t); t = setTimeout(() => { S.q = e.target.value.trim(); $('#rdochips').querySelectorAll('a').forEach(x => x.classList.toggle('on', !S.q && x.dataset.t === S.tag)); run(true); }, 450); }; drawMine(); run(true); rdoApi('/now/journeyfm').then(n => { const el = $('#rdonow'); if (el) { rdoNow.journeyfm = n; rdoDrawNow(el, n); } }).catch(() => {}); }