]> git.sesse.net Git - casparcg/blob - modules/html/producer/html_producer.cpp
#222 Modified html_producer so that Caspar ticks the web browser. The javascript...
[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/env.h>
34 #include <common/concurrency/executor.h>
35 #include <common/concurrency/lock.h>
36 #include <common/diagnostics/graph.h>
37 #include <common/utility/timer.h>
38
39 #include <berkelium/Berkelium.hpp>
40 #include <berkelium/Context.hpp>
41 #include <berkelium/Window.hpp>
42 #include <berkelium/WindowDelegate.hpp>
43 #include <berkelium/Rect.hpp>
44
45 #include <boost/algorithm/string/predicate.hpp>
46 #include <boost/algorithm/string/trim.hpp>
47 #include <boost/filesystem.hpp>
48 #include <boost/format.hpp>
49
50 #include <tbb/atomic.h>
51 #include <tbb/parallel_for.h>
52
53 #include "html.h"
54
55 namespace caspar { namespace html {
56                 
57 class html_producer 
58         : public core::frame_producer
59         , public Berkelium::WindowDelegate
60 {       
61         core::monitor::subject                          monitor_subject_;
62         const std::wstring                                      url_;   
63         safe_ptr<diagnostics::graph>            graph_;
64         
65         const safe_ptr<core::frame_factory>     frame_factory_;
66
67         safe_ptr<core::basic_frame>                     last_frame_;
68         mutable boost::mutex                            last_frame_mutex_;
69         
70         std::vector<unsigned char>                      frame_;
71         mutable boost::mutex                            frame_mutex_;
72
73         tbb::atomic<bool>                                       invalidated_;
74
75         std::unique_ptr<Berkelium::Window>      window_;
76
77         executor                                                        executor_;
78         
79 public:
80         html_producer(
81                 const safe_ptr<core::frame_factory>& frame_factory, 
82                 const std::wstring& url) 
83                 : url_(url)             
84                 , frame_factory_(frame_factory)
85                 , last_frame_(core::basic_frame::empty())
86                 , frame_(frame_factory->get_video_format_desc().width * frame_factory->get_video_format_desc().height * 4, 0)
87                 , executor_(L"html_producer")
88         {               
89                 invalidated_ = true;
90
91                 graph_->set_text(print());
92                 diagnostics::register_graph(graph_);
93
94                 html::invoke([&]
95                 {
96                         {
97                                 std::unique_ptr<Berkelium::Context> context(Berkelium::Context::create());
98                                 window_.reset(Berkelium::Window::create(context.get()));
99                         }
100
101                         window_->resize(
102                                 frame_factory->get_video_format_desc().width,
103                                 frame_factory->get_video_format_desc().height);
104                         window_->setDelegate(this);
105                         window_->setTransparent(true);
106                         
107                         const auto narrow_url = narrow(url);
108                         
109                         if(!window_->navigateTo(
110                                 Berkelium::URLString::point_to(
111                                         narrow_url.data(), 
112                                         narrow_url.length())))
113                         {
114                                 BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("Failed to navigate."));
115                         }
116                 });
117
118                 tick();
119         }
120
121         ~html_producer()
122         {
123                 html::invoke([=]
124                 {
125                         window_.reset();
126                 });
127         }
128         
129         // frame_producer
130                 
131         safe_ptr<core::basic_frame> receive(int) override
132         {                               
133                 executor_.begin_invoke([this]{ tick(); });
134
135                 return last_frame();
136         }
137
138         safe_ptr<core::basic_frame> last_frame() const override
139         {               
140                 return lock(last_frame_mutex_, [&]
141                 {       
142                         return last_frame_;
143                 });
144         }               
145         
146         boost::unique_future<std::wstring> call(const std::wstring& param) override
147         {       
148                 static const boost::wregex play_exp(L"PLAY\\s*(\\d+)?", boost::regex::icase);
149                 static const boost::wregex stop_exp(L"STOP\\s*(\\d+)?", boost::regex::icase);
150                 static const boost::wregex next_exp(L"NEXT\\s*(\\d+)?", boost::regex::icase);
151                 static const boost::wregex remove_exp(L"REMOVE\\s*(\\d+)?", boost::regex::icase);
152                 static const boost::wregex update_exp(L"UPDATE\\s+(\\d+)?\"?(?<VALUE>.*)\"?", boost::regex::icase);
153                 static const boost::wregex invoke_exp(L"INVOKE\\s+(\\d+)?\"?(?<VALUE>.*)\"?", boost::regex::icase);
154
155                 auto command = [=]
156                 {
157                         auto javascript = param;
158
159                         boost::wsmatch what;
160
161                         if (boost::regex_match(param, what, play_exp))
162                         {
163                                 javascript = (boost::wformat(L"play()")).str();
164                         }
165                         else if (boost::regex_match(param, what, stop_exp))
166                         {
167                                 javascript = (boost::wformat(L"stop()")).str();
168                         }
169                         else if (boost::regex_match(param, what, next_exp))
170                         {
171                                 javascript = (boost::wformat(L"next()")).str();
172                         }
173                         else if (boost::regex_match(param, what, remove_exp))
174                         {
175                                 javascript = (boost::wformat(L"remove()")).str();
176                         }
177                         else if (boost::regex_match(param, what, update_exp))
178                         {
179                                 javascript = (boost::wformat(L"update(\"%1%\")") % boost::algorithm::trim_copy_if(what["VALUE"].str(), boost::is_any_of(" \""))).str();
180                         }
181                         else if (boost::regex_match(param, what, invoke_exp))
182                         {
183                                 javascript = (boost::wformat(L"invoke(\"%1%\")") % boost::algorithm::trim_copy_if(what["VALUE"].str(), boost::is_any_of(" \""))).str();
184                         }
185                                                                                                 
186                         window_->executeJavascript(Berkelium::WideString::point_to(javascript.data(), javascript.length()));
187                 };
188                 
189                 boost::packaged_task<std::wstring> task([=]() -> std::wstring
190                 {
191                         html::invoke(command);
192
193                         return L"";
194                 });
195
196                 task();
197
198                 return task.get_future();
199         }
200                 
201         std::wstring print() const override
202         { 
203                 return L"html[" + url_ + L"]";          
204         }       
205
206         boost::property_tree::wptree info() const override
207         {
208                 boost::property_tree::wptree info;
209                 info.add(L"type", L"html-producer");
210                 return info;
211         }
212         
213         core::monitor::subject& monitor_output()
214         {
215                 return monitor_subject_;
216         }
217         
218         // Berkelium::WindowDelegate
219         
220         void onPaint(
221                 Berkelium::Window* wini, 
222                 const unsigned char* bitmap_in, 
223                 const Berkelium::Rect& bitmap_rect, 
224                 size_t num_copy_rects, 
225                 const Berkelium::Rect* copy_rects,      
226                 int dx, 
227                 int dy, 
228                 const Berkelium::Rect& scroll_rect) override
229         {               
230                 lock(frame_mutex_, [&]
231                 {       
232                         invalidated_ = true;
233
234                         tbb::parallel_for<int>(0, num_copy_rects, 1, [&](int i)
235                         {                                                                       
236                                 tbb::parallel_for<int>(0, copy_rects[i].height(), 1, [&](int y)
237                                 {
238                                         memcpy(
239                                                 frame_.data() + ((y + copy_rects[i].top()) * frame_factory_->get_video_format_desc().width + copy_rects[i].left()) * 4,
240                                                 bitmap_in + ((y + copy_rects[i].top() - bitmap_rect.top()) * bitmap_rect.width() + copy_rects[i].left() - bitmap_rect.left()) * 4,
241                                                 copy_rects[i].width() * 4);
242                                 });
243                         });
244                 });
245         }
246
247     void onExternalHost(
248         Berkelium::Window *win,
249         Berkelium::WideString message,
250         Berkelium::URLString origin,
251         Berkelium::URLString target) override
252         {
253         }
254
255         // html_producer
256
257         void invoke_on_enter_frame()
258         {
259                 html::invoke([this]
260                 {
261                         static const std::wstring javascript = L"onEnterFrame()";
262                         window_->executeJavascript(Berkelium::WideString::point_to(javascript.data(), javascript.length()));
263                 });
264         }
265                 
266         safe_ptr<core::basic_frame> draw(
267                 safe_ptr<core::write_frame> frame, 
268                 core::field_mode::type field_mode)
269         {
270                 const auto& pixel_desc = frame->get_pixel_format_desc();
271
272                 CASPAR_ASSERT(pixel_desc.pix_fmt == core::pixel_format::bgra);
273                                 
274                 const auto& height = pixel_desc.planes[0].height;
275                 const auto& linesize = pixel_desc.planes[0].linesize;
276
277                 invoke_on_enter_frame();
278
279                 lock(frame_mutex_, [&]
280                 {                               
281                         tbb::parallel_for<int>(
282                                 field_mode == core::field_mode::upper ? 0 : 1, 
283                                 height,
284                                 field_mode == core::field_mode::progressive ? 1 : 2,
285                                 [&](int y)
286                         {
287                                 memcpy(
288                                         frame->image_data().begin() + y * linesize, 
289                                         frame_.data() + y * linesize,
290                                         linesize);
291                         });
292                 });
293                                                         
294                 return frame;
295         }       
296
297         void tick()
298         {
299                 if(invalidated_.fetch_and_store(false))
300                 {
301                         high_prec_timer timer;
302                         timer.tick(0.0);
303
304                         core::pixel_format_desc pixel_desc;
305                         pixel_desc.pix_fmt = core::pixel_format::bgra;
306                         pixel_desc.planes.push_back(
307                                 core::pixel_format_desc::plane(
308                                         frame_factory_->get_video_format_desc().width, 
309                                         frame_factory_->get_video_format_desc().height,
310                                         4));
311
312                         auto frame = frame_factory_->create_frame(this, pixel_desc);
313
314                         const auto& format_desc = frame_factory_->get_video_format_desc();
315
316                         if (format_desc.field_mode != core::field_mode::progressive)
317                         {
318                                 draw(frame, format_desc.field_mode);
319                                 
320                                 executor_.yield();
321                                 timer.tick(1.0 / (format_desc.fps * format_desc.field_count));
322                 
323                                 draw(frame, static_cast<core::field_mode::type>(format_desc.field_mode ^ core::field_mode::progressive));
324                         }
325                         else
326                         {                               
327                                 draw(frame, format_desc.field_mode);                    
328                         }       
329                 
330                         frame->commit();
331
332                         lock(last_frame_mutex_, [&]
333                         {       
334                                 last_frame_ = frame;
335                         });
336                 }
337         }
338 };
339
340 safe_ptr<core::frame_producer> create_producer(
341         const safe_ptr<core::frame_factory>& frame_factory,
342         const core::parameters& params)
343 {
344         const auto filename = env::template_folder() + L"\\" + params.at_original(0) + L".html";
345         
346         const auto url = boost::filesystem::exists(filename) 
347                 ? filename 
348                 : params.at_original(0);
349                 
350         if(!boost::algorithm::contains(url, ".") || boost::algorithm::ends_with(url, "_A") || boost::algorithm::ends_with(url, "_ALPHA"))
351                 return core::frame_producer::empty();
352
353         return core::create_producer_destroy_proxy(
354                 core::create_producer_print_proxy(
355                         make_safe<html_producer>(
356                                 frame_factory,
357                                 url)));
358 }
359
360 }}