]> git.sesse.net Git - casparcg/blob - modules/html/producer/html_producer.cpp
* Manually merged patch in http://casparcg.com/forum/viewtopic.php?f=3&t=2586
[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/diagnostics/graph.h>
38 #include <common/utility/timer.h>
39 #include <common/memory/memcpy.h>
40
41 #include <boost/algorithm/string/predicate.hpp>
42 #include <boost/algorithm/string/trim.hpp>
43 #include <boost/filesystem.hpp>
44 #include <boost/format.hpp>
45
46 #include <tbb/atomic.h>
47 #include <tbb/parallel_for.h>
48
49 #include <cef_task.h>
50 #include <cef_app.h>
51 #include <cef_client.h>
52 #include <cef_render_handler.h>
53
54 #include "html.h"
55
56 #pragma comment (lib, "libcef.lib")
57 #pragma comment (lib, "libcef_dll_wrapper.lib")
58
59 namespace caspar {
60         namespace html {
61                 
62                 class html_client
63                         : public CefClient
64                         , public CefRenderHandler
65                         , public CefLifeSpanHandler
66                 {
67
68                         safe_ptr<core::frame_factory>   frame_factory_;
69                         tbb::atomic<bool>                               invalidated_;
70                         std::vector<unsigned char>              frame_;
71                         mutable boost::mutex                    frame_mutex_;
72
73                         safe_ptr<core::basic_frame>             last_frame_;
74                         mutable boost::mutex                    last_frame_mutex_;
75
76                         CefRefPtr<CefBrowser>                   browser_;
77
78                         executor                                                executor_;
79
80                 public:
81
82                         html_client(safe_ptr<core::frame_factory> frame_factory)
83                                 : frame_factory_(frame_factory)
84                                 , frame_(frame_factory->get_video_format_desc().width * frame_factory->get_video_format_desc().height * 4, 0)
85                                 , last_frame_(core::basic_frame::empty())
86                                 , executor_(L"html_producer")
87                         {
88                                 invalidated_ = true;
89                                 executor_.begin_invoke([&]{ update(); });
90                         }
91
92                         safe_ptr<core::basic_frame> receive(int)
93                         {
94                                 auto frame = last_frame();
95                                 executor_.begin_invoke([&]{ update(); });
96                                 return frame;
97                         }
98
99                         safe_ptr<core::basic_frame> last_frame() const
100                         {
101                                 return lock(last_frame_mutex_, [&]
102                                 {
103                                         return last_frame_;
104                                 });
105                         }
106
107                         void execute_javascript(const std::wstring& javascript)
108                         {
109                                 html::begin_invoke([=]
110                                 {
111                                         if (browser_ != nullptr)
112                                                 browser_->GetMainFrame()->ExecuteJavaScript(narrow(javascript).c_str(), browser_->GetMainFrame()->GetURL(), 0);
113                                 });
114                         }
115
116                         void close()
117                         {
118                                 html::invoke([=]
119                                 {
120                                         if (browser_ != nullptr)
121                                         {
122                                                 browser_->GetHost()->CloseBrowser(true);
123                                         }
124                                 });
125                         }
126
127                 private:
128
129                         bool GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect)
130                         {
131                                 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
132
133                                 rect = CefRect(0, 0, frame_factory_->get_video_format_desc().width, frame_factory_->get_video_format_desc().height);
134                                 return true;
135                         }
136
137                         void OnPaint(CefRefPtr<CefBrowser> browser, PaintElementType type, const RectList &dirtyRects, const void *buffer, int width, int height)
138                         {
139                                 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
140
141                                 lock(frame_mutex_, [&]
142                                 {
143                                         invalidated_ = true;
144                                         fast_memcpy(frame_.data(), buffer, width * height * 4);
145                                 });
146                         }
147
148                         void OnAfterCreated(CefRefPtr<CefBrowser> browser) override
149                         {
150                                 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
151
152                                 browser_ = browser;
153                         }
154
155                         void OnBeforeClose(CefRefPtr<CefBrowser> browser) override
156                         {
157                                 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
158
159                                 browser_ = nullptr;
160                         }
161
162                         bool DoClose(CefRefPtr<CefBrowser> browser) override
163                         {
164                                 CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
165
166                                 return false;
167                         }
168
169                         CefRefPtr<CefRenderHandler> GetRenderHandler() override
170                         {
171                                 return this;
172                         }
173
174                         CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override
175                         {
176                                 return this;
177                         }
178
179                         void invoke_on_enter_frame()
180                         {
181                                 //html::invoke([this]
182                                 //{
183                                 //      static const std::wstring javascript = L"onEnterFrame()";
184                                 //      execute_javascript(javascript);
185                                 //});
186                         }
187
188                         safe_ptr<core::basic_frame> draw(safe_ptr<core::write_frame> frame, core::field_mode::type field_mode)
189                         {
190                                 const auto& pixel_desc = frame->get_pixel_format_desc();
191
192                                 CASPAR_ASSERT(pixel_desc.pix_fmt == core::pixel_format::bgra);
193
194                                 const auto& height = pixel_desc.planes[0].height;
195                                 const auto& linesize = pixel_desc.planes[0].linesize;
196                                 
197                                 lock(frame_mutex_, [&]
198                                 {
199                                         tbb::parallel_for<int>(
200                                                 field_mode == core::field_mode::upper ? 0 : 1,
201                                                 height,
202                                                 field_mode == core::field_mode::progressive ? 1 : 2,
203                                                 [&](int y)
204                                         {
205                                                 fast_memcpy(
206                                                         frame->image_data().begin() + y * linesize,
207                                                         frame_.data() + y * linesize,
208                                                         linesize);
209                                         });
210                                 });
211
212                                 return frame;
213                         }
214
215                         void update()
216                         {
217                                 invoke_on_enter_frame();
218
219                                 if (invalidated_.fetch_and_store(false))
220                                 {
221                                         high_prec_timer timer;
222                                         timer.tick(0.0);
223
224                                         core::pixel_format_desc pixel_desc;
225                                         pixel_desc.pix_fmt = core::pixel_format::bgra;
226                                         pixel_desc.planes.push_back(
227                                                 core::pixel_format_desc::plane(
228                                                 frame_factory_->get_video_format_desc().width,
229                                                 frame_factory_->get_video_format_desc().height,
230                                                 4));
231
232                                         auto frame = frame_factory_->create_frame(this, pixel_desc);
233
234                                         const auto& format_desc = frame_factory_->get_video_format_desc();
235
236                                         if (format_desc.field_mode != core::field_mode::progressive)
237                                         {
238                                                 draw(frame, format_desc.field_mode);
239
240                                                 executor_.yield();
241                                                 timer.tick(1.0 / (format_desc.fps * format_desc.field_count));
242
243                                                 draw(frame, static_cast<core::field_mode::type>(format_desc.field_mode ^ core::field_mode::progressive));
244                                         }
245                                         else
246                                         {
247                                                 draw(frame, format_desc.field_mode);
248                                         }
249
250                                         frame->commit();
251
252                                         lock(last_frame_mutex_, [&]
253                                         {
254                                                 last_frame_ = frame;
255                                         });
256                                 }
257                         }
258
259                         IMPLEMENT_REFCOUNTING(html_client);
260                 };
261
262                 class html_producer
263                         : public core::frame_producer
264                 {
265                         core::monitor::subject                          monitor_subject_;
266                         const std::wstring                                      url_;
267                         safe_ptr<diagnostics::graph>            graph_;
268
269                         CefRefPtr<html_client>                          client_;
270
271                 public:
272                         html_producer(
273                                 const safe_ptr<core::frame_factory>& frame_factory,
274                                 const std::wstring& url)
275                                 : url_(url)
276                         {
277                                 graph_->set_text(print());
278                                 diagnostics::register_graph(graph_);
279
280                                 html::invoke([&]
281                                 {
282                                         client_ = new html_client(frame_factory);
283
284                                         CefWindowInfo window_info;
285
286                                         window_info.SetTransparentPainting(TRUE);
287                                         window_info.SetAsOffScreen(nullptr);
288                                         //window_info.SetAsWindowless(nullptr, true);
289                                         
290                                         CefBrowserSettings browser_settings;    
291                                         CefBrowserHost::CreateBrowser(window_info, client_.get(), url, browser_settings, nullptr);
292                                 });
293                         }
294
295                         ~html_producer()
296                         {
297                                 client_->close();
298                         }
299
300                         // frame_producer
301
302                         safe_ptr<core::basic_frame> receive(int) override
303                         {
304                                 return client_->receive(0);
305                         }
306
307                         safe_ptr<core::basic_frame> last_frame() const override
308                         {
309                                 return client_->last_frame();
310                         }
311
312                         boost::unique_future<std::wstring> call(const std::wstring& param) override
313                         {
314                                 static const boost::wregex play_exp(L"PLAY\\s*(\\d+)?", boost::regex::icase);
315                                 static const boost::wregex stop_exp(L"STOP\\s*(\\d+)?", boost::regex::icase);
316                                 static const boost::wregex next_exp(L"NEXT\\s*(\\d+)?", boost::regex::icase);
317                                 static const boost::wregex remove_exp(L"REMOVE\\s*(\\d+)?", boost::regex::icase);
318                                 static const boost::wregex update_exp(L"UPDATE\\s+(\\d+)?\"?(?<VALUE>.*)\"?", boost::regex::icase);
319                                 static const boost::wregex invoke_exp(L"INVOKE\\s+(\\d+)?\"?(?<VALUE>.*)\"?", boost::regex::icase);
320
321                                 auto command = [=]
322                                 {
323                                         auto javascript = param;
324
325                                         boost::wsmatch what;
326
327                                         if (boost::regex_match(param, what, play_exp))
328                                         {
329                                                 javascript = (boost::wformat(L"play()")).str();
330                                         }
331                                         else if (boost::regex_match(param, what, stop_exp))
332                                         {
333                                                 javascript = (boost::wformat(L"stop()")).str();
334                                         }
335                                         else if (boost::regex_match(param, what, next_exp))
336                                         {
337                                                 javascript = (boost::wformat(L"next()")).str();
338                                         }
339                                         else if (boost::regex_match(param, what, remove_exp))
340                                         {
341                                                 javascript = (boost::wformat(L"remove()")).str();
342                                         }
343                                         else if (boost::regex_match(param, what, update_exp))
344                                         {
345                                                 javascript = (boost::wformat(L"update(\"%1%\")") % boost::algorithm::trim_copy_if(what["VALUE"].str(), boost::is_any_of(" \""))).str();
346                                         }
347                                         else if (boost::regex_match(param, what, invoke_exp))
348                                         {
349                                                 javascript = (boost::wformat(L"invoke(\"%1%\")") % boost::algorithm::trim_copy_if(what["VALUE"].str(), boost::is_any_of(" \""))).str();
350                                         }
351
352                                         client_->execute_javascript(javascript);
353                                 };
354
355                                 boost::packaged_task<std::wstring> task([=]() -> std::wstring
356                                 {
357                                         html::invoke(command);
358                                         return L"";
359                                 });
360
361                                 task();
362
363                                 return task.get_future();
364                         }
365
366                         std::wstring print() const override
367                         {
368                                 return L"html[" + url_ + L"]";
369                         }
370
371                         boost::property_tree::wptree info() const override
372                         {
373                                 boost::property_tree::wptree info;
374                                 info.add(L"type", L"html-producer");
375                                 return info;
376                         }
377
378                         core::monitor::subject& monitor_output()
379                         {
380                                 return monitor_subject_;
381                         }
382                 };
383
384                 safe_ptr<core::frame_producer> create_producer(
385                         const safe_ptr<core::frame_factory>& frame_factory,
386                         const core::parameters& params)
387                 {
388                         const auto filename = env::template_folder() + L"\\" + params.at_original(0) + L".html";
389         
390                         if (!boost::filesystem::exists(filename) && params.at(0) != L"[HTML]")
391                                 return core::frame_producer::empty();
392
393                         const auto url = boost::filesystem::exists(filename) 
394                                 ? filename
395                                 : params.at_original(1);
396                 
397                         if(!boost::algorithm::contains(url, ".") || boost::algorithm::ends_with(url, "_A") || boost::algorithm::ends_with(url, "_ALPHA"))
398                                 return core::frame_producer::empty();
399
400                         return core::create_producer_destroy_proxy(
401                                 core::create_producer_print_proxy(
402                                         make_safe<html_producer>(
403                                                 frame_factory,
404                                                 url)));
405                 }
406
407         }
408 }