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