feat(media): add a video file layer that decodes into the scene
TASK 21's picker slice: the decoder, manager, pacing and loop mechanism were
already shipped but nothing ever acquired a session, so a media Source rendered
nothing. Adds "Video File" to the (+) menu (video-filtered OpenFileDialog),
one decode session per distinct path with a HasSession guard, release when the
last scene reference goes, and a post-load reconcile. Loop is a manager-level
per-path answer ("any live reference loops") applied before StartAsync and
updated live via SetLooping. Properties panel shows the file with Change… and
Loop. Volume stays unwired: media audio rides the desktop loopback, TRAX-style.
Reference: OBS's media source (file → decode → composite into the scene) is the
established answer for this feature; the lifecycle shape mirrors this repo's
WebView2/ScreenCapture registration rather than inventing a new one.
Test: ytLive.Tests/MediaSourceAcquisitionTests.cs — add starts one decoder and
frames reach the layer, loop flip reaches the decoder, remove stops it.
Docs: task-21 closed, TASKS row + Day-4 row + stale 3·17 row corrected,
frame-pipeline acquisition note, HANDOFF rewritten. verify.sh green.
This commit is contained in:
@@ -790,6 +790,17 @@ Full suite 290/291 passing, the sole failure the pre-existing compositor pixel t
|
||||
() => 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).
|
||||
**Acquisition shipped 2026-11-05:** `AddMediaCommand` (`MainViewModel.Media.cs`) opens a video-filtered
|
||||
`OpenFileDialog` and `AddMediaSource(path)` adds a `MediaSource` layer through the ordinary `AddSource`
|
||||
path, defaulting to a full-canvas 1920x1080 rect, then `RegisterMediaSource` subscribes the layer and
|
||||
`EnsureMediaSession` starts one decoder per **distinct path** (`HasSession` guard keeps refcounts at 0/1).
|
||||
Remove (`RemoveElement`) unsubscribes and releases only when no scene references the file any more;
|
||||
`ReacquireMediaSources` runs after `LoadLayout` so persisted layers get sessions and stale files are
|
||||
released (`GetActiveKeys`). Loop is a manager-level per-path answer: the ctor takes
|
||||
`Func<string,bool>? loopProvider` (production: "any live media reference loops"), applied before
|
||||
`StartAsync`, and `SetLooping(key, bool)` applies a live toggle. A file swap releases the old path after
|
||||
the layer no longer references it. Volume stays unwired on purpose — sound rides the desktop loopback
|
||||
(TRAX-style), so a per-file slider would be a control with no effect.
|
||||
- **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`
|
||||
|
||||
Reference in New Issue
Block a user