using NAudio.CoreAudioApi; using NAudio.Wave; namespace ytLive.Services.Audio; /// /// Captures the selected microphone (TASK 4 ship step 4) via WASAPI. Resolves /// the NAudio device by FriendlyName matching MicSourceName (the app only /// persists DisplayName), falling back to the default capture endpoint. /// public sealed class WasapiMicAudioSource : IAudioSource { private readonly Func _micNameProvider; private WasapiCapture? _capture; /// Returns the current mic DisplayName; read /// at each Start so a device picked mid-session takes effect immediately /// (the mixer restarts the mic on pick). public WasapiMicAudioSource(Func micNameProvider) { _micNameProvider = micNameProvider; } public event Action? Started; public event Action? SampleReady; public event Action? Failed; public void Start() { if (_capture != null) throw new InvalidOperationException("Already started."); try { var device = ResolveDevice(); _capture = device != null ? new WasapiCapture(device) : new WasapiCapture(); _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 MMDevice? ResolveDevice() { var micName = _micNameProvider(); if (string.IsNullOrWhiteSpace(micName)) return null; try { using var enumerator = new MMDeviceEnumerator(); foreach (var endpoint in enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active)) { if (string.Equals(endpoint.FriendlyName, micName, StringComparison.OrdinalIgnoreCase)) return endpoint; } } catch { } return null; } private void OnDataAvailable(object? sender, WaveInEventArgs e) { if (e.BytesRecorded <= 0) return; var format = _capture?.WaveFormat ?? WaveFormat.CreateIeeeFloatWaveFormat(48000, 1); 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); } }