Files
radio-free-elsewhere/CLAUDE.md
T
Daniel MesserandClaude Opus 5.5 74dfb3c146 Add recently played tracking with shows and broadcasts
- sql/schema.sql: shows, broadcasts and played_tracks tables, seeded
  with the five shows
- cli/poll_icecast.php: cron-run poller that records broadcasts and
  tracks every 15 seconds while streaming; tracks are cleared ten
  minutes after a broadcast ends
- cli/set_show.php: queue the next show, or assign one to the live
  broadcast
- recently_played.php: track history rows for live.php, with album art
  and album title linking to Last.fm
- live.php: recently played list, show name in heading, and per-show
  header background
- includes/icecast.php: Icecast parsing, album art and Last.fm helpers
  shared by now_playing.php and the poller
- includes/db.php: use UTC for the database session

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 06:45:44 -05:00

4.8 KiB

Radio Free Elsewhere

Project Overview

Radio Free Elsewhere is an Internet radio station streaming eclectic music (Celtic folk, ambient/space, surf rock, and more). It runs on Icecast hosted on a Yunohost server. Streams are live and occasional — a few times per week in the evenings, typically around 7:30pm Eastern, running roughly two hours. This is not a 24/7 automated station.

The shows have distinct themes and identities:

  • O'Morain's Pub — Celtic folk
  • Cosmoeuphoria — ambient and space music
  • Bisexual Surfers From the Twilight of Time — surf rock and rockabilly
  • The Bollocks - punk rock
  • The Spin - eclectic tunes from all genres

Scope

This repo covers the frontend only — the public-facing website. Icecast configuration, Yunohost server administration, and streaming software are out of scope. Do not look for or suggest changes to server-side streaming config.

Key Files

  • index.php — Main page. Contains the audio player, now-playing widget, upcoming schedule section, and about section. Show information is updated here manually before each broadcast.
  • now_playing.php — Fetches the currently playing track from the Icecast JSON status API and returns it as HTML. Called by the frontend every 15 seconds via setInterval.
  • live.php — Page for active broadcasts. Player on the left, recently played list on the right; the header background and heading change with the current show.
  • recently_played.php — Returns the current broadcast's track history as table rows for live.php.
  • includes/icecast.php — Shared Icecast fetching/parsing, album art lookup and Last.fm link helpers.
  • includes/db.php — getDB() returns a shared PDO connection (session time zone UTC).
  • includes/broadcasts.php — Broadcast lifecycle and track history logic.
  • cli/poll_icecast.php — Cron-run poller that records broadcasts and tracks.
  • cli/set_show.php — Sets which show is airing next (or now).

Architecture

  • Backend: PHP only. No Composer, no package manager, no build step.
  • Frontend: HTML5 UP Strata template with custom CSS additions. No npm, no webpack, no preprocessor — edit CSS directly in assets/css/main.css or inline <style> blocks.
  • Now-playing: index.php and live.php fetch now_playing.php via JavaScript every 15 seconds and inject the result into #now-playing. The fetch uses arrayBuffer + TextDecoder to handle the UTF-8 encoding correctly on the client side.

Database

MariaDB on localhost. Credentials live in config/db_credentials.php, which is gitignored and uploaded to the server separately; config/db_credentials.example.php is the template. Schema is in sql/schema.sql:

  • shows — the shows, with an optional background_image (filename in images/backgrounds/).
  • broadcasts — one row per streaming session: pending (queued by cli/set_show.php), live, or ended.
  • played_tracks — tracks per broadcast. Cleared ten minutes after the broadcast ends; no long-term history is kept.

cli/poll_icecast.php runs from cron every minute and polls Icecast four times, 15 seconds apart. A broadcast starts when the stream comes online and ends after five minutes unseen (so brief drop-outs don't split a show). Before going live, run php cli/set_show.php <slug> to choose the show.

Icecast Integration

  • Stream URL: https://radio.cyberpunklibrarian.nohost.me/rfe
  • Status API: https://radio.cyberpunklibrarian.nohost.me/status-json.xsl

The status API returns JSON with track metadata under icestats.source. The source key may be a single object or an array depending on how many mountpoints are active; now_playing.php handles both cases.

Known Encoding Quirk

Icecast double-encodes track metadata as Latin-1 interpreted as UTF-8. The fix in decodeMetaField() in includes/icecast.php deliberately converts the string from UTF-8 to ISO-8859-1 to strip the outer encoding layer and recover the correct UTF-8 byte sequence. Do not remove or "simplify" this conversion — it is intentional and necessary for non-ASCII characters (accented names, etc.) to display correctly.

Deployment

There is no CI/CD pipeline. Deployment is manual:

  1. Commit and push changes to the self-hosted Gitea repository.
  2. Upload changed files to the Yunohost web server via SFTP.

There is no staging environment — changes go straight to production.

Updating Show Information

Show info lives in the #one section of index.php. Previous shows are commented out rather than deleted, so there's a history of past broadcasts in the markup. When updating for a new show, comment out the current show block and add the new one above it.