Add audio device reinit for proper exclusive mode release
This commit is contained in:
1 parent
b6b019e659
commit
0eb0b2ec1f
4 files changed
+151
-77
No files matched your search
@@ -348,6 +348,17 @@ pub fn get_audio_output_status(
|
||||
})
|
||||
}
|
||||
|
||||
/// Reinitialize audio device (releases and re-acquires)
|
||||
/// Call this when changing audio settings like exclusive mode or output device
|
||||
#[tauri::command]
|
||||
pub fn reinit_audio_device(
|
||||
device: Option<String>,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> Result<(), String> {
|
||||
log::info!("Command: reinit_audio_device {:?}", device);
|
||||
state.player.reinit_device(device)
|
||||
}
|
||||
|
||||
/// PipeWire/PulseAudio sink information
|
||||
#[derive(serde::Serialize, Clone)]
|
||||
pub struct PipewireSink {
|
||||
|
||||
@@ -236,6 +236,7 @@ pub fn run() {
|
||||
commands::get_audio_devices,
|
||||
commands::get_audio_output_status,
|
||||
commands::get_pipewire_sinks,
|
||||
commands::reinit_audio_device,
|
||||
// Queue commands
|
||||
commands::add_to_queue,
|
||||
commands::add_to_queue_next,
|
||||
|
||||
+119
-68
@@ -34,6 +34,8 @@ enum AudioCommand {
|
||||
SetVolume(f32),
|
||||
/// Seek to position in seconds
|
||||
Seek(u64),
|
||||
/// Reinitialize audio device (releases and re-acquires)
|
||||
ReinitDevice { device_name: Option<String> },
|
||||
}
|
||||
|
||||
/// Event payload for playback state updates
|
||||
@@ -197,85 +199,82 @@ impl Player {
|
||||
.unwrap_or(false)
|
||||
};
|
||||
|
||||
// Find the requested device or use default
|
||||
let device = if let Some(ref name) = device_name {
|
||||
log::info!("Looking for audio device: {}", name);
|
||||
let found = host.output_devices()
|
||||
.ok()
|
||||
.and_then(|mut devices| {
|
||||
devices.find(|d| d.name().ok().as_ref() == Some(name))
|
||||
});
|
||||
// Helper to find and initialize audio device
|
||||
let init_device = |name: &Option<String>, state: &SharedState| -> Option<(OutputStream, rodio::OutputStreamHandle)> {
|
||||
let device = if let Some(ref name) = name {
|
||||
log::info!("Looking for audio device: {}", name);
|
||||
let found = host.output_devices()
|
||||
.ok()
|
||||
.and_then(|mut devices| {
|
||||
devices.find(|d| d.name().ok().as_ref() == Some(name))
|
||||
});
|
||||
|
||||
// Validate the found device
|
||||
match found {
|
||||
Some(d) if is_device_valid(&d) => {
|
||||
log::info!("Found and validated device: {}", name);
|
||||
Some(d)
|
||||
match found {
|
||||
Some(d) if is_device_valid(&d) => {
|
||||
log::info!("Found and validated device: {}", name);
|
||||
Some(d)
|
||||
}
|
||||
Some(_) => {
|
||||
log::warn!("Device '{}' found but has no valid output configs, using default", name);
|
||||
host.default_output_device()
|
||||
}
|
||||
None => {
|
||||
log::warn!("Device '{}' not found, using default", name);
|
||||
host.default_output_device()
|
||||
}
|
||||
}
|
||||
Some(_) => {
|
||||
log::warn!("Device '{}' found but has no valid output configs, 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);
|
||||
state.set_current_device(Some(name));
|
||||
}
|
||||
d
|
||||
}
|
||||
None => {
|
||||
log::warn!("Device '{}' not found, using default", name);
|
||||
host.default_output_device()
|
||||
log::error!("No audio output device available");
|
||||
state.set_current_device(None);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
thread_state.set_current_device(Some(name));
|
||||
match OutputStream::try_from_device(&device) {
|
||||
Ok(s) => {
|
||||
log::info!("Audio output initialized successfully");
|
||||
Some(s)
|
||||
}
|
||||
d
|
||||
}
|
||||
None => {
|
||||
log::error!("No audio output device available");
|
||||
thread_state.set_current_device(None);
|
||||
// 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 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,
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to create audio output on device: {}. Trying default...", e);
|
||||
match OutputStream::try_default() {
|
||||
Ok(s) => {
|
||||
log::info!("Fallback to default audio output succeeded");
|
||||
Some(s)
|
||||
}
|
||||
Err(e2) => {
|
||||
log::error!("Failed to create default audio output: {}", e2);
|
||||
state.set_current_device(None);
|
||||
None
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize audio device
|
||||
let mut current_device_name = device_name.clone();
|
||||
let mut stream_opt: Option<(OutputStream, rodio::OutputStreamHandle)> =
|
||||
init_device(¤t_device_name, &thread_state);
|
||||
|
||||
if stream_opt.is_none() {
|
||||
// Wait for ReinitDevice command
|
||||
log::warn!("No audio device available, waiting for reinit command...");
|
||||
}
|
||||
|
||||
let mut current_sink: Option<Sink> = None;
|
||||
// Store audio data for seeking (we need to re-decode from the beginning)
|
||||
let mut current_audio_data: Option<Vec<u8>> = None;
|
||||
@@ -291,6 +290,12 @@ impl Player {
|
||||
Ok(AudioCommand::Play { data, track_id, duration_secs }) => {
|
||||
log::info!("Audio thread: playing track {}", track_id);
|
||||
|
||||
// Check if we have an audio stream
|
||||
let Some(ref stream) = stream_opt else {
|
||||
log::error!("Audio thread: no audio device available");
|
||||
continue;
|
||||
};
|
||||
|
||||
// Stop existing playback
|
||||
if let Some(sink) = current_sink.take() {
|
||||
sink.stop();
|
||||
@@ -300,7 +305,7 @@ impl Player {
|
||||
current_audio_data = Some(data.clone());
|
||||
|
||||
// Create new sink
|
||||
let sink = match Sink::try_new(&stream_handle) {
|
||||
let sink = match Sink::try_new(&stream.1) {
|
||||
Ok(s) => {
|
||||
consecutive_sink_failures = 0; // Reset on success
|
||||
s
|
||||
@@ -391,6 +396,11 @@ impl Player {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(ref stream) = stream_opt else {
|
||||
log::error!("Audio thread: cannot seek - no audio device available");
|
||||
continue;
|
||||
};
|
||||
|
||||
log::info!("Audio thread: seeking to {}s", position_secs);
|
||||
|
||||
// Stop current sink
|
||||
@@ -399,7 +409,7 @@ impl Player {
|
||||
}
|
||||
|
||||
// Create new sink
|
||||
let sink = match Sink::try_new(&stream_handle) {
|
||||
let sink = match Sink::try_new(&stream.1) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
log::error!("Failed to create sink for seek: {}", e);
|
||||
@@ -442,6 +452,39 @@ impl Player {
|
||||
current_sink = Some(sink);
|
||||
log::info!("Audio thread: seeked to {}s (was_playing: {})", position_secs, was_playing);
|
||||
}
|
||||
Ok(AudioCommand::ReinitDevice { device_name: new_device }) => {
|
||||
log::info!("Audio thread: reinitializing device (new: {:?})", new_device);
|
||||
|
||||
// Stop any current playback
|
||||
if let Some(sink) = current_sink.take() {
|
||||
sink.stop();
|
||||
}
|
||||
|
||||
// Drop the current stream to release the device
|
||||
// This is the key step - dropping releases exclusive access
|
||||
drop(stream_opt.take());
|
||||
log::info!("Audio thread: previous stream dropped, device released");
|
||||
|
||||
// Small delay to let the system release the device
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
|
||||
// Reinitialize with new device
|
||||
current_device_name = new_device;
|
||||
stream_opt = init_device(¤t_device_name, &thread_state);
|
||||
|
||||
if stream_opt.is_some() {
|
||||
log::info!("Audio thread: device reinitialized successfully");
|
||||
consecutive_sink_failures = 0;
|
||||
} else {
|
||||
log::error!("Audio thread: failed to reinitialize device");
|
||||
}
|
||||
|
||||
// Clear playback state
|
||||
thread_state.is_playing.store(false, Ordering::SeqCst);
|
||||
thread_state.position.store(0, Ordering::SeqCst);
|
||||
thread_state.playback_start_millis.store(0, Ordering::SeqCst);
|
||||
current_audio_data = None;
|
||||
}
|
||||
Err(RecvTimeoutError::Timeout) => {
|
||||
// Check if sink is empty (playback finished)
|
||||
if let Some(ref sink) = current_sink {
|
||||
@@ -598,6 +641,14 @@ impl Player {
|
||||
.map_err(|e| format!("Failed to send seek command: {}", e))
|
||||
}
|
||||
|
||||
/// Reinitialize audio device (releases and re-acquires the device)
|
||||
/// Use this when changing audio settings like exclusive mode
|
||||
pub fn reinit_device(&self, device_name: Option<String>) -> Result<(), String> {
|
||||
self.tx
|
||||
.send(AudioCommand::ReinitDevice { device_name })
|
||||
.map_err(|e| format!("Failed to send reinit command: {}", e))
|
||||
}
|
||||
|
||||
/// Get current playback state with real-time position
|
||||
pub fn get_state(&self) -> Result<PlaybackState, String> {
|
||||
Ok(PlaybackState {
|
||||
|
||||
Reference in new issue
Block a user