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:
vicrodh committed 2026-01-11 03:54:56 -06:00
1 parent f2d3b0e65b
commit c80f2d9583
4 files changed
+170 -136

No files matched your search

+18 -2
View File
@@ -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
View File
@@ -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;
}
};