Refactor audio capture error handling and cleanup logic

- Removed console logging from the useSystemAudioCapture hook to streamline the code.
- Introduced error handling in the audio capture state to capture and report errors more effectively.
- Updated the cleanup logic to ensure proper handling of errors during audio capture on unmount.
- Enhanced error messages for better clarity when audio capture fails.
This commit is contained in:
Jamie Pine
2026-01-26 20:12:37 -08:00
parent b59c0f44e5
commit e59f86aa63
4 changed files with 29 additions and 17 deletions
+1 -10
View File
@@ -41,7 +41,6 @@ export function useSystemAudioCapture({
}, []); }, []);
const startRecording = useCallback(async () => { const startRecording = useCallback(async () => {
console.log('[useSystemAudioCapture] startRecording called');
if (!isTauri()) { if (!isTauri()) {
const errorMsg = 'System audio capture is only available in the desktop app.'; const errorMsg = 'System audio capture is only available in the desktop app.';
setError(errorMsg); setError(errorMsg);
@@ -55,16 +54,13 @@ export function useSystemAudioCapture({
} }
try { try {
console.log('[useSystemAudioCapture] Starting capture...');
setError(null); setError(null);
setDuration(0); setDuration(0);
// Start native capture // Start native capture
console.log('[useSystemAudioCapture] Calling invoke...');
await invoke('start_system_audio_capture', { await invoke('start_system_audio_capture', {
maxDurationSecs: maxDurationSeconds, maxDurationSecs: maxDurationSeconds,
}); });
console.log('[useSystemAudioCapture] Invoke completed, starting timer');
setIsRecording(true); setIsRecording(true);
isRecordingRef.current = true; isRecordingRef.current = true;
@@ -78,14 +74,11 @@ export function useSystemAudioCapture({
// Auto-stop at max duration // Auto-stop at max duration
if (elapsed >= maxDurationSeconds && stopRecordingRef.current) { if (elapsed >= maxDurationSeconds && stopRecordingRef.current) {
console.log('[useSystemAudioCapture] Max duration reached, auto-stopping');
void stopRecordingRef.current(); void stopRecordingRef.current();
} }
} }
}, 100); }, 100);
console.log('[useSystemAudioCapture] Timer started');
} catch (err) { } catch (err) {
console.error('[useSystemAudioCapture] Error starting recording:', err);
const errorMessage = const errorMessage =
err instanceof Error err instanceof Error
? err.message ? err.message
@@ -157,17 +150,15 @@ export function useSystemAudioCapture({
// Cleanup on unmount only // Cleanup on unmount only
useEffect(() => { useEffect(() => {
return () => { return () => {
console.log('[useSystemAudioCapture] Component unmounting, cleaning up');
if (timerRef.current !== null) { if (timerRef.current !== null) {
clearInterval(timerRef.current); clearInterval(timerRef.current);
timerRef.current = null; timerRef.current = null;
} }
// Cancel recording on unmount if still recording // Cancel recording on unmount if still recording
if (isRecordingRef.current && isTauri()) { if (isRecordingRef.current && isTauri()) {
console.log('[useSystemAudioCapture] Canceling recording on unmount');
// Call stop directly without the callback to avoid stale closure // Call stop directly without the callback to avoid stale closure
invoke('stop_system_audio_capture').catch((err) => { invoke('stop_system_audio_capture').catch((err) => {
console.error('[useSystemAudioCapture] Error stopping on unmount:', err); console.error('Error stopping audio capture on unmount:', err);
}); });
} }
}; };
Binary file not shown.
+3
View File
@@ -18,6 +18,7 @@ pub struct AudioCaptureState {
pub sample_rate: Arc<Mutex<u32>>, pub sample_rate: Arc<Mutex<u32>>,
pub channels: Arc<Mutex<u16>>, pub channels: Arc<Mutex<u16>>,
pub stop_tx: Arc<Mutex<Option<tokio::sync::mpsc::Sender<()>>>>, pub stop_tx: Arc<Mutex<Option<tokio::sync::mpsc::Sender<()>>>>,
pub error: Arc<Mutex<Option<String>>>,
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub stream: Arc<Mutex<Option<SCStream>>>, pub stream: Arc<Mutex<Option<SCStream>>>,
} }
@@ -29,6 +30,7 @@ impl AudioCaptureState {
sample_rate: Arc::new(Mutex::new(44100)), sample_rate: Arc::new(Mutex::new(44100)),
channels: Arc::new(Mutex::new(2)), channels: Arc::new(Mutex::new(2)),
stop_tx: Arc::new(Mutex::new(None)), stop_tx: Arc::new(Mutex::new(None)),
error: Arc::new(Mutex::new(None)),
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
stream: Arc::new(Mutex::new(None)), stream: Arc::new(Mutex::new(None)),
} }
@@ -36,5 +38,6 @@ impl AudioCaptureState {
pub fn reset(&self) { pub fn reset(&self) {
*self.samples.lock().unwrap() = Vec::new(); *self.samples.lock().unwrap() = Vec::new();
*self.error.lock().unwrap() = None;
} }
} }
+25 -7
View File
@@ -19,6 +19,7 @@ pub async fn start_capture(
let sample_rate_arc = state.sample_rate.clone(); let sample_rate_arc = state.sample_rate.clone();
let channels_arc = state.channels.clone(); let channels_arc = state.channels.clone();
let stop_tx = state.stop_tx.clone(); let stop_tx = state.stop_tx.clone();
let error_arc = state.error.clone();
// Use AtomicBool for stop signal (works with non-Send types) // Use AtomicBool for stop signal (works with non-Send types)
let stop_flag = Arc::new(AtomicBool::new(false)); let stop_flag = Arc::new(AtomicBool::new(false));
@@ -42,7 +43,9 @@ pub async fn start_capture(
{ {
Ok(d) => d, Ok(d) => d,
Err(e) => { Err(e) => {
eprintln!("Failed to get audio device: {}", e); let error_msg = format!("Failed to get audio device: {}", e);
eprintln!("{}", error_msg);
*error_arc.lock().unwrap() = Some(error_msg);
return; return;
} }
}; };
@@ -50,7 +53,9 @@ pub async fn start_capture(
let mut audio_client = match device.get_iaudioclient() { let mut audio_client = match device.get_iaudioclient() {
Ok(client) => client, Ok(client) => client,
Err(e) => { Err(e) => {
eprintln!("Failed to get audio client: {}", e); let error_msg = format!("Failed to get audio client: {}", e);
eprintln!("{}", error_msg);
*error_arc.lock().unwrap() = Some(error_msg);
return; return;
} }
}; };
@@ -58,7 +63,9 @@ pub async fn start_capture(
let mix_format = match audio_client.get_mixformat() { let mix_format = match audio_client.get_mixformat() {
Ok(format) => format, Ok(format) => format,
Err(e) => { Err(e) => {
eprintln!("Failed to get mix format: {}", e); let error_msg = format!("Failed to get mix format: {}", e);
eprintln!("{}", error_msg);
*error_arc.lock().unwrap() = Some(error_msg);
return; return;
} }
}; };
@@ -76,20 +83,26 @@ pub async fn start_capture(
}; };
if let Err(e) = audio_client.initialize_client(&mix_format, &Direction::Capture, &stream_mode) { if let Err(e) = audio_client.initialize_client(&mix_format, &Direction::Capture, &stream_mode) {
eprintln!("Failed to initialize audio client: {}", e); let error_msg = format!("Failed to initialize audio client: {}", e);
eprintln!("{}", error_msg);
*error_arc.lock().unwrap() = Some(error_msg);
return; return;
} }
let capture_client = match audio_client.get_audiocaptureclient() { let capture_client = match audio_client.get_audiocaptureclient() {
Ok(client) => client, Ok(client) => client,
Err(e) => { Err(e) => {
eprintln!("Failed to get capture client: {}", e); let error_msg = format!("Failed to get capture client: {}", e);
eprintln!("{}", error_msg);
*error_arc.lock().unwrap() = Some(error_msg);
return; return;
} }
}; };
if let Err(e) = audio_client.start_stream() { if let Err(e) = audio_client.start_stream() {
eprintln!("Failed to start stream: {}", e); let error_msg = format!("Failed to start stream: {}", e);
eprintln!("{}", error_msg);
*error_arc.lock().unwrap() = Some(error_msg);
return; return;
} }
@@ -176,13 +189,18 @@ pub async fn stop_capture(state: &AudioCaptureState) -> Result<String, String> {
// Wait a bit for capture to stop // Wait a bit for capture to stop
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
// Check if there was an error during capture
if let Some(error) = state.error.lock().unwrap().as_ref() {
return Err(error.clone());
}
// Get samples // Get samples
let samples = state.samples.lock().unwrap().clone(); let samples = state.samples.lock().unwrap().clone();
let sample_rate = *state.sample_rate.lock().unwrap(); let sample_rate = *state.sample_rate.lock().unwrap();
let channels = *state.channels.lock().unwrap(); let channels = *state.channels.lock().unwrap();
if samples.is_empty() { if samples.is_empty() {
return Err("No audio samples captured".to_string()); return Err("No audio samples captured. Make sure audio is playing on your system during recording.".to_string());
} }
// Convert to WAV // Convert to WAV