Add a transparent stream proxy for creamfresh radio
All checks were successful
Build and push Docker image / build (push) Successful in 19s
Test / test (push) Successful in 1m34s

A plain <audio src="https://user:pass@host/..."> doesn't get its
embedded Basic Auth credentials honoured consistently across
browsers (the user hit a native auth prompt on play) -- this instead
holds the credentials server-side and relays the request. Forwards
the client's own Icy-MetaData header and the upstream's icy-* response
headers unchanged, so the existing icy.py-based metadata SSE keeps
working when pointed at this URL instead of the direct one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Drq8o4HfFmwkXz2AgvaQ4p
This commit is contained in:
marwin 2026-09-04 11:29:52 +02:00
parent 0fd5461daa
commit 9228906db8
2 changed files with 42 additions and 0 deletions

View file

@ -19,4 +19,5 @@ urlpatterns = [
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'),
path('radio/creamfresh-stream/', views.creamfresh_stream, name='creamfresh_stream'),
]

View file

@ -581,6 +581,47 @@ def import_m3u(request):
# Minimal HTTP stream player (standalone tab for mixed-content streams)
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# creamfresh radio proxy
# ---------------------------------------------------------------------------
# Held server-side only -- never exposed to the browser this way. The
# upstream is protected by HTTP Basic Auth; a plain <audio src="user:pass@..">
# doesn't get credentials honoured consistently across browsers, so this
# transparently relays the request instead (including the client's own
# Icy-MetaData header, so the existing icy.py-based metadata SSE keeps
# working unmodified when pointed at this URL instead of the direct one).
CREAMFRESH_STREAM_URL = 'https://radio.creamfresh.xyz/stream.mp3'
CREAMFRESH_AUTH = ('player', 'MJMr58p83zAU5zZaDGU0_BIn')
def creamfresh_stream(request):
headers = {}
if request.META.get('HTTP_ICY_METADATA'):
headers['Icy-MetaData'] = request.META['HTTP_ICY_METADATA']
try:
upstream = requests.get(
CREAMFRESH_STREAM_URL,
auth=CREAMFRESH_AUTH,
headers=headers,
stream=True,
timeout=15,
)
except requests.RequestException:
return HttpResponse(status=502)
response = StreamingHttpResponse(
upstream.iter_content(chunk_size=4096),
content_type=upstream.headers.get('Content-Type', 'audio/mpeg'),
status=upstream.status_code,
)
for h in ('icy-metaint', 'icy-name', 'icy-genre', 'icy-br', 'icy-description', 'icy-url'):
if h in upstream.headers:
response[h] = upstream.headers[h]
response['Cache-Control'] = 'no-cache'
return response
def stream_player(request):
url = request.GET.get('url', '').strip()
name = request.GET.get('name', '').strip()