225 lines
13 KiB
Markdown
225 lines
13 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to TeamsISO are documented here. The format follows
|
||
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
|
||
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||
|
||
## [Unreleased]
|
||
|
||
### Added — May 2026 feature batch
|
||
|
||
#### Engine
|
||
- NDI Groups: discovery + sender support so Teams' raw broadcasts can be
|
||
pinned to a private "teamsiso-input" group while TeamsISO's own
|
||
normalized outputs broadcast on Public.
|
||
- One-click "Apply transcoder topology" writes `ndi-config.v1.json` so all
|
||
Teams broadcasts go to the private group and TeamsISO re-emits on Public.
|
||
- `RawBgraRecorderSink` per-output recorder: `IRecorderSink` interface +
|
||
raw BGRA stream + `manifest.json` + `convert.cmd` script for FFmpeg
|
||
conversion to H.264 MKV.
|
||
- Recording markers: `IRecorderSink.AddMarker(label)` fan-out via
|
||
`IIsoController.AddRecordingMarker`. Markers land in `manifest.json`
|
||
under `markers[]` for post-production chaptering.
|
||
- Preview thumbnails: `IsoPipeline.LatestProcessedFrame` published via
|
||
`Volatile.Read` so the UI can render 160×90 BGRA thumbnails in the
|
||
participants DataGrid at 1Hz.
|
||
- Idempotent `ParticipantTracker.HandleAdded`: re-emitting Added for an
|
||
already-live source refreshes LastSeen instead of duplicating the row.
|
||
Fixes the "click Refresh and rows ghost-duplicate" bug introduced by
|
||
the Refresh-Discovery affordance.
|
||
- `IIsoController.RefreshDiscovery()` rebuilds the NDI finder on the next
|
||
poll tick — useful right after applying a new transcoder topology.
|
||
- `IIsoController.AddRecordingMarker(label)` fan-out to every active recorder.
|
||
- `IIsoController.SetRecording(enabled, dir)` global recording toggle;
|
||
per-participant override via `EnableIsoAsync(...recordOverride...)`.
|
||
|
||
#### Host (WPF)
|
||
- Active Speaker as a synthetic routable participant with deterministic v5
|
||
GUID derived from `auto-mix:<machine>`.
|
||
- Auto-disable on departure: when a participant's NDI source disappears,
|
||
optionally tear down their pipeline.
|
||
- Operator presets: chromeless `Presets…` dialog with Save / Apply /
|
||
Delete / Duplicate / Export / Import. Persisted at
|
||
`%LOCALAPPDATA%\TeamsISO\presets.json`. Bundle format
|
||
`teamsiso-presets-bundle/v1` for migration between machines.
|
||
- Auto-apply last preset on launch (configurable, off by default).
|
||
- `--apply-preset NAME` CLI flag for desktop-shortcut workflows.
|
||
- `PresetApplier` — single source of truth for "apply preset to live
|
||
participants" used by the dialog, REST surface, and auto-apply path.
|
||
- Live preview thumbnails per participant (160×90 BGRA WriteableBitmap).
|
||
- Right-click context menu on participant rows: Toggle ISO, Record-this-
|
||
participant, Copy NDI source name.
|
||
- Live filter input (substring match on display name).
|
||
- "Enable all online" + "Stop all ISOs" + "Refresh" header actions.
|
||
- Per-participant recording opt-out checkbox (Rec column).
|
||
- Custom NDI output name template with `{name}`/`{guid}`/`{machine}`/
|
||
`{timestamp}` tokens.
|
||
- Phase E.1 — Launcher: rail "Launch / Stop Teams" toggle.
|
||
- Phase E.2 — Window orchestration: hide / show Teams windows from the rail.
|
||
- Phase E.3 — In-call controls (UIA): Mute, Camera, Share, Leave, Raise hand,
|
||
plus PostMessage shortcut forwarding fallback. Candidate names localized
|
||
for English / German / Spanish / French / Portuguese / Japanese.
|
||
- Crash diagnostics: AppDomain + Dispatcher + TaskScheduler unhandled
|
||
exception handlers wired to Serilog.Critical + user-facing dialog.
|
||
- First-launch onboarding dialog with 5-step setup checklist.
|
||
- About dialog gained "Show welcome", "Check for updates", "Export diagnostics"
|
||
buttons.
|
||
- Diagnostic bundle export: zips logs + config + presets + version metadata
|
||
into `~/Downloads/teamsiso-diagnostics-<ts>.zip` for bug reports.
|
||
- Update check: manual via About + auto-on-launch banner (throttled to 24h,
|
||
opt-out via flag file at `%LOCALAPPDATA%\TeamsISO\no-update-check.flag`).
|
||
- Disk space watcher auto-disables recording at <1GB free.
|
||
- Settings panel refactored into OUTPUT / NETWORK / DISPLAY tabs.
|
||
- Reset-to-defaults button in OUTPUT tab.
|
||
- Enriched footer: REC badge, control-surface badge, session timer (HH:MM:SS
|
||
since first ISO went live), dynamic status text ("3/5 ISOs live · 2 recording").
|
||
- Window-scoped keyboard shortcuts: F1 (help), Ctrl+M (marker), Ctrl+Shift+S
|
||
(stop all), Ctrl+R (refresh discovery).
|
||
- F1 help / cheat-sheet dialog.
|
||
- `UIPreferences` static persists `HideLocalSelf`, `AutoDisableOnDeparture`,
|
||
`ParticipantSort` (JoinOrder / Alphabetical / OnlineFirst) across launches
|
||
to `%LOCALAPPDATA%\TeamsISO\ui-prefs.json`.
|
||
- Pop-out per-participant preview window (right-click → Open preview…)
|
||
refreshes at ~20Hz and is multi-monitor friendly.
|
||
- Configurable participant sort order via the DISPLAY tab dropdown.
|
||
- Stop-All confirms before tearing down running pipelines (catches
|
||
mid-show misclicks).
|
||
- About dialog gained "Logs / Recordings / Notes" folder shortcut buttons.
|
||
- `NotesWindow` inline viewer for today's show-notes file with 2s polling.
|
||
- Duplicate-preset action in the Presets dialog with smart `(copy N)`
|
||
name suggestions.
|
||
- `--apply-preset NAME` command-line flag for desktop-shortcut workflows.
|
||
- New `TeamsISO.App.Tests` net8.0-windows test project. Initial coverage:
|
||
`OperatorPresetStoreTests` (round-trip, name collisions, schema, bundle
|
||
import/export, garbage-file resilience), `OutputNameTemplateTests` (token
|
||
expansion + sanitization), `OscMessageTests` (wire-format parsing of
|
||
int/float/string/T/F type tags). Backed by an `InternalsVisibleTo` grant
|
||
+ a test-only `OperatorPresetStore.PathOverride` hook.
|
||
- `IsoHealthStats.PeakAudioLevel` field + DataGrid VU-bar UI scaffolding.
|
||
Engine still emits 0.0 (audio capture is a focused follow-up); the bar's
|
||
decay logic is in place so it animates as soon as engine-side audio
|
||
parsing lands.
|
||
- `MediaFoundationRecorderSink` scaffold under `#if MF_AVAILABLE` for
|
||
inline H.264 encoding via Vortice.MediaFoundation. ~10× smaller files
|
||
than the raw BGRA recorder. Activation steps documented at
|
||
`docs/REAL-TIME-RECORDING.md`.
|
||
- System-tray icon + minimize-to-tray toggle. Adds
|
||
`<UseWindowsForms>true</UseWindowsForms>` for `NotifyIcon`; the
|
||
`TrayIconHost` lives on `App` (process lifetime, not main-window
|
||
lifetime). Right-click menu has Show / Stop all ISOs / Exit.
|
||
- Built-in NDI test pattern: `TeamsISO.Console --test-pattern` broadcasts
|
||
a synthetic 1280×720 30fps source named `TEAMSISO_TEST` showing SMPTE
|
||
color bars + a moving sweep band. Verifies NDI runtime, sender
|
||
configuration, and downstream discovery without needing Teams running.
|
||
Backed by `TestPatternGenerator` in the engine + 4 unit tests covering
|
||
buffer size, alpha, color distinctness, and sweep animation.
|
||
- Always-toast on participant disconnect, regardless of `AutoDisableOnDeparture`
|
||
setting. Distinguishes "ISO torn down" (auto-disable on) from "ISO still
|
||
running on slate" (auto-disable off) so operators don't miss a silent
|
||
drop mid-show.
|
||
- **Restart this ISO** right-click action — disable + brief delay + re-enable
|
||
for one participant only. Useful when a single feed flakes without
|
||
affecting other ISOs.
|
||
- **Roll recording** action: rolls every active recording into a new chunk
|
||
(disable + re-enable each pipeline; recorder finalizes its `manifest.json`
|
||
and starts a fresh subdirectory). Surfaced via `MainViewModel.RollRecordingCommand`,
|
||
REST `POST /recording/roll`, and OSC `/teamsiso/recording/roll`. Useful
|
||
for chaptering between show segments.
|
||
- **Engine audio peak metering** — `IsoHealthStats.PeakAudioLevel` now
|
||
reports real values (was always 0.0). New `INdiInterop.CaptureAudioPeak`
|
||
method polls audio frames; production `NdiInteropPInvoke` parses
|
||
`NDIlib_audio_frame_v3_t` and computes max-absolute peak across all
|
||
channels. `NdiReceiver` runs a sibling audio capture loop on the same
|
||
lifetime so the existing video path is unaffected. UI VU bars in the
|
||
participants DataGrid now animate against real source audio. Failures
|
||
in the audio loop are caught + logged but never re-thrown — a
|
||
misbehaving audio path must never tear down the live video pipeline.
|
||
14 new unit tests in `AudioPeakComputerTests` covering FLTP / FLT / PCM
|
||
s16 across edge cases (clipping, `short.MinValue` overflow, defensive
|
||
`totalSamples`-vs-buffer mismatch handling).
|
||
|
||
#### LAN-reachable control surface
|
||
- `ControlSurfaceServer.Start(port, bindToLan)` and `OscBridge.Start(port,
|
||
bindToLan)` switch between `127.0.0.1` and all-interfaces (`http://+:port/`,
|
||
`IPAddress.Any`) based on the new `ControlSurfaceLanReachable` UI preference.
|
||
Settings VM persists the toggle, restarts both surfaces on flip, and
|
||
surfaces a `ControlSurfaceUrl` (computed from the host's first physical-NIC
|
||
routable IPv4 — Tailscale / VPN / APIPA addresses are skipped) plus a Copy
|
||
button. Use case: headless host PC running Teams + TeamsISO; thin client
|
||
on the same LAN drives `/ui` or hits the REST endpoints. Closed-network
|
||
deployment, no auth — documented as a trusted-LAN-only mode in
|
||
`docs/CONTROL-SURFACE.md`. First-time use requires a one-shot
|
||
`netsh http add urlacl url=http://+:9755/ user=Everyone` (so the listener
|
||
can bind without admin); the diagnostic warning fires the exact command
|
||
string in the log if the bind fails.
|
||
|
||
#### UI polish — visible affordances on the dark canvas
|
||
- Hover state on every themed button (Ghost / Caption / RailIcon / IsoToggle)
|
||
was barely distinguishable from the resting state. Bumped `Wd.SurfaceHover`
|
||
+ `Wd.SurfaceActive` colours for sufficient contrast, added dedicated
|
||
`Wd.Button.HoverBg` / `Wd.Button.PressBg` brushes with a slight chroma tint,
|
||
and added cyan accent borders so mouse-hover and tab-focus give an
|
||
unmistakable affordance regardless of which surface the button sits on.
|
||
IsoToggle keeps its status-coded background (LIVE cyan / ERROR coral /
|
||
NO SIGNAL amber) on hover; the affordance is a 2px cyan border pop.
|
||
- `IsKeyboardFocused` triggers added on every themed button so tab-cycling
|
||
through the UI gives visual feedback (was nothing — `FocusVisualStyle`
|
||
was `x:Null` with no replacement).
|
||
- ScrollBar restyled: slim transparent track + tinted thumb (Edge / VS Code
|
||
pattern) in place of the chunky Win9x default with line-up / line-down
|
||
arrow buttons. Track-clicks above/below the thumb still page-scroll.
|
||
- ToolTip restyled: SurfaceElevated card with rounded 6px corner + 320px
|
||
text wrap, replacing the cream Win98 popup. Affects every tooltip across
|
||
MainWindow.
|
||
- ContextMenu / MenuItem restyled: dark card with rounded corners + cyan-
|
||
tinted hover. Affects the right-click menu on participant rows
|
||
(Toggle ISO / Restart this ISO / Open preview / Record / Copy NDI source name).
|
||
|
||
#### Control surface
|
||
- REST API on `127.0.0.1:9755` with endpoints for participant ISO toggle (by
|
||
Id or display name), preset apply, refresh discovery, stop-all, recording
|
||
on/off, marker drop, notes, and Teams in-call commands. Documented at
|
||
`docs/CONTROL-SURFACE.md`.
|
||
- WebSocket `/ws` pushes live participant state at 4Hz with snapshot diffing.
|
||
- OSC bridge on UDP `127.0.0.1:9000` mirrors the REST vocabulary
|
||
(`/teamsiso/iso "Jane" 1`, `/teamsiso/preset "..."`, etc.).
|
||
- Embedded HTML control panel at `GET /ui` — phone-friendly remote with
|
||
live state and one-click action buttons.
|
||
- Show notes service: `POST /notes` and `/teamsiso/notes "..."` append
|
||
timestamped lines to `%LOCALAPPDATA%\TeamsISO\Notes\<YYYY-MM-DD>.md`.
|
||
|
||
#### CI / Release
|
||
- Forgejo CI is green; tag-push release workflow builds + tests + publishes
|
||
+ builds MSI on a Windows runner and attaches it to the auto-created
|
||
release via the REST API.
|
||
- Optional MSI + exe code-signing wired into `release.yml` — gated on
|
||
`SIGN_CERT_PFX_BASE64` + `SIGN_CERT_PASSWORD` Forgejo Secrets.
|
||
|
||
### Fixed
|
||
|
||
- `.slnf` path-separator mismatch (forward slashes for cross-platform).
|
||
- NDI native DLL resolution via `NativeLibrary` resolver.
|
||
- `ExpectedRuntimeVersionPrefix` updated to NDI 6 banner format.
|
||
- `NdiSourceParser` accepts current Teams desktop's `MS Teams - <name>`
|
||
brand format.
|
||
- ActiveSpeaker source removal no longer poisons the rename-window
|
||
heuristic for a Participant joining the same machine within the window.
|
||
- `IsoPipeline.State` access synchronized via `Volatile.Read/Write`.
|
||
- REST handlers now correctly marshal `ObservableCollection` reads + writes
|
||
through the UI dispatcher.
|
||
- WebSocket upgrade no longer falls into `res.Close()` finally block (was
|
||
killing freshly-upgraded connections).
|
||
- `ParticipantViewModel.UpdateThumbnail` defends against malformed frames
|
||
(`width*height*4 > Pixels.Length`).
|
||
- `HasThumbnail` correctly fires `PropertyChanged` when `Thumbnail`
|
||
transitions from null.
|
||
- WinForms / WPF `Application` and `MessageBox` namespace collision
|
||
(introduced when `<UseWindowsForms>true</UseWindowsForms>` was added for
|
||
the system tray) resolved via project-wide `GlobalUsings.cs`.
|
||
- `GetLanIPv4()` now skips Tailscale / VPN tunnel adapters and APIPA
|
||
(`169.254.x`) so the displayed control-surface URL points at the
|
||
routable LAN IP (verified on a host with both Ethernet 10.x and a
|
||
Tailscale 169.254 link-local — picker now correctly returns the
|
||
Ethernet address).
|
||
|
||
[Unreleased]: https://forge.wilddragon.net/zgaetano/teamsiso/compare/v0.1.0...HEAD
|