github Psychotoxical/psysonic app-v1.2.0
Psysonic v1.2.0

latest releases: app-v1.34.6, v1.34.6, app-v1.34.5...
25 days ago

Added

  • Rust Audio Engine (replaces Howler.js)
  • New native audio backend built in Rust using rodio. Audio is now decoded and played entirely in the Tauri backend — no more reliance on the WebView's element or GStreamer pipeline quirks.
  • Tauri commands: audio_play, audio_pause, audio_resume, audio_stop, audio_seek, audio_set_volume.
  • Frontend events: audio:playing (with duration), audio:progress (every 500 ms), audio:ended, audio:error.
  • Generation counter (AtomicU64) ensures stale downloads from skipped tracks are cancelled immediately and do not emit events.
  • Wall-clock position tracking (seek_offset + elapsed) instead of sink.empty() (unreliable in rodio 0.19 for VBR MP3). audio:ended fires after two consecutive ticks within 1 second of the track end — avoids false positives near the end without adding latency.
  • Seek via sink.try_seek() — no pause/play cycle, no spurious ended events.
  • Volume clamped to [0.0, 1.0] on every call.
  • Playback Persistence & Cold-Start Resume
  • currentTrack, queue, queueIndex, and currentTime are now persisted to localStorage via Zustand partialize.
  • On app restart with a previously loaded track, clicking Play resumes from the saved position without losing the queue.
  • Position priority: server play queue position (if > 0) takes precedence over the locally saved value, so cross-device resume works correctly.
  • Random Mix — Genre Filter & Blacklist
  • Exclude audiobooks & radio plays toggle: filters out songs whose genre, title, or album match a hardcoded list (Hörbuch, Hörspiel, Audiobook, Spoken Word, Podcast, Krimi, Thriller, Speech, Fantasy, Comedy, Literature, and more).
  • Custom genre blacklist: add any genre keyword via the collapsible chip panel on the Random Mix page or in Settings → Random Mix. Persisted across sessions.
  • Clickable genre chips in the tracklist: clicking an unblocked genre tag adds it to the blacklist instantly with 1.5 s visual feedback. Blocked genres are shown in red.
  • Blacklist filter checks song.genre, song.title, and song.album to catch mislabelled tracks.
  • Random Mix — Super Genre Mix
  • Nine pre-defined Super Genres (Metal, Rock, Pop, Electronic, Jazz, Classical, Hip-Hop, Country, World) appear as buttons, auto-generated from the server's genre list — only genres with at least one matching keyword are shown.
  • Selecting a Super Genre fetches up to 50 songs distributed across all matched sub-genres in parallel, then shuffles the result.
  • Progressive rendering: the tracklist appears as soon as the first genre request returns — users with large Metal/Rock libraries no longer stare at a spinner for the entire fetch. A small inline spinner next to the title indicates that more genres are still loading.
  • "Load 10 more" button: fetches 10 additional songs from the same matched genres and appends them to the play queue.
  • Random playlist is automatically hidden while a Genre Mix is active.
  • Fetch timeout raised to 45 seconds per genre request (was 15 s) and Promise.allSettled used so a single slow/failing genre does not abort the entire mix.
  • Queue Panel
  • Shuffle button in the queue header: Fisher-Yates shuffles all queued tracks while keeping the currently playing track at position 0. Button is disabled when the queue has fewer than 2 entries.

UI / UX

  • LiveSearch keyboard navigation: arrow keys navigate the dropdown, Enter selects the highlighted item or navigates to the full search results page, Escape closes the dropdown.
  • Multi-line tooltip support: add data-tooltip-wrap attribute to any element with data-tooltip to enable line-wrapping (uses white-space: pre-line + \n in the string). Respects a 220 px max-width.
  • Genre column info icon in Random Mix tracklist header: hover tooltip explains the clickable-genre-to-blacklist feature.
  • Update link in the sidebar now uses Tauri Shell plugin open() to launch the system browser correctly — has no effect inside a Tauri WebView.

Fixed

  • Songs skipping immediately (root cause: Tauri v2 IPC maps Rust snake_case parameters to camelCase on the JS side — duration_hint must be durationHint). All invoke() calls updated.
  • Play button doing nothing after restart: currentTrack was null after restart (not persisted). Fixed by adding it to partialize.
  • Position not restored after restart: initializeFromServerQueue overwrote the local saved position with the server value even when the server reported 0. Now falls back to the localStorage value when the server position is 0.
  • Genre Mix blank on Metal/Rock: a single timed-out genre request caused Promise.all to reject the entire mix. Replaced with Promise.allSettled + 45 s timeout; partial results are shown immediately.
  • Tooltip z-index: tooltips in the main content area were rendered behind the queue panel. Fixed by giving .main-content z-index: 1, establishing a stacking context above the queue (which sits later in DOM order).
  • Sidebar title clipping: "Psysonic" brand text was truncated at narrow viewport widths. Minimum sidebar width raised from 180 px to 200 px.

Changed

  • Audio architecture: Howler.js removed. All audio state (isPlaying, isAudioPaused, currentTime, duration) is now driven by Tauri events from the Rust engine rather than Howler callbacks.
  • Random Mix layout: Filter/blacklist panel and Genre Mix buttons are now combined in a two-column card at the top of the page instead of being scattered across the page.
  • Hardcoded genre blacklist extended with: Fantasy, Comedy, Literature.
  • getRandomSongs now accepts an optional timeout parameter (default 15 s) so callers can pass a longer value for large-library scenarios.

Don't miss a new psysonic release

NewReleases is sending notifications on new releases.