Skip to content

Commit cae791a

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

File tree

4 files changed

+317
-7
lines changed

4 files changed

+317
-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

+266
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
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,
79+
calldata_t *cd)
80+
{
81+
if (!obs_websocket_ensure_ph())
82+
return false;
83+
84+
if (!vendor || !proc_name || !strlen(proc_name) || !cd)
85+
return false;
86+
87+
calldata_set_ptr(cd, "vendor", vendor);
88+
89+
proc_handler_call(_ph, proc_name, cd);
90+
return calldata_bool(cd, "success");
91+
}
92+
93+
/* ==================== GENERAL API FUNCTIONS ==================== */
94+
95+
// Gets the API version built with the obs-websocket plugin
96+
static inline unsigned int obs_websocket_get_api_version(void)
97+
{
98+
if (!obs_websocket_ensure_ph())
99+
return 0;
100+
101+
calldata_t cd = {0, 0, 0, 0};
102+
103+
if (!proc_handler_call(_ph, "get_api_version", &cd))
104+
return 1; // API v1 does not include get_api_version
105+
106+
unsigned int ret = (unsigned int)calldata_int(&cd, "version");
107+
108+
calldata_free(&cd);
109+
110+
return ret;
111+
}
112+
113+
// Calls an obs-websocket request. Free response with `obs_websocket_request_response_free()`
114+
static inline struct obs_websocket_request_response *obs_websocket_call_request(const char *request_type,
115+
obs_data_t *request_data
116+
#ifdef __cplusplus
117+
= NULL
118+
#endif
119+
)
120+
{
121+
if (!obs_websocket_ensure_ph())
122+
return NULL;
123+
124+
const char *request_data_string = NULL;
125+
if (request_data)
126+
request_data_string = obs_data_get_json(request_data);
127+
128+
calldata_t cd = {0, 0, 0, 0};
129+
calldata_set_string(&cd, "request_type", request_type);
130+
calldata_set_string(&cd, "request_data", request_data_string);
131+
132+
proc_handler_call(_ph, "call_request", &cd);
133+
134+
struct obs_websocket_request_response *ret =
135+
(struct obs_websocket_request_response *)calldata_ptr(&cd, "response");
136+
137+
calldata_free(&cd);
138+
139+
return ret;
140+
}
141+
142+
// Free a request response object returned by `obs_websocket_call_request()`
143+
static inline void obs_websocket_request_response_free(struct obs_websocket_request_response *response)
144+
{
145+
if (!response)
146+
return;
147+
148+
if (response->comment)
149+
bfree(response->comment);
150+
if (response->response_data)
151+
bfree(response->response_data);
152+
bfree(response);
153+
}
154+
155+
// Register an event handler to receive obs-websocket events
156+
static inline bool obs_websocket_register_event_callback(obs_websocket_event_callback_function event_callback,
157+
void *priv_data)
158+
{
159+
if (!obs_websocket_ensure_ph())
160+
return false;
161+
162+
struct obs_websocket_event_callback cb = {event_callback, priv_data};
163+
164+
calldata_t cd = {0, 0, 0, 0};
165+
calldata_set_ptr(&cd, "callback", &cb);
166+
167+
proc_handler_call(_ph, "register_event_callback", &cd);
168+
169+
bool ret = calldata_bool(&cd, "success");
170+
171+
calldata_free(&cd);
172+
173+
return ret;
174+
}
175+
176+
// Unregister an existing event handler
177+
static inline bool obs_websocket_unregister_event_callback(obs_websocket_event_callback_function event_callback,
178+
void *priv_data)
179+
{
180+
if (!obs_websocket_ensure_ph())
181+
return false;
182+
183+
struct obs_websocket_event_callback cb = {event_callback, priv_data};
184+
185+
calldata_t cd = {0, 0, 0, 0};
186+
calldata_set_ptr(&cd, "callback", &cb);
187+
188+
proc_handler_call(_ph, "unregister_event_callback", &cd);
189+
190+
bool ret = calldata_bool(&cd, "success");
191+
192+
calldata_free(&cd);
193+
194+
return ret;
195+
}
196+
197+
/* ==================== VENDOR API FUNCTIONS ==================== */
198+
199+
// ALWAYS CALL ONLY VIA `obs_module_post_load()` CALLBACK!
200+
// Registers a new "vendor" (Example: obs-ndi)
201+
static inline obs_websocket_vendor obs_websocket_register_vendor(const char *vendor_name)
202+
{
203+
if (!obs_websocket_ensure_ph())
204+
return NULL;
205+
206+
calldata_t cd = {0, 0, 0, 0};
207+
calldata_set_string(&cd, "name", vendor_name);
208+
209+
proc_handler_call(_ph, "vendor_register", &cd);
210+
obs_websocket_vendor ret = calldata_ptr(&cd, "vendor");
211+
calldata_free(&cd);
212+
213+
return ret;
214+
}
215+
216+
// Registers a new request for a vendor
217+
static inline bool obs_websocket_vendor_register_request(obs_websocket_vendor vendor, const char *request_type,
218+
obs_websocket_request_callback_function request_callback,
219+
void *priv_data)
220+
{
221+
struct obs_websocket_request_callback cb = {request_callback, priv_data};
222+
223+
calldata_t cd = {0, 0, 0, 0};
224+
calldata_set_string(&cd, "type", request_type);
225+
calldata_set_ptr(&cd, "callback", &cb);
226+
227+
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_register", &cd);
228+
calldata_free(&cd);
229+
230+
return success;
231+
}
232+
233+
// Unregisters an existing vendor request
234+
static inline bool obs_websocket_vendor_unregister_request(obs_websocket_vendor vendor, const char *request_type)
235+
{
236+
calldata_t cd = {0, 0, 0, 0};
237+
calldata_set_string(&cd, "type", request_type);
238+
239+
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_unregister", &cd);
240+
calldata_free(&cd);
241+
242+
return success;
243+
}
244+
245+
// Does not affect event_data refcount.
246+
// Emits an event under the vendor's name
247+
static inline bool obs_websocket_vendor_emit_event(obs_websocket_vendor vendor, const char *event_name,
248+
obs_data_t *event_data)
249+
{
250+
calldata_t cd = {0, 0, 0, 0};
251+
calldata_set_string(&cd, "type", event_name);
252+
calldata_set_ptr(&cd, "data", (void *)event_data);
253+
254+
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_event_emit", &cd);
255+
calldata_free(&cd);
256+
257+
return success;
258+
}
259+
260+
/* ==================== END API FUNCTIONS ==================== */
261+
262+
#ifdef __cplusplus
263+
}
264+
#endif
265+
266+
#endif

0 commit comments

Comments
 (0)