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:
@@ -117,7 +117,7 @@ public partial class LayoutStore : IDisposable
|
||||
TextContent, TextFontFamily, TextFontSize, TextColor, TextBold, TextItalic,
|
||||
TextUnderline, TextAlignment, TextVerticalAlignment, MediaMuted,
|
||||
ChromaKeyEnabled, ChromaKeyColor, ChromaKeySimilarity, ChromaKeySoftness,
|
||||
ChromaKeySpill, ChromaScreen
|
||||
ChromaKeySpill, ChromaScreen, IsReplayLayer
|
||||
FROM Source ORDER BY SortOrder
|
||||
""";
|
||||
using var reader = cmd.ExecuteReader();
|
||||
@@ -198,6 +198,8 @@ public partial class LayoutStore : IDisposable
|
||||
ChromaScreen = reader.IsDBNull(reader.GetOrdinal("ChromaScreen"))
|
||||
? ChromaScreen.Green
|
||||
: (ChromaScreen)reader.GetInt32(reader.GetOrdinal("ChromaScreen")),
|
||||
IsReplayLayer = !reader.IsDBNull(reader.GetOrdinal("IsReplayLayer"))
|
||||
&& reader.GetInt32(reader.GetOrdinal("IsReplayLayer")) != 0,
|
||||
};
|
||||
if (!sourcesByScene.TryGetValue(sceneId, out var list))
|
||||
sourcesByScene[sceneId] = list = new List<(Source, int)>();
|
||||
|
||||
@@ -48,6 +48,7 @@ public partial class LayoutStore : IDisposable
|
||||
ClipShape TEXT NOT NULL DEFAULT 'Traditional',
|
||||
IsMirrored INTEGER NOT NULL DEFAULT 0,
|
||||
IsBackground INTEGER NOT NULL DEFAULT 0,
|
||||
IsReplayLayer INTEGER NOT NULL DEFAULT 0,
|
||||
CaptureKey TEXT,
|
||||
SortOrder INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
@@ -320,6 +321,14 @@ public partial class LayoutStore : IDisposable
|
||||
cmd.CommandText = "ALTER TABLE Source ADD COLUMN ChatSuperChatColor TEXT NOT NULL DEFAULT '#eab308';";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
// TASK 65: a media layer that plays the replay clips instead of a chosen file.
|
||||
if (!columns.Contains("IsReplayLayer"))
|
||||
{
|
||||
using var cmd = _connection.CreateCommand();
|
||||
cmd.CommandText = "ALTER TABLE Source ADD COLUMN IsReplayLayer INTEGER NOT NULL DEFAULT 0;";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
if (!columns.Contains("ShowDesktop"))
|
||||
{
|
||||
using var cmd = _connection.CreateCommand();
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user