2026.06.07
[2026.06.07]
Added
- CMAF (HLS + DASH) PCM sink — new
rockbox-cmafcrate encodes PCM to AAC-LC (fdk-aac) and serves HLS + DASH manifests with fMP4 segments over HTTP; enabled viaaudio_output = "cmaf"(or"hls"/"dash") pluscmaf_http_port,cmaf_bitrate, and optionalcmaf_segment_dirfor mirroring artefacts to disk for an external origin (nginx, Caddy, CDN); registered asPCM_SINK_CMAF = 8infirmware/pcm.c; surfaced as a virtual device selectable via/connect/cmafwith broadcast icons in the GPUI, Expo, web, and macOS device pickers - Standalone HLS/DASH player — new
crates/hlsdecodes.m3u8/.mpdURLs and pushes PCM straight into the active sink via newpcm_external_write/pcm_external_set_freqfirmware hooks (no pcmbuf, no codec dispatcher) so the same audio-output graph (cpal, AirPlay, Snapcast, CMAF, …) reroutes a Rockbox-internal HLS broadcast to any sink the user picks;PlayTrack/ pause / resume / next / previous / seek /hardStopincrates/rpcdetect an active HLS session and dispatch locally or forward to the broadcaster over gRPC so peers stay in sync - Web UI:
HlsAutoConnectattaches an<audio>element to/hls/master.m3u8whenever the active device type iscmaf/hls/dash, andHlsVolumeControladds a local browser volume slider; Docker defaultaudio_outputflipped tocmafand port7882exposed; new Mintlify page documents the sink; GraphQLglobalSettings.cmafHttpPortadded
Fixed
- CMAF sink: encoder now bootstraps with a full
SEGMENT_WINDOWof silence sohls.js/dash.jsdon't fatal on a fresh manifest; a dedicated silence-pacer thread keeps the manifest live between tracks without ever mixing into real-audio chunks;pcm_cmaf_start()is now called eagerly fromload_settings/ device connect so the HTTP endpoint binds before the first track plays - Android HTTP streaming smoothness —
cpal_threadpriority boosted (setpriority(PRIO_PROCESS, tid, -19)) andNowPlayingServicenow acquiresPARTIAL_WAKE_LOCK+WifiLockwhile the daemon is running, eliminating the doze-induced stutters on Wi-Fi remote streams netstream:rb_net_lenandrb_net_content_typenow wait foropen_donebefore reading stream state, so callers see the real length / MIME instead of-1/ empty when the HTTP open is still in flightnetstream: removed TCP keepalive from the globalreqwestclient — keepalive probes were tripping middleware and aborting long Range reads on some CDNsnetstream: non-blockingrb_net_open()returns a handle immediately while the connect happens in a worker; combined with TCP keepalive on the per-stream client (kept) and an EOF probe for servers that omitContent-Length, this unblocks both the audio thread and the UI on slow first-byte serversnetstream: detect and reconnect on premature TCP EOF mid-stream — the prefetch thread now restarts the underlying request from the last known offset instead of declaring the stream dead, fixing mid-track cutoffs on lossy mobile connectionsnetstream: seekRangerequests now retry on transient errors; huge forward skips on servers that ignoreRangefast-fail instead of redownloading the whole prefixnetstream: 30 s hard timeout removed fromread_into— the prefetch thread's own retry budget now governs how long a read can wait, so a brief stall no longer kills the streampcm-cpal: DMA thread exits immediately on stream error instead of drainingpcmbuf, so the next track / device switch can re-arm the sink without waiting for a stale flushcpalsink: recover from stream errors and break the push deadlock — error callback now signals the writer sopcm_cpal_pushreturns instead of spinning on a dead stream- Android: larger prefetch buffer (16 MB) + more retries make HTTP streams resilient to Wi-Fi / cellular handoffs
- Navidrome HTTP track artwork — stream URL is now propagated as the track
pathand the bridge derives the cover-art URL from it, so artwork appears in the miniplayer, full-screen player, and queue without an extra round-trip
Changed
- Default Docker
audio_outputflipped tocmaf; port 7882 added to the exposed ports list so HLS / DASH playback works out of the box from a container
Fixed
- HTTP streaming: removed reqwest total-request timeout (only
connect_timeout15 s remains) — the previous 30 s deadline killed large remote files mid-stream;read_as_file()reverted to a retry-loop that fills the full requested buffer - Buffering interleaving:
fill_buffer()now passesBUFFERING_DEFAULT_FILECHUNKinstead of0when a second handle has remaining data, so next-track pre-buffering round-robins with the current track instead of monopolising the buffering thread and starving the ring buffer - HTTP pre-buffering cutting current-track playback:
buffer_handle()caps HTTP handles to oneBUFFERING_DEFAULT_FILECHUNKper call;streamfd.creplaces per-chunkfprintf(stderr, …)withlogf()(compiled out in production) to eliminate hundreds of blockingwrite(2)syscalls per track - Expo: Navidrome cover art now appears in the miniplayer, full-screen player, and queue when playing ND HTTP streams —
coverArtUrlFromStreamUrl()added tonavidrome-client.tsreconstructs agetCoverArtURL from theid,u,t,sparameters embedded in the stream URL; used as a fallback intrackFromProtowhenalbum_artis empty
Full Changelog: 2026.05.28...2026.06.07