fix: webcam identity->device key for output frames; top bar always has a Start; light-pill grouping
Take two (2026-09-01) three confirmed defects, all from creator feedback + the
new resolver logging:
1. NO WEBCAM IN OUTPUT: WebcamSceneConfig.WebcamId carries the identity GUID;
CameraManager keys sessions by DEVICE id — GetLatestFrame(guid) returned null
forever, the compositor silently dropped the layer while the preview (bitmap
path) looked fine. The resolver logging added in 8dcaee0 caught it red-handed.
DeviceKeyForWebcam maps identity->device through the _webcam singleton (identity
mismatch / unknown ids pass through). Test: WebcamOutputKeyTests.
2. START BUTTON VANISHED AFTER STOP: my pills-clear ruling met the old
ShowPrimaryStartButton gate (needed IsConnected or a lit REC pill) — signed-out,
pills-off = blank top bar, no session reachable. Start is now ALWAYS the idle
face; unarmed Start records (local needs no account — no dead-end no-op); the
Sign In button folds into Start's context menu ('Sign in to YouTube', shown while
disconnected) with a dedicated SignInCommand. SessionTeardownTests extended:
stopped session must leave a reachable Start.
3. TOP BAR ORDER (creator spec): [sign light] REC [pill] [sign light] ON-AIR [pill]
— each reality lamp now sits in front of its own intent switch (was: two pills,
then two orphaned dots). Sign labels keep the shared StatusSignText style.
Tests: WebcamOutputKey, SessionTeardown, FramePump, WebcamMenuGate, GlobalHotkey,
BroadcastPullOut — 14/14 across the six classes, clean build 0 warnings.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Xunit;
|
||||
using ytLive.Models;
|
||||
using ytLive.Services;
|
||||
using ytLive.ViewModels;
|
||||
|
||||
namespace ytLive.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Take-2 fix (2026-09-01): configs reference the webcam by IDENTITY GUID while
|
||||
/// CameraManager sessions are keyed by DEVICE id — the output resolver looked up the
|
||||
/// wrong dictionary and the camera was structurally invisible on stream/recordings
|
||||
/// while the preview (bitmap path) looked fine. DeviceKeyForWebcam is the mapping;
|
||||
/// this test seeds a layout whose identity differs from its device id and asserts
|
||||
/// the map (plus unknown-id passthrough).
|
||||
/// </summary>
|
||||
[Collection("RealApp")]
|
||||
public sealed class WebcamOutputKeyTests
|
||||
{
|
||||
private readonly RealAppHost _app;
|
||||
|
||||
public WebcamOutputKeyTests(RealAppHost app) => _app = app;
|
||||
|
||||
[Fact]
|
||||
public void DeviceKeyForWebcam_Maps_Identity_To_Device()
|
||||
{
|
||||
_app.Run(Run);
|
||||
}
|
||||
|
||||
private void Run()
|
||||
{
|
||||
var tempDb = Path.Combine(Path.GetTempPath(), $"ytLlive-wckey-{Guid.NewGuid():N}.db");
|
||||
MainViewModel.LayoutPathOverride = tempDb;
|
||||
var window = default(MainWindow);
|
||||
try
|
||||
{
|
||||
using (var schema = new LayoutStore(tempDb)) { }
|
||||
SqliteConnection.ClearAllPools();
|
||||
Seed(tempDb);
|
||||
|
||||
window = new MainWindow();
|
||||
var vm = (MainViewModel)window.DataContext;
|
||||
|
||||
Assert.Equal("dev-physical-9", vm.DeviceKeyForWebcam("wid-identity-7"));
|
||||
Assert.Equal("unknown-id", vm.DeviceKeyForWebcam("unknown-id"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (window != null) window.Close();
|
||||
MainViewModel.LayoutPathOverride = null;
|
||||
SqliteConnection.ClearAllPools();
|
||||
try { File.Delete(tempDb); } catch { /* best-effort */ }
|
||||
}
|
||||
}
|
||||
|
||||
private static void Seed(string path)
|
||||
{
|
||||
using var connection = new SqliteConnection($"Data Source={path}");
|
||||
connection.Open();
|
||||
|
||||
var scenes = new (string Id, string Name)[]
|
||||
{
|
||||
("scene-starting", "Starting"),
|
||||
("scene-live", "Live"),
|
||||
("scene-brb", "BRB"),
|
||||
("scene-chat", "Chat"),
|
||||
("scene-ending", "Ending"),
|
||||
};
|
||||
foreach (var (id, name) in scenes)
|
||||
{
|
||||
using var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = "INSERT INTO Scene (Id, Name, HasBackground, SortOrder) VALUES ($id, $name, 1, $order);";
|
||||
cmd.Parameters.AddWithValue("$id", id);
|
||||
cmd.Parameters.AddWithValue("$name", name);
|
||||
cmd.Parameters.AddWithValue("$order", Array.IndexOf(scenes, (id, name)));
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
using (var webcam = connection.CreateCommand())
|
||||
{
|
||||
webcam.CommandText = "INSERT INTO Webcam (Id, DeviceId, Name) VALUES ('wid-identity-7', 'dev-physical-9', 'Cam');";
|
||||
webcam.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
using (var config = connection.CreateCommand())
|
||||
{
|
||||
config.CommandText =
|
||||
@"INSERT INTO WebcamSceneConfig
|
||||
(SceneId, WebcamId, IsVisible, X, Y, Width, Height, Opacity, SortOrder)
|
||||
VALUES ('scene-live', 'wid-identity-7', 1, 100, 100, 480, 270, 1, 0);";
|
||||
config.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user