You've already forked Atomcms-edit
189 lines
9.2 KiB
PHP
Executable File
189 lines
9.2 KiB
PHP
Executable File
<!DOCTYPE html>
|
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Radio Player</title>
|
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
|
<style>
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
background: {{ $settings['theme'] === 'transparent' ? 'transparent' : ($settings['theme'] === 'light' ? '#ffffff' : '#1a1a2e') }};
|
|
color: {{ $settings['textColor'] }};
|
|
overflow: hidden;
|
|
}
|
|
.ep { width: 100%; height: 100vh; display: flex; flex-direction: column; padding: 16px; }
|
|
.ep-hdr { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
|
|
.ep-brand { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 14px; text-transform: uppercase; letter-spacing: 1px; }
|
|
.ep-brand .dot { width: 8px; height: 8px; border-radius: 50%; background: {{ $settings['primaryColor'] }}; animation: pulse 1.5s ease-in-out infinite; }
|
|
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
|
|
.ep-listeners { font-size: 12px; opacity: 0.7; display: flex; align-items: center; gap: 4px; }
|
|
.ep-np { flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: 24px 0; }
|
|
.ep-track { font-size: 18px; font-weight: 600; margin-bottom: 4px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
.ep-artist { font-size: 14px; opacity: 0.7; }
|
|
.ep-ctrls { display: flex; align-items: center; justify-content: center; gap: 16px; margin-bottom: 16px; }
|
|
.ep-btn { width: 56px; height: 56px; border-radius: 50%; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: transform 0.15s; background: {{ $settings['primaryColor'] }}; color: #fff; }
|
|
.ep-btn:hover { transform: scale(1.05); }
|
|
.ep-btn:active { transform: scale(0.95); }
|
|
.ep-btn svg { width: 24px; height: 24px; fill: currentColor; }
|
|
.ep-vol { display: flex; align-items: center; gap: 8px; width: 120px; margin: 0 auto; }
|
|
.ep-vol svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; opacity: 0.6; flex-shrink: 0; }
|
|
.ep-vol input[type="range"] { flex: 1; height: 4px; -webkit-appearance: none; appearance: none; background: rgba(255,255,255,0.2); border-radius: 2px; outline: none; }
|
|
.ep-vol input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 14px; height: 14px; border-radius: 50%; background: {{ $settings['primaryColor'] }}; cursor: pointer; }
|
|
.ep-vol input[type="range"]::-moz-range-thumb { width: 14px; height: 14px; border-radius: 50%; background: {{ $settings['primaryColor'] }}; cursor: pointer; border: none; }
|
|
.ep-dj { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 8px; font-size: 12px; opacity: 0.8; }
|
|
.ep-dj img { width: 28px; height: 28px; border-radius: 50%; object-fit: cover; }
|
|
.ep-offline { text-align: center; padding: 32px; opacity: 0.6; font-size: 14px; }
|
|
body.light .ep-vol input[type="range"] { background: rgba(0,0,0,0.15); }
|
|
</style>
|
|
</head>
|
|
<body class="{{ $settings['theme'] === 'light' ? 'light' : '' }}">
|
|
<div class="ep" x-data="radioEmbed()" x-init="init()">
|
|
<div x-show="!enabled" class="ep-offline">Radio is offline</div>
|
|
|
|
<div x-show="enabled">
|
|
<div class="ep-hdr">
|
|
<div class="ep-brand">
|
|
<span class="dot"></span>
|
|
<span>RADIO</span>
|
|
</div>
|
|
<div class="ep-listeners" x-show="listenerCount !== null">
|
|
<span x-text="listenerCount"></span> listeners
|
|
</div>
|
|
</div>
|
|
|
|
<div class="ep-np">
|
|
<div class="ep-track" x-text="trackTitle">Radio</div>
|
|
<div class="ep-artist" x-show="trackArtist" x-text="trackArtist"></div>
|
|
</div>
|
|
|
|
<div class="ep-ctrls">
|
|
<button class="ep-btn" @click="togglePlay()" x-show="!isPlaying">
|
|
<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
|
|
</button>
|
|
<button class="ep-btn" @click="togglePlay()" x-show="isPlaying">
|
|
<svg viewBox="0 0 24 24"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="ep-vol">
|
|
<svg viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.536 8.464a5 5 0 010 7.072M18.364 5.636a9 9 0 010 12.728"/></svg>
|
|
<input type="range" x-model="volume" min="0" max="100" @input="updateVolume()">
|
|
</div>
|
|
|
|
<div class="ep-dj" x-show="djName">
|
|
<img x-show="djAvatar" x-bind:src="djAvatar" x-bind:alt="djName">
|
|
<span x-text="djName"></span>
|
|
</div>
|
|
|
|
<audio x-ref="audio" preload="none">
|
|
<source x-bind:src="streamUrl" type="audio/mpeg">
|
|
</audio>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function radioEmbed() {
|
|
return {
|
|
enabled: false,
|
|
streamUrl: '{{ $settings['streamUrl'] }}',
|
|
isPlaying: {{ $settings['autoPlay'] ? 'true' : 'false' }},
|
|
volume: 80,
|
|
trackTitle: 'Radio',
|
|
trackArtist: '',
|
|
listenerCount: null,
|
|
djName: '',
|
|
djAvatar: '',
|
|
sseSource: null,
|
|
|
|
init() {
|
|
this.loadConfig();
|
|
this.connectSse();
|
|
},
|
|
|
|
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;
|
|
if (data.look && !data.is_auto_dj) {
|
|
this.djAvatar = 'https://www.habbo.nl/habbo-imaging/avatarimage?figure=' + data.look + '&size=m';
|
|
} else {
|
|
this.djAvatar = '';
|
|
}
|
|
}
|
|
} catch (err) {}
|
|
});
|
|
|
|
this.sseSource.onerror = () => {
|
|
this.sseSource.close();
|
|
setTimeout(() => this.connectSse(), 5000);
|
|
};
|
|
},
|
|
|
|
loadConfig() {
|
|
fetch('/api/radio/config')
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.enabled && data.stream_url) {
|
|
this.enabled = true;
|
|
this.streamUrl = data.stream_url;
|
|
if ({{ $settings['autoPlay'] ? 'true' : 'false' }}) {
|
|
setTimeout(() => this.togglePlay(), 500);
|
|
}
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
},
|
|
|
|
togglePlay() {
|
|
const audio = this.$refs.audio;
|
|
if (this.isPlaying) {
|
|
audio.pause();
|
|
this.isPlaying = false;
|
|
} else {
|
|
audio.play().catch(() => {});
|
|
this.isPlaying = true;
|
|
}
|
|
},
|
|
|
|
updateVolume() {
|
|
const audio = this.$refs.audio;
|
|
if (audio) audio.volume = this.volume / 100;
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|