chore: initial commit — Radio v0.1.0 (Phase A complete)
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>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* Radio — main admin page (WP Admin → Radio → My Radio).
|
||||
*
|
||||
* Larger player with the same controls as the dashboard widget. Both
|
||||
* surfaces share the same JS (assets/js/radio.js binds to every
|
||||
* .radio-player on the page).
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) { exit; }
|
||||
|
||||
function radio_render_main_page() {
|
||||
if ( ! current_user_can( 'read' ) ) {
|
||||
wp_die( esc_html__( 'You do not have permission to view this page.', 'radio' ) );
|
||||
}
|
||||
|
||||
$state = radio_get_state();
|
||||
$station = radio_find_station( $state['station_id'] );
|
||||
$stations = radio_get_stations_grouped();
|
||||
$count = count( radio_get_stations_flat() );
|
||||
?>
|
||||
<div class="wrap radio-wrap">
|
||||
<h1><?php esc_html_e( 'Radio', 'radio' ); ?></h1>
|
||||
|
||||
<p class="radio-intro">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %d = number of stations */
|
||||
esc_html__( 'A friendly tab of background music for your WordPress admin. %d hand-curated SomaFM stations across 10 genres, all free, no ads.', 'radio' ),
|
||||
(int) $count
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
|
||||
<div class="radio-player radio-player--main" data-radio-surface="main">
|
||||
<div class="radio-player__now radio-player__now--large">
|
||||
<div class="radio-player__label"><?php esc_html_e( 'Now Playing', 'radio' ); ?></div>
|
||||
<div class="radio-player__station-name" data-radio-name><?php echo esc_html( $station['name'] ); ?></div>
|
||||
<div class="radio-player__station-desc" data-radio-desc><?php echo esc_html( $station['description'] ); ?></div>
|
||||
<div class="radio-player__station-genre"><?php echo esc_html( $station['genre'] ); ?></div>
|
||||
</div>
|
||||
|
||||
<div class="radio-player__controls radio-player__controls--large">
|
||||
<button type="button" class="radio-player__play button button-primary button-hero" data-radio-play title="<?php esc_attr_e( 'Play', 'radio' ); ?>">
|
||||
<span class="dashicons dashicons-controls-play"></span>
|
||||
</button>
|
||||
|
||||
<div class="radio-player__volume">
|
||||
<span class="dashicons dashicons-controls-volumeon"></span>
|
||||
<input type="range" min="0" max="100" value="<?php echo esc_attr( (int) round( $state['volume'] * 100 ) ); ?>" data-radio-volume aria-label="<?php esc_attr_e( 'Volume', 'radio' ); ?>">
|
||||
<span class="radio-player__volume-pct" data-radio-volume-pct><?php echo esc_html( (int) round( $state['volume'] * 100 ) ); ?>%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="radio-player__station-select">
|
||||
<label for="radio-station-main"><?php esc_html_e( 'Station', 'radio' ); ?></label>
|
||||
<select id="radio-station-main" data-radio-station>
|
||||
<?php foreach ( $stations as $genre => $entries ) :
|
||||
if ( empty( $entries ) ) { continue; }
|
||||
?>
|
||||
<optgroup label="<?php echo esc_attr( $genre ); ?>">
|
||||
<?php foreach ( $entries as $entry ) : ?>
|
||||
<option value="<?php echo esc_attr( $entry['id'] ); ?>" data-url="<?php echo esc_attr( $entry['url'] ); ?>" data-desc="<?php echo esc_attr( $entry['description'] ); ?>" data-genre="<?php echo esc_attr( $entry['genre'] ); ?>" <?php selected( $entry['id'], $state['station_id'] ); ?>>
|
||||
<?php echo esc_html( $entry['name'] ); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</optgroup>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="radio-player__error" data-radio-error hidden></div>
|
||||
|
||||
<audio data-radio-audio preload="none"></audio>
|
||||
</div>
|
||||
|
||||
<p class="radio-player__credit radio-player__credit--main">
|
||||
<?php
|
||||
printf(
|
||||
wp_kses(
|
||||
/* translators: %s = link to somafm.com */
|
||||
__( 'Stations and streams provided by %s — an independent, listener-supported, commercial-free internet radio network. Please consider supporting them.', 'radio' ),
|
||||
array( 'a' => array( 'href' => true, 'target' => true, 'rel' => true ) )
|
||||
),
|
||||
'<a href="https://somafm.com/" target="_blank" rel="noopener">SomaFM</a>'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
Reference in New Issue
Block a user