Releases: tsirysndr/rockboxd
Releases · tsirysndr/rockboxd
2026.06.26
[2026.06.26]
Added
library: periodic library rescan + delete reconciliation as a backstop for filesystem watcher events that get dropped silently — Linux inotify is a no-op on NFS/SMB/FUSE mounts, and the kqueue backend on BSDs (NetBSD) coalesces multi-file drops into a singleVnode::Writeevent and only surfaces one new file per coalesce;start_watchernow spawns a background tokio task that ticks everyROCKBOX_RESCAN_INTERVAL_SECSseconds (default120, set0to disable), runsscan_audio_filesthen the newaudio_scan::reconcile_deletionswhich walksrepo::track::all(already filtersis_remote = 0) and callsrepo::track::delete_by_pathfor any track whose path no longer exists on disk; overlapping ticks are skipped viatokio::sync::Mutex::try_lockso the periodic pass can never pile up behind a still-running initial scan on a large libraryci: Debian and Fedora package builds, published to GitHub Releases and Gemfury — new.github/workflows/linux-x86_64-build.ymlbuildsrockbox,rockboxd,librockboxd.a, androckbox-gpui(viagpui/package.sh, hard-fails if the Linux GPUI build breaks), downloadstypesense-serverv30.1 fromdl.typesense.org, and packages a.deband.rpmthat ship all four binaries plus therockbox-gpuidesktop entry and PNG icon; the existinglinux-aarch64-build.ymlis extended with a.debcontainingrockbox+rockboxd+typesense-server(no GPUI);linux-armhf-build.ymlgains a.debwith justrockboxd; each workflow checks forFURY_TOKENandFURY_ACCOUNTsecrets and pushes its own packages topush.fury.io, mirroring the smolsonic release pipeline
Full Changelog: 2026.06.25...2026.06.26
2026.06.25
[2026.06.25]
Added
- Embedded S3 admin web UI — new React + Vite SPA under
crates/s3/s3webui/(TanStack Router/Query + Jotai + FlyonUI/Tailwind) embedded intorockbox-s3viarust-embedand served at/admin/on the S3 port; the dashboard signs requests with SigV4 directly via AWS SDK v3 in the browser, login validates against the configureds3_access_key/s3_secret_key, and the standard buckets / objects / upload / settings views are wired in;HEAD /{bucket}is now implemented soHeadBucketsucceeds; theDockerfilegains an in-container Bun builder stage and every CI workflow that compilesrockbox-s3runs the SPA build first so the binary always ships with an up-to-date UI
Changed
- Renamed the user-facing product from "Rockbox Zig" to "Rockbox Daemon" and updated every reference to the GitHub repo URL, AUR package (
rockbox-zig-bin→rockboxd-bin), macOS pkg identifier, ElectronappId, and Gleam SDK metadata; the published npm scope@rockbox-zig/sdkand therockboxzig.mintlify.appdocs URL are intentionally unchanged
Fixed
ci(gpui): force relink oflibrockboxd.ain therelease-gpuiworkflow — the cache key only hashesCargo.lock, so edits tocrates/embedsources could leave a stale archive missing newer exports (e.g.rb_daemon_start), failing the gpui link step
Full Changelog: 2026.06.23...2026.06.25
2026.06.23
[2026.06.23]
Added
- S3-compatible HTTP API — new
crates/s3actix-web server listens ons3_port(default9000) and exposesmusic_diras a single fixed bucket (music, regionus-east-1); supportsPutObject,DeleteObject,GetObject,HeadObject,ListObjectsV2, andListBucketswith AWS Signature V4 header-form authentication; the news3_enabled,s3_host,s3_port,s3_access_key,s3_secret_keykeys insettings.tomlgate startup (server stays off when disabled or credentials are empty); per-PUT cap 2 GiB, uploads restricted to the same audio-extension allowlist as the library scanner;STREAMING-AWS4-HMAC-SHA256-PAYLOADis not supported (clients should useUNSIGNED-PAYLOADor sign the full body — awscli v2.23+ needsAWS_REQUEST_CHECKSUM_CALCULATION=when_requiredto opt out of the new default-on chunked signing); README, CLAUDE.md and the mintlify reference document client recipes for awscli, rclone, and MinIO Client library: filesystem watcher (crates/library/src/watcher.rs) — recursivenotify-based watch onmusic_dirkeeps the SQLite tag database in sync with on-disk changes;Createand data-modify events callsave_audio_metadata,Removeevents call the newrepo::track::delete_by_path(cascades throughalbum_tracks,artist_tracks,playlist_tracks,favourites), and rename events split into add/remove pairs; non-audio files are ignored via the same 18-extension allowlist the scanner uses; the watcher boots after the initial scan incrates/cli/src/lib.rs::run_indexing()and is also the sync engine behind the new S3 API — no separate DB code path
Fixed
build: linkCoreServices.frameworkon macOS so thenotifycrate's FSEvents backend resolves at link time — macOS-Intel CI was failing the Zig link step with undefined symbols_FSEventStream{Create,Invalidate,Release,ScheduleWithRunLoop,Start,Stop}; added to both therockboxdexecutable and the embeddablelibrockboxd.alink blocks inzig/build.zig, and to the embedder-facing framework list in CLAUDE.md
Full Changelog: 2026.06.18...2026.06.23
2026.06.18
[2026.06.18]
Fixed
server: hard-exit whenrun_http_server()returnsErr— the spawning thread used to log and exit silently, leaving:6063dead while the rest of rockboxd kept running and surfaced only as downstream GraphQL request errorsplayback: skip HTTP look-ahead of the currently-playing URL —REPEAT_ALLon a single-track playlist wrappedplaylist_peek(+N)back to the same URL every look-ahead cycle, each iteration spawning a fresh TCP+TLS+GET and a 4 MB prefetch; treating same-URL HTTP look-ahead as end-of-playlist lets natural-end re-load the next URL fresh without thrashing the buffering thread (local files unaffected — they share the page cache)playback: throttle HTTP look-ahead while the current track is still streaming — parallel HTTP bufopens shared the buffering-thread filechunk round-robin and starved the active codec until "no more PCM data" fired; deferred until the active stream finishespcm-cmaf: poll every 10 ms for up to 5 s for the next PCM chunk before bailing — the sink used to give up the instantpcm_play_dma_complete_callbackreturnedfalse, killing playback within ~23 ms of every track start whenever the codec hadn't queued the next chunk yetlibrary: replace.expect()panics onProbe::open()in thealbum_art,copyright_message, andlabelextractors with logged errors that returnOk(None), so an unreadable track no longer aborts the library scan
Full Changelog: 2026.06.15...2026.06.18
2026.06.15
[2026.06.15]
Added
rockboxd login <handle>— OAuth login to Rocksky using your Bluesky handle; opens the authorisation URL in the default browser and spins up a minimal tokio TCP server on port 6996 to receive the callback token, which is persisted to~/.config/rockbox.org/tokenrockboxd whoami— print the currently logged-in Rocksky user (reads the stored token and resolves the handle via the Rocksky API)rockboxd settings pull [--did <DID_OR_HANDLE>]— fetch audio settings (equalizer, crossfade, replaygain, tone/bass/treble/balance/channels) from Rocksky and merge them into~/.config/rockbox.org/settings.tomlwithout touching any other fields;--didenables public access — any user's settings can be pulled without a token by passing their DID or handlerockboxd settings push— read the currentsettings.tomland upload the audio sections (equalizer, crossfade, replaygain, tone) to Rocksky viaapp.rocksky.rockbox.putAudioSettings; requires a valid stored token- All four subcommands exit the process immediately after completing — the Zig firmware and gRPC/HTTP servers are never started, avoiding the C-global-initialisation segfault that would occur before
main_c()runs
Full Changelog: 2026.06.14...2026.06.15
2026.06.14
[2026.06.14]
Added
arm-unknown-linux-gnueabihfcross-compilation target — newscripts/build-armhf.shbuilds a native ARMv6 hard-floatrockboxdbinary (e.g. Raspberry Pi Zero) using theDockerfile.arm-unknown-linux-gnueabihfcross-toolchain; Zig links with-Dtarget=arm-linux-gnueabihf -Dcpu=arm1176jzf_s;Cross.tomlwirescross buildto the same Docker image; firmware configure target208(ARMHFHOST) reuses the headless target files witharm-linux-gnueabihf-gccand-march=armv6 -marm -mfpu=vfp -mfloat-abi=hardcrates/alsa-sink— direct libasound PCM sink for ARM Linux; usessnd_pcm_writei(RWInterleaved, same asaplay), avoiding cpal's ALSA backend and thesnd_pcm_status_get_htstampnull-PLT-entry crash on older ARM devices; ALSA is opened once inpcm_alsa_postinit()and the writer thread lives for the daemon lifetime so resume after a pcmbuf-dry stall is instant (no re-open latency); enabled via--features fts5,alsa-sinkin the ARM build; registered asPCM_SINK_ALSA = 9infirmware/export/pcm_sink.hfirmware/target/hosted/headless/pcm-alsa.c— C PCM sink ops mirroringpcm-cpal.cbut callingpcm_alsa_*entry points.github/workflows/linux-armhf-build.yml— CI workflow that builds and uploads the armhf binary to GitHub Releases
Fixed
- ARM Linux:
SIGILLfrom__ARMv7ABSLongThunk__— Ubuntu'sarm-linux-gnueabihf-gccdefaults to-march=armv7-a; added-march=armv6 -marmto the configurearmhfhostcc()function so all C objects are tagged ARMv6; Zig's LLD then uses ARMv6-compatible thunks that work on ARM1176JZF-S - ARM Linux:
SIGILLat startup — LLD derivesHasMovtfrom the target triple (arm-linux-gnueabihf= conventional ARMv7), generatingmovw/movtthunks even when object attributes say ARMv6; fixed by usingReleaseFastto produce a compact binary (< 32 MB) that fits within LLD's direct-branch range, eliminating the need for long-range veneers - ARM Linux:
SIGSEGVinSimpleBroker::subscribe(dyn Anyvtable null) — Zig's LLD generates zero vtable entries fordyn Any + SendCOMDAT groups on ARM 32-bit inReleaseFastmode; replacedHashMap<TypeId, Box<dyn Any + Send>>incrates/graphql/src/simplebroker.rswith a type-erasedErasedSendersstruct storing the drop function as a heap pointer written at runtime (not a link-time vtable), so every function pointer is a valid non-zero Thumb address - ARM Linux:
SIGSEGVinalsa::pcm::Status::get_htstamp— on ARM devices where libasound shipssnd_pcm_status_get_htstampas a static inline (not an exported symbol) the PLT entry resolves to 0x00000000 at runtime, crashing in cpal's ALSA timing probe; fixed by replacing cpal with the directalsa-sinkthat never calls this function - ARM Linux: 32-bit ABI mismatches in
crates/sys—c_long/c_ulongare 32-bit on ARM (not 64-bit); addedas c_long/as c_ulongcasts inmetadata.rs,playback.rs,playlist.rs,sound/dsp.rs,system.rs,tagcache.rs, andas u64/as i64field casts intypes/mp3_entry.rs;crates/cli/src/lib.rsnow useslibc::rlim_tinstead ofu64forrlimitfields - ARM Linux:
audiohw_set_volumeundefined reference topcm_cpal_set_volume— gated the cpal volume call on!ARMHFHOSTinaudiohw-noop.c; volume is handled by Rockbox's DSP layer (HAVE_SW_TONE_CONTROLS) on ARM - ARM Linux:
PCM_SINK_ALSA = 9array-bounds error inpcm.c— enum entry was declared beforePCM_SINK_CMAF = 8, makingPCM_SINK_NUM = 9andsinks[9]out-of-bounds; moved ALSA entry after CMAF soPCM_SINK_NUM = 10 firmware/export/config.h: added#elif defined(ARMHFHOST)→#include "config/armhfhost.h"so the ARM hosted build is recognised as a valid platform; addedARMHFHOSTguard toaudiohw.h(sdl_codec.h inclusion) andfilesystem-app.c(rbhomepointer declaration)metadata:probe_content_type_formatnow logs the exactContent-Typestring received (or reports thatstream_content_typereturned < 0) to stderr, making HTTP format-detection failures visible; addedaudio/x-aacandaudio/vnd.dlna.adtsto the AAC-BSF MIME mapping
Full Changelog: 2026.06.07...2026.06.14
2026.06.08
[2026.06.08]
Fixed
- GraphQL
playAlbum/playArtistTracks/playGenreTracks/playDirectory/playTrack/playLikedTracks/playAllTrackswere no-ops when the active output was the CMAF (HLS / DASH) sink — thecheck_and_load_player!macro used ahost != "" && port != 0heuristic to detect external cast players, but CMAF advertiseshost="localhost",port=7882for its own HTTP server, so the macro misrouted the request to/player/load(which 404s becausestate.playeris only populated for Chromecast) and returnedOk(0)before building the playlist; now matches the RPC variant and gates onis_cast_deviceinstead, so local PCM sinks (CMAF, FIFO, builtin, squeezelite, AirPlay, UPnP) fall through to the regular playlist-build path - MPD
restore_playlist: bounds-check the persistedresume_indexagainst the current playlist length before indexing — a stale resume index from a prior session with a longer queue was panicking the MPD thread withindex out of bounds: the len is 15 but the index is 91and aborting the daemon
Full Changelog: 2026.06.07...2026.06.08
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
2026.05.28
[2026.05.28]
Added
netstream: background prefetch thread per HTTP stream — a dedicated thread fills a 2 MBVecDequebuffer from the network sorb_net_readdrains in sub-µs without ever blocking on TCP; forward seeks within the prefetch window consume buffered bytes without issuing a newRangerequest; backward or large (>2 MB) forward seeks issue aRangerequest and replace the reader thread atomically
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.27...2026.05.28
2026.05.27
[2026.05.27]
Added
- Navidrome / Subsonic support in the macOS Swift app —
NavidromeService(Subsonic API client with MD5 token auth),NavidromeManager(multi-server persistence, active server switching, optimistic star toggling, cover art derivation from stream URLs),NdResponseCache(stale-while-revalidate actor cache, 30 min fresh TTL, 24 h eviction),NdLibraryView(Albums / Artists / Songs / Liked / Playlists sections with infinite-scroll pagination),NdSongRowView(track art toggle, hover play, star button, Play Next/Last + Go to Album/Artist context menu),NdAlbumDetailView,NdArtistDetailView,NdPlaylistDetailView(Play / Shuffle), and search integration (when a Navidrome server is active,search3replaces local gRPC search with ND artist circles, album cards, and song rows)
Removed
- PCM volume normalizer (
pcm_normalizer.c,pcm_normalizer.h, Rust bindings, settings field, docs) — superseded by ReplayGain perceived-loudness normalisation
Fixed
- Expo:
AbortSignal.timeout()replaced withAbortController+setTimeoutinnavidrome-client.ts—AbortSignal.timeoutis absent in some Hermes / React Native versions and was silently swallowing timeouts, making every fetch returnnull; switched tomd5npm package (removed inline implementation); setNSAllowsArbitraryLoads=truein iOSinfoPlistto unblock HTTPS servers that do not meet strict ATS TLS requirements - Expo ND album detail: mirror local
album/[id].tsxhero layout — blurred background image (blurRadius=40) + dark gradient overlay + art shadow + scale/fade scroll animation + sticky header title fade-in; cover art URL now uses a stable salt derived from credentials soexpo-image's disk cache is not busted on every render - Expo ND detail screens: cover art now renders correctly by placing computed dimensions on a parent
Viewand givingImageclassName="w-full h-full"so NativeWind owns the style; track rows in album and playlist detail screens now include aTrackMenuButton"…" context menu - GPUI: Navidrome cover art is now derived directly from the stream URL parameters (
id,u,t,s) instead of requiring an active server connection, eliminating blank album art when playback starts before the ND panel is connected; removes thePENDING_COVER_ARTstaging mutex and the asyncgetSonground-trip - macOS Now Playing /
MPNowPlayingInfoCenter: cover art priority corrected —coverArtUrl(forStreamUrl:)is now tried first (returnsnilfor local tracks), then falls back toalbumArt; the previous order always hit thealbumArtbranch even when it pointed at an empty path, so Navidrome tracks showed no artwork in the system Now Playing widget - CI: Android firmware build workflows now delete
make.depbeforemake libto force a fresh dependency scan after prefix-restore cache hits that carry stale header dependencies (e.g. thepcm_normalizer.hremoval)
Full Changelog: 2026.05.25...2026.05.27