diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-04 02:57:52 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-04 02:57:52 +0300 |
| commit | 55c138263e190a1e79d4b997bfb9397306c0ec1f (patch) | |
| tree | 440e1cca113be67132a6617b4282dc5c1147950d /src/sound.rs | |
| parent | 951fb1d88be721461783a239c152cf143d741dc4 (diff) | |
feat: support custom mp3 notification sound overrides
Diffstat (limited to 'src/sound.rs')
| -rw-r--r-- | src/sound.rs | 58 |
1 files changed, 45 insertions, 13 deletions
diff --git a/src/sound.rs b/src/sound.rs index aa6fffe6..a156349b 100644 --- a/src/sound.rs +++ b/src/sound.rs @@ -4,7 +4,10 @@ //! Uses afplay (macOS) or paplay/aplay (Linux) — no Rust audio dependencies. use std::io::Write; -use std::process::Command; +use std::path::Path; +use std::process::{Command, Output}; + +use tracing::warn; static SOUND_DONE: &[u8] = include_bytes!("../assets/sounds/done.mp3"); static SOUND_REQUEST: &[u8] = include_bytes!("../assets/sounds/request.mp3"); @@ -20,16 +23,37 @@ pub enum Sound { /// Play a notification sound in a background thread. /// Silently does nothing if no audio player is available. -pub fn play(sound: Sound) { +pub fn play(sound: Sound, config: &crate::config::SoundConfig) { + let custom_path = config.path_for(sound); std::thread::spawn(move || { + if let Some(path) = custom_path { + match play_file(&path) { + Ok(()) => return, + Err(err) => { + warn!(path = %path.display(), sound = ?sound, err = %err, "custom sound playback failed, falling back to built-in sound") + } + } + } + let data = match sound { Sound::Done => SOUND_DONE, Sound::Request => SOUND_REQUEST, }; - let _ = play_bytes(data); + + if let Err(err) = play_bytes(data) { + warn!(sound = ?sound, err = %err, "sound playback failed"); + } }); } +fn play_file(path: &Path) -> Result<(), String> { + match run_player(path) { + Ok(output) if output.status.success() => Ok(()), + Ok(output) => Err(format!("player exited with {}", output.status)), + Err(err) => Err(err), + } +} + fn play_bytes(data: &[u8]) -> Result<(), String> { // Write to a temp file (audio players need a file path) let tmp = std::env::temp_dir().join(format!("herdr-sound-{}.mp3", std::process::id())); @@ -37,21 +61,29 @@ fn play_bytes(data: &[u8]) -> Result<(), String> { file.write_all(data).map_err(|e| e.to_string())?; drop(file); - let result = if cfg!(target_os = "macos") { - Command::new("afplay").arg(&tmp).output() - } else { - // Try paplay (PulseAudio) first, fall back to aplay (ALSA) - Command::new("paplay") - .arg(&tmp) - .output() - .or_else(|_| Command::new("aplay").arg(&tmp).output()) - }; + let result = run_player(&tmp); let _ = std::fs::remove_file(&tmp); match result { Ok(output) if output.status.success() => Ok(()), Ok(output) => Err(format!("player exited with {}", output.status)), - Err(e) => Err(format!("no audio player available: {e}")), + Err(e) => Err(e), + } +} + +fn run_player(path: &Path) -> Result<Output, String> { + if cfg!(target_os = "macos") { + Command::new("afplay") + .arg(path) + .output() + .map_err(|e| format!("no audio player available: {e}")) + } else { + // Try paplay (PulseAudio) first, fall back to aplay (ALSA) + Command::new("paplay") + .arg(path) + .output() + .or_else(|_| Command::new("aplay").arg(path).output()) + .map_err(|e| format!("no audio player available: {e}")) } } |
