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