The previous optimization skipped all rate-switch logic in standard mode,
but left the stream at e.g. 88.2 kHz if Hi-Res was toggled off mid-session.
Fix: store device_default_rate (from cold-start open) in AudioEngine.
In audio_play, compute target_rate = hi_res ? output_rate : device_default_rate
and reopen only when target differs from current — no-op cost when already
at the right rate (the common case in pure standard mode).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Stream switch: skip entirely when hi_res_enabled=false. Previously the
engine compared 44100 vs. device-default 48000 Hz and re-opened the device
on every track start — unnecessary IPC + PipeWire reconfigure in safe mode.
MSS buffer: 512 KB in standard mode (was 4 MB). For in-memory MP3/AAC
the large buffer caused eager allocation competing with playback startup.
4 MB is kept only for hi-res (high-bitrate FLAC benefits from fewer reads).
ThreadPriority: no longer set Max at audio-stream-thread startup. Max is
now applied only when a hi-res reopen is requested, keeping the scheduler
budget for the actual decode threads during standard playback.
audio_preload: 8 s throttle delay + gen guard so background pre-fetch does
not compete with the decode/sink-feed cycle of the just-started track.
needs_prefill: now gated on hi_res_enabled to make the condition explicit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fetches album artwork from the iTunes Search API and passes the URL
directly to Discord's large_image field. Subsonic cover URLs require
auth and can't be used by Discord directly.
- 3-strategy search: exact quoted → relaxed → track title fallback
- 1-hour in-memory cache per artist|album key
- iTunes search runs in tokio::task::spawn_blocking (reqwest::blocking
must not run on the Tokio async executor)
- artwork_cache wrapped in Arc<Mutex<...>> for cross-thread sharing
- Fallback to the pre-uploaded "psysonic" asset when no match found
- Cargo.toml: blocking feature alongside rustls-tls
Co-Authored-By: kilyabin <kilyabin@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The native-tls backend (macOS Security framework) was failing with
'bad protocol version' when connecting to HTTPS music servers.
Switching to rustls (statically linked, no system dependency) fixes
playback on macOS and ensures consistent TLS 1.2/1.3 support across
all platforms.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Internet Radio full release (HTML5 engine, card UI, RadioDirectoryModal,
cover upload), Backup/Restore, Albums year filter, Statistics Library
Insights (playtime/genres/formats), Playlist cover upload, resizable
tracklist columns for Playlists & Favorites, crossfade fine control,
Settings Storage tab redesign, various fixes and UI polish.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Incorporates PRs #38 (nisarg-78), #42#43#44 (JulianNymark) with fixes:
Audio / Playback
- Artist Radio starts immediately from fast getTopSongs (local library);
getSimilarSongs2 (Last.fm) enriches queue in background — no more wait
- Fix seek audio glitch: EqualPowerFadeIn only resets to zero-gain on
seeks to track start (<100 ms); all other seeks resume at unity gain
- OGG/Vorbis container support via symphonia-format-ogg (PR #42)
- Human-readable audio error messages in SizedDecoder (PR #44)
- Audio playback errors shown as themed toast notifications (PR #43)
Queue / Radio
- Infinite Queue: proactive load of 5 tracks (was 25) when ≤2 remain
- Radio: proactive reload at ≤2 remaining tracks, independent of
Infinite Queue setting — radio no longer stops at last track
- Fix: clicking Start Radio multiple times no longer stacks duplicates
- Fix: Start Radio on artist keeps current song playing
- Manual tracks always appear before Radio, Radio before Auto-added
- Queue separators: "— Radio —" and "— Auto —" dividers
- Fix: radio proactive load now works even when songs lack artistId
(uses currentRadioArtistId module var as fallback)
UI / UX
- Click synced lyrics lines to seek (PR #38)
- Volume scroll wheel on volume slider (PR #38)
- Lyrics: active / completed / upcoming visual states (PR #38)
- Shared toast utility (src/utils/toast.ts) replaces inline toast fn
- Auto-updater: relaunch_after_update Rust command exits first to
release single-instance lock before spawning new process
About / Credits
- nisarg-78 and JulianNymark added to contributors (since v1.29.0)
- netherguy4 added as Special Thanks for feature ideas and feedback
- i18n: aboutSpecialThanksLabel in all 5 languages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
current_sample_rate was initialized to 44100, causing every track to be
resampled down to 44.1 kHz. Setting it to 0 disables resampling until
the actual track rate is known, so songs play at their native sample rate.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replay gain was not working because track objects were created manually without
including replay gain metadata from the Subsonic API response.
Changes:
- Add songToTrack() helper function to properly map SubsonicSong to Track with
replayGainTrackDb, replayGainAlbumDb, and replayGainPeak fields
- Add audio_update_replay_gain Tauri command for dynamic volume recalculation
when replay gain settings change mid-playback
- Add updateReplayGainForCurrentTrack() to recalculate volume when toggling
replay gain setting
- Fetch fresh track data on cold resume (app relaunch) to ensure replay gain
values are current from server
- Update all files that create track objects to use songToTrack()
Fixes issue where toggling replay gain ON/OFF or changing between track/album
mode had no effect on currently playing or newly played tracks.
- layout: overflow:hidden on .app-shell + min-height:0 on sidebar/main/queue
prevents player bar from being pushed off-screen when window is resized
below OS minHeight constraint (ignored by some Linux WMs)
- lib.rs: pass main window HWND to souvlaki PlatformConfig on Windows so
SMTC hooks into the existing Win32 message loop instead of creating its
own — fixes media keys on Windows without crashing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SMTC init via souvlaki requires a valid HWND + COM message loop, which
are not available in setup(). Guarded behind #[cfg(not(target_os = "windows"))].
mpris_set_metadata / mpris_set_playback no-op on Windows via the None branch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
### Added
- SVG logo with theme-adaptive gradient in sidebar (full wordmark + P-icon for collapsed state)
- Player bar: song title/artist marquee scroll on overflow
- Player bar: live volume percentage tooltip on slider hover
### Changed
- Sidebar collapse button moved to right-edge hover tab
- Player bar: fixed 320px track info width, increased waveform margins
- Settings: Server tab opens by default
- Crossfade: experimental badge removed (stable)
- Help page: Lyrics/Keybindings/Font entries added, theme count corrected
### Fixed
- Global shortcuts double-fire (Rust-side ShortcutMap idempotency fix)
- W98 theme: comprehensive contrast fixes for navy hover backgrounds
- Help page: removed orphaned translation key under Playback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Themes:
- Add Poison (phosphor green on charcoal), Nucleo (warm brass light), Classic Winamp (LCD glow, Winamp yellow/orange) themes
- Overhaul Psychowave — refined deep violet, no longer WIP
- Reorganise ThemePicker into groups: Catppuccin, Nord, Retro, Tokyo Night, Psysonic Themes
- Add --volume-accent CSS var for per-theme volume slider colour override
Queue:
- Full toolbar redesign with centred round buttons (Shuffle/Save/Load/Clear/Gapless/Crossfade)
- Crossfade popover with 1–10 s range slider, right-aligned to avoid viewport overflow
- Queue header: title + count + duration inline in accent colour, close button removed
- Tech info (codec/bitrate) as frosted-glass overlay badge on cover art
UI:
- CoverLightbox shared component for album cover (AlbumHeader) and artist avatar (ArtistDetail)
- NowPlayingDropdown: separate spinning/loading state — button always clickable, icon spins 600 ms min
- Settings: "Experimental" badge on Crossfade and Gapless toggles
- Help page: 2-column grid layout, new Crossfade & Gapless Q&A entry, updated theme/language entries
i18n:
- Full French (fr) and Dutch (nl) translations across all namespaces
- Language selector sorted alphabetically (nl, en, fr, de)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
### Audio
- Replay Gain support (track + album mode, configurable pre-gain, hard limiter)
- Crossfade between tracks (configurable duration 1–10 s)
- Gapless preloading ⚠️ experimental/alpha — enable in Settings → Playback
- Atomic sink swap: old track plays until new one is decoded, then stops cleanly
### UI / UX
- Settings redesigned with tab navigation (General, Playback, About)
- Changelog viewer in Settings → About with collapsible version entries
- Download Folder Modal: choose folder + "remember" checkbox per-download
- EQ popup now accessible directly from the Player Bar
- "Also Featured On" section on Artist pages for non-album appearances
### Fixes
- Bundle identifier changed from dev.psysonic.app → dev.psysonic.player (fixes macOS warning)
- Version sync: all four version files (package.json, Cargo.toml, tauri.conf.json, PKGBUILD) now at 1.6.0
### Known Issues
- FLAC seeking via waveform seekbar does not work (MP3/OGG unaffected) — under investigation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
### 10-Band Graphic Equalizer
- Full EQ in Rust audio engine via EqSource<S> biquad peak filters
- 10 built-in presets + custom preset save/delete
- EqSource::try_seek() implemented — also fixes waveform seek which broke
silently when EQ was introduced (rodio returned SeekError::NotSupported)
- EQ state persisted in localStorage, synced to Rust on startup
### Connection Indicator
- LED in header (green/red/pulsing) with server name and LAN/WAN label
- Offline overlay with retry button when server is unreachable
- useConnectionStatus hook
### New App Icon
- New logo (logo-psysonic.png) applied across Login, Sidebar, Settings,
README and all Tauri platform icons (Windows, macOS, Linux, Android, iOS)
### Now Playing Page
- New /now-playing route added
### Fixes
- WaveformSeek: mousemove/mouseup on window to fix drag outside canvas
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Queue: year added to now-playing meta, cover enlarged to 90px + top-aligned, default width 340px
- Artist pages: Last.fm/Wikipedia open in system browser with button label feedback
- README: AUR badge + install section, Nord theme mention, in-app browser bullet removed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
### Audio Engine
- Replace Howler.js with native Rust/rodio backend (src-tauri/src/audio.rs)
- audio_play/pause/resume/stop/seek/set_volume Tauri commands
- Generation counter cancels stale in-flight downloads on skip
- Wall-clock position tracking; audio:ended after 2 ticks near end
### Playback Persistence
- Persist currentTrack, queue, queueIndex, currentTime to localStorage
- Cold-start resume: play + seek to saved position on app restart
- Position priority: server queue > localStorage
### Random Mix
- Genre blacklist: excludeAudiobooks toggle + custom chip-based blacklist
- Clickable genre chips in tracklist to blacklist on the fly
- Super Genre Mix: 9 genres, progressive rendering, Load 10 more
- Promise.allSettled + 45s timeout for large Metal/Rock libraries
- Two-column filter panel at top of page
### Queue & UI
- Shuffle button in queue header (Fisher-Yates, keeps current track at 0)
- LiveSearch arrow key / Enter / Escape keyboard navigation
- Multi-line tooltip support (data-tooltip-wrap attribute)
- Update link uses Tauri shell open() to launch system browser
- Sidebar min-width 180px → 200px (fixes Psysonic title clipping)
- Tooltip z-index fix: main-content z-index:1 over queue panel
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>