Wire audio device selection from settings to player
- Player::new() now accepts optional device name - Uses OutputStream::try_from_device() for specific device - AppState reads saved device from audio settings on startup - SettingsView loads/saves audio settings to backend - Changes take effect on app restart
This commit is contained in:
1 parent
f2d3b0e65b
commit
c80f2d9583
4 files changed
+170
-136
No files matched your search
+18
-2
@@ -47,9 +47,13 @@ pub struct AppState {
|
||||
|
||||
impl AppState {
|
||||
pub fn new() -> Self {
|
||||
Self::with_device(None)
|
||||
}
|
||||
|
||||
pub fn with_device(device_name: Option<String>) -> Self {
|
||||
Self {
|
||||
client: Arc::new(Mutex::new(QobuzClient::default())),
|
||||
player: Player::new(),
|
||||
player: Player::new(device_name),
|
||||
queue: QueueManager::new(),
|
||||
media_controls: MediaControlsManager::new(),
|
||||
audio_cache: Arc::new(AudioCache::default()),
|
||||
@@ -128,11 +132,23 @@ pub fn run() {
|
||||
let audio_settings_state = config::audio_settings::AudioSettingsState::new()
|
||||
.expect("Failed to initialize audio settings");
|
||||
|
||||
// Read saved audio device setting for player initialization
|
||||
let saved_device = audio_settings_state
|
||||
.store
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|store| store.get_settings().ok())
|
||||
.and_then(|settings| settings.output_device);
|
||||
|
||||
if let Some(ref device) = saved_device {
|
||||
log::info!("Initializing player with saved device: {}", device);
|
||||
}
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.manage(AppState::new())
|
||||
.manage(AppState::with_device(saved_device))
|
||||
.setup(|app| {
|
||||
// Start background task to emit playback events
|
||||
let app_handle = app.handle().clone();
|
||||
|
||||
+62
-16
@@ -16,6 +16,7 @@ use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use rodio::{Decoder, OutputStream, Sink, Source};
|
||||
use rodio::cpal::traits::{DeviceTrait, HostTrait};
|
||||
|
||||
use crate::api::{client::QobuzClient, models::Quality};
|
||||
|
||||
@@ -157,12 +158,14 @@ pub struct Player {
|
||||
|
||||
impl Default for Player {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
Self::new(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn new() -> Self {
|
||||
/// Create a new player with an optional specific output device
|
||||
/// If device_name is None, uses the system default device
|
||||
pub fn new(device_name: Option<String>) -> Self {
|
||||
let (tx, rx) = mpsc::channel::<AudioCommand>();
|
||||
let state = SharedState::new();
|
||||
let thread_state = state.clone();
|
||||
@@ -171,28 +174,71 @@ impl Player {
|
||||
thread::spawn(move || {
|
||||
log::info!("Audio thread starting...");
|
||||
|
||||
// Create output stream on this thread (it must stay here)
|
||||
let (_stream, stream_handle) = match OutputStream::try_default() {
|
||||
// Get the audio host
|
||||
let host = rodio::cpal::default_host();
|
||||
|
||||
// Find the requested device or use default
|
||||
let device = if let Some(ref name) = device_name {
|
||||
log::info!("Looking for audio device: {}", name);
|
||||
host.output_devices()
|
||||
.ok()
|
||||
.and_then(|mut devices| {
|
||||
devices.find(|d| d.name().ok().as_ref() == Some(name))
|
||||
})
|
||||
.or_else(|| {
|
||||
log::warn!("Device '{}' not found, using default", name);
|
||||
host.default_output_device()
|
||||
})
|
||||
} else {
|
||||
log::info!("Using default audio device");
|
||||
host.default_output_device()
|
||||
};
|
||||
|
||||
let device = match device {
|
||||
Some(d) => {
|
||||
if let Ok(name) = d.name() {
|
||||
log::info!("Using audio device: {}", name);
|
||||
}
|
||||
d
|
||||
}
|
||||
None => {
|
||||
log::error!("No audio output device available");
|
||||
// Keep thread alive to receive commands
|
||||
loop {
|
||||
match rx.recv() {
|
||||
Ok(_) => log::warn!("Audio command received but no device available"),
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Create output stream on the selected device
|
||||
let (_stream, stream_handle) = match OutputStream::try_from_device(&device) {
|
||||
Ok(s) => {
|
||||
log::info!("Audio output initialized successfully");
|
||||
s
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to create audio output: {}. Audio playback will not work.", e);
|
||||
// Keep the thread alive to receive commands (even if we can't play)
|
||||
// This prevents the channel from closing immediately
|
||||
loop {
|
||||
match rx.recv() {
|
||||
Ok(_) => {
|
||||
log::warn!("Audio command received but audio output is unavailable");
|
||||
}
|
||||
Err(_) => {
|
||||
log::info!("Audio thread: channel closed, exiting");
|
||||
break;
|
||||
log::error!("Failed to create audio output on device: {}. Trying default...", e);
|
||||
// Fallback to default
|
||||
match OutputStream::try_default() {
|
||||
Ok(s) => {
|
||||
log::info!("Fallback to default audio output succeeded");
|
||||
s
|
||||
}
|
||||
Err(e2) => {
|
||||
log::error!("Failed to create default audio output: {}", e2);
|
||||
loop {
|
||||
match rx.recv() {
|
||||
Ok(_) => log::warn!("Audio command received but audio output is unavailable"),
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user