using System; using System.Threading.Tasks; using System.Windows.Threading; namespace ytLive.Services; /// Per-session web capture pace: a DispatcherTimer that drops ticks while a /// capture is in flight (latest-wins). Raising the naive 10Hz cadence to ~30Hz would /// stack concurrent CapturePreviewAsync calls (a full 1920×1080 PNG encode costs /// ~10-30ms), so overlapping ticks are dropped, never queued; the effective cadence /// is max(interval, capture duration). The interval and the in-flight drop live here, /// not in the manager, so the scheduler is unit-testable without a WebView2 runtime. internal sealed class CaptureScheduler { private readonly Dispatcher _dispatcher; private readonly Func _capture; private DispatcherTimer? _timer; private int _intervalMs; private bool _inFlight; private Task _pending = Task.CompletedTask; public CaptureScheduler(Dispatcher dispatcher, int intervalMs, Func capture) { _dispatcher = dispatcher; _intervalMs = Math.Max(16, intervalMs); _capture = capture; } /// The task of the capture currently in flight, or a completed task when /// idle. Test seam: lets the caller await exactly the running capture. internal Task Pending => _pending; public void Start() { if (_timer != null) return; _timer = new DispatcherTimer( TimeSpan.FromMilliseconds(_intervalMs), DispatcherPriority.Background, (_, _) => Tick(), _dispatcher); _timer.Start(); } public void Stop() => _timer?.Stop(); public void SetInterval(int milliseconds) { _intervalMs = Math.Max(16, milliseconds); if (_timer != null) _timer.Interval = TimeSpan.FromMilliseconds(_intervalMs); } /// Runs the capture unless one is already in flight — the dropped tick is /// the latest-wins policy. Tick runs on the dispatcher thread (timer + test), so /// _inFlight needs no locking. internal void Tick() { if (_inFlight) return; _inFlight = true; _pending = RunCapture(); } private async Task RunCapture() { try { await _capture(); } finally { _inFlight = false; } } }