1 /******************************************************************************
2 Copyright (C) 2014 by John R. Bradley <jrb@turrettech.com>
3 Copyright (C) 2018 by Hugh Bailey ("Jim") <jim@obsproject.com>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 ******************************************************************************/
18
19 #include "browser-client.hpp"
20 #include "obs-browser-source.hpp"
21 #include "base64/base64.hpp"
22 #include "json11/json11.hpp"
23 #include <obs-frontend-api.h>
24 #include <obs.hpp>
25 #include <util/platform.h>
26
27 using namespace json11;
28
~BrowserClient()29 BrowserClient::~BrowserClient()
30 {
31 #if defined(SHARED_TEXTURE_SUPPORT_ENABLED) && USE_TEXTURE_COPY
32 if (sharing_available) {
33 obs_enter_graphics();
34 gs_texture_destroy(texture);
35 obs_leave_graphics();
36 }
37 #endif
38 }
39
GetLoadHandler()40 CefRefPtr<CefLoadHandler> BrowserClient::GetLoadHandler()
41 {
42 return this;
43 }
44
GetRenderHandler()45 CefRefPtr<CefRenderHandler> BrowserClient::GetRenderHandler()
46 {
47 return this;
48 }
49
GetDisplayHandler()50 CefRefPtr<CefDisplayHandler> BrowserClient::GetDisplayHandler()
51 {
52 return this;
53 }
54
GetLifeSpanHandler()55 CefRefPtr<CefLifeSpanHandler> BrowserClient::GetLifeSpanHandler()
56 {
57 return this;
58 }
59
GetContextMenuHandler()60 CefRefPtr<CefContextMenuHandler> BrowserClient::GetContextMenuHandler()
61 {
62 return this;
63 }
64
65 #if CHROME_VERSION_BUILD >= 3683
GetAudioHandler()66 CefRefPtr<CefAudioHandler> BrowserClient::GetAudioHandler()
67 {
68 return reroute_audio ? this : nullptr;
69 }
70 #endif
71
OnBeforePopup(CefRefPtr<CefBrowser>,CefRefPtr<CefFrame>,const CefString &,const CefString &,WindowOpenDisposition,bool,const CefPopupFeatures &,CefWindowInfo &,CefRefPtr<CefClient> &,CefBrowserSettings &,CefRefPtr<CefDictionaryValue> &,bool *)72 bool BrowserClient::OnBeforePopup(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame>,
73 const CefString &, const CefString &,
74 WindowOpenDisposition, bool,
75 const CefPopupFeatures &, CefWindowInfo &,
76 CefRefPtr<CefClient> &, CefBrowserSettings &,
77 #if CHROME_VERSION_BUILD >= 3770
78 CefRefPtr<CefDictionaryValue> &,
79 #endif
80 bool *)
81 {
82 /* block popups */
83 return true;
84 }
85
OnBeforeContextMenu(CefRefPtr<CefBrowser>,CefRefPtr<CefFrame>,CefRefPtr<CefContextMenuParams>,CefRefPtr<CefMenuModel> model)86 void BrowserClient::OnBeforeContextMenu(CefRefPtr<CefBrowser>,
87 CefRefPtr<CefFrame>,
88 CefRefPtr<CefContextMenuParams>,
89 CefRefPtr<CefMenuModel> model)
90 {
91 /* remove all context menu contributions */
92 model->Clear();
93 }
94
OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame>,CefProcessId,CefRefPtr<CefProcessMessage> message)95 bool BrowserClient::OnProcessMessageReceived(
96 CefRefPtr<CefBrowser> browser,
97 #if CHROME_VERSION_BUILD >= 3770
98 CefRefPtr<CefFrame>,
99 #endif
100 CefProcessId, CefRefPtr<CefProcessMessage> message)
101 {
102 const std::string &name = message->GetName();
103 Json json;
104
105 if (!bs) {
106 return false;
107 }
108
109 if (name == "getCurrentScene") {
110 OBSSource current_scene = obs_frontend_get_current_scene();
111 obs_source_release(current_scene);
112
113 if (!current_scene)
114 return false;
115
116 const char *name = obs_source_get_name(current_scene);
117 if (!name)
118 return false;
119
120 json = Json::object{
121 {"name", name},
122 {"width", (int)obs_source_get_width(current_scene)},
123 {"height", (int)obs_source_get_height(current_scene)}};
124
125 } else if (name == "getStatus") {
126 json = Json::object{
127 {"recording", obs_frontend_recording_active()},
128 {"streaming", obs_frontend_streaming_active()},
129 {"recordingPaused", obs_frontend_recording_paused()},
130 {"replaybuffer", obs_frontend_replay_buffer_active()},
131 {"virtualcam", obs_frontend_virtualcam_active()}};
132
133 } else if (name == "saveReplayBuffer") {
134 obs_frontend_replay_buffer_save();
135 } else {
136 return false;
137 }
138
139 CefRefPtr<CefProcessMessage> msg =
140 CefProcessMessage::Create("executeCallback");
141
142 CefRefPtr<CefListValue> args = msg->GetArgumentList();
143 args->SetInt(0, message->GetArgumentList()->GetInt(0));
144 args->SetString(1, json.dump());
145
146 SendBrowserProcessMessage(browser, PID_RENDERER, msg);
147
148 return true;
149 }
150 #if CHROME_VERSION_BUILD >= 3578
GetViewRect(CefRefPtr<CefBrowser>,CefRect & rect)151 void BrowserClient::GetViewRect(
152 #else
153 bool BrowserClient::GetViewRect(
154 #endif
155 CefRefPtr<CefBrowser>, CefRect &rect)
156 {
157 if (!bs) {
158 #if CHROME_VERSION_BUILD >= 3578
159 rect.Set(0, 0, 16, 16);
160 return;
161 #else
162 return false;
163 #endif
164 }
165
166 rect.Set(0, 0, bs->width < 1 ? 1 : bs->width,
167 bs->height < 1 ? 1 : bs->height);
168 #if CHROME_VERSION_BUILD >= 3578
169 return;
170 #else
171 return true;
172 #endif
173 }
174
OnPaint(CefRefPtr<CefBrowser>,PaintElementType type,const RectList &,const void * buffer,int width,int height)175 void BrowserClient::OnPaint(CefRefPtr<CefBrowser>, PaintElementType type,
176 const RectList &, const void *buffer, int width,
177 int height)
178 {
179 if (type != PET_VIEW) {
180 return;
181 }
182
183 #ifdef SHARED_TEXTURE_SUPPORT_ENABLED
184 if (sharing_available) {
185 return;
186 }
187 #endif
188
189 if (!bs) {
190 return;
191 }
192
193 if (bs->width != width || bs->height != height) {
194 obs_enter_graphics();
195 bs->DestroyTextures();
196 obs_leave_graphics();
197 }
198
199 if (!bs->texture && width && height) {
200 obs_enter_graphics();
201 bs->texture = gs_texture_create(width, height, GS_BGRA, 1,
202 (const uint8_t **)&buffer,
203 GS_DYNAMIC);
204 bs->width = width;
205 bs->height = height;
206 obs_leave_graphics();
207 } else {
208 obs_enter_graphics();
209 gs_texture_set_image(bs->texture, (const uint8_t *)buffer,
210 width * 4, false);
211 obs_leave_graphics();
212 }
213 }
214
215 #ifdef SHARED_TEXTURE_SUPPORT_ENABLED
OnAcceleratedPaint(CefRefPtr<CefBrowser>,PaintElementType,const RectList &,void * shared_handle)216 void BrowserClient::OnAcceleratedPaint(CefRefPtr<CefBrowser>, PaintElementType,
217 const RectList &, void *shared_handle)
218 {
219 if (!bs) {
220 return;
221 }
222
223 if (shared_handle != last_handle) {
224 obs_enter_graphics();
225 #if USE_TEXTURE_COPY
226 gs_texture_destroy(texture);
227 texture = nullptr;
228 #endif
229 gs_texture_destroy(bs->texture);
230 bs->texture = nullptr;
231
232 #if USE_TEXTURE_COPY
233 texture = gs_texture_open_shared(
234 (uint32_t)(uintptr_t)shared_handle);
235
236 uint32_t cx = gs_texture_get_width(texture);
237 uint32_t cy = gs_texture_get_height(texture);
238 gs_color_format format = gs_texture_get_color_format(texture);
239
240 bs->texture = gs_texture_create(cx, cy, format, 1, nullptr, 0);
241 #else
242 bs->texture = gs_texture_open_shared(
243 (uint32_t)(uintptr_t)shared_handle);
244 #endif
245 obs_leave_graphics();
246
247 last_handle = shared_handle;
248 }
249
250 #if USE_TEXTURE_COPY
251 if (texture && bs->texture) {
252 obs_enter_graphics();
253 gs_copy_texture(bs->texture, texture);
254 obs_leave_graphics();
255 }
256 #endif
257 }
258 #endif
259
260 #if CHROME_VERSION_BUILD >= 3683
GetSpeakerLayout(CefAudioHandler::ChannelLayout cefLayout)261 static speaker_layout GetSpeakerLayout(CefAudioHandler::ChannelLayout cefLayout)
262 {
263 switch (cefLayout) {
264 case CEF_CHANNEL_LAYOUT_MONO:
265 return SPEAKERS_MONO; /**< Channels: MONO */
266 case CEF_CHANNEL_LAYOUT_STEREO:
267 return SPEAKERS_STEREO; /**< Channels: FL, FR */
268 case CEF_CHANNEL_LAYOUT_2POINT1:
269 return SPEAKERS_2POINT1; /**< Channels: FL, FR, LFE */
270 case CEF_CHANNEL_LAYOUT_2_2:
271 case CEF_CHANNEL_LAYOUT_QUAD:
272 case CEF_CHANNEL_LAYOUT_4_0:
273 return SPEAKERS_4POINT0; /**< Channels: FL, FR, FC, RC */
274 case CEF_CHANNEL_LAYOUT_4_1:
275 return SPEAKERS_4POINT1; /**< Channels: FL, FR, FC, LFE, RC */
276 case CEF_CHANNEL_LAYOUT_5_1:
277 case CEF_CHANNEL_LAYOUT_5_1_BACK:
278 return SPEAKERS_5POINT1; /**< Channels: FL, FR, FC, LFE, RL, RR */
279 case CEF_CHANNEL_LAYOUT_7_1:
280 case CEF_CHANNEL_LAYOUT_7_1_WIDE_BACK:
281 case CEF_CHANNEL_LAYOUT_7_1_WIDE:
282 return SPEAKERS_7POINT1; /**< Channels: FL, FR, FC, LFE, RL, RR, SL, SR */
283 default:
284 return SPEAKERS_UNKNOWN;
285 }
286 }
287 #endif
288
289 #if CHROME_VERSION_BUILD >= 4103
OnAudioStreamStarted(CefRefPtr<CefBrowser> browser,const CefAudioParameters & params_,int channels_)290 void BrowserClient::OnAudioStreamStarted(CefRefPtr<CefBrowser> browser,
291 const CefAudioParameters ¶ms_,
292 int channels_)
293 {
294 UNUSED_PARAMETER(browser);
295 channels = channels_;
296 channel_layout = (ChannelLayout)params_.channel_layout;
297 sample_rate = params_.sample_rate;
298 frames_per_buffer = params_.frames_per_buffer;
299 }
300
OnAudioStreamPacket(CefRefPtr<CefBrowser> browser,const float ** data,int frames,int64_t pts)301 void BrowserClient::OnAudioStreamPacket(CefRefPtr<CefBrowser> browser,
302 const float **data, int frames,
303 int64_t pts)
304 {
305 UNUSED_PARAMETER(browser);
306 if (!bs) {
307 return;
308 }
309 struct obs_source_audio audio = {};
310 const uint8_t **pcm = (const uint8_t **)data;
311 speaker_layout speakers = GetSpeakerLayout(channel_layout);
312 int speaker_count = get_audio_channels(speakers);
313 for (int i = 0; i < speaker_count; i++)
314 audio.data[i] = pcm[i];
315 audio.samples_per_sec = sample_rate;
316 audio.frames = frames;
317 audio.format = AUDIO_FORMAT_FLOAT_PLANAR;
318 audio.speakers = speakers;
319 audio.timestamp = (uint64_t)pts * 1000000LLU;
320 obs_source_output_audio(bs->source, &audio);
321 }
322
OnAudioStreamStopped(CefRefPtr<CefBrowser> browser)323 void BrowserClient::OnAudioStreamStopped(CefRefPtr<CefBrowser> browser)
324 {
325 UNUSED_PARAMETER(browser);
326 if (!bs) {
327 return;
328 }
329 }
330
OnAudioStreamError(CefRefPtr<CefBrowser> browser,const CefString & message)331 void BrowserClient::OnAudioStreamError(CefRefPtr<CefBrowser> browser,
332 const CefString &message)
333 {
334 UNUSED_PARAMETER(browser);
335 UNUSED_PARAMETER(message);
336 if (!bs) {
337 return;
338 }
339 }
340
Convert2CEFSpeakerLayout(int channels)341 static CefAudioHandler::ChannelLayout Convert2CEFSpeakerLayout(int channels)
342 {
343 switch (channels) {
344 case 1:
345 return CEF_CHANNEL_LAYOUT_MONO;
346 case 2:
347 return CEF_CHANNEL_LAYOUT_STEREO;
348 case 3:
349 return CEF_CHANNEL_LAYOUT_2_1;
350 case 4:
351 return CEF_CHANNEL_LAYOUT_4_0;
352 case 5:
353 return CEF_CHANNEL_LAYOUT_4_1;
354 case 6:
355 return CEF_CHANNEL_LAYOUT_5_1;
356 case 8:
357 return CEF_CHANNEL_LAYOUT_7_1;
358 default:
359 return CEF_CHANNEL_LAYOUT_UNSUPPORTED;
360 }
361 }
362
GetAudioParameters(CefRefPtr<CefBrowser> browser,CefAudioParameters & params)363 bool BrowserClient::GetAudioParameters(CefRefPtr<CefBrowser> browser,
364 CefAudioParameters ¶ms)
365 {
366 UNUSED_PARAMETER(browser);
367 int channels = (int)audio_output_get_channels(obs_get_audio());
368 params.channel_layout = Convert2CEFSpeakerLayout(channels);
369 params.sample_rate = (int)audio_output_get_sample_rate(obs_get_audio());
370 params.frames_per_buffer = kFramesPerBuffer;
371 return true;
372 }
373 #elif CHROME_VERSION_BUILD >= 3683 && CHROME_VERSION_BUILD < 4103
OnAudioStreamStarted(CefRefPtr<CefBrowser> browser,int id,int,ChannelLayout channel_layout,int sample_rate,int)374 void BrowserClient::OnAudioStreamStarted(CefRefPtr<CefBrowser> browser, int id,
375 int, ChannelLayout channel_layout,
376 int sample_rate, int)
377 {
378 UNUSED_PARAMETER(browser);
379 if (!bs) {
380 return;
381 }
382
383 AudioStream &stream = bs->audio_streams[id];
384 if (!stream.source) {
385 stream.source = obs_source_create_private("audio_line", nullptr,
386 nullptr);
387 obs_source_release(stream.source);
388
389 obs_source_add_active_child(bs->source, stream.source);
390
391 std::lock_guard<std::mutex> lock(bs->audio_sources_mutex);
392 bs->audio_sources.push_back(stream.source);
393 }
394
395 stream.speakers = GetSpeakerLayout(channel_layout);
396 stream.channels = get_audio_channels(stream.speakers);
397 stream.sample_rate = sample_rate;
398 }
399
OnAudioStreamPacket(CefRefPtr<CefBrowser> browser,int id,const float ** data,int frames,int64_t pts)400 void BrowserClient::OnAudioStreamPacket(CefRefPtr<CefBrowser> browser, int id,
401 const float **data, int frames,
402 int64_t pts)
403 {
404 UNUSED_PARAMETER(browser);
405 if (!bs) {
406 return;
407 }
408
409 AudioStream &stream = bs->audio_streams[id];
410 struct obs_source_audio audio = {};
411
412 const uint8_t **pcm = (const uint8_t **)data;
413 for (int i = 0; i < stream.channels; i++)
414 audio.data[i] = pcm[i];
415
416 audio.samples_per_sec = stream.sample_rate;
417 audio.frames = frames;
418 audio.format = AUDIO_FORMAT_FLOAT_PLANAR;
419 audio.speakers = stream.speakers;
420 audio.timestamp = (uint64_t)pts * 1000000LLU;
421
422 obs_source_output_audio(stream.source, &audio);
423 }
424
OnAudioStreamStopped(CefRefPtr<CefBrowser> browser,int id)425 void BrowserClient::OnAudioStreamStopped(CefRefPtr<CefBrowser> browser, int id)
426 {
427 UNUSED_PARAMETER(browser);
428 if (!bs) {
429 return;
430 }
431
432 auto pair = bs->audio_streams.find(id);
433 if (pair == bs->audio_streams.end()) {
434 return;
435 }
436
437 AudioStream &stream = pair->second;
438 {
439 std::lock_guard<std::mutex> lock(bs->audio_sources_mutex);
440 for (size_t i = 0; i < bs->audio_sources.size(); i++) {
441 obs_source_t *source = bs->audio_sources[i];
442 if (source == stream.source) {
443 bs->audio_sources.erase(
444 bs->audio_sources.begin() + i);
445 break;
446 }
447 }
448 }
449 bs->audio_streams.erase(pair);
450 }
451 #endif
452
OnLoadEnd(CefRefPtr<CefBrowser>,CefRefPtr<CefFrame> frame,int)453 void BrowserClient::OnLoadEnd(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame> frame,
454 int)
455 {
456 if (!bs) {
457 return;
458 }
459
460 if (frame->IsMain() && bs->css.length()) {
461 std::string uriEncodedCSS =
462 CefURIEncode(bs->css, false).ToString();
463
464 std::string script;
465 script += "const obsCSS = document.createElement('style');";
466 script += "obsCSS.innerHTML = decodeURIComponent(\"" +
467 uriEncodedCSS + "\");";
468 script += "document.querySelector('head').appendChild(obsCSS);";
469
470 frame->ExecuteJavaScript(script, "", 0);
471 }
472 }
473
OnConsoleMessage(CefRefPtr<CefBrowser>,cef_log_severity_t level,const CefString & message,const CefString & source,int line)474 bool BrowserClient::OnConsoleMessage(CefRefPtr<CefBrowser>,
475 #if CHROME_VERSION_BUILD >= 3282
476 cef_log_severity_t level,
477 #endif
478 const CefString &message,
479 const CefString &source, int line)
480 {
481 int errorLevel = LOG_INFO;
482 switch (level) {
483 case LOGSEVERITY_ERROR:
484 errorLevel = LOG_WARNING;
485 break;
486 case LOGSEVERITY_FATAL:
487 errorLevel = LOG_ERROR;
488 break;
489 default:
490 return false;
491 }
492
493 blog(errorLevel, "obs-browser: %s (source: %s:%d)",
494 message.ToString().c_str(), source.ToString().c_str(), line);
495 return false;
496 }
497