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.
This commit is contained in:
Maxim Isaev
2026-04-14 22:17:29 +03:00
parent 915f0143f7
commit 1ff4f1ef0f
3 changed files with 47 additions and 5 deletions
+30
View File
@@ -619,6 +619,15 @@ dependencies = [
"libloading 0.8.9", "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]] [[package]]
name = "cocoa" name = "cocoa"
version = "0.24.1" version = "0.24.1"
@@ -3078,6 +3087,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 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]] [[package]]
name = "ordered-stream" name = "ordered-stream"
version = "0.2.0" version = "0.2.0"
@@ -3581,6 +3599,7 @@ dependencies = [
"serde_json", "serde_json",
"souvlaki", "souvlaki",
"symphonia", "symphonia",
"symphonia-adapter-libopus",
"tauri", "tauri",
"tauri-build", "tauri-build",
"tauri-plugin-dialog", "tauri-plugin-dialog",
@@ -4739,6 +4758,17 @@ dependencies = [
"symphonia-metadata", "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]] [[package]]
name = "symphonia-bundle-flac" name = "symphonia-bundle-flac"
version = "0.5.5" version = "0.5.5"
+2 -1
View File
@@ -7,7 +7,7 @@ license = ""
repository = "" repository = ""
default-run = "psysonic" default-run = "psysonic"
edition = "2021" edition = "2021"
rust-version = "1.77.2" rust-version = "1.89"
[lib] [lib]
name = "psysonic_lib" name = "psysonic_lib"
@@ -46,6 +46,7 @@ url = "2"
thread-priority = "1" thread-priority = "1"
lofty = "0.22" lofty = "0.22"
id3 = "1.16.4" id3 = "1.16.4"
symphonia-adapter-libopus = "0.2.7"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2" libc = "0.2"
+15 -4
View File
@@ -1,5 +1,5 @@
use std::io::{Cursor, Read, Seek, SeekFrom}; 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::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
#[cfg(unix)] #[cfg(unix)]
@@ -13,7 +13,7 @@ use rodio::source::UniformSourceIterator;
use serde::Serialize; use serde::Serialize;
use symphonia::core::{ use symphonia::core::{
audio::{AudioBufferRef, SampleBuffer, SignalSpec}, audio::{AudioBufferRef, SampleBuffer, SignalSpec},
codecs::{DecoderOptions, CODEC_TYPE_NULL}, codecs::{CodecRegistry, DecoderOptions, CODEC_TYPE_NULL},
formats::{FormatOptions, FormatReader, SeekMode, SeekTo}, formats::{FormatOptions, FormatReader, SeekMode, SeekTo},
io::{MediaSource, MediaSourceStream, MediaSourceStreamOptions}, io::{MediaSource, MediaSourceStream, MediaSourceStreamOptions},
meta::MetadataOptions, meta::MetadataOptions,
@@ -682,11 +682,22 @@ impl Drop for RadioLiveState {
// [features] // [features]
// fdk-aac = ["dep:symphonia-adapter-fdk-aac"] // fdk-aac = ["dep:symphonia-adapter-fdk-aac"]
/// Symphonias default codec set for our enabled features, plus Opus via libopus.
fn psysonic_codec_registry() -> &'static CodecRegistry {
static REGISTRY: OnceLock<CodecRegistry> = OnceLock::new();
REGISTRY.get_or_init(|| {
let mut registry = CodecRegistry::new();
symphonia::default::register_enabled_codecs(&mut registry);
registry.register_all::<symphonia_adapter_libopus::OpusDecoder>();
registry
})
}
fn try_make_radio_decoder( fn try_make_radio_decoder(
params: &symphonia::core::codecs::CodecParameters, params: &symphonia::core::codecs::CodecParameters,
opts: &DecoderOptions, opts: &DecoderOptions,
) -> Result<Box<dyn symphonia::core::codecs::Decoder>, symphonia::core::errors::Error> { ) -> Result<Box<dyn symphonia::core::codecs::Decoder>, symphonia::core::errors::Error> {
symphonia::default::get_codecs().make(params, opts) psysonic_codec_registry().make(params, opts)
} }
// ── Async HTTP Download Task ────────────────────────────────────────────────── // ── Async HTTP Download Task ──────────────────────────────────────────────────
@@ -980,7 +991,7 @@ impl SizedDecoder {
.zip(track.codec_params.n_frames) .zip(track.codec_params.n_frames)
.map(|(base, frames)| base.calc_time(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()) .make(&track.codec_params, &DecoderOptions::default())
.map_err(|e| { .map_err(|e| {
eprintln!("[psysonic] codec init failed: {e}"); eprintln!("[psysonic] codec init failed: {e}");