Skip to content

Commit ab5209e

Browse files
committed
Add function to call websocket request from JavaScript
1 parent b56fd78 commit ab5209e

File tree

4 files changed

+310
-7
lines changed

4 files changed

+310
-7
lines changed

README.md

+25
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,31 @@ Permissions required: ALL
315315
window.obsstudio.stopVirtualcam()
316316
```
317317

318+
#### Call Websocket request
319+
Permissions required: ALL
320+
```js
321+
/**
322+
* @typedef {Object} WebsocketResponse
323+
* @property {number} code - RequestStatus code
324+
* @property {bool} result - is true if the request resulted in Success. False if otherwise.
325+
* @property {string} responseData - JSON string containing response data
326+
* @property {string} comment - may be provided by the server on errors to offer further details on why a request failed.
327+
*/
328+
329+
/**
330+
* @callback WebsocketRequestCallback
331+
* @param {WebsocketResponse} response
332+
*/
333+
334+
/**
335+
* @param {WebsocketRequestCallback} cb
336+
* @param {string} request_type - The request type to call
337+
* @param {string} request_data - JSON string containing appropriate request data
338+
*/
339+
window.obsstudio.websocketRequest(function (response_data) {
340+
console.log(JSON.stringify(response))
341+
}, request_type, request_data)
342+
```
318343

319344
### Register for visibility callbacks
320345

browser-app.cpp

+7-7
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,13 @@ void BrowserApp::OnBeforeCommandLineProcessing(const CefString &, CefRefPtr<CefC
9999
#endif
100100
}
101101

102-
std::vector<std::string> exposedFunctions = {"getControlLevel", "getCurrentScene", "getStatus",
103-
"startRecording", "stopRecording", "startStreaming",
104-
"stopStreaming", "pauseRecording", "unpauseRecording",
105-
"startReplayBuffer", "stopReplayBuffer", "saveReplayBuffer",
106-
"startVirtualcam", "stopVirtualcam", "getScenes",
107-
"setCurrentScene", "getTransitions", "getCurrentTransition",
108-
"setCurrentTransition"};
102+
std::vector<std::string> exposedFunctions = {"getControlLevel", "getCurrentScene", "getStatus",
103+
"startRecording", "stopRecording", "startStreaming",
104+
"stopStreaming", "pauseRecording", "unpauseRecording",
105+
"startReplayBuffer", "stopReplayBuffer", "saveReplayBuffer",
106+
"startVirtualcam", "stopVirtualcam", "getScenes",
107+
"setCurrentScene", "getTransitions", "getCurrentTransition",
108+
"setCurrentTransition", "websocketRequest"};
109109

110110
void BrowserApp::OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame>, CefRefPtr<CefV8Context> context)
111111
{

browser-client.cpp

+19
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "browser-client.hpp"
2020
#include "obs-browser-source.hpp"
2121
#include "base64/base64.hpp"
22+
#include "obs-websocket-api.h"
2223
#include <nlohmann/json.hpp>
2324
#include <obs-frontend-api.h>
2425
#include <obs.hpp>
@@ -153,6 +154,24 @@ bool BrowserClient::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefR
153154
obs_frontend_start_virtualcam();
154155
} else if (name == "stopVirtualcam") {
155156
obs_frontend_stop_virtualcam();
157+
} else if (name == "websocketRequest") {
158+
std::string request_type = input_args->GetString(1).ToString();
159+
std::string request_data_string = input_args->GetString(2).ToString();
160+
OBSDataAutoRelease request_data = obs_data_create_from_json(request_data_string.c_str());
161+
struct obs_websocket_request_response *response =
162+
obs_websocket_call_request(request_type.c_str(), request_data);
163+
if (response) {
164+
json = {{"code", response->status_code}, {"result", response->status_code == 100}};
165+
if (response->response_data)
166+
json["responseData"] = response->response_data;
167+
if (response->comment)
168+
json["comment"] = response->comment;
169+
obs_websocket_request_response_free(response);
170+
} else {
171+
json = {{"code", 205}, // GenericError
172+
{"result", false},
173+
{"comment", "Unable to call obs-websocket request"}};
174+
}
156175
}
157176
[[fallthrough]];
158177
case ControlLevel::Advanced:

obs-websocket-api.h

+259
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
/*
2+
obs-websocket
3+
Copyright (C) 2016-2021 Stephane Lepin <[email protected]>
4+
Copyright (C) 2020-2022 Kyle Manning <[email protected]>
5+
6+
This program is free software; you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation; either version 2 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License along
17+
with this program. If not, see <https://www.gnu.org/licenses/>
18+
*/
19+
20+
#ifndef _OBS_WEBSOCKET_API_H
21+
#define _OBS_WEBSOCKET_API_H
22+
23+
#include <obs.h>
24+
25+
#define OBS_WEBSOCKET_API_VERSION 3
26+
27+
#ifdef __cplusplus
28+
extern "C" {
29+
#endif
30+
31+
typedef void *obs_websocket_vendor;
32+
typedef void (*obs_websocket_request_callback_function)(obs_data_t *, obs_data_t *, void *);
33+
typedef void (*obs_websocket_event_callback_function)(uint64_t, const char *, const char *, void *);
34+
35+
struct obs_websocket_request_response {
36+
unsigned int status_code;
37+
char *comment;
38+
char *response_data; // JSON string, because obs_data_t* only supports array<object>, so conversions would break API.
39+
};
40+
41+
/* ==================== INTERNAL DEFINITIONS ==================== */
42+
43+
struct obs_websocket_request_callback {
44+
obs_websocket_request_callback_function callback;
45+
void *priv_data;
46+
};
47+
48+
struct obs_websocket_event_callback {
49+
obs_websocket_event_callback_function callback;
50+
void *priv_data;
51+
};
52+
53+
static proc_handler_t *_ph;
54+
55+
/* ==================== INTERNAL API FUNCTIONS ==================== */
56+
57+
static inline proc_handler_t *obs_websocket_get_ph(void)
58+
{
59+
proc_handler_t *global_ph = obs_get_proc_handler();
60+
assert(global_ph != NULL);
61+
62+
calldata_t cd = {0, 0, 0, 0};
63+
if (!proc_handler_call(global_ph, "obs_websocket_api_get_ph", &cd))
64+
blog(LOG_DEBUG, "Unable to fetch obs-websocket proc handler object. obs-websocket not installed?");
65+
proc_handler_t *ret = (proc_handler_t *)calldata_ptr(&cd, "ph");
66+
calldata_free(&cd);
67+
68+
return ret;
69+
}
70+
71+
static inline bool obs_websocket_ensure_ph(void)
72+
{
73+
if (!_ph)
74+
_ph = obs_websocket_get_ph();
75+
return _ph != NULL;
76+
}
77+
78+
static inline bool obs_websocket_vendor_run_simple_proc(obs_websocket_vendor vendor, const char *proc_name, calldata_t *cd)
79+
{
80+
if (!obs_websocket_ensure_ph())
81+
return false;
82+
83+
if (!vendor || !proc_name || !strlen(proc_name) || !cd)
84+
return false;
85+
86+
calldata_set_ptr(cd, "vendor", vendor);
87+
88+
proc_handler_call(_ph, proc_name, cd);
89+
return calldata_bool(cd, "success");
90+
}
91+
92+
/* ==================== GENERAL API FUNCTIONS ==================== */
93+
94+
// Gets the API version built with the obs-websocket plugin
95+
static inline unsigned int obs_websocket_get_api_version(void)
96+
{
97+
if (!obs_websocket_ensure_ph())
98+
return 0;
99+
100+
calldata_t cd = {0, 0, 0, 0};
101+
102+
if (!proc_handler_call(_ph, "get_api_version", &cd))
103+
return 1; // API v1 does not include get_api_version
104+
105+
unsigned int ret = (unsigned int)calldata_int(&cd, "version");
106+
107+
calldata_free(&cd);
108+
109+
return ret;
110+
}
111+
112+
// Calls an obs-websocket request. Free response with `obs_websocket_request_response_free()`
113+
static inline struct obs_websocket_request_response *obs_websocket_call_request(const char *request_type, obs_data_t *request_data
114+
#ifdef __cplusplus
115+
= NULL
116+
#endif
117+
)
118+
{
119+
if (!obs_websocket_ensure_ph())
120+
return NULL;
121+
122+
const char *request_data_string = NULL;
123+
if (request_data)
124+
request_data_string = obs_data_get_json(request_data);
125+
126+
calldata_t cd = {0, 0, 0, 0};
127+
calldata_set_string(&cd, "request_type", request_type);
128+
calldata_set_string(&cd, "request_data", request_data_string);
129+
130+
proc_handler_call(_ph, "call_request", &cd);
131+
132+
struct obs_websocket_request_response *ret = (struct obs_websocket_request_response *)calldata_ptr(&cd, "response");
133+
134+
calldata_free(&cd);
135+
136+
return ret;
137+
}
138+
139+
// Free a request response object returned by `obs_websocket_call_request()`
140+
static inline void obs_websocket_request_response_free(struct obs_websocket_request_response *response)
141+
{
142+
if (!response)
143+
return;
144+
145+
if (response->comment)
146+
bfree(response->comment);
147+
if (response->response_data)
148+
bfree(response->response_data);
149+
bfree(response);
150+
}
151+
152+
// Register an event handler to receive obs-websocket events
153+
static inline bool obs_websocket_register_event_callback(obs_websocket_event_callback_function event_callback, void *priv_data)
154+
{
155+
if (!obs_websocket_ensure_ph())
156+
return false;
157+
158+
struct obs_websocket_event_callback cb = {event_callback, priv_data};
159+
160+
calldata_t cd = {0, 0, 0, 0};
161+
calldata_set_ptr(&cd, "callback", &cb);
162+
163+
proc_handler_call(_ph, "register_event_callback", &cd);
164+
165+
bool ret = calldata_bool(&cd, "success");
166+
167+
calldata_free(&cd);
168+
169+
return ret;
170+
}
171+
172+
// Unregister an existing event handler
173+
static inline bool obs_websocket_unregister_event_callback(obs_websocket_event_callback_function event_callback, void *priv_data)
174+
{
175+
if (!obs_websocket_ensure_ph())
176+
return false;
177+
178+
struct obs_websocket_event_callback cb = {event_callback, priv_data};
179+
180+
calldata_t cd = {0, 0, 0, 0};
181+
calldata_set_ptr(&cd, "callback", &cb);
182+
183+
proc_handler_call(_ph, "unregister_event_callback", &cd);
184+
185+
bool ret = calldata_bool(&cd, "success");
186+
187+
calldata_free(&cd);
188+
189+
return ret;
190+
}
191+
192+
/* ==================== VENDOR API FUNCTIONS ==================== */
193+
194+
// ALWAYS CALL ONLY VIA `obs_module_post_load()` CALLBACK!
195+
// Registers a new "vendor" (Example: obs-ndi)
196+
static inline obs_websocket_vendor obs_websocket_register_vendor(const char *vendor_name)
197+
{
198+
if (!obs_websocket_ensure_ph())
199+
return NULL;
200+
201+
calldata_t cd = {0, 0, 0, 0};
202+
calldata_set_string(&cd, "name", vendor_name);
203+
204+
proc_handler_call(_ph, "vendor_register", &cd);
205+
obs_websocket_vendor ret = calldata_ptr(&cd, "vendor");
206+
calldata_free(&cd);
207+
208+
return ret;
209+
}
210+
211+
// Registers a new request for a vendor
212+
static inline bool obs_websocket_vendor_register_request(obs_websocket_vendor vendor, const char *request_type,
213+
obs_websocket_request_callback_function request_callback, void *priv_data)
214+
{
215+
struct obs_websocket_request_callback cb = {request_callback, priv_data};
216+
217+
calldata_t cd = {0, 0, 0, 0};
218+
calldata_set_string(&cd, "type", request_type);
219+
calldata_set_ptr(&cd, "callback", &cb);
220+
221+
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_register", &cd);
222+
calldata_free(&cd);
223+
224+
return success;
225+
}
226+
227+
// Unregisters an existing vendor request
228+
static inline bool obs_websocket_vendor_unregister_request(obs_websocket_vendor vendor, const char *request_type)
229+
{
230+
calldata_t cd = {0, 0, 0, 0};
231+
calldata_set_string(&cd, "type", request_type);
232+
233+
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_unregister", &cd);
234+
calldata_free(&cd);
235+
236+
return success;
237+
}
238+
239+
// Does not affect event_data refcount.
240+
// Emits an event under the vendor's name
241+
static inline bool obs_websocket_vendor_emit_event(obs_websocket_vendor vendor, const char *event_name, obs_data_t *event_data)
242+
{
243+
calldata_t cd = {0, 0, 0, 0};
244+
calldata_set_string(&cd, "type", event_name);
245+
calldata_set_ptr(&cd, "data", (void *)event_data);
246+
247+
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_event_emit", &cd);
248+
calldata_free(&cd);
249+
250+
return success;
251+
}
252+
253+
/* ==================== END API FUNCTIONS ==================== */
254+
255+
#ifdef __cplusplus
256+
}
257+
#endif
258+
259+
#endif

0 commit comments

Comments
 (0)