Open HTTP streams in minimal standalone player tab
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>
This commit is contained in:
parent
83304c197d
commit
85776390f6
4 changed files with 184 additions and 19 deletions
|
|
@ -18,4 +18,5 @@ urlpatterns = [
|
|||
path('radio/notes/<int:pk>/', views.save_station_notes, name='save_station_notes'),
|
||||
path('radio/focus/record/', views.record_focus_session, name='record_focus_session'),
|
||||
path('radio/focus/stats/', views.focus_stats, name='focus_stats'),
|
||||
path('radio/stream-player/', views.stream_player, name='stream_player'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import json
|
||||
import socket
|
||||
import ssl as ssl_module
|
||||
import time
|
||||
import urllib.parse
|
||||
from datetime import datetime
|
||||
|
|
@ -573,3 +575,18 @@ def import_m3u(request):
|
|||
skipped += 1
|
||||
|
||||
return JsonResponse({'ok': True, 'added': added, 'skipped': skipped})
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Minimal HTTP stream player (standalone tab for mixed-content streams)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def stream_player(request):
|
||||
url = request.GET.get('url', '').strip()
|
||||
name = request.GET.get('name', '').strip()
|
||||
vol = request.GET.get('vol', '204').strip()
|
||||
try:
|
||||
vol = max(0, min(255, int(vol)))
|
||||
except ValueError:
|
||||
vol = 204
|
||||
return render(request, 'radio/stream_player.html', {'stream_url': url, 'stream_name': name, 'stream_vol': vol})
|
||||
|
|
|
|||
|
|
@ -67,28 +67,18 @@ function escapeHtml(str) {
|
|||
function playStation(url, name, stationId) {
|
||||
stopPlayback(false);
|
||||
|
||||
// HTTP stream on HTTPS page → open minimal player in new tab, keep main window as home base
|
||||
if (location.protocol === 'https:' && url.startsWith('http://')) {
|
||||
const vol = localStorage.getItem('diora_volume') || '204';
|
||||
const params = new URLSearchParams({ url, name, vol });
|
||||
window.open('/radio/stream-player/?' + params, '_blank');
|
||||
return;
|
||||
}
|
||||
|
||||
currentStation = { url, name, id: stationId || null };
|
||||
isPlaying = true;
|
||||
|
||||
// If page is HTTPS and stream is HTTP, try upgrading to HTTPS first.
|
||||
// Browsers block mixed content (HTTP media on HTTPS pages).
|
||||
const playUrl = (location.protocol === 'https:' && url.startsWith('http://'))
|
||||
? url.replace('http://', 'https://')
|
||||
: url;
|
||||
|
||||
audio.onerror = () => {
|
||||
const wasUpgraded = playUrl !== url;
|
||||
if (wasUpgraded) {
|
||||
$('now-playing-track').textContent = 'Stream nicht erreichbar (HTTP-only, kein HTTPS-Fallback)';
|
||||
} else {
|
||||
$('now-playing-track').textContent = 'Stream konnte nicht geladen werden';
|
||||
}
|
||||
isPlaying = false;
|
||||
$('play-stop-btn').textContent = '▶ Play';
|
||||
$('play-stop-btn').classList.remove('playing');
|
||||
};
|
||||
|
||||
audio.src = playUrl;
|
||||
audio.src = url;
|
||||
const volSlider = document.getElementById('volume');
|
||||
if (volSlider) audio.volume = volSlider.value / 255;
|
||||
audio.play().catch(() => {
|
||||
|
|
|
|||
157
templates/radio/stream_player.html
Normal file
157
templates/radio/stream_player.html
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
<!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>
|
||||
Loading…
Add table
Reference in a new issue