using System.Diagnostics; using Toak.Core; using Toak.Core.Interfaces; namespace Toak.Audio; public class FfmpegAudioRecorder(IRecordingStateTracker stateTracker, INotifications notifications) : IAudioRecorder { private readonly string _wavPath = Constants.Paths.RecordingWavFile; private readonly IRecordingStateTracker _stateTracker = stateTracker; private readonly INotifications _notifications = notifications; public string GetWavPath() => _wavPath; public void StartRecording() { if (File.Exists(_wavPath)) { Logger.LogDebug($"Deleting old audio file: {_wavPath}"); File.Delete(_wavPath); } Logger.LogDebug("Starting ffmpeg to record audio..."); var pInfo = new ProcessStartInfo { FileName = Constants.Commands.AudioFfmpeg, Arguments = $"-f pulse -i default -ac 1 -ar 16000 \"{_wavPath}\"", UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true }; var process = Process.Start(pInfo); if (process != null) { _stateTracker.SetRecording(process.Id); _notifications.Notify("Recording Started (FFmpeg)"); } } public void StopRecording() { var pid = _stateTracker.GetRecordingPid(); if (!pid.HasValue) return; Logger.LogDebug($"Found active ffmpeg process with PID {pid.Value}. Attempting to stop..."); try { var process = Process.GetProcessById(pid.Value); if (process.HasExited) return; // Gracefully stop ffmpeg using SIGINT to ensure WAV headers are finalizing cleanly Process.Start(new ProcessStartInfo { FileName = Constants.Commands.ProcessKill, Arguments = $"-INT {pid.Value}", CreateNoWindow = true, UseShellExecute = false })?.WaitForExit(); process.WaitForExit(2000); // give it a moment to flush } catch (Exception ex) { // Process might already be dead Console.WriteLine($"[FfmpegAudioRecorder] Error stopping ffmpeg: {ex.Message}"); } finally { _stateTracker.ClearRecording(); } } }