Relays to the DJ's own /dj/feedback via the same server-side-auth
proxy pattern as the stream itself, so the browser never needs the
station's credentials. Buttons only show when the currently playing
station is creamfresh radio (matched by URL against a hardcoded
constant) -- no other station's backend does anything with a vote,
so no other station gets the buttons yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Drq8o4HfFmwkXz2AgvaQ4p
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
- HIGHLIGHTS_MAX_BYTES, BOOKMARKS_MAX_BYTES: books size limits now in settings
- PODCAST_INBOX_PAGE_SIZE: shared between podcasts/views.py and app.js via DIORA_CONFIG
- VOLUME_DEFAULT: radio stream player default volume
- ITUNES_TIMEOUT: unified iTunes API timeout (was 5s vs 6s inconsistency)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Python booleans (True/False) in saved_stations (is_favorite field) and
history (scrobbled field) were being rendered literally into JS via
|safe, causing 'True is not defined' ReferenceError that broke all JS
including book loading.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>