mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
a41e3a624a4661e983503e6589e235de6e671e6b
9 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a41e3a624a |
feat(song-info): show absolute file path on Navidrome via native API (#504)
* feat(song-info): show absolute file path on Navidrome via native API
Subsonic's `getSong.view` returns at most a relative path (or none on
Navidrome), so the Path row in the Song Info modal stayed empty for
most users. Feishin and the Navidrome web client surface the full
server-side path by hitting Navidrome's native `/api/song/{id}` instead.
Added an `nd_get_song_path` Tauri command that logs in to the native
API with the active server's credentials, fetches the song, and returns
the `path` string. Wired it into `SongInfoModal`: the Subsonic
`getSong` call still drives the rest of the dialog, and the native call
runs in parallel only when the active server's identity is
"navidrome". When it returns a path, that absolute path replaces the
relative Subsonic value; native-API failures are silent and the modal
falls back to whatever Subsonic provided.
No token cache yet — the modal is opened occasionally enough that one
fresh login per call is fine.
Closes discussion #479.
* docs: changelog entry for PR #504
Logs the Navidrome native-API absolute file path support for the
Song Info dialog in v1.46.0 "## Added".
* docs: settings contributor entry for PR #504, drop competitor mention
Adds the song-info absolute path bullet to Psychotoxical's contributors
list in Settings, and rewords the existing CHANGELOG entry so neither
text references competing clients.
|
||
|
|
59744601d4 |
feat(composer): Browse by Composer page (issue #465) (#487)
* feat(composer): Browse by Composer page (issue #465) New library section listing every artist credited as composer on at least one track, with a detail page showing all works they're credited on in that role. Targeted at classical-music libraries where the "recording artist" tag carries the orchestra and the "composer" tag carries Bach / Mozart / Chopin. Hits Navidrome's native /api/artist?_filters={"role":"composer"} for the listing and /api/album?_filters={"role_composer_id":"…"} for the works grid — Subsonic getArtist only follows AlbumArtist relations and returns 0 albums for composer-only credits, so the native API is the only path that works. Requires Navidrome 0.55+ (uses library_artist.stats role aggregation); on older / pure-Subsonic servers the page shows a one-line capability banner. - Two new Tauri commands: nd_list_artists_by_role + nd_list_albums_by_artist_role, generic over participant role so conductor / lyricist / arranger pages are trivial to add later. - Composers grid: text-only compact tiles (name + participation count pulled from stats[role].albumCount). No avatars — composer libraries carry no useful imagery and the listing endpoint exposes no image URLs anyway. - ComposerDetail: hero with Last.fm bio (via getArtistInfo2) plus the full work grid, with a graceful fallback when the artist has no external info synced. - Sidebar entry default off (Feather icon) — opt-in for the niche classical use case. - nd_retry backoffs widened from [500] to [300, 800, 1800] — helps every nd_* call survive intermittent TLS-handshake-EOF errors that some reverse-proxy setups produce when keep-alive pools churn. - Distinguishes "server can't do this" (HTTP 400/404/422/501) from transient errors so the capability banner only fires when the server actually rejects the request shape; everything else gets a retry button. - i18n in all 8 supported locales. * fix(composer): address review feedback on detail page + role queries - Re-fetch ComposerDetail when music-library scope changes; previously the album grid stayed stale until navigation while the list refreshed. - Thread library_id through nd_list_artists_by_role and nd_list_albums_by_artist_role so role queries respect the active Navidrome library, matching the Subsonic musicFolderId already piped through libraryFilterParams(). - Fix CachedImage cache-key mismatch on ComposerDetail: a Last.fm header image was stored under the Subsonic cover-art key, aliasing cache entries and risking cross-source pollution. - Consolidate the two contradictory composer-imagery comments in Composers.tsx into a single accurate one (the older one referenced an Images toggle that was never implemented). - Align openLink toast duration with ArtistDetail (1500ms -> 2500ms). * fix(composer): keep bio across scope changes, add share, degrade gracefully Three remaining items from the latest review pass on the composer flow. 1. Bio survives a music-library scope change. The previous fix added musicLibraryFilterVersion to the load effect, but that effect also did setInfo(null) while the getArtistInfo effect still depended on [id] alone — so a scope bump on the open page wiped the bio without re-fetching it. Move the info reset into the bio effect (keyed on id) and out of the load effect: the album grid still refreshes on scope change; the Last.fm header image and biography survive untouched, since both are library-independent. 2. Composers join the share pipeline as a first-class entity kind. Extend EntityShareKind with 'composer' (and isEntityKind), branch applySharePastePayload to validate via getArtist (same id pool) and navigate to /composer/:id, and wire a Share button into ComposerDetail. A pasted composer link now opens the composer view instead of the artist view, matching what was copied. i18n added in all 8 locales (sharePaste.composerUnavailable, openedComposer; composerDetail.shareComposer, unknownComposer). 3. Partial server failure no longer hides the works. If getArtist rejects but ndListAlbumsByArtistRole succeeds, the page used to show full "not found" despite having data to display. Switch the not-found gate to require both empty (`!artist && !albums`) and render a degraded header (placeholder name, no Wikipedia / favourite / share / Last.fm image) when only metadata is missing. * fix(composer): right-click share copies a composer link, not an artist link The context menu opened from a composer card / row uses type='artist' because every composer-action (radio, favourite, rating, add-to-playlist) is identical to the artist counterpart — they share an id space and a backend representation. Sharing was the one exception: the "Share Link" entry produced a 'psysonic2-' string with k='artist', so a paste opened /artist/:id even though the user came from /composers. Add an optional shareKindOverride to openContextMenu (default: undefined, preserves existing behaviour) and have the artist-typed branch consult it when calling copyShareLink. Composers.tsx now passes 'composer' on both right-click sites; nothing else changes downstream because the override only affects the share kind. * polish(composer): show Last.fm avatar even without server metadata Two minor follow-ups from the latest review. - ComposerDetail: drop the `&& artist` guard on the header-avatar render path. info?.largeImageUrl can resolve through getArtistInfo(id) without ever needing the SubsonicArtist record, so the previous gate hid a perfectly good Last.fm portrait whenever getArtist failed but the bio fetch succeeded. Replace artist.name with displayName so the alt / aria-label degrade to the localised "Composer" placeholder instead of empty strings. - copyEntityShareLink: doc comment now mentions composer alongside track / album / artist. * fix(composer): derive Last.fm cache key from route id, not from artist record Follow-up to the previous polish: the avatar render path no longer requires `artist` to be populated, but the cache-key gate still did. So when getArtist failed but getArtistInfo returned a Last.fm portrait, the key fell through to coverKey — which is empty without an artist record, re-creating the very aliasing bug the earlier Subsonic-vs-Last.fm fix was meant to close. Switch the Last.fm branch to the route id (same id namespace as the SubsonicArtist record), so the key stays stable whenever Last.fm art is shown, independent of getArtist succeeding. * docs: CHANGELOG + Contributors entry for composer browsing (PR #487) |
||
|
|
b084e96c1f |
fix: prune stale analysis queues and cap loudness backfill window (#480)
* fix(analysis): prune stale backfill jobs and limit prefetch window Drop pending backfill and cpu-seed jobs that are no longer in the active playback queue, and add debug counters for pruned work. Limit loudness backfill scheduling to the current track plus the next five tracks to prevent runaway queue growth in dev sessions. * chore(analysis): remove unused loudness prefetch parameter Drop the now-unused incoming-tracks parameter from the loudness prefetch helper and update internal call sites to match the current queue-window scheduling logic. * docs(changelog): document analysis queue control fix (#480) Add a short 1.46.0 Fixed entry describing stale backfill pruning, the current+5 loudness backfill window cap, and debug prune counters for diagnostics. * docs(contributors): add cucadmuh entry for PR #480 Logs the analysis-queue prune + loudness backfill window cap in the Settings → System → Contributors list. |
||
|
|
8d8c1aa8a3 |
Environment upgrade & hot-cache playback (#463)
* chore: upgrade dependencies and migrate playback to rodio 0.22 Bump npm and Rust crates; adapt symphonia decoding, ringbuf 0.5, lofty tags, and discord-rich-presence usage. Use native rodio Player/MixerDeviceSink and cpal device descriptions; drop the unused cpal patch. Align Vite 8 build targets and chunking; remove redundant dynamic imports and fix hot-cache debug logging imports. * perf(build): lazy-load routes and restore default chunk warnings Lazy-load all routed pages with React.lazy to shrink the main bundle; wrap root Routes in Suspense for lazy Login. Drop chunkSizeWarningLimit override so Vite uses the default 500 kB threshold. * fix(windows): tray double-click without spurious menu; clean unused import Disable tray menu on left mouse-up on Windows so a double-click to hide the main window does not immediately reopen the context menu (tray-icon default menu_on_left_click). Gate std::fs in app_api/core behind cfg(linux) for /proc-only code so Windows builds stay warning-free. * fix(sidebar): preserve new-releases read state under storage cap When merging seen album ids, keep the current newest sample first so the 500-id localStorage limit does not truncate freshly marked reads and bring back the unread badge. * fix(audio): hot-cache replay, analysis no-op skips, playback source UI Retain stream_completed_cache across audio_stop so end-of-queue replay can use RAM promote or disk hot file instead of re-ranging HTTP. Add cpu_seed_redundant_for_track gate before file/bytes seeds and local-file spawn; emit analysis:waveform-updated only on Upserted. Ranged/legacy promote checks generation after await before filling the slot. Frontend: promote on same-track and cold resume; set currentPlaybackSource on resume, queue undo restore, and gapless track switch so cache/stream icons stay accurate. Import tauri::Manager for try_state in audio_play. * fix(ts): narrow activeServerId for hot-cache promote calls promoteCompletedStreamToHotCache expects a string; bind non-null server ids in repeat-one, playTrack prev/same-track, and cold resume paths so tauri production build (tsc) succeeds. * fix(player): handle same-track hot-cache promote promise chain Add .catch for promoteCompletedStreamToHotCache → runPlayTrackBody so sync throws and unexpected rejections do not surface as unhandled in DevTools; reset defer-hot-cache prefetch and isPlaying on failure. * chore(nix): sync npmDepsHash with package-lock.json * chore(release): finalize 1.46.0 CHANGELOG with PR #463 links Document the release with full GitHub PR #463 on every subsection so entries stay attributable if sections are reordered. Fix ContextMenu lines where dynamic imports were accidentally merged onto one line. * docs(contributors): credit cucadmuh for #463 |
||
|
|
a6cc2e2ad4 |
perf(linux): WebKit probe, throttled progress IPC, snapshot playback UI (#452)
* feat(linux): optional native GDK for Nix gdk-session Introduce PSYSONIC_ALLOW_NATIVE_GDK so main skips the default GDK_BACKEND=x11 pin when the Nix gdk-session wrapper sets the flag. Remove GDK_BACKEND from the npm tauri:dev script so it does not override nix develop defaults. * fix(ui): portal server switch menu above sidebar Main column stacks below the sidebar (layout z-index), so an in-tree dropdown could never win over the left nav. Render the menu via createPortal to document.body with fixed coordinates, matching the library scope picker. * feat(perf): add mainstage probe controls and cut WebKit repaint load Add a dedicated performance probe surface for mainstage/home toggles and wire Linux CPU diagnostics to isolate expensive UI paths. Tune waveform drawing and Home artwork clipping/windowing so visible content loads immediately while reducing WebKit compositor pressure during playback. * fix(perf): stop hero rotation when section is off-screen Gate hero auto-rotation and backdrop crossfade by real viewport visibility using the actual scrolling ancestor. This prevents periodic 10-second CPU spikes from hidden hero updates while preserving normal behavior when the hero is visible. * fix(perf): isolate player progress updates from mainstage diagnostics Add probe toggles for PlayerBar waveform and live progress UI updates to confirm playback progress churn as the main CPU driver. Restore Home artwork quality defaults and keep visual-degradation modes opt-in via debug flags only. * fix(hero): resume background and autoplay after viewport return Re-check hero visibility on focus/visibility changes and add a short recovery poll while off-screen so missed scroll/RAF events cannot leave hero animation paused. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(perf): decouple playback progress from mainstage compositing pressure Throttle audio progress delivery and route live seekbar timing through a lightweight progress channel to cut focus-time WebKit CPU spikes. Add focused diagnostics in Performance Probe and restore hero/waveform behavior so visuals remain stable while profiling. * fix(debug): open performance probe with Ctrl+Shift+D Replace logo-triggered opening with a keyboard shortcut and keep logo purely decorative to avoid accidental probe activation. * docs(changelog): document experiment/performance probe and playback work Add an [Unreleased] section for the performance probe, throttled audio progress IPC, snapshot-based live UI updates, WaveformSeek scheduling over the same canvas bar renderer, Hero/Home rail fixes, and Linux/Nix GDK dev ergonomics. * perf(linux): add WebKit probe, throttle progress IPC, snapshot playback UI Ship Performance Probe (Ctrl+Shift+D), Rust-throttled audio:progress, a playback progress snapshot channel with coarse Zustand timeline commits, Linux /proc CPU readout for the probe, Hero and Home rail artwork fixes, Tracks SongRail windowing parity, MPRIS cleanup, gated perf counters, and WaveformSeek paused-seek correctness. Documented in CHANGELOG for PR #452. * docs(changelog): fold perf work into 1.45.0 and refresh date Drop the separate 1.45.1 heading; keep PR #452 notes under 1.45.0 Added and set the section date to 2026-05-04. Restore the safety preface before the versioned sections. * docs(changelog): order 1.45.0 Added entries by PR number Sort the 1.45.0 release notes so subsections follow ascending PR id (390 through 452), with PR #452 last. --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
1e05180418 |
feat(shortcuts): action registry + dynamic CLI help + new input targets (#435)
* feat(shortcuts): unify action-driven shortcut and CLI routing Centralize shortcut action metadata in one TypeScript registry and route keyboard, global shortcut, mini-window, and CLI inputs through shared runtime handlers. Keep CLI as an abstract transport layer by emitting player-command payloads without depending on shortcut definitions. * feat(shortcuts): generate CLI action help from shortcut registry Move no-arg player commands and their descriptions into the central action registry so CLI parsing and --player help are derived dynamically from one source of truth. Also route runtime action execution through the registry and remove duplicated shortcut runtime handling. * feat(shortcuts): add new input actions and hidden F1 help binding Add the requested input actions (search, advanced search, sidebar, mute, equalizer, repeat, now playing, lyrics, favorite current track) to the central shortcut action registry and wire runtime handlers for sidebar/equalizer toggles. Keep Help bound to F1 by default while hiding it from Settings input lists, and backfill persisted keybindings with new defaults so F1 works for existing users. Requested by @zunoz (Discord community). |
||
|
|
2e9618cf54 |
fix(audio): Windows playback stutter under GPU load (#334) (#426)
* fix(audio): promote WASAPI render thread to MMCSS Pro Audio on Windows
Wraps the outermost audio source in a `PriorityBoostSource` that calls
`AvSetMmThreadCharacteristicsW("Pro Audio")` on its first sample. The
cpal output-stream callback runs `Source::next` on the WASAPI render
thread, which is otherwise normal-priority and gets preempted under
WebView2 / DWM / GPU pressure — producing the audible click/stutter
reported in issue #334. No-op on Linux/macOS (PipeWire/rtkit and
CoreAudio promote their audio threads externally).
* fix(build): repair Windows compile after audio split + lib decompose
Two pre-existing build breakers on Windows that surfaced after the
`use super::*;` cleanup (
|
||
|
|
9cc74a7f88 |
style(tauri): drop redundant use super::*; imports
Five lib_commands submodules either had `use super::*;` duplicated during the split (core.rs, offline.rs, mini.rs — leftover line 3) or didn't reference the parent scope at all (navidrome.rs, bandsintown.rs). Removing them silences all five `unused import` warnings; cargo check is now warning-free. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
cfeec22c82 |
refactor(tauri): decompose lib command surface into domain modules
Split the large lib command module into nested app_api, cache, sync, and ui submodules while preserving command signatures and behavior. This keeps command responsibilities isolated and makes further extraction safer. |