Let's write a simple module to control OBS using websockets.
OBS already have a websocket server which can accept many command to control its features. We're going to use that websocket server to make some actions like:
- Change scene
- Apply source filters
- Many other things
Basic configuration
First we need to install a websocket client, fresh is a easy to use client that works on top on mint.
Let's add the following code to our mix.exs
file.
{:earmark, "~> 1.4"},
+ {:fresh, "~> 0.4.4"}
]
end
Now we need to define a module that use all the functionality of fresh
, like the following code:
defmodule MyApp.OBS do
use Fresh
def handle_connect(_status, _headers, state) do
payload = %{"op" => 1, "d" => %{rpcVersion: 1}}
{:reply, {:text, Jason.encode!(payload)}, state}
end
end
At this point this just connect to the websocket and responds with a specific message {"op": 1, "d": {"rpcVersion": 1}}
, this is required to "authenticate" against OBS websocket server. This according to OBS websocket protocol. In case we set up a password in our OBS settings we need to modify this response.
Now we need to start our client, we can do it manually or using application.ex
like the following code:
@impl true
def start(_type, _args) do
children = [
+ # OBS websocket client
+ {MyApp.OBS,
+ uri: "ws://localhost:4455", state: nil, opts: [name: {:local, :obs_websocket_client}]}
]
end
Here is where we define 2 things:
- the location of the websocket server, if we are running
OBS
in the same machine we can usews://localhost:4455
, this is the default address thatOBS
uses. - the "name" of the process that will be used later to send messages to. In this case is
:obs_websocket_client
. The client is just anerlang
process so we need some way to send messages to it.
Controlling OBS
Now that we have a working websocket client we can send messages to OBS
.
We are going to implement a function to change the scene in OBS
, to do that we need to use the SetCurrentProgramScene action.
Let's add 3 functions to our OBS
module.
+def change_scene(scene_name) do
+ send_message("SetCurrentProgramScene", %{"sceneName" => scene_name})
+end
+defp send_message(type, data) do
+ payload = %{
+ "op" => 6,
+ "d" => %{
+ # generate a random identifier, we can use any other module
+ "requestId" => Ecto.UUID.generate(),
+ "requestType" => type,
+ "requestData" => data
+ }
+ }
+ send(:obs_websocket_client, {:send, Jason.encode!(payload)})
+end
+def handle_info({:send, message}, state) do
+ {:reply, [{:text, message}], state}
+end
send_message/2
, receive a type and a map, it will use these to prepare a payload which will be send to our websocket client. We're usingsend/2
because our client is just another process, which have the name defined previously inapplication.ex
::obs_websocket_client
.handle_info/2
, this will receive the message and send it to the websocket server, becauseOBS
protocol just use plain text we use a{:text, message}
response.change_scene/1
this just reuse the previously defined functions. We send theSetCurrentProgramScence
message with a specific payload. Now we can just callMyApp.OBS.change_scene("gaming")
and it will be changed.
Enjoy 🎉.