172 lines
6.6 KiB
PHP
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);
|