Skip to content

Releases: tsirysndr/rockboxd

2026.06.26

26 Jun 19:03

Choose a tag to compare

[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 single Vnode::Write event and only surfaces one new file per coalesce; start_watcher now spawns a background tokio task that ticks every ROCKBOX_RESCAN_INTERVAL_SECS seconds (default 120, set 0 to disable), runs scan_audio_files then the new audio_scan::reconcile_deletions which walks repo::track::all (already filters is_remote = 0) and calls repo::track::delete_by_path for any track whose path no longer exists on disk; overlapping ticks are skipped via tokio::sync::Mutex::try_lock so the periodic pass can never pile up behind a still-running initial scan on a large library
  • ci: Debian and Fedora package builds, published to GitHub Releases and Gemfury — new .github/workflows/linux-x86_64-build.yml builds rockbox, rockboxd, librockboxd.a, and rockbox-gpui (via gpui/package.sh, hard-fails if the Linux GPUI build breaks), downloads typesense-server v30.1 from dl.typesense.org, and packages a .deb and .rpm that ship all four binaries plus the rockbox-gpui desktop entry and PNG icon; the existing linux-aarch64-build.yml is extended with a .deb containing rockbox + rockboxd + typesense-server (no GPUI); linux-armhf-build.yml gains a .deb with just rockboxd; each workflow checks for FURY_TOKEN and FURY_ACCOUNT secrets and pushes its own packages to push.fury.io, mirroring the smolsonic release pipeline

Full Changelog: 2026.06.25...2026.06.26

2026.06.25

25 Jun 14:02

Choose a tag to compare

[2026.06.25]

Added

  • Embedded S3 admin web UI — new React + Vite SPA under crates/s3/s3webui/ (TanStack Router/Query + Jotai + FlyonUI/Tailwind) embedded into rockbox-s3 via rust-embed and 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 configured s3_access_key / s3_secret_key, and the standard buckets / objects / upload / settings views are wired in; HEAD /{bucket} is now implemented so HeadBucket succeeds; the Dockerfile gains an in-container Bun builder stage and every CI workflow that compiles rockbox-s3 runs 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-binrockboxd-bin), macOS pkg identifier, Electron appId, and Gleam SDK metadata; the published npm scope @rockbox-zig/sdk and the rockboxzig.mintlify.app docs URL are intentionally unchanged

Fixed

  • ci(gpui): force relink of librockboxd.a in the release-gpui workflow — the cache key only hashes Cargo.lock, so edits to crates/embed sources 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

23 Jun 17:52

Choose a tag to compare

[2026.06.23]

Added

  • S3-compatible HTTP API — new crates/s3 actix-web server listens on s3_port (default 9000) and exposes music_dir as a single fixed bucket (music, region us-east-1); supports PutObject, DeleteObject, GetObject, HeadObject, ListObjectsV2, and ListBuckets with AWS Signature V4 header-form authentication; the new s3_enabled, s3_host, s3_port, s3_access_key, s3_secret_key keys in settings.toml gate 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-PAYLOAD is not supported (clients should use UNSIGNED-PAYLOAD or sign the full body — awscli v2.23+ needs AWS_REQUEST_CHECKSUM_CALCULATION=when_required to 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) — recursive notify-based watch on music_dir keeps the SQLite tag database in sync with on-disk changes; Create and data-modify events call save_audio_metadata, Remove events call the new repo::track::delete_by_path (cascades through album_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 in crates/cli/src/lib.rs::run_indexing() and is also the sync engine behind the new S3 API — no separate DB code path

Fixed

  • build: link CoreServices.framework on macOS so the notify crate'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 the rockboxd executable and the embeddable librockboxd.a link blocks in zig/build.zig, and to the embedder-facing framework list in CLAUDE.md

Full Changelog: 2026.06.18...2026.06.23

2026.06.18

18 Jun 15:14

Choose a tag to compare

[2026.06.18]

Fixed

  • server: hard-exit when run_http_server() returns Err — the spawning thread used to log and exit silently, leaving :6063 dead while the rest of rockboxd kept running and surfaced only as downstream GraphQL request errors
  • playback: skip HTTP look-ahead of the currently-playing URL — REPEAT_ALL on a single-track playlist wrapped playlist_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 finishes
  • pcm-cmaf: poll every 10 ms for up to 5 s for the next PCM chunk before bailing — the sink used to give up the instant pcm_play_dma_complete_callback returned false, killing playback within ~23 ms of every track start whenever the codec hadn't queued the next chunk yet
  • library: replace .expect() panics on Probe::open() in the album_art, copyright_message, and label extractors with logged errors that return Ok(None), so an unreadable track no longer aborts the library scan

Full Changelog: 2026.06.15...2026.06.18

2026.06.15

15 Jun 19:45

Choose a tag to compare

[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/token
  • rockboxd 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.toml without touching any other fields; --did enables public access — any user's settings can be pulled without a token by passing their DID or handle
  • rockboxd settings push — read the current settings.toml and upload the audio sections (equalizer, crossfade, replaygain, tone) to Rocksky via app.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

14 Jun 18:18

Choose a tag to compare

[2026.06.14]

Added

  • arm-unknown-linux-gnueabihf cross-compilation target — new scripts/build-armhf.sh builds a native ARMv6 hard-float rockboxd binary (e.g. Raspberry Pi Zero) using the Dockerfile.arm-unknown-linux-gnueabihf cross-toolchain; Zig links with -Dtarget=arm-linux-gnueabihf -Dcpu=arm1176jzf_s; Cross.toml wires cross build to the same Docker image; firmware configure target 208 (ARMHFHOST) reuses the headless target files with arm-linux-gnueabihf-gcc and -march=armv6 -marm -mfpu=vfp -mfloat-abi=hard
  • crates/alsa-sink — direct libasound PCM sink for ARM Linux; uses snd_pcm_writei (RWInterleaved, same as aplay), avoiding cpal's ALSA backend and the snd_pcm_status_get_htstamp null-PLT-entry crash on older ARM devices; ALSA is opened once in pcm_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-sink in the ARM build; registered as PCM_SINK_ALSA = 9 in firmware/export/pcm_sink.h
  • firmware/target/hosted/headless/pcm-alsa.c — C PCM sink ops mirroring pcm-cpal.c but calling pcm_alsa_* entry points
  • .github/workflows/linux-armhf-build.yml — CI workflow that builds and uploads the armhf binary to GitHub Releases

Fixed

  • ARM Linux: SIGILL from __ARMv7ABSLongThunk__ — Ubuntu's arm-linux-gnueabihf-gcc defaults to -march=armv7-a; added -march=armv6 -marm to the configure armhfhostcc() function so all C objects are tagged ARMv6; Zig's LLD then uses ARMv6-compatible thunks that work on ARM1176JZF-S
  • ARM Linux: SIGILL at startup — LLD derives HasMovt from the target triple (arm-linux-gnueabihf = conventional ARMv7), generating movw/movt thunks even when object attributes say ARMv6; fixed by using ReleaseFast to produce a compact binary (< 32 MB) that fits within LLD's direct-branch range, eliminating the need for long-range veneers
  • ARM Linux: SIGSEGV in SimpleBroker::subscribe (dyn Any vtable null) — Zig's LLD generates zero vtable entries for dyn Any + Send COMDAT groups on ARM 32-bit in ReleaseFast mode; replaced HashMap<TypeId, Box<dyn Any + Send>> in crates/graphql/src/simplebroker.rs with a type-erased ErasedSenders struct 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: SIGSEGV in alsa::pcm::Status::get_htstamp — on ARM devices where libasound ships snd_pcm_status_get_htstamp as 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 direct alsa-sink that never calls this function
  • ARM Linux: 32-bit ABI mismatches in crates/sysc_long/c_ulong are 32-bit on ARM (not 64-bit); added as c_long / as c_ulong casts in metadata.rs, playback.rs, playlist.rs, sound/dsp.rs, system.rs, tagcache.rs, and as u64/as i64 field casts in types/mp3_entry.rs; crates/cli/src/lib.rs now uses libc::rlim_t instead of u64 for rlimit fields
  • ARM Linux: audiohw_set_volume undefined reference to pcm_cpal_set_volume — gated the cpal volume call on !ARMHFHOST in audiohw-noop.c; volume is handled by Rockbox's DSP layer (HAVE_SW_TONE_CONTROLS) on ARM
  • ARM Linux: PCM_SINK_ALSA = 9 array-bounds error in pcm.c — enum entry was declared before PCM_SINK_CMAF = 8, making PCM_SINK_NUM = 9 and sinks[9] out-of-bounds; moved ALSA entry after CMAF so PCM_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; added ARMHFHOST guard to audiohw.h (sdl_codec.h inclusion) and filesystem-app.c (rbhome pointer declaration)
  • metadata: probe_content_type_format now logs the exact Content-Type string received (or reports that stream_content_type returned < 0) to stderr, making HTTP format-detection failures visible; added audio/x-aac and audio/vnd.dlna.adts to the AAC-BSF MIME mapping

Full Changelog: 2026.06.07...2026.06.14

2026.06.08

08 Jun 06:32

Choose a tag to compare

[2026.06.08]

Fixed

  • GraphQL playAlbum / playArtistTracks / playGenreTracks / playDirectory / playTrack / playLikedTracks / playAllTracks were no-ops when the active output was the CMAF (HLS / DASH) sink — the check_and_load_player! macro used a host != "" && port != 0 heuristic to detect external cast players, but CMAF advertises host="localhost", port=7882 for its own HTTP server, so the macro misrouted the request to /player/load (which 404s because state.player is only populated for Chromecast) and returned Ok(0) before building the playlist; now matches the RPC variant and gates on is_cast_device instead, so local PCM sinks (CMAF, FIFO, builtin, squeezelite, AirPlay, UPnP) fall through to the regular playlist-build path
  • MPD restore_playlist: bounds-check the persisted resume_index against the current playlist length before indexing — a stale resume index from a prior session with a longer queue was panicking the MPD thread with index out of bounds: the len is 15 but the index is 91 and aborting the daemon

Full Changelog: 2026.06.07...2026.06.08

2026.06.07

07 Jun 10:32

Choose a tag to compare

[2026.06.07]

Added

  • CMAF (HLS + DASH) PCM sink — new rockbox-cmaf crate encodes PCM to AAC-LC (fdk-aac) and serves HLS + DASH manifests with fMP4 segments over HTTP; enabled via audio_output = "cmaf" (or "hls" / "dash") plus cmaf_http_port, cmaf_bitrate, and optional cmaf_segment_dir for mirroring artefacts to disk for an external origin (nginx, Caddy, CDN); registered as PCM_SINK_CMAF = 8 in firmware/pcm.c; surfaced as a virtual device selectable via /connect/cmaf with broadcast icons in the GPUI, Expo, web, and macOS device pickers
  • Standalone HLS/DASH player — new crates/hls decodes .m3u8 / .mpd URLs and pushes PCM straight into the active sink via new pcm_external_write / pcm_external_set_freq firmware 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 / hardStop in crates/rpc detect an active HLS session and dispatch locally or forward to the broadcaster over gRPC so peers stay in sync
  • Web UI: HlsAutoConnect attaches an <audio> element to /hls/master.m3u8 whenever the active device type is cmaf / hls / dash, and HlsVolumeControl adds a local browser volume slider; Docker default audio_output flipped to cmaf and port 7882 exposed; new Mintlify page documents the sink; GraphQL globalSettings.cmafHttpPort added

Fixed

  • CMAF sink: encoder now bootstraps with a full SEGMENT_WINDOW of silence so hls.js / dash.js don'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 from load_settings / device connect so the HTTP endpoint binds before the first track plays
  • Android HTTP streaming smoothness — cpal_thread priority boosted (setpriority(PRIO_PROCESS, tid, -19)) and NowPlayingService now acquires PARTIAL_WAKE_LOCK + WifiLock while the daemon is running, eliminating the doze-induced stutters on Wi-Fi remote streams
  • netstream: rb_net_len and rb_net_content_type now wait for open_done before reading stream state, so callers see the real length / MIME instead of -1 / empty when the HTTP open is still in flight
  • netstream: removed TCP keepalive from the global reqwest client — keepalive probes were tripping middleware and aborting long Range reads on some CDNs
  • netstream: non-blocking rb_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 omit Content-Length, this unblocks both the audio thread and the UI on slow first-byte servers
  • netstream: 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 connections
  • netstream: seek Range requests now retry on transient errors; huge forward skips on servers that ignore Range fast-fail instead of redownloading the whole prefix
  • netstream: 30 s hard timeout removed from read_into — the prefetch thread's own retry budget now governs how long a read can wait, so a brief stall no longer kills the stream
  • pcm-cpal: DMA thread exits immediately on stream error instead of draining pcmbuf, so the next track / device switch can re-arm the sink without waiting for a stale flush
  • cpal sink: recover from stream errors and break the push deadlock — error callback now signals the writer so pcm_cpal_push returns 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 path and 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_output flipped to cmaf; 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_timeout 15 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 passes BUFFERING_DEFAULT_FILECHUNK instead of 0 when 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 one BUFFERING_DEFAULT_FILECHUNK per call; streamfd.c replaces per-chunk fprintf(stderr, …) with logf() (compiled out in production) to eliminate hundreds of blocking write(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 to navidrome-client.ts reconstructs a getCoverArt URL from the id, u, t, s parameters embedded in the stream URL; used as a fallback in trackFromProto when album_art is empty

Full Changelog: 2026.05.28...2026.06.07

2026.05.28

28 May 20:59
60912c6

Choose a tag to compare

[2026.05.28]

Added

  • netstream: background prefetch thread per HTTP stream — a dedicated thread fills a 2 MB VecDeque buffer from the network so rb_net_read drains in sub-µs without ever blocking on TCP; forward seeks within the prefetch window consume buffered bytes without issuing a new Range request; backward or large (>2 MB) forward seeks issue a Range request and replace the reader thread atomically

Fixed

  • HTTP streaming: removed reqwest total-request timeout (only connect_timeout 15 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 passes BUFFERING_DEFAULT_FILECHUNK instead of 0 when 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 one BUFFERING_DEFAULT_FILECHUNK per call; streamfd.c replaces per-chunk fprintf(stderr, …) with logf() (compiled out in production) to eliminate hundreds of blocking write(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 to navidrome-client.ts reconstructs a getCoverArt URL from the id, u, t, s parameters embedded in the stream URL; used as a fallback in trackFromProto when album_art is empty

Full Changelog: 2026.05.27...2026.05.28

2026.05.27

27 May 20:16

Choose a tag to compare

[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, search3 replaces 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 with AbortController + setTimeout in navidrome-client.tsAbortSignal.timeout is absent in some Hermes / React Native versions and was silently swallowing timeouts, making every fetch return null; switched to md5 npm package (removed inline implementation); set NSAllowsArbitraryLoads=true in iOS infoPlist to unblock HTTPS servers that do not meet strict ATS TLS requirements
  • Expo ND album detail: mirror local album/[id].tsx hero 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 so expo-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 View and giving Image className="w-full h-full" so NativeWind owns the style; track rows in album and playlist detail screens now include a TrackMenuButton "…" 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 the PENDING_COVER_ART staging mutex and the async getSong round-trip
  • macOS Now Playing / MPNowPlayingInfoCenter: cover art priority corrected — coverArtUrl(forStreamUrl:) is now tried first (returns nil for local tracks), then falls back to albumArt; the previous order always hit the albumArt branch 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.dep before make lib to force a fresh dependency scan after prefix-restore cache hits that carry stale header dependencies (e.g. the pcm_normalizer.h removal)

Full Changelog: 2026.05.25...2026.05.27