Files
radio/api/nowplaying.php
T

172 lines
6.6 KiB
PHP

<?php
/**
* api/nowplaying.php
* ------------------
* Fetches the station's current metadata from whatever backend the operator
* uses (Icecast, AzuraCast, Shoutcast, or a custom JSON feed) and returns a
* single normalized shape the browser can rely on:
*
* {
* "online": true|false,
* "artist": "…",
* "title": "…",
* "album": "…",
* "art": "…" | "", // empty if the provider has no art
* "listeners": 12 | null,
* "listeners_peak": 34 | null
* }
*
* Doing this server-side avoids browser CORS problems and hides internal
* backend addresses (e.g. 127.0.0.1 / LAN IPs) from visitors.
*/
require_once __DIR__ . '/_bootstrap.php';
$provider = cfg('nowplaying.provider');
$url = cfg('nowplaying.url');
$timeout = cfg('nowplaying.timeout');
$delim = cfg('nowplaying.delimiter');
$out = [
'online' => false,
'artist' => '',
'title' => '',
'album' => '',
'art' => '',
'listeners' => null,
'listeners_peak' => null,
];
/** Remove configured junk (e.g. " (Remastered)", ".mp3") from a string. */
function clean_str($s) {
$patterns = cfg('nowplaying.cleanup');
if (empty($patterns)) return trim($s);
$isRegex = cfg('nowplaying.cleanup_is_regex');
foreach ($patterns as $p) {
if ($p === '') continue;
if ($isRegex) {
$out = @preg_replace('/' . str_replace('/', '\\/', $p) . '/i', '', $s);
if ($out !== null) $s = $out; // ignore invalid patterns
} else {
$s = str_ireplace($p, '', $s);
}
}
return trim($s);
}
/** Split "Artist <delim> Title" into [artist, title]. */
function split_title($combined, $delim) {
$combined = trim((string) $combined);
if ($combined === '') return ['', ''];
if ($delim !== '' && strpos($combined, $delim) !== false) {
$parts = explode($delim, $combined, 2);
return [trim($parts[0]), trim($parts[1])];
}
// No delimiter found — treat the whole thing as the title.
return ['', $combined];
}
$raw = http_get($url, $timeout);
if ($raw === null) {
json_out($out); // backend unreachable -> offline
}
$data = json_decode($raw, true);
if (!is_array($data)) {
json_out($out);
}
switch ($provider) {
/* --------------------------------------------------------------- */
case 'azuracast':
$np = $data['now_playing'] ?? [];
$song = $np['song'] ?? [];
$out['artist'] = (string) ($song['artist'] ?? '');
$out['title'] = (string) ($song['title'] ?? '');
$out['album'] = (string) ($song['album'] ?? '');
$out['art'] = (string) ($song['art'] ?? '');
if ($out['artist'] === '' && $out['title'] === '' && !empty($song['text'])) {
[$out['artist'], $out['title']] = split_title($song['text'], $delim);
}
$out['listeners'] = isset($data['listeners']['current']) ? (int) $data['listeners']['current'] : null;
$out['listeners_peak'] = isset($data['listeners']['total']) ? (int) $data['listeners']['total'] : null;
$out['online'] = !empty($data['is_online']) && ($out['title'] !== '' || $out['artist'] !== '');
break;
/* --------------------------------------------------------------- */
case 'shoutcast':
$combined = $data['streamtitle'] ?? $data['songtitle'] ?? '';
[$out['artist'], $out['title']] = split_title($combined, $delim);
$out['listeners'] = isset($data['currentlisteners']) ? (int) $data['currentlisteners'] : null;
$out['listeners_peak'] = isset($data['peaklisteners']) ? (int) $data['peaklisteners'] : null;
$out['online'] = ($out['title'] !== '' || $out['artist'] !== '');
break;
/* --------------------------------------------------------------- */
case 'custom':
$c = cfg('nowplaying.custom');
if (!empty($c['title_combined'])) {
[$out['artist'], $out['title']] = split_title(dot_get($data, $c['title_combined'], ''), $delim);
} else {
$out['artist'] = (string) dot_get($data, $c['artist'], '');
$out['title'] = (string) dot_get($data, $c['title'], '');
}
$out['album'] = (string) dot_get($data, $c['album'], '');
$out['art'] = (string) dot_get($data, $c['art'], '');
$listeners = dot_get($data, $c['listeners'], null);
$out['listeners'] = ($listeners === null) ? null : (int) $listeners;
$out['online'] = ($out['title'] !== '' || $out['artist'] !== '');
break;
/* --------------------------------------------------------------- */
case 'icecast':
default:
$ice = $data['icestats'] ?? [];
$source = $ice['source'] ?? null;
if ($source === null) break;
// "source" can be a single object or a list of mounts.
$sources = isset($source[0]) ? $source : [$source];
$mount = cfg('nowplaying.mount');
$chosen = null;
if ($mount !== '') {
foreach ($sources as $s) {
$listenurl = (string) ($s['listenurl'] ?? '');
if ($listenurl !== '' && substr($listenurl, -strlen($mount)) === $mount) {
$chosen = $s;
break;
}
}
}
if ($chosen === null) {
// First source that actually has a title, else the first one.
foreach ($sources as $s) {
if (!empty($s['title']) || !empty($s['artist'])) { $chosen = $s; break; }
}
if ($chosen === null) $chosen = $sources[0];
}
// Liquidsoap can emit artist/title separately; prefer that.
if (!empty($chosen['artist']) || !empty($chosen['title'])) {
$maybeArtist = (string) ($chosen['artist'] ?? '');
$maybeTitle = (string) ($chosen['title'] ?? '');
if ($maybeArtist !== '' && $maybeTitle !== '') {
$out['artist'] = trim($maybeArtist);
$out['title'] = trim($maybeTitle);
} else {
[$out['artist'], $out['title']] = split_title($maybeTitle !== '' ? $maybeTitle : $maybeArtist, $delim);
}
}
$out['listeners'] = isset($chosen['listeners']) ? (int) $chosen['listeners'] : null;
$out['listeners_peak'] = isset($chosen['listener_peak']) ? (int) $chosen['listener_peak'] : null;
$out['online'] = ($out['title'] !== '' || $out['artist'] !== '');
break;
}
// Strip configured junk from the metadata before returning it.
$out['artist'] = clean_str($out['artist']);
$out['title'] = clean_str($out['title']);
json_out($out);