namespace ytLive.Services;
///
/// Default : samples a full-screen monitor
/// provider (the existing IFullScreenDetector) and the live loopback
/// level and advances a . The pure transitions
/// live in GameAudioHysteresis (unit-tested); this wrapper owns the providers
/// and the raised-changed event. WPF-free — the VM owns the poll timer.
///
public sealed class GameAudioDetector : IGameAudioDetector
{
private const float SoundFloor = 0.005f;
private readonly Func _foregroundFullScreenMonitorProvider;
private readonly Func _loopbackLevelProvider;
private readonly Func _now;
private readonly GameAudioHysteresis _hysteresis = new();
/// Returns the monitor a
/// full-screen foreground window covers, or null when windowed/none.
/// Current smoothed desktop/game level (0..1).
/// Clock for the hysteresis windows; injectable for tests.
public GameAudioDetector(
Func foregroundFullScreenMonitorProvider,
Func loopbackLevelProvider,
Func? now = null)
{
_foregroundFullScreenMonitorProvider = foregroundFullScreenMonitorProvider;
_loopbackLevelProvider = loopbackLevelProvider;
_now = now ?? (() => DateTime.Now);
}
public bool IsGameAudioActive => _hysteresis.IsActive;
public event Action? IsGameAudioActiveChanged;
public void Poll()
{
var before = _hysteresis.IsActive;
var isFullScreen = _foregroundFullScreenMonitorProvider() != null;
var hasSound = _loopbackLevelProvider() > SoundFloor;
_hysteresis.Update(isFullScreen, hasSound, _now());
if (before != _hysteresis.IsActive)
IsGameAudioActiveChanged?.Invoke(_hysteresis.IsActive);
}
}