diff --git a/CHANGELOG.md b/CHANGELOG.md index ef388256..6fa5383c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.25.1] - 2026-04-01 + +### Fixed + +- **Opus playback** *(reported by [@powerfool](https://github.com/powerfool))*: `.opus` files were silently skipped because Symphonia has no Opus decoder. Opus tracks are now transcoded server-side to FLAC via the Subsonic `format=flac` parameter, avoiding any additional lossy encoding step. +- **Single-instance enforcement** *(reported by [@netherguy4](https://github.com/netherguy4))*: Re-launching the app while it was already running (including minimized to tray) would spawn a new independent process, leading to playback conflicts and state divergence. Integrated `tauri-plugin-single-instance` — subsequent launches are intercepted, the existing window is shown, unminimized, and focused instead. + +--- + ## [1.25.0] - 2026-04-01 ### Added diff --git a/package.json b/package.json index 269b1e82..2599cb3e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "psysonic", - "version": "1.25.0", + "version": "1.25.1", "private": true, "scripts": { "dev": "vite", diff --git a/packages/aur/PKGBUILD b/packages/aur/PKGBUILD index 15ca4bb3..a7bde794 100644 --- a/packages/aur/PKGBUILD +++ b/packages/aur/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Psychotoxic pkgname=psysonic -pkgver=1.25.0 +pkgver=1.25.1 pkgrel=1 pkgdesc="Desktop music player for Subsonic API-compatible servers (Navidrome, Gonic, etc.)" arch=('x86_64') diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a3e3751a..6383640b 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -85,6 +85,18 @@ dependencies = [ "futures-core", ] +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-channel" version = "2.5.0" @@ -198,6 +210,24 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io 2.6.0", + "async-lock 3.4.2", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.4.1", + "futures-lite 2.6.1", + "rustix 1.1.4", +] + [[package]] name = "async-recursion" version = "1.1.1" @@ -1059,6 +1089,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + [[package]] name = "enumflags2" version = "0.7.12" @@ -3377,6 +3413,7 @@ dependencies = [ "tauri-plugin-fs", "tauri-plugin-global-shortcut", "tauri-plugin-shell", + "tauri-plugin-single-instance", "tauri-plugin-store", "tauri-plugin-window-state", "tokio", @@ -4279,8 +4316,8 @@ dependencies = [ "pollster", "thiserror 1.0.69", "windows 0.44.0", - "zbus", - "zvariant", + "zbus 3.15.2", + "zvariant 3.15.2", ] [[package]] @@ -4827,6 +4864,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "tauri-plugin-single-instance" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc61e4822b8f74d68278e09161d3e3fdd1b14b9eb781e24edccaabf10c420e8c" +dependencies = [ + "serde", + "serde_json", + "tauri", + "thiserror 2.0.18", + "tracing", + "windows-sys 0.60.2", + "zbus 5.14.0", +] + [[package]] name = "tauri-plugin-store" version = "2.4.2" @@ -6569,12 +6621,12 @@ version = "3.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" dependencies = [ - "async-broadcast", + "async-broadcast 0.5.1", "async-executor", "async-fs", "async-io 1.13.0", "async-lock 2.8.0", - "async-process", + "async-process 1.8.1", "async-recursion", "async-task", "async-trait", @@ -6599,9 +6651,44 @@ dependencies = [ "uds_windows", "winapi", "xdg-home", - "zbus_macros", - "zbus_names", - "zvariant", + "zbus_macros 3.15.2", + "zbus_names 2.6.1", + "zvariant 3.15.2", +] + +[[package]] +name = "zbus" +version = "5.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc" +dependencies = [ + "async-broadcast 0.7.2", + "async-executor", + "async-io 2.6.0", + "async-lock 3.4.2", + "async-process 2.5.0", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener 5.4.1", + "futures-core", + "futures-lite 2.6.1", + "hex", + "libc", + "ordered-stream", + "rustix 1.1.4", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 0.7.15", + "zbus_macros 5.14.0", + "zbus_names 4.3.1", + "zvariant 5.10.0", ] [[package]] @@ -6615,7 +6702,22 @@ dependencies = [ "quote", "regex", "syn 1.0.109", - "zvariant_utils", + "zvariant_utils 1.0.1", +] + +[[package]] +name = "zbus_macros" +version = "5.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "zbus_names 4.3.1", + "zvariant 5.10.0", + "zvariant_utils 3.3.0", ] [[package]] @@ -6626,7 +6728,18 @@ checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" dependencies = [ "serde", "static_assertions", - "zvariant", + "zvariant 3.15.2", +] + +[[package]] +name = "zbus_names" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" +dependencies = [ + "serde", + "winnow 0.7.15", + "zvariant 5.10.0", ] [[package]] @@ -6726,7 +6839,21 @@ dependencies = [ "libc", "serde", "static_assertions", - "zvariant_derive", + "zvariant_derive 3.15.2", +] + +[[package]] +name = "zvariant" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 0.7.15", + "zvariant_derive 5.10.0", + "zvariant_utils 3.3.0", ] [[package]] @@ -6739,7 +6866,20 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "zvariant_utils", + "zvariant_utils 1.0.1", +] + +[[package]] +name = "zvariant_derive" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "zvariant_utils 3.3.0", ] [[package]] @@ -6752,3 +6892,16 @@ dependencies = [ "quote", "syn 1.0.109", ] + +[[package]] +name = "zvariant_utils" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.117", + "winnow 0.7.15", +] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 8ed67810..8afcf6c1 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "psysonic" -version = "1.25.0" +version = "1.25.1" description = "Psysonic Desktop Music Player" authors = [] license = "" @@ -22,6 +22,7 @@ tauri-build = { version = "2", features = [] } [dependencies] tauri = { version = "2", features = ["tray-icon", "image-png"] } +tauri-plugin-single-instance = "2" tauri-plugin-shell = "2" tauri-plugin-global-shortcut = "2" tauri-plugin-store = "2" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index f6bccbe6..3e89ef25 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -30,6 +30,7 @@ "core:window:allow-set-fullscreen", "core:window:allow-is-fullscreen", "core:window:allow-create", - "core:webview:allow-create-webview-window" + "core:webview:allow-create-webview-window", + "single-instance:default" ] } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e832f841..9de5b533 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -302,6 +302,12 @@ pub fn run() { .plugin(tauri_plugin_store::Builder::default().build()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_fs::init()) + .plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| { + let window = app.get_webview_window("main").expect("no main window"); + let _ = window.show(); + let _ = window.unminimize(); + let _ = window.set_focus(); + })) .setup(|app| { // ── System tray ─────────────────────────────────────────────── diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 6e507f73..8dd86b32 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Psysonic", - "version": "1.25.0", + "version": "1.25.1", "identifier": "dev.psysonic.player", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/api/subsonic.ts b/src/api/subsonic.ts index b719881f..4ba77794 100644 --- a/src/api/subsonic.ts +++ b/src/api/subsonic.ts @@ -325,7 +325,7 @@ export async function reportNowPlaying(id: string): Promise { } // ─── Stream URL ─────────────────────────────────────────────── -export function buildStreamUrl(id: string): string { +export function buildStreamUrl(id: string, suffix?: string): string { const { getBaseUrl, getActiveServer } = useAuthStore.getState(); const server = getActiveServer(); const baseUrl = getBaseUrl(); @@ -336,6 +336,11 @@ export function buildStreamUrl(id: string): string { u: server?.username ?? '', t: token, s: salt, v: '1.16.1', c: 'psysonic', f: 'json', }); + + if (suffix === 'opus') { + p.set('format', 'flac'); // Transcode OPUS to FLAC since Rust/Symphonia has no Opus decoder + } + return `${baseUrl}/rest/stream.view?${p.toString()}`; }