feat(65): the replay layer plays — once in Live, looping everywhere else

608 green, 0 warnings. Completes the layer half of TASK 65.

- **(+) → Replay Clip** adds an ordinary media layer with an app-owned path, one per scene, silent by
  default (`MediaMuted`) and looping only outside Live — the compositor needs no special case.
- **F10 saves → the Live layer plays it once, then hides** (`SessionCompleted` → invisible, guard
  cleared). Every other scene with a replay layer keeps looping the kept set, advancing on each clip's
  completion, wrapping at the end.
- **The guard the creator asked for:** while that Live replay is playing, **F10 is inert** with a plain
  explanation. That is what replaces the impossible "exclude the layer from its own clip" — with one
  encoder and two muxes the clip *is* the program, so the fix is to not clip a clip.

`ReplayLayerTests` drives the real VM: one layer per scene, muted by default, once-through in Live,
looping in BRB, and a stray F10 with clips off writes nothing.
This commit is contained in:
2026-11-10 12:54:54 -08:00
parent a5c2061b55
commit 5fcfa545e5
5 changed files with 163 additions and 0 deletions
+2
View File
@@ -125,6 +125,8 @@
<MenuItem Header="Image" Command="{Binding AddImageCommand}"/>
<MenuItem Header="Video File" Command="{Binding AddMediaCommand}"
ToolTip="Play a video file in the scene (mp4, mov, avi, mkv, webm)"/>
<MenuItem Header="Replay Clip" Command="{Binding AddReplayLayerCommand}"
ToolTip="Plays saved replay clips: once in Live, looping in other scenes"/>
<MenuItem Header="Text" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.TextOverlay}"/>
<MenuItem Header="Countdown" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.Countdown}"
Visibility="{Binding IsCountdownAvailable, Converter={StaticResource BoolToVis}}"