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