]> git.sesse.net Git - casparcg/commitdiff
html producer:
authorHelge Norberg <helge.norberg@gmail.com>
Tue, 26 Aug 2014 13:52:54 +0000 (15:52 +0200)
committerHelge Norberg <helge.norberg@svt.se>
Wed, 27 Aug 2014 09:08:43 +0000 (11:08 +0200)
Implemented a custom version of window.requestAnimationFrame which will follow the pace of the channel, for perfectly smooth animations.
No longer manually interlaces frames, to allow for mixer fill transforms without artifacts.
Now uses CEF3 event loop to avoid 100% CPU core usage.

CHANGES.txt
modules/html/html.cpp
modules/html/html.h
modules/html/html.vcxproj
modules/html/producer/html_producer.cpp
shell/server.cpp
shell/shell.vcxproj

index d9c1ceb42cd8a13fa817a3489fdd41ef8893e4f0..73357f00c7dac1727cb4f2e0af5c354b1757d72c 100644 (file)
@@ -16,11 +16,18 @@ AMCP
 \r
   o Added RESUME command to complement PAUSE. (Peter Keuter)\r
 \r
-Producers\r
----------\r
+HTML producer\r
+-------------\r
 \r
-  o The html_producer no longer tries to play all files with a . in their name.\r
+  o No longer tries to play all files with a . in their name.\r
     (Georgi Chorbadzhiyski)\r
+  o Reimplemented using CEF3 instead of Berkelium, which enables use of WebGL\r
+    and more. CEF3 is actively maintained, which Berkelium is not. (Robert Nagy)\r
+  o Implements a custom version of window.requestAnimationFrame which will\r
+    follow the pace of the channel, for perfectly smooth animations.\r
+  o No longer manually interlaces frames, to allow for mixer fill transforms\r
+    without artifacts.\r
+  o Now uses CEF3 event loop to avoid 100% CPU core usage.\r
 \r
 \r
 \r
index fdff3a447ccfd02c70b92b5f2fa938cf8e35422c..9c54959d30585836ff21e7970b3a6999dd7dcab2 100644 (file)
 #include "producer/html_producer.h"
 
 #include <common/concurrency/executor.h>
+#include <common/concurrency/future_util.h>
 
 #include <boost/thread.hpp>
 #include <boost/asio.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/foreach.hpp>
+#include <boost/timer.hpp>
+#include <boost/range/algorithm/remove_if.hpp>
+#include <boost/thread/future.hpp>
+#include <boost/lexical_cast.hpp>
 
 #include <cef_app.h>
 
-#include <tbb/atomic.h>
-
 #pragma comment(lib, "libcef.lib")
 #pragma comment(lib, "libcef_dll_wrapper.lib")
 
 namespace caspar { namespace html {
 
-tbb::atomic<bool>                g_cef_running;
 std::unique_ptr<executor> g_cef_executor;
 
-void tick()
+void caspar_log(
+               const CefRefPtr<CefBrowser>& browser,
+               log::severity_level level,
+               const std::string& message)
 {
-       if (!g_cef_running)
-               return;
-
-       CefDoMessageLoopWork();
-       g_cef_executor->begin_invoke([&]{ tick(); });
+       if (browser)
+       {
+               auto msg = CefProcessMessage::Create(LOG_MESSAGE_NAME);
+               msg->GetArgumentList()->SetInt(0, level);
+               msg->GetArgumentList()->SetString(1, message);
+               browser->SendProcessMessage(PID_BROWSER, msg);
+       }
 }
 
+class animation_handler : public CefV8Handler
+{
+private:
+       std::vector<CefRefPtr<CefV8Value>> callbacks_;
+       boost::timer since_start_timer_;
+public:
+       CefRefPtr<CefBrowser> browser;
+       std::function<CefRefPtr<CefV8Context>()> get_context;
+
+       bool Execute(
+                       const CefString& name,
+                       CefRefPtr<CefV8Value> object,
+                       const CefV8ValueList& arguments,
+                       CefRefPtr<CefV8Value>& retval,
+                       CefString& exception) override
+       {
+               if (!CefCurrentlyOn(TID_RENDERER))
+                       return false;
+
+               if (arguments.size() < 1 || !arguments.at(0)->IsFunction())
+               {
+                       return false;
+               }
+
+               callbacks_.push_back(arguments.at(0));
+
+               if (browser)
+                       browser->SendProcessMessage(PID_BROWSER, CefProcessMessage::Create(
+                                       ANIMATION_FRAME_REQUESTED_MESSAGE_NAME));
+
+               return true;
+       }
+
+       void tick()
+       {
+               if (!get_context)
+                       return;
+
+               auto context = get_context();
+
+               if (!context)
+                       return;
+
+               if (!CefCurrentlyOn(TID_RENDERER))
+                       return;
+
+               std::vector<CefRefPtr<CefV8Value>> callbacks;
+               callbacks_.swap(callbacks);
+
+               CefV8ValueList callback_args;
+               CefTime timestamp;
+               timestamp.Now();
+               callback_args.push_back(CefV8Value::CreateDouble(
+                               since_start_timer_.elapsed() * 1000.0));
+
+               BOOST_FOREACH(auto callback, callbacks)
+               {
+                       callback->ExecuteFunctionWithContext(
+                                       context, callback, callback_args);
+               }
+       }
+
+       IMPLEMENT_REFCOUNTING(animation_handler);
+};
+
+class remove_handler : public CefV8Handler
+{
+       CefRefPtr<CefBrowser> browser_;
+public:
+       remove_handler(CefRefPtr<CefBrowser> browser)
+               : browser_(browser)
+       {
+       }
+
+       bool Execute(
+                       const CefString& name,
+                       CefRefPtr<CefV8Value> object,
+                       const CefV8ValueList& arguments,
+                       CefRefPtr<CefV8Value>& retval,
+                       CefString& exception) override
+       {
+               if (!CefCurrentlyOn(TID_RENDERER))
+                       return false;
+
+               browser_->SendProcessMessage(
+                               PID_BROWSER,
+                               CefProcessMessage::Create(REMOVE_MESSAGE_NAME));
+
+               return true;
+       }
+
+       IMPLEMENT_REFCOUNTING(remove_handler);
+};
+
+class renderer_application : public CefApp, CefRenderProcessHandler
+{
+       std::vector<std::pair<CefRefPtr<animation_handler>, CefRefPtr<CefV8Context>>> contexts_per_handlers_;
+       //std::map<CefRefPtr<animation_handler>, CefRefPtr<CefV8Context>> contexts_per_handlers_;
+public:
+       CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() override
+       {
+               return this;
+       }
+
+       CefRefPtr<CefV8Context> get_context(
+                       const CefRefPtr<animation_handler>& handler)
+       {
+               BOOST_FOREACH(auto& ctx, contexts_per_handlers_)
+               {
+                       if (ctx.first == handler)
+                               return ctx.second;
+               }
+
+               return nullptr;
+       }
+
+       void OnContextCreated(
+                       CefRefPtr<CefBrowser> browser,
+                       CefRefPtr<CefFrame> frame,
+                       CefRefPtr<CefV8Context> context) override
+       {
+               caspar_log(browser, log::trace,
+                               "context for frame "
+                               + boost::lexical_cast<std::string>(frame->GetIdentifier())
+                               + " created");
+
+               CefRefPtr<animation_handler> handler = new animation_handler;
+               contexts_per_handlers_.push_back(std::make_pair(handler, context));
+               auto handler_ptr = handler.get();
+
+               handler->browser = browser;
+               handler->get_context = [this, handler_ptr]
+               {
+                       return get_context(handler_ptr);
+               };
+
+               auto window = context->GetGlobal();
+
+               auto function = CefV8Value::CreateFunction(
+                               "requestAnimationFrame",
+                               handler.get());
+               window->SetValue(
+                               "requestAnimationFrame",
+                               function,
+                               V8_PROPERTY_ATTRIBUTE_NONE);
+
+               function = CefV8Value::CreateFunction(
+                               "remove",
+                               new remove_handler(browser));
+               window->SetValue(
+                               "remove",
+                               function,
+                               V8_PROPERTY_ATTRIBUTE_NONE);
+       }
+
+       void OnContextReleased(
+                       CefRefPtr<CefBrowser> browser,
+                       CefRefPtr<CefFrame> frame,
+                       CefRefPtr<CefV8Context> context)
+       {
+               auto removed = boost::remove_if(
+                               contexts_per_handlers_, [&](const std::pair<
+                                               CefRefPtr<animation_handler>,
+                                               CefRefPtr<CefV8Context>>& c)
+               {
+                       return c.second->IsSame(context);
+               });
+
+               if (removed != contexts_per_handlers_.end())
+                       caspar_log(browser, log::trace,
+                                       "context for frame "
+                                       + boost::lexical_cast<std::string>(frame->GetIdentifier())
+                                       + " released");
+               else
+                       caspar_log(browser, log::warning,
+                                       "context for frame "
+                                       + boost::lexical_cast<std::string>(frame->GetIdentifier())
+                                       + " released, but not found");
+       }
+
+       void OnBrowserDestroyed(CefRefPtr<CefBrowser> browser) override
+       {
+               contexts_per_handlers_.clear();
+       }
+
+       bool OnProcessMessageReceived(
+                       CefRefPtr<CefBrowser> browser,
+                       CefProcessId source_process,
+                       CefRefPtr<CefProcessMessage> message) override
+       {
+               if (message->GetName().ToString() == TICK_MESSAGE_NAME)
+               {
+                       BOOST_FOREACH(auto& handler, contexts_per_handlers_)
+                       {
+                               handler.first->tick();
+                       }
+
+                       return true;
+               }
+               else
+               {
+                       return false;
+               }
+       }
+
+       IMPLEMENT_REFCOUNTING(renderer_application);
+};
+
 bool init()
 {
        CefMainArgs main_args;
 
-       if (CefExecuteProcess(main_args, nullptr, nullptr) >= 0)
+       if (CefExecuteProcess(main_args, CefRefPtr<CefApp>(new renderer_application), nullptr) >= 0)
                return false;
 
        core::register_producer_factory(html::create_producer);
        
        g_cef_executor.reset(new executor(L"cef"));
-       g_cef_running = true;
-
        g_cef_executor->invoke([&]
        {
                CefSettings settings;
                //settings.windowless_rendering_enabled = true;
                CefInitialize(main_args, settings, nullptr, nullptr);
        });
-
        g_cef_executor->begin_invoke([&]
        {
-               tick();
+               CefRunMessageLoop();
        });
 
        return true;
@@ -79,23 +292,73 @@ bool init()
 
 void uninit()
 {
-       g_cef_executor->invoke([=]
+       invoke([]
+       {
+               CefQuitMessageLoop();
+       });
+       g_cef_executor->begin_invoke([&]
        {
-               g_cef_running = false;
-               g_cef_executor->wait();
                CefShutdown();
        });
 }
 
+class cef_task : public CefTask
+{
+private:
+       boost::promise<void> promise_;
+       std::function<void ()> function_;
+public:
+       cef_task(const std::function<void ()>& function)
+               : function_(function)
+       {
+       }
+
+       void Execute() override
+       {
+               CASPAR_LOG(trace) << "[cef_task] executing task";
+
+               try
+               {
+                       function_();
+                       promise_.set_value();
+                       CASPAR_LOG(trace) << "[cef_task] task succeeded";
+               }
+               catch (...)
+               {
+                       promise_.set_exception(boost::current_exception());
+                       CASPAR_LOG(warning) << "[cef_task] task failed";
+               }
+       }
+
+       boost::unique_future<void> future()
+       {
+               return promise_.get_future();
+       }
+
+       IMPLEMENT_REFCOUNTING(shutdown_task);
+};
 
 void invoke(const std::function<void()>& func)
 {
-       g_cef_executor->invoke(func);
+       begin_invoke(func).get();
 }
 
 
-void begin_invoke(const std::function<void()>& func)
+boost::unique_future<void> begin_invoke(const std::function<void()>& func)
 {
-       g_cef_executor->begin_invoke(func);
+       CefRefPtr<cef_task> task = new cef_task(func);
+
+       if (CefCurrentlyOn(TID_UI))
+       {
+               // Avoid deadlock.
+               task->Execute();
+               return task->future();
+       }
+
+       if (CefPostTask(TID_UI, task.get()))
+               return task->future();
+       else
+               BOOST_THROW_EXCEPTION(caspar_exception()
+                               << msg_info("[cef_executor] Could not post task"));
 }
 }}
index 72d944b8bc30602cf2c6a683d16d45940621d3c5..6faa8f797599a6e8eb75c884c000bb5dd1f796e7 100644 (file)
 #include <functional>
 #include <string>
 
+#include <boost/thread/future.hpp>
+
 namespace caspar { namespace html {
 
+const std::string TICK_MESSAGE_NAME = "CasparCGTick";
+const std::string ANIMATION_FRAME_REQUESTED_MESSAGE_NAME =
+               "CasparCGAnimationFrameRequested";
+const std::string REMOVE_MESSAGE_NAME = "CasparCGRemove";
+const std::string LOG_MESSAGE_NAME = "CasparCGLog";
+
 bool init();
 void uninit();
 void invoke(const std::function<void()>& func);
-void begin_invoke(const std::function<void()>& func);
-}}
\ No newline at end of file
+boost::unique_future<void> begin_invoke(const std::function<void()>& func);
+
+}}
index 206845465567ae7b0bda808f8b74a0490b47e11e..b79fd11a2a8170f838c9ff3ef12f4f448bccdbb5 100644 (file)
   <ItemGroup>
     <ClCompile Include="html.cpp">
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">NotUsing</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Develop|Win32'">NotUsing</PrecompiledHeader>
     </ClCompile>
     <ClCompile Include="producer\html_producer.cpp">
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">../stdafx.h</PrecompiledHeaderFile>
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">../stdafx.h</PrecompiledHeaderFile>
       <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">../stdafx.h</PrecompiledHeaderFile>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">NotUsing</PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Develop|Win32'">NotUsing</PrecompiledHeader>
     </ClCompile>
   </ItemGroup>
   <ItemGroup>
index 37ae29d43bcdd400737f051ee671a2335aa18e8f..c06f48f405eac5a6e972e212874a66ef206444c1 100644 (file)
 #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 <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"
 
 #pragma comment (lib, "libcef.lib")
@@ -63,33 +68,52 @@ namespace caspar {
                        : 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::atomic<bool>                               invalidated_;
-                       std::vector<unsigned char>              frame_;
-                       mutable boost::mutex                    frame_mutex_;
+                       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_;
-                       mutable boost::mutex                    last_frame_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_;
+                       CefRefPtr<CefBrowser>                                   browser_;
 
-                       executor                                                executor_;
+                       executor                                                                executor_;
 
                public:
 
-                       html_client(safe_ptr<core::frame_factory> frame_factory)
-                               : frame_factory_(frame_factory)
-                               , frame_(frame_factory->get_video_format_desc().width * frame_factory->get_video_format_desc().height * 4, 0)
+                       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")
                        {
-                               invalidated_ = true;
+                               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> receive(int)
+                       safe_ptr<core::basic_frame> receive()
                        {
                                auto frame = last_frame();
                                executor_.begin_invoke([&]{ update(); });
@@ -106,15 +130,24 @@ namespace caspar {
 
                        void execute_javascript(const std::wstring& javascript)
                        {
-                               html::begin_invoke([=]
+                               if (!loaded_)
                                {
-                                       if (browser_ != nullptr)
-                                               browser_->GetMainFrame()->ExecuteJavaScript(narrow(javascript).c_str(), browser_->GetMainFrame()->GetURL(), 0);
-                               });
+                                       javascript_before_load_.push(javascript);
+                               }
+                               else
+                               {
+                                       execute_queued_javascript();
+                                       do_execute_javascript(javascript);
+                               }
                        }
 
                        void close()
                        {
+                               if (!animation_frame_requested_)
+                                       CASPAR_LOG(warning) << print()
+                                                       << " window.requestAnimationFrame() never called. "
+                                                       << "Animations might have been laggy";
+
                                html::invoke([=]
                                {
                                        if (browser_ != nullptr)
@@ -124,25 +157,67 @@ namespace caspar {
                                });
                        }
 
+                       void remove()
+                       {
+                               close();
+                               removed_ = true;
+                       }
+
+                       bool is_removed() const
+                       {
+                               return removed_;
+                       }
+
                private:
 
                        bool GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect)
                        {
                                CASPAR_ASSERT(CefCurrentlyOn(TID_UI));
 
-                               rect = CefRect(0, 0, frame_factory_->get_video_format_desc().width, frame_factory_->get_video_format_desc().height);
+                               rect = CefRect(0, 0, frame_factory_->get_video_format_desc().square_width, frame_factory_->get_video_format_desc().square_height);
                                return true;
                        }
 
-                       void OnPaint(CefRefPtr<CefBrowser> browser, PaintElementType type, const RectList &dirtyRects, const void *buffer, int width, int height)
+                       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));
 
-                               lock(frame_mutex_, [&]
+                               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_, [&]
                                {
-                                       invalidated_ = true;
-                                       fast_memcpy(frame_.data(), buffer, width * height * 4);
+                                       frames_.push(frame);
+
+                                       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
@@ -176,38 +251,91 @@ namespace caspar {
                                return this;
                        }
 
-                       void invoke_on_enter_frame()
+                       CefRefPtr<CefLoadHandler> GetLoadHandler() override {
+                               return this;
+                       }
+
+                       void OnLoadEnd(
+                                       CefRefPtr<CefBrowser> browser,
+                                       CefRefPtr<CefFrame> frame,
+                                       int httpStatusCode) override
                        {
-                               //html::invoke([this]
-                               //{
-                               //      static const std::wstring javascript = L"onEnterFrame()";
-                               //      execute_javascript(javascript);
-                               //});
+                               loaded_ = true;
+                               execute_queued_javascript();
                        }
 
-                       safe_ptr<core::basic_frame> draw(safe_ptr<core::write_frame> frame, core::field_mode::type field_mode)
+                       bool OnProcessMessageReceived(
+                                       CefRefPtr<CefBrowser> browser,
+                                       CefProcessId source_process,
+                                       CefRefPtr<CefProcessMessage> message) override
                        {
-                               const auto& pixel_desc = frame->get_pixel_format_desc();
+                               auto name = message->GetName().ToString();
+
+                               if (name == ANIMATION_FRAME_REQUESTED_MESSAGE_NAME)
+                               {
+                                       CASPAR_LOG(trace)
+                                                       << print() << L" Requested animation frame";
+                                       animation_frame_requested_ = true;
+
+                                       return true;
+                               }
+                               else if (name == REMOVE_MESSAGE_NAME)
+                               {
+                                       remove();
+
+                                       return true;
+                               }
+                               else if (name == LOG_MESSAGE_NAME)
+                               {
+                                       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;
+                               }
 
-                               CASPAR_ASSERT(pixel_desc.pix_fmt == core::pixel_format::bgra);
+                               return false;
+                       }
+
+                       void invoke_on_enter_frame()
+                       {
+                               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();
+                       }
 
-                               const auto& height = pixel_desc.planes[0].height;
-                               const auto& linesize = pixel_desc.planes[0].linesize;
-                               
-                               lock(frame_mutex_, [&]
+                       bool try_pop(safe_ptr<core::basic_frame>& result)
+                       {
+                               return lock(frames_mutex_, [&]() -> bool
                                {
-                                       tbb::parallel_for<int>(
-                                               field_mode == core::field_mode::upper ? 0 : 1,
-                                               height,
-                                               field_mode == core::field_mode::progressive ? 1 : 2,
-                                               [&](int y)
+                                       if (!frames_.empty())
                                        {
-                                               fast_memcpy(
-                                                       frame->image_data().begin() + y * linesize,
-                                                       frame_.data() + y * linesize,
-                                                       linesize);
-                                       });
+                                               result = frames_.front();
+                                               frames_.pop();
+
+                                               return true;
+                                       }
+
+                                       return false;
                                });
+                       }
+
+                       safe_ptr<core::basic_frame> pop()
+                       {
+                               safe_ptr<core::basic_frame> frame;
+
+                               if (!try_pop(frame))
+                               {
+                                       BOOST_THROW_EXCEPTION(caspar_exception() << msg_info(narrow(print()) + "No frame in buffer"));
+                               }
 
                                return frame;
                        }
@@ -216,46 +344,82 @@ namespace caspar {
                        {
                                invoke_on_enter_frame();
 
-                               if (invalidated_.fetch_and_store(false))
-                               {
-                                       high_prec_timer timer;
-                                       timer.tick(0.0);
-
-                                       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));
+                               high_prec_timer timer;
+                               timer.tick(0.0);
+                               const auto& format_desc = frame_factory_->get_video_format_desc();
 
-                                       auto frame = frame_factory_->create_frame(this, pixel_desc);
-
-                                       const auto& format_desc = frame_factory_->get_video_format_desc();
+                               bool has_frames = lock(frames_mutex_, [&]
+                               {
+                                       return frames_.size() >= format_desc.field_count;
+                               });
 
+                               if (has_frames)
+                               {
                                        if (format_desc.field_mode != core::field_mode::progressive)
                                        {
-                                               draw(frame, format_desc.field_mode);
+                                               auto frame1 = pop();
 
                                                executor_.yield();
                                                timer.tick(1.0 / (format_desc.fps * format_desc.field_count));
+                                               invoke_on_enter_frame();
+
+                                               auto frame2 = pop();
 
-                                               draw(frame, static_cast<core::field_mode::type>(format_desc.field_mode ^ core::field_mode::progressive));
+                                               lock(last_frame_mutex_, [&]
+                                               {
+                                                       last_progressive_frame_ = frame2;
+                                                       last_frame_ = core::basic_frame::interlace(frame1, frame2, format_desc.field_mode);
+                                               });
                                        }
                                        else
                                        {
-                                               draw(frame, format_desc.field_mode);
-                                       }
+                                               auto frame = pop();
 
-                                       frame->commit();
+                                               lock(last_frame_mutex_, [&]
+                                               {
+                                                       last_frame_ = frame;
+                                               });
+                                       }
+                               }
+                               else
+                               {
+                                       graph_->set_tag("late-frame");
 
-                                       lock(last_frame_mutex_, [&]
+                                       if (format_desc.field_mode != core::field_mode::progressive)
                                        {
-                                               last_frame_ = frame;
-                                       });
+                                               lock(last_frame_mutex_, [&]
+                                               {
+                                                       last_frame_ = last_progressive_frame_;
+                                               });
+
+                                               timer.tick(1.0 / (format_desc.fps * format_desc.field_count));
+                                               invoke_on_enter_frame();
+                                       }
                                }
                        }
 
+                       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
+                       {
+                               return L"html[" + url_ + L"]";
+                       }
+
                        IMPLEMENT_REFCOUNTING(html_client);
                };
 
@@ -264,7 +428,6 @@ namespace caspar {
                {
                        core::monitor::subject                          monitor_subject_;
                        const std::wstring                                      url_;
-                       safe_ptr<diagnostics::graph>            graph_;
 
                        CefRefPtr<html_client>                          client_;
 
@@ -274,12 +437,9 @@ namespace caspar {
                                const std::wstring& url)
                                : url_(url)
                        {
-                               graph_->set_text(print());
-                               diagnostics::register_graph(graph_);
-
                                html::invoke([&]
                                {
-                                       client_ = new html_client(frame_factory);
+                                       client_ = new html_client(frame_factory, url_);
 
                                        CefWindowInfo window_info;
 
@@ -287,26 +447,40 @@ namespace caspar {
                                        window_info.SetAsOffScreen(nullptr);
                                        //window_info.SetAsWindowless(nullptr, true);
                                        
-                                       CefBrowserSettings browser_settings;    
+                                       CefBrowserSettings browser_settings;
                                        CefBrowserHost::CreateBrowser(window_info, client_.get(), url, browser_settings, nullptr);
                                });
                        }
 
                        ~html_producer()
                        {
-                               client_->close();
+                               if (client_)
+                                       client_->close();
                        }
 
                        // frame_producer
 
                        safe_ptr<core::basic_frame> receive(int) override
                        {
-                               return client_->receive(0);
+                               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_->last_frame();
+                               return client_
+                                               ? client_->last_frame()
+                                               : core::basic_frame::empty();
                        }
 
                        boost::unique_future<std::wstring> call(const std::wstring& param) override
@@ -318,49 +492,42 @@ namespace caspar {
                                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;
-
-                                       boost::wsmatch what;
+                               if (!client_)
+                                       return wrap_as_future(std::wstring(L""));
 
-                                       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))
-                                       {
-                                               javascript = (boost::wformat(L"remove()")).str();
-                                       }
-                                       else if (boost::regex_match(param, what, update_exp))
-                                       {
-                                               javascript = (boost::wformat(L"update(\"%1%\")") % boost::algorithm::trim_copy_if(what["VALUE"].str(), boost::is_any_of(" \""))).str();
-                                       }
-                                       else if (boost::regex_match(param, what, invoke_exp))
-                                       {
-                                               javascript = (boost::wformat(L"invoke(\"%1%\")") % boost::algorithm::trim_copy_if(what["VALUE"].str(), boost::is_any_of(" \""))).str();
-                                       }
+                               auto javascript = param;
 
-                                       client_->execute_javascript(javascript);
-                               };
+                               boost::wsmatch what;
 
-                               boost::packaged_task<std::wstring> task([=]() -> std::wstring
+                               if (boost::regex_match(param, what, play_exp))
                                {
-                                       html::invoke(command);
-                                       return L"";
-                               });
+                                       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))
+                               {
+                                       javascript = (boost::wformat(L"%1%()") % boost::algorithm::trim_copy_if(what["VALUE"].str(), boost::is_any_of(" \""))).str();
+                               }
 
-                               task();
+                               client_->execute_javascript(javascript);
 
-                               return task.get_future();
+                               return wrap_as_future(std::wstring(L""));
                        }
 
                        std::wstring print() const override
index bc3bcca2822f603065621d1d08813f10a5b373b8..54ee39936759b46c076179e31bce97ed653cd5d9 100644 (file)
@@ -171,6 +171,7 @@ struct server::implementation : boost::noncopyable
 \r
        ~implementation()\r
        {\r
+               diagnostics::show_graphs(false);\r
                running_ = false;\r
                initial_media_info_thread_.join();\r
                thumbnail_generator_.reset();\r
index 3e889ed0fa6e838b9268320c54f36e4bd594c1df..8bf5b24e9582b83b45e3c8e9a555b26070accb6a 100644 (file)
     <PostBuildEvent>\r
       <Command>copy "$(SolutionDir)dependencies\ffmpeg\bin\*.dll" "$(OutDir)"\r
 copy "$(SolutionDir)dependencies\cef\bin\debug\*.*" "$(OutDir)"\r
+mkdir "$(OutDir)\locales"\r
 copy "$(SolutionDir)dependencies\cef\bin\debug\locales\*.*" "$(OutDir)\locales"\r
 copy "$(SolutionDir)dependencies\FreeImage\Dist\*.dll" "$(OutDir)"\r
 copy "$(SolutionDir)dependencies\glew-1.6.0\bin\*.dll" "$(OutDir)"\r
@@ -271,6 +272,7 @@ copy "$(ProjectDir)casparcg_auto_restart.bat" "$(OutDir)"</Command>
     <PostBuildEvent>\r
       <Command>copy "$(SolutionDir)dependencies\ffmpeg\bin\*.dll" "$(OutDir)"\r
 copy "$(SolutionDir)dependencies\cef\bin\release\*.*" "$(OutDir)"\r
+mkdir "$(OutDir)\locales"\r
 copy "$(SolutionDir)dependencies\cef\bin\release\locales\*.*" "$(OutDir)\locales"\r
 copy "$(SolutionDir)dependencies\FreeImage\Dist\*.dll" "$(OutDir)"\r
 copy "$(SolutionDir)dependencies\glew-1.6.0\bin\*.dll" "$(OutDir)"\r
@@ -311,7 +313,7 @@ copy "$(ProjectDir)casparcg_auto_restart.bat" "$(OutDir)"</Command>
       </Command>\r
     </PreLinkEvent>\r
     <Link>\r
-      <AdditionalDependencies>sfml-system-s.lib;sfml-audio-s.lib;sfml-window-s.lib;sfml-graphics-s.lib;OpenGL32.lib;FreeImage.lib;Winmm.lib;Ws2_32.lib;avformat.lib;avcodec.lib;avdevice.lib;avutil.lib;avfilter.lib;swscale.lib;tbb.lib;glew32.lib;zdll.lib;berkelium.lib</AdditionalDependencies>\r
+      <AdditionalDependencies>sfml-system-s.lib;sfml-audio-s.lib;sfml-window-s.lib;sfml-graphics-s.lib;OpenGL32.lib;FreeImage.lib;Winmm.lib;Ws2_32.lib;avformat.lib;avcodec.lib;avdevice.lib;avutil.lib;avfilter.lib;swscale.lib;swresample.lib;tbb.lib;glew32.lib;zdll.lib</AdditionalDependencies>\r
       <Version>\r
       </Version>\r
       <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>\r
@@ -334,6 +336,7 @@ copy "$(ProjectDir)casparcg_auto_restart.bat" "$(OutDir)"</Command>
     <PostBuildEvent>\r
       <Command>copy "$(SolutionDir)dependencies\ffmpeg\bin\*.dll" "$(OutDir)"\r
 copy "$(SolutionDir)dependencies\cef\bin\release\*.*" "$(OutDir)"\r
+mkdir "$(OutDir)\locales"\r
 copy "$(SolutionDir)dependencies\cef\bin\release\locales\*.*" "$(OutDir)\locales"\r
 copy "$(SolutionDir)dependencies\FreeImage\Dist\*.dll" "$(OutDir)"\r
 copy "$(SolutionDir)dependencies\glew-1.6.0\bin\*.dll" "$(OutDir)"\r
@@ -374,7 +377,7 @@ copy "$(ProjectDir)casparcg_auto_restart.bat" "$(OutDir)"</Command>
       </Command>\r
     </PreLinkEvent>\r
     <Link>\r
-      <AdditionalDependencies>sfml-system-s.lib;sfml-audio-s.lib;sfml-window-s.lib;sfml-graphics-s.lib;OpenGL32.lib;FreeImage.lib;Winmm.lib;Ws2_32.lib;avformat.lib;avcodec.lib;avdevice.lib;avutil.lib;avfilter.lib;swscale.lib;tbb.lib;glew32.lib;zdll.lib;berkelium.lib</AdditionalDependencies>\r
+      <AdditionalDependencies>sfml-system-s.lib;sfml-audio-s.lib;sfml-window-s.lib;sfml-graphics-s.lib;OpenGL32.lib;FreeImage.lib;Winmm.lib;Ws2_32.lib;avformat.lib;avcodec.lib;avdevice.lib;avutil.lib;avfilter.lib;swscale.lib;swresample.lib;tbb.lib;glew32.lib;zdll.lib</AdditionalDependencies>\r
       <Version>\r
       </Version>\r
       <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>\r
@@ -397,7 +400,8 @@ copy "$(ProjectDir)casparcg_auto_restart.bat" "$(OutDir)"</Command>
     <PostBuildEvent>\r
       <Command>copy "$(SolutionDir)dependencies\ffmpeg\bin\*.dll" "$(OutDir)"\r
 copy "$(SolutionDir)dependencies\cef\bin\release\*.*" "$(OutDir)"\r
-copy "$(SolutionDir)dependencies\cef\bin\release\locales\*.*" "$(OutDir)\locales"\r
+mkdir "$(OutDir)\locales"\r
+copy "$(SolutionDir)dependencies\cef\bin\release\locales" "$(OutDir)\locales"\r
 copy "$(SolutionDir)dependencies\FreeImage\Dist\*.dll" "$(OutDir)"\r
 copy "$(SolutionDir)dependencies\glew-1.6.0\bin\*.dll" "$(OutDir)"\r
 copy "$(SolutionDir)dependencies\tbb\bin\ia32\vc10\*.dll" "$(OutDir)"\r