Add player functionality with HLS support and API integration

- Implemented a new Player class in player.js to handle audio playback, including HLS support using hls.js.
- Created a shared API module in api.js for making HTTP requests with proper error handling.
- Added DOM utility functions in dom.js for creating and clearing elements.
- Introduced WebSocket connection handling in ws.js for real-time updates.
- Developed a comprehensive CSS stylesheet for styling the application, including a high-contrast theme.
This commit is contained in:
Marco Mooren
2026-05-10 14:43:00 +02:00
commit e0a60f7b64
51 changed files with 9022 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
// Restore image_url from seed JSON files for any station where it is currently NULL.
// Match priority: explicit uuid → uuidFromSlug(slug) → exact name.
import 'dotenv/config';
import Database from 'better-sqlite3';
import { readFileSync, readdirSync } from 'node:fs';
import { resolve, dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createHash } from 'node:crypto';
const __dirname = dirname(fileURLToPath(import.meta.url));
const SEED_DIR = resolve(__dirname, '../../data/seed');
function uuidFromSlug(slug) {
const h = createHash('sha1').update('oradio:' + slug).digest('hex');
return [h.slice(0, 8), h.slice(8, 12), '5' + h.slice(13, 16), '8' + h.slice(17, 20), h.slice(20, 32)].join('-');
}
const db = new Database(process.env.DB_PATH || './data/db/oradio.sqlite');
const apply = process.argv.includes('--apply');
const entries = [];
for (const f of readdirSync(SEED_DIR).filter((x) => x.startsWith('stations') && x.endsWith('.json'))) {
try {
const data = JSON.parse(readFileSync(join(SEED_DIR, f), 'utf8'));
if (Array.isArray(data)) entries.push(...data);
} catch { }
}
const byUuid = new Map();
const byName = new Map();
for (const e of entries) {
if (!e.image_url) continue;
const u = e.uuid || (e.slug ? uuidFromSlug(e.slug) : null);
if (u) byUuid.set(u, e.image_url);
if (e.name) byName.set(e.name.toLowerCase(), e.image_url);
}
const rows = db.prepare(`SELECT id, uuid, name FROM stations WHERE image_url IS NULL OR image_url = ''`).all();
const upd = db.prepare('UPDATE stations SET image_url = ? WHERE id = ?');
let restored = 0;
for (const r of rows) {
const url = (r.uuid && byUuid.get(r.uuid)) || byName.get(r.name?.toLowerCase());
if (!url) continue;
console.log(`restore ${r.name} -> ${url}`);
if (apply) upd.run(url, r.id);
restored++;
}
console.log(`Done. restored=${restored}${apply ? '' : ' (dry run; pass --apply to write)'}`);