]> git.sesse.net Git - casparcg/blobdiff - modules/html/producer/html_producer.cpp
[html] cosmetics
[casparcg] / modules / html / producer / html_producer.cpp
index 3dbc1c68f600d65066e0e03d1c400023e04868a4..c624a4ebb7cddcebd586963336a68a2da57ad5fb 100644 (file)
 #include <core/producer/frame_producer.h>
 #include <core/mixer/write_frame.h>
 
+#include <common/utility/assert.h>
 #include <common/env.h>
 #include <common/concurrency/executor.h>
 #include <common/concurrency/lock.h>
+#include <common/concurrency/future_util.h>
 #include <common/diagnostics/graph.h>
 #include <common/utility/timer.h>
-
-#include <berkelium/Berkelium.hpp>
-#include <berkelium/Context.hpp>
-#include <berkelium/Window.hpp>
-#include <berkelium/WindowDelegate.hpp>
-#include <berkelium/Rect.hpp>
+#include <common/memory/memcpy.h>
 
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/algorithm/string/trim.hpp>
+#include <boost/algorithm/string/replace.hpp>
 #include <boost/filesystem.hpp>
 #include <boost/format.hpp>
+#include <boost/timer.hpp>
 
 #include <tbb/atomic.h>
-#include <tbb/parallel_for.h>
+#include <tbb/concurrent_queue.h>
+
+#include <cef_task.h>
+#include <cef_app.h>
+#include <cef_client.h>
+#include <cef_render_handler.h>
+
+#include <queue>
 
 #include "html.h"
 
-namespace caspar { namespace html {
+#pragma comment (lib, "libcef.lib")
+#pragma comment (lib, "libcef_dll_wrapper.lib")
+
+namespace caspar {
+       namespace html {
                
-class html_producer 
-       : public core::frame_producer
-       , public Berkelium::WindowDelegate
-{      
-       core::monitor::subject                          monitor_subject_;
-       const std::wstring                                      url_;   
-       safe_ptr<diagnostics::graph>            graph_;
-       
-       const safe_ptr<core::frame_factory>     frame_factory_;
+               class html_client
+                       : public CefClient
+                       , public CefRenderHandler
+                       , public CefLifeSpanHandler
+                       , public CefLoadHandler
+               {
+                       std::wstring                                                    url_;
+                       safe_ptr<diagnostics::graph>                    graph_;
+                       boost::timer                                                    tick_timer_;
+                       boost::timer                                                    frame_timer_;
+                       boost::timer                                                    paint_timer_;
+
+                       safe_ptr<core::frame_factory>                   frame_factory_;
+                       tbb::concurrent_queue<std::wstring>             javascript_before_load_;
+                       tbb::atomic<bool>                                               loaded_;
+                       tbb::atomic<bool>                                               removed_;
+                       tbb::atomic<bool>                                               animation_frame_requested_;
+                       std::queue<safe_ptr<core::basic_frame>> frames_;
+                       mutable boost::mutex                                    frames_mutex_;
+
+                       safe_ptr<core::basic_frame>                             last_frame_;
+                       safe_ptr<core::basic_frame>                             last_progressive_frame_;
+                       mutable boost::mutex                                    last_frame_mutex_;
+
+                       CefRefPtr<CefBrowser>                                   browser_;
+
+                       executor                                                                executor_;
+
+               public:
+
+                       html_client(safe_ptr<core::frame_factory> frame_factory, const std::wstring& url)
+                               : url_(url)
+                               , frame_factory_(frame_factory)
+                               , last_frame_(core::basic_frame::empty())
+                               , last_progressive_frame_(core::basic_frame::empty())
+                               , executor_(L"html_producer")
+                       {
+                               graph_->set_color("browser-tick-time", diagnostics::color(0.1f, 1.0f, 0.1f));
+                               graph_->set_color("tick-time", diagnostics::color(0.0f, 0.6f, 0.9f));
+                               graph_->set_color("late-frame", diagnostics::color(0.6f, 0.3f, 0.9f));
+                               graph_->set_text(print());
+                               diagnostics::register_graph(graph_);
+
+                               loaded_ = false;
+                               removed_ = false;
+                               animation_frame_requested_ = false;
+                               executor_.begin_invoke([&]{ update(); });
+                       }
 
-       safe_ptr<core::basic_frame>                     last_frame_;
-       mutable boost::mutex                            last_frame_mutex_;
-       
-       std::vector<unsigned char>                      frame_;
-       mutable boost::mutex                            frame_mutex_;
+                       safe_ptr<core::basic_frame> receive()
+                       {
+                               auto frame = last_frame();
+                               executor_.begin_invoke([&]{ update(); });
+                               return frame;
+                       }
 
-       tbb::atomic<bool>                                       invalidated_;
+                       safe_ptr<core::basic_frame> last_frame() const
+                       {
+                               return lock(last_frame_mutex_, [&]
+                               {
+                                       return last_frame_;
+                               });
+                       }
 
-       std::unique_ptr<Berkelium::Window>      window_;
+                       void execute_javascript(const std::wstring& javascript)
+                       {
+                               if (!loaded_)
+                               {
+                                       javascript_before_load_.push(javascript);
+                               }
+                               else
+                               {
+                                       execute_queued_javascript();
+                                       do_execute_javascript(javascript);
+                               }
+                       }
 
-       executor                                                        executor_;
-       
-public:
-       html_producer(
-               const safe_ptr<core::frame_factory>& frame_factory, 
-               const std::wstring& url) 
-               : url_(url)             
-               , frame_factory_(frame_factory)
-               , last_frame_(core::basic_frame::empty())
-               , frame_(frame_factory->get_video_format_desc().width * frame_factory->get_video_format_desc().height * 4, 0)
-               , executor_(L"html_producer")
-       {               
-               invalidated_ = true;
-
-               graph_->set_text(print());
-               diagnostics::register_graph(graph_);
-
-               html::invoke([&]
-               {
+                       void close()
                        {
-                               std::unique_ptr<Berkelium::Context> context(Berkelium::Context::create());
-                               window_.reset(Berkelium::Window::create(context.get()));
-                       }
-
-                       window_->resize(
-                               frame_factory->get_video_format_desc().width,
-                               frame_factory->get_video_format_desc().height);
-                       window_->setDelegate(this);
-                       window_->setTransparent(true);
-                       
-                       const auto narrow_url = narrow(url);
-                       
-                       if(!window_->navigateTo(
-                               Berkelium::URLString::point_to(
-                                       narrow_url.data(), 
-                                       narrow_url.length())))
+                               if (!animation_frame_requested_)
+                                       CASPAR_LOG(warning) << print()
+                                                       << " window.requestAnimationFrame() never called. "
+                                                       << "Animations might have been laggy";
+
+                               html::invoke([=]
+                               {
+                                       if (browser_ != nullptr)
+                                       {
+                                               browser_->GetHost()->CloseBrowser(true);
+                                       }
+                               });
+                       }
+
+                       void remove()
                        {
-                               BOOST_THROW_EXCEPTION(caspar_exception() << msg_info("Failed to navigate."));
+                               close();
+                               removed_ = true;
                        }
-               });
 
-               tick();
-       }
+                       bool is_removed() const
+                       {
+                               return removed_;
+                       }
 
-       ~html_producer()
-       {
-               html::invoke([=]
-               {
-                       window_.reset();
-               });
-       }
-       
-       // frame_producer
-               
-       safe_ptr<core::basic_frame> receive(int) override
-       {                               
-               executor_.begin_invoke([this]{ tick(); });
+               private:
 
-               return last_frame();
-       }
+                       bool GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect)
+                       {
+                               CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
 
-       safe_ptr<core::basic_frame> last_frame() const override
-       {               
-               return lock(last_frame_mutex_, [&]
-               {       
-                       return last_frame_;
-               });
-       }               
-       
-       boost::unique_future<std::wstring> call(const std::wstring& param) override
-       {       
-               static const boost::wregex play_exp(L"PLAY\\s*(\\d+)?", boost::regex::icase);
-               static const boost::wregex stop_exp(L"STOP\\s*(\\d+)?", boost::regex::icase);
-               static const boost::wregex next_exp(L"NEXT\\s*(\\d+)?", boost::regex::icase);
-               static const boost::wregex remove_exp(L"REMOVE\\s*(\\d+)?", boost::regex::icase);
-               static const boost::wregex update_exp(L"UPDATE\\s+(\\d+)?\"?(?<VALUE>.*)\"?", boost::regex::icase);
-               static const boost::wregex invoke_exp(L"INVOKE\\s+(\\d+)?\"?(?<VALUE>.*)\"?", boost::regex::icase);
-
-               auto command = [=]
-               {
-                       auto javascript = param;
+                               rect = CefRect(0, 0, frame_factory_->get_video_format_desc().square_width, frame_factory_->get_video_format_desc().square_height);
+                               return true;
+                       }
 
-                       boost::wsmatch what;
+                       void OnPaint(
+                                       CefRefPtr<CefBrowser> browser,
+                                       PaintElementType type,
+                                       const RectList &dirtyRects,
+                                       const void *buffer,
+                                       int width,
+                                       int height)
+                       {
+                               graph_->set_value("browser-tick-time", paint_timer_.elapsed()
+                                               * frame_factory_->get_video_format_desc().fps
+                                               * frame_factory_->get_video_format_desc().field_count
+                                               * 0.5);
+                               paint_timer_.restart();
+                               CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
+
+                               boost::timer copy_timer;
+                               core::pixel_format_desc pixel_desc;
+                                       pixel_desc.pix_fmt = core::pixel_format::bgra;
+                                       pixel_desc.planes.push_back(
+                                               core::pixel_format_desc::plane(width, height, 4));
+                               auto frame = frame_factory_->create_frame(this, pixel_desc);
+                               fast_memcpy(frame->image_data().begin(), buffer, width * height * 4);
+                               frame->commit();
+
+                               lock(frames_mutex_, [&]
+                               {
+                                       frames_.push(frame);
 
-                       if (boost::regex_match(param, what, play_exp))
+                                       size_t max_in_queue = frame_factory_->get_video_format_desc().field_count;
+
+                                       while (frames_.size() > max_in_queue)
+                                       {
+                                               frames_.pop();
+                                               graph_->set_tag("dropped-frame");
+                                       }
+                               });
+                               graph_->set_value("copy-time", copy_timer.elapsed()
+                                               * frame_factory_->get_video_format_desc().fps
+                                               * frame_factory_->get_video_format_desc().field_count
+                                               * 0.5);
+                       }
+
+                       void OnAfterCreated(CefRefPtr<CefBrowser> browser) override
                        {
-                               javascript = (boost::wformat(L"play()")).str();
+                               CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
+
+                               browser_ = browser;
                        }
-                       else if (boost::regex_match(param, what, stop_exp))
+
+                       void OnBeforeClose(CefRefPtr<CefBrowser> browser) override
                        {
-                               javascript = (boost::wformat(L"stop()")).str();
+                               CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
+
+                               browser_ = nullptr;
                        }
-                       else if (boost::regex_match(param, what, next_exp))
+
+                       bool DoClose(CefRefPtr<CefBrowser> browser) override
                        {
-                               javascript = (boost::wformat(L"next()")).str();
+                               CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
+
+                               return false;
                        }
-                       else if (boost::regex_match(param, what, remove_exp))
+
+                       CefRefPtr<CefRenderHandler> GetRenderHandler() override
                        {
-                               javascript = (boost::wformat(L"remove()")).str();
+                               return this;
                        }
-                       else if (boost::regex_match(param, what, update_exp))
+
+                       CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override
                        {
-                               javascript = (boost::wformat(L"update(\"%1%\")") % boost::algorithm::trim_copy_if(what["VALUE"].str(), boost::is_any_of(" \""))).str();
+                               return this;
+                       }
+
+                       CefRefPtr<CefLoadHandler> GetLoadHandler() override {
+                               return this;
                        }
-                       else if (boost::regex_match(param, what, invoke_exp))
+
+                       void OnLoadEnd(
+                                       CefRefPtr<CefBrowser> browser,
+                                       CefRefPtr<CefFrame> frame,
+                                       int httpStatusCode) override
                        {
-                               javascript = (boost::wformat(L"invoke(\"%1%\")") % boost::algorithm::trim_copy_if(what["VALUE"].str(), boost::is_any_of(" \""))).str();
+                               loaded_ = true;
+                               execute_queued_javascript();
                        }
-                                                                                               
-                       window_->executeJavascript(Berkelium::WideString::point_to(javascript.data(), javascript.length()));
-               };
-               
-               boost::packaged_task<std::wstring> task([=]() -> std::wstring
-               {
-                       html::invoke(command);
 
-                       return L"";
-               });
+                       bool OnProcessMessageReceived(
+                                       CefRefPtr<CefBrowser> browser,
+                                       CefProcessId source_process,
+                                       CefRefPtr<CefProcessMessage> message) override
+                       {
+                               auto name = message->GetName().ToString();
 
-               task();
+                               if (name == ANIMATION_FRAME_REQUESTED_MESSAGE_NAME)
+                               {
+                                       CASPAR_LOG(trace)
+                                                       << print() << L" Requested animation frame";
+                                       animation_frame_requested_ = true;
 
-               return task.get_future();
-       }
-               
-       std::wstring print() const override
-       { 
-               return L"html[" + url_ + L"]";          
-       }       
-
-       boost::property_tree::wptree info() const override
-       {
-               boost::property_tree::wptree info;
-               info.add(L"type", L"html-producer");
-               return info;
-       }
-       
-       core::monitor::subject& monitor_output()
-       {
-               return monitor_subject_;
-       }
-       
-       // Berkelium::WindowDelegate
-       
-       void onPaint(
-               Berkelium::Window* wini, 
-               const unsigned char* bitmap_in, 
-               const Berkelium::Rect& bitmap_rect, 
-               size_t num_copy_rects, 
-               const Berkelium::Rect* copy_rects,      
-               int dx, 
-               int dy, 
-               const Berkelium::Rect& scroll_rect) override
-       {               
-               lock(frame_mutex_, [&]
-               {       
-                       invalidated_ = true;
-
-                       tbb::parallel_for<int>(0, num_copy_rects, 1, [&](int i)
-                       {                                                                       
-                               tbb::parallel_for<int>(0, copy_rects[i].height(), 1, [&](int y)
+                                       return true;
+                               }
+                               else if (name == REMOVE_MESSAGE_NAME)
+                               {
+                                       remove();
+
+                                       return true;
+                               }
+                               else if (name == LOG_MESSAGE_NAME)
                                {
-                                       memcpy(
-                                               frame_.data() + ((y + copy_rects[i].top()) * frame_factory_->get_video_format_desc().width + copy_rects[i].left()) * 4,
-                                               bitmap_in + ((y + copy_rects[i].top() - bitmap_rect.top()) * bitmap_rect.width() + copy_rects[i].left() - bitmap_rect.left()) * 4,
-                                               copy_rects[i].width() * 4);
+                                       auto args = message->GetArgumentList();
+                                       auto severity =
+                                               static_cast<log::severity_level>(args->GetInt(0));
+                                       auto msg = args->GetString(1).ToWString();
+
+                                       BOOST_LOG_STREAM_WITH_PARAMS(
+                                                       log::get_logger(),
+                                                       (boost::log::keywords::severity = severity))
+                                               << print() << L" [renderer_process] " << msg;
+                               }
+
+                               return false;
+                       }
+
+                       void invoke_requested_animation_frames()
+                       {
+                               if (browser_)
+                                       browser_->SendProcessMessage(
+                                                       CefProcessId::PID_RENDERER,
+                                                       CefProcessMessage::Create(TICK_MESSAGE_NAME));
+                               graph_->set_value("tick-time", tick_timer_.elapsed()
+                                               * frame_factory_->get_video_format_desc().fps
+                                               * frame_factory_->get_video_format_desc().field_count
+                                               * 0.5);
+                               tick_timer_.restart();
+                       }
+
+                       bool try_pop(safe_ptr<core::basic_frame>& result)
+                       {
+                               return lock(frames_mutex_, [&]() -> bool
+                               {
+                                       if (!frames_.empty())
+                                       {
+                                               result = frames_.front();
+                                               frames_.pop();
+
+                                               return true;
+                                       }
+
+                                       return false;
                                });
-                       });
-               });
-       }
+                       }
 
-    void onExternalHost(
-        Berkelium::Window *win,
-        Berkelium::WideString message,
-        Berkelium::URLString origin,
-        Berkelium::URLString target) override
-       {
-       }
+                       safe_ptr<core::basic_frame> pop()
+                       {
+                               safe_ptr<core::basic_frame> frame;
 
-       // html_producer
+                               if (!try_pop(frame))
+                               {
+                                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + "No frame in buffer"));
+                               }
 
-       void invoke_on_enter_frame()
-       {
-               html::invoke([this]
-               {
-                       static const std::wstring javascript = L"onEnterFrame()";
-                       window_->executeJavascript(Berkelium::WideString::point_to(javascript.data(), javascript.length()));
-               });
-       }
-               
-       safe_ptr<core::basic_frame> draw(
-               safe_ptr<core::write_frame> frame, 
-               core::field_mode::type field_mode)
-       {
-               const auto& pixel_desc = frame->get_pixel_format_desc();
-
-               CASPAR_ASSERT(pixel_desc.pix_fmt == core::pixel_format::bgra);
-                               
-               const auto& height = pixel_desc.planes[0].height;
-               const auto& linesize = pixel_desc.planes[0].linesize;
-
-               invoke_on_enter_frame();
-
-               lock(frame_mutex_, [&]
-               {                               
-                       tbb::parallel_for<int>(
-                               field_mode == core::field_mode::upper ? 0 : 1, 
-                               height,
-                               field_mode == core::field_mode::progressive ? 1 : 2,
-                               [&](int y)
+                               return frame;
+                       }
+
+                       void update()
+                       {
+                               invoke_requested_animation_frames();
+
+                               high_prec_timer timer;
+                               timer.tick(0.0);
+                               const auto& format_desc = frame_factory_->get_video_format_desc();
+
+                               auto num_frames = lock(frames_mutex_, [&]
+                               {
+                                       return frames_.size();
+                               });
+
+                               if (num_frames >= format_desc.field_count)
+                               {
+                                       if (format_desc.field_mode != core::field_mode::progressive)
+                                       {
+                                               auto frame1 = pop();
+
+                                               executor_.yield();
+                                               timer.tick(1.0 / (format_desc.fps * format_desc.field_count));
+                                               invoke_requested_animation_frames();
+
+                                               auto frame2 = pop();
+
+                                               lock(last_frame_mutex_, [&]
+                                               {
+                                                       last_progressive_frame_ = frame2;
+                                                       last_frame_ = core::basic_frame::interlace(frame1, frame2, format_desc.field_mode);
+                                               });
+                                       }
+                                       else
+                                       {
+                                               auto frame = pop();
+
+                                               lock(last_frame_mutex_, [&]
+                                               {
+                                                       last_frame_ = frame;
+                                               });
+                                       }
+                               }
+                               else if (num_frames == 1) // Interlaced but only one frame
+                               {                         // available. Probably the last frame
+                                                         // of some animation sequence.
+                                       auto frame = pop();
+
+                                       lock(last_frame_mutex_, [&]
+                                       {
+                                               last_progressive_frame_ = frame;
+                                               last_frame_ = frame;
+                                       });
+
+                                       timer.tick(1.0 / (format_desc.fps * format_desc.field_count));
+                                       invoke_requested_animation_frames();
+                               }
+                               else
+                               {
+                                       graph_->set_tag("late-frame");
+
+                                       if (format_desc.field_mode != core::field_mode::progressive)
+                                       {
+                                               lock(last_frame_mutex_, [&]
+                                               {
+                                                       last_frame_ = last_progressive_frame_;
+                                               });
+
+                                               timer.tick(1.0 / (format_desc.fps * format_desc.field_count));
+                                               invoke_requested_animation_frames();
+                                       }
+                               }
+                       }
+
+                       void do_execute_javascript(const std::wstring& javascript)
+                       {
+                               html::begin_invoke([=]
+                               {
+                                       if (browser_ != nullptr)
+                                               browser_->GetMainFrame()->ExecuteJavaScript(narrow(javascript).c_str(), browser_->GetMainFrame()->GetURL(), 0);
+                               });
+                       }
+
+                       void execute_queued_javascript()
+                       {
+                               std::wstring javascript;
+
+                               while (javascript_before_load_.try_pop(javascript))
+                                       do_execute_javascript(javascript);
+                       }
+
+                       std::wstring print() const
                        {
-                               memcpy(
-                                       frame->image_data().begin() + y * linesize, 
-                                       frame_.data() + y * linesize,
-                                       linesize);
-                       });
-               });
-                                                       
-               return frame;
-       }       
-
-       void tick()
-       {
-               if(invalidated_.fetch_and_store(false))
+                               return L"html[" + url_ + L"]";
+                       }
+
+                       IMPLEMENT_REFCOUNTING(html_client);
+               };
+
+               class html_producer
+                       : public core::frame_producer
                {
-                       high_prec_timer timer;
-                       timer.tick(0.0);
+                       core::monitor::subject                          monitor_subject_;
+                       const std::wstring                                      url_;
 
-                       core::pixel_format_desc pixel_desc;
-                       pixel_desc.pix_fmt = core::pixel_format::bgra;
-                       pixel_desc.planes.push_back(
-                               core::pixel_format_desc::plane(
-                                       frame_factory_->get_video_format_desc().width, 
-                                       frame_factory_->get_video_format_desc().height,
-                                       4));
+                       CefRefPtr<html_client>                          client_;
 
-                       auto frame = frame_factory_->create_frame(this, pixel_desc);
+               public:
+                       html_producer(
+                               const safe_ptr<core::frame_factory>& frame_factory,
+                               const std::wstring& url)
+                               : url_(url)
+                       {
+                               html::invoke([&]
+                               {
+                                       client_ = new html_client(frame_factory, url_);
 
-                       const auto& format_desc = frame_factory_->get_video_format_desc();
+                                       CefWindowInfo window_info;
+
+                                       window_info.SetTransparentPainting(TRUE);
+                                       window_info.SetAsOffScreen(nullptr);
+                                       //window_info.SetAsWindowless(nullptr, true);
+                                       
+                                       CefBrowserSettings browser_settings;
+                                       CefBrowserHost::CreateBrowser(window_info, client_.get(), url, browser_settings, nullptr);
+                               });
+                       }
 
-                       if (format_desc.field_mode != core::field_mode::progressive)
+                       ~html_producer()
                        {
-                               draw(frame, format_desc.field_mode);
-                               
-                               executor_.yield();
-                               timer.tick(1.0 / (format_desc.fps * format_desc.field_count));
-               
-                               draw(frame, static_cast<core::field_mode::type>(format_desc.field_mode ^ core::field_mode::progressive));
+                               if (client_)
+                                       client_->close();
                        }
-                       else
-                       {                               
-                               draw(frame, format_desc.field_mode);                    
-                       }       
-               
-                       frame->commit();
 
-                       lock(last_frame_mutex_, [&]
-                       {       
-                               last_frame_ = frame;
-                       });
-               }
-       }
-};
+                       // frame_producer
 
-safe_ptr<core::frame_producer> create_producer(
-       const safe_ptr<core::frame_factory>& frame_factory,
-       const core::parameters& params)
-{
-       const auto filename = env::template_folder() + L"\\" + params.at_original(0) + L".html";
+                       safe_ptr<core::basic_frame> receive(int) override
+                       {
+                               if (client_)
+                               {
+                                       if (client_->is_removed())
+                                       {
+                                               client_ = nullptr;
+                                               return core::basic_frame::empty();
+                                       }
+
+                                       return client_->receive();
+                               }
+
+                               return core::basic_frame::empty();
+                       }
+
+                       safe_ptr<core::basic_frame> last_frame() const override
+                       {
+                               return client_
+                                               ? client_->last_frame()
+                                               : core::basic_frame::empty();
+                       }
+
+                       boost::unique_future<std::wstring> call(const std::wstring& param) override
+                       {
+                               static const boost::wregex play_exp(L"PLAY\\s*(\\d+)?", boost::regex::icase);
+                               static const boost::wregex stop_exp(L"STOP\\s*(\\d+)?", boost::regex::icase);
+                               static const boost::wregex next_exp(L"NEXT\\s*(\\d+)?", boost::regex::icase);
+                               static const boost::wregex remove_exp(L"REMOVE\\s*(\\d+)?", boost::regex::icase);
+                               static const boost::wregex update_exp(L"UPDATE\\s+(\\d+)?\"?(?<VALUE>.*)\"?", boost::regex::icase);
+                               static const boost::wregex invoke_exp(L"INVOKE\\s+(\\d+)?\"?(?<VALUE>.*)\"?", boost::regex::icase);
+
+                               if (!client_)
+                                       return wrap_as_future(std::wstring(L""));
+
+                               auto javascript = param;
+
+                               boost::wsmatch what;
+
+                               if (boost::regex_match(param, what, play_exp))
+                               {
+                                       javascript = (boost::wformat(L"play()")).str();
+                               }
+                               else if (boost::regex_match(param, what, stop_exp))
+                               {
+                                       javascript = (boost::wformat(L"stop()")).str();
+                               }
+                               else if (boost::regex_match(param, what, next_exp))
+                               {
+                                       javascript = (boost::wformat(L"next()")).str();
+                               }
+                               else if (boost::regex_match(param, what, remove_exp))
+                               {
+                                       client_->remove();
+                                       return wrap_as_future(std::wstring(L""));
+                               }
+                               else if (boost::regex_match(param, what, update_exp))
+                               {
+                                       javascript = (boost::wformat(L"update(\"%1%\")") % boost::algorithm::replace_all_copy(boost::algorithm::trim_copy_if(what["VALUE"].str(), boost::is_any_of(" \"")), "\"", "\\\"")).str();
+                               }
+                               else if (boost::regex_match(param, what, invoke_exp))
+                               {
+                                       auto function_call = boost::algorithm::trim_copy_if(what["VALUE"].str(), boost::is_any_of(" \""));
+
+                                       // Append empty () if no parameter list has been given
+                                       javascript = boost::ends_with(function_call, ")") ? function_call : function_call + L"()";
+                               }
+
+                               client_->execute_javascript(javascript);
+
+                               return wrap_as_future(std::wstring(L""));
+                       }
+
+                       std::wstring print() const override
+                       {
+                               return L"html[" + url_ + L"]";
+                       }
+
+                       boost::property_tree::wptree info() const override
+                       {
+                               boost::property_tree::wptree info;
+                               info.add(L"type", L"html-producer");
+                               return info;
+                       }
+
+                       core::monitor::subject& monitor_output()
+                       {
+                               return monitor_subject_;
+                       }
+               };
+
+               safe_ptr<core::frame_producer> create_producer(
+                       const safe_ptr<core::frame_factory>& frame_factory,
+                       const core::parameters& params)
+               {
+                       const auto filename = env::template_folder() + L"\\" + params.at_original(0) + L".html";
        
-       if (!boost::filesystem::exists(filename) && params.at(0) != L"[HTML]")
-               return core::frame_producer::empty();
+                       if (!boost::filesystem::exists(filename) && params.at(0) != L"[HTML]")
+                               return core::frame_producer::empty();
 
-       const auto url = boost::filesystem::exists(filename) 
-               ? filename
-               : params.at_original(1);
+                       const auto url = boost::filesystem::exists(filename) 
+                               ? filename
+                               : params.at_original(1);
                
-       if(!boost::algorithm::contains(url, ".") || boost::algorithm::ends_with(url, "_A") || boost::algorithm::ends_with(url, "_ALPHA"))
-               return core::frame_producer::empty();
-
-       return core::create_producer_destroy_proxy(
-               core::create_producer_print_proxy(
-                       make_safe<html_producer>(
-                               frame_factory,
-                               url)));
-}
+                       if(!boost::algorithm::contains(url, ".") || boost::algorithm::ends_with(url, "_A") || boost::algorithm::ends_with(url, "_ALPHA"))
+                               return core::frame_producer::empty();
+
+                       return core::create_producer_destroy_proxy(
+                               core::create_producer_print_proxy(
+                                       make_safe<html_producer>(
+                                               frame_factory,
+                                               url)));
+               }
 
-}}
\ No newline at end of file
+       }
+}
\ No newline at end of file