This article demonstrates how to automate OBS Studio programmatically using C#.
The prerequisite is that OBS Studio is already running and properly configured.
The first step is to establish a connection to OBS Studio via the WebSocket API.
public async Task<bool> ConnectAsync(CancellationToken ct = default)
{
if (IsConnected) return true;
await _connectLock.WaitAsync(ct);
try
{
if (IsConnected) return true;
await DisposeSocketAsync();
_ws = new ClientWebSocket();
_ws.Options.AddSubProtocol("obswebsocket.json");
await _ws.ConnectAsync(opt.Uri, ct);
// Hello (op=0)
var hello = await ReceiveJsonAsync(_ws, ct);
if (hello.GetProperty("op").GetInt32() != 0)
throw new Exception("Expected OBS Hello (op=0).");
var helloD = hello.GetProperty("d");
int rpcVersion = helloD.GetProperty("rpcVersion").GetInt32();
string? auth = null;
if (helloD.TryGetProperty("authentication", out var authObj))
{
var challenge = authObj.GetProperty("challenge").GetString()!;
var salt = authObj.GetProperty("salt").GetString()!;
auth = CreateObsAuthString(opt.Password, salt, challenge);
}
// Identify (op=1)
var identify = new
{
op = 1,
d = new
{
rpcVersion,
authentication = auth,
eventSubscriptions = opt.EventSubscriptions
}
};
await SendJsonAsync(_ws, identify, ct);
var identified = await ReceiveJsonAsync(_ws, ct);
if (identified.GetProperty("op").GetInt32() != 2)
throw new Exception("OBS Identify failed (expected op=2).");
_isConnected = true;
var ws = _ws;
_receiveLoop = Task.Run(() => ReceiveLoopAsync(ws, CancellationToken.None));
await loggerAsync.Log($"OBS connected: {opt.Uri}");
return true;
}
catch (OperationCanceledException)
{
await loggerAsync.Log("OBS connect canceled.");
_isConnected = false;
await DisposeSocketAsync();
return false;
}
catch (Exception ex)
{
_isConnected = false;
await loggerAsync.Log(ex);
await DisposeSocketAsync();
return false;
}
finally
{
_connectLock.Release();
}
}
Sending a command to OBS Studio is implemented as follows:
public async Task<bool> SendAsync(string requestType, object? requestData = null, CancellationToken ct = default)
{
await loggerAsync.Log($"requestType: {requestType}");
if (!await ConnectAsync(ct))
{
return false;
}
var ws = _ws;
if (ws is null || ws.State != WebSocketState.Open || !_isConnected)
return false;
var requestId = Guid.NewGuid().ToString("N");
var tcs = new TaskCompletionSource<JsonElement>(TaskCreationOptions.RunContinuationsAsynchronously);
_pending[requestId] = tcs;
try
{
var payload = new
{
op = 6,
d = new { requestType, requestId, requestData = requestData ?? new { } }
};
await _sendLock.WaitAsync(ct);
try
{
await SendJsonAsync(ws, payload, ct);
}
finally
{
_sendLock.Release();
}
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
timeoutCts.CancelAfter(TimeSpan.FromSeconds(15));
var msg = await tcs.Task.WaitAsync(timeoutCts.Token);
var d = msg.GetProperty("d");
var status = d.GetProperty("requestStatus");
if (!status.GetProperty("result").GetBoolean())
{
var code = status.TryGetProperty("code", out var c) ? c.GetInt32() : -1;
var comment = status.TryGetProperty("comment", out var cm) ? cm.GetString() : "(no comment)";
throw new Exception($"OBS request '{requestType}' failed. code={code}, comment={comment}");
}
return true;
}
catch (Exception ex)
{
await loggerAsync.Log(ex);
return false;
}
finally
{
_pending.TryRemove(requestId, out _);
}
}
Starting a recording looks like this:
public async Task StartAsync(StartStopRecordingCommand command, CancellationToken ct = default)
{
var format = BuildFilenameFormat(command, opt.RecordDirectory) ?? opt.FilenameFormatting;
await loggerAsync.Log($"Starting recording with filename format: {format}");
if (!string.IsNullOrWhiteSpace(opt.RecordDirectory))
{
try
{
await obs.SendAsync("SetRecordDirectory", new { recordDirectory = opt.RecordDirectory }, ct);
await loggerAsync.Log($"Set recording directory to: {opt.RecordDirectory}");
}
catch
{
await obs.SendAsync("SetProfileParameter", new
{
parameterCategory = "SimpleOutput",
parameterName = "FilePath",
parameterValue = opt.RecordDirectory
}, ct);
}
}
try
{
await obs.SendAsync("SetProfileParameter", new
{
parameterCategory = "Output",
parameterName = "FilenameFormatting",
parameterValue = format
}, ct);
}
catch
{
}
await obs.SendAsync("StartRecord", null, ct);
}
Full example download from here