93 lines
2.4 KiB
C#
93 lines
2.4 KiB
C#
using ytLive.Services;
|
|
|
|
namespace ytLive.Services.Audio;
|
|
|
|
/// <summary>
|
|
/// Owns the mic + desktop/game capture sources (TASK 4 ship step 4) and drives
|
|
/// the live mic meter. Runs only while live: started when go-live succeeds,
|
|
/// stopped on end-stream. Mic samples are level-metered and forwarded; loopback
|
|
/// samples are currently dropped (consumed by the encoder mix in a later step).
|
|
/// </summary>
|
|
public sealed class AudioMixer : IDisposable
|
|
{
|
|
private readonly IAudioSource _mic;
|
|
private readonly IAudioSource _loopback;
|
|
private readonly AudioLevelMeter _meter;
|
|
private readonly Action<string>? _log;
|
|
private bool _started;
|
|
|
|
public AudioMixer(IAudioSource mic, IAudioSource loopback, Action<string>? log = null)
|
|
{
|
|
_mic = mic;
|
|
_loopback = loopback;
|
|
_meter = new AudioLevelMeter();
|
|
_log = log;
|
|
|
|
_mic.SampleReady += OnMicSample;
|
|
_loopback.SampleReady += OnLoopbackSample;
|
|
_mic.Failed += OnMicFailed;
|
|
_loopback.Failed += OnLoopbackFailed;
|
|
}
|
|
|
|
/// <summary>Current smoothed mic level (0..1).</summary>
|
|
public float MicLevel => _meter.Level;
|
|
|
|
/// <summary>Raised whenever the smoothed mic level changes.</summary>
|
|
public event Action<float>? MicLevelChanged;
|
|
|
|
public void Start()
|
|
{
|
|
if (_started)
|
|
return;
|
|
|
|
_started = true;
|
|
_meter.Reset();
|
|
_loopback.Start();
|
|
_mic.Start();
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
if (!_started)
|
|
return;
|
|
|
|
_started = false;
|
|
_mic.Stop();
|
|
_loopback.Stop();
|
|
_meter.Reset();
|
|
MicLevelChanged?.Invoke(0);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Stop();
|
|
_mic.SampleReady -= OnMicSample;
|
|
_loopback.SampleReady -= OnLoopbackSample;
|
|
_mic.Failed -= OnMicFailed;
|
|
_loopback.Failed -= OnLoopbackFailed;
|
|
_mic.Dispose();
|
|
_loopback.Dispose();
|
|
}
|
|
|
|
private void OnMicSample(AudioSample sample)
|
|
{
|
|
MicLevelChanged?.Invoke(_meter.Push(sample));
|
|
}
|
|
|
|
private void OnLoopbackSample(AudioSample sample)
|
|
{
|
|
// Desktop/game audio: captured for the future encoder mix; no UI yet.
|
|
}
|
|
|
|
private void OnMicFailed(Exception ex)
|
|
{
|
|
_log?.Invoke($"Mic capture failed: {ex.Message}");
|
|
MicLevelChanged?.Invoke(0);
|
|
}
|
|
|
|
private void OnLoopbackFailed(Exception ex)
|
|
{
|
|
_log?.Invoke($"Desktop audio capture failed: {ex.Message}");
|
|
}
|
|
}
|