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/frame/draw_frame.h>
28 #include <core/frame/frame_factory.h>
29 #include <core/producer/frame_producer.h>
30 #include <core/interaction/interaction_event.h>
31 #include <core/frame/frame.h>
32 #include <core/frame/pixel_format.h>
33 #include <core/frame/audio_channel_layout.h>
34 #include <core/frame/geometry.h>
35 #include <core/help/help_repository.h>
36 #include <core/help/help_sink.h>
38 #include <common/assert.h>
39 #include <common/env.h>
40 #include <common/executor.h>
41 #include <common/lock.h>
42 #include <common/future.h>
43 #include <common/diagnostics/graph.h>
44 #include <common/prec_timer.h>
45 #include <common/linq.h>
46 #include <common/os/filesystem.h>
48 #include <boost/algorithm/string/predicate.hpp>
49 #include <boost/filesystem.hpp>
50 #include <boost/timer.hpp>
51 #include <boost/log/trivial.hpp>
52 #include <boost/property_tree/ptree.hpp>
54 #include <tbb/atomic.h>
55 #include <tbb/concurrent_queue.h>
58 #pragma warning(disable: 4458)
61 #include <cef_client.h>
62 #include <cef_render_handler.h>
71 #pragma comment (lib, "libcef.lib")
72 #pragma comment (lib, "libcef_dll_wrapper.lib")
74 namespace caspar { namespace html {
78 , public CefRenderHandler
79 , public CefLifeSpanHandler
80 , public CefLoadHandler
83 spl::shared_ptr<diagnostics::graph> graph_;
84 boost::timer tick_timer_;
85 boost::timer frame_timer_;
86 boost::timer paint_timer_;
88 spl::shared_ptr<core::frame_factory> frame_factory_;
89 core::video_format_desc format_desc_;
90 tbb::concurrent_queue<std::wstring> javascript_before_load_;
91 tbb::atomic<bool> loaded_;
92 tbb::atomic<bool> removed_;
93 tbb::atomic<bool> animation_frame_requested_;
94 std::queue<core::draw_frame> frames_;
95 mutable boost::mutex frames_mutex_;
97 core::draw_frame last_frame_;
98 core::draw_frame last_progressive_frame_;
99 mutable boost::mutex last_frame_mutex_;
101 CefRefPtr<CefBrowser> browser_;
108 spl::shared_ptr<core::frame_factory> frame_factory,
109 const core::video_format_desc& format_desc,
110 const std::wstring& url)
112 , frame_factory_(std::move(frame_factory))
113 , format_desc_(format_desc)
114 , last_frame_(core::draw_frame::empty())
115 , last_progressive_frame_(core::draw_frame::empty())
116 , executor_(L"html_producer")
118 graph_->set_color("browser-tick-time", diagnostics::color(0.1f, 1.0f, 0.1f));
119 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
120 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.9f));
121 graph_->set_text(print());
122 diagnostics::register_graph(graph_);
126 animation_frame_requested_ = false;
127 executor_.begin_invoke([&]{ update(); });
130 core::draw_frame receive()
132 auto frame = last_frame();
133 executor_.begin_invoke([&]{ update(); });
137 core::draw_frame last_frame() const
139 return lock(last_frame_mutex_, [&]
145 void execute_javascript(const std::wstring& javascript)
149 javascript_before_load_.push(javascript);
153 execute_queued_javascript();
154 do_execute_javascript(javascript);
158 CefRefPtr<CefBrowserHost> get_browser_host()
160 return browser_->GetHost();
165 if (!animation_frame_requested_)
166 CASPAR_LOG(warning) << print()
167 << " window.requestAnimationFrame() never called. "
168 << "Animations might have been laggy";
172 if (browser_ != nullptr)
174 browser_->GetHost()->CloseBrowser(true);
185 bool is_removed() const
192 bool GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect)
194 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
196 rect = CefRect(0, 0, format_desc_.square_width, format_desc_.square_height);
201 CefRefPtr<CefBrowser> browser,
202 PaintElementType type,
203 const RectList &dirtyRects,
208 graph_->set_value("browser-tick-time", paint_timer_.elapsed()
210 * format_desc_.field_count
212 paint_timer_.restart();
213 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
215 boost::timer copy_timer;
216 core::pixel_format_desc pixel_desc;
217 pixel_desc.format = core::pixel_format::bgra;
218 pixel_desc.planes.push_back(
219 core::pixel_format_desc::plane(width, height, 4));
220 auto frame = frame_factory_->create_frame(this, pixel_desc, core::audio_channel_layout::invalid());
221 A_memcpy(frame.image_data().begin(), buffer, width * height * 4);
223 lock(frames_mutex_, [&]
225 frames_.push(core::draw_frame(std::move(frame)));
227 size_t max_in_queue = format_desc_.field_count;
229 while (frames_.size() > max_in_queue)
232 graph_->set_tag(diagnostics::tag_severity::WARNING, "dropped-frame");
235 graph_->set_value("copy-time", copy_timer.elapsed()
237 * format_desc_.field_count
241 void OnAfterCreated(CefRefPtr<CefBrowser> browser) override
243 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
248 void OnBeforeClose(CefRefPtr<CefBrowser> browser) override
250 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
255 bool DoClose(CefRefPtr<CefBrowser> browser) override
257 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
262 CefRefPtr<CefRenderHandler> GetRenderHandler() override
267 CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override
272 CefRefPtr<CefLoadHandler> GetLoadHandler() override {
277 CefRefPtr<CefBrowser> browser,
278 CefRefPtr<CefFrame> frame,
279 int httpStatusCode) override
282 execute_queued_javascript();
285 bool OnProcessMessageReceived(
286 CefRefPtr<CefBrowser> browser,
287 CefProcessId source_process,
288 CefRefPtr<CefProcessMessage> message) override
290 auto name = message->GetName().ToString();
292 if (name == ANIMATION_FRAME_REQUESTED_MESSAGE_NAME)
295 << print() << L" Requested animation frame";
296 animation_frame_requested_ = true;
300 else if (name == REMOVE_MESSAGE_NAME)
306 else if (name == LOG_MESSAGE_NAME)
308 auto args = message->GetArgumentList();
310 static_cast<boost::log::trivial::severity_level>(args->GetInt(0));
311 auto msg = args->GetString(1).ToWString();
313 BOOST_LOG_STREAM_WITH_PARAMS(
315 (boost::log::keywords::severity = severity))
316 << print() << L" [renderer_process] " << msg;
322 void invoke_requested_animation_frames()
325 browser_->SendProcessMessage(
326 CefProcessId::PID_RENDERER,
327 CefProcessMessage::Create(TICK_MESSAGE_NAME));
328 graph_->set_value("tick-time", tick_timer_.elapsed()
330 * format_desc_.field_count
332 tick_timer_.restart();
335 bool try_pop(core::draw_frame& result)
337 return lock(frames_mutex_, [&]() -> bool
339 if (!frames_.empty())
341 result = std::move(frames_.front());
351 core::draw_frame pop()
353 core::draw_frame frame;
357 CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " No frame in buffer"));
365 invoke_requested_animation_frames();
370 auto num_frames = lock(frames_mutex_, [&]
372 return frames_.size();
375 if (num_frames >= format_desc_.field_count)
377 if (format_desc_.field_mode != core::field_mode::progressive)
381 executor_.yield(caspar::task_priority::lowest_priority);
382 timer.tick(1.0 / (format_desc_.fps * format_desc_.field_count));
383 invoke_requested_animation_frames();
387 lock(last_frame_mutex_, [&]
389 last_progressive_frame_ = frame2;
390 last_frame_ = core::draw_frame::interlace(frame1, frame2, format_desc_.field_mode);
397 lock(last_frame_mutex_, [&]
403 else if (num_frames == 1) // Interlaced but only one frame
404 { // available. Probably the last frame
405 // of some animation sequence.
408 lock(last_frame_mutex_, [&]
410 last_progressive_frame_ = frame;
414 timer.tick(1.0 / (format_desc_.fps * format_desc_.field_count));
415 invoke_requested_animation_frames();
419 graph_->set_tag(diagnostics::tag_severity::INFO, "late-frame");
421 if (format_desc_.field_mode != core::field_mode::progressive)
423 lock(last_frame_mutex_, [&]
425 last_frame_ = last_progressive_frame_;
428 timer.tick(1.0 / (format_desc_.fps * format_desc_.field_count));
429 invoke_requested_animation_frames();
434 void do_execute_javascript(const std::wstring& javascript)
436 html::begin_invoke([=]
438 if (browser_ != nullptr)
439 browser_->GetMainFrame()->ExecuteJavaScript(u8(javascript).c_str(), browser_->GetMainFrame()->GetURL(), 0);
443 void execute_queued_javascript()
445 std::wstring javascript;
447 while (javascript_before_load_.try_pop(javascript))
448 do_execute_javascript(javascript);
451 std::wstring print() const
453 return L"html[" + url_ + L"]";
456 IMPLEMENT_REFCOUNTING(html_client);
460 : public core::frame_producer_base
462 core::monitor::subject monitor_subject_;
463 const std::wstring url_;
464 core::constraints constraints_;
466 CefRefPtr<html_client> client_;
470 const spl::shared_ptr<core::frame_factory>& frame_factory,
471 const core::video_format_desc& format_desc,
472 const std::wstring& url)
475 constraints_.width.set(format_desc.square_width);
476 constraints_.height.set(format_desc.square_height);
480 client_ = new html_client(frame_factory, format_desc, url_);
482 CefWindowInfo window_info;
484 window_info.SetTransparentPainting(true);
485 window_info.SetAsOffScreen(nullptr);
486 //window_info.SetAsWindowless(nullptr, true);
488 CefBrowserSettings browser_settings;
489 browser_settings.web_security = cef_state_t::STATE_DISABLED;
490 CefBrowserHost::CreateBrowser(window_info, client_.get(), url, browser_settings, nullptr);
502 std::wstring name() const override
507 void on_interaction(const core::interaction_event::ptr& event) override
509 if (core::is<core::mouse_move_event>(event))
511 auto move = core::as<core::mouse_move_event>(event);
512 int x = static_cast<int>(move->x * constraints_.width.get());
513 int y = static_cast<int>(move->y * constraints_.height.get());
518 client_->get_browser_host()->SendMouseMoveEvent(e, false);
520 else if (core::is<core::mouse_button_event>(event))
522 auto button = core::as<core::mouse_button_event>(event);
523 int x = static_cast<int>(button->x * constraints_.width.get());
524 int y = static_cast<int>(button->y * constraints_.height.get());
529 client_->get_browser_host()->SendMouseClickEvent(
531 static_cast<CefBrowserHost::MouseButtonType>(button->button),
535 else if (core::is<core::mouse_wheel_event>(event))
537 auto wheel = core::as<core::mouse_wheel_event>(event);
538 int x = static_cast<int>(wheel->x * constraints_.width.get());
539 int y = static_cast<int>(wheel->y * constraints_.height.get());
544 static const int WHEEL_TICKS_AMPLIFICATION = 40;
545 client_->get_browser_host()->SendMouseWheelEvent(
548 wheel->ticks_delta * WHEEL_TICKS_AMPLIFICATION); // delta_y
552 bool collides(double x, double y) const override
557 core::draw_frame receive_impl() override
561 if (client_->is_removed())
564 return core::draw_frame::empty();
567 return client_->receive();
570 return core::draw_frame::empty();
573 std::future<std::wstring> call(const std::vector<std::wstring>& params) override
576 return make_ready_future(std::wstring(L""));
578 auto javascript = params.at(0);
580 client_->execute_javascript(javascript);
582 return make_ready_future(std::wstring(L""));
585 std::wstring print() const override
587 return L"html[" + url_ + L"]";
590 boost::property_tree::wptree info() const override
592 boost::property_tree::wptree info;
593 info.add(L"type", L"html-producer");
597 core::constraints& pixel_constraints() override
602 core::monitor::subject& monitor_output()
604 return monitor_subject_;
608 void describe_producer(core::help_sink& sink, const core::help_repository& repo)
610 sink.short_description(L"Renders a web page in real time.");
611 sink.syntax(L"{[html_filename:string]},{[HTML] [url:string]}");
612 sink.para()->text(L"Embeds an actual web browser and renders the content in realtime.");
614 ->text(L"HTML content can either be stored locally under the ")->code(L"templates")
615 ->text(L" folder or fetched directly via an URL. If a .html file is found with the name ")
616 ->code(L"html_filename")->text(L" under the ")->code(L"templates")->text(L" folder it will be rendered. If the ")
617 ->code(L"[HTML] url")->text(L" syntax is used instead, the URL will be loaded.");
618 sink.para()->text(L"Examples:");
619 sink.example(L">> PLAY 1-10 [HTML] http://www.casparcg.com");
620 sink.example(L">> PLAY 1-10 folder/html_file");
623 spl::shared_ptr<core::frame_producer> create_producer(
624 const core::frame_producer_dependencies& dependencies,
625 const std::vector<std::wstring>& params)
627 const auto filename = env::template_folder() + params.at(0) + L".html";
628 const auto found_filename = find_case_insensitive(filename);
630 if (!found_filename && !boost::iequals(params.at(0), L"[HTML]"))
631 return core::frame_producer::empty();
633 const auto url = found_filename
634 ? L"file://" + *found_filename
637 if (!boost::algorithm::contains(url, ".") || boost::algorithm::ends_with(url, "_A") || boost::algorithm::ends_with(url, "_ALPHA"))
638 return core::frame_producer::empty();
640 return core::create_destroy_proxy(spl::make_shared<html_producer>(
641 dependencies.frame_factory,
642 dependencies.format_desc,