diff --git a/openless-all/app/src-tauri/src/coordinator.rs b/openless-all/app/src-tauri/src/coordinator.rs index eee246ba..4be58b85 100644 --- a/openless-all/app/src-tauri/src/coordinator.rs +++ b/openless-all/app/src-tauri/src/coordinator.rs @@ -98,6 +98,20 @@ struct SessionState { front_app: Option, } +struct SharedRecordingMuteState { + guard: Option, + holders: u32, +} + +impl SharedRecordingMuteState { + fn new() -> Self { + Self { + guard: None, + holders: 0, + } + } +} + impl Default for SessionState { fn default() -> Self { Self { @@ -132,7 +146,7 @@ struct Inner { /// 释放时机由 prefs.local_asr_keep_loaded_secs 决定。 local_asr_cache: Arc, recorder: Mutex>>, - recording_mute: Mutex>, + recording_mute: Mutex, hotkey: Mutex>, hotkey_status: Mutex, hotkey_trigger_held: AtomicBool, @@ -153,8 +167,6 @@ struct Inner { qa_asr: Mutex>>, /// QA 用的 Recorder 句柄。 qa_recorder: Mutex>, - /// QA 录音期间的系统输出静音 guard。 - qa_recording_mute: Mutex>, /// QA SSE 流取消标志。begin_qa_session 重置为 false;cancel_qa_session 设 true; /// polish::chat_completion_history_streaming 的 loop 每帧检查,true 时 break loop /// 避免取消后 LLM 仍 drain HTTP body 烧 token。详见 issue #161。 @@ -230,7 +242,7 @@ impl Coordinator { state: Mutex::new(SessionState::default()), asr: Mutex::new(None), recorder: Mutex::new(None), - recording_mute: Mutex::new(None), + recording_mute: Mutex::new(SharedRecordingMuteState::new()), hotkey: Mutex::new(None), hotkey_status: Mutex::new(HotkeyStatus::default()), hotkey_trigger_held: AtomicBool::new(false), @@ -240,7 +252,6 @@ impl Coordinator { capsule_layout: Mutex::new(None), qa_asr: Mutex::new(None), qa_recorder: Mutex::new(None), - qa_recording_mute: Mutex::new(None), qa_stream_cancelled: Arc::new(AtomicBool::new(false)), local_asr_cache: Arc::new(crate::asr::local::LocalAsrCache::new()), }), @@ -878,45 +889,37 @@ fn store_recorder_for_session(inner: &Arc, session_id: u64, recorder: Rec *inner.recorder.lock() = Some(SessionResource::new(session_id, recorder)); } -fn activate_recording_mute(inner: &Arc) { - if !inner.prefs.get().mute_during_recording || inner.recording_mute.lock().is_some() { +fn acquire_recording_mute(inner: &Arc, owner: &str) { + if !inner.prefs.get().mute_during_recording { return; } - match crate::audio_mute::AudioMuteGuard::activate() { - Ok(guard) => { - *inner.recording_mute.lock() = Some(guard); - log::info!("[audio-mute] system output muted for dictation recording"); - } - Err(err) => { - log::warn!("[audio-mute] failed to mute output for dictation: {err}"); + let mut mute = inner.recording_mute.lock(); + if mute.holders == 0 { + match crate::audio_mute::AudioMuteGuard::activate() { + Ok(guard) => { + mute.guard = Some(guard); + log::info!("[audio-mute] system output muted for recording"); + } + Err(err) => { + log::warn!("[audio-mute] failed to mute output for {owner}: {err}"); + return; + } } } + mute.holders = mute.holders.saturating_add(1); + log::info!("[audio-mute] acquired by {owner}; holders={}", mute.holders); } -fn restore_recording_mute(inner: &Arc) { - if inner.recording_mute.lock().take().is_some() { - log::info!("[audio-mute] system output mute restored for dictation"); - } -} - -fn activate_qa_recording_mute(inner: &Arc) { - if !inner.prefs.get().mute_during_recording || inner.qa_recording_mute.lock().is_some() { +fn release_recording_mute(inner: &Arc, owner: &str) { + let mut mute = inner.recording_mute.lock(); + if mute.holders == 0 { return; } - match crate::audio_mute::AudioMuteGuard::activate() { - Ok(guard) => { - *inner.qa_recording_mute.lock() = Some(guard); - log::info!("[audio-mute] system output muted for QA recording"); - } - Err(err) => { - log::warn!("[audio-mute] failed to mute output for QA: {err}"); - } - } -} - -fn restore_qa_recording_mute(inner: &Arc) { - if inner.qa_recording_mute.lock().take().is_some() { - log::info!("[audio-mute] system output mute restored for QA"); + mute.holders -= 1; + log::info!("[audio-mute] released by {owner}; holders={}", mute.holders); + if mute.holders == 0 { + mute.guard.take(); + log::info!("[audio-mute] system output mute restored after recording"); } } @@ -924,7 +927,7 @@ fn stop_qa_recorder(inner: &Arc) { if let Some(rec) = inner.qa_recorder.lock().take() { rec.stop(); } - restore_qa_recording_mute(inner); + release_recording_mute(inner, "qa"); } fn take_recorder_for_session(inner: &Arc, session_id: u64) -> Option { @@ -935,7 +938,7 @@ fn take_recorder_for_session(inner: &Arc, session_id: u64) -> Option, session_id: u64) { if let Some(recorder) = take_recorder_for_session(inner, session_id) { recorder.stop(); - restore_recording_mute(inner); + release_recording_mute(inner, "dictation"); } } @@ -957,7 +960,7 @@ fn stop_recorder_if_pending_start_stop(inner: &Arc) { } if let Some(rec) = take_recorder_for_session(inner, session_id) { rec.stop(); - restore_recording_mute(inner); + release_recording_mute(inner, "dictation"); let elapsed = inner.state.lock().started_at.elapsed().as_millis() as u64; emit_capsule(inner, CapsuleState::Transcribing, 0.0, elapsed, None, None); log::info!("[coord] stopped recorder while ASR is still connecting"); @@ -1291,7 +1294,7 @@ fn start_recorder_for_starting( ); }); - activate_recording_mute(inner); + acquire_recording_mute(inner, "dictation"); match Recorder::start(consumer, level_handler) { Ok((rec, runtime_errors)) => { store_recorder_for_session(inner, session_id, rec); @@ -1311,7 +1314,7 @@ fn start_recorder_for_starting( None, ); restore_prepared_windows_ime_session(inner, session_id); - restore_recording_mute(inner); + release_recording_mute(inner, "dictation"); inner.state.lock().phase = SessionPhase::Idle; schedule_capsule_idle(inner, CAPSULE_AUTO_HIDE_DELAY_MS); return Err(e.to_string()); @@ -1497,7 +1500,7 @@ async fn end_session(inner: &Arc) -> Result<(), String> { if let Some(rec) = take_recorder_for_session(inner, current_session_id) { rec.stop(); - restore_recording_mute(inner); + release_recording_mute(inner, "dictation"); } let asr_opt = take_asr_for_session(inner, current_session_id); @@ -2559,7 +2562,7 @@ async fn begin_qa_session(inner: &Arc) -> Result<(), String> { ); }); - activate_qa_recording_mute(inner); + acquire_recording_mute(inner, "qa"); match Recorder::start(consumer, level_handler) { Ok((rec, runtime_errors)) => { *inner.qa_recorder.lock() = Some(rec); @@ -2570,7 +2573,7 @@ async fn begin_qa_session(inner: &Arc) -> Result<(), String> { Err(e) => { log::error!("[coord] QA recorder start failed: {e}"); inner.qa_asr.lock().take(); - restore_qa_recording_mute(inner); + release_recording_mute(inner, "qa"); finish_qa_with_error(inner, format!("录音启动失败: {e}")); return Err(e.to_string()); }