diff --git a/src-tauri/resources/pop_start.wav b/src-tauri/resources/pop_start.wav index fe23de9..86ef9d4 100644 Binary files a/src-tauri/resources/pop_start.wav and b/src-tauri/resources/pop_start.wav differ diff --git a/src-tauri/resources/pop_stop.wav b/src-tauri/resources/pop_stop.wav index 04024ad..7b9376d 100644 Binary files a/src-tauri/resources/pop_stop.wav and b/src-tauri/resources/pop_stop.wav differ diff --git a/src-tauri/src/actions.rs b/src-tauri/src/actions.rs index ad9c340..d47dc84 100644 --- a/src-tauri/src/actions.rs +++ b/src-tauri/src/actions.rs @@ -1,4 +1,4 @@ -use crate::audio_feedback::{play_feedback_sound, SoundType}; +use crate::audio_feedback::{play_feedback_sound, play_feedback_sound_blocking, SoundType}; use crate::managers::audio::AudioRecordingManager; use crate::managers::history::HistoryManager; use crate::managers::transcription::TranscriptionManager; @@ -183,12 +183,12 @@ impl ShortcutAction for TranscribeAction { if is_always_on { // Always-on mode: Play audio feedback immediately, then apply mute after sound finishes debug!("Always-on mode: Playing audio feedback immediately"); - play_feedback_sound(app, SoundType::Start); - - // Apply mute after audio feedback has time to play (500ms should be enough for most sounds) let rm_clone = Arc::clone(&rm); + let app_clone = app.clone(); + // The blocking helper exits immediately if audio feedback is disabled, + // so we can always reuse this thread to ensure mute happens right after playback. std::thread::spawn(move || { - std::thread::sleep(std::time::Duration::from_millis(500)); + play_feedback_sound_blocking(&app_clone, SoundType::Start); rm_clone.apply_mute(); }); @@ -206,11 +206,10 @@ impl ShortcutAction for TranscribeAction { let rm_clone = Arc::clone(&rm); std::thread::spawn(move || { std::thread::sleep(std::time::Duration::from_millis(100)); - debug!("Playing delayed audio feedback"); - play_feedback_sound(&app_clone, SoundType::Start); - - // Apply mute after audio feedback has time to play - std::thread::sleep(std::time::Duration::from_millis(500)); + debug!("Handling delayed audio feedback/mute sequence"); + // Helper handles disabled audio feedback by returning early, so we reuse it + // to keep mute sequencing consistent in every mode. + play_feedback_sound_blocking(&app_clone, SoundType::Start); rm_clone.apply_mute(); }); } else { diff --git a/src-tauri/src/audio_feedback.rs b/src-tauri/src/audio_feedback.rs index 39764d8..0d7b85f 100644 --- a/src-tauri/src/audio_feedback.rs +++ b/src-tauri/src/audio_feedback.rs @@ -1,9 +1,11 @@ -use crate::settings; +use crate::settings::SoundTheme; +use crate::settings::{self, AppSettings}; use cpal::traits::{DeviceTrait, HostTrait}; use log::{debug, error, warn}; use rodio::OutputStreamBuilder; use std::fs::File; use std::io::BufReader; +use std::path::{Path, PathBuf}; use std::thread; use tauri::{AppHandle, Manager}; @@ -12,73 +14,79 @@ pub enum SoundType { Stop, } -/// Plays an audio resource from the specified directory. -fn play_sound(app: &AppHandle, resource_path: &str, base_dir: tauri::path::BaseDirectory) { - let app_handle = app.clone(); - let resource_path = resource_path.to_string(); - let volume = settings::get_settings(app).audio_feedback_volume; - - thread::spawn(move || { - let audio_path = match app_handle.path().resolve(&resource_path, base_dir) { - Ok(path) => path.to_path_buf(), - Err(e) => { - error!( - "Failed to resolve audio file path '{}': {}", - resource_path, e - ); - return; - } - }; - - let settings = settings::get_settings(&app_handle); - let selected_device = settings.selected_output_device.clone(); - - if let Err(e) = play_audio_file(&audio_path, selected_device, volume) { - error!("Failed to play sound '{}': {}", resource_path, e); - } - }); +fn resolve_sound_path( + app: &AppHandle, + settings: &AppSettings, + sound_type: SoundType, +) -> Option { + let sound_file = get_sound_path(settings, sound_type); + let base_dir = get_sound_base_dir(settings); + app.path().resolve(&sound_file, base_dir).ok() } -fn get_sound_path(app: &AppHandle, sound_type: SoundType) -> String { - let settings = settings::get_settings(app); - match sound_type { - SoundType::Start => match settings.sound_theme { - crate::settings::SoundTheme::Custom => "custom_start.wav".to_string(), - _ => settings.sound_theme.to_start_path(), - }, - SoundType::Stop => match settings.sound_theme { - crate::settings::SoundTheme::Custom => "custom_stop.wav".to_string(), - _ => settings.sound_theme.to_stop_path(), - }, +fn get_sound_path(settings: &AppSettings, sound_type: SoundType) -> String { + match (settings.sound_theme, sound_type) { + (SoundTheme::Custom, SoundType::Start) => "custom_start.wav".to_string(), + (SoundTheme::Custom, SoundType::Stop) => "custom_stop.wav".to_string(), + (_, SoundType::Start) => settings.sound_theme.to_start_path(), + (_, SoundType::Stop) => settings.sound_theme.to_stop_path(), + } +} + +fn get_sound_base_dir(settings: &AppSettings) -> tauri::path::BaseDirectory { + match settings.sound_theme { + SoundTheme::Custom => tauri::path::BaseDirectory::AppData, + _ => tauri::path::BaseDirectory::Resource, } } pub fn play_feedback_sound(app: &AppHandle, sound_type: SoundType) { - // Only play if audio feedback is enabled let settings = settings::get_settings(app); if !settings.audio_feedback { return; } + if let Some(path) = resolve_sound_path(app, &settings, sound_type) { + play_sound_async(app, path); + } +} - let sound_file = get_sound_path(app, sound_type); - let base_dir = if settings.sound_theme == crate::settings::SoundTheme::Custom { - tauri::path::BaseDirectory::AppData - } else { - tauri::path::BaseDirectory::Resource - }; - play_sound(app, &sound_file, base_dir); +pub fn play_feedback_sound_blocking(app: &AppHandle, sound_type: SoundType) { + let settings = settings::get_settings(app); + if !settings.audio_feedback { + return; + } + if let Some(path) = resolve_sound_path(app, &settings, sound_type) { + play_sound_blocking(app, &path); + } } pub fn play_test_sound(app: &AppHandle, sound_type: SoundType) { - // Always play test sound, regardless of audio_feedback setting let settings = settings::get_settings(app); - let sound_file = get_sound_path(app, sound_type); - let base_dir = if settings.sound_theme == crate::settings::SoundTheme::Custom { - tauri::path::BaseDirectory::AppData - } else { - tauri::path::BaseDirectory::Resource - }; - play_sound(app, &sound_file, base_dir); + if let Some(path) = resolve_sound_path(app, &settings, sound_type) { + play_sound_async(app, path); + } +} + +fn play_sound_async(app: &AppHandle, path: PathBuf) { + let app_handle = app.clone(); + thread::spawn(move || { + if let Err(e) = play_sound_at_path(&app_handle, path.as_path()) { + error!("Failed to play sound '{}': {}", path.display(), e); + } + }); +} + +fn play_sound_blocking(app: &AppHandle, path: &Path) { + if let Err(e) = play_sound_at_path(app, path) { + error!("Failed to play sound '{}': {}", path.display(), e); + } +} + +fn play_sound_at_path(app: &AppHandle, path: &Path) -> Result<(), Box> { + let settings = settings::get_settings(app); + let volume = settings.audio_feedback_volume; + let selected_device = settings.selected_output_device.clone(); + play_audio_file(path, selected_device, volume) } fn play_audio_file( diff --git a/src/components/settings/SoundPicker.tsx b/src/components/settings/SoundPicker.tsx index 629f737..3c35832 100644 --- a/src/components/settings/SoundPicker.tsx +++ b/src/components/settings/SoundPicker.tsx @@ -33,8 +33,6 @@ export const SoundPicker: React.FC = ({ const handlePlayBothSounds = async () => { await playTestSound("start"); - // Wait before playing stop sound - await new Promise((resolve) => setTimeout(resolve, 800)); await playTestSound("stop"); };