Skip to content

This release adds 3 notable features for engineering teams evaluating rollout.

Published 24d Media Servers
✓ No known CVEs patched
Read the diff → Tool health → What is this tool? →

✓ No known CVEs patched in this version

Topics

airplay audio flatpak graphql grpc libadwaita
+14 more
macosx mpd mpris music-player music-streaming navidrome rockbox rust self-hosted snapcast tokio typescript typesense zig

Summary

AI summary

Updates 2026.07.02, plain, and synced across a mixed release.

Full changelog

[2026.07.02]

Added

  • jellyfin: Playlists API — full CRUD from the OpenAPI spec so third-party clients (Findroid, Streamyfin, Moonfin, Amcfy) can create, edit, and delete playlists through the standard surface without falling back to the Subsonic bridge; POST /Playlists returns PlaylistCreationResult, GET/POST /Playlists/{id}, GET/POST/DELETE /Playlists/{id}/Items, POST /Playlists/{id}/Items/{itemId}/Move/{newIndex}, GET /Playlists/{id}/Users, and DELETE /Items/{id} (which only accepts playlists — tracks/albums/artists return 403); backed by the existing rockbox-playlists::PlaylistStore so the Subsonic and Jellyfin surfaces share state; a new virtual "Playlists" CollectionFolder shows up in /Users/{uid}/Views alongside Music so clients render a top-level tile; PlaylistItemId values are synthesized deterministically from (playlist_id, position) so entry-id round-trips through remove/move without a mapping table
  • jellyfin: Favorites API — new jf_favorites (kind, native_id, favorited_at) migration covers tracks, albums, artists, and playlists; writes for tracks/albums mirror to rockbox-library's existing favourites table so smart-playlist is_liked rules and the Subsonic bridge stay in sync, and reads take the union so likes added elsewhere still surface here; POST/DELETE /UserFavoriteItems/{id} (10.9+) and legacy POST/DELETE /Users/{uid}/FavoriteItems/{id} both return the freshly-updated UserItemDataDto; ?IsFavorite=true and ?Filters=IsFavorite on /Items route through a list_favorites orchestrator that honours IncludeItemTypes across all four kinds
  • jellyfin: UserData API — new jf_user_data (kind, native_id, played, play_count, playback_position_ticks, last_played_at, likes, rating, updated_at) composite-PK cache backs the spec's per-item user-data fields; for tracks the store merges with rockbox-playlists::track_stats on read (whichever play_count is higher wins, engine's last_played fills a null) so audio-engine counters surface on the Jellyfin side without a sync step; GET /UserItems/{id}/UserData (10.9+) and legacy GET /Users/{uid}/Items/{id}/UserData roll up IsFavorite + playback + rating + likes; the matching POST accepts a partial UpdateUserItemDataDto where unset fields preserve stored state per spec, and IsFavorite is forwarded to the favorites store so both surfaces stay coherent; all four *_to_dto helpers now emit real user-data fields instead of hardcoded zeros
  • jellyfin: InstantMix API — seed-based mix generator covers GET /Items/{id}/InstantMix (generic dispatcher) plus the legacy /Songs, /Albums, /Artists, /Artists/InstantMix?id=, /Playlists, and /MusicGenres/{name} per-kind aliases; algorithm anchors the seed (or its own tracks), fills with same-artist_id matches, then same-genre_id, then a random tail from the whole library; track seeds keep position 0 anchored through the shuffle so "play me first" behaviour is preserved; results dedup by native id and truncate to Limit (default 50)
  • jellyfin: Lyrics API — reads a .lrc (synced) or .txt (plain) sidecar next to the audio file; GET /Audio/{id}/Lyrics returns a LyricDto (with LyricLine.Start in 100-ns ticks and a LyricMetadata block populated from ar/al/ti/by/offset/length/re/ve/au header tags), POST accepts raw LRC / plain text OR a JSON LyricDto body (distinguished by Content-Type — JSON re-serializes as LRC so external players can read the file), DELETE removes sidecars idempotently, GET /Audio/{id}/RemoteSearch/Lyrics and GET /Providers/Lyrics return empty arrays since no remote providers ship; LRC parser handles header tags, multi-timestamp expansion, and offset application; unit tests cover synced/multi-timestamp/plain parsing
  • jellyfin: Similar API backed by Last.fm and MusicBrainz plugins — GET /Items/{id}/Similar plus legacy /Artists/{id}/Similar and /Albums/{id}/Similar route through a plugin orchestrator that seeds artist.getsimilar / track.getsimilar from Last.fm and canonicalizes MBIDs through MusicBrainz before matching results back to the local library; artist seed → local artist by exact/case-insensitive name; album seed → same-artist expansion (Last.fm has no album endpoint) → their local albums; track seed → local (title, artist) lookup with the returned artist MBID cross-referenced through MB; plugins are gated on lastfm_api_key / musicbrainz_user_agent — no key means no fetches, and SimilarResult short-circuits to empty rather than falling back to random suggestions; both plugin activations log at startup
  • jellyfin: RemoteImage API backed by Cover Art Archive — GET /Items/{id}/RemoteImages returns candidate cover art (honours type, startIndex, limit), GET /Items/{id}/RemoteImages/Providers lists active providers, and POST /Items/{id}/RemoteImages/Download?imageUrl=&type= fetches the URL, saves under ~/.config/rockbox.org/covers/ using the same md5(album_id) filename scheme the audio scanner uses, and updates album.album_art in one shot; MusicBrainz search resolves (artist, album) to an MBID via a Lucene-escaped release-group query; CAA client handles both historical (small/large) and size-suffixed (250/500/1200) thumbnail shapes; track seeds fall back to their parent album so the "change cover art" action from a now-playing view works; gated on the same musicbrainz_user_agent as the Similar plugin
  • jellyfin: Genre Browsing API — new KIND_GENRE + remember_genre so genres get stable GUIDs; genre_to_dto populates SongCount / AlbumCount for chip UIs; GET /Genres and /MusicGenres (sorted list, honours searchTerm / nameStartsWith / range / pagination); GET /Genres/{name}, /MusicGenres/{name}, and legacy /Users/{uid}/Genres/{name} with case-insensitive fallback; items_impl gained a parentId=<genre_guid> branch (dispatches on IncludeItemTypes to return tracks / albums / artists under the genre) and an IncludeItemTypes=MusicGenre shorthand for the flat list
  • jellyfin: Filters API — GET /Items/Filters returns QueryFiltersLegacy (flat genre name list), GET /Items/Filters2 returns QueryFilters (NameGuidPair list so chips round-trip through ?genreIds=<guid> without a name→guid lookup), plus the pre-10.9 GET /Users/{uid}/Items/Filters alias; years come from distinct non-zero album.year; tags and official ratings stay as empty arrays since rockbox doesn't track them
  • jellyfin: Item Counts endpoint — GET /Items/Counts returns ItemCounts with real SongCount / AlbumCount / ArtistCount from repo::*::count_filtered; unsupported kinds (movies, series, episodes, trailers, boxsets, books) stay at 0 per spec; ItemCount sums the three we surface
  • jellyfin: Last.fm artist enrichment — new jf_artist_enrichment (artist_id PK, mbid, bio, tags, image_url, fetched_at) cache backs Overview and Genres on MusicArtist DTOs; LastFm::artist_info calls artist.getInfo, cleans the trailing "Read more on Last.fm" link, extracts tags and the largest image URL; enrichment::get_artist is a cache-only read used by every artist_to_dto (SQLite lookup — list requests stay fast), enrichment::refresh_artist enforces a 7-day TTL and upserts on demand from the detail handlers; serves stale rows on network errors so a blip doesn't blank the bio
  • jellyfin: Last.fm album enrichment — mirrors the artist flow with a new jf_album_enrichment (album_id PK, mbid, description, tags, image_url, fetched_at) cache; LastFm::album_info calls album.getInfo with MBID priority over (artist, album), parses the wiki summary/content; album_to_dto reads the cache on every call so list paths stay fast; the album detail branches in item_by_id / user_item_by_id call refresh_album first so the returned DTO carries a hot description
  • jellyfin: home-rail routes replace the earlier empty_items stubs so Findroid / Streamyfin / official web actually populate their home screens — /Items/Resume + /Users/{uid}/Items/Resume return tracks with non-zero PlaybackPositionTicks ordered by updated_at DESC, /UserItems/Resume and /UserItems/Latest cover the legacy plain-array shape Findroid still uses, /Items/Suggestions + /Users/{uid}/Items/Suggestions return random tracks by default (honours IncludeItemTypes=MusicAlbum / MusicArtist and mediaType=Audio) via SQL RANDOM() so the rail refreshes each session; the generic /Items handler now recognizes sortBy=PlayCount and sortBy=DatePlayed (plus DateLastPlayed) with sortOrder=Descending|Ascending, joining track LEFT JOIN track_stats so the "Most Played" and "Recently Played" home rails work through the standard /Items endpoint without dedicated handlers
  • settings: new optional lastfm_api_key: Option<String> and musicbrainz_user_agent: Option<String> fields on NewGlobalSettings — the Jellyfin Similar / RemoteImage / enrichment plugins short-circuit to empty when their respective field is absent, so the plugins only activate when the corresponding credentials are present in settings.toml

Full Changelog: https://github.com/tsirysndr/rockboxd/compare/2026.06.29...2026.07.02

Weekly OSS security release digest.

The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.

No spam, unsubscribe anytime.

Share this release

Track A modern Music Player Daemon based on Rockbox firmware

Get notified when new releases ship.

Sign up free

About A modern Music Player Daemon based on Rockbox firmware

All releases →

Beta — feedback welcome: [email protected]