Rank: Newbie
Groups: Registered
Joined: 7/18/2025(UTC) Posts: 1  Location: Birmingham
|
Hey fellow vMixers, Long time reader, but first time posting here. Hoping to share some knowledge others might find useful. Background, our vMix studio has been having a bit of an overhaul this summer ready for a busy autumn of events. With enhancements to the control surfaces (extra stream decks and some old akai midi interfaces replaced with behringer X-Touch controllers) we started from scratch tidying up all scripts and updating to companion v4. However we've regularly wanted to have an easy way of controlling audio status (Live/Green room) that is automated to what's actually in program. With lots of corporate panel discussions, with people coming and going, and opening and closing calls to get the next presenters ready, doing this manually sometimes leads to mistakes. After some serious trial and error we've created the below script, that checks if inputs 101-108 are live on Mix 1 or if the input live on Mix 1 has these inputs active on a layer, it then fires "CallerX Live" the script that changes audio routing for that caller (removes from green room bus, adds to live bus) and actives the onair toggle in the green room and changes the video return feed to the live output. The 'database' about which caller is live is stored in a blank title input, this allows the script to check for changes between live / green room and then trigger "CallerX GR" script that does the reverse. Using the title gets around the fact there's no variable storage when working strictly within the vMix scripting engine. I hope this script can help others with automating their productions and minimising errors in live production. Usual caveat of test, test, TEST and REALLY TEST! this code before using it in your production. Code:
' vMix VB.NET script: Detect live vMixCall inputs 101–108 on Mix 1 (even in layers), update title 50, and run trigger scripts
' CONFIGURATION
Dim callerInputs = New Integer() {101, 102, 103, 104, 105, 106, 107, 108}
Dim callerScriptsLive = New String() {"Caller1 Live", "Caller2 Live", "Caller3 Live", "Caller4 Live", "Caller5 Live", "Caller6 Live", "Caller7 Live", "Caller8 Live"}
Dim callerScriptsGone = New String() {"Caller1 GR", "Caller2 GR", "Caller3 GR", "Caller4 GR", "Caller5 GR", "Caller6 GR", "Caller7 GR", "Caller8 GR"}
Dim titleInput As String = "50"
Dim titleField As String = "Message.Text"
' LOAD XML
Dim xml As String = API.XML()
Dim doc As New System.Xml.XmlDocument()
doc.LoadXml(xml)
' GET CURRENT STATE FROM TITLE (Input 50)
Dim titleNode As Xml.XmlNode = doc.SelectSingleNode("//input[@number='" & titleInput & "']/text")
Dim lastStateRaw As String = If(titleNode IsNot Nothing, titleNode.InnerText, "")
Dim stateTagIndex As Integer = lastStateRaw.IndexOf("[state:")
Dim lastVisibleCallers As New System.Collections.Generic.List(Of Integer)
If stateTagIndex >= 0 Then
Dim stateData As String = lastStateRaw.Substring(stateTagIndex + 7).TrimEnd("]"c)
For Each part As String In stateData.Split(","c)
Dim num As Integer
If Integer.TryParse(part.Trim(), num) Then
lastVisibleCallers.Add(num)
End If
Next
' Optional: strip out the tag from visible display message
lastStateRaw = lastStateRaw.Substring(0, stateTagIndex).Trim()
End If
' GET MIX 1 ACTIVE INPUT
Dim mix1InputNumNode As Xml.XmlNode = doc.SelectSingleNode("//active")
Dim mix1InputNum As String = If(mix1InputNumNode IsNot Nothing, mix1InputNumNode.InnerText, "")
If mix1InputNum = "" Then
API.Function("SetText", Input:=titleInput, SelectedName:=titleField, Value:="Mix 1 not found")
Return
End If
Dim mix1Input As Xml.XmlNode = doc.SelectSingleNode("//input[@number='" & mix1InputNum & "']")
If mix1Input Is Nothing Then
API.Function("SetText", Input:=titleInput, SelectedName:=titleField, Value:="Mix 1 input not found")
Return
End If
' SCAN FOR CALLERS (including nested layers)
Dim visibleCallers As New System.Collections.Generic.List(Of Integer)
Dim inputStack As New System.Collections.Generic.Stack(Of Xml.XmlNode)
Dim visitedKeys As New System.Collections.Generic.List(Of String)
inputStack.Push(mix1Input)
Do While inputStack.Count > 0
Dim currentInput As Xml.XmlNode = inputStack.Pop()
If currentInput.Attributes("key") Is Nothing Then Continue Do
Dim key As String = currentInput.Attributes("key").Value
If visitedKeys.Contains(key) Then Continue Do
visitedKeys.Add(key)
Dim numAttr = currentInput.Attributes("number")
If numAttr IsNot Nothing Then
Dim num As Integer = Integer.Parse(numAttr.Value)
If Array.IndexOf(callerInputs, num) >= 0 AndAlso Not visibleCallers.Contains(num) Then
visibleCallers.Add(num)
End If
End If
For Each overlay As Xml.XmlNode In currentInput.SelectNodes("overlay")
Dim layerKey As String = If(overlay.Attributes("key") IsNot Nothing, overlay.Attributes("key").Value, overlay.InnerText)
If layerKey <> "" AndAlso Not visitedKeys.Contains(layerKey) Then
Dim layeredInput As Xml.XmlNode = doc.SelectSingleNode("//input[@key='" & layerKey & "']")
If layeredInput IsNot Nothing Then inputStack.Push(layeredInput)
End If
Next
Loop
' TRIGGER SCRIPTS
For i As Integer = 0 To callerInputs.Length - 1
Dim num As Integer = callerInputs(i)
Dim nowVisible As Boolean = visibleCallers.Contains(num)
Dim wasVisible As Boolean = lastVisibleCallers.Contains(num)
If nowVisible And Not wasVisible Then
API.Function("ScriptStart", Value:=callerScriptsLive(i))
ElseIf Not nowVisible And wasVisible Then
API.Function("ScriptStart", Value:=callerScriptsGone(i))
End If
Next
' BUILD NEW DISPLAY MESSAGE
Dim displayText As String = If(visibleCallers.Count > 0, "Callers: " & String.Join(", ", visibleCallers), "No callers live")
Dim stateTag As String = "[state:" & String.Join(",", visibleCallers) & "]"
API.Function("SetText", Input:=titleInput, SelectedName:=titleField, Value:=displayText & " " & stateTag)
|