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/frame/frame.h>
31 #include <core/frame/pixel_format.h>
32 #include <core/frame/geometry.h>
34 #include <common/assert.h>
35 #include <common/env.h>
36 #include <common/executor.h>
37 #include <common/lock.h>
38 #include <common/future.h>
39 #include <common/diagnostics/graph.h>
40 #include <common/prec_timer.h>
41 #include <common/linq.h>
42 #include <common/os/filesystem.h>
44 #include <boost/algorithm/string/predicate.hpp>
45 #include <boost/filesystem.hpp>
46 #include <boost/timer.hpp>
47 #include <boost/log/trivial.hpp>
48 #include <boost/property_tree/ptree.hpp>
50 #include <tbb/atomic.h>
51 #include <tbb/concurrent_queue.h>
55 #include <cef_client.h>
56 #include <cef_render_handler.h>
64 #pragma comment (lib, "libcef.lib")
65 #pragma comment (lib, "libcef_dll_wrapper.lib")
72 , public CefRenderHandler
73 , public CefLifeSpanHandler
74 , public CefLoadHandler
77 spl::shared_ptr<diagnostics::graph> graph_;
78 boost::timer tick_timer_;
79 boost::timer frame_timer_;
80 boost::timer paint_timer_;
82 spl::shared_ptr<core::frame_factory> frame_factory_;
83 core::video_format_desc format_desc_;
84 tbb::concurrent_queue<std::wstring> javascript_before_load_;
85 tbb::atomic<bool> loaded_;
86 tbb::atomic<bool> removed_;
87 tbb::atomic<bool> animation_frame_requested_;
88 std::queue<core::draw_frame> frames_;
89 mutable boost::mutex frames_mutex_;
91 core::draw_frame last_frame_;
92 core::draw_frame last_progressive_frame_;
93 mutable boost::mutex last_frame_mutex_;
95 CefRefPtr<CefBrowser> browser_;
102 spl::shared_ptr<core::frame_factory> frame_factory,
103 const core::video_format_desc& format_desc,
104 const std::wstring& url)
106 , frame_factory_(std::move(frame_factory))
107 , format_desc_(format_desc)
108 , last_frame_(core::draw_frame::empty())
109 , last_progressive_frame_(core::draw_frame::empty())
110 , executor_(L"html_producer")
112 graph_->set_color("browser-tick-time", diagnostics::color(0.1f, 1.0f, 0.1f));
113 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
114 graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.9f));
115 graph_->set_text(print());
116 diagnostics::register_graph(graph_);
120 animation_frame_requested_ = false;
121 executor_.begin_invoke([&]{ update(); });
124 core::draw_frame receive()
126 auto frame = last_frame();
127 executor_.begin_invoke([&]{ update(); });
131 core::draw_frame last_frame() const
133 return lock(last_frame_mutex_, [&]
139 void execute_javascript(const std::wstring& javascript)
143 javascript_before_load_.push(javascript);
147 execute_queued_javascript();
148 do_execute_javascript(javascript);
154 if (!animation_frame_requested_)
155 CASPAR_LOG(warning) << print()
156 << " window.requestAnimationFrame() never called. "
157 << "Animations might have been laggy";
161 if (browser_ != nullptr)
163 browser_->GetHost()->CloseBrowser(true);
174 bool is_removed() const
181 bool GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect)
183 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
185 rect = CefRect(0, 0, format_desc_.square_width, format_desc_.square_height);
190 CefRefPtr<CefBrowser> browser,
191 PaintElementType type,
192 const RectList &dirtyRects,
197 graph_->set_value("browser-tick-time", paint_timer_.elapsed()
199 * format_desc_.field_count
201 paint_timer_.restart();
202 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
204 boost::timer copy_timer;
205 core::pixel_format_desc pixel_desc;
206 pixel_desc.format = core::pixel_format::bgra;
207 pixel_desc.planes.push_back(
208 core::pixel_format_desc::plane(width, height, 4));
209 auto frame = frame_factory_->create_frame(this, pixel_desc);
210 A_memcpy(frame.image_data().begin(), buffer, width * height * 4);
212 lock(frames_mutex_, [&]
214 frames_.push(core::draw_frame(std::move(frame)));
216 size_t max_in_queue = format_desc_.field_count;
218 while (frames_.size() > max_in_queue)
221 graph_->set_tag("dropped-frame");
224 graph_->set_value("copy-time", copy_timer.elapsed()
226 * format_desc_.field_count
230 void OnAfterCreated(CefRefPtr<CefBrowser> browser) override
232 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
237 void OnBeforeClose(CefRefPtr<CefBrowser> browser) override
239 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
244 bool DoClose(CefRefPtr<CefBrowser> browser) override
246 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
251 CefRefPtr<CefRenderHandler> GetRenderHandler() override
256 CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override
261 CefRefPtr<CefLoadHandler> GetLoadHandler() override {
266 CefRefPtr<CefBrowser> browser,
267 CefRefPtr<CefFrame> frame,
268 int httpStatusCode) override
271 execute_queued_javascript();
274 bool OnProcessMessageReceived(
275 CefRefPtr<CefBrowser> browser,
276 CefProcessId source_process,
277 CefRefPtr<CefProcessMessage> message) override
279 auto name = message->GetName().ToString();
281 if (name == ANIMATION_FRAME_REQUESTED_MESSAGE_NAME)
284 << print() << L" Requested animation frame";
285 animation_frame_requested_ = true;
289 else if (name == REMOVE_MESSAGE_NAME)
295 else if (name == LOG_MESSAGE_NAME)
297 auto args = message->GetArgumentList();
299 static_cast<boost::log::trivial::severity_level>(args->GetInt(0));
300 auto msg = args->GetString(1).ToWString();
302 BOOST_LOG_STREAM_WITH_PARAMS(
304 (boost::log::keywords::severity = severity))
305 << print() << L" [renderer_process] " << msg;
311 void invoke_requested_animation_frames()
314 browser_->SendProcessMessage(
315 CefProcessId::PID_RENDERER,
316 CefProcessMessage::Create(TICK_MESSAGE_NAME));
317 graph_->set_value("tick-time", tick_timer_.elapsed()
319 * format_desc_.field_count
321 tick_timer_.restart();
324 bool try_pop(core::draw_frame& result)
326 return lock(frames_mutex_, [&]() -> bool
328 if (!frames_.empty())
330 result = frames_.front();
340 core::draw_frame pop()
342 core::draw_frame frame;
346 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " No frame in buffer"));
354 invoke_requested_animation_frames();
359 auto num_frames = lock(frames_mutex_, [&]
361 return frames_.size();
364 if (num_frames >= format_desc_.field_count)
366 if (format_desc_.field_mode != core::field_mode::progressive)
370 executor_.yield(caspar::task_priority::lowest_priority);
371 timer.tick(1.0 / (format_desc_.fps * format_desc_.field_count));
372 invoke_requested_animation_frames();
376 lock(last_frame_mutex_, [&]
378 last_progressive_frame_ = frame2;
379 last_frame_ = core::draw_frame::interlace(frame1, frame2, format_desc_.field_mode);
386 lock(last_frame_mutex_, [&]
392 else if (num_frames == 1) // Interlaced but only one frame
393 { // available. Probably the last frame
394 // of some animation sequence.
397 lock(last_frame_mutex_, [&]
399 last_progressive_frame_ = frame;
403 timer.tick(1.0 / (format_desc_.fps * format_desc_.field_count));
404 invoke_requested_animation_frames();
408 graph_->set_tag("late-frame");
410 if (format_desc_.field_mode != core::field_mode::progressive)
412 lock(last_frame_mutex_, [&]
414 last_frame_ = last_progressive_frame_;
417 timer.tick(1.0 / (format_desc_.fps * format_desc_.field_count));
418 invoke_requested_animation_frames();
423 void do_execute_javascript(const std::wstring& javascript)
425 html::begin_invoke([=]
427 if (browser_ != nullptr)
428 browser_->GetMainFrame()->ExecuteJavaScript(u8(javascript).c_str(), browser_->GetMainFrame()->GetURL(), 0);
432 void execute_queued_javascript()
434 std::wstring javascript;
436 while (javascript_before_load_.try_pop(javascript))
437 do_execute_javascript(javascript);
440 std::wstring print() const
442 return L"html[" + url_ + L"]";
445 IMPLEMENT_REFCOUNTING(html_client);
449 : public core::frame_producer_base
451 core::monitor::subject monitor_subject_;
452 const std::wstring url_;
453 core::constraints constraints_;
455 CefRefPtr<html_client> client_;
459 const spl::shared_ptr<core::frame_factory>& frame_factory,
460 const core::video_format_desc& format_desc,
461 const std::wstring& url)
464 constraints_.width.set(format_desc.square_width);
465 constraints_.height.set(format_desc.square_height);
469 client_ = new html_client(frame_factory, format_desc, url_);
471 CefWindowInfo window_info;
473 window_info.SetTransparentPainting(true);
474 window_info.SetAsOffScreen(nullptr);
475 //window_info.SetAsWindowless(nullptr, true);
477 CefBrowserSettings browser_settings;
478 CefBrowserHost::CreateBrowser(window_info, client_.get(), url, browser_settings, nullptr);
490 std::wstring name() const override
495 core::draw_frame receive_impl() override
499 if (client_->is_removed())
502 return core::draw_frame::empty();
505 return client_->receive();
508 return core::draw_frame::empty();
511 std::future<std::wstring> call(const std::vector<std::wstring>& params) override
514 return make_ready_future(std::wstring(L""));
516 auto javascript = params.at(0);
518 client_->execute_javascript(javascript);
520 return make_ready_future(std::wstring(L""));
523 std::wstring print() const override
525 return L"html[" + url_ + L"]";
528 boost::property_tree::wptree info() const override
530 boost::property_tree::wptree info;
531 info.add(L"type", L"html-producer");
535 core::constraints& pixel_constraints() override
540 core::monitor::subject& monitor_output()
542 return monitor_subject_;
546 spl::shared_ptr<core::frame_producer> create_producer(
547 const spl::shared_ptr<core::frame_factory>& frame_factory,
548 const core::video_format_desc& format_desc,
549 const std::vector<std::wstring>& params)
551 const auto filename = env::template_folder() + params.at(0) + L".html";
552 const auto found_filename = find_case_insensitive(filename);
554 if (!found_filename && !boost::iequals(params.at(0), L"[HTML]"))
555 return core::frame_producer::empty();
557 const auto url = found_filename
558 ? L"file://" + *found_filename
561 if (!boost::algorithm::contains(url, ".") || boost::algorithm::ends_with(url, "_A") || boost::algorithm::ends_with(url, "_ALPHA"))
562 return core::frame_producer::empty();
564 return core::create_destroy_proxy(spl::make_shared<html_producer>(