From 1ff4f1ef0f7060b88c28f2c800d62cbf0246baa8 Mon Sep 17 00:00:00 2001 From: Maxim Isaev Date: Tue, 14 Apr 2026 22:17:29 +0300 Subject: [PATCH] feat(audio): add Opus playback via symphonia-adapter-libopus Wire a lazy CodecRegistry built with symphonia::default::register_enabled_codecs plus symphonia_adapter_libopus::OpusDecoder for SizedDecoder and radio decoders. Raise package rust-version to 1.89 to match the MSRV required by symphonia-adapter-libopus. --- src-tauri/Cargo.lock | 30 ++++++++++++++++++++++++++++++ src-tauri/Cargo.toml | 3 ++- src-tauri/src/audio.rs | 19 +++++++++++++++---- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index bb1be099..6692f65f 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -619,6 +619,15 @@ dependencies = [ "libloading 0.8.9", ] +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "cocoa" version = "0.24.1" @@ -3078,6 +3087,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "opusic-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3280fe5b6f97ac1a35a0ac003e2fb0b92f8e4bdf2b2057e1bf9b87acca5696" +dependencies = [ + "cmake", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -3581,6 +3599,7 @@ dependencies = [ "serde_json", "souvlaki", "symphonia", + "symphonia-adapter-libopus", "tauri", "tauri-build", "tauri-plugin-dialog", @@ -4739,6 +4758,17 @@ dependencies = [ "symphonia-metadata", ] +[[package]] +name = "symphonia-adapter-libopus" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d17450685dda0e87467eddf3e0f9c0b2a1707fc5c3234c111f70d46c6e4494" +dependencies = [ + "log", + "opusic-sys", + "symphonia-core", +] + [[package]] name = "symphonia-bundle-flac" version = "0.5.5" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index c0f0787f..42ecf5a5 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -7,7 +7,7 @@ license = "" repository = "" default-run = "psysonic" edition = "2021" -rust-version = "1.77.2" +rust-version = "1.89" [lib] name = "psysonic_lib" @@ -46,6 +46,7 @@ url = "2" thread-priority = "1" lofty = "0.22" id3 = "1.16.4" +symphonia-adapter-libopus = "0.2.7" [target.'cfg(unix)'.dependencies] libc = "0.2" diff --git a/src-tauri/src/audio.rs b/src-tauri/src/audio.rs index c26c61f1..e049ebe6 100644 --- a/src-tauri/src/audio.rs +++ b/src-tauri/src/audio.rs @@ -1,5 +1,5 @@ use std::io::{Cursor, Read, Seek, SeekFrom}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, OnceLock}; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; use std::time::{Duration, Instant}; #[cfg(unix)] @@ -13,7 +13,7 @@ use rodio::source::UniformSourceIterator; use serde::Serialize; use symphonia::core::{ audio::{AudioBufferRef, SampleBuffer, SignalSpec}, - codecs::{DecoderOptions, CODEC_TYPE_NULL}, + codecs::{CodecRegistry, DecoderOptions, CODEC_TYPE_NULL}, formats::{FormatOptions, FormatReader, SeekMode, SeekTo}, io::{MediaSource, MediaSourceStream, MediaSourceStreamOptions}, meta::MetadataOptions, @@ -682,11 +682,22 @@ impl Drop for RadioLiveState { // [features] // fdk-aac = ["dep:symphonia-adapter-fdk-aac"] +/// Symphonia’s default codec set for our enabled features, plus Opus via libopus. +fn psysonic_codec_registry() -> &'static CodecRegistry { + static REGISTRY: OnceLock = OnceLock::new(); + REGISTRY.get_or_init(|| { + let mut registry = CodecRegistry::new(); + symphonia::default::register_enabled_codecs(&mut registry); + registry.register_all::(); + registry + }) +} + fn try_make_radio_decoder( params: &symphonia::core::codecs::CodecParameters, opts: &DecoderOptions, ) -> Result, symphonia::core::errors::Error> { - symphonia::default::get_codecs().make(params, opts) + psysonic_codec_registry().make(params, opts) } // ── Async HTTP Download Task ────────────────────────────────────────────────── @@ -980,7 +991,7 @@ impl SizedDecoder { .zip(track.codec_params.n_frames) .map(|(base, frames)| base.calc_time(frames)); - let mut decoder = symphonia::default::get_codecs() + let mut decoder = psysonic_codec_registry() .make(&track.codec_params, &DecoderOptions::default()) .map_err(|e| { eprintln!("[psysonic] codec init failed: {e}");