TASK 4 ship step 4: WASAPI audio capture — NAudio loopback + mic behind an IAudioSource seam, AudioMixer driving AudioLevel while live, pure level meter + WaveToFloat, NAudio.Wasapi 2.2.1 (MIT, notices item 9) — 139 tests passing, 0 warnings
This commit is contained in:
@@ -14,6 +14,7 @@ using Microsoft.Win32;
|
||||
using ytLive.Helpers;
|
||||
using ytLive.Models;
|
||||
using ytLive.Services;
|
||||
using ytLive.Services.Audio;
|
||||
|
||||
namespace ytLive.ViewModels;
|
||||
|
||||
@@ -79,6 +80,12 @@ public class MainViewModel : ViewModelBase
|
||||
private SocialsConfig? _socials;
|
||||
private readonly IMicrophoneEnumerator _microphoneEnumerator;
|
||||
|
||||
// Live audio capture (TASK 4 ship step 4): desktop/game via WASAPI loopback,
|
||||
// mic via WASAPI capture, both owned by the mixer and running only while
|
||||
// live. Mic level feeds AudioLevel (the meter); loopback is for the future
|
||||
// encoder mix. Private by design — no UI beyond the existing mic controls.
|
||||
private readonly AudioMixer _audioMixer;
|
||||
|
||||
// Screen backdrop: a permanent live capture (desktop/game) that every scene
|
||||
// shows at the bottom layer. One shared capture session per key — the
|
||||
// ScreenCaptureManager refcounts by key, mirroring CameraManager.
|
||||
@@ -832,6 +839,12 @@ public class MainViewModel : ViewModelBase
|
||||
|
||||
_microphoneEnumerator = new WinRtMicrophoneEnumerator();
|
||||
|
||||
_audioMixer = new AudioMixer(
|
||||
new WasapiMicAudioSource(() => MicSourceName),
|
||||
new WasapiLoopbackAudioSource(),
|
||||
message => AppLog.Write(message));
|
||||
_audioMixer.MicLevelChanged += OnMicLevelChanged;
|
||||
|
||||
_fullScreenDetector = new Win32FullScreenDetector();
|
||||
foreach (var display in _fullScreenDetector.GetDisplays())
|
||||
Displays.Add(display);
|
||||
@@ -1725,6 +1738,7 @@ public class MainViewModel : ViewModelBase
|
||||
? "ytLlive"
|
||||
: $"{dialog.StreamTitle} — ytLlive";
|
||||
StreamStatus = StreamStatus.Streaming;
|
||||
_audioMixer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1732,6 +1746,7 @@ public class MainViewModel : ViewModelBase
|
||||
{
|
||||
StreamStatus = StreamStatus.Offline;
|
||||
WindowTitle = "ytLlive";
|
||||
_audioMixer.Stop();
|
||||
// Graceful end completes the session = signs out (the DPAPI token is
|
||||
// cleared so the next Start Stream requires a fresh sign-in). A crash
|
||||
// never runs this, so the token survives and the creator stays signed in.
|
||||
@@ -1742,6 +1757,17 @@ public class MainViewModel : ViewModelBase
|
||||
AppLog.Write("Stream ended; session signed out");
|
||||
}
|
||||
|
||||
private void OnMicLevelChanged(float level)
|
||||
{
|
||||
// NAudio raises on its capture thread; marshal to the UI thread so the
|
||||
// meter binding updates safely.
|
||||
var dispatcher = System.Windows.Application.Current?.Dispatcher;
|
||||
if (dispatcher != null && !dispatcher.CheckAccess())
|
||||
dispatcher.BeginInvoke(() => AudioLevel = level);
|
||||
else
|
||||
AudioLevel = level;
|
||||
}
|
||||
|
||||
private void UpdateLiveVisuals()
|
||||
{
|
||||
var live = IsLive;
|
||||
|
||||
Reference in New Issue
Block a user