From abbf9bd476831670f6bca54c34ff1121bb7ebf53 Mon Sep 17 00:00:00 2001 From: rafalau Date: Wed, 10 Jun 2026 13:04:59 -0300 Subject: [PATCH 01/12] Add SRT Monitor Data Provider widget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New widget that receives and previews SRT (Secure Reliable Transport) streams directly inside vMixUTC using LibVLCSharp + libvlc. Features: - Listener and Caller modes - Configurable port, latency, AES passphrase, key length and stream ID - Embedded video preview with VideoView (Win32 HWND via LibVLCSharp.WPF) - Per-instance audio control via :no-audio media option — isolated between multiple SRT Monitor instances on the same layout - Default IP pre-filled from the current vMix connection - Vertical resize support (IsResizeableVertical) - Play/Stop icon buttons (Font Awesome) - State persisted through vMixUTC's DataProvider serialization mechanism Core changes: - vMixUTCSRTDataProvider: new project (SrtMonitorDataProvider.dll) - vMixControlExternalData: IsResizeableVertical = true - vMixControlContainer.xaml: vertical resize thumb - ControlTemplates.xaml: Height binding on ExternalDataWidgetControl - MainViewModel: ProcessHotkey skips focus steal when TextBox has focus - HighPrecisionTimer / Popcron.Sheets: target framework bump to v4.7.2 Co-Authored-By: Claude Sonnet 4.6 --- HighPrecisionTimer/HighPrecisionTimer.csproj | 4 +- Popcron.Sheets/Popcron.Sheets.csproj | 4 +- .../Controls/vMixControlContainer.xaml | 1 + vMixController/Skins/ControlTemplates.xaml | 4 +- vMixController/ViewModel/MainViewModel.cs | 6 +- .../Widgets/vMixControlExternalData.cs | 2 + vMixControllerMultiState.sln | 15 ++ vMixUTCSRTDataProvider/FodyWeavers.xml | 4 + vMixUTCSRTDataProvider/FodyWeavers.xsd | 176 ++++++++++++++ vMixUTCSRTDataProvider/OnWidgetUI.xaml | 184 ++++++++++++++ vMixUTCSRTDataProvider/OnWidgetUI.xaml.cs | 207 ++++++++++++++++ .../Properties/AssemblyInfo.cs | 17 ++ vMixUTCSRTDataProvider/SRTDataProvider.cs | 229 ++++++++++++++++++ vMixUTCSRTDataProvider/app.config | 19 ++ vMixUTCSRTDataProvider/packages.config | 12 + .../vMixUTCSRTDataProvider.csproj | 155 ++++++++++++ 16 files changed, 1032 insertions(+), 7 deletions(-) create mode 100644 vMixUTCSRTDataProvider/FodyWeavers.xml create mode 100644 vMixUTCSRTDataProvider/FodyWeavers.xsd create mode 100644 vMixUTCSRTDataProvider/OnWidgetUI.xaml create mode 100644 vMixUTCSRTDataProvider/OnWidgetUI.xaml.cs create mode 100644 vMixUTCSRTDataProvider/Properties/AssemblyInfo.cs create mode 100644 vMixUTCSRTDataProvider/SRTDataProvider.cs create mode 100644 vMixUTCSRTDataProvider/app.config create mode 100644 vMixUTCSRTDataProvider/packages.config create mode 100644 vMixUTCSRTDataProvider/vMixUTCSRTDataProvider.csproj diff --git a/HighPrecisionTimer/HighPrecisionTimer.csproj b/HighPrecisionTimer/HighPrecisionTimer.csproj index 2fc9fb0..338e500 100644 --- a/HighPrecisionTimer/HighPrecisionTimer.csproj +++ b/HighPrecisionTimer/HighPrecisionTimer.csproj @@ -9,7 +9,7 @@ Properties HighPrecisionTimer HighPrecisionTimer - v4.6.2 + v4.7.2 512 @@ -58,4 +58,4 @@ --> - \ No newline at end of file + diff --git a/Popcron.Sheets/Popcron.Sheets.csproj b/Popcron.Sheets/Popcron.Sheets.csproj index 469efc7..0ce1cea 100644 --- a/Popcron.Sheets/Popcron.Sheets.csproj +++ b/Popcron.Sheets/Popcron.Sheets.csproj @@ -9,7 +9,7 @@ Properties Popcron.Sheets Popcron.Sheets - v4.6.2 + v4.7.2 512 true @@ -216,4 +216,4 @@ - \ No newline at end of file + diff --git a/vMixController/Controls/vMixControlContainer.xaml b/vMixController/Controls/vMixControlContainer.xaml index 858f12f..0a8bf4d 100644 --- a/vMixController/Controls/vMixControlContainer.xaml +++ b/vMixController/Controls/vMixControlContainer.xaml @@ -249,6 +249,7 @@ + diff --git a/vMixController/Skins/ControlTemplates.xaml b/vMixController/Skins/ControlTemplates.xaml index e184734..6d87991 100644 --- a/vMixController/Skins/ControlTemplates.xaml +++ b/vMixController/Skins/ControlTemplates.xaml @@ -915,8 +915,8 @@ - - + + + + + + + + diff --git a/vMixUTCSRTDataProvider/OnWidgetUI.xaml.cs b/vMixUTCSRTDataProvider/OnWidgetUI.xaml.cs new file mode 100644 index 0000000..01cb38e --- /dev/null +++ b/vMixUTCSRTDataProvider/OnWidgetUI.xaml.cs @@ -0,0 +1,207 @@ +using LibVLCSharp.Shared; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Threading; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using VlcMediaPlayer = LibVLCSharp.Shared.MediaPlayer; + +namespace UTCSRTDataProvider +{ + public partial class OnWidgetUI : UserControl, IDisposable + { + private static int _nextId = 0; + private readonly int _id = Interlocked.Increment(ref _nextId); + + private VlcMediaPlayer _mediaPlayer; + private LibVLC _vlc; + private string _lastUrl; + private bool _isConnected; + private bool _reconnecting; + private bool _disposed; + + static OnWidgetUI() + { + Trace.Listeners.Remove("Default"); + } + + public OnWidgetUI() + { + InitializeComponent(); + + if (DesignerProperties.GetIsInDesignMode(this)) + return; + + VideoPlaceholder.Text = $"Aguardando... [#{_id}]"; + DataContextChanged += OnDataContextChanged; + } + + private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) + { + if (e.NewValue is SRTDataProvider neu) + AudioCheckBox.IsChecked = neu.IsAudioEnabled; + } + + private SRTDataProvider Provider => DataContext as SRTDataProvider; + + private void AudioCheckBox_Checked(object sender, RoutedEventArgs e) + { + if (Provider != null) Provider.IsAudioEnabled = true; + if (_isConnected) DoReconnect(); + } + + private void AudioCheckBox_Unchecked(object sender, RoutedEventArgs e) + { + if (Provider != null) Provider.IsAudioEnabled = false; + if (_isConnected) DoReconnect(); + } + + private Media BuildMedia() + { + var media = new Media(_vlc, _lastUrl, FromType.FromLocation); + media.AddOption(":network-caching=300"); + if (AudioCheckBox.IsChecked != true) + media.AddOption(":no-audio"); + return media; + } + + // Reconnect without explicit Stop — Play(newMedia) makes VLC transition internally. + // _reconnecting suppresses the intermediate Stopped event so video stays visible. + private void DoReconnect() + { + if (_mediaPlayer == null || _vlc == null || string.IsNullOrEmpty(_lastUrl)) return; + _reconnecting = true; + SetStatus("Reconectando...", null); + var media = BuildMedia(); + _mediaPlayer.Play(media); + media.Dispose(); + } + + private void ShowVideo() + { + VideoView.Visibility = Visibility.Visible; + VideoPlaceholder.Visibility = Visibility.Collapsed; + } + + private void HideVideo() + { + VideoView.Visibility = Visibility.Collapsed; + VideoPlaceholder.Visibility = Visibility.Visible; + } + + private void ConnectButton_Click(object sender, RoutedEventArgs e) + { + _vlc = Provider?.LibVLCInstance; + if (_vlc == null) + { + SetStatus("VLC não encontrado na pasta 'libvlc/win-x64'", false); + return; + } + + _lastUrl = Provider?.BuildUrl(); + if (string.IsNullOrEmpty(_lastUrl)) + { + SetStatus(Provider?.Mode == SRTMode.Caller + ? "Preencha IP e Porta" + : "Preencha a Porta", false); + return; + } + + if (_mediaPlayer == null) + { + _mediaPlayer = new VlcMediaPlayer(_vlc); + _mediaPlayer.Playing += VlcMediaPlayer_Playing; + _mediaPlayer.EncounteredError += VlcMediaPlayer_Error; + _mediaPlayer.Stopped += VlcMediaPlayer_Stopped; + _mediaPlayer.EndReached += VlcMediaPlayer_Stopped; + } + + ShowVideo(); + VideoView.MediaPlayer = _mediaPlayer; + _mediaPlayer.Stop(); + SetStatus("Conectando...", null); + + var media = BuildMedia(); + _mediaPlayer.Play(media); + media.Dispose(); + } + + private void DisconnectButton_Click(object sender, RoutedEventArgs e) + { + _reconnecting = false; + _isConnected = false; + _mediaPlayer?.Stop(); + SetStatus("Desconectado", false); + HideVideo(); + } + + private void VlcMediaPlayer_Playing(object sender, EventArgs e) + { + Dispatcher.BeginInvoke(new Action(() => + { + _reconnecting = false; + _isConnected = true; + SetStatus("Conectado", true); + })); + } + + private void VlcMediaPlayer_Error(object sender, EventArgs e) + { + Dispatcher.BeginInvoke(new Action(() => + { + _reconnecting = false; + _isConnected = false; + SetStatus("Erro de conexão", false); + HideVideo(); + })); + } + + private void VlcMediaPlayer_Stopped(object sender, EventArgs e) + { + Dispatcher.BeginInvoke(new Action(() => + { + if (_reconnecting) return; + if (StatusText.Text == "Conectado") + { + _isConnected = false; + SetStatus("Desconectado", false); + HideVideo(); + } + })); + } + + private void SetStatus(string text, bool? connected) + { + StatusText.Text = text; + var color = connected == true + ? Colors.LimeGreen + : connected == false + ? Colors.OrangeRed + : Colors.Orange; + var brush = new SolidColorBrush(color); + StatusText.Foreground = brush; + StatusIndicator.Fill = brush; + } + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + + DataContextChanged -= OnDataContextChanged; + + if (_mediaPlayer != null) + { + _mediaPlayer.Playing -= VlcMediaPlayer_Playing; + _mediaPlayer.EncounteredError -= VlcMediaPlayer_Error; + _mediaPlayer.Stopped -= VlcMediaPlayer_Stopped; + _mediaPlayer.EndReached -= VlcMediaPlayer_Stopped; + _mediaPlayer.Stop(); + _mediaPlayer.Dispose(); + _mediaPlayer = null; + } + } + } +} diff --git a/vMixUTCSRTDataProvider/Properties/AssemblyInfo.cs b/vMixUTCSRTDataProvider/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b35c68a --- /dev/null +++ b/vMixUTCSRTDataProvider/Properties/AssemblyInfo.cs @@ -0,0 +1,17 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("vMixUTCSRTDataProvider")] +[assembly: AssemblyDescription("SRT Monitor")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("vMixUTCSRTDataProvider")] +[assembly: AssemblyCopyright("Copyright © 2025")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] +[assembly: Guid("f8a24b3c-e1d2-4f56-a78b-c90d12ef5678")] + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/vMixUTCSRTDataProvider/SRTDataProvider.cs b/vMixUTCSRTDataProvider/SRTDataProvider.cs new file mode 100644 index 0000000..3a1c097 --- /dev/null +++ b/vMixUTCSRTDataProvider/SRTDataProvider.cs @@ -0,0 +1,229 @@ +using LibVLCSharp.Shared; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Windows; +using vMixAPI; +using vMixControllerDataProvider; + +namespace UTCSRTDataProvider +{ + public enum SRTMode + { + Listener = 0, + Caller = 1 + } + + [Description("SRT Monitor")] + public class SRTDataProvider : IvMixDataProvider, IDisposable, INotifyPropertyChanged + { + // Core.Initialize only needs to run once; LibVLC instances are per-provider + // so each SRT Monitor has fully isolated audio state. + private static bool _coreInitialized; + private static readonly object _initLock = new object(); + private static string _vlcDir; + + internal LibVLC LibVLCInstance { get; private set; } + + private static string GetDefaultIp() + { + try + { + var url = StateFabrique.GetUrl(); + var uri = new Uri(url); + return uri.Host; + } + catch + { + return "127.0.0.1"; + } + } + + internal void EnsureVlcInitialized() + { + if (LibVLCInstance != null) return; + lock (_initLock) + { + if (!_coreInitialized) + { + try + { + var dir = Path.GetDirectoryName(typeof(SRTDataProvider).Assembly.Location); + _vlcDir = Path.Combine(dir, "libvlc", "win-x64"); + Core.Initialize(_vlcDir); + _coreInitialized = true; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[SRTDataProvider] Core.Initialize failed: {ex.Message}"); + return; + } + } + } + try + { + LibVLCInstance = new LibVLC(enableDebugLogs: false); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"[SRTDataProvider] LibVLC init failed: {ex.Message}"); + } + } + + private OnWidgetUI _ui; + private SRTMode _mode = SRTMode.Listener; + private string _ip = GetDefaultIp(); + private int _port = 4000; + private int _latency = 200; + private int _keyLength = 32; + private string _passphrase = ""; + private string _streamId = ""; + private bool _isAudioEnabled = true; + + public SRTMode Mode + { + get => _mode; + set + { + if (_mode == value) return; + _mode = value; + Notify(nameof(Mode)); + Notify(nameof(IsCallerMode)); + Notify(nameof(ModeIndex)); + } + } + + public bool IsCallerMode => Mode == SRTMode.Caller; + + public int ModeIndex + { + get => (int)Mode; + set { Mode = (SRTMode)value; } + } + + public string IP + { + get => _ip; + set { if (_ip == value) return; _ip = value; Notify(nameof(IP)); } + } + + public int Port + { + get => _port; + set { if (_port == value) return; _port = value; Notify(nameof(Port)); } + } + + public int Latency + { + get => _latency; + set { if (_latency == value) return; _latency = value; Notify(nameof(Latency)); } + } + + public int KeyLength + { + get => _keyLength; + set + { + if (_keyLength == value) return; + _keyLength = value; + Notify(nameof(KeyLength)); + Notify(nameof(KeyLengthIndex)); + } + } + + public int KeyLengthIndex + { + get => _keyLength == 16 ? 0 : _keyLength == 24 ? 1 : 2; + set { KeyLength = value == 0 ? 16 : value == 1 ? 24 : 32; } + } + + public string Passphrase + { + get => _passphrase; + set { if (_passphrase == value) return; _passphrase = value; Notify(nameof(Passphrase)); } + } + + public string StreamId + { + get => _streamId; + set { if (_streamId == value) return; _streamId = value; Notify(nameof(StreamId)); } + } + + public bool IsAudioEnabled + { + get => _isAudioEnabled; + set { if (_isAudioEnabled == value) return; _isAudioEnabled = value; Notify(nameof(IsAudioEnabled)); } + } + + public event PropertyChangedEventHandler PropertyChanged; + private void Notify(string n) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n)); + + public SRTDataProvider() + { + EnsureVlcInitialized(); + _ui = new OnWidgetUI { DataContext = this }; + } + + internal string BuildUrl() + { + if (_port <= 0 || _port > 65535) return null; + if (Mode == SRTMode.Caller && string.IsNullOrWhiteSpace(_ip)) return null; + + var sb = new System.Text.StringBuilder("srt://"); + + if (Mode == SRTMode.Caller) + sb.Append(_ip.Trim()); + + sb.Append($":{_port}"); + sb.Append($"?mode={Mode.ToString().ToLower()}"); + sb.Append($"&latency={_latency}"); + + if (!string.IsNullOrWhiteSpace(_passphrase)) + { + sb.Append($"&passphrase={Uri.EscapeDataString(_passphrase.Trim())}"); + sb.Append($"&pbkeylen={_keyLength}"); + } + + if (!string.IsNullOrWhiteSpace(_streamId)) + sb.Append($"&streamid={Uri.EscapeDataString(_streamId.Trim())}"); + + return sb.ToString(); + } + + public int Period { get; set; } + public bool IsProvidingCustomProperties => false; + public UIElement CustomUI => _ui; + public string[] Values => new string[0]; + public void ShowProperties(Window owner) { } + + public List GetProperties() => new List + { + (int)Mode, IP, Port, Latency, KeyLength, Passphrase, StreamId, IsAudioEnabled + }; + + public void SetProperties(List props) + { + if (props == null || props.Count == 0) return; + try + { + if (props.Count > 0 && props[0] != null) Mode = (SRTMode)Convert.ToInt32(props[0]); + if (props.Count > 1) IP = props[1] as string ?? ""; + if (props.Count > 2 && props[2] != null) Port = Convert.ToInt32(props[2]); + if (props.Count > 3 && props[3] != null) Latency = Convert.ToInt32(props[3]); + if (props.Count > 4 && props[4] != null) KeyLength = Convert.ToInt32(props[4]); + if (props.Count > 5) Passphrase = props[5] as string ?? ""; + if (props.Count > 6) StreamId = props[6] as string ?? ""; + if (props.Count > 7 && props[7] != null) IsAudioEnabled = Convert.ToBoolean(props[7]); + } + catch { } + } + + public void Dispose() + { + (_ui as IDisposable)?.Dispose(); + LibVLCInstance?.Dispose(); + LibVLCInstance = null; + } + } +} diff --git a/vMixUTCSRTDataProvider/app.config b/vMixUTCSRTDataProvider/app.config new file mode 100644 index 0000000..561f337 --- /dev/null +++ b/vMixUTCSRTDataProvider/app.config @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/vMixUTCSRTDataProvider/packages.config b/vMixUTCSRTDataProvider/packages.config new file mode 100644 index 0000000..43f6271 --- /dev/null +++ b/vMixUTCSRTDataProvider/packages.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/vMixUTCSRTDataProvider/vMixUTCSRTDataProvider.csproj b/vMixUTCSRTDataProvider/vMixUTCSRTDataProvider.csproj new file mode 100644 index 0000000..5eba0bf --- /dev/null +++ b/vMixUTCSRTDataProvider/vMixUTCSRTDataProvider.csproj @@ -0,0 +1,155 @@ + + + + + + Debug + AnyCPU + {F8A24B3C-E1D2-4F56-A78B-C90D12EF5678} + Library + Properties + UTCSRTDataProvider + SrtMonitorDataProvider + v4.7.2 + 512 + + + + + + true + full + false + ..\vMixController\bin\Debug\DataProviders\ + DEBUG;TRACE + prompt + 4 + x64 + + + pdbonly + true + ..\vMixController\bin\Release\DataProviders\ + TRACE + prompt + 4 + x64 + + + + + ..\packages\LibVLCSharp.3.8.5\lib\net471\LibVLCSharp.dll + + + ..\packages\LibVLCSharp.WPF.3.8.5\lib\net461\LibVLCSharp.WPF.dll + + + + ..\packages\MvvmLightLibs.5.4.1.1\lib\net45\GalaSoft.MvvmLight.dll + + + ..\packages\MvvmLightLibs.5.4.1.1\lib\net45\GalaSoft.MvvmLight.Extras.dll + + + ..\packages\MvvmLightLibs.5.4.1.1\lib\net45\GalaSoft.MvvmLight.Platform.dll + + + ..\packages\Costura.Fody.6.0.0\lib\netstandard2.0\Costura.dll + + + + ..\packages\Extended.Wpf.Toolkit.5.0.0\lib\net40\Xceed.Wpf.Toolkit.dll + False + + + + + + + + + + + + + ..\packages\MvvmLightLibs.5.4.1.1\lib\net45\System.Windows.Interactivity.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll + + + + + + + OnWidgetUI.xaml + + + + + Designer + MSBuild:Compile + + + + + {41B25F7D-B035-401B-A50A-C5BB3983C2AC} + vMixAPI + False + + + {21c1a5ec-9fd2-44f5-bdac-98f0787102c3} + vMixControllerDataProvider + + + {90bfeee2-82e3-47d2-9b56-47ddb4c611f2} + vMixControllerSkin + False + + + + + + + + + + + + + + + + + + +cd $(OutDir) +if not exist vlc mkdir vlc +if exist libvlc.dll move /y libvlc.dll vlc\libvlc.dll +if exist libvlccore.dll move /y libvlccore.dll vlc\libvlccore.dll +if exist plugins xcopy /e /q /y plugins\ vlc\plugins\ +if exist plugins rmdir /s /q plugins +del vMixControllerDataProvider.dll /q 2>nul +for /f %%F in ('dir /b /a-d ^| findstr /vile "DataProvider.dll"') do del "%%F" /Q /S + + + + + Pacote NuGet ausente: {0}. Use "Restaurar Pacotes NuGet" no Visual Studio. + + + + + From 1c14c68a1f3cb6bd12a973b7bc36a53f71310489 Mon Sep 17 00:00:00 2001 From: rafalau Date: Wed, 10 Jun 2026 13:05:37 -0300 Subject: [PATCH 02/12] Add README for SRT Monitor Data Provider Co-Authored-By: Claude Sonnet 4.6 --- vMixUTCSRTDataProvider/README.md | 98 ++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 vMixUTCSRTDataProvider/README.md diff --git a/vMixUTCSRTDataProvider/README.md b/vMixUTCSRTDataProvider/README.md new file mode 100644 index 0000000..5bf05bf --- /dev/null +++ b/vMixUTCSRTDataProvider/README.md @@ -0,0 +1,98 @@ +# SRT Monitor — Data Provider para vMixUTC + +Widget de monitoramento de streams SRT integrado ao [vMixUTC](https://github.com/elgarf/vMixUTC), desenvolvido como um **Data Provider** externo. + +--- + +## O que é + +O **SRT Monitor** permite receber e visualizar streams de vídeo via protocolo **SRT (Secure Reliable Transport)** diretamente dentro do vMixUTC, sem precisar abrir um player externo. Ideal para monitorar sinais de entrada antes de colocá-los no ar no vMix. + +--- + +## Funcionalidades + +| Recurso | Descrição | +|---|---| +| Modo Listener | Aguarda conexão de entrada na porta configurada | +| Modo Caller | Conecta ativamente em um host remoto (IP + Porta) | +| Preview de vídeo | Janela de preview embutida no widget com redimensionamento vertical | +| Controle de áudio | Checkbox por instância — isola completamente o áudio de cada widget | +| IP padrão automático | Pré-preenche o IP do host vMix já configurado no UTC | +| Configurações avançadas | Latência, AES passphrase, key length (128/192/256 bits), Stream ID | +| Ícones ▶ / ■ | Botões conectar/desconectar com ícones Font Awesome | +| Múltiplas instâncias | Cada widget opera de forma completamente independente | + +--- + +## Requisitos + +### libvlc (VLC nativo) + +O widget depende das DLLs nativas do VLC. Elas **não estão incluídas** no repositório (são grandes demais). + +1. Baixe o **VLC 3.x** para Windows 64-bit em [videolan.org](https://www.videolan.org) +2. Copie as pastas `libvlc` e `plugins` do diretório de instalação do VLC para: + +``` +vMixController/bin/Debug/DataProviders/libvlc/win-x64/ +``` + +A estrutura esperada: + +``` +DataProviders/ +├── SrtMonitorDataProvider.dll +└── libvlc/ + └── win-x64/ + ├── libvlc.dll + ├── libvlccore.dll + └── plugins/ + └── ... +``` + +--- + +## Tecnologias utilizadas + +| Biblioteca | Versão | Finalidade | +|---|---|---| +| [LibVLCSharp](https://github.com/videolan/libvlcsharp) | 3.8.5 | Wrapper .NET para libvlc | +| [LibVLCSharp.WPF](https://github.com/videolan/libvlcsharp) | 3.8.5 | Controle WPF para preview de vídeo | +| libvlc (VLC) | 3.x | Engine de reprodução / SRT decoder | +| .NET Framework | 4.7.2 | Target framework do vMixUTC | +| Costura.Fody | — | Embute as DLLs gerenciadas no assembly final | + +--- + +## Arquitetura + +O plugin implementa a interface `IvMixDataProvider` do vMixUTC: + +``` +vMixControlExternalData (host widget no UTC) + └── SRTDataProvider (IvMixDataProvider) + ├── LibVLC (instância por widget — isolamento de áudio) + └── OnWidgetUI (UI WPF com VideoView embutido) + └── MediaPlayer (por instância) +``` + +Cada widget cria sua própria instância de `LibVLC` e `MediaPlayer`, garantindo isolamento total de vídeo e áudio entre múltiplos monitores na mesma tela. + +O controle de áudio usa a opção `:no-audio` diretamente na mídia quando o checkbox é desmarcado, e reconecta automaticamente ao alternar — evitando qualquer compartilhamento de estado no pipeline de áudio do Windows. + +--- + +## Como compilar + +```bash +# Restaurar pacotes NuGet e compilar (gera o DLL em vMixController/bin/Debug/DataProviders/) +MSBuild vMixUTCSRTDataProvider.csproj /p:Configuration=Debug /p:Platform=AnyCPU /t:Build +``` + +--- + +## Créditos + +Idealizado por **[rafalau](https://github.com/rafalau)**. +Implementado com assistência de **Claude Sonnet 4.6** (Anthropic). From 53746ccb322fecfaf5e40bfbc586282c8b9a1e71 Mon Sep 17 00:00:00 2001 From: rafalau Date: Fri, 19 Jun 2026 15:37:33 -0300 Subject: [PATCH 03/12] =?UTF-8?q?Fix=20SRT=20plugin=20not=20loading=20in?= =?UTF-8?q?=20Release=20=E2=80=94=20change=20PlatformTarget=20to=20AnyCPU?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The main vMixController.exe is built with 32BITPREF=1 (runs as WoW64/32-bit). The SRT plugin was compiled as x64 (PE32+), causing BadImageFormatException on load. Changed PlatformTarget from x64 to AnyCPU (PE32) so the managed DLL loads in both 32-bit and 64-bit host processes. VLC native DLLs remain in libvlc/win-x64/ and are loaded at runtime only when the user connects. Co-Authored-By: Claude Sonnet 4.6 --- vMixUTCSRTDataProvider/vMixUTCSRTDataProvider.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vMixUTCSRTDataProvider/vMixUTCSRTDataProvider.csproj b/vMixUTCSRTDataProvider/vMixUTCSRTDataProvider.csproj index 5eba0bf..b9212a5 100644 --- a/vMixUTCSRTDataProvider/vMixUTCSRTDataProvider.csproj +++ b/vMixUTCSRTDataProvider/vMixUTCSRTDataProvider.csproj @@ -24,7 +24,7 @@ DEBUG;TRACE prompt 4 - x64 + AnyCPU pdbonly @@ -33,7 +33,7 @@ TRACE prompt 4 - x64 + AnyCPU From f4e9d3a85c6a301ab39fea4596d791cb755dad61 Mon Sep 17 00:00:00 2001 From: rafalau Date: Fri, 19 Jun 2026 15:42:42 -0300 Subject: [PATCH 04/12] Fix VLC path selection for 32-bit process Detect process architecture at runtime and load libvlc from win-x86 when the host process is 32-bit (WoW64), instead of always hardcoding win-x64 which fails to load under the 32BITPREF=1 Release binary. Co-Authored-By: Claude Sonnet 4.6 --- vMixUTCSRTDataProvider/SRTDataProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vMixUTCSRTDataProvider/SRTDataProvider.cs b/vMixUTCSRTDataProvider/SRTDataProvider.cs index 3a1c097..2decc90 100644 --- a/vMixUTCSRTDataProvider/SRTDataProvider.cs +++ b/vMixUTCSRTDataProvider/SRTDataProvider.cs @@ -50,7 +50,8 @@ internal void EnsureVlcInitialized() try { var dir = Path.GetDirectoryName(typeof(SRTDataProvider).Assembly.Location); - _vlcDir = Path.Combine(dir, "libvlc", "win-x64"); + var arch = Environment.Is64BitProcess ? "win-x64" : "win-x86"; + _vlcDir = Path.Combine(dir, "libvlc", arch); Core.Initialize(_vlcDir); _coreInitialized = true; } From 250a6e7cf9175e298a25c8972970639d186b08a6 Mon Sep 17 00:00:00 2001 From: rafalau Date: Sat, 20 Jun 2026 15:21:21 -0300 Subject: [PATCH 05/12] Fix Release build: Prefer32Bit=false, VLC path, download instructions - vMixController Release: add Prefer32Bit=false so process runs as 64-bit (same as Debug), allowing win-x64 VLC to load correctly - SRTDataProvider: detect process arch at runtime for VLC path selection - OnWidgetUI: surface real VLC init error instead of hardcoded message - README: add download link and install instructions for the prebuilt ZIP Co-Authored-By: Claude Sonnet 4.6 --- vMixController/vMixController.csproj | 1 + vMixUTCSRTDataProvider/OnWidgetUI.xaml.cs | 2 +- vMixUTCSRTDataProvider/README.md | 34 +++++++++++------------ vMixUTCSRTDataProvider/SRTDataProvider.cs | 5 ++-- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/vMixController/vMixController.csproj b/vMixController/vMixController.csproj index c450ffb..b986259 100644 --- a/vMixController/vMixController.csproj +++ b/vMixController/vMixController.csproj @@ -63,6 +63,7 @@ prompt 4 false + false settings-gears %281%29.ico diff --git a/vMixUTCSRTDataProvider/OnWidgetUI.xaml.cs b/vMixUTCSRTDataProvider/OnWidgetUI.xaml.cs index 01cb38e..6f9d19c 100644 --- a/vMixUTCSRTDataProvider/OnWidgetUI.xaml.cs +++ b/vMixUTCSRTDataProvider/OnWidgetUI.xaml.cs @@ -96,7 +96,7 @@ private void ConnectButton_Click(object sender, RoutedEventArgs e) _vlc = Provider?.LibVLCInstance; if (_vlc == null) { - SetStatus("VLC não encontrado na pasta 'libvlc/win-x64'", false); + SetStatus(Provider?.VlcInitError ?? "VLC não inicializado", false); return; } diff --git a/vMixUTCSRTDataProvider/README.md b/vMixUTCSRTDataProvider/README.md index 5bf05bf..e26e23f 100644 --- a/vMixUTCSRTDataProvider/README.md +++ b/vMixUTCSRTDataProvider/README.md @@ -25,32 +25,32 @@ O **SRT Monitor** permite receber e visualizar streams de vídeo via protocolo * --- -## Requisitos +## Download e Instalação -### libvlc (VLC nativo) +### Download pronto para usar -O widget depende das DLLs nativas do VLC. Elas **não estão incluídas** no repositório (são grandes demais). +**[⬇ Baixar SrtMonitorDataProvider.zip](https://github.com/rafalau/vMixUTC/releases/download/v1.0.0-srt/SrtMonitorDataProvider.zip)** (~44 MB) -1. Baixe o **VLC 3.x** para Windows 64-bit em [videolan.org](https://www.videolan.org) -2. Copie as pastas `libvlc` e `plugins` do diretório de instalação do VLC para: +O ZIP já inclui o plugin **e** as DLLs nativas do VLC. Não é necessário instalar o VLC separadamente. -``` -vMixController/bin/Debug/DataProviders/libvlc/win-x64/ -``` +### Como instalar -A estrutura esperada: +1. Extraia o ZIP +2. Copie a pasta `DataProviders` extraída para dentro da pasta de instalação do **vMixUTC**, mesclando com a pasta existente: ``` -DataProviders/ -├── SrtMonitorDataProvider.dll -└── libvlc/ - └── win-x64/ - ├── libvlc.dll - ├── libvlccore.dll - └── plugins/ - └── ... +vMixUTC/ +└── DataProviders/ ← cole aqui o conteúdo extraído + ├── SrtMonitorDataProvider.dll + └── libvlc/ + └── win-x64/ + ├── libvlc.dll + ├── libvlccore.dll + └── plugins/ ``` +3. Reinicie o vMixUTC — o widget **SRT Monitor** aparecerá na lista de Data Providers. + --- ## Tecnologias utilizadas diff --git a/vMixUTCSRTDataProvider/SRTDataProvider.cs b/vMixUTCSRTDataProvider/SRTDataProvider.cs index 2decc90..9437e13 100644 --- a/vMixUTCSRTDataProvider/SRTDataProvider.cs +++ b/vMixUTCSRTDataProvider/SRTDataProvider.cs @@ -25,6 +25,7 @@ public class SRTDataProvider : IvMixDataProvider, IDisposable, INotifyPropertyCh private static string _vlcDir; internal LibVLC LibVLCInstance { get; private set; } + internal string VlcInitError { get; private set; } private static string GetDefaultIp() { @@ -57,7 +58,7 @@ internal void EnsureVlcInitialized() } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"[SRTDataProvider] Core.Initialize failed: {ex.Message}"); + VlcInitError = $"Core.Initialize falhou em '{_vlcDir}': {ex.GetType().Name}: {ex.Message}"; return; } } @@ -68,7 +69,7 @@ internal void EnsureVlcInitialized() } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"[SRTDataProvider] LibVLC init failed: {ex.Message}"); + VlcInitError = $"new LibVLC() falhou: {ex.GetType().Name}: {ex.Message}"; } } From 4ba8812ce1aba2f70f0f468c09a46792ec7f6600 Mon Sep 17 00:00:00 2001 From: rafalau Date: Sat, 20 Jun 2026 15:22:54 -0300 Subject: [PATCH 06/12] Add SRT Monitor download link to main README Co-Authored-By: Claude Sonnet 4.6 --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index d800333..155145f 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,15 @@ vMix Universal Title Controller (UTC) is program, which work with vMix through official vMix API. It can replace some paid or expensive software for making titles/scoreboards/lower thirds (by using pure vMix XAML/GT titles) and add ability of base scripting. + +--- + +## Plugins disponíveis + +### SRT Monitor Data Provider + +Monitore streams SRT diretamente dentro do vMixUTC, em modo Listener ou Caller, com preview de vídeo embutido. + +**[⬇ Baixar SrtMonitorDataProvider.zip](https://github.com/rafalau/vMixUTC/releases/download/v1.0.0-srt/SrtMonitorDataProvider.zip)** (~44 MB — já inclui VLC) + +Instruções de instalação: [vMixUTCSRTDataProvider/README.md](vMixUTCSRTDataProvider/README.md) From 41e2382b99d25b6b1e4349f75bda1b11db496ed0 Mon Sep 17 00:00:00 2001 From: rafalau Date: Sat, 20 Jun 2026 15:57:04 -0300 Subject: [PATCH 07/12] Add Enable Auto Sync via vMix TCP subscription (port 8099) New toggle "Enable Auto Sync" in the menu. When enabled, a background TCP connection subscribes to SUBSCRIBE ACTS on vMix port 8099. Any event fired by vMix (input added, switched, changed) triggers an immediate SyncTovMixState() with 300ms debounce, instead of waiting for the 20s polling cycle. - VmixTcpSubscriber: connects TCP 8099, sends SUBSCRIBE ACTS, fires debounced event on any ACTS line; auto-reconnects on disconnect - MainWindowSettings.AutoSync: persisted bool property - MainViewModel: starts/stops subscriber when AutoSync or IP changes - MainWindow.xaml: checkable "Enable Auto Sync" menu item Co-Authored-By: Claude Sonnet 4.6 --- vMixController/Classes/MainWindowSettings.cs | 13 +++ vMixController/Classes/VmixTcpSubscriber.cs | 91 ++++++++++++++++++++ vMixController/MainWindow.xaml | 8 ++ vMixController/ViewModel/MainViewModel.cs | 26 ++++++ vMixController/vMixController.csproj | 1 + 5 files changed, 139 insertions(+) create mode 100644 vMixController/Classes/VmixTcpSubscriber.cs diff --git a/vMixController/Classes/MainWindowSettings.cs b/vMixController/Classes/MainWindowSettings.cs index 0a891df..b8cd880 100644 --- a/vMixController/Classes/MainWindowSettings.cs +++ b/vMixController/Classes/MainWindowSettings.cs @@ -772,6 +772,19 @@ public void UpdatePages() RaisePropertyChanged(PagesPropertyName); } + private bool _autoSync = false; + + public bool AutoSync + { + get { return _autoSync; } + set + { + if (_autoSync == value) return; + _autoSync = value; + RaisePropertyChanged(nameof(AutoSync)); + } + } + internal void AddRecentFile(string fileName) { if (RecentFiles == null) diff --git a/vMixController/Classes/VmixTcpSubscriber.cs b/vMixController/Classes/VmixTcpSubscriber.cs new file mode 100644 index 0000000..255c348 --- /dev/null +++ b/vMixController/Classes/VmixTcpSubscriber.cs @@ -0,0 +1,91 @@ +using System; +using System.IO; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace vMixController.Classes +{ + internal sealed class VmixTcpSubscriber : IDisposable + { + public event EventHandler ActsReceived; + + private CancellationTokenSource _cts; + private Task _loopTask; + private Timer _debounce; + private const int TcpPort = 8099; + private const int DebounceMs = 300; + + public void Start(string host) + { + Stop(); + _cts = new CancellationTokenSource(); + var token = _cts.Token; + _loopTask = Task.Run(() => LoopAsync(host, token)); + } + + public void Stop() + { + _cts?.Cancel(); + _loopTask = null; + _cts?.Dispose(); + _cts = null; + } + + private async Task LoopAsync(string host, CancellationToken ct) + { + while (!ct.IsCancellationRequested) + { + try + { + using (var client = new TcpClient()) + { + client.ReceiveTimeout = 2000; + await client.ConnectAsync(host, TcpPort).ConfigureAwait(false); + + using (var stream = client.GetStream()) + using (var writer = new StreamWriter(stream) { AutoFlush = true, NewLine = "\r\n" }) + using (var reader = new StreamReader(stream)) + { + await writer.WriteLineAsync("SUBSCRIBE ACTS").ConfigureAwait(false); + + while (!ct.IsCancellationRequested) + { + string line; + try + { + line = await reader.ReadLineAsync().ConfigureAwait(false); + } + catch (IOException) { break; } + + if (line == null) break; + + // "SUBSCRIBE OK ACTS" is the subscription confirmation — skip it + // actual events arrive as "ACTS OK ..." + if (line.StartsWith("ACTS ", StringComparison.Ordinal)) + FireDebounced(); + } + } + } + } + catch (OperationCanceledException) { break; } + catch { /* connection failed — retry after delay */ } + + try { await Task.Delay(3000, ct).ConfigureAwait(false); } + catch (OperationCanceledException) { break; } + } + } + + private void FireDebounced() + { + _debounce?.Dispose(); + _debounce = new Timer(_ => ActsReceived?.Invoke(this, EventArgs.Empty), null, DebounceMs, Timeout.Infinite); + } + + public void Dispose() + { + Stop(); + _debounce?.Dispose(); + } + } +} diff --git a/vMixController/MainWindow.xaml b/vMixController/MainWindow.xaml index c791e91..4eda31c 100644 --- a/vMixController/MainWindow.xaml +++ b/vMixController/MainWindow.xaml @@ -498,6 +498,14 @@ + + + + + + + + diff --git a/vMixController/ViewModel/MainViewModel.cs b/vMixController/ViewModel/MainViewModel.cs index 431b3da..31ecf5e 100644 --- a/vMixController/ViewModel/MainViewModel.cs +++ b/vMixController/ViewModel/MainViewModel.cs @@ -400,6 +400,29 @@ private void WindowSettings_PropertyChanged(object sender, System.ComponentModel CheckvMixConnection(null, new EventArgs()); }); + if (e.PropertyName == nameof(Classes.MainWindowSettings.AutoSync) || + e.PropertyName == nameof(Classes.MainWindowSettings.IP)) + UpdateTcpSubscriber(); + } + + private void UpdateTcpSubscriber() + { + if (WindowSettings?.AutoSync == true && !string.IsNullOrWhiteSpace(WindowSettings.IP)) + { + _tcpSubscriber.ActsReceived -= OnVmixActsReceived; + _tcpSubscriber.ActsReceived += OnVmixActsReceived; + _tcpSubscriber.Start(WindowSettings.IP); + } + else + { + _tcpSubscriber.ActsReceived -= OnVmixActsReceived; + _tcpSubscriber.Stop(); + } + } + + private void OnVmixActsReceived(object sender, EventArgs e) + { + Application.Current?.Dispatcher.BeginInvoke(new Action(SyncTovMixState)); } private void LocalizationManager_CultureChanged(object sender, EventArgs e) @@ -2664,6 +2687,7 @@ public RelayCommand ResetScalingCommand DispatcherTimer _connectTimer = new DispatcherTimer(); DispatcherTimer _metricsTimer = new DispatcherTimer(); + Classes.VmixTcpSubscriber _tcpSubscriber = new Classes.VmixTcpSubscriber(); string _documentsPath; @@ -3224,6 +3248,8 @@ protected virtual void Dispose(bool managed) _connectTimer.Stop(); _connectTimer.Tick -= CheckvMixConnection; _metricsTimer.Stop(); + _tcpSubscriber.ActsReceived -= OnVmixActsReceived; + _tcpSubscriber.Dispose(); LocalizationManager.Instance.CultureChanged -= LocalizationManager_CultureChanged; vMixAPI.StateFabrique.OnStateCreated -= State_OnStateCreated; } diff --git a/vMixController/vMixController.csproj b/vMixController/vMixController.csproj index b986259..e98574a 100644 --- a/vMixController/vMixController.csproj +++ b/vMixController/vMixController.csproj @@ -424,6 +424,7 @@ + From 43060a046c9a24a2fba72593e442d7c0f6c17124 Mon Sep 17 00:00:00 2001 From: rafalau Date: Sat, 20 Jun 2026 16:55:02 -0300 Subject: [PATCH 08/12] Fix Auto Sync: add 2s XML polling to detect all vMix changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SUBSCRIBE ACTS only fires on function calls, missing text edits and other direct vMix UI changes. Add a 2-second XML polling loop that compares the full vMix XML hash — if anything changed, triggers sync. TCP subscriber is kept as a complement for instant ACTS notification. Co-Authored-By: Claude Sonnet 4.6 --- vMixController/ViewModel/MainViewModel.cs | 35 +++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/vMixController/ViewModel/MainViewModel.cs b/vMixController/ViewModel/MainViewModel.cs index 31ecf5e..501115c 100644 --- a/vMixController/ViewModel/MainViewModel.cs +++ b/vMixController/ViewModel/MainViewModel.cs @@ -402,21 +402,28 @@ private void WindowSettings_PropertyChanged(object sender, System.ComponentModel if (e.PropertyName == nameof(Classes.MainWindowSettings.AutoSync) || e.PropertyName == nameof(Classes.MainWindowSettings.IP)) - UpdateTcpSubscriber(); + UpdateAutoSync(); } - private void UpdateTcpSubscriber() + private void UpdateAutoSync() { if (WindowSettings?.AutoSync == true && !string.IsNullOrWhiteSpace(WindowSettings.IP)) { + // TCP subscriber: catches ACTS events instantly (cuts, transitions, API calls) _tcpSubscriber.ActsReceived -= OnVmixActsReceived; _tcpSubscriber.ActsReceived += OnVmixActsReceived; _tcpSubscriber.Start(WindowSettings.IP); + + // Fast XML poll every 2s: catches text changes, input list changes, anything else + _lastXmlHash = null; + _autoSyncTimer.Start(); } else { _tcpSubscriber.ActsReceived -= OnVmixActsReceived; _tcpSubscriber.Stop(); + _autoSyncTimer.Stop(); + _lastXmlHash = null; } } @@ -425,6 +432,23 @@ private void OnVmixActsReceived(object sender, EventArgs e) Application.Current?.Dispatcher.BeginInvoke(new Action(SyncTovMixState)); } + private void AutoSyncTick(object sender, EventArgs e) + { + if (!IsUrlValid || WindowSettings?.AutoSync != true) return; + var url = vMixAPI.StateFabrique.GetUrl(WindowSettings.IP, WindowSettings.Port); + vMixAPI.APIRequestManagerV2.GetApiResponseAsync(url, new WeakAction((response, exception) => + { + if (exception != null || string.IsNullOrEmpty(response)) return; + var hash = response.GetHashCode().ToString(); + Application.Current?.Dispatcher.BeginInvoke(new Action(() => + { + if (_lastXmlHash != null && _lastXmlHash != hash) + SyncTovMixState(); + _lastXmlHash = hash; + })); + }), vMixAPI.StateFabrique.GetCredentials(WindowSettings.HttpLogin, WindowSettings.HttpPassword)); + } + private void LocalizationManager_CultureChanged(object sender, EventArgs e) { LocalizeVirtualInputs(Model); @@ -2687,7 +2711,9 @@ public RelayCommand ResetScalingCommand DispatcherTimer _connectTimer = new DispatcherTimer(); DispatcherTimer _metricsTimer = new DispatcherTimer(); + DispatcherTimer _autoSyncTimer = new DispatcherTimer(); Classes.VmixTcpSubscriber _tcpSubscriber = new Classes.VmixTcpSubscriber(); + private string _lastXmlHash = null; string _documentsPath; @@ -2772,6 +2798,9 @@ public MainViewModel() _connectTimer.Tick += CheckvMixConnection; _connectTimer.Start(); + _autoSyncTimer.Interval = TimeSpan.FromSeconds(2); + _autoSyncTimer.Tick += AutoSyncTick; + _metricsTimer.Interval = TimeSpan.FromSeconds(30); _metricsTimer.Tick += (sender, args) => { @@ -3248,6 +3277,8 @@ protected virtual void Dispose(bool managed) _connectTimer.Stop(); _connectTimer.Tick -= CheckvMixConnection; _metricsTimer.Stop(); + _autoSyncTimer.Stop(); + _autoSyncTimer.Tick -= AutoSyncTick; _tcpSubscriber.ActsReceived -= OnVmixActsReceived; _tcpSubscriber.Dispose(); LocalizationManager.Instance.CultureChanged -= LocalizationManager_CultureChanged; From ed48a43824cf124e62fb43d80bcf5e2c60a8fee9 Mon Sep 17 00:00:00 2001 From: rafalau Date: Sat, 20 Jun 2026 17:00:04 -0300 Subject: [PATCH 09/12] Simplify Auto Sync: call SyncTovMixState every 1s when enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove complex hash/TCP approach. When Enable Auto Sync is active, a DispatcherTimer fires SyncTovMixState() every second — guaranteed to catch any change in vMix regardless of type. Co-Authored-By: Claude Sonnet 4.6 --- vMixController/ViewModel/MainViewModel.cs | 42 +++-------------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/vMixController/ViewModel/MainViewModel.cs b/vMixController/ViewModel/MainViewModel.cs index 501115c..d2bd412 100644 --- a/vMixController/ViewModel/MainViewModel.cs +++ b/vMixController/ViewModel/MainViewModel.cs @@ -400,53 +400,21 @@ private void WindowSettings_PropertyChanged(object sender, System.ComponentModel CheckvMixConnection(null, new EventArgs()); }); - if (e.PropertyName == nameof(Classes.MainWindowSettings.AutoSync) || - e.PropertyName == nameof(Classes.MainWindowSettings.IP)) + if (e.PropertyName == nameof(Classes.MainWindowSettings.AutoSync)) UpdateAutoSync(); } private void UpdateAutoSync() { - if (WindowSettings?.AutoSync == true && !string.IsNullOrWhiteSpace(WindowSettings.IP)) - { - // TCP subscriber: catches ACTS events instantly (cuts, transitions, API calls) - _tcpSubscriber.ActsReceived -= OnVmixActsReceived; - _tcpSubscriber.ActsReceived += OnVmixActsReceived; - _tcpSubscriber.Start(WindowSettings.IP); - - // Fast XML poll every 2s: catches text changes, input list changes, anything else - _lastXmlHash = null; + if (WindowSettings?.AutoSync == true) _autoSyncTimer.Start(); - } else - { - _tcpSubscriber.ActsReceived -= OnVmixActsReceived; - _tcpSubscriber.Stop(); _autoSyncTimer.Stop(); - _lastXmlHash = null; - } - } - - private void OnVmixActsReceived(object sender, EventArgs e) - { - Application.Current?.Dispatcher.BeginInvoke(new Action(SyncTovMixState)); } private void AutoSyncTick(object sender, EventArgs e) { - if (!IsUrlValid || WindowSettings?.AutoSync != true) return; - var url = vMixAPI.StateFabrique.GetUrl(WindowSettings.IP, WindowSettings.Port); - vMixAPI.APIRequestManagerV2.GetApiResponseAsync(url, new WeakAction((response, exception) => - { - if (exception != null || string.IsNullOrEmpty(response)) return; - var hash = response.GetHashCode().ToString(); - Application.Current?.Dispatcher.BeginInvoke(new Action(() => - { - if (_lastXmlHash != null && _lastXmlHash != hash) - SyncTovMixState(); - _lastXmlHash = hash; - })); - }), vMixAPI.StateFabrique.GetCredentials(WindowSettings.HttpLogin, WindowSettings.HttpPassword)); + SyncTovMixState(); } private void LocalizationManager_CultureChanged(object sender, EventArgs e) @@ -2713,7 +2681,6 @@ public RelayCommand ResetScalingCommand DispatcherTimer _metricsTimer = new DispatcherTimer(); DispatcherTimer _autoSyncTimer = new DispatcherTimer(); Classes.VmixTcpSubscriber _tcpSubscriber = new Classes.VmixTcpSubscriber(); - private string _lastXmlHash = null; string _documentsPath; @@ -2798,7 +2765,7 @@ public MainViewModel() _connectTimer.Tick += CheckvMixConnection; _connectTimer.Start(); - _autoSyncTimer.Interval = TimeSpan.FromSeconds(2); + _autoSyncTimer.Interval = TimeSpan.FromSeconds(1); _autoSyncTimer.Tick += AutoSyncTick; _metricsTimer.Interval = TimeSpan.FromSeconds(30); @@ -3279,7 +3246,6 @@ protected virtual void Dispose(bool managed) _metricsTimer.Stop(); _autoSyncTimer.Stop(); _autoSyncTimer.Tick -= AutoSyncTick; - _tcpSubscriber.ActsReceived -= OnVmixActsReceived; _tcpSubscriber.Dispose(); LocalizationManager.Instance.CultureChanged -= LocalizationManager_CultureChanged; vMixAPI.StateFabrique.OnStateCreated -= State_OnStateCreated; From 456d2dacd4efc7f342eedda2f370ed3ee68739d2 Mon Sep 17 00:00:00 2001 From: rafalau Date: Sat, 20 Jun 2026 17:19:17 -0300 Subject: [PATCH 10/12] Fix Auto Sync: bypass cache and start timer on WindowSettings load - UpdateAsync(ignoreCache): new param passes ignoreCache:true to SendFunction so the batcher doesn't return stale data every 1s - AutoSyncTick calls Model.UpdateAsync(ignoreCache:true) directly - UpdateAutoSync() is now also called when WindowSettings is set, so the timer starts immediately if AutoSync was saved as true Co-Authored-By: Claude Sonnet 4.6 --- vMixAPI/State.cs | 4 ++-- vMixController/ViewModel/MainViewModel.cs | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/vMixAPI/State.cs b/vMixAPI/State.cs index 2e0e95f..eab0500 100644 --- a/vMixAPI/State.cs +++ b/vMixAPI/State.cs @@ -363,7 +363,7 @@ public bool Update() } } - public void UpdateAsync() + public void UpdateAsync(bool ignoreCache = false) { SendFunction("", true, x => { @@ -453,7 +453,7 @@ public void UpdateAsync() else Dispatcher.BeginInvoke(updateAction); - }); + }, ignoreCache: ignoreCache); } // Hold callback state strongly until async response is received and callback is dispatched. diff --git a/vMixController/ViewModel/MainViewModel.cs b/vMixController/ViewModel/MainViewModel.cs index d2bd412..9ab9973 100644 --- a/vMixController/ViewModel/MainViewModel.cs +++ b/vMixController/ViewModel/MainViewModel.cs @@ -389,6 +389,7 @@ public MainWindowSettings WindowSettings _windowSettings.PropertyChanged += WindowSettings_PropertyChanged; RaisePropertyChanged(nameof(WindowSettings)); + UpdateAutoSync(); } } @@ -414,7 +415,10 @@ private void UpdateAutoSync() private void AutoSyncTick(object sender, EventArgs e) { - SyncTovMixState(); + if (Model != null) + Model.UpdateAsync(ignoreCache: true); + else + SyncTovMixState(); } private void LocalizationManager_CultureChanged(object sender, EventArgs e) From 9f079b2925e8775454eef3197708c73bfcc9e52c Mon Sep 17 00:00:00 2001 From: rafalau Date: Sat, 20 Jun 2026 17:25:58 -0300 Subject: [PATCH 11/12] Update README: v1.1 download link and Enable Auto Sync feature Co-Authored-By: Claude Sonnet 4.6 --- README.md | 2 +- vMixUTCSRTDataProvider/README.md | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 155145f..ac4b571 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,6 @@ and add ability of base scripting. Monitore streams SRT diretamente dentro do vMixUTC, em modo Listener ou Caller, com preview de vídeo embutido. -**[⬇ Baixar SrtMonitorDataProvider.zip](https://github.com/rafalau/vMixUTC/releases/download/v1.0.0-srt/SrtMonitorDataProvider.zip)** (~44 MB — já inclui VLC) +**[⬇ Baixar SrtMonitorDataProvider.zip](https://github.com/rafalau/vMixUTC/releases/download/v1.1.0-srt/SrtMonitorDataProvider.zip)** (~44 MB — já inclui VLC) — *v1.1: Enable Auto Sync* Instruções de instalação: [vMixUTCSRTDataProvider/README.md](vMixUTCSRTDataProvider/README.md) diff --git a/vMixUTCSRTDataProvider/README.md b/vMixUTCSRTDataProvider/README.md index e26e23f..85b8fa0 100644 --- a/vMixUTCSRTDataProvider/README.md +++ b/vMixUTCSRTDataProvider/README.md @@ -25,11 +25,19 @@ O **SRT Monitor** permite receber e visualizar streams de vídeo via protocolo * --- +## Enable Auto Sync *(v1.1)* + +O vMixUTC não atualiza automaticamente quando algo muda no vMix — é necessário clicar em **Sync** manualmente. A partir da v1.1, o menu do UTC conta com o toggle **Enable Auto Sync**. + +Quando ativado, o UTC consulta o vMix a cada segundo (ignorando cache) e detecta qualquer alteração — inputs adicionados, textos modificados, configurações trocadas — atualizando a interface automaticamente. + +--- + ## Download e Instalação ### Download pronto para usar -**[⬇ Baixar SrtMonitorDataProvider.zip](https://github.com/rafalau/vMixUTC/releases/download/v1.0.0-srt/SrtMonitorDataProvider.zip)** (~44 MB) +**[⬇ Baixar SrtMonitorDataProvider.zip](https://github.com/rafalau/vMixUTC/releases/download/v1.1.0-srt/SrtMonitorDataProvider.zip)** (~44 MB) O ZIP já inclui o plugin **e** as DLLs nativas do VLC. Não é necessário instalar o VLC separadamente. From d097eb84746c6a71cb6e31a261c9e7258f115f9e Mon Sep 17 00:00:00 2001 From: rafalau Date: Sat, 20 Jun 2026 17:43:26 -0300 Subject: [PATCH 12/12] Update README: ZIP from Release build (~89 MB), simplify install instructions Co-Authored-By: Claude Sonnet 4.6 --- README.md | 2 +- vMixUTCSRTDataProvider/README.md | 67 ++++++-------------------------- 2 files changed, 12 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index ac4b571..7a1e841 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,6 @@ and add ability of base scripting. Monitore streams SRT diretamente dentro do vMixUTC, em modo Listener ou Caller, com preview de vídeo embutido. -**[⬇ Baixar SrtMonitorDataProvider.zip](https://github.com/rafalau/vMixUTC/releases/download/v1.1.0-srt/SrtMonitorDataProvider.zip)** (~44 MB — já inclui VLC) — *v1.1: Enable Auto Sync* +**[⬇ Baixar SrtMonitorDataProvider.zip](https://github.com/rafalau/vMixUTC/releases/download/v1.1.0-srt/SrtMonitorDataProvider.zip)** (~86 MB — já inclui VLC) — *v1.1: Enable Auto Sync* Instruções de instalação: [vMixUTCSRTDataProvider/README.md](vMixUTCSRTDataProvider/README.md) diff --git a/vMixUTCSRTDataProvider/README.md b/vMixUTCSRTDataProvider/README.md index 85b8fa0..79cee48 100644 --- a/vMixUTCSRTDataProvider/README.md +++ b/vMixUTCSRTDataProvider/README.md @@ -4,6 +4,16 @@ Widget de monitoramento de streams SRT integrado ao [vMixUTC](https://github.com --- +## Download e Instalação + +**[⬇ Baixar SrtMonitorDataProvider.zip](https://github.com/rafalau/vMixUTC/releases/download/v1.1.0-srt/SrtMonitorDataProvider.zip)** (~86 MB — já inclui VLC) + +1. Extraia o ZIP +2. Copie a pasta `DataProviders` para dentro da pasta de instalação do **vMixUTC**, mesclando com a pasta existente +3. Reinicie o vMixUTC — o widget **SRT Monitor** aparecerá na lista de Data Providers + +--- + ## O que é O **SRT Monitor** permite receber e visualizar streams de vídeo via protocolo **SRT (Secure Reliable Transport)** diretamente dentro do vMixUTC, sem precisar abrir um player externo. Ideal para monitorar sinais de entrada antes de colocá-los no ar no vMix. @@ -29,35 +39,7 @@ O **SRT Monitor** permite receber e visualizar streams de vídeo via protocolo * O vMixUTC não atualiza automaticamente quando algo muda no vMix — é necessário clicar em **Sync** manualmente. A partir da v1.1, o menu do UTC conta com o toggle **Enable Auto Sync**. -Quando ativado, o UTC consulta o vMix a cada segundo (ignorando cache) e detecta qualquer alteração — inputs adicionados, textos modificados, configurações trocadas — atualizando a interface automaticamente. - ---- - -## Download e Instalação - -### Download pronto para usar - -**[⬇ Baixar SrtMonitorDataProvider.zip](https://github.com/rafalau/vMixUTC/releases/download/v1.1.0-srt/SrtMonitorDataProvider.zip)** (~44 MB) - -O ZIP já inclui o plugin **e** as DLLs nativas do VLC. Não é necessário instalar o VLC separadamente. - -### Como instalar - -1. Extraia o ZIP -2. Copie a pasta `DataProviders` extraída para dentro da pasta de instalação do **vMixUTC**, mesclando com a pasta existente: - -``` -vMixUTC/ -└── DataProviders/ ← cole aqui o conteúdo extraído - ├── SrtMonitorDataProvider.dll - └── libvlc/ - └── win-x64/ - ├── libvlc.dll - ├── libvlccore.dll - └── plugins/ -``` - -3. Reinicie o vMixUTC — o widget **SRT Monitor** aparecerá na lista de Data Providers. +Quando ativado, o UTC consulta o vMix a cada segundo e detecta qualquer alteração — inputs adicionados, textos modificados, configurações trocadas — atualizando a interface automaticamente. --- @@ -73,33 +55,6 @@ vMixUTC/ --- -## Arquitetura - -O plugin implementa a interface `IvMixDataProvider` do vMixUTC: - -``` -vMixControlExternalData (host widget no UTC) - └── SRTDataProvider (IvMixDataProvider) - ├── LibVLC (instância por widget — isolamento de áudio) - └── OnWidgetUI (UI WPF com VideoView embutido) - └── MediaPlayer (por instância) -``` - -Cada widget cria sua própria instância de `LibVLC` e `MediaPlayer`, garantindo isolamento total de vídeo e áudio entre múltiplos monitores na mesma tela. - -O controle de áudio usa a opção `:no-audio` diretamente na mídia quando o checkbox é desmarcado, e reconecta automaticamente ao alternar — evitando qualquer compartilhamento de estado no pipeline de áudio do Windows. - ---- - -## Como compilar - -```bash -# Restaurar pacotes NuGet e compilar (gera o DLL em vMixController/bin/Debug/DataProviders/) -MSBuild vMixUTCSRTDataProvider.csproj /p:Configuration=Debug /p:Platform=AnyCPU /t:Build -``` - ---- - ## Créditos Idealizado por **[rafalau](https://github.com/rafalau)**.