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