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