e2ecc248e0
Four builds (89/90 + two) burned proving the alert video was PERFECT: real h264 1280x720, 240 frames decoded, alert audio in the live mix, and the shipped asset byte-identical (md5 0ee1f496…) to the creator's llamacasty-dancingLlama-thankyou.mp4 with every sampled frame full bright content. Build 90's new diagnostics then showed the frame reaching BOTH consumers every second for the whole clip — `Alert preview: frame=680x200 a255` and `Output resolver: frame 680x200 playing=True` — an opaque, correctly-sized frame, handed over and never seen. Root cause: Controls/PreviewPane.xaml keeps the per-element Image Collapsed unless a DataTrigger fires, and there was no IsAlertBox trigger (only IsImageSource / IsChatBox / IsWebSource / IsWebcam). The alert box's Image was therefore Collapsed forever. Chat boxes rendered because they HAVE a trigger — that asymmetry is the whole clue, and it is why removing/re-adding the layer could never fix it. Fix: an IsAlertBox DataTrigger beside the IsChatBox one. Two Good Dog tests, because the existing fakes had been hiding this: - AlertBoxPreviewVisibilityTests (red `Expected: Visible / Actual: Collapsed`, now green) drives the real MainWindow + PreviewPane and asserts the bound Image is Visible — the first alert test that crosses the XAML at all. - AlertClipOutputTests is the first test in the repo to run a REAL codec: pinned ffmpeg generates a clip, the real AlertClipDecoder + AlertOverlayLayer + SceneCompositor composite it, and the box rect must be a colourful picture that DIFFERS from idle. It passed while the feature was broken in the app — which is exactly why it was needed: it exonerated decode+layer+compositor and pointed the hunt at the last hop. Also keeps the two once-per-second alert diagnostics (resolver + preview) that settled it, pending the creator's call on whether to keep them. Notes: the pinned BtbN ffmpeg has no libx264, so the test clip is -c:v mpeg4. SceneCompositor.Render fills its base with opaque black, so the test compares against an idle render rather than counting non-zero bytes. MediaSource has the same missing trigger in PreviewPane.xaml — left unfixed as out of scope, noted in HANDOFF. Full suite 324/324. Lesson recorded in MyMistakes.md rule (5): a producer that hands over a correct frame has still delivered nothing — when output arrives and the user still sees nothing, stop auditing the producer and audit the consumer's VISIBILITY.
113 lines
4.0 KiB
C#
113 lines
4.0 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Imaging;
|
|
using Microsoft.Data.Sqlite;
|
|
using Xunit;
|
|
using ytLive.Models;
|
|
using ytLive.ViewModels;
|
|
|
|
namespace ytLive.Tests;
|
|
|
|
/// <summary>
|
|
/// TASK 47 Good Dog (take three, 2026-09-26): the alert box's frame must be
|
|
/// DRAWN by the preview pane. The 14:26 and 14:28 live runs on build 90 proved
|
|
/// everything up to the last hop: <c>Alert preview: frame=680x200 a255</c> and
|
|
/// <c>Output resolver: frame 680x200 playing=True</c> every second for the whole
|
|
/// clip — a real, opaque, correctly-sized frame (the shipped llama asset decodes
|
|
/// bright, 1280x720 h264) handed to the canvas — and the box stayed blank.
|
|
/// Root cause: the preview's per-element <c>Image</c> only becomes Visible for
|
|
/// IsImageSource / IsChatBox / IsWebSource / IsWebcam, so an AlertBox's Image
|
|
/// stayed Collapsed and the bound frame was never rendered.
|
|
/// </summary>
|
|
[Collection("RealApp")]
|
|
public sealed class AlertBoxPreviewVisibilityTests
|
|
{
|
|
private readonly RealAppHost _app;
|
|
|
|
public AlertBoxPreviewVisibilityTests(RealAppHost app) => _app = app;
|
|
|
|
[Fact]
|
|
public void AlertBox_HandedAFrame_IsDrawnInThePreviewPane()
|
|
{
|
|
_app.Run(Run);
|
|
}
|
|
|
|
private void Run()
|
|
{
|
|
var tempDb = Path.Combine(Path.GetTempPath(), $"ytLlive-test-{Guid.NewGuid():N}.db");
|
|
MainViewModel.LayoutPathOverride = tempDb;
|
|
var window = new MainWindow();
|
|
var vm = (MainViewModel)window.DataContext;
|
|
try
|
|
{
|
|
window.Show();
|
|
window.UpdateLayout();
|
|
|
|
var box = new Source
|
|
{
|
|
Type = SourceType.AlertBox,
|
|
Name = "Stream Alerts",
|
|
X = 100,
|
|
Y = 100,
|
|
Width = 320,
|
|
Height = 180,
|
|
};
|
|
vm.StagedScene!.Elements.Add(box);
|
|
vm.SelectedElement = box;
|
|
|
|
// Exactly what the layer writes onto the source every tick while a clip
|
|
// plays: a real, opaque, box-sized frame.
|
|
box.VideoImageSource = SolidFrame(320, 180);
|
|
window.UpdateLayout();
|
|
|
|
var pane = (UserControl)window.FindName("PreviewPane")!;
|
|
var image = FindImageShowing(pane, box.VideoImageSource);
|
|
Assert.True(image != null,
|
|
"the preview pane never bound the alert box's frame to an Image");
|
|
Assert.Equal(Visibility.Visible, image!.Visibility);
|
|
Assert.True(image.IsVisible,
|
|
"the alert box's Image is Collapsed, so the frame is never rendered");
|
|
}
|
|
finally
|
|
{
|
|
window.Close();
|
|
MainViewModel.LayoutPathOverride = null;
|
|
SqliteConnection.ClearAllPools();
|
|
try { File.Delete(tempDb); } catch { /* best-effort cleanup */ }
|
|
}
|
|
}
|
|
|
|
private static WriteableBitmap SolidFrame(int w, int h)
|
|
{
|
|
var bitmap = new WriteableBitmap(w, h, 96, 96, PixelFormats.Bgra32, null);
|
|
var pixels = new byte[w * h * 4];
|
|
for (var i = 0; i < pixels.Length; i += 4)
|
|
{
|
|
pixels[i] = 40; // B
|
|
pixels[i + 1] = 90; // G
|
|
pixels[i + 2] = 200; // R
|
|
pixels[i + 3] = 255; // opaque
|
|
}
|
|
bitmap.WritePixels(new Int32Rect(0, 0, w, h), pixels, w * 4, 0);
|
|
return bitmap;
|
|
}
|
|
|
|
private static Image? FindImageShowing(DependencyObject parent, ImageSource? source)
|
|
=> Walk(parent, e => e is Image i && ReferenceEquals(i.Source, source)) as Image;
|
|
|
|
private static DependencyObject? Walk(DependencyObject parent, Func<DependencyObject, bool> predicate)
|
|
{
|
|
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
|
|
{
|
|
var child = VisualTreeHelper.GetChild(parent, i);
|
|
if (predicate(child)) return child;
|
|
var found = Walk(child, predicate);
|
|
if (found != null) return found;
|
|
}
|
|
return null;
|
|
}
|
|
}
|