Per-Participant NDI ISO Controller for Microsoft Teams. Receives Teams NDI streams, normalizes framerate/resolution, and re-emits clean ISO outputs for live production switchers (vMix, OBS, Ross). Wild Dragon LLC.
Lands the approved shape brief as the WinUI 3 MainWindow:
* 64px left rail with brand mark, primary nav (participants), Teams
launch / hide / settings buttons, and the engine-status puck at the
bottom. All five rail buttons use Segoe Fluent Icons glyphs at a
uniform 20px optical size; no more bespoke <Path Data> shapes with
inconsistent stroke weights.
* 44px custom title bar via ExtendsContentIntoTitleBar +
SetTitleBar(AppTitleBar). The drag region absorbs the three live-state
pills inline (session timer 'live * 00:14:32', REC count + elapsed,
disk free) and a slim sun/moon theme-toggle button to the left of the
system Min/Max/Close controls. System buttons inherit ButtonForeground
Color etc. from AppWindow.TitleBar so they match palette in both
themes.
* Section header with 'Participants * count' display, filter input,
Refresh + Presets (Secondary buttons), and 'Enable all online' as
the single cyan Primary button - finally a real button hierarchy
instead of seven indistinguishable ghost buttons.
* Participants list rendered as ItemsRepeater + DataTemplate for now;
the CommunityToolkit DataGrid migration follows in a separate commit.
Row template at 64px height with: 3px cyan left border for active
speaker, avatar with initials in cyan-muted circle, name + codec line,
signal lock state with dot, audio meter via ProgressBar, output name
in JetBrains Mono, ISO state pill (LIVE/OFF/ERROR) at right.
* Conditional in-call control bar below the table: Mute / Camera /
Share / Marker / Leave + overflow kebab. Muted state binds the
destructive coral treatment to the Mute button; Leave is also
destructive (coral border + text); everything else is Secondary.
Tight 8px spacing keeps the bar dense without crowding.
* Slim 32px status bar at the bottom: control-surface URL on the left
(cyan dot indicator), keyboard-shortcut hints on the right in
tertiary mono. Replaces the WPF host's six-column footer.
Implementation notes:
* MockParticipant model populates the table with representative data
(Maya / Daniel / Aicha / Sam, one as active speaker) until the
ParticipantViewModel binding migrates over from the WPF host.
* Custom Program.cs takes ownership of Main from the XAML compiler
(DISABLE_XAML_GENERATED_MAIN). Calls Bootstrap.TryInitialize(0x00010006)
before Application.Start so the unpackaged .exe can locate the
WindowsAppSDK 1.6 framework MSIX at launch. Shutdown is paired in
a finally block.
* Theme toggle in code-behind flips Window.Content.RequestedTheme
between Dark and Light. {ThemeResource} bindings auto-swap across
the visual tree; system title-bar buttons (outside the XAML tree)
get color updates inline so they stay readable in both modes.
* app.manifest deferred from build - the framework-emitted manifest
covers DPI awareness and supportedOS GUIDs; reintroducing our own
goes in the next commit alongside the bootstrapper hardening.
Known issue: the unpackaged .exe currently fails to activate on this
build host with 'this application could not be started' before Main
runs. Build is clean; published output runs the same way. Diagnosing
the activation failure is the next session's first task (likely the
runtimeconfig.json including Microsoft.WindowsDesktop.App which WinUI 3
doesn't want, or a missing CRT redistributable). The WPF host remains
the running build until that's resolved.
dotnet build TeamsISO.Windows.slnf -c Debug: 0 warnings, 0 errors.
|
||
|---|---|---|
| .forgejo/workflows | ||
| docs | ||
| installer | ||
| src | ||
| .editorconfig | ||
| .gitignore | ||
| build-and-test.ps1 | ||
| CHANGELOG.md | ||
| commit-and-push.ps1 | ||
| coverlet.runsettings | ||
| DESIGN.md | ||
| Directory.Build.props | ||
| PRODUCT.md | ||
| README.md | ||
| TeamsISO.Linux.slnf | ||
| TeamsISO.sln | ||
| TeamsISO.Windows.slnf | ||
TeamsISO
Per-Participant NDI ISO Controller for Microsoft Teams.
TeamsISO sits between Microsoft Teams' raw NDI broadcast output and a live-production environment. It receives each participant's NDI stream, normalizes framerate / resolution / aspect / audio per a configured target, and re-emits clean, individually-addressable NDI sources for ingestion into a switcher (vMix, OBS, Ross, hardware capture).
What it does
- Discovers participants as Teams broadcasts each one over NDI, surfacing the operator-friendly display name (handles current "MS Teams - Name" format and the legacy "(Teams) Name" format).
- Normalizes feeds to a consistent framerate, resolution, aspect mode, and audio routing — so the downstream switcher gets predictable inputs regardless of what each participant's webcam is doing.
- Routes per-participant as separate NDI sources with a configurable
output-name template (
TEAMSISO_{name},{guid},{machine},{timestamp}tokens). - Records each ISO to disk simultaneously — raw BGRA + sidecar manifest.json
- ffmpeg convert.cmd — so post-production gets a clean per-guest archive.
- Embeds Teams orchestration: launch and stop Teams from the rail, hide Teams' UI windows during a show, drive in-call controls (mute, camera, share, leave, raise hand) via UIAutomation.
- Operator presets save the current per-participant ISO assignment and custom output names, applicable on next launch automatically.
- Live preview thumbnails per participant in the participants table, plus pop-out floating preview windows (right-click → Open preview…) for multi-monitor monitoring.
- External control surface — REST + WebSocket on
127.0.0.1:9755and OSC on UDP127.0.0.1:9000for Bitfocus Companion / Stream Deck / TouchOSC integration. Self-contained HTML control panel at/uifor phone-as-controller. - Crash diagnostics wired to a rolling daily Serilog file sink under
%LOCALAPPDATA%\TeamsISO\Logs\. - Update check against
forge.wilddragon.net's release API — manual or silent on launch (throttled to 24h). - Diagnostic bundle export zips logs + config + presets for bug reports.
Status
Pre-1.0. The May 2026 batch is feature-complete; v1.0 cut is gated on
code-signing the MSI and a smoke pass against a real Teams meeting.
See CHANGELOG.md for the [Unreleased] entry.
Build
Requires .NET 8 SDK on Windows (the TeamsISO.App host is net8.0-windows
WPF).
dotnet restore TeamsISO.Windows.slnf
dotnet build TeamsISO.Windows.slnf -c Release
dotnet test TeamsISO.Windows.slnf --filter "Category!=ndi&requires!=ndi"
The shipped helper scripts in the repo root automate this:
pwsh -File .\build-and-test.ps1
pwsh -File .\commit-and-push.ps1
Documentation
- Control surface API — REST + WebSocket + OSC reference with curl recipes and a Companion config example.
- Releasing — tag-push workflow, MSI signing path.
- Architecture spec — design overview.
- Embedded Teams orchestration spec — Phase E roadmap.
Keyboard shortcuts
| Key | Action |
|---|---|
F1 |
Open help / cheat sheet |
Ctrl + M |
Drop a timestamped marker into every active recording |
Ctrl + Shift + S |
Stop every running ISO (emergency) |
Ctrl + R |
Refresh NDI discovery (rebuild finder) |
File locations
| Path | Contents |
|---|---|
%APPDATA%\TeamsISO\config.json |
Engine settings (framerate, NDI groups, etc.) |
%LOCALAPPDATA%\TeamsISO\presets.json |
Saved operator presets + auto-apply preference |
%LOCALAPPDATA%\TeamsISO\Logs\ |
Rolling daily diagnostic logs |
%LOCALAPPDATA%\TeamsISO\Notes\ |
Per-day show-notes markdown files |
%USERPROFILE%\Videos\TeamsISO\<date>\ |
Default recording output |
%APPDATA%\NDI\ndi-config.v1.json |
NDI Access Manager group routing |
License
Proprietary, © Wild Dragon LLC 2026.