123 lines
4.5 KiB
PHP
Executable File
123 lines
4.5 KiB
PHP
Executable File
<?php
|
|
// Broadcast and track-history logic shared by the poller, the CLI tools and the live page.
|
|
|
|
if (basename($_SERVER['SCRIPT_FILENAME'] ?? '') === basename(__FILE__)) {
|
|
http_response_code(403);
|
|
exit;
|
|
}
|
|
|
|
require_once __DIR__ . '/icecast.php';
|
|
|
|
// A live broadcast not seen for this long is considered ended (covers brief encoder drop-outs)
|
|
const BROADCAST_GRACE_MINUTES = 5;
|
|
// Tracks are cleared this long after their broadcast ends
|
|
const TRACK_RETENTION_MINUTES = 10;
|
|
|
|
function liveBroadcast(PDO $db): ?array {
|
|
$row = $db->query(
|
|
'SELECT * FROM broadcasts
|
|
WHERE started_at IS NOT NULL AND ended_at IS NULL
|
|
ORDER BY started_at DESC LIMIT 1'
|
|
)->fetch();
|
|
return $row ?: null;
|
|
}
|
|
|
|
function pendingBroadcast(PDO $db): ?array {
|
|
$row = $db->query(
|
|
'SELECT * FROM broadcasts
|
|
WHERE started_at IS NULL AND ended_at IS NULL
|
|
ORDER BY created_at DESC LIMIT 1'
|
|
)->fetch();
|
|
return $row ?: null;
|
|
}
|
|
|
|
// The show for the live broadcast, or the queued one if nothing is live yet
|
|
function currentShow(PDO $db): ?array {
|
|
$row = $db->query(
|
|
'SELECT s.* FROM broadcasts b
|
|
JOIN shows s ON s.id = b.show_id
|
|
WHERE b.ended_at IS NULL
|
|
ORDER BY b.started_at IS NULL, b.started_at DESC, b.created_at DESC
|
|
LIMIT 1'
|
|
)->fetch();
|
|
return $row ?: null;
|
|
}
|
|
|
|
// Assigns a show to the live broadcast, or queues it for the next one if nothing is live.
|
|
// Returns 'live' or 'queued'.
|
|
function setBroadcastShow(PDO $db, int $showId): string {
|
|
$live = liveBroadcast($db);
|
|
if ($live !== null) {
|
|
$db->prepare('UPDATE broadcasts SET show_id = ? WHERE id = ?')->execute([$showId, $live['id']]);
|
|
return 'live';
|
|
}
|
|
clearQueuedShow($db);
|
|
$db->prepare('INSERT INTO broadcasts (show_id) VALUES (?)')->execute([$showId]);
|
|
return 'queued';
|
|
}
|
|
|
|
function clearQueuedShow(PDO $db): void {
|
|
$db->exec('DELETE FROM broadcasts WHERE started_at IS NULL');
|
|
}
|
|
|
|
// Tracks from the most recent broadcast that has started, newest first
|
|
function recentTracks(PDO $db, int $limit = 100): array {
|
|
$stmt = $db->prepare(
|
|
'SELECT * FROM played_tracks
|
|
WHERE broadcast_id = (SELECT id FROM broadcasts WHERE started_at IS NOT NULL ORDER BY started_at DESC LIMIT 1)
|
|
ORDER BY played_at DESC, id DESC
|
|
LIMIT ?'
|
|
);
|
|
$stmt->bindValue(1, $limit, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
return $stmt->fetchAll();
|
|
}
|
|
|
|
// Record one poll of the Icecast status. $track is the result of fetchCurrentTrack(): an array while
|
|
// streaming or null when offline. (Don't call this when the status API was unreachable.)
|
|
function recordPoll(PDO $db, ?array $track): void {
|
|
$db->exec(
|
|
'UPDATE broadcasts SET ended_at = last_seen_at
|
|
WHERE started_at IS NOT NULL AND ended_at IS NULL
|
|
AND last_seen_at < NOW() - INTERVAL ' . BROADCAST_GRACE_MINUTES . ' MINUTE'
|
|
);
|
|
$db->exec(
|
|
'DELETE pt FROM played_tracks pt
|
|
JOIN broadcasts b ON b.id = pt.broadcast_id
|
|
WHERE b.ended_at < NOW() - INTERVAL ' . TRACK_RETENTION_MINUTES . ' MINUTE'
|
|
);
|
|
|
|
if ($track === null) return;
|
|
|
|
$broadcast = liveBroadcast($db);
|
|
if ($broadcast !== null) {
|
|
$db->prepare('UPDATE broadcasts SET last_seen_at = NOW() WHERE id = ?')->execute([$broadcast['id']]);
|
|
$broadcastId = (int)$broadcast['id'];
|
|
} elseif (($pending = pendingBroadcast($db)) !== null) {
|
|
$db->prepare('UPDATE broadcasts SET started_at = NOW(), last_seen_at = NOW() WHERE id = ?')->execute([$pending['id']]);
|
|
$broadcastId = (int)$pending['id'];
|
|
} else {
|
|
// Nobody queued a show; record the broadcast anyway so track history still works
|
|
$db->exec('INSERT INTO broadcasts (started_at, last_seen_at) VALUES (NOW(), NOW())');
|
|
$broadcastId = (int)$db->lastInsertId();
|
|
}
|
|
|
|
if ($track['title'] === '') return;
|
|
|
|
$stmt = $db->prepare('SELECT title, artist, album FROM played_tracks WHERE broadcast_id = ? ORDER BY played_at DESC, id DESC LIMIT 1');
|
|
$stmt->execute([$broadcastId]);
|
|
$last = $stmt->fetch();
|
|
if ($last && $last['title'] === $track['title'] && $last['artist'] === $track['artist'] && $last['album'] === $track['album']) {
|
|
return;
|
|
}
|
|
|
|
$db->prepare('INSERT INTO played_tracks (broadcast_id, title, artist, album, art_url) VALUES (?, ?, ?, ?, ?)')
|
|
->execute([
|
|
$broadcastId,
|
|
mb_substr($track['title'], 0, 255),
|
|
mb_substr($track['artist'], 0, 255),
|
|
mb_substr($track['album'], 0, 255),
|
|
lookupAlbumArt($track['artist'], $track['album']),
|
|
]);
|
|
}
|