ControlSurfaceServer.cs was 1061 lines / 47KB — a single class hosting the HttpListener loop, the route dispatch, and every endpoint body in between. Splits the class via partial-class into a thin host file plus one partial per route group, all under Services/ControlSurface/. * Services/ControlSurfaceServer.cs (was 1061L → now 400L) — kept here: Start / Stop / DisposeAsync (the listener lifecycle), AcceptLoopAsync, HandleRequestAsync (the route table itself, with its CORS preflight + WebSocket upgrade + JSON dispatch), the response helpers (ReadBodyAsync / WriteJsonAsync / TryGetBool / TryGetString), the NotFound switch-arm, and the JsonSerializerOptions singleton. * Services/ControlSurface/Endpoints/HomeEndpoints.cs — GetServerInfo, TryRead helper. * Services/ControlSurface/Endpoints/ParticipantsEndpoints.cs (the biggest split) — GetParticipants, SetIsoOverrideByIdAsync, ClearIsoOverrideByIdAsync, TryParseEnum, ToggleIsoByIdAsync, ToggleIsoByNameAsync, ToggleByIdAsync. Together: every /participants/* handler. * Services/ControlSurface/Endpoints/PresetsEndpoints.cs — RefreshDiscovery, StopAllAsync, ApplyPresetAsync. * Services/ControlSurface/Endpoints/TeamsEndpoints.cs — InvokeTeams (the helper that maps a TeamsControlBridge result to the JSON body). * Services/ControlSurface/Endpoints/TopologyEndpoints.cs — GetTopology, ApplyTopologyAsync, RestoreTopologyAsync. * Services/ControlSurface/Endpoints/NotesEndpoints.cs — AppendNote. * Services/ControlSurface/Endpoints/ThumbnailEndpoint.cs — TryEncodeThumbnailJpeg (which is actually the BMP path now) + EncodeBmpDownscaled + the LE byte writers. The legacy TryEncodeThumbnailJpeg_WpfDeadCode helper that was dead-coded "for posterity" is gone — no call sites; we removed-comments-on-removed- code is the anti-pattern we wanted to fix. * Services/ControlSurface/WebSocketHub.cs — HandleWebSocketAsync, PushSnapshotIfChangedAsync, SendAsync, GetSnapshotJsonAsync. The push-timer wiring stays in the host's Start() so the lifetime is obvious where the connection is opened. No behavior change. The route table in HandleRequestAsync still dispatches by (HttpMethod, path) — only the handler bodies moved. Build clean; 56 + 104 tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
91 lines
3.4 KiB
C#
91 lines
3.4 KiB
C#
namespace TeamsISO.App.Services;
|
|
|
|
// /topology/* route handlers — read + apply / restore the machine NDI
|
|
// access-manager config so the operator can flip transcoder topology
|
|
// without leaving the web UI.
|
|
//
|
|
// GET /topology → GetTopology
|
|
// POST /topology/apply → ApplyTopologyAsync
|
|
// POST /topology/restore → RestoreTopologyAsync
|
|
public sealed partial class ControlSurfaceServer
|
|
{
|
|
/// <summary>
|
|
/// Report the current NDI machine topology. "mode" is "hidden" when
|
|
/// local senders are confined to the private group (raw Teams sources
|
|
/// invisible to the rest of the LAN), "public" otherwise. Reads the
|
|
/// machine NDI config file directly — no caching, so the result
|
|
/// reflects whatever state the file is in right now (including
|
|
/// manual edits).
|
|
/// </summary>
|
|
private object GetTopology()
|
|
{
|
|
try
|
|
{
|
|
var (mode, sends, recvs) = NdiAccessManagerConfig.ReadCurrent();
|
|
return new
|
|
{
|
|
mode,
|
|
senders = sends,
|
|
receivers = recvs,
|
|
configPath = NdiAccessManagerConfig.ConfigPath,
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new { ok = false, error = ex.Message };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply the transcoder topology: machine senders → <c>teamsiso-input</c>,
|
|
/// receivers → <c>public + teamsiso-input</c>; engine groups updated to
|
|
/// match (discover from teamsiso-input, broadcast on public). Operator
|
|
/// MUST restart Teams afterward for it to read the new NDI config.
|
|
/// </summary>
|
|
private async Task<object> ApplyTopologyAsync()
|
|
{
|
|
var result = NdiAccessManagerConfig.ApplyTranscoderTopology();
|
|
if (!result.Success)
|
|
{
|
|
return new { ok = false, error = result.ErrorMessage, configPath = result.ConfigPath };
|
|
}
|
|
// Mirror what the WPF settings VM does so the engine groups +
|
|
// machine config stay in lockstep.
|
|
var ourGroups = new TeamsISO.Engine.Domain.NdiGroupSettings(
|
|
DiscoveryGroups: NdiAccessManagerConfig.TranscoderInputGroup,
|
|
OutputGroups: "public");
|
|
await _controller.SetGroupSettingsAsync(ourGroups, CancellationToken.None);
|
|
return new
|
|
{
|
|
ok = true,
|
|
mode = "hidden",
|
|
backupPath = result.BackupPath,
|
|
note = "Restart Microsoft Teams for the new NDI config to take effect there.",
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restore the machine NDI defaults: senders + receivers both on
|
|
/// <c>public</c>. Engine groups go back to null/defaults too. Operator
|
|
/// must restart Teams for it to broadcast on public again.
|
|
/// </summary>
|
|
private async Task<object> RestoreTopologyAsync()
|
|
{
|
|
var result = NdiAccessManagerConfig.RestoreDefaults();
|
|
if (!result.Success)
|
|
{
|
|
return new { ok = false, error = result.ErrorMessage, configPath = result.ConfigPath };
|
|
}
|
|
var ourGroups = new TeamsISO.Engine.Domain.NdiGroupSettings(
|
|
DiscoveryGroups: null,
|
|
OutputGroups: null);
|
|
await _controller.SetGroupSettingsAsync(ourGroups, CancellationToken.None);
|
|
return new
|
|
{
|
|
ok = true,
|
|
mode = "public",
|
|
backupPath = result.BackupPath,
|
|
note = "Restart Microsoft Teams for the new NDI config to take effect there.",
|
|
};
|
|
}
|
|
}
|