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:
@@ -188,6 +188,8 @@ public partial class MainViewModel : ViewModelBase
|
||||
public ICommand AddSceneCommand { get; }
|
||||
public ICommand AddSourceCommand { get; }
|
||||
public ICommand AddImageCommand { get; }
|
||||
public ICommand AddMediaCommand { get; }
|
||||
public ICommand ChangeMediaFileCommand { get; }
|
||||
public ICommand RemoveSourceCommand { get; }
|
||||
public ICommand EditElementCommand { get; }
|
||||
public ICommand ToggleElementVisibilityCommand { get; }
|
||||
@@ -260,6 +262,10 @@ public partial class MainViewModel : ViewModelBase
|
||||
AddSourceCommand = new RelayCommand(parameter => AddSource(parameter));
|
||||
AddWebcamCommand = new RelayCommand(_ => _ = AddWebcamToStagedSceneAsync());
|
||||
AddImageCommand = new RelayCommand(_ => AddImage());
|
||||
AddMediaCommand = new RelayCommand(_ => AddMedia());
|
||||
ChangeMediaFileCommand = new RelayCommand(
|
||||
element => ChangeMediaFile(element as Source),
|
||||
element => element is Source { IsMediaSource: true });
|
||||
RemoveSourceCommand = new RelayCommand(element => { PushPreviewUndo(); RemoveElement(element as SceneElement); });
|
||||
EditElementCommand = new RelayCommand(element => BeginEditElement(element as SceneElement));
|
||||
ToggleElementVisibilityCommand = new RelayCommand(element => { PushPreviewUndo(); ToggleElementVisibility(element as SceneElement); });
|
||||
@@ -396,12 +402,14 @@ public partial class MainViewModel : ViewModelBase
|
||||
_screenCaptureManager.CaptureFailed += OnScreenCaptureFailed;
|
||||
|
||||
_mediaManager = new MediaVideoSourceManager(
|
||||
path => new MediaVideoSource(
|
||||
MediaFrameSourceFactoryOverride ?? (path => new MediaVideoSource(
|
||||
path, MediaDecodeWidth, MediaDecodeHeight,
|
||||
new FfmpegLocator(), () => new FfmpegDecodeProcess(),
|
||||
frameRateProbe: new FfmpegFrameRateProbe(
|
||||
new FfmpegLocator(), () => new FfmpegDecodeProcess())),
|
||||
System.Windows.Application.Current?.Dispatcher);
|
||||
new FfmpegLocator(), () => new FfmpegDecodeProcess()))),
|
||||
System.Windows.Application.Current?.Dispatcher,
|
||||
// Loop intent is per Source; the manager needs the per-*path* answer.
|
||||
AnyMediaReferenceLoops);
|
||||
_mediaManager.PreviewBitmapChanged += OnMediaPreviewBitmapChanged;
|
||||
_mediaManager.MediaFailed += OnMediaFailed;
|
||||
|
||||
@@ -466,6 +474,10 @@ public partial class MainViewModel : ViewModelBase
|
||||
/// real ONNX model. Mirrors <see cref="CameraFrameSourceFactoryOverride"/>.</summary>
|
||||
internal static Services.BackgroundEffects.IPersonSegmenter? PersonSegmenterOverride { get; set; }
|
||||
|
||||
/// <summary>Test seam (TASK 21): a fake media decoder so a test can prove add/release
|
||||
/// lifecycle without spawning ffmpeg. Mirrors <see cref="CameraFrameSourceFactoryOverride"/>.</summary>
|
||||
internal static Func<string, IMediaFrameSource?>? MediaFrameSourceFactoryOverride { get; set; }
|
||||
|
||||
private void LoadLayout()
|
||||
{
|
||||
_isLoading = true;
|
||||
@@ -518,6 +530,7 @@ public partial class MainViewModel : ViewModelBase
|
||||
UpdateBackgroundImage();
|
||||
ReacquireWebcam();
|
||||
ReacquireScreenCaptures();
|
||||
ReacquireMediaSources();
|
||||
RegisterLoadedWebSources();
|
||||
_socials = _layoutStore.Socials;
|
||||
RenderSocialBarFrame();
|
||||
|
||||
Reference in New Issue
Block a user