feat(65): the replay layer exists in the model; the exclusion problem is solved by the guard

Slice 5a, scaffolding only. 607 green.

- `Source.IsReplayLayer` + schema column + roundtrip: a media layer that shows saved clips instead of a
  chosen file. The compositor needs to know nothing — it is a media layer whose path the VM owns.
- **Two spec corrections from the creator (2026-11-10), recorded in the task:**
  1. The immediate replay of a fresh clip happens **only in the Live scene**. While it plays, **F10 is
     inert** ("replay in progress").
  2. That guard replaces the "layer excluded from its own clip" idea, which is **impossible** with one
     encoder and two muxes: the clip is a second mux of the same frames, so it necessarily contains what
     viewers saw. No second encode, no fiction about a clean feed.

Remaining for 65: the add-layer menu entry, `ClipSaved` → play (Live: once then hide; elsewhere: loop the
kept set), `SessionCompleted` → advance or hide, and silent-by-default audio.
This commit is contained in:
2026-11-10 12:48:41 -08:00
parent 28657918f9
commit a5c2061b55
5 changed files with 34 additions and 5 deletions
+4 -2
View File
@@ -106,7 +106,7 @@ public partial class LayoutStore : IDisposable
TextContent, TextFontFamily, TextFontSize, TextColor, TextBold, TextItalic,
TextUnderline, TextAlignment, TextVerticalAlignment, MediaMuted,
ChromaKeyEnabled, ChromaKeyColor, ChromaKeySimilarity,
ChromaKeySoftness, ChromaKeySpill, ChromaScreen)
ChromaKeySoftness, ChromaKeySpill, ChromaScreen, IsReplayLayer)
VALUES ($id, $sceneId, $assetId, $type, $name, $isEnabled,
$x, $y, $w, $h, $opacity, $monitor,
$clip, $mirrored, $isBackground, $captureKey, $showDesktop, $sort,
@@ -118,7 +118,7 @@ public partial class LayoutStore : IDisposable
$textContent, $textFontFamily, $textFontSize, $textColor, $textBold, $textItalic,
$textUnderline, $textAlignment, $textVerticalAlignment, $mediaMuted,
$chromaEnabled, $chromaColor, $chromaSimilarity, $chromaSoftness,
$chromaSpill, $chromaScreen)
$chromaSpill, $chromaScreen, $isReplayLayer)
""";
cmd.Transaction = tx;
var idP = cmd.Parameters.Add("$id", SqliteType.Text);
@@ -158,6 +158,7 @@ public partial class LayoutStore : IDisposable
var chromaSoftnessP = cmd.Parameters.Add("$chromaSoftness", SqliteType.Real);
var chromaSpillP = cmd.Parameters.Add("$chromaSpill", SqliteType.Integer);
var chromaScreenP = cmd.Parameters.Add("$chromaScreen", SqliteType.Integer);
var replayLayerP = cmd.Parameters.Add("$isReplayLayer", SqliteType.Integer);
var mediaPlaybackStateP = cmd.Parameters.Add("$mediaPlaybackState", SqliteType.Text);
var alertVideoAssetIdP = cmd.Parameters.Add("$alertVideoAssetId", SqliteType.Text);
var alertVideoPathP = cmd.Parameters.Add("$alertVideoPath", SqliteType.Text);
@@ -226,6 +227,7 @@ public partial class LayoutStore : IDisposable
chromaSoftnessP.Value = source.ChromaKeySoftness;
chromaSpillP.Value = source.ChromaKeySpill ? 1 : 0;
chromaScreenP.Value = (int)source.ChromaScreen;
replayLayerP.Value = source.IsReplayLayer ? 1 : 0;
mediaPlaybackStateP.Value = source.MediaPlaybackState.ToString();
alertVideoAssetIdP.Value = (object?)source.AlertVideoAssetId ?? DBNull.Value;
alertVideoPathP.Value = (object?)source.AlertVideoPath ?? DBNull.Value;