You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Features
--------
- feat(transcode): auto-spawn MP3 mounts on connect (config.AutoTranscodeMP3Bitrates) so Safari/iOS listeners always get a playable sibling for Ogg/Opus sources. Player auto-falls-back via pickAudioSource probe.
- feat(metadata): mirror source metadata into transcoded outputs and serve canonical ICY response headers (icy-name, icy-genre, icy-url, icy-description, icy-br, icy-pub) on listener requests so mpv/mplayer and similar Icecast clients display artist + title + station info.
- feat(server): per-mount NameOverride in MountSettings — set a name_override on an advanced_mounts entry to rebrand a station server-side without touching the encoder.
- feat(server): SubscribeInternal on relay.Stream + an internal-listener set; transcoders subscribe via this variant so they stop padding the dashboard listener count, listener_histories rows, and the YP "listeners" field. Both ListenersCount() and StreamStats.ListenersCount subtract internal subscribers.
- feat(server): YP reports now include normaliseYPGenre — multi-word phrases like "drum and bass" are hyphenated so dir.xiph.org keeps them as one tag instead of three.
- feat(dashboard): real per-mount listeners-over-time SVG chart on the admin dashboard, sourced from the listener_histories table via /admin/insights?hours=N (1H / 24H / 7D). Server-side downsamples to ~200 points per mount so the 7-day view stays small. No new JS dependency.
Fixes
-----
- fix(yp): send Icecast2-canonical YP fields — `st` for the current stream title (we were sending `title`, which dir.xiph.org ignores), `listeners` for the live count (was missing entirely), and `bitrate` alongside the existing `cps`. Legacy aliases retained.
- fix(yp): capture SID returned by the directory server, accept 201 Created as success (was treated as failure), use the real ContentType when reporting.
- fix(player): create AudioContext before calling resumeAudio() — handlePlay used to resume before connectAudio created the context, wasting the user-gesture window and leaving audio routed through a still-suspended graph (silent until a second click). Reordered to connect → resume → play.
- fix(player): await resumeAudio() so the AudioContext finishes resuming before el.play(); abort + cancel the pickAudioSource probe so each candidate stops streaming after headers; replace `truncate` on the hero title with `break-words` so long song titles wrap instead of clipping to "…".