Skip to content

v2.2.0

Choose a tag to compare

@github-actions github-actions released this 07 May 11:48
· 94 commits to main since this release
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 "…".