# Spec: Embedded Teams meeting orchestration

**Status:** Draft. Authored 2026-05-08.

## Problem

Operators currently run two apps side by side: Microsoft Teams (which broadcasts
NDI from its meetings) and TeamsISO (which consumes those NDI sources, normalizes
them, and re-emits clean ISOs). Two issues fall out:

1. **Two interfaces, one workflow.** Switching between Teams to drive the meeting
   and TeamsISO to drive ISO routing is friction during a live show.
2. **Teams' raw NDI bleeds into the production network.** Even with
   TeamsISO running, Teams broadcasts its at-source-resolution / at-source-framerate
   feeds on the same `Public` NDI group that switchers and recorders subscribe to.
   Operators see "garbage" NDI sources alongside the clean TeamsISO outputs unless
   they manually configure NDI groups (which most don't).

The user's stated north star: **let me host the meeting from inside TeamsISO. Run
Teams in the background. Show me one interface; expose only the proper outputs.**

## Constraints

- Microsoft Teams' NDI broadcast feature is desktop-only — the web client does not
  broadcast NDI. We cannot replace Teams with a WebView2 view of `teams.microsoft.com`.
- We do not have a native Teams SDK. The Microsoft Graph API exposes some meeting
  control (create/join/end), but in-call operations (mute, share, react) are largely
  out of scope or behind enterprise tenant configuration.
- Win32 window embedding (`SetParent`) of a foreign process's window is technically
  possible but produces a fragile UX — Teams will break out, render incorrectly, or
  fail to honor parent-window inputs.
- NDI group routing is the standard primitive for hiding noisy producers. We
  shipped this in commit `909237f`. It works.

## Architecture

A three-phase rollout. Each phase is shippable on its own.

### Phase E.1 — Teams launcher (launches Teams as a subprocess)

The minimum viable embed. TeamsISO grows a "Launch Teams" affordance on the rail.
Clicking it:

1. Reads the global `NdiGroupSettings.DiscoveryGroups` from `EngineConfig`. If
   empty, defaults to `teamsiso-input`.
2. Opens **NDI Access Manager** (or programmatically writes its config) so Teams
   broadcasts on `teamsiso-input` rather than `Public`.
3. Launches `ms-teams:` URI (or the `MSTeams.exe` directly for the new client) in
   the background.
4. Marks Teams as "owned by TeamsISO" — the rail icon flips to "Stop Teams"; on
   click, sends WM_CLOSE to the Teams main window.
5. Surfaces meeting health in the existing engine-status pill (e.g. "Teams running
   • 2 participants").

Implementation effort: **a few hours.** Pure WPF + ProcessStartInfo + a small
NdiAccessManagerHelper that reads/writes Teams' config.

### Phase E.2 — Window orchestration

Teams' main window is repositioned + minimized when launched, so the user's
foreground experience is the TeamsISO window. Optional:

- Pin Teams to a hidden virtual desktop with `IVirtualDesktopManager`.
- Forward keyboard shortcuts (mute, camera, share) from TeamsISO into Teams via
  `SendInput` while Teams' window is hidden.

Implementation effort: **a day.** Mostly Win32 plumbing.

### Phase E.3 — Meeting controls in TeamsISO's UI

A "Meeting" panel in the left rail that shows the active call's participant list
(with their mute / video state) and exposes Join/Leave/Mute/Share controls. Two
ways to plumb this:

- **Microsoft Graph API for the chrome.** Auth as the user via OAuth (interactive
  device-code flow), poll `/me/onlineMeetings` for active meetings, render in
  TeamsISO's UI. In-call mute/cam state is not exposed via Graph as of writing —
  Phase E.3 would surface participant *presence* but not mic/cam controls.
- **Teams' UI Automation tree.** Walk Teams' window with `UIAutomation` to read
  call state. Brittle but usable; what other "Teams remote" tools do.

Implementation effort: **a week per route.** Recommend Graph for read paths,
UIAutomation for write paths.

## Out of scope (for now)

- Hosting the actual meeting media stack (audio/video render, mixer, network).
  Teams owns this and we don't want to.
- Replacing Teams entirely with our own SIP/WebRTC stack. That's a different
  product.

## Decision required

User to confirm:

1. Phase E.1 first (just launcher + group routing). Yes/no.
2. Whether to use `ms-teams:` URI launch or the new MSTeams.exe binary path
   (`%LOCALAPPDATA%\Microsoft\WindowsApps\ms-teams.exe`).
3. Whether to ship NDI Access Manager config writes, or just document the manual
   steps and trust the user to set them once.

## Implementation log

- 2026-05-08: First version of this spec drafted while user is asleep.
- 2026-05-08: Phase E.1 partial — "Launch Teams" rail button shipped (commit
  pending). Group-routing automation deferred until user confirms approach.
