using NAudio.Wave; namespace ytLive.Services.Audio; /// /// Captures the user's desktop/game audio (TASK 4 ship step 4) via WASAPI /// loopback on the default render device. Runs for the app's lifetime so the /// game audio bar stays live in preview. /// public sealed class WasapiLoopbackAudioSource : IAudioSource { private WasapiLoopbackCapture? _capture; public event Action? Started; public event Action? SampleReady; public event Action? Failed; public void Start() { if (_capture != null) throw new InvalidOperationException("Already started."); try { _capture = new WasapiLoopbackCapture(); _capture.DataAvailable += OnDataAvailable; _capture.RecordingStopped += OnRecordingStopped; _capture.StartRecording(); Started?.Invoke(); } catch (Exception ex) { Stop(); Failed?.Invoke(ex); } } public void Stop() { if (_capture == null) return; try { _capture.StopRecording(); } catch { } _capture.Dispose(); _capture = null; } public void Dispose() => Stop(); private void OnDataAvailable(object? sender, WaveInEventArgs e) { if (e.BytesRecorded <= 0) return; var format = _capture?.WaveFormat ?? WaveFormat.CreateIeeeFloatWaveFormat(48000, 2); var samples = WaveToFloat.Convert(e.Buffer, e.BytesRecorded, format); if (samples.Length > 0) SampleReady?.Invoke(new AudioSample(samples, format.SampleRate, format.Channels)); } private void OnRecordingStopped(object? sender, StoppedEventArgs e) { if (e.Exception != null) Failed?.Invoke(e.Exception); } }