2 * Copyright 2013 Sveriges Television AB http://casparcg.com/
4 * This file is part of CasparCG (www.casparcg.com).
6 * CasparCG 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 3 of the License, or
9 * (at your option) any later version.
11 * CasparCG 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.
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
19 * Author: Robert Nagy, ronag89@gmail.com
22 #include "html_producer.h"
24 #include <core/video_format.h>
26 #include <core/monitor/monitor.h>
27 #include <core/parameters/parameters.h>
28 #include <core/producer/frame/basic_frame.h>
29 #include <core/producer/frame/frame_factory.h>
30 #include <core/producer/frame_producer.h>
31 #include <core/mixer/write_frame.h>
33 #include <common/utility/assert.h>
34 #include <common/env.h>
35 #include <common/concurrency/executor.h>
36 #include <common/concurrency/lock.h>
37 #include <common/concurrency/future_util.h>
38 #include <common/diagnostics/graph.h>
39 #include <common/utility/timer.h>
40 #include <common/memory/memcpy.h>
42 #include <boost/algorithm/string/predicate.hpp>
43 #include <boost/algorithm/string/trim.hpp>
44 #include <boost/algorithm/string/replace.hpp>
45 #include <boost/filesystem.hpp>
46 #include <boost/format.hpp>
47 #include <boost/timer.hpp>
49 #include <tbb/atomic.h>
50 #include <tbb/concurrent_queue.h>
54 #include <cef_client.h>
55 #include <cef_render_handler.h>
61 #pragma comment (lib, "libcef.lib")
62 #pragma comment (lib, "libcef_dll_wrapper.lib")
69 , public CefRenderHandler
70 , public CefLifeSpanHandler
71 , public CefLoadHandler
74 safe_ptr<diagnostics::graph> graph_;
75 boost::timer tick_timer_;
76 boost::timer frame_timer_;
77 boost::timer paint_timer_;
79 safe_ptr<core::frame_factory> frame_factory_;
80 tbb::concurrent_queue<std::wstring> javascript_before_load_;
81 tbb::atomic<bool> loaded_;
82 tbb::atomic<bool> removed_;
83 tbb::atomic<bool> animation_frame_requested_;
84 std::queue<safe_ptr<core::basic_frame>> frames_;
85 mutable boost::mutex frames_mutex_;
87 safe_ptr<core::basic_frame> last_frame_;
88 safe_ptr<core::basic_frame> last_progressive_frame_;
89 mutable boost::mutex last_frame_mutex_;
91 CefRefPtr<CefBrowser> browser_;
97 html_client(safe_ptr<core::frame_factory> frame_factory, const std::wstring& url)
99 , frame_factory_(frame_factory)
100 , last_frame_(core::basic_frame::empty())
101 , last_progressive_frame_(core::basic_frame::empty())
102 , executor_(L"html_producer")
104 graph_->set_color("browser-tick-time", diagnostics::color(0.1f, 1.0f, 0.1f));
105 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
106 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.9f));
107 graph_->set_text(print());
108 diagnostics::register_graph(graph_);
112 animation_frame_requested_ = false;
113 executor_.begin_invoke([&]{ update(); });
116 safe_ptr<core::basic_frame> receive()
118 auto frame = last_frame();
119 executor_.begin_invoke([&]{ update(); });
123 safe_ptr<core::basic_frame> last_frame() const
125 return lock(last_frame_mutex_, [&]
131 void execute_javascript(const std::wstring& javascript)
135 javascript_before_load_.push(javascript);
139 execute_queued_javascript();
140 do_execute_javascript(javascript);
146 if (!animation_frame_requested_)
147 CASPAR_LOG(warning) << print()
148 << " window.requestAnimationFrame() never called. "
149 << "Animations might have been laggy";
153 if (browser_ != nullptr)
155 browser_->GetHost()->CloseBrowser(true);
166 bool is_removed() const
173 bool GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect)
175 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
177 rect = CefRect(0, 0, frame_factory_->get_video_format_desc().square_width, frame_factory_->get_video_format_desc().square_height);
182 CefRefPtr<CefBrowser> browser,
183 PaintElementType type,
184 const RectList &dirtyRects,
189 graph_->set_value("browser-tick-time", paint_timer_.elapsed()
190 * frame_factory_->get_video_format_desc().fps
191 * frame_factory_->get_video_format_desc().field_count
193 paint_timer_.restart();
194 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
196 boost::timer copy_timer;
197 core::pixel_format_desc pixel_desc;
198 pixel_desc.pix_fmt = core::pixel_format::bgra;
199 pixel_desc.planes.push_back(
200 core::pixel_format_desc::plane(width, height, 4));
201 auto frame = frame_factory_->create_frame(this, pixel_desc);
202 fast_memcpy(frame->image_data().begin(), buffer, width * height * 4);
205 lock(frames_mutex_, [&]
209 size_t max_in_queue = frame_factory_->get_video_format_desc().field_count;
211 while (frames_.size() > max_in_queue)
214 graph_->set_tag("dropped-frame");
217 graph_->set_value("copy-time", copy_timer.elapsed()
218 * frame_factory_->get_video_format_desc().fps
219 * frame_factory_->get_video_format_desc().field_count
223 void OnAfterCreated(CefRefPtr<CefBrowser> browser) override
225 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
230 void OnBeforeClose(CefRefPtr<CefBrowser> browser) override
232 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
237 bool DoClose(CefRefPtr<CefBrowser> browser) override
239 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
244 CefRefPtr<CefRenderHandler> GetRenderHandler() override
249 CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override
254 CefRefPtr<CefLoadHandler> GetLoadHandler() override {
259 CefRefPtr<CefBrowser> browser,
260 CefRefPtr<CefFrame> frame,
261 int httpStatusCode) override
264 execute_queued_javascript();
267 bool OnProcessMessageReceived(
268 CefRefPtr<CefBrowser> browser,
269 CefProcessId source_process,
270 CefRefPtr<CefProcessMessage> message) override
272 auto name = message->GetName().ToString();
274 if (name == ANIMATION_FRAME_REQUESTED_MESSAGE_NAME)
277 << print() << L" Requested animation frame";
278 animation_frame_requested_ = true;
282 else if (name == REMOVE_MESSAGE_NAME)
288 else if (name == LOG_MESSAGE_NAME)
290 auto args = message->GetArgumentList();
292 static_cast<log::severity_level>(args->GetInt(0));
293 auto msg = args->GetString(1).ToWString();
295 BOOST_LOG_STREAM_WITH_PARAMS(
297 (boost::log::keywords::severity = severity))
298 << print() << L" [renderer_process] " << msg;
304 void invoke_requested_animation_frames()
307 browser_->SendProcessMessage(
308 CefProcessId::PID_RENDERER,
309 CefProcessMessage::Create(TICK_MESSAGE_NAME));
310 graph_->set_value("tick-time", tick_timer_.elapsed()
311 * frame_factory_->get_video_format_desc().fps
312 * frame_factory_->get_video_format_desc().field_count
314 tick_timer_.restart();
317 bool try_pop(safe_ptr<core::basic_frame>& result)
319 return lock(frames_mutex_, [&]() -> bool
321 if (!frames_.empty())
323 result = frames_.front();
333 safe_ptr<core::basic_frame> pop()
335 safe_ptr<core::basic_frame> frame;
339 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + "No frame in buffer"));
347 invoke_requested_animation_frames();
349 high_prec_timer timer;
351 const auto& format_desc = frame_factory_->get_video_format_desc();
353 auto num_frames = lock(frames_mutex_, [&]
355 return frames_.size();
358 if (num_frames >= format_desc.field_count)
360 if (format_desc.field_mode != core::field_mode::progressive)
365 timer.tick(1.0 / (format_desc.fps * format_desc.field_count));
366 invoke_requested_animation_frames();
370 lock(last_frame_mutex_, [&]
372 last_progressive_frame_ = frame2;
373 last_frame_ = core::basic_frame::interlace(frame1, frame2, format_desc.field_mode);
380 lock(last_frame_mutex_, [&]
386 else if (num_frames == 1) // Interlaced but only one frame
387 { // available. Probably the last frame
388 // of some animation sequence.
391 lock(last_frame_mutex_, [&]
393 last_progressive_frame_ = frame;
397 timer.tick(1.0 / (format_desc.fps * format_desc.field_count));
398 invoke_requested_animation_frames();
402 graph_->set_tag("late-frame");
404 if (format_desc.field_mode != core::field_mode::progressive)
406 lock(last_frame_mutex_, [&]
408 last_frame_ = last_progressive_frame_;
411 timer.tick(1.0 / (format_desc.fps * format_desc.field_count));
412 invoke_requested_animation_frames();
417 void do_execute_javascript(const std::wstring& javascript)
419 html::begin_invoke([=]
421 if (browser_ != nullptr)
422 browser_->GetMainFrame()->ExecuteJavaScript(narrow(javascript).c_str(), browser_->GetMainFrame()->GetURL(), 0);
426 void execute_queued_javascript()
428 std::wstring javascript;
430 while (javascript_before_load_.try_pop(javascript))
431 do_execute_javascript(javascript);
434 std::wstring print() const
436 return L"html[" + url_ + L"]";
439 IMPLEMENT_REFCOUNTING(html_client);
443 : public core::frame_producer
445 core::monitor::subject monitor_subject_;
446 const std::wstring url_;
448 CefRefPtr<html_client> client_;
452 const safe_ptr<core::frame_factory>& frame_factory,
453 const std::wstring& url)
458 client_ = new html_client(frame_factory, url_);
460 CefWindowInfo window_info;
462 window_info.SetTransparentPainting(TRUE);
463 window_info.SetAsOffScreen(nullptr);
464 //window_info.SetAsWindowless(nullptr, true);
466 CefBrowserSettings browser_settings;
467 CefBrowserHost::CreateBrowser(window_info, client_.get(), url, browser_settings, nullptr);
479 safe_ptr<core::basic_frame> receive(int) override
483 if (client_->is_removed())
486 return core::basic_frame::empty();
489 return client_->receive();
492 return core::basic_frame::empty();
495 safe_ptr<core::basic_frame> last_frame() const override
498 ? client_->last_frame()
499 : core::basic_frame::empty();
502 boost::unique_future<std::wstring> call(const std::wstring& param) override
504 static const boost::wregex play_exp(L"PLAY\\s*(\\d+)?", boost::regex::icase);
505 static const boost::wregex stop_exp(L"STOP\\s*(\\d+)?", boost::regex::icase);
506 static const boost::wregex next_exp(L"NEXT\\s*(\\d+)?", boost::regex::icase);
507 static const boost::wregex remove_exp(L"REMOVE\\s*(\\d+)?", boost::regex::icase);
508 static const boost::wregex update_exp(L"UPDATE\\s+(\\d+)?\"?(?<VALUE>.*)\"?", boost::regex::icase);
509 static const boost::wregex invoke_exp(L"INVOKE\\s+(\\d+)?\"?(?<VALUE>.*)\"?", boost::regex::icase);
512 return wrap_as_future(std::wstring(L""));
514 auto javascript = param;
518 if (boost::regex_match(param, what, play_exp))
520 javascript = (boost::wformat(L"play()")).str();
522 else if (boost::regex_match(param, what, stop_exp))
524 javascript = (boost::wformat(L"stop()")).str();
526 else if (boost::regex_match(param, what, next_exp))
528 javascript = (boost::wformat(L"next()")).str();
530 else if (boost::regex_match(param, what, remove_exp))
533 return wrap_as_future(std::wstring(L""));
535 else if (boost::regex_match(param, what, update_exp))
537 javascript = (boost::wformat(L"update(\"%1%\")") % boost::algorithm::replace_all_copy(boost::algorithm::trim_copy_if(what["VALUE"].str(), boost::is_any_of(" \"")), "\"", "\\\"")).str();
539 else if (boost::regex_match(param, what, invoke_exp))
541 auto function_call = boost::algorithm::trim_copy_if(what["VALUE"].str(), boost::is_any_of(" \""));
543 // Append empty () if no parameter list has been given
544 javascript = boost::ends_with(function_call, ")") ? function_call : function_call + L"()";
547 client_->execute_javascript(javascript);
549 return wrap_as_future(std::wstring(L""));
552 std::wstring print() const override
554 return L"html[" + url_ + L"]";
557 boost::property_tree::wptree info() const override
559 boost::property_tree::wptree info;
560 info.add(L"type", L"html-producer");
564 core::monitor::subject& monitor_output()
566 return monitor_subject_;
570 safe_ptr<core::frame_producer> create_producer(
571 const safe_ptr<core::frame_factory>& frame_factory,
572 const core::parameters& params)
574 const auto filename = env::template_folder() + L"\\" + params.at_original(0) + L".html";
576 if (!boost::filesystem::exists(filename) && params.at(0) != L"[HTML]")
577 return core::frame_producer::empty();
579 const auto url = boost::filesystem::exists(filename)
581 : params.at_original(1);
583 if(!boost::algorithm::contains(url, ".") || boost::algorithm::ends_with(url, "_A") || boost::algorithm::ends_with(url, "_ALPHA"))
584 return core::frame_producer::empty();
586 return core::create_producer_destroy_proxy(
587 core::create_producer_print_proxy(
588 make_safe<html_producer>(