Instead of trying a HTTPS upgrade (which fails for IP-based streams): - playStation() detects http:// URL on https:// page, opens /radio/stream-player/ with url, name, vol as query params, then returns — main stream is already stopped by the stopPlayback(false) call at the top of playStation() - New view stream_player renders a standalone minimal player page - Template: auto-plays on load, correct volume from URL param, volume changes synced back to localStorage so main window picks it up next time, live track metadata via SSE, tab title updates on track change, close-tab button Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
157 lines
4.2 KiB
HTML
157 lines
4.2 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{{ stream_name|default:"Radio" }}</title>
|
|
<style>
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body {
|
|
font-family: system-ui, sans-serif;
|
|
background: #0d0d0d;
|
|
color: #e0e0e0;
|
|
min-height: 100svh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 20px;
|
|
padding: 24px;
|
|
}
|
|
#station-name {
|
|
font-size: 1.3rem;
|
|
font-weight: 700;
|
|
text-align: center;
|
|
}
|
|
#track-name {
|
|
font-size: 0.9rem;
|
|
color: #888;
|
|
text-align: center;
|
|
min-height: 1.2em;
|
|
}
|
|
.controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
#play-btn {
|
|
background: #e63946;
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 6px;
|
|
padding: 8px 20px;
|
|
font-size: 1rem;
|
|
cursor: pointer;
|
|
min-width: 90px;
|
|
}
|
|
#play-btn:hover { background: #c1121f; }
|
|
.vol-wrap {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-size: 0.85rem;
|
|
color: #888;
|
|
}
|
|
#vol-slider { width: 90px; accent-color: #e63946; }
|
|
#vol-num {
|
|
width: 42px;
|
|
background: #1a1a1a;
|
|
border: 1px solid #333;
|
|
color: #e0e0e0;
|
|
border-radius: 4px;
|
|
padding: 2px 4px;
|
|
font-size: 0.85rem;
|
|
text-align: center;
|
|
}
|
|
#back-btn {
|
|
background: none;
|
|
border: none;
|
|
color: #555;
|
|
font-size: 0.8rem;
|
|
cursor: pointer;
|
|
text-decoration: underline;
|
|
margin-top: 8px;
|
|
}
|
|
#back-btn:hover { color: #aaa; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="station-name">{{ stream_name|default:"Radio" }}</div>
|
|
<div id="track-name"></div>
|
|
|
|
<div class="controls">
|
|
<button id="play-btn">▶ Play</button>
|
|
<div class="vol-wrap">
|
|
<span>vol</span>
|
|
<input type="range" id="vol-slider" min="0" max="255" value="{{ stream_vol }}">
|
|
<input type="number" id="vol-num" min="0" max="255" value="{{ stream_vol }}">
|
|
</div>
|
|
</div>
|
|
|
|
<button id="back-btn" onclick="window.close()">← close tab</button>
|
|
|
|
<script>
|
|
const audio = new Audio();
|
|
let playing = false;
|
|
let sse = null;
|
|
|
|
const streamUrl = '{{ stream_url|escapejs }}';
|
|
const stationName = '{{ stream_name|escapejs }}';
|
|
|
|
// Volume
|
|
function setVol(v) {
|
|
v = Math.max(0, Math.min(255, Math.round(v)));
|
|
audio.volume = v / 255;
|
|
document.getElementById('vol-slider').value = v;
|
|
document.getElementById('vol-num').value = v;
|
|
try { localStorage.setItem('diora_volume', v); } catch (_) {}
|
|
}
|
|
|
|
const slider = document.getElementById('vol-slider');
|
|
const numIn = document.getElementById('vol-num');
|
|
slider.addEventListener('input', () => setVol(parseInt(slider.value, 10)));
|
|
numIn.addEventListener('change', () => setVol(parseInt(numIn.value, 10)));
|
|
|
|
// Play / Stop
|
|
const playBtn = document.getElementById('play-btn');
|
|
|
|
function startPlay() {
|
|
audio.src = streamUrl;
|
|
setVol(parseInt(slider.value, 10));
|
|
audio.play().catch(() => {});
|
|
playing = true;
|
|
playBtn.innerHTML = '▮▮ Stop';
|
|
|
|
// SSE metadata
|
|
if (sse) sse.close();
|
|
sse = new EventSource('/radio/sse/?url=' + encodeURIComponent(streamUrl));
|
|
sse.onmessage = e => {
|
|
try {
|
|
const data = JSON.parse(e.data);
|
|
if (data.track) {
|
|
document.getElementById('track-name').textContent = data.track;
|
|
document.title = data.track + ' — ' + stationName;
|
|
}
|
|
} catch (_) {}
|
|
};
|
|
}
|
|
|
|
function stopPlay() {
|
|
audio.pause();
|
|
audio.src = '';
|
|
playing = false;
|
|
playBtn.innerHTML = '▶ Play';
|
|
document.getElementById('track-name').textContent = '';
|
|
document.title = stationName;
|
|
if (sse) { sse.close(); sse = null; }
|
|
}
|
|
|
|
playBtn.addEventListener('click', () => {
|
|
if (playing) stopPlay(); else startPlay();
|
|
});
|
|
|
|
// Auto-play on load
|
|
document.addEventListener('DOMContentLoaded', startPlay);
|
|
</script>
|
|
</body>
|
|
</html>
|