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 &params_,
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 &params)
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