using System.Windows;
using TeamsISO.App.Services;
using TeamsISO.Engine.Controller;
using TeamsISO.Engine.Domain;
namespace TeamsISO.App.ViewModels;
///
/// Bindings for the global settings panel: framerate, resolution, aspect, audio,
/// NDI groups (discovery + output), and the participant-list hide-Local toggle.
///
public sealed class GlobalSettingsViewModel : ObservableObject
{
private readonly IIsoController _controller;
private readonly ToastViewModel? _toast;
private TargetFramerate _framerate;
private TargetResolution _resolution;
private AspectMode _aspect;
private AudioMode _audio;
private string _discoveryGroups;
private string _outputGroups;
private bool _hideLocalSelf = true;
public GlobalSettingsViewModel(IIsoController controller, ToastViewModel? toast = null)
{
_controller = controller;
_toast = toast;
var current = controller.GlobalSettings;
_framerate = current.Framerate;
_resolution = current.Resolution;
_aspect = current.Aspect;
_audio = current.Audio;
var groups = controller.GroupSettings;
_discoveryGroups = groups.DiscoveryGroups ?? string.Empty;
_outputGroups = groups.OutputGroups ?? string.Empty;
ApplyCommand = new AsyncRelayCommand(ApplyAsync);
ApplyTranscoderTopologyCommand = new AsyncRelayCommand(ApplyTranscoderTopologyAsync);
}
public IEnumerable AvailableFramerates => Enum.GetValues();
public IEnumerable AvailableResolutions => Enum.GetValues();
public IEnumerable AvailableAspectModes => Enum.GetValues();
public IEnumerable AvailableAudioModes => Enum.GetValues();
public TargetFramerate Framerate { get => _framerate; set => SetField(ref _framerate, value); }
public TargetResolution Resolution { get => _resolution; set => SetField(ref _resolution, value); }
public AspectMode Aspect { get => _aspect; set => SetField(ref _aspect, value); }
public AudioMode Audio { get => _audio; set => SetField(ref _audio, value); }
/// NDI discovery group(s) — comma-separated. Empty = default (Public).
public string DiscoveryGroups { get => _discoveryGroups; set => SetField(ref _discoveryGroups, value); }
/// NDI output group(s) — comma-separated. Empty = default (Public).
public string OutputGroups { get => _outputGroups; set => SetField(ref _outputGroups, value); }
///
/// Hide the user's own self-preview ("(Local)") from the participants list.
/// On by default — operators rarely want to ISO-route their own preview.
/// Read by when filtering the list it presents.
///
public bool HideLocalSelf { get => _hideLocalSelf; set => SetField(ref _hideLocalSelf, value); }
public AsyncRelayCommand ApplyCommand { get; }
///
/// One-click "set up the transcoder topology" — writes ndi-config.v1.json so all
/// local senders broadcast on a private group ("teamsiso-input") while local
/// receivers can see both that and "public", then sets the engine's discovery and
/// output groups to align (engine receives from the private group, emits on Public).
/// User has to restart Teams for the new ndi-config.v1.json to take effect there.
///
public AsyncRelayCommand ApplyTranscoderTopologyCommand { get; }
private async Task ApplyAsync()
{
var settings = new FrameProcessingSettings(_framerate, _resolution, _aspect, _audio);
await _controller.SetGlobalSettingsAsync(settings, CancellationToken.None);
var groups = new NdiGroupSettings(
DiscoveryGroups: string.IsNullOrWhiteSpace(_discoveryGroups) ? null : _discoveryGroups.Trim(),
OutputGroups: string.IsNullOrWhiteSpace(_outputGroups) ? null : _outputGroups.Trim());
await _controller.SetGroupSettingsAsync(groups, CancellationToken.None);
_toast?.Show("Settings saved");
}
private async Task ApplyTranscoderTopologyAsync()
{
// 1. Update the machine-wide NDI config so Teams' raw broadcasts go to the
// private group instead of polluting Public.
var result = NdiAccessManagerConfig.ApplyTranscoderTopology();
if (!result.Success)
{
MessageBox.Show(
$"Could not write NDI Access Manager config.\n\n{result.ErrorMessage}\n\nPath: {result.ConfigPath}",
"TeamsISO — Apply transcoder topology",
MessageBoxButton.OK,
MessageBoxImage.Warning);
return;
}
// 2. Update the engine: receive only from the private group, emit on Public.
var ourGroups = new NdiGroupSettings(
DiscoveryGroups: NdiAccessManagerConfig.TranscoderInputGroup,
OutputGroups: "public");
await _controller.SetGroupSettingsAsync(ourGroups, CancellationToken.None);
// 3. Reflect the new values in the bound text boxes.
DiscoveryGroups = NdiAccessManagerConfig.TranscoderInputGroup;
OutputGroups = "public";
var backupNote = result.BackupPath is null
? "No prior NDI config existed; a fresh one was created."
: $"A backup of your prior NDI config was saved to:\n{result.BackupPath}";
MessageBox.Show(
"Transcoder topology applied. ✓\n\n" +
"• Local senders (Teams, etc.) will broadcast on group 'teamsiso-input'.\n" +
"• Local receivers will see both 'public' and 'teamsiso-input'.\n" +
"• TeamsISO will discover from 'teamsiso-input' and re-emit on 'public'.\n\n" +
"RESTART Microsoft Teams for the new NDI config to take effect there.\n\n" +
backupNote,
"TeamsISO — Apply transcoder topology",
MessageBoxButton.OK,
MessageBoxImage.Information);
_toast?.Show("Transcoder topology applied — restart Teams to take effect");
}
}