Files
Atomcms-edit/app/Filament/Pages/Radio/AutoDjPlaylist.php
T
root 0c6c558a59 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
2026-05-24 14:07:32 +02:00

208 lines
7.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Filament\Pages\Radio;
use App\Models\RadioAutoDjTrack;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Table;
final class AutoDjPlaylist extends Page implements HasTable
{
use InteractsWithTable;
#[\Override]
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-play-circle';
#[\Override]
protected static string|\UnitEnum|null $navigationGroup = 'Radio';
#[\Override]
protected static ?string $navigationLabel = 'Auto DJ Playlist';
#[\Override]
protected static ?string $title = 'Auto DJ Playlist';
#[\Override]
protected string $view = 'filament.pages.radio.auto-dj-playlist';
public function table(Table $table): Table
{
return $table
->query(RadioAutoDjTrack::query())
->defaultSort('sort_order')
->columns([
TextColumn::make('sort_order')
->label('#')
->sortable()
->width(50),
TextColumn::make('title')
->label('Titel')
->searchable()
->sortable(),
TextColumn::make('artist')
->label('Artiest')
->searchable()
->sortable(),
TextColumn::make('duration')
->label('Duur')
->formatStateUsing(fn ($state) => $state ? gmdate('i:s', $state) : '-'),
TextColumn::make('play_count')
->label('Gespeeld')
->sortable(),
TextColumn::make('last_played_at')
->label('Laatst gespeeld')
->dateTime('d-m-Y H:i')
->placeholder('Nooit'),
IconColumn::make('is_active')
->label('Actief')
->boolean()
->trueIcon('heroicon-o-check-circle')
->falseIcon('heroicon-o-x-circle')
->trueColor('success')
->falseColor('danger'),
])
->recordActions(function ($record) {
return [
ActionGroup::make([
Action::make('toggle_active')
->label($record->is_active ? 'Deactiveren' : 'Activeren')
->icon($record->is_active ? 'heroicon-o-pause' : 'heroicon-o-play')
->action(fn () => $this->toggleActive($record)),
Action::make('move_up')
->label('Omhoog')
->icon('heroicon-o-chevron-up')
->action(fn () => $this->moveUp($record)),
Action::make('move_down')
->label('Omlaag')
->icon('heroicon-o-chevron-down')
->action(fn () => $this->moveDown($record)),
Action::make('delete')
->label('Verwijderen')
->icon('heroicon-o-trash')
->color('danger')
->requiresConfirmation()
->action(fn () => $record->delete()),
]),
];
})
->toolbarActions([
Action::make('create')
->label('Track Toevoegen')
->icon('heroicon-o-plus')
->color('primary')
->form([
TextInput::make('title')
->label('Titel')
->required()
->maxLength(255),
TextInput::make('artist')
->label('Artiest')
->maxLength(255),
TextInput::make('album')
->label('Album')
->maxLength(255),
TextInput::make('artwork_url')
->label('Afbeelding URL')
->url(),
TextInput::make('duration')
->label('Duur (seconden)')
->numeric()
->minValue(0),
Toggle::make('is_active')
->label('Actief')
->default(true),
])
->action(function (array $data) {
$this->createTrack($data);
}),
])
->poll('30s');
}
public function getStatusBadge(): string
{
$autoDj = cache('radio_auto_dj_active');
if ($autoDj) {
return 'Auto DJ is actief - speelt: ' . ($autoDj['title'] ?? 'onbekend');
}
return 'Auto DJ is niet actief';
}
private function createTrack(array $data): void
{
$maxOrder = RadioAutoDjTrack::max('sort_order') ?? 0;
RadioAutoDjTrack::create([
'title' => $data['title'],
'artist' => $data['artist'] ?? null,
'album' => $data['album'] ?? null,
'artwork_url' => $data['artwork_url'] ?? null,
'duration' => $data['duration'] ? (int) $data['duration'] : null,
'is_active' => $data['is_active'] ?? true,
'sort_order' => $maxOrder + 1,
]);
Notification::make()
->success()
->title('Track toegevoegd aan playlist')
->send();
}
private function toggleActive(RadioAutoDjTrack $track): void
{
$track->update(['is_active' => ! $track->is_active]);
Notification::make()
->success()
->title($track->is_active ? 'Track geactiveerd' : 'Track gedeactiveerd')
->send();
}
private function moveUp(RadioAutoDjTrack $track): void
{
$prev = RadioAutoDjTrack::where('sort_order', '<', $track->sort_order)
->orderBy('sort_order', 'desc')
->first();
if ($prev) {
$temp = $track->sort_order;
$track->update(['sort_order' => $prev->sort_order]);
$prev->update(['sort_order' => $temp]);
}
}
private function moveDown(RadioAutoDjTrack $track): void
{
$next = RadioAutoDjTrack::where('sort_order', '>', $track->sort_order)
->orderBy('sort_order')
->first();
if ($next) {
$temp = $track->sort_order;
$track->update(['sort_order' => $next->sort_order]);
$next->update(['sort_order' => $temp]);
}
}
#[\Override]
protected function getHeaderActions(): array
{
return [];
}
}