Radio: artist and song in the player, lyrics for the song that is on (LRCLIB), synced for The Journey FM

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaC9HmtHMdRP8dAbN4wAKj
This commit is contained in:
Claude committed 2026-10-25 16:32:21 +00:00
1 parent 1e46aad9ea
commit 387736fb56
11 files changed
+129 -43

No files matched your search

+4 -3
View File
@@ -2,7 +2,7 @@
const http = require('http'), assert = require('assert'), fs = require('fs'), os = require('os'), path = require('path');
process.env.DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'nehub-radio-'));
process.env.RADIO_ALLOW_PRIVATE = '1'; // the fake servers below are on this machine
const AUDIO = Buffer.alloc(4096, 0x55), META = Buffer.from("StreamTitle='Great Song - Some Band';"); let port;
const AUDIO = Buffer.alloc(4096, 0x55), META = Buffer.from("StreamTitle='Some Band - Great Song';"); let port;
const fake = http.createServer((req, res) => {
const u = new URL(req.url, 'http://x'), p = u.pathname, J = o => { res.writeHead(200, { 'content-type': 'application/json' }); res.end(JSON.stringify(o)); };
if (p === '/json/stations/search') return J([
@@ -32,11 +32,12 @@ const fake = http.createServer((req, res) => {
r = await call('/play/journeyfm'); assert.equal(r.status, 200); assert.equal(r.headers.get('content-type'), 'audio/mpeg'); let body = Buffer.from(await r.arrayBuffer());
assert.equal(body.length, 3072, 'the song-title blocks are taken out of the audio'); assert.ok(body.equals(AUDIO.subarray(0, 3072)), 'and the audio is untouched');
await new Promise(ok => setTimeout(ok, 100)); r = await j(await call('/now/aaaaaaaa-1111')); assert.equal(r.title, '', 'nothing yet for a station not played');
r = await call('/play/aaaaaaaa-1111'); assert.equal(r.status, 200); await r.arrayBuffer(); r = await j(await call('/now/aaaaaaaa-1111')); assert.equal(r.title, 'Great Song - Some Band', 'the title carried in the stream is reported');
r = await call('/play/aaaaaaaa-1111'); assert.equal(r.status, 200); await r.arrayBuffer(); r = await j(await call('/now/aaaaaaaa-1111')); assert.equal(r.title, 'Great Song', 'the title carried in the stream is reported'); assert.equal(r.artist, 'Some Band', '"Artist - Title" is split into the artist and the title'); assert.equal(r.startedAt, 0, 'with no clock from the station there is no start time');
r = await call('/play/eeeeeeee-5555'); assert.equal(r.status, 502, 'a playlist file is not a stream');
assert.equal((await call('/play/..%2Fx')).status, 404); assert.equal((await call('/play/zzzzzzzz-9999')).status, 502, 'an unknown station is an error, not a crash');
// The Journey's own feed
r = await j(await call('/now/journeyfm')); assert.equal(r.title, "A Lil' Church (Nobody's Too Lost)"); assert.equal(r.artist, 'Tobymac'); assert.equal(r.recent.length, 2, "the station's own sound bites (no artist) are not listed songs");
r = await j(await call('/now/journeyfm')); assert.equal(r.title, "A Lil' Church (Nobody's Too Lost)"); assert.equal(r.artist, 'Tobymac'); assert.equal(new Date(r.startedAt).toISOString(), '2026-10-25T02:01:37.000Z', "the station's clock (US Eastern) becomes a real moment in time"); assert.equal(r.recent.length, 2, "the station's own sound bites (no artist) are not listed songs");
const E = radio.easternEpoch; assert.equal(new Date(E('01/15/2026 08:00:00')).toISOString(), '2026-01-15T13:00:00.000Z', 'winter: Eastern time is 5 hours behind'); assert.equal(new Date(E('07/04/2026 12:00:00')).toISOString(), '2026-07-04T16:00:00.000Z', 'summer: 4 hours behind'); assert.equal(new Date(E('03/08/2026 03:30:00')).toISOString(), '2026-03-08T07:30:00.000Z', 'just after the clocks go forward'); assert.equal(E('nonsense'), 0);
// saved stations
assert.equal((await call('/stations', { method: 'POST', body: JSON.stringify({ id: 'aaaaaaaa-1111' }) })).status, 200); r = await j(await call('/stations')); assert.deepEqual(r.mine.map(s => s.name), ['Good FM']);
await call('/stations', { method: 'POST', body: JSON.stringify({ id: 'aaaaaaaa-1111' }) }); assert.equal((await j(await call('/stations'))).mine.length, 1, 'saving twice keeps one');