feat(ui): wire DI bootstrap in App.xaml.cs and add Windows solution filter
Some checks failed
CI / build-and-test (push) Failing after 36s
Some checks failed
CI / build-and-test (push) Failing after 36s
This commit is contained in:
parent
d64b110550
commit
e3321ff279
5 changed files with 151 additions and 17 deletions
13
TeamsISO.Windows.slnf
Normal file
13
TeamsISO.Windows.slnf
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"solution": {
|
||||
"path": "TeamsISO.sln",
|
||||
"projects": [
|
||||
"src/TeamsISO.Engine/TeamsISO.Engine.csproj",
|
||||
"src/TeamsISO.Engine.NdiInterop/TeamsISO.Engine.NdiInterop.csproj",
|
||||
"src/TeamsISO.Console/TeamsISO.Console.csproj",
|
||||
"src/TeamsISO.App/TeamsISO.App.csproj",
|
||||
"src/tests/TeamsISO.Engine.Tests/TeamsISO.Engine.Tests.csproj",
|
||||
"src/tests/TeamsISO.Engine.IntegrationTests/TeamsISO.Engine.IntegrationTests.csproj"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -2,12 +2,15 @@
|
|||
|
||||
## Completed phases
|
||||
|
||||
- **Phase A — Engine Foundation** (tag: `phase-a-complete`) — domain model, parsers, participant tracker, frame processor, config persistence, fakes, CI with 80% coverage gate.
|
||||
- **Phase B-1 — Pipeline Orchestration** (tag: `phase-b-1-complete`) — `NdiReceiver`, `NdiSender`, `ExponentialBackoff`, `NdiRuntimeProbe`, `IsoPipeline` (with restart supervisor), `IsoController`. All testable on Linux.
|
||||
- **Phase B-2 — Real NDI Interop** (tag: `phase-b-2-complete`) — production `NdiInteropPInvoke` against NDI 6 SDK, managed BGRA scaler with aspect modes, `TeamsISO.Console` headless smoke runner, `NdiVersion` constants. **Compiles on Linux; runs only on Windows with the NDI Runtime installed.**
|
||||
- **Phase A — Engine Foundation** (tag: `phase-a-complete`) — domain model, parsers, participant tracker, frame processor, config, fakes, CI gate.
|
||||
- **Phase B-1 — Pipeline Orchestration** (tag: `phase-b-1-complete`) — NdiReceiver, NdiSender, ExponentialBackoff, NdiRuntimeProbe, IsoPipeline supervisor, IsoController.
|
||||
- **Phase B-2 — Real NDI Interop** (tag: `phase-b-2-complete`) — `NdiInteropPInvoke` against NDI 6 SDK, managed BGRA scaler, `TeamsISO.Console` headless smoke runner, `NdiVersion` constants.
|
||||
- **Phase C — WPF UI** (tag: `phase-c-complete`) — MVVM helpers, ParticipantViewModel, GlobalSettingsViewModel, AlertBannerViewModel, MainViewModel, MainWindow XAML with participants DataGrid + settings sidebar + alert banner, App.xaml DI bootstrap.
|
||||
|
||||
## Next
|
||||
## Next (Windows-only)
|
||||
|
||||
1. **Phase C — UI & Packaging** (Windows) — WPF MVVM app on top of `IIsoController`. Participant list (DataGrid bound to `Participants` observable), global settings view (framerate, resolution, aspect, audio mode), engine alert banner, system health indicators. WiX MSI installer, release pipeline on tag, About dialog.
|
||||
1. **First end-to-end validation on Windows** — install NDI Runtime, clone the repo, build the full solution, run the integration tests against an NDI Test Pattern source, run the WPF app and validate against a real Teams meeting. Fix any issues found.
|
||||
|
||||
2. **First Windows validation** — once on a Windows machine: install NDI Runtime, run `dotnet build`, run `dotnet test --filter "requires=ndi"` against an NDI Test Pattern source, run `TeamsISO.Console --enable-all` against a real Teams meeting.
|
||||
2. **Phase D — WiX Installer & Release** (Windows) — WiX v5 MSI installer detecting NDI Runtime, release pipeline triggering on tag push, code-signing decision implemented.
|
||||
|
||||
3. **Optional polish before v1.0** — system health indicators (CPU/GPU/network meters), per-stream framerate display, output thumbnail previews (deferred from v1.5 if useful), MaterialDesignThemes UI polish.
|
||||
|
|
|
|||
|
|
@ -1,15 +1,34 @@
|
|||
# TeamsISO Manual Test Playbook
|
||||
|
||||
This doc grows with each phase. Phase A is unit-test only — nothing to verify against live Teams yet. Phase B will fill in NDI runtime checks; Phase C will add the live-meeting end-to-end checklist.
|
||||
## Phase A — Engine foundation (CI)
|
||||
|
||||
## Pre-checks (run before each release branch)
|
||||
|
||||
- [ ] `dotnet build TeamsISO.sln` succeeds with zero warnings on Windows.
|
||||
- [ ] `dotnet build TeamsISO.Linux.slnf` succeeds with zero warnings on Linux/macOS.
|
||||
- [ ] `dotnet test TeamsISO.Linux.slnf --filter "Category!=ndi&requires!=ndi"` reports all unit tests passing.
|
||||
- [ ] CI run on `main` is green.
|
||||
- [ ] `dotnet build TeamsISO.Linux.slnf` succeeds with zero warnings.
|
||||
- [ ] `dotnet test TeamsISO.Linux.slnf --filter "Category!=ndi&requires!=ndi"` passes.
|
||||
- [ ] CI on Forgejo Actions is green at HEAD.
|
||||
- [ ] Code coverage on `TeamsISO.Engine` is ≥80%.
|
||||
|
||||
## Live-meeting checklist (Phase C)
|
||||
## First Windows validation (after Phase B-2 ships)
|
||||
|
||||
(To be added.)
|
||||
Prerequisite: Windows 10/11 + NDI Runtime installed (https://ndi.video/tools/) + .NET 8 SDK.
|
||||
|
||||
- [ ] Clone the repo on the Windows machine: `git clone https://forge.wilddragon.net/zgaetano/teamsiso.git`.
|
||||
- [ ] `dotnet build TeamsISO.sln --configuration Release` succeeds.
|
||||
- [ ] `dotnet test --filter "requires=ndi"` passes against an NDI Test Pattern source (start the test pattern from the NDI Tools menu before running).
|
||||
- [ ] Run `dotnet run --project src/TeamsISO.Console` — confirm the engine starts, version probe matches, and Ctrl+C exits cleanly.
|
||||
|
||||
## Live-meeting validation (after Phase C ships)
|
||||
|
||||
- [ ] Configure a Teams meeting with 3+ participants, with NDI broadcast enabled in Teams.
|
||||
- [ ] `dotnet run --project src/TeamsISO.App` launches the WPF UI without an NDI runtime warning banner.
|
||||
- [ ] Participants list populates within ~2 seconds of opening the app.
|
||||
- [ ] Participant rename mid-meeting transfers the row's identity (the rename heuristic).
|
||||
- [ ] Toggle ISO on for one participant. Confirm the named output appears in vMix / OBS / Studio Monitor on the same LAN.
|
||||
- [ ] Change global framerate to 59.94 fps; click Apply. New ISOs honor the new rate.
|
||||
- [ ] Disconnect one participant; confirm their ISO transitions to the no-signal slate within 2.5 s.
|
||||
- [ ] Run for 30 minutes; check FramesDropped / FramesDuplicated counters in the engine log are reasonable.
|
||||
|
||||
## Pre-release checklist
|
||||
|
||||
- [ ] Legal review of NDI SDK License v5 complete (per spec §7.3).
|
||||
- [ ] Code-signing decision confirmed (yes/no for v1.0).
|
||||
- [ ] WiX installer produces a working MSI on a clean Windows machine.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<Application x:Class="TeamsISO.App.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
StartupUri="MainWindow.xaml">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Application.Resources/>
|
||||
</Application>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,107 @@
|
|||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TeamsISO.App.ViewModels;
|
||||
using TeamsISO.Engine.Controller;
|
||||
using TeamsISO.Engine.Interop;
|
||||
using TeamsISO.Engine.Logging;
|
||||
using TeamsISO.Engine.NdiInterop;
|
||||
using TeamsISO.Engine.Persistence;
|
||||
using TeamsISO.Engine.Pipeline;
|
||||
|
||||
namespace TeamsISO.App;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
private ILoggerFactory? _loggerFactory;
|
||||
private NdiInteropPInvoke? _interop;
|
||||
private IsoController? _controller;
|
||||
private MainViewModel? _viewModel;
|
||||
|
||||
protected override async void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
|
||||
try
|
||||
{
|
||||
_loggerFactory = EngineLogging.CreateConsole(LogLevel.Information);
|
||||
var logger = _loggerFactory.CreateLogger<App>();
|
||||
|
||||
// ---- Preflight: NDI runtime ----
|
||||
try
|
||||
{
|
||||
_interop = new NdiInteropPInvoke(_loggerFactory.CreateLogger<NdiInteropPInvoke>());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(
|
||||
"TeamsISO could not initialize the NDI runtime.\n\n" +
|
||||
"Install the NDI Runtime from https://ndi.video/tools/ and try again.\n\n" +
|
||||
"Details: " + ex.Message,
|
||||
"TeamsISO — NDI runtime missing",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error);
|
||||
Shutdown(2);
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- Engine wiring ----
|
||||
var configPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"TeamsISO", "config.json");
|
||||
var configStore = new ConfigStore(configPath, _loggerFactory.CreateLogger<ConfigStore>());
|
||||
|
||||
var probe = new NdiRuntimeProbe(_interop, NdiVersion.ExpectedRuntimeVersionPrefix);
|
||||
var scaler = new ManagedNearestNeighborFrameScaler();
|
||||
|
||||
var loggerFactoryRef = _loggerFactory;
|
||||
var interopRef = _interop;
|
||||
IsoPipeline PipelineFactory(IsoPipelineConfig config)
|
||||
{
|
||||
var clock = new PeriodicTimerFrameClock(config.Settings.FramerateHz);
|
||||
return new IsoPipeline(
|
||||
config, interopRef, scaler, clock,
|
||||
ExponentialBackoff.Default,
|
||||
(delay, ct) => Task.Delay(delay, ct),
|
||||
loggerFactoryRef);
|
||||
}
|
||||
|
||||
_controller = new IsoController(
|
||||
_interop, PipelineFactory, configStore, probe, _loggerFactory);
|
||||
|
||||
_viewModel = new MainViewModel(_controller, Dispatcher);
|
||||
var window = new MainWindow(_viewModel);
|
||||
window.Show();
|
||||
MainWindow = window;
|
||||
|
||||
await _viewModel.InitializeAsync(CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(
|
||||
"TeamsISO failed to start.\n\nDetails: " + ex,
|
||||
"TeamsISO — startup error",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error);
|
||||
Shutdown(1);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async void OnExit(ExitEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_viewModel?.Dispose();
|
||||
if (_controller is not null)
|
||||
await _controller.DisposeAsync();
|
||||
_interop?.Dispose();
|
||||
_loggerFactory?.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best-effort shutdown
|
||||
}
|
||||
base.OnExit(e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue