071ec8be0c
Wire the MediaVideoSourceManager into the live pipeline (new partial
ViewModels/MainViewModel.Media.cs mirrors Webcam/Background partials):
readonly _mediaManager assigned in the core ctor (decode at 1920x1080 master,
dispatcher-coalesced preview), OnMediaPreviewBitmapChanged adopts the shared
bitmap onto every IsMediaSource with a matching MediaPath, a
Source { IsMediaSource, MediaPath } case in ResolveOutputFrame feeds
GetLatestFrame, and the manager is disposed on shutdown.
Source.DisplaySource now routes VideoImageSource for MediaSource (Source.cs),
so media previews/canvas show decoded video.
Compositor needs no change: BlitContent UniformToFill-scales any frame to the
element rect. Decoder/manager/seam derived-work mirrors ScreenCaptureManager
(citation in commit for slice-step-3).
Test (one unit for this change, mirroring the live-capture case):
Source_DisplaySource_IsVideoImageSource_WhenMediaSource. 14/14 media +
DisplaySource tests pass, build 0 warnings. Docs (TASKS/HANDOFF/ai.md) updated.
183 lines
6.3 KiB
C#
183 lines
6.3 KiB
C#
using Xunit;
|
|
using ytLive.Models;
|
|
using ytLive.ViewModels;
|
|
|
|
namespace ytLive.Tests;
|
|
|
|
/// <summary>
|
|
/// The background is a permanent, non-deletable bottom layer holding live
|
|
/// desktop/game capture, and it exists only in the canonical Live scene (see
|
|
/// <see cref="SceneCatalog"/>). Every background-enabled scene
|
|
/// (<see cref="Scene.HasBackground"/>) is healed to have exactly one, inserted
|
|
/// first; it is never reorderable or removable by the UI. Scenes without the
|
|
/// flag never get one.
|
|
/// </summary>
|
|
public class BackgroundTests
|
|
{
|
|
[Fact]
|
|
public void EnsureBackground_OnEmptyScene_InsertsFullFrameBackground()
|
|
{
|
|
var scene = new Scene { Name = "S", HasBackground = true };
|
|
|
|
var background = MainViewModel.EnsureBackground(scene);
|
|
|
|
Assert.NotNull(background);
|
|
Assert.Single(scene.Elements);
|
|
Assert.Same(background, scene.Elements[0]);
|
|
Assert.True(background.IsBackground);
|
|
Assert.True(background.IsEnabled);
|
|
// Non-Live scenes get the static-art flavor; only Live captures.
|
|
Assert.Equal(SourceType.Background, background.Type);
|
|
Assert.Equal("Background", background.Name);
|
|
Assert.Equal(0, background.X);
|
|
Assert.Equal(0, background.Y);
|
|
Assert.Equal(1920, background.Width);
|
|
Assert.Equal(1080, background.Height);
|
|
}
|
|
|
|
[Fact]
|
|
public void EnsureBackground_ExistingBackground_IsIdempotent()
|
|
{
|
|
var scene = new Scene { Name = "S", HasBackground = true };
|
|
var first = MainViewModel.EnsureBackground(scene);
|
|
var second = MainViewModel.EnsureBackground(scene);
|
|
|
|
Assert.Same(first, second);
|
|
Assert.Single(scene.Elements);
|
|
}
|
|
|
|
[Fact]
|
|
public void EnsureBackground_HealsMissingBackgroundAtIndexZero()
|
|
{
|
|
var scene = new Scene { Name = "S", HasBackground = true };
|
|
var image = new Source { Name = "Logo", Type = SourceType.Image };
|
|
scene.Elements.Add(image);
|
|
|
|
MainViewModel.EnsureBackground(scene);
|
|
|
|
Assert.Equal(2, scene.Elements.Count);
|
|
Assert.True(((Source)scene.Elements[0]).IsBackground);
|
|
Assert.Same(image, scene.Elements[1]);
|
|
}
|
|
|
|
[Fact]
|
|
public void EnsureBackground_SceneWithoutHasBackground_ReturnsNullAndInsertsNothing()
|
|
{
|
|
var scene = new Scene { Name = "Ending", HasBackground = false };
|
|
|
|
var background = MainViewModel.EnsureBackground(scene);
|
|
|
|
Assert.Null(background);
|
|
Assert.Empty(scene.Elements);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(SourceType.DisplayCapture, true)]
|
|
[InlineData(SourceType.WindowCapture, true)]
|
|
[InlineData(SourceType.Background, false)]
|
|
[InlineData(SourceType.Image, false)]
|
|
[InlineData(SourceType.TextOverlay, false)]
|
|
public void Source_IsLiveCapture_OnlyForDisplayAndWindow(SourceType type, bool expected)
|
|
{
|
|
Assert.Equal(expected, new Source { Type = type }.IsLiveCapture);
|
|
}
|
|
|
|
[Fact]
|
|
public void Source_DisplaySource_IsVideoImageSource_WhenLiveCapture()
|
|
{
|
|
var source = new Source { Type = SourceType.DisplayCapture };
|
|
var bitmap = new System.Windows.Media.Imaging.WriteableBitmap(
|
|
2, 2, 96, 96, System.Windows.Media.PixelFormats.Bgra32, null);
|
|
source.VideoImageSource = bitmap;
|
|
|
|
Assert.Same(bitmap, source.DisplaySource);
|
|
}
|
|
|
|
// TASK 21: a media source's DisplaySource routes its live video frames (like
|
|
// Display/Window capture), so media previews show decoded video, not the stale
|
|
// default art.
|
|
[Fact]
|
|
public void Source_DisplaySource_IsVideoImageSource_WhenMediaSource()
|
|
{
|
|
var source = new Source { Type = SourceType.MediaSource };
|
|
var bitmap = new System.Windows.Media.Imaging.WriteableBitmap(
|
|
2, 2, 96, 96, System.Windows.Media.PixelFormats.Bgra32, null);
|
|
source.VideoImageSource = bitmap;
|
|
|
|
Assert.Same(bitmap, source.DisplaySource);
|
|
}
|
|
|
|
[Fact]
|
|
public void Source_DisplaySource_FallsBackToImage_WhenLiveCaptureWithNoVideoImage()
|
|
{
|
|
var source = new Source { Type = SourceType.DisplayCapture };
|
|
Assert.Null(source.DisplaySource);
|
|
}
|
|
|
|
[Fact]
|
|
public void Source_DisplaySource_IsImageSource_WhenNotLiveCapture()
|
|
{
|
|
var source = new Source { Type = SourceType.Background };
|
|
var bitmap = new System.Windows.Media.Imaging.WriteableBitmap(
|
|
2, 2, 96, 96, System.Windows.Media.PixelFormats.Bgra32, null);
|
|
source.VideoImageSource = bitmap;
|
|
|
|
Assert.NotSame(bitmap, source.DisplaySource);
|
|
Assert.Null(source.DisplaySource);
|
|
}
|
|
|
|
[Fact]
|
|
public void Source_TypeChange_RaisesDisplaySource()
|
|
{
|
|
var source = new Source { Type = SourceType.Image };
|
|
var raised = new List<string>();
|
|
source.PropertyChanged += (_, e) => raised.Add(e.PropertyName ?? string.Empty);
|
|
|
|
source.Type = SourceType.DisplayCapture;
|
|
|
|
Assert.Contains(nameof(Source.IsLiveCapture), raised);
|
|
Assert.Contains(nameof(Source.DisplaySource), raised);
|
|
}
|
|
|
|
[Fact]
|
|
public void AllScenes_BackgroundElement_HasIsBackgroundTrue()
|
|
{
|
|
foreach (var name in SceneCatalog.All)
|
|
{
|
|
var scene = new Scene { Name = name, HasBackground = SceneCatalog.HasBackground(name) };
|
|
var bg = new Source { Name = "Background", Type = SourceType.Background };
|
|
scene.Elements.Add(bg);
|
|
|
|
Assert.True(bg.IsBackground,
|
|
$"{name}: Background element must have IsBackground = true for properties panel");
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void AllScenes_BackgroundElement_IsBackgroundDerivedFromType()
|
|
{
|
|
var bg = new Source { Type = SourceType.Background };
|
|
Assert.True(bg.IsBackground);
|
|
|
|
var nonBg = new Source { Type = SourceType.Image };
|
|
Assert.False(nonBg.IsBackground);
|
|
|
|
var display = new Source { Type = SourceType.DisplayCapture };
|
|
Assert.False(display.IsBackground);
|
|
}
|
|
|
|
[Fact]
|
|
public void AllScenes_BackgroundElement_SupportsUseDefaultBackground()
|
|
{
|
|
foreach (var name in SceneCatalog.All)
|
|
{
|
|
var scene = new Scene { Name = name };
|
|
var bg = new Source { Name = "Background", Type = SourceType.Background };
|
|
scene.Elements.Add(bg);
|
|
|
|
Assert.True(bg.UseDefaultBackground,
|
|
$"{name}: Background element must default to UseDefaultBackground = true");
|
|
}
|
|
}
|
|
}
|