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