TASK 21 slice 2b: native-FPS pacing in MediaVideoSource (probe -> delay seam)

- MediaVideoSource takes optional IFrameRateProbe? + Func<TimeSpan,CancellationToken,Task>? delay
  seams (default Task.Delay); probes FPS once in RunAsync, delays by 1/fps after each
  emitted frame. No probe/unknown fps -> no pacing (ffmpeg pipe backpressure throttles).
- Test: MediaVideoSource_PacesFramesByProbedFps (fake probe returns 1000fps + recording
  delay; one delay per frame ~= 1ms). Media tests 5/5, build 0 warnings.

Derivative reference: per-frame delay pacing of decoded output, standard in media playback.
This commit is contained in:
2026-08-31 19:47:38 -07:00
parent 8fa78423e6
commit a2219be198
5 changed files with 68 additions and 12 deletions
+7 -4
View File
@@ -683,12 +683,15 @@ seam:** `Func<Scene?>`, `Func<SceneElement, VideoFrame?>` resolver, `Func<Compos
the pipe produces them. Wired into the live pipeline (slice 1 step 4): `ResolveOutputFrame` reads
`_mediaManager.GetLatestFrame(MediaPath)` for a `MediaSource`, and `Source.DisplaySource`
(`Models/Source.cs`) routes `VideoImageSource` for `MediaSource` so the preview/canvas show decoded video.
Slice 2a shipped the probe seam: `FfmpegFrameRateParser` (pure, `avg_frame_rate=` then `r_frame_rate=`
rational N/N/M, unknown→null) + `IFrameRateProbe`/`FfmpegFrameRateProbe` (derives sibling `ffprobe.exe`
Slice 2a shipped the probe seam: `FfmpegFrameRateParser` (pure, `avg_frame_rate=` then
`r_frame_rate=` rational N/N/M, unknown→null) + `IFrameRateProbe`/`FfmpegFrameRateProbe` (derives sibling `ffprobe.exe`
from the located ffmpeg dir, reuses the `IDecodeProcess` seam for the ffprobe subprocess text; null if
ffprobe absent) + `FfmpegLocator.ProbeFileName` now also extracts `ffprobe.exe` from the pinned archive
(conditional — old caches without it just get no pacing).
Still open: session acquisition on add/remove (UI picker), native-FPS pacing injection (probe→delay, slice 2b), loop control.
(conditional — old caches without it just get no pacing). Slice 2b wired pacing: `MediaVideoSource`
takes optional `IFrameRateProbe?` + `Func<TimeSpan,CancellationToken,Task>? delay` (default `Task.Delay`)
seams, probes FPS once in `RunAsync`, and delays by 1/fps after each emitted frame; no probe/unknown → no
pacing (ffmpeg's own pipe backpressure already throttles the decode).
Still open: session acquisition on add/remove (UI picker), loop control.
- **Social bar on the output (bar bug-fix branch):** the `FramePump` takes an optional
`socialBar: Func<(VideoFrame? Frame, SocialBarPosition Position)>?` seam, re-read **every frame** (so a
mid-stream position flip applies immediately). The strip is pre-rasterized by `Compositor/SocialBarRenderer.cs`