TASK 21 slice 3: loop control in MediaVideoSource (process factory + Loop flag)

- IMediaFrameSource gains bool Looping.
- MediaVideoSource ctor takes Func<IDecodeProcess> processFactory instead of a
  single IDecodeProcess: a System.Diagnostics.Process can't be re-Start()ed, so
  each loop pass creates a fresh decoder. Decode wrapped in do-while(Looping):
  restart on natural EOF instead of raising Completed.
- Production wiring (MainViewModel media factory): passes the process factory
  AND FfmpegFrameRateProbe -- closes the slice-2b gap where production had no
  probe and therefore no pacing.
- Tests: loop test (single frame re-emits across passes, Completed only when
  loop cleared); fakes updated for the new interface member. Media tests 12/12,
  build 0 warnings.

Wiring Source.MediaIsLooping into the flag needs a manager-level per-path loop
provider -> lands with the UI-picker (acquisition) slice.

Derivative reference: looping media by restarting decode on EOF, standard in
playback/overlay tooling (OBS media source repeat).
This commit is contained in:
2026-08-31 19:52:10 -07:00
parent a2219be198
commit 881addb5b4
8 changed files with 98 additions and 28 deletions
+8 -2
View File
@@ -690,8 +690,14 @@ seam:** `Func<Scene?>`, `Func<SceneElement, VideoFrame?>` resolver, `Func<Compos
(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.
pacing (ffmpeg's own pipe backpressure already throttles the decode). Slice 3 added loop control:
`IMediaFrameSource.Looping` (bool); `MediaVideoSource` takes a `Func<IDecodeProcess>` process factory (a single
`Process` can't be re-`Start()`ed, so each loop pass creates a fresh decoder) and wraps the decode in a
`do…while (Looping)` — restart on natural EOF instead of raising `Completed`. Production wiring (MainViewModel
media factory): passes `() => new FfmpegDecodeProcess()` as the factory, `FfmpegFrameRateProbe(new FfmpegLocator(),
() => new FfmpegDecodeProcess())` as the probe.
Still open: session acquisition on add/remove (UI picker) + wiring `Source.MediaIsLooping` into
`IMediaFrameSource.Looping` (needs a manager-level per-path loop provider, comes with the picker slice).
- **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`