Files
Psychotoxical-psysonic/WHATS_NEW.md
T
Psychotoxical b950d4704b feat(cover): artist artwork from fanart.tv (off by default) (#1137)
* feat(cover): add artist_artwork_lookup table + accessors

Image-scraper P0 (design-review §12): additive library-SQLite migration 013
plus get/upsert/clear-per-server accessors for the external artist-artwork
lookup (fanart.tv). Render never reads it; the on-demand cover ensure path
and the mbid_ambiguous 24h negative cache use it. server_id = serverIndexKey.

* feat(cover): add fanart.tv + getArtistInfo2 provider layer

Image-scraper P0 (§7/§19/§23): new cover_cache/external.rs — Rust-side
getArtistInfo2 tag-MBID resolution plus fanart.tv v3/music URL + first
artistbackground fetch (BYOK client_key sent in addition to the project
api_key per fanart.tv ToS, §22). Extract a shared build_subsonic_url helper
in fetch.rs (cover URL behaviour unchanged). Add a dedicated low-concurrency
fanart_http_sem so external HTTP never starves Navidrome (§26). URL builders
unit-tested; wired into ensure_inner next.

* feat(cover): wire fanart.tv external branch into ensure_inner

Image-scraper P0 (§16): on-demand artist `fanart` ensures try fanart.tv
before the Navidrome fallback. MBID resolved Rust-side via getArtistInfo2
(§23, tag MBID); on a miss it falls through WITHOUT a .fetch-failed marker so
Navidrome stays the display fallback (§28). External tiers are written as
{tier}-fanart.webp in the same entity dir (same cacheKind, §16) — 2000 + 512
(matryoshka §17); peek prefers them for the fanart surface. Dedicated
low-concurrency fanart lane (§26); .miss-fanart ~30min negative marker.
Additive IPC args (externalArtworkEnabled, surfaceKind), off by default and
gated by PSYSONIC_FANART_KEY — inert until a render surface opts in (P1).
Quality gate (§11), name->MusicBrainz (§19), and lookup-table writes are P1.

* feat(cover): fanart-first peek for the fanart surface

Image-scraper P0: for an artist `fanart` ensure, the early peek serves only
the external {tier}-fanart.webp tiers; if none exist yet it returns None so
ensure runs the external branch (fetch fanart) instead of short-circuiting on
a cached Navidrome tier. Realises "fanart prioritised" (§18) for the opt-in
surface; Navidrome stays the fallback inside the branch's miss path.

* chore(cover): dev-only artist-fanart spike helper

DEV-only window.psyFanartSpike(name) — resolves an artist by name and fires
the real cover_cache_ensure with externalArtworkEnabled+surfaceKind=fanart to
verify the P0 pipeline against a live server (with PSYSONIC_FANART_KEY set).
Not wired in production.

* feat(cover): §11 quality gate for the fanart surface

Before an external fanart fetch, check whether a Navidrome tier already on
disk is an HQ ~16:9 image (width >= 1280, aspect 1.6-2.0) and skip the fetch
if so — square artist portraits never satisfy it, so the common case still
fetches. Reads tier dimensions only (no full decode). Rust consts per the
design review. Unit-tested predicate.

* feat(cover): persist fanart resolution in artist_artwork_lookup (§12)

Wire the lookup table as both the MBID resolution cache and the negative
cache: a cached MBID skips the getArtistInfo2 round-trip; no_mbid/mbid_ambiguous
back off 24h and miss 30min from updated_at before re-querying. Writes
hit/miss/no_mbid with mbid/mbid_source/provider; transient network errors are
not cached. All store reads/writes run off the async executor via
spawn_blocking and no-op before login. Store reached via app.try_state::<LibraryRuntime>().

* feat(cover): compile-time fanart key fallback + album/name IPC args

A runtime PSYSONIC_FANART_KEY still wins (dev), else the key baked in at build
time via option_env! (release). Add additive artistName/albumTitle ensure args
as context for the §19 name->MusicBrainz fallback (inert until the render
passes them). Library backfill passes None.

* feat(settings): add External Artwork Scraper toggle under Integrations

Master toggle (themeStore, off by default per §20) in a new Integrations
subsection, alongside the other opt-in third-party categories. Contacts
fanart.tv only when enabled. i18n across all 9 locales.

* feat(cover): wire fanart background into the fullscreen player (§28)

New useArtistFanart hook resolves a fanart.tv 16:9 background via a dedicated
cover_cache_ensure (surfaceKind=fanart) — it bypasses the shared peek/disk-src
cache (the {tier}-fanart.webp surface is keyed differently) and reuses
coverDiskUrl for the asset URL. Fullscreen background priority is now
fanart -> Navidrome artist image (cover pipeline) -> album cover; the live
useFsArtistPortrait probe is deleted (§28). Additive ensure opts
(surfaceKind/artistName/albumTitle); externalArtworkEnabled is derived in
ensureArgsFromRef from the master toggle and restricted to the artist fanart
surface, so plain cover ensures are unaffected.

* feat(cover): generalize external surface to fanart + banner (§13)

surfaceKind='banner' fetches the fanart.tv musicbanner array -> {tier}-banner.webp
in the same entity dir; fanart stays the 16:9 artistbackground. The ensure
branch, peek, lookup rows (per-surface surface_kind), miss marker
(.miss-<surface>) and tier suffix are all surface-parameterised. The §11
quality gate stays fanart-only (the banner strip has its own aspect). Unit
test for the surface->fanart JSON key map.

* feat(artist): fanart banner on the artist-detail header (§13, Option B)

The artist-detail header gets an album-detail-style background layer: fanart.tv
banner (musicbanner) -> the 16:9 fanart background cropped to the strip ->
empty. Both via a shared useArtistExternalImage hook (useArtistBanner /
useArtistFanart); each fetches on demand and shares the Rust cache, so the
header and the fullscreen player warm each other's images. The header is its
own stacking context (isolation) so a z-index:-1 banner clips behind the avatar
+ meta with no content wrapper; the album-style framing (padding/radius/clip)
is applied only when a banner is shown, so the off-by-default case stays
pixel-identical.

* refactor(artist): reuse album-detail header structure for the fanart banner

The artist-detail header now uses the same album-detail-* container classes as
AlbumHeader (header/bg/overlay/content/hero) with the fanart banner as the
background; the Back button moves inside the header. A surgical
`artist-detail-bleed` cancels the artist page's .content-body padding so the
banner is full-bleed to the container edges, matching the album header exactly
instead of the earlier inset card. Reverts the experimental artist-specific bg
CSS.

* feat(cover): drop artist_artwork_lookup rows on clear-cover-cache (§12/B.4)

cover_cache_clear_server already removed the server's whole cover dir (so the
{tier}-fanart.webp / -banner.webp tiers + .miss-* markers go with it); also
clear the artist_artwork_lookup rows for that server (off-thread) so no stale
resolution state lingers. Automatic toggle-off purge deferred — turning the
toggle off already hides external artwork (render is gated), and explicit
cache-clear now cleans external state too.

* feat(cover): name->MusicBrainz album-confirmed MBID resolution (§19)

When getArtistInfo2 has no tag MBID and the ensure carries the artist name +
an album in context (fullscreen), one MusicBrainz release-search query resolves
the artist MBID: the primary artist across score>=90 releases wins, conflicting
ids -> mbid_ambiguous (24h backoff), none -> no_mbid. Sends the required
User-Agent; a single-permit musicbrainz_sem + >=1s spacing holds us under MB's
rate limit. mbid_source=musicbrainz persisted. Banner surface (no album
context) correctly skips this. Pure classify/escape helpers unit-tested.

* fix(cover): enable banner surface in ensureArgsFromRef

externalEnsureFields only set externalArtworkEnabled for surfaceKind 'fanart',
so the 'banner' surface never fired — the artist-detail header always fell back
to the fanart image instead of the fanart.tv musicbanner. Both external artist
surfaces (fanart/banner) now enable the external branch.

* feat(settings): optional BYOK personal fanart key field

Add an optional personal fanart.tv API key field to the External Artwork
Scraper block (shown when the toggle is on): a masked input, a saved/in-use
status line, and the simple note that it is sent in addition to the app key.
Persisted in themeStore and plumbed through cover_cache_ensure
(externalArtworkByok); Rust prefers the settings key, falling back to the
PSYSONIC_FANART_CLIENT_KEY dev env. i18n x9.

* fix(cover): resolve artist-page fanart image collision on navigation

The artist-detail header keyed its fanart/banner hooks on the route `id`,
which flips immediately on navigation while `artist`/`albums` refetch a beat
later. The mismatched ensure wrote the previous artist's image under the new
artist's id (e.g. Sepultura's image under Lordi's id).

- key on the loaded `artist.id`, not the route `id`, so id/name/album always
  describe the same artist
- pick the §19 album context from an album that actually belongs to this
  artist (`albums.find(a => a.artistId === artist.id)`), so a stale album can't
  run a mismatched name→MusicBrainz query or cache a wrong `no_mbid`
- reset `src` on every input change in `useArtistExternalImage` so a previous
  artist's image never lingers while the new one resolves

* fix(cover): strip trailing album qualifier before MusicBrainz lookup

Library titles like "Show No Mercy (2004 Remastered)" or "Album [Deluxe
Edition]" failed the §19 MusicBrainz release query, blocking name-confirmed
MBID resolution. `normalize_album_for_mb` strips a single trailing
parenthetical/bracketed qualifier; leading qualifiers (e.g. "(What's the
Story) Morning Glory?") are left intact. Unit-tested.

* fix(cover): don't cache no_mbid when album context is unavailable

The banner ensure could fire before the artist's albums loaded, with no album
in context. The old code cached `no_mbid` there and the 24h backoff then
blocked the later ensure that arrived *with* album context. Could-not-attempt
is not tried-and-failed: the no-album branch now returns without persisting.

* fix(cover): don't emit tier-ready for external fanart/banner surfaces

`try_external_fanart` emitted `cover:tier-ready` with the `{tier}-{surface}.webp`
path. That event is keyed by the canonical cover key (cacheKind/cacheEntityId/
tier, no surface), so the frontend `useCoverArtBridge` listener seeded the
Navidrome artist cover's disk-src cache with the external image — leaking
fanart/banner into the plain artist cover (avatar, fullscreen "navidrome-artist"
fallback) even with the scraper toggled off.

Remove the emit: the fanart/banner hooks read the path from the
`cover_cache_ensure` return value, so no event is needed. (No disk-level
overwrite — the suffixed files are never matched by `tier_exists`; this was
frontend disk-src-cache cross-contamination.)

* fix(cover): wait for the final external background before showing it, with fade-in

The fullscreen player and artist-detail header flashed several backgrounds in
sequence while the fanart resolved (upscaled album cover → Navidrome artist
image → fanart), and the artist header could show the fanart first and then
swap to the banner.

- the album cover is no longer a background source — it only feeds the
  foreground thumbnail
- the external-artwork hooks return `{ src, pending }` so callers can tell
  "still resolving" (hold back) from "resolved, no image" (fall back now)
- fullscreen background: scraper on → fanart, empty while it resolves, Navidrome
  artist image only on a confirmed miss; scraper off → Navidrome artist image
- artist header: the banner is preferred — nothing shows while it resolves
  (no fanart flash), fanart is the fallback only once the banner misses
- both backgrounds preload the chosen image and fade it in (`onLoad` plus a
  `ref` `complete` check so an already-cached image, whose load event can fire
  before React attaches the handler, still appears). The header fade is a
  scoped inline opacity so the shared `album-detail-bg` class is untouched.

* ci(release): pass PSYSONIC_FANART_KEY into the macOS + Linux builds

* refactor(cover): extract external-artwork ensure into its own module

Pure code move: the on-demand fanart/banner fetch, the quality gate, the
surface-aware peek and the lookup-table cache move from cover_cache/mod.rs
into cover_cache/external_ensure.rs. Behaviour unchanged; mod.rs 1877 -> 1488.

* chore(cover): remove dev-only fanart spike helper

The real render wiring now exercises the external ensure branch, so the
dev-only window.psyFanartSpike helper is redundant.

* feat(cover): purge external artwork on opt-out (B3)

New cover_cache_purge_external command: when the External Artwork toggle is
turned off, drop every fetched {tier}-{provider}.webp, .miss-{provider}
marker and artist_artwork_lookup row across all configured servers, leaving
the canonical Navidrome covers intact. Opting out now removes the
third-party-sourced data instead of just hiding it (design-review §9/§12/B.4).

* docs: changelog, credits and what's new for artist fanart (PR #1137)
2026-06-20 21:04:21 +02:00

11 KiB
Raw Blame History

What's New

User-facing release highlights for the in-app What's New screen. Maintainers refresh the current line before promoting to next / release. Technical details and PR credits stay in CHANGELOG.md.

Within each section, order by user impact (most noticeable first) — not PR merge order. CHANGELOG.md keeps strict PR order inside Added / Changed / Fixed.

[1.49.0]

Highlights

AutoDJ — minimum pauses, maximum music

  • New AutoDJ mode — a smart crossfade that blends tracks intelligently. Each transition is shaped to fit the music: awkward gaps fade away, handovers feel natural, and you spend less time in silence between songs. It's now its own choice in Settings → Audio and its own button in the queue toolbar, sitting alongside Crossfade and Gapless — pick one at a time. Off by default; classic Crossfade is unchanged.

Playlist folders — your playlists, organised

  • Folders on the Playlists page and in the sidebar keep long lists tidy — group by mood, occasion, or anything you like. Drag playlists in, rename and collapse folders, or choose Move to folder from the right-click menu. Switch back to a flat list whenever you prefer.

Theme store — spot updates, pick your style

  • Version numbers on store themes and ones you have installed make it obvious when an update is ready.
  • Filter for animated or static themes only — less scrolling when you already know the look you want.

Settings — tidier and easier to scan

  • Settings are grouped into clear, labelled panels so related options sit together — less hunting around. The Native Hi-Res Playback option now explains in plain language what it actually does.
  • Normalization and Track transitions are now their own sections under Settings → Audio, and the queue options (display mode, toolbar, and Play-Next order) are gathered into one Queue Settings group under Personalisation.

Japanese — now in your language

  • Psysonic is now available in Japanese (日本語) — pick it from the language menu on the Settings and Login screens.

Artist artwork — richer artist and fullscreen views

  • Switch on External Artwork Scraper under Settings → Integrations to pull artist imagery from fanart.tv: a wide backdrop on the fullscreen player and a banner across the top of the artist page. Off by default, your Navidrome covers stay in charge, and turning it back off removes the fetched images again.

Fixed

Playback and audio

  • Opus/Ogg tracks no longer fight the seekbar while they are still loading — scrub to where you want to be and keep listening.

Player and playlists

  • Add to playlist from the player bar adds the song you are hearing, not the whole album.

Browse and library

  • Albums sorted by artist now list each artist's work AZ by title — no more random order within a name.
  • Artist → Year keeps artists grouped but walks through their albums chronologically, oldest first.

Windows

  • Windows: Previous, Play/Pause, and Next are back when you hover the taskbar icon — and Play/Pause shows whether music is playing or paused.

[1.48.1]

Fixed

Playback and audio

  • Changing tracks — skipping, or the automatic advance at the end of a song — no longer freezes the interface for a few seconds: the progress bar and lyrics keep updating, and on Windows a change of output device now takes effect right away.
  • Seeking an Opus/Ogg track — and then pressing Stop — no longer crashes the app.
  • macOS: pausing or stopping playback and then unplugging headphones (or switching the output device) no longer makes playback restart — it stays paused or stopped.

Offline, Now Playing, and Navidrome

  • On large Navidrome libraries, background library sync no longer locks up database writes for minutes at a time, so play history, ratings, and other saves go through without long delays.

Themes and integrations

  • Discord Rich Presence shows the album cover again when a server profile has both a local and a public address.

Other

  • Windows: the system media controls (Quick Settings media tile, lock screen, and third-party flyouts) now show the album cover and display Psysonic with its icon instead of "Unknown application".
  • macOS: closing the window with the red close button now respects Minimize to Tray — with it on, the window hides to the tray instead of quitting.

[1.48.0]

Highlights

Offline listening

  • When the server is unreachable, browse and detail pages show what you already have locally instead of empty errors — albums, artists, playlists, and cross-server favorites.
  • Starred tracks, pinned albums, and playlists live under one media folder; browse them in Offline Library and see disk usage at a glance.
  • Favorites auto-sync keeps loved songs on disk; pinned albums and playlists refresh when the library index updates.

Music Network — scrobble beyond Last.fm

  • Settings → Integrations now hosts a Music Network: connect Last.fm, Libre.fm, ListenBrainz, Maloja, Rocksky, Koito, or your own GNU FM instance — and scrobble to several at once.
  • Pick a primary service for loved tracks, similar artists, and stats; other connections still receive scrobbles. Your existing Last.fm setup migrates automatically.
  • A master switch turns the whole network on or off.

Theme Store

  • Browse and install community themes from Settings → Themes — search, dark/light filter, full-size previews, and sort by popularity or date.
  • Six palettes ship with the app; everything else installs on demand and works offline after the first download.
  • Now Playing follows every theme cleanly, including light palettes.
  • Import a theme from a local .zip when you have a package from a friend or your own design.
  • The sidebar nudges you when an installed theme has an update; one-click update from the theme card.

Fullscreen player

  • Rebuilt for much lower CPU and memory use: a calm, sharp fullscreen view with album art, waveform seekbar, up-next queue, synced lyrics, ratings, and a clock that follows your Clock format setting.
  • The song title no longer shows a leading track number, and descenders (g, j, p, q, y) are no longer clipped.

Live — richer now playing on Navidrome 0.62+

  • On servers with OpenSubsonic playbackReport (Navidrome ≥ 0.62), Live shows who is playing or paused, how far into the track they are, and playback speed when another client sends it — with smooth position updates between refreshes.
  • In Who is listening?, each listener shows a small status dot (playing, paused, or idle) instead of a vague “minutes ago” line.

Queue — Timeline mode

  • A third queue layout keeps the current track in the middle with history above and up next below — great for long listening sessions. Cycle the header control or pick it in Settings → Personalisation → Queue display.

Settings → Servers

  • Each card shows the server software and version (e.g. Navidrome 0.62.0) under the name, with a cleaner two-line layout and compact actions.
  • Navidrome 0.62+ shows a green AudioMuse-AI badge when the plugin is detected — no manual toggle on current Navidrome.

Sidebar — pin Now Playing to the top

  • New Settings → Sidebar toggle moves Now Playing to the top of the sidebar instead of the bottom (off by default).

Startup

  • A themed loading splash appears while the app starts — colours follow your active theme, including community palettes.

Improved

  • Audio decoding runs on Symphonia 0.6; streams start sooner and recover from stalls without restarting the player.
  • The Preload Next Track toggle under Settings → Storage → Buffering is gone — playback no longer waits on that extra RAM prefetch. Gapless, crossfade, and Hot Cache behave as before.
  • New Semitones playback-speed strategy (±12 st, 0.1 step) with two-decimal speed readout; optional fine steps in Settings → Audio → Advanced.

Fixed

Playback and audio

  • Windows: the app no longer keeps the audio device open while idle, so the system can sleep when music is not playing.
  • macOS: steady playback stutter from background device polling is gone on the default output path.
  • After a long pause, the seekbar shows the saved position immediately and the next Play resumes without an audible blip at track start.
  • Stop keeps the real waveform on the seekbar instead of falling back to flat bars.

Offline, Now Playing, and Navidrome

  • Now Playing cards (from this album, discography, most played) stay populated during cached and offline playback instead of blanking out on track change.
  • Navidrome Show in Now Playing and play-count scrobbles work when audio plays from hot cache, offline pins, or auto-synced favorites.
  • Mixed-server queues still report to the correct Navidrome server.

Themes and integrations

  • Self-hosted Music Network targets (Koito, Maloja, custom GNU FM with a pasted token) scrobble again — reconnect once if you connected before this fix.
  • Favoriting from the player bar, fullscreen player, or shortcuts updates the star in track lists and playlists immediately.
  • Discord Rich Presence shows album art again when covers come from the server.
  • Focus rings and dropdown borders follow the active theme consistently.

Browse and library

  • Tracks tagged with several genres in one field (e.g. Metal/Ambient/Experimental) match each genre again in browse, filters, and search.
  • All Albums → Only compilations returns results for common tagging patterns.
  • Album grids show the album artist on compilations instead of a random track artist.
  • Song rails (Random Picks, Discover Songs, etc.) link each name in multi-artist credits separately.
  • Artist → Top Tracks play works even when the artist page has no albums loaded yet.
  • Home → Most Played no longer jumps the page when you load more albums.
  • Mainstage hero backdrop stays in sync when you skip albums quickly.

Other

  • Linux: the curl | bash auto-installer works again.
  • Linux: internet radio no longer appears twice in the desktop now-playing overlay.
  • On Navidrome 0.62+, add/edit/delete radio stations is shown only to admin accounts; everyone can still play and favourite stations.
  • Linux custom title bar: pick window button styles (dots, flat, pill, and more) and optionally hide minimize in Settings → Appearance.
  • The active server card under Settings → Servers draws a complete border on all sides.

Under the hood

  • Navidrome 0.62+ auto-detects AudioMuse-AI and routes Instant Mix / Lucky Mix through the smarter API when the plugin is present — older Navidrome keeps the manual toggle you already know.