TASK 4 audio follow-up: game audio bar + mic status dot + always-on capture — the footer's second audio control (desktop/game, a mirror of the mic bar: meter + mute + volume) appears only while a full-screen game is producing sound (IGameAudioDetector seam + GameAudioHysteresis: show ~500ms of fullscreen+sound, hide ~1s after leaving fullscreen, silence never hides an active bar; VM polls on a 250ms timer); capture now runs for the app's lifetime so both meters preview live (started at startup via StartMicCaptureAsync, disposed in Shutdown — no longer go-live driven); MIC label is a button with a status dot (Models/MicStatus: green via the source Started event, yellow = mic problem, red = no device); PickMicrophone swaps the live device immediately via AudioMixer.RestartMic; fixed a latent ?.Invoke(meter.Push(...)) short-circuit that skipped the meter update when nothing subscribed — 167 tests passing, 0 warnings

This commit is contained in:
2026-08-13 11:29:44 -07:00
parent ea250c02a6
commit 9f9ed34627
20 changed files with 897 additions and 82 deletions
+76
View File
@@ -0,0 +1,76 @@
using Xunit;
using ytLive.Services;
namespace ytLive.Tests;
/// <summary>
/// TASK 4 game audio bar: the default detector's provider wiring — it composes
/// the full-screen monitor + loopback level into the hysteresis and raises
/// IsGameAudioActiveChanged on transitions. The transition math itself lives in
/// GameAudioHysteresisTests.
/// </summary>
public class GameAudioDetectorTests
{
[Fact]
public void Poll_RaisesChanged_WhenGameAppearsAndLeaves()
{
var now = new DateTime(2026, 8, 13, 12, 0, 0);
int? monitor = 0;
var level = 0f;
var detector = new GameAudioDetector(() => monitor, () => level, () => now);
var changes = new List<bool>();
detector.IsGameAudioActiveChanged += a => changes.Add(a);
level = 0.9f;
detector.Poll();
Assert.False(detector.IsGameAudioActive);
now = now.AddMilliseconds(600);
detector.Poll();
Assert.True(detector.IsGameAudioActive);
Assert.Equal(new[] { true }, changes);
level = 0f;
now = now.AddSeconds(2);
detector.Poll();
Assert.True(detector.IsGameAudioActive);
Assert.Equal(new[] { true }, changes);
monitor = null;
detector.Poll();
Assert.True(detector.IsGameAudioActive);
now = now.AddSeconds(2);
detector.Poll();
Assert.False(detector.IsGameAudioActive);
Assert.Equal(new[] { true, false }, changes);
}
[Fact]
public void Poll_StaysInactive_WhenNoFullScreenMonitor()
{
var detector = new GameAudioDetector(() => null, () => 0.9f);
var changes = 0;
detector.IsGameAudioActiveChanged += _ => changes++;
for (var i = 0; i < 5; i++)
detector.Poll();
Assert.False(detector.IsGameAudioActive);
Assert.Equal(0, changes);
}
[Fact]
public void Poll_StaysInactive_WhileLoopbackSilent()
{
var detector = new GameAudioDetector(() => 0, () => 0f);
var changes = 0;
detector.IsGameAudioActiveChanged += _ => changes++;
for (var i = 0; i < 5; i++)
detector.Poll();
Assert.False(detector.IsGameAudioActive);
Assert.Equal(0, changes);
}
}