Add radio embed widget, SSE real-time, song history, moderation panel, and Auto DJ

- Embed widget: standalone iframe player with dark/light/transparent themes, copy-paste embed code admin page
- Real-time SSE: streaming now-playing/listeners/dj events, replaces polling in radio-player and embed
- Song history: auto-records song changes to radio_song_plays table, Filament resource to view
- DJ moderation: unified panel for shouts approval, song request queue, DJ applications
- Auto DJ: playlist management with round-robin playback when no DJ is live
- Refactored radio-player Alpine component to use EventSource API with auto-reconnect
This commit is contained in:
root
2026-05-24 14:07:32 +02:00
parent 5476dce882
commit 0c6c558a59
32 changed files with 2236 additions and 29 deletions
@@ -167,6 +167,18 @@ class RadioController extends Controller
public function nowPlaying(): JsonResponse
{
$autoDj = Cache::get('radio_auto_dj_active');
if ($autoDj !== null) {
return response()->json([
'enabled' => true,
'song' => $autoDj['title'],
'artist' => $autoDj['artist'] ?? null,
'title' => $autoDj['title'],
'is_auto_dj' => true,
]);
}
$nowPlaying = Cache::remember('radio_nowplaying', 10, function () {
$apiUrl = $this->getSetting(RadioSettings::NowPlayingEnabled)
? ($this->getSetting(RadioSettings::NowPlayingApiUrl) ?: $this->streamService->getAzureCastApiUrl())
@@ -175,6 +187,8 @@ class RadioController extends Controller
return $apiUrl ? $this->streamService->getNowPlaying($apiUrl) : ['enabled' => false, 'song' => null];
});
$nowPlaying['is_auto_dj'] = false;
return response()->json($nowPlaying);
}
@@ -195,9 +209,13 @@ class RadioController extends Controller
{
$dj = $this->scheduleService->getCurrentDJ($this->getSetting(RadioSettings::CurrentDjId));
$autoDj = Cache::get('radio_auto_dj_active');
return response()->json([
'dj' => $dj,
'is_live' => $dj !== null,
'is_auto_dj' => $autoDj !== null,
'auto_dj_song' => $autoDj['title'] ?? null,
]);
}
@@ -218,6 +236,8 @@ class RadioController extends Controller
$streamUrl = $this->streamService->formatStreamUrl($settings[RadioSettings::StreamUrl->value] ?? '');
$azureCast = $this->streamService->detectAzureCast();
$autoDj = Cache::get('radio_auto_dj_active');
return response()->json([
'enabled' => (bool) ($settings[RadioSettings::Enabled->value] ?? false),
'stream_url' => $streamUrl,
@@ -231,6 +251,8 @@ class RadioController extends Controller
'widget_position' => $settings[RadioSettings::WidgetPosition->value] ?? 'bottom-right',
'is_azurecast' => $azureCast['detected'],
'azurecast_detected' => $azureCast['detected'],
'is_auto_dj' => $autoDj !== null,
'auto_dj_song' => $autoDj['title'] ?? null,
]);
}