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;
}
}
}