Commands/Send-OBS.ps1
function Send-OBS { <# .SYNOPSIS Sends messages to the OBS websocket. .DESCRIPTION Sends one or more messages to the OBS websocket. .LINK Receive-OBS .LINK Watch-OBS #> param( # The data to send to the obs websocket. [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias('Payload')] $MessageData, # If provided, will sleep after each step. # If -StepTime is less than 10000 ticks, it will be treated as frames per second. # If -SerialFrame was provied, -StepTime will be the number of frames to wait. [Parameter(ValueFromPipelineByPropertyName)] [timespan] $StepTime, # If set, will process a batch of requests in parallel. [switch] $Parallel, # If set, will process a batch of requests in parallel. [switch] $SerialFrame, # If set, will receive responses from batches of requests. [Alias('ReceiveBatches')] [switch] $ReceiveBatch, # If set, will never attempt to receive a response. [Alias('NoReceive','IgnoreResponse','IgnoreReceive','DoNotReceiveResponse')] [switch] $NoResponse ) begin { $allMessages = [Collections.Queue]::new() # Keep track of how many requests we have done of a given type # (this makes creating RequestIDs easy) if (-not $script:ObsRequestsCounts) { $script:ObsRequestsCounts = @{} } if (-not $script:ObsWebSockets) { $script:ObsWebSockets = [Ordered]@{} } function SendSingleMessageToOBS { param( [Parameter(ValueFromPipeline)] $payloadObject ) process { if ($null -eq $payloadObject.op) { if ($payloadObject.requestID) { $payloadObject = [Ordered]@{ op = 6 d = $payloadObject } } elseif ($payloadObject.authentication) { $payloadObject = [Ordered]@{ op = 1 d = $payloadObject } } else { Write-Verbose "No payload provided, broadcasting event" $myRequestType = 'BroadcastCustomEvent' # If we don't have a request counter for this request type if (-not $script:ObsRequestsCounts[$myRequestType]) { # initialize it to zero. $script:ObsRequestsCounts[$myRequestType] = 0 } # Increment the counter for requests of this type $script:ObsRequestsCounts[$myRequestType]++ # and make a request ID from that. $myRequestId = "$myRequestType.$($script:ObsRequestsCounts[$myRequestType])" $payloadObject = [Ordered]@{ op = 6 d = [Ordered]@{ requestId = $myRequestId requestType = $myRequestType eventData = $payloadObject } } } } $PayloadJson = ConvertTo-Json -Depth 100 -InputObject $payloadObject # And create a byte segment to send it off. $SendSegment = [ArraySegment[Byte]]::new([Text.Encoding]::UTF8.GetBytes($PayloadJson)) # If we have no OBS connections if (-not $script:ObsConnections.Values) { # error out Write-Error "Not connected to OBS. Use Connect-OBS." return } # Otherwise, walk over each connection foreach ($obsConnectionInfo in @($script:ObsConnections.GetEnumerator())) { $obsConnection = $obsConnectionInfo.Value $OBSWebSocketUri = $obsConnectionInfo.Key $OBSWebSocket = $obsConnection.Websocket if ($VerbosePreference -notin 'silentlyContinue', 'ignore') { Write-Verbose "Sending $payloadJSON" } # In event-driven contexts, the websocket attachment may not work due to the accessibility of ScriptMethods. if ($OBSWebSocket -eq $null) { # to work around this, we can find our websocket by looking at the event it generated on connection if (-not $script:OBSWebSockets[$OBSWebSocketUri] -or $script:OBSWebSockets[$OBSWebSocketUri].State -ne 'Open') { $webSocketEvents = @(Get-Event -SourceIdentifier obs.powershell.websocket) [Array]::Reverse($webSocketEvents) foreach ($webSocketEvent in $webSocketEvents) { if ($webSocketEvent.MessageData.Uri -eq $OBSWebSocketUri -and $webSocketEvent.WebSocket.State -eq 'Open' ) { # Once we find an open socket, cache it. $script:OBSWebSockets[$webSocketEvent.MessageData.Uri] = $webSocketEvent.MessageData.WebSocket break } } } # set the socket to what is in the cache. $OBSWebSocket = $script:OBSWebSockets[$OBSWebSocketUri] # If there was still no socket if (-not $OBSWebSocket) { # write an error Write-Error "No websocket for $obsWebSocketUri" continue # and continue. } } # Since we have a working websocket, send the payload to it. $null = $OBSWebSocket.SendAsync($SendSegment,'Text', $true, [Threading.CancellationToken]::new($false)) # If a response was expected (and we did explicitly say to ignore responses) if ($payloadObject.d.requestID -and (-not $NoResponse) ) { if ($payloadObject.op -ne 8 -or $ReceiveBatch) { $payloadObject | . Receive-OBS } } } } } } process { foreach ($message in $MessageData) { if ($null -eq $message) { continue } $allMessages.Enqueue($Message) if ($StepTime.TotalMilliseconds -gt 0) { if ($SerialFrame) { $allMessages.Enqueue([PSCustomObject][Ordered]@{ requestType = 'Sleep' requestData = @{ sleepFrames = [int]$StepTime.Ticks } }) } else { if ($StepTime.Ticks -lt 10000) { $StepTime = [TimeSpan]::FromMilliseconds(1000 / $StepTime.Ticks) } $allMessages.Enqueue([PSCustomObject][Ordered]@{ requestType = 'Sleep' requestData = @{ sleepMillis = [int]$StepTime.TotalMilliseconds } }) } } } } end { if ($allMessages.Count -eq 1) { $payloadObject = $allMessages[0] $payloadObject | SendSingleMessageToOBS } elseif ($allMessages.Count -gt 0 -and $allMessages.ToArray().RequestType) { if (-not $script:ObsRequestsCounts["Batch"]) { $script:ObsRequestsCounts["Batch"] = 0 } $script:ObsRequestsCounts["Batch"]++ [PSCustomObject]@{ op = 8 d = [Ordered]@{ requestId = "Batch.$([guid]::NewGuid())" executionType = if ($Parallel) { 2 } elseif ($SerialFrame) { 1 } else { 0 } requests = $allMessages.ToArray() } } | SendSingleMessageToOBS } else { $allMessages | SendSingleMessageToOBS } } } |