You've already forked Atomcms-edit
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:
@@ -42,10 +42,14 @@
|
||||
<!-- DJ Info -->
|
||||
<div x-show="currentDJ" class="flex items-center gap-4 mb-4">
|
||||
<div class="w-12 h-12 rounded-full bg-gradient-to-br from-amber-400 to-amber-600 flex items-center justify-center overflow-hidden">
|
||||
<img x-bind:src="djAvatar" x-bind:alt="djName" class="w-full h-full object-cover">
|
||||
<img x-show="!isAutoDj" x-bind:src="djAvatar" x-bind:alt="djName" class="w-full h-full object-cover">
|
||||
<svg x-show="isAutoDj" class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-amber-400">PRESENTATOR</p>
|
||||
<p x-show="!isAutoDj" class="text-xs text-amber-400">PRESENTATOR</p>
|
||||
<p x-show="isAutoDj" class="text-xs text-gray-400">AUTO DJ</p>
|
||||
<p x-text="djName" class="font-semibold">--</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,15 +110,72 @@ function radioPlayer() {
|
||||
currentDJ: null,
|
||||
djName: '--',
|
||||
djAvatar: '',
|
||||
isAutoDj: false,
|
||||
sseSource: null,
|
||||
|
||||
initPlayer() {
|
||||
this.checkVisibility();
|
||||
this.loadSettings();
|
||||
setInterval(() => this.updateListeners(), 30000);
|
||||
setInterval(() => this.updateNowPlaying(), 15000);
|
||||
this.connectSse();
|
||||
this.updateVisibilityByUrl();
|
||||
},
|
||||
|
||||
destroy() {
|
||||
if (this.sseSource) {
|
||||
this.sseSource.close();
|
||||
this.sseSource = null;
|
||||
}
|
||||
},
|
||||
|
||||
connectSse() {
|
||||
this.sseSource = new EventSource('/api/radio/sse');
|
||||
|
||||
this.sseSource.addEventListener('now-playing', (e) => {
|
||||
try {
|
||||
const data = JSON.parse(e.data);
|
||||
if (data && data.title) {
|
||||
this.trackTitle = data.title;
|
||||
this.trackArtist = data.artist || '';
|
||||
}
|
||||
} catch (err) {}
|
||||
});
|
||||
|
||||
this.sseSource.addEventListener('listeners', (e) => {
|
||||
try {
|
||||
const data = JSON.parse(e.data);
|
||||
if (data && data.count !== undefined) {
|
||||
this.listenerCount = data.count.toLocaleString();
|
||||
}
|
||||
} catch (err) {}
|
||||
});
|
||||
|
||||
this.sseSource.addEventListener('dj', (e) => {
|
||||
try {
|
||||
const data = JSON.parse(e.data);
|
||||
if (data && data.username) {
|
||||
this.djName = data.username;
|
||||
this.isAutoDj = data.is_auto_dj || false;
|
||||
if (data.look && !data.is_auto_dj) {
|
||||
this.djAvatar = 'https://www.habbo.nl/habbo-imaging/avatarimage?figure=' + data.look + '&size=m';
|
||||
} else {
|
||||
this.djAvatar = '';
|
||||
}
|
||||
this.currentDJ = data;
|
||||
} else {
|
||||
this.currentDJ = null;
|
||||
this.djName = '--';
|
||||
this.djAvatar = '';
|
||||
this.isAutoDj = false;
|
||||
}
|
||||
} catch (err) {}
|
||||
});
|
||||
|
||||
this.sseSource.onerror = () => {
|
||||
this.sseSource.close();
|
||||
setTimeout(() => this.connectSse(), 5000);
|
||||
};
|
||||
},
|
||||
|
||||
checkVisibility() {
|
||||
if (!this.showWidget) {
|
||||
this.showWidget = false;
|
||||
@@ -164,29 +225,6 @@ function radioPlayer() {
|
||||
audio.play().catch(() => {});
|
||||
this.isPlaying = true;
|
||||
}
|
||||
},
|
||||
|
||||
updateListeners() {
|
||||
fetch('/api/radio/listeners')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
this.listenerCount = data.count.toLocaleString();
|
||||
})
|
||||
.catch(() => {
|
||||
this.listenerCount = '--';
|
||||
});
|
||||
},
|
||||
|
||||
updateNowPlaying() {
|
||||
fetch('/api/radio/now-playing')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.title) {
|
||||
this.trackTitle = data.title;
|
||||
this.trackArtist = data.artist || '';
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user