]> git.sesse.net Git - casparcg/blob - modules/html/producer/html_producer.cpp
[general] #598 Removed all usages of asmlib, because it is worse performing than...
[casparcg] / modules / html / producer / html_producer.cpp
1 /*
2 * Copyright 2013 Sveriges Television AB http://casparcg.com/
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
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.
10 *
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.
15 *
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/>.
18 *
19 * Author: Robert Nagy, ronag89@gmail.com
20 */
21
22 #include "html_producer.h"
23
24 #include <core/video_format.h>
25
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>
37
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>
47
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>
53
54 #include <tbb/atomic.h>
55 #include <tbb/concurrent_queue.h>
56
57 #pragma warning(push)
58 #pragma warning(disable: 4458)
59 #include <cef_task.h>
60 #include <cef_app.h>
61 #include <cef_client.h>
62 #include <cef_render_handler.h>
63 #pragma warning(pop)
64
65 #include <queue>
66
67 #include "../html.h"
68
69 #pragma comment (lib, "libcef.lib")
70 #pragma comment (lib, "libcef_dll_wrapper.lib")
71
72 namespace caspar { namespace html {
73
74 class html_client
75         : public CefClient
76         , public CefRenderHandler
77         , public CefLifeSpanHandler
78         , public CefLoadHandler
79 {
80         std::wstring                                                    url_;
81         spl::shared_ptr<diagnostics::graph>             graph_;
82         boost::timer                                                    tick_timer_;
83         boost::timer                                                    frame_timer_;
84         boost::timer                                                    paint_timer_;
85
86         spl::shared_ptr<core::frame_factory>    frame_factory_;
87         core::video_format_desc                                 format_desc_;
88         tbb::concurrent_queue<std::wstring>             javascript_before_load_;
89         tbb::atomic<bool>                                               loaded_;
90         tbb::atomic<bool>                                               removed_;
91         std::queue<core::draw_frame>                    frames_;
92         mutable boost::mutex                                    frames_mutex_;
93
94         core::draw_frame                                                last_frame_;
95         core::draw_frame                                                last_progressive_frame_;
96         mutable boost::mutex                                    last_frame_mutex_;
97
98         CefRefPtr<CefBrowser>                                   browser_;
99
100         executor                                                                executor_;
101
102 public:
103
104         html_client(
105                         spl::shared_ptr<core::frame_factory> frame_factory,
106                         const core::video_format_desc& format_desc,
107                         const std::wstring& url)
108                 : url_(url)
109                 , frame_factory_(std::move(frame_factory))
110                 , format_desc_(format_desc)
111                 , last_frame_(core::draw_frame::empty())
112                 , last_progressive_frame_(core::draw_frame::empty())
113                 , executor_(L"html_producer")
114         {
115                 graph_->set_color("browser-tick-time", diagnostics::color(0.1f, 1.0f, 0.1f));
116                 graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
117                 graph_->set_color("dropped-frame", diagnostics::color(0.3f, 0.6f, 0.3f));
118                 graph_->set_text(print());
119                 diagnostics::register_graph(graph_);
120
121                 loaded_ = false;
122                 removed_ = false;
123                 executor_.begin_invoke([&]{ update(); });
124         }
125
126         core::draw_frame receive()
127         {
128                 auto frame = last_frame();
129                 executor_.begin_invoke([&]{ update(); });
130                 return frame;
131         }
132
133         core::draw_frame last_frame() const
134         {
135                 return lock(last_frame_mutex_, [&]
136                 {
137                         return last_frame_;
138                 });
139         }
140
141         void execute_javascript(const std::wstring& javascript)
142         {
143                 if (!loaded_)
144                 {
145                         javascript_before_load_.push(javascript);
146                 }
147                 else
148                 {
149                         execute_queued_javascript();
150                         do_execute_javascript(javascript);
151                 }
152         }
153
154         CefRefPtr<CefBrowserHost> get_browser_host()
155         {
156                 return browser_->GetHost();
157         }
158
159         void close()
160         {
161                 html::invoke([=]
162                 {
163                         if (browser_ != nullptr)
164                         {
165                                 browser_->GetHost()->CloseBrowser(true);
166                         }
167                 });
168         }
169
170         void remove()
171         {
172                 close();
173                 removed_ = true;
174         }
175
176         bool is_removed() const
177         {
178                 return removed_;
179         }
180
181 private:
182
183         bool GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect)
184         {
185                 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
186
187                 rect = CefRect(0, 0, format_desc_.square_width, format_desc_.square_height);
188                 return true;
189         }
190
191         void OnPaint(
192                         CefRefPtr<CefBrowser> browser,
193                         PaintElementType type,
194                         const RectList &dirtyRects,
195                         const void *buffer,
196                         int width,
197                         int height)
198         {
199                 graph_->set_value("browser-tick-time", paint_timer_.elapsed()
200                                 * format_desc_.fps
201                                 * format_desc_.field_count
202                                 * 0.5);
203                 paint_timer_.restart();
204                 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
205
206                 core::pixel_format_desc pixel_desc;
207                         pixel_desc.format = core::pixel_format::bgra;
208                         pixel_desc.planes.push_back(
209                                 core::pixel_format_desc::plane(width, height, 4));
210                 auto frame = frame_factory_->create_frame(this, pixel_desc, core::audio_channel_layout::invalid());
211                 std::memcpy(frame.image_data().begin(), buffer, width * height * 4);
212
213                 lock(frames_mutex_, [&]
214                 {
215                         frames_.push(core::draw_frame(std::move(frame)));
216
217                         size_t max_in_queue = format_desc_.field_count + 1;
218
219                         while (frames_.size() > max_in_queue)
220                         {
221                                 frames_.pop();
222                                 graph_->set_tag(diagnostics::tag_severity::WARNING, "dropped-frame");
223                         }
224                 });
225         }
226
227         void OnAfterCreated(CefRefPtr<CefBrowser> browser) override
228         {
229                 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
230
231                 browser_ = browser;
232         }
233
234         void OnBeforeClose(CefRefPtr<CefBrowser> browser) override
235         {
236                 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
237
238                 browser_ = nullptr;
239         }
240
241         bool DoClose(CefRefPtr<CefBrowser> browser) override
242         {
243                 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
244
245                 return false;
246         }
247
248         CefRefPtr<CefRenderHandler> GetRenderHandler() override
249         {
250                 return this;
251         }
252
253         CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override
254         {
255                 return this;
256         }
257
258         CefRefPtr<CefLoadHandler> GetLoadHandler() override {
259                 return this;
260         }
261
262         void OnLoadEnd(
263                         CefRefPtr<CefBrowser> browser,
264                         CefRefPtr<CefFrame> frame,
265                         int httpStatusCode) override
266         {
267                 loaded_ = true;
268                 execute_queued_javascript();
269         }
270
271         bool OnProcessMessageReceived(
272                         CefRefPtr<CefBrowser> browser,
273                         CefProcessId source_process,
274                         CefRefPtr<CefProcessMessage> message) override
275         {
276                 auto name = message->GetName().ToString();
277
278                 if (name == REMOVE_MESSAGE_NAME)
279                 {
280                         remove();
281
282                         return true;
283                 }
284                 else if (name == LOG_MESSAGE_NAME)
285                 {
286                         auto args = message->GetArgumentList();
287                         auto severity =
288                                 static_cast<boost::log::trivial::severity_level>(args->GetInt(0));
289                         auto msg = args->GetString(1).ToWString();
290
291                         BOOST_LOG_STREAM_WITH_PARAMS(
292                                         log::logger::get(),
293                                         (boost::log::keywords::severity = severity))
294                                 << print() << L" [renderer_process] " << msg;
295                 }
296
297                 return false;
298         }
299
300         void invoke_requested_animation_frames()
301         {
302                 if (browser_)
303                         browser_->SendProcessMessage(
304                                         CefProcessId::PID_RENDERER,
305                                         CefProcessMessage::Create(TICK_MESSAGE_NAME));
306
307                 graph_->set_value("tick-time", tick_timer_.elapsed()
308                                 * format_desc_.fps
309                                 * format_desc_.field_count
310                                 * 0.5);
311                 tick_timer_.restart();
312         }
313
314         bool try_pop(core::draw_frame& result)
315         {
316                 return lock(frames_mutex_, [&]() -> bool
317                 {
318                         if (!frames_.empty())
319                         {
320                                 result = std::move(frames_.front());
321                                 frames_.pop();
322
323                                 return true;
324                         }
325
326                         return false;
327                 });
328         }
329
330         core::draw_frame pop()
331         {
332                 core::draw_frame frame;
333
334                 if (!try_pop(frame))
335                 {
336                         CASPAR_THROW_EXCEPTION(caspar_exception() << msg_info(u8(print()) + " No frame in buffer"));
337                 }
338
339                 return frame;
340         }
341
342         void update()
343         {
344                 invoke_requested_animation_frames();
345
346                 prec_timer timer;
347                 timer.tick(0.0);
348
349                 auto num_frames = lock(frames_mutex_, [&]
350                 {
351                         return frames_.size();
352                 });
353
354                 if (num_frames >= format_desc_.field_count)
355                 {
356                         if (format_desc_.field_mode != core::field_mode::progressive)
357                         {
358                                 auto frame1 = pop();
359
360                                 executor_.yield(caspar::task_priority::lowest_priority);
361                                 timer.tick(1.0 / (format_desc_.fps * format_desc_.field_count));
362                                 invoke_requested_animation_frames();
363
364                                 auto frame2 = pop();
365
366                                 lock(last_frame_mutex_, [&]
367                                 {
368                                         last_progressive_frame_ = frame2;
369                                         last_frame_ = core::draw_frame::interlace(frame1, frame2, format_desc_.field_mode);
370                                 });
371                         }
372                         else
373                         {
374                                 auto frame = pop();
375
376                                 lock(last_frame_mutex_, [&]
377                                 {
378                                         last_frame_ = frame;
379                                 });
380                         }
381                 }
382                 else if (num_frames == 1) // Interlaced but only one frame
383                 {                         // available. Probably the last frame
384                                           // of some animation sequence.
385                         auto frame = pop();
386
387                         lock(last_frame_mutex_, [&]
388                         {
389                                 last_progressive_frame_ = frame;
390                                 last_frame_ = frame;
391                         });
392
393                         timer.tick(1.0 / (format_desc_.fps * format_desc_.field_count));
394                         invoke_requested_animation_frames();
395                 }
396                 else
397                 {
398                         if (format_desc_.field_mode != core::field_mode::progressive)
399                                 lock(last_frame_mutex_, [&]
400                                 {
401                                         last_frame_ = last_progressive_frame_;
402                                 });
403                 }
404         }
405
406         void do_execute_javascript(const std::wstring& javascript)
407         {
408                 html::begin_invoke([=]
409                 {
410                         if (browser_ != nullptr)
411                                 browser_->GetMainFrame()->ExecuteJavaScript(u8(javascript).c_str(), browser_->GetMainFrame()->GetURL(), 0);
412                 });
413         }
414
415         void execute_queued_javascript()
416         {
417                 std::wstring javascript;
418
419                 while (javascript_before_load_.try_pop(javascript))
420                         do_execute_javascript(javascript);
421         }
422
423         std::wstring print() const
424         {
425                 return L"html[" + url_ + L"]";
426         }
427
428         IMPLEMENT_REFCOUNTING(html_client);
429 };
430
431 class html_producer
432         : public core::frame_producer_base
433 {
434         core::monitor::subject  monitor_subject_;
435         const std::wstring              url_;
436         core::constraints               constraints_;
437
438         CefRefPtr<html_client>  client_;
439
440 public:
441         html_producer(
442                 const spl::shared_ptr<core::frame_factory>& frame_factory,
443                 const core::video_format_desc& format_desc,
444                 const std::wstring& url)
445                 : url_(url)
446         {
447                 constraints_.width.set(format_desc.square_width);
448                 constraints_.height.set(format_desc.square_height);
449
450                 html::invoke([&]
451                 {
452                         client_ = new html_client(frame_factory, format_desc, url_);
453
454                         CefWindowInfo window_info;
455
456                         window_info.SetTransparentPainting(true);
457                         window_info.SetAsOffScreen(nullptr);
458                         //window_info.SetAsWindowless(nullptr, true);
459
460                         CefBrowserSettings browser_settings;
461                         browser_settings.web_security = cef_state_t::STATE_DISABLED;
462                         CefBrowserHost::CreateBrowser(window_info, client_.get(), url, browser_settings, nullptr);
463                 });
464         }
465
466         ~html_producer()
467         {
468                 if (client_)
469                         client_->close();
470         }
471
472         // frame_producer
473
474         std::wstring name() const override
475         {
476                 return L"html";
477         }
478
479         void on_interaction(const core::interaction_event::ptr& event) override
480         {
481                 if (core::is<core::mouse_move_event>(event))
482                 {
483                         auto move = core::as<core::mouse_move_event>(event);
484                         int x = static_cast<int>(move->x * constraints_.width.get());
485                         int y = static_cast<int>(move->y * constraints_.height.get());
486
487                         CefMouseEvent e;
488                         e.x = x;
489                         e.y = y;
490                         client_->get_browser_host()->SendMouseMoveEvent(e, false);
491                 }
492                 else if (core::is<core::mouse_button_event>(event))
493                 {
494                         auto button = core::as<core::mouse_button_event>(event);
495                         int x = static_cast<int>(button->x * constraints_.width.get());
496                         int y = static_cast<int>(button->y * constraints_.height.get());
497
498                         CefMouseEvent e;
499                         e.x = x;
500                         e.y = y;
501                         client_->get_browser_host()->SendMouseClickEvent(
502                                         e,
503                                         static_cast<CefBrowserHost::MouseButtonType>(button->button),
504                                         !button->pressed,
505                                         1);
506                 }
507                 else if (core::is<core::mouse_wheel_event>(event))
508                 {
509                         auto wheel = core::as<core::mouse_wheel_event>(event);
510                         int x = static_cast<int>(wheel->x * constraints_.width.get());
511                         int y = static_cast<int>(wheel->y * constraints_.height.get());
512
513                         CefMouseEvent e;
514                         e.x = x;
515                         e.y = y;
516                         static const int WHEEL_TICKS_AMPLIFICATION = 40;
517                         client_->get_browser_host()->SendMouseWheelEvent(
518                                         e,
519                                         0,                                               // delta_x
520                                         wheel->ticks_delta * WHEEL_TICKS_AMPLIFICATION); // delta_y
521                 }
522         }
523
524         bool collides(double x, double y) const override
525         {
526                 return true;
527         }
528
529         core::draw_frame receive_impl() override
530         {
531                 if (client_)
532                 {
533                         if (client_->is_removed())
534                         {
535                                 client_ = nullptr;
536                                 return core::draw_frame::empty();
537                         }
538
539                         return client_->receive();
540                 }
541
542                 return core::draw_frame::empty();
543         }
544
545         std::future<std::wstring> call(const std::vector<std::wstring>& params) override
546         {
547                 if (!client_)
548                         return make_ready_future(std::wstring(L""));
549
550                 auto javascript = params.at(0);
551
552                 client_->execute_javascript(javascript);
553
554                 return make_ready_future(std::wstring(L""));
555         }
556
557         std::wstring print() const override
558         {
559                 return L"html[" + url_ + L"]";
560         }
561
562         boost::property_tree::wptree info() const override
563         {
564                 boost::property_tree::wptree info;
565                 info.add(L"type", L"html-producer");
566                 return info;
567         }
568
569         core::constraints& pixel_constraints() override
570         {
571                 return constraints_;
572         }
573
574         core::monitor::subject& monitor_output()
575         {
576                 return monitor_subject_;
577         }
578 };
579
580 void describe_producer(core::help_sink& sink, const core::help_repository& repo)
581 {
582         sink.short_description(L"Renders a web page in real time.");
583         sink.syntax(L"{[html_filename:string]},{[HTML] [url:string]}");
584         sink.para()->text(L"Embeds an actual web browser and renders the content in realtime.");
585         sink.para()
586                 ->text(L"HTML content can either be stored locally under the ")->code(L"templates")
587                 ->text(L" folder or fetched directly via an URL. If a .html file is found with the name ")
588                 ->code(L"html_filename")->text(L" under the ")->code(L"templates")->text(L" folder it will be rendered. If the ")
589                 ->code(L"[HTML] url")->text(L" syntax is used instead, the URL will be loaded.");
590         sink.para()->text(L"Examples:");
591         sink.example(L">> PLAY 1-10 [HTML] http://www.casparcg.com");
592         sink.example(L">> PLAY 1-10 folder/html_file");
593 }
594
595 spl::shared_ptr<core::frame_producer> create_producer(
596                 const core::frame_producer_dependencies& dependencies,
597                 const std::vector<std::wstring>& params)
598 {
599         const auto filename                     = env::template_folder() + params.at(0) + L".html";
600         const auto found_filename       = find_case_insensitive(filename);
601         const auto html_prefix          = boost::iequals(params.at(0), L"[HTML]");
602
603         if (!found_filename && !html_prefix)
604                 return core::frame_producer::empty();
605
606         const auto url = found_filename
607                 ? L"file://" + *found_filename
608                 : params.at(1);
609
610         if (!html_prefix && (!boost::algorithm::contains(url, ".") || boost::algorithm::ends_with(url, "_A") || boost::algorithm::ends_with(url, "_ALPHA")))
611                 return core::frame_producer::empty();
612
613         return core::create_destroy_proxy(spl::make_shared<html_producer>(
614                         dependencies.frame_factory,
615                         dependencies.format_desc,
616                         url));
617 }
618
619 }}