Some checks failed
CI / build-and-test (push) Failing after 27s
The WinUI 3 host now stands up the full engine pipeline on launch and
discovers participants from the operator's live Teams meeting. Verified
end-to-end against a real call: window opened, NDI runtime preflight
passed, IsoController spun up, and Participants observable yielded
three live entries ((Local), Active Speaker, Brendon Power) with their
TEAMSISO_<name> output names in the redesigned shell.
What this commit lands:
ViewModels/ (slim ports of the WPF host's view-models — engine layer
shared verbatim via ProjectReference):
* ObservableObject.cs — INPC base, mirrors the WPF version
* RelayCommand.cs — sync + typed + async variants; ICommand is the
same shared type across both hosts (System.ObjectModel.dll)
* ParticipantViewModel.cs — DisplayName / Initials / SourceCodec /
IsoStateLabel / DisplayedAudioLevel / IsActiveSpeaker / IsOnline /
OutputName + ToggleIsoCommand. Drops the WPF-specific thumbnail
WriteableBitmap path, clipboard, PreviewWindow, snapshot encoder —
those come back when the WinUI imaging pipeline is wired (Phase 5
of the migration plan).
* MainViewModel.cs — subscribes to IIsoController.Participants on a
DispatcherQueueSynchronizationContext, owns the ObservableCollection,
runs a 1Hz DispatcherQueueTimer for stats + active-speaker
highlight + session-elapsed text. Commands: EnableAllOnline,
StopAllIsos, RefreshDiscovery, DropRecordingMarker, ToggleByIndex.
App.xaml.cs:
* OnLaunched brings up MainWindow first, then fires WireEngineAsync
so the user sees the shell immediately while NDI preflight + engine
setup proceed.
* Full pipeline: EngineLogging → NdiInteropPInvoke (with friendly
fallback message if the NDI runtime isn't installed) → ConfigStore
at %APPDATA%\TeamsISO\config.json → NdiRuntimeProbe + scaler →
IsoPipeline factory → IsoController → MainViewModel →
MainWindow.AttachViewModel → IsoController.StartAsync.
* Logger writes to the same %LOCALAPPDATA%\TeamsISO\Logs as the WPF
host so a mixed-host operator sees a single timeline.
MainWindow.xaml + .xaml.cs:
* x:Name on the section header buttons (RefreshButton, StopAllButton,
EnableAllButton, MarkerButton) and on the status bar text + the
ParticipantsHost grid.
* AttachViewModel wires those buttons to view-model commands; pushes
StatusText + ParticipantCountText through PropertyChanged.
* BuildSimpleRow imperatively constructs each row (Grid with name +
codec + output + ISO toggle pill) instead of going through a
DataTemplate. Rationale: declaring a DataTemplate in
Grid.Resources OR loading one via XamlReader.Load both crash WinUI
3's XAML parser at runtime on this build host (same HR=0x802b000a
we saw with the SettingsDrawer NavigationView). Imperative
construction sidesteps the parser. The rich row template (avatar
circle, audio meter, active-speaker accent) returns in Phase 5
alongside the CommunityToolkit DataGrid swap.
* Per-row PropertyChanged subscriptions refresh DisplayName, codec,
output name, and ISO pill text as the engine pushes updates.
Verified live (PID 4824, 2026-05-13 08:02): three real Teams
participants appeared in the redesigned shell within seconds of launch,
status bar populated with "3 participants · 0 routing", section
header showed "Participants 3", and the title-bar live pills rendered
in their proper places (placeholder values for now; binding to
SessionElapsed / IsRecording lands in the next commit).
26 lines
921 B
C#
26 lines
921 B
C#
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace TeamsISO.App.WinUI.ViewModels;
|
|
|
|
/// <summary>
|
|
/// Minimal MVVM base implementing <see cref="INotifyPropertyChanged"/>.
|
|
/// Mirrors the WPF host's ObservableObject so view-model code reads the
|
|
/// same across hosts.
|
|
/// </summary>
|
|
public abstract class ObservableObject : INotifyPropertyChanged
|
|
{
|
|
public event PropertyChangedEventHandler? PropertyChanged;
|
|
|
|
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
|
|
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
|
|
{
|
|
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
|
|
field = value;
|
|
OnPropertyChanged(propertyName);
|
|
return true;
|
|
}
|
|
}
|