a22ddfb6d3
First release of a-radio — a free, focused SomaFM player for the
WordPress admin. Extracted-and-rebuilt from RangerPlex's RadioPlayer
component, simplified along the way (no Node CORS proxy needed; the
browser plays SomaFM streams directly).
Phase A deliverables — all in this commit:
PLAYER SURFACES
- Dashboard widget on WP Admin → Dashboard (compact: play/pause +
station select + volume).
- Dedicated admin page at WP Admin → Radio → My Radio (larger
layout, station genre badge, volume %% display).
- Both surfaces share the same JS — picking a station on one mirrors
to the other within the same admin session.
STATIONS
- 44 SomaFM stations grouped by 10 genres (Ambient / Electronic /
Lounge / Rock / Metal / Jazz / World / Reggae / Holiday / Specials).
- Ported verbatim from RangerPlex's RadioPlayer.tsx.
- Default station: Groove Salad (most popular SomaFM channel, safe
ambient/coding pick).
STATE
- Per-user state stored in user_meta under key `radio_state`:
{ station_id, volume, theme, last_played_at }.
- Settings page lets the user pick default station, default volume,
theme (auto/light/dark), and hide the dashboard widget.
- AJAX endpoint `radio_save_state` persists changes without a page
reload. Nonce-protected, capability-checked, only known keys
accepted, station validated against the list, volume clamped to
[0,1].
UPDATES
- Self-hosted updater wired to Gitea (ranger/a-radio) from commit 1.
- Direct port of Buddy's inc/updater.php with BUDDY_* → RADIO_* and
buddy_* → radio_* renames. Same 12h-success / 1h-error caching.
ARCHITECTURE
- No `wp` prefix (trademark policy).
- GPL v2+ public Gitea repo from day one.
- Vanilla JS only — no React, no webpack, no minified-only files.
- CSS-only animations, all assets local.
- Single H1 per admin page.
- Direct HTML5 <audio> playback (SomaFM has CORS headers; no PHP
audio proxy needed). This is the key simplification vs RangerPlex.
COMPLIANCE
- "Powered by SomaFM" credit displayed on both player surfaces with
link to somafm.com. About page invites users to donate to SomaFM
directly.
PHASE PROGRESSION (not in this commit)
- Phase B — Settings polish + retry on transient stream errors +
README.md formatted for WP.org submission.
- Phase C — Now-playing metadata via SomaFM's per-station song
history endpoint (this is where the RangerPlex proxy logic ports
to — server-side, for metadata not audio).
- Phase D — [ranger_radio] shortcode for frontend embedding.
- Phase E — Favorites system.
- Phase F — Multi-provider (Radio Paradise / NTS / KEXP / BBC).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
132 lines
12 KiB
PHP
132 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* SomaFM station list — 44 stations grouped into 10 genres.
|
|
*
|
|
* Ported verbatim from the RangerPlex Ranger Radio app. URLs use the
|
|
* ice1.somafm.com 128kbps MP3 stream endpoints; SomaFM has had CORS
|
|
* headers for years so the browser can hit these directly.
|
|
*
|
|
* Genre ordering follows the original RangerPlex grouping:
|
|
* Ambient → Electronic → Lounge → Rock → Metal → Jazz → World →
|
|
* Reggae → Holiday → Specials.
|
|
*
|
|
* If SomaFM ever rotates a stream URL, update it here.
|
|
* Reference: https://somafm.com/listen/
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) { exit; }
|
|
|
|
/**
|
|
* Return all stations as an ordered array, each entry containing:
|
|
* id, name, url, genre, description.
|
|
*
|
|
* @return array[]
|
|
*/
|
|
function radio_get_stations_flat() {
|
|
return array(
|
|
// Ambient / Focus
|
|
array( 'id' => 'soma-groovesalad', 'name' => 'Groove Salad', 'url' => 'https://ice1.somafm.com/groovesalad-128-mp3', 'genre' => 'Ambient', 'description' => 'Ambient/downtempo beats and grooves' ),
|
|
array( 'id' => 'soma-dronezone', 'name' => 'Drone Zone', 'url' => 'https://ice1.somafm.com/dronezone-128-mp3', 'genre' => 'Ambient', 'description' => 'Atmospheric textures with minimal beats' ),
|
|
array( 'id' => 'soma-deepspaceone', 'name' => 'Deep Space One', 'url' => 'https://ice1.somafm.com/deepspaceone-128-mp3', 'genre' => 'Ambient', 'description' => 'Deep ambient electronic and space music' ),
|
|
array( 'id' => 'soma-groovesalad-classic', 'name' => 'Groove Salad Classic', 'url' => 'https://ice1.somafm.com/gsclassic-128-mp3', 'genre' => 'Ambient', 'description' => 'Classic early 2000s ambient/downtempo' ),
|
|
array( 'id' => 'soma-spacestation', 'name' => 'Space Station Soma', 'url' => 'https://ice1.somafm.com/spacestation-128-mp3', 'genre' => 'Ambient', 'description' => 'Spaced-out ambient and mid-tempo electronica' ),
|
|
array( 'id' => 'soma-synphaera', 'name' => 'Synphaera Radio', 'url' => 'https://ice1.somafm.com/synphaera-128-mp3', 'genre' => 'Ambient', 'description' => 'Modern electronic ambient and space music' ),
|
|
array( 'id' => 'soma-darkzone', 'name' => 'The Dark Zone', 'url' => 'https://ice1.somafm.com/darkzone-128-mp3', 'genre' => 'Ambient', 'description' => 'The darker side of deep ambient' ),
|
|
array( 'id' => 'soma-missioncontrol', 'name' => 'Mission Control', 'url' => 'https://ice1.somafm.com/missioncontrol-128-mp3','genre'=> 'Ambient', 'description' => 'Celebrating NASA and space explorers' ),
|
|
|
|
// Electronic / Coding
|
|
array( 'id' => 'soma-defcon', 'name' => 'DEF CON Radio', 'url' => 'https://ice1.somafm.com/defcon-128-mp3', 'genre' => 'Electronic', 'description' => 'Music for hacking and coding' ),
|
|
array( 'id' => 'soma-beatblender', 'name' => 'Beat Blender', 'url' => 'https://ice1.somafm.com/beatblender-128-mp3', 'genre' => 'Electronic', 'description' => 'Deep-house and downtempo chill' ),
|
|
array( 'id' => 'soma-cliqhop', 'name' => 'cliqhop idm', 'url' => 'https://ice1.somafm.com/cliqhop-128-mp3', 'genre' => 'Electronic', 'description' => 'Intelligent Dance Music (IDM)' ),
|
|
array( 'id' => 'soma-thetrip', 'name' => 'The Trip', 'url' => 'https://ice1.somafm.com/thetrip-128-mp3', 'genre' => 'Electronic', 'description' => 'Progressive house and trance' ),
|
|
array( 'id' => 'soma-poptron', 'name' => 'PopTron', 'url' => 'https://ice1.somafm.com/poptron-128-mp3', 'genre' => 'Electronic', 'description' => 'Electropop and indie dance rock' ),
|
|
array( 'id' => 'soma-fluid', 'name' => 'Fluid', 'url' => 'https://ice1.somafm.com/fluid-128-mp3', 'genre' => 'Electronic', 'description' => 'Instrumental hiphop and liquid trap' ),
|
|
array( 'id' => 'soma-vaporwaves', 'name' => 'Vaporwaves', 'url' => 'https://ice1.somafm.com/vaporwaves-128-mp3', 'genre' => 'Electronic', 'description' => 'All Vaporwave, all the time' ),
|
|
array( 'id' => 'soma-digitalis', 'name' => 'Digitalis', 'url' => 'https://ice1.somafm.com/digitalis-128-mp3', 'genre' => 'Electronic', 'description' => 'Digitally affected analog rock' ),
|
|
array( 'id' => 'soma-dubstep', 'name' => 'Dub Step Beyond', 'url' => 'https://ice1.somafm.com/dubstep-128-mp3', 'genre' => 'Electronic', 'description' => 'Dubstep, dub and deep bass' ),
|
|
|
|
// Lounge / Chill
|
|
array( 'id' => 'soma-lush', 'name' => 'Lush', 'url' => 'https://ice1.somafm.com/lush-128-mp3', 'genre' => 'Lounge', 'description' => 'Sensuous mellow female vocals' ),
|
|
array( 'id' => 'soma-secretagent', 'name' => 'Secret Agent', 'url' => 'https://ice1.somafm.com/secretagent-128-mp3', 'genre' => 'Lounge', 'description' => 'Soundtrack for your mysterious life' ),
|
|
array( 'id' => 'soma-illinoisstreet', 'name' => 'Illinois Street Lounge', 'url' => 'https://ice1.somafm.com/illstreet-128-mp3', 'genre' => 'Lounge', 'description' => 'Classic bachelor pad and exotica' ),
|
|
array( 'id' => 'soma-bossabeyond', 'name' => 'Bossa Beyond', 'url' => 'https://ice1.somafm.com/bossa-128-mp3', 'genre' => 'Lounge', 'description' => 'Brazilian Bossa Nova and Samba' ),
|
|
|
|
// Rock / Alternative
|
|
array( 'id' => 'soma-indiepop', 'name' => 'Indie Pop Rocks!', 'url' => 'https://ice1.somafm.com/indiepop-128-mp3', 'genre' => 'Rock', 'description' => 'Favorite indie pop tracks' ),
|
|
array( 'id' => 'soma-u80s', 'name' => 'Underground 80s', 'url' => 'https://ice1.somafm.com/u80s-128-mp3', 'genre' => 'Rock', 'description' => 'Early 80s UK Synthpop and New Wave' ),
|
|
array( 'id' => 'soma-seventies', 'name' => 'Left Coast 70s', 'url' => 'https://ice1.somafm.com/seventies-128-mp3', 'genre' => 'Rock', 'description' => 'Mellow album rock from the 70s' ),
|
|
array( 'id' => 'soma-folkforward', 'name' => 'Folk Forward', 'url' => 'https://ice1.somafm.com/folkfwd-128-mp3', 'genre' => 'Rock', 'description' => 'Indie Folk and Alt-folk' ),
|
|
array( 'id' => 'soma-bootliquor', 'name' => 'Boot Liquor', 'url' => 'https://ice1.somafm.com/bootliquor-128-mp3', 'genre' => 'Rock', 'description' => 'Americana Roots music' ),
|
|
|
|
// Metal / Heavy
|
|
array( 'id' => 'soma-metal', 'name' => 'Metal Detector', 'url' => 'https://ice1.somafm.com/metal-128-mp3', 'genre' => 'Metal', 'description' => 'From black to doom, thrash to post' ),
|
|
array( 'id' => 'soma-doomed', 'name' => 'Doomed', 'url' => 'https://ice1.somafm.com/doomed-128-mp3', 'genre' => 'Metal', 'description' => 'Dark industrial/ambient music' ),
|
|
|
|
// Jazz / Soul
|
|
array( 'id' => 'soma-sonicuniverse', 'name' => 'Sonic Universe', 'url' => 'https://ice1.somafm.com/sonicuniverse-128-mp3','genre' => 'Jazz', 'description' => 'Eclectic avant-garde jazz' ),
|
|
array( 'id' => 'soma-7soul', 'name' => 'Seven Inch Soul', 'url' => 'https://ice1.somafm.com/7soul-128-mp3', 'genre' => 'Jazz', 'description' => 'Vintage soul from vinyl 45s' ),
|
|
|
|
// World / International
|
|
array( 'id' => 'soma-thistle', 'name' => 'ThistleRadio', 'url' => 'https://ice1.somafm.com/thistle-128-mp3', 'genre' => 'World', 'description' => 'Celtic roots and branches' ),
|
|
array( 'id' => 'soma-suburbsofgoa', 'name' => 'Suburbs of Goa', 'url' => 'https://ice1.somafm.com/suburbsofgoa-128-mp3', 'genre' => 'World', 'description' => 'Asian world beats and beyond' ),
|
|
array( 'id' => 'soma-tikitime', 'name' => 'Tiki Time', 'url' => 'https://ice1.somafm.com/tikitime-128-mp3', 'genre' => 'World', 'description' => 'Classic Tiki and island rhythms' ),
|
|
|
|
// Reggae
|
|
array( 'id' => 'soma-reggae', 'name' => 'Heavyweight Reggae', 'url' => 'https://ice1.somafm.com/reggae-128-mp3', 'genre' => 'Reggae', 'description' => 'Reggae, Ska, Rocksteady classics' ),
|
|
|
|
// Holiday (seasonal)
|
|
array( 'id' => 'soma-xmaslounge', 'name' => 'Christmas Lounge', 'url' => 'https://ice1.somafm.com/christmas-128-mp3', 'genre' => 'Holiday', 'description' => 'Chilled holiday grooves' ),
|
|
array( 'id' => 'soma-xmasrocks', 'name' => 'Christmas Rocks!', 'url' => 'https://ice1.somafm.com/xmasrocks-128-mp3', 'genre' => 'Holiday', 'description' => 'Indie/alternative holiday season' ),
|
|
array( 'id' => 'soma-xmasinfrisko', 'name' => 'Xmas in Frisko', 'url' => 'https://ice1.somafm.com/xmasinfrisko-128-mp3', 'genre' => 'Holiday', 'description' => 'Wacky eclectic holiday mix' ),
|
|
array( 'id' => 'soma-jollysoul', 'name' => "Jolly Ol' Soul", 'url' => 'https://ice1.somafm.com/jollysoul-128-mp3', 'genre' => 'Holiday', 'description' => 'Soul of the season' ),
|
|
array( 'id' => 'soma-deptstore', 'name' => 'Department Store Christmas','url' => 'https://ice1.somafm.com/deptstore-128-mp3', 'genre' => 'Holiday', 'description' => 'Holiday elevator music' ),
|
|
|
|
// Specials / Eclectic
|
|
array( 'id' => 'soma-blackrock', 'name' => 'Black Rock FM', 'url' => 'https://ice1.somafm.com/brfm-128-mp3', 'genre' => 'Specials', 'description' => 'From Burning Man festival' ),
|
|
array( 'id' => 'soma-covers', 'name' => 'Covers', 'url' => 'https://ice1.somafm.com/covers-128-mp3', 'genre' => 'Specials', 'description' => 'Songs you know by artists you don\'t' ),
|
|
array( 'id' => 'soma-sf1033', 'name' => 'SF 10-33', 'url' => 'https://ice1.somafm.com/sf1033-128-mp3', 'genre' => 'Specials', 'description' => 'Ambient + SF public safety radio' ),
|
|
array( 'id' => 'soma-n5md', 'name' => 'n5MD Radio', 'url' => 'https://ice1.somafm.com/n5md-128-mp3', 'genre' => 'Specials', 'description' => 'Emotional experiments in music' ),
|
|
array( 'id' => 'soma-insound', 'name' => 'The In-Sound', 'url' => 'https://ice1.somafm.com/insound-128-mp3', 'genre' => 'Specials', 'description' => '60s/70s Hipster Euro Pop' ),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Return stations grouped by genre, in genre-display order.
|
|
*
|
|
* @return array Map of genre => array of station entries.
|
|
*/
|
|
function radio_get_stations_grouped() {
|
|
$genre_order = array( 'Ambient', 'Electronic', 'Lounge', 'Rock', 'Metal', 'Jazz', 'World', 'Reggae', 'Holiday', 'Specials' );
|
|
$grouped = array_fill_keys( $genre_order, array() );
|
|
foreach ( radio_get_stations_flat() as $station ) {
|
|
$genre = isset( $station['genre'] ) ? $station['genre'] : 'Specials';
|
|
if ( ! isset( $grouped[ $genre ] ) ) {
|
|
$grouped[ $genre ] = array();
|
|
}
|
|
$grouped[ $genre ][] = $station;
|
|
}
|
|
return $grouped;
|
|
}
|
|
|
|
/**
|
|
* Look up a station by its id.
|
|
*
|
|
* @param string $station_id
|
|
* @return array|null Station entry or null if not found.
|
|
*/
|
|
function radio_find_station( $station_id ) {
|
|
$station_id = sanitize_key( $station_id );
|
|
foreach ( radio_get_stations_flat() as $s ) {
|
|
if ( $s['id'] === $station_id ) { return $s; }
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Default station — the one new users land on. Groove Salad is the
|
|
* most popular SomaFM channel and a safe ambient/coding default.
|
|
*/
|
|
function radio_default_station_id() {
|
|
return 'soma-groovesalad';
|
|
}
|