Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions src-tauri/src/managers/audio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ use crate::helpers::clamshell;
use crate::settings::{get_settings, AppSettings};
use crate::utils;
use log::{debug, error, info};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Instant;
use std::time::{Duration, Instant};
use tauri::Manager;

const STREAM_IDLE_TIMEOUT: Duration = Duration::from_secs(30);

fn set_mute(mute: bool) {
// Expected behavior:
// - Windows: works on most systems using standard audio drivers.
Expand Down Expand Up @@ -149,6 +152,7 @@ pub struct AudioRecordingManager {
is_open: Arc<Mutex<bool>>,
is_recording: Arc<Mutex<bool>>,
did_mute: Arc<Mutex<bool>>,
close_generation: Arc<AtomicU64>,
}

impl AudioRecordingManager {
Expand All @@ -171,6 +175,7 @@ impl AudioRecordingManager {
is_open: Arc::new(Mutex::new(false)),
is_recording: Arc::new(Mutex::new(false)),
did_mute: Arc::new(Mutex::new(false)),
close_generation: Arc::new(AtomicU64::new(0)),
};

// Always-on? Open immediately.
Expand Down Expand Up @@ -210,6 +215,22 @@ impl AudioRecordingManager {
}
}

fn schedule_lazy_close(&self) {
let gen = self.close_generation.fetch_add(1, Ordering::SeqCst) + 1;
let app = self.app_handle.clone();
std::thread::spawn(move || {
std::thread::sleep(STREAM_IDLE_TIMEOUT);
let rm = app.state::<Arc<AudioRecordingManager>>();
if rm.close_generation.load(Ordering::SeqCst) == gen {
info!(
"Closing idle microphone stream after {:?}",
STREAM_IDLE_TIMEOUT
);
rm.stop_microphone_stream();
}
});
}

/* ---------- microphone life-cycle -------------------------------------- */

/// Applies mute if mute_while_recording is enabled and stream is open
Expand Down Expand Up @@ -321,6 +342,7 @@ impl AudioRecordingManager {
}
(MicrophoneMode::OnDemand, MicrophoneMode::AlwaysOn) => {
drop(mode_guard);
self.close_generation.fetch_add(1, Ordering::SeqCst);
self.start_microphone_stream()?;
}
_ => {}
Expand All @@ -338,6 +360,8 @@ impl AudioRecordingManager {
if let RecordingState::Idle = *state {
// Ensure microphone is open in on-demand mode
if matches!(*self.mode.lock().unwrap(), MicrophoneMode::OnDemand) {
// Cancel any pending lazy close
self.close_generation.fetch_add(1, Ordering::SeqCst);
if let Err(e) = self.start_microphone_stream() {
error!("Failed to open microphone stream: {e}");
return false;
Expand Down Expand Up @@ -395,9 +419,9 @@ impl AudioRecordingManager {

*self.is_recording.lock().unwrap() = false;

// In on-demand mode turn the mic off again
// In on-demand mode, schedule a delayed close instead of closing immediately
if matches!(*self.mode.lock().unwrap(), MicrophoneMode::OnDemand) {
self.stop_microphone_stream();
self.schedule_lazy_close();
}

// Pad if very short
Expand Down Expand Up @@ -435,9 +459,9 @@ impl AudioRecordingManager {

*self.is_recording.lock().unwrap() = false;

// In on-demand mode turn the mic off again
// In on-demand mode, schedule a delayed close instead of closing immediately
if matches!(*self.mode.lock().unwrap(), MicrophoneMode::OnDemand) {
self.stop_microphone_stream();
self.schedule_lazy_close();
}
}
}
Expand Down