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"); } }