2026-07-24 05:34:07 -05:00
2026-07-24 05:34:07 -05:00
2026-07-24 05:34:07 -05:00
2026-07-24 05:34:07 -05:00
2026-07-24 05:34:07 -05:00
2025-04-11 12:06:32 -05:00
2026-07-24 05:34:07 -05:00
2026-07-24 05:34:07 -05:00
2025-04-11 12:06:32 -05:00
2026-07-24 05:34:07 -05:00

Radio

A small, self-hostable website for an internet radio station. Point it at your stream, edit one .env file, and you get a live player with album art, a "now playing" title, a recently-played history, listener counts, and optional lyrics — no code changes required.

Originally built for radio.dou.bet; this version is generic so anyone can drop in their own configuration.

How it works

browser  ──▶  index.php / assets/*      (the page + player)
browser  ──▶  api/nowplaying.php  ──▶   Icecast / AzuraCast / Shoutcast / custom
browser  ──▶  api/artwork.php     ──▶   Genius API      (optional)
browser  ──▶  api/lyrics.php      ──▶   Genius song page (optional)

The browser only ever talks to this site. PHP fetches your stream backend server-side and returns one normalized JSON shape, which means no CORS headaches and your internal backend address (e.g. 127.0.0.1:8000 behind nginx) is never exposed to visitors. A typical deployment is nginx → PHP-FPM on the web box, with nginx also proxying the audio stream from Icecast.

Requirements

  • PHP 7.4+ (8.x recommended) with cURL, served by any web server (nginx + PHP-FPM, Apache, Caddy…)
  • A running stream with a now-playing endpoint — Icecast, AzuraCast, Shoutcast, or any JSON feed
  • (optional) a free Genius API token for album art and lyrics

Quick start

git clone https://git.dou.bet/iamdoubz/radio.git
cd radio
cp .env.example .env
# edit .env — at minimum set SITE_NAME, STREAM_URL, and NOWPLAYING_URL
php -S localhost:8000        # for a quick local test

Then open http://localhost:8000. For production, serve the folder with nginx/Apache and PHP-FPM (see the example config near the bottom).

Configuration

Everything lives in .env — see .env.example for the fully documented list. The main groups are:

  • Branding & metadata — SITE_NAME, SITE_DESCRIPTION, SITE_KEYWORDS, SITE_URL, OG_IMAGE.
  • Stream — STREAM_URL (what the browser plays) and STREAM_TYPE.
  • Now playing — NOWPLAYING_PROVIDER, NOWPLAYING_URL, and provider-specific keys (below).
  • Artwork & lyrics — ARTWORK_SOURCE, LYRICS_ENABLED, GENIUS_TOKEN.
  • Theme & colors — THEME_BG, THEME_ACCENT, THEME_TEXT, THEME_FONT, … (drive CSS variables directly).

Now-playing providers

Set NOWPLAYING_PROVIDER and NOWPLAYING_URL:

Provider NOWPLAYING_URL example Notes
icecast http://127.0.0.1:8000/status-json.xsl Set ICECAST_MOUNT if you run several mounts. Splits the stream title on TITLE_DELIMITER.
azuracast https://azura.example.com/api/nowplaying/your_station Richest option — provides artist, title, album, art, and listeners natively (no Genius needed).
shoutcast http://127.0.0.1:8000/stats?json=1 Splits the stream title on TITLE_DELIMITER.
custom any URL returning JSON Map fields with CUSTOM_ARTIST_PATH, CUSTOM_TITLE_PATH, CUSTOM_ART_PATH, etc. (dot notation, e.g. data.track.0.artist).

TITLE_DELIMITER is how a combined Artist – Title string is split. The original dou.bet station uses " <-|-> "; most setups use " - ".

Artwork & lyrics

ARTWORK_SOURCE controls where cover art comes from:

  • auto — use the art your provider supplies (e.g. AzuraCast); otherwise look it up on Genius; otherwise show the placeholder.
  • provider — only ever use provider art.
  • genius — always look art up on Genius.
  • none — always show FALLBACK_ART.

Lyrics require LYRICS_ENABLED=true and a Genius token. Lyrics are scraped from the public Genius song page and returned as plain text; if Genius changes their page layout, api/lyrics.php may need a tweak.

You can hard-code artwork for specific tracks by creating config/overrides.json:

{ "artwork": [ { "artist": "Steve Reich", "art": "https://example.com/reich.jpg" } ] }

Theming

Colors and fonts are exposed as CSS variables generated from your .env, so you can re-skin the site without editing CSS. For deeper layout changes edit assets/css/radio.css, which uses those variables (var(--accent), var(--bg), …).

Project structure

config.php            # loads .env, applies defaults, exposes cfg()/config()
index.php             # the page
partials/head.php     # <head>: meta, SEO, theme variables, analytics
partials/noscript.php # no-JavaScript fallback
api/_bootstrap.php    # shared helpers (config, HTTP, JSON)
api/nowplaying.php    # normalizes Icecast/AzuraCast/Shoutcast/custom
api/artwork.php       # optional Genius album-art lookup
api/lyrics.php        # optional Genius lyrics scraper
assets/css/radio.css  # themeable styles
assets/js/player.js   # vanilla-JS player (no jQuery)
assets/img/           # placeholder art
.env.example          # every setting, documented

nginx + PHP-FPM example

server {
    server_name radio.example.com;
    root /var/www/radio;
    index index.php;

    # Proxy the audio stream from Icecast (keeps Icecast off the public net).
    location = /stream {
        proxy_pass http://127.0.0.1:8000/your_mount;
        proxy_set_header Host $http_host;
    }

    location / { try_files $uri $uri/ /index.php$is_args$args; }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }
}

With the server-side api/nowplaying.php proxy you no longer need the old status-json.xsl / peers.xsl nginx location blocks — PHP fetches the backend directly via NOWPLAYING_URL.

Security notes

  • .env is gitignored; keep your Genius token there, never in committed code.
  • Set SHOW_ERRORS=false and DEBUG=false in production.
  • The lyrics/artwork endpoints echo back only sanitized data (plain-text lyrics, provider URLs).

Credits

Broadcast with Icecast and Mixxx; artwork and lyrics via the Genius API. Licensed under the terms in LICENSE.

S
Description
Source code for my radio website
https://radio.dou.bet
Readme MIT
1.1 MiB
0 Stars 1 Watchers 0 Forks
Languages
PHP 51.5%
JavaScript 36%
CSS 12.5%