feat: v1.29.0 — Radio fast-start, seek fix, OGG, error toasts, contributors

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>
This commit is contained in:
Psychotoxical
2026-04-02 22:17:55 +02:00
parent 95283d792b
commit 7263d93d42
20 changed files with 6264 additions and 3900 deletions
+45
View File
@@ -30,6 +30,50 @@ fn exit_app(app_handle: tauri::AppHandle) {
app_handle.exit(0);
}
/// Restart after an in-app update.
///
/// `relaunch()` from tauri-plugin-process spawns the new process while the old one
/// is still alive, so the single-instance plugin in the new process sees the old
/// socket and kills itself immediately (endless loop).
///
/// This command instead:
/// 1. Spawns a shell snippet that waits ~1.5 s and then opens the app again —
/// by then the old process and its single-instance socket are fully gone.
/// 2. Calls `app.exit(0)` directly, which bypasses `on_window_event`
/// prevent_close() and releases the single-instance lock immediately.
#[tauri::command]
fn relaunch_after_update(app: tauri::AppHandle) {
let exe = std::env::current_exe().unwrap_or_default();
#[cfg(target_os = "windows")]
{
let exe_str = exe.to_string_lossy().to_string().replace('\'', "''");
let script = format!(
"Start-Sleep -Milliseconds 1500; Start-Process '{exe_str}'"
);
let _ = std::process::Command::new("powershell")
.args(["-WindowStyle", "Hidden", "-NonInteractive", "-Command", &script])
.spawn();
}
#[cfg(target_os = "macos")]
{
// exe lives at Psysonic.app/Contents/MacOS/psysonic — walk up to the bundle.
let bundle = exe
.parent() // MacOS/
.and_then(|p| p.parent()) // Contents/
.and_then(|p| p.parent()); // Psysonic.app
if let Some(bundle_path) = bundle {
let escaped = bundle_path.to_string_lossy().replace('"', "\\\"");
let _ = std::process::Command::new("sh")
.args(["-c", &format!("sleep 1.5 && open \"{escaped}\"")])
.spawn();
}
}
app.exit(0);
}
/// Proxy Last.fm API calls through Rust/reqwest to avoid WebView networking restrictions.
/// `params` is a list of [key, value] pairs (method must be included).
/// If `sign` is true an api_sig is computed. If `get` is true, a GET request is made.
@@ -498,6 +542,7 @@ pub fn run() {
download_track_offline,
delete_offline_track,
get_offline_cache_size,
relaunch_after_update,
])
.run(tauri::generate_context!())
.expect("error while running Psysonic");