A radio tower emitting sound waves, a music eq dancing in the wind, on air, streaming, internet radio station logo

# 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](https://radio.dou.bet); this version is generic so anyone can drop in their own configuration. ## Features - Custom player — play/pause, seek + volume, elapsed time, and a **LIVE** badge for live streams (or a plain native player with `PLAYER_STYLE=simple`). - Now playing from **Icecast, AzuraCast, Shoutcast, or any JSON feed**, normalized server-side. - Album art and lyrics via **Genius**, with an optional **Last.fm** art fallback. - Page background that recolors to the current album art (auto light/dark text). - **MediaSession** integration so track info appears in OS/lock-screen media controls. - Configurable title cleanup (strip `(Remastered)`, `[Explicit]`, `.mp3`, …). - Recently-played history, live listener count, and optional Matomo analytics. - Fully themeable from `.env` (CSS variables) — no CSS editing required. ## 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](https://genius.com/api-clients) for album art and lyrics ## Quick start ```bash 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 . 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`](.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 `" - "`. Use `TITLE_CLEANUP` to strip junk from titles (e.g. `.mp3`, `[Explicit]`, `(Remastered)`). Separate entries with `||`; set `CLEANUP_IS_REGEX=true` to treat each entry as a case-insensitive regex. ### 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`. If Genius has no cover, set `LASTFM_API_KEY` to fall back to Last.fm artwork. (`ARTWORK_SOURCE=lastfm` uses Last.fm only; `auto` tries Genius then Last.fm.) 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. Set `LYRICS_PREFER_ENGLISH=true` to prefer the first English-language match when a song has several Genius entries. ### Player & appearance - `PLAYER_STYLE` — `rich` (custom controls, default) or `simple` (native `