Merge a set of changes which run the OpenGL video updates in a separate v2.15.40
authorCarl Hetherington <cth@carlh.net>
Wed, 8 Jan 2020 21:22:01 +0000 (22:22 +0100)
committerCarl Hetherington <cth@carlh.net>
Wed, 8 Jan 2020 21:22:01 +0000 (22:22 +0100)
thread, hopefully making things more elegant and robust.

31 files changed:
hacks/stress.py [new file with mode: 0644]
src/lib/cross.cc
src/lib/cross.h
src/lib/film.cc
src/lib/film.h
src/lib/playlist.cc
src/lib/playlist.h
src/tools/dcpomatic.cc
src/tools/dcpomatic_player.cc
src/tools/stress [new file with mode: 0755]
src/tools/wscript
src/wx/closed_captions_dialog.cc
src/wx/closed_captions_dialog.h
src/wx/controls.cc
src/wx/controls.h
src/wx/film_viewer.cc
src/wx/film_viewer.h
src/wx/gl_video_view.cc
src/wx/gl_video_view.h
src/wx/simple_video_view.cc
src/wx/simple_video_view.h
src/wx/standard_controls.cc
src/wx/standard_controls.h
src/wx/swaroop_controls.cc
src/wx/swaroop_controls.h
src/wx/timeline.cc
src/wx/timeline.h
src/wx/video_view.cc [new file with mode: 0644]
src/wx/video_view.h
src/wx/wscript
wscript

diff --git a/hacks/stress.py b/hacks/stress.py
new file mode 100644 (file)
index 0000000..46d0638
--- /dev/null
@@ -0,0 +1,22 @@
+#!/usr/bin/python3
+
+import random
+
+def make(dcp, output, seeks):
+       with open(output, 'w') as f:
+               # Open the DCP and start it playing
+               print("O %s" % dcp, file=f)
+               print("P", file=f)
+               for i in range(seeks):
+                       # Wait a bit
+                       print("W %d" % random.randint(500, 60000), file=f)
+                       # Seek
+                       print("K %d" % random.randint(0, 4095), file=f)
+                       # Make sure we're still playing
+                       print("P", file=f)
+               print("S", file=f)
+
+
+make("/home/carl/DCP/Examples/BohemianRhapsody_TLR-7_S_DE-XX_DE_51_2K_TCFG_20180514_TM_IOP_OV/", "boho", 64)
+
+
index 8d82f7a51bcde1a17ea077eb32060ad85a305e38..5d35d5a4bbcb28da70ca7b77f0ceec678c1a826e 100644 (file)
@@ -75,6 +75,17 @@ dcpomatic_sleep_seconds (int s)
 #endif
 }
 
+void
+dcpomatic_sleep_milliseconds (int ms)
+{
+#ifdef DCPOMATIC_POSIX
+       usleep (ms * 1000);
+#endif
+#ifdef DCPOMATIC_WINDOWS
+       Sleep (ms);
+#endif
+}
+
 /** @return A string of CPU information (model name etc.) */
 string
 cpu_info ()
index 67709463ae4163e8ca86b1864618426b412f2d1d..584fa2b426a7f2fab9460205edbd47cd2395bf8f 100644 (file)
@@ -39,6 +39,7 @@ class Log;
 struct AVIOContext;
 
 void dcpomatic_sleep_seconds (int);
+void dcpomatic_sleep_milliseconds (int);
 extern std::string cpu_info ();
 extern void run_ffprobe (boost::filesystem::path, boost::filesystem::path);
 extern std::list<std::pair<std::string, std::string> > mount_info ();
index 2a50e8c81dd732d360cf2f93147c253d355b7942..90b18ea707eb3e20c043160e482b18a8704c9688 100644 (file)
@@ -167,8 +167,9 @@ Film::Film (optional<boost::filesystem::path> dir)
        set_isdcf_date_today ();
 
        _playlist_change_connection = _playlist->Change.connect (bind (&Film::playlist_change, this, _1));
-       _playlist_order_changed_connection = _playlist->OrderChanged.connect (bind (&Film::playlist_order_changed, this));
+       _playlist_order_changed_connection = _playlist->OrderChange.connect (bind (&Film::playlist_order_changed, this));
        _playlist_content_change_connection = _playlist->ContentChange.connect (bind (&Film::playlist_content_change, this, _1, _2, _3, _4));
+       _playlist_length_change_connection = _playlist->LengthChange.connect (bind(&Film::playlist_length_change, this));
 
        if (dir) {
                /* Make state.directory a complete path without ..s (where possible)
@@ -1292,6 +1293,12 @@ Film::playlist_content_change (ChangeType type, weak_ptr<Content> c, int p, bool
        }
 }
 
+void
+Film::playlist_length_change ()
+{
+       LengthChange ();
+}
+
 void
 Film::playlist_change (ChangeType type)
 {
index 68f8b5334a4f1ec9c6eaff5288d56aecae415d54..c722518805855732bda05fcc06c94427997a6cdb 100644 (file)
@@ -390,6 +390,11 @@ public:
        /** Emitted when some property of our content has changed */
        mutable boost::signals2::signal<void (ChangeType, boost::weak_ptr<Content>, int, bool)> ContentChange;
 
+       /** Emitted when the film's length might have changed; this is not like a normal
+           property as its value is derived from the playlist, so it has its own signal.
+       */
+       mutable boost::signals2::signal<void ()> LengthChange;
+
        /** Emitted when we have something important to tell the user */
        boost::signals2::signal<void (std::string)> Message;
 
@@ -409,6 +414,7 @@ private:
        void playlist_change (ChangeType);
        void playlist_order_changed ();
        void playlist_content_change (ChangeType type, boost::weak_ptr<Content>, int, bool frequent);
+       void playlist_length_change ();
        void maybe_add_content (boost::weak_ptr<Job>, boost::weak_ptr<Content>, bool disable_audio_analysis);
        void audio_analysis_finished ();
        void check_settings_consistency ();
@@ -486,6 +492,7 @@ private:
        boost::signals2::scoped_connection _playlist_change_connection;
        boost::signals2::scoped_connection _playlist_order_changed_connection;
        boost::signals2::scoped_connection _playlist_content_change_connection;
+       boost::signals2::scoped_connection _playlist_length_change_connection;
        std::list<boost::signals2::connection> _job_connections;
        std::list<boost::signals2::connection> _audio_analysis_connections;
 
index 9e96c693a529679b197ae66e0ba6c67d8a2fc248..48053bbf485aa893ddd903646ee911baaba05bdc 100644 (file)
@@ -111,8 +111,11 @@ Playlist::content_change (weak_ptr<const Film> weak_film, ChangeType type, weak_
                        }
 
                        if (changed) {
-                               OrderChanged ();
+                               OrderChange ();
                        }
+
+                       /* The length might have changed, and that's good enough for this signal */
+                       LengthChange ();
                }
        }
 
@@ -281,6 +284,8 @@ Playlist::add (shared_ptr<const Film> film, shared_ptr<Content> c)
        }
 
        Change (CHANGE_TYPE_DONE);
+
+       LengthChange ();
 }
 
 void
@@ -312,6 +317,8 @@ Playlist::remove (shared_ptr<Content> c)
        }
 
        /* This won't change order, so it does not need a sort */
+
+       LengthChange ();
 }
 
 void
@@ -334,9 +341,11 @@ Playlist::remove (ContentList c)
                }
        }
 
+       Change (CHANGE_TYPE_DONE);
+
        /* This won't change order, so it does not need a sort */
 
-       Change (CHANGE_TYPE_DONE);
+       LengthChange ();
 }
 
 class FrameRateCandidate
index d7db75d0fb9ed44feccfb7a70a0561780eea5eb6..dc984aacf077d64f78ea37520d9162cbfdbad9f8 100644 (file)
@@ -77,7 +77,9 @@ public:
 
        /** Emitted when content has been added to or removed from the playlist; implies OrderChanged */
        mutable boost::signals2::signal<void (ChangeType)> Change;
-       mutable boost::signals2::signal<void ()> OrderChanged;
+       mutable boost::signals2::signal<void ()> OrderChange;
+       /** Emitted when the length might have changed; may sometimes be emitted when it has not */
+       mutable boost::signals2::signal<void ()> LengthChange;
 
        mutable boost::signals2::signal<void (ChangeType, boost::weak_ptr<Content>, int, bool)> ContentChange;
 
index 68bf20732a0685d79b3e44800a4003c921522093..47851a21874249f67244ed168259c2c47d9ddbb6 100644 (file)
@@ -92,6 +92,9 @@
 #include <wx/preferences.h>
 #include <wx/splash.h>
 #include <wx/wxhtml.h>
+#ifdef __WXGTK__
+#include <X11/Xlib.h>
+#endif
 #ifdef __WXMSW__
 #include <shellapi.h>
 #endif
@@ -1503,7 +1506,11 @@ public:
                : wxApp ()
                , _frame (0)
                , _splash (0)
-       {}
+       {
+#ifdef DCPOMATIC_LINUX
+               XInitThreads ();
+#endif
+       }
 
 private:
 
index f68d0ead27bad93741e5cd54eca261f33baf199a..328093d8a18f6b5891360e90a61b2ee7cc55e3ba 100644 (file)
 #include <wx/preferences.h>
 #include <wx/progdlg.h>
 #include <wx/display.h>
+#ifdef __WXGTK__
+#include <X11/Xlib.h>
+#endif
 #ifdef __WXOSX__
 #include <ApplicationServices/ApplicationServices.h>
 #endif
 #include <boost/bind.hpp>
+#include <boost/algorithm/string.hpp>
 #include <iostream>
 
 #ifdef check
@@ -90,8 +94,61 @@ using boost::optional;
 using boost::dynamic_pointer_cast;
 using boost::thread;
 using boost::bind;
+using dcp::raw_convert;
 using namespace dcpomatic;
 
+#ifdef DCPOMATIC_PLAYER_STRESS_TEST
+#define STRESS_TEST_CHECK_INTERVAL 20
+
+class Command
+{
+public:
+       enum Type {
+               NONE,
+               OPEN,
+               PLAY,
+               WAIT,
+               STOP,
+               SEEK,
+       };
+
+       Command(string line)
+               : type (NONE)
+               , int_param (0)
+       {
+               vector<string> bits;
+               boost::split (bits, line, boost::is_any_of(" "));
+               if (bits[0] == "O") {
+                       if (bits.size() != 2) {
+                               return;
+                       }
+                       type = OPEN;
+                       string_param = bits[1];
+               } else if (bits[0] == "P") {
+                       type = PLAY;
+               } else if (bits[0] == "W") {
+                       if (bits.size() != 2) {
+                               return;
+                       }
+                       type = WAIT;
+                       int_param = raw_convert<int>(bits[1]);
+               } else if (bits[0] == "S") {
+                       type = STOP;
+               } else if (bits[0] == "K") {
+                       if (bits.size() != 2) {
+                               return;
+                       }
+                       type = SEEK;
+                       int_param = raw_convert<int>(bits[1]);
+               }
+       }
+
+       Type type;
+       string string_param;
+       int int_param;
+};
+#endif
+
 enum {
        ID_file_open = 1,
        ID_file_add_ov,
@@ -136,7 +193,10 @@ public:
                , _system_information_dialog (0)
                , _view_full_screen (0)
                , _view_dual_screen (0)
-       {
+#ifdef DCPOMATIC_PLAYER_STRESS_TEST
+               , _timer (this)
+#endif
+{
                dcpomatic_log.reset (new NullLog());
 
 #if defined(DCPOMATIC_WINDOWS)
@@ -233,6 +293,70 @@ public:
 #endif
        }
 
+#ifdef DCPOMATIC_PLAYER_STRESS_TEST
+       void stress (boost::filesystem::path script_file)
+       {
+               Bind (wxEVT_TIMER, boost::bind(&DOMFrame::check_commands, this));
+               _timer.Start(STRESS_TEST_CHECK_INTERVAL);
+               vector<string> lines;
+               string const script = dcp::file_to_string(script_file);
+               boost::split (lines, script, boost::is_any_of("\n"));
+               BOOST_FOREACH (string i, lines) {
+                       _commands.push_back (Command(i));
+               }
+               _current_command = _commands.begin();
+       }
+
+       void check_commands ()
+       {
+               if (_current_command == _commands.end()) {
+                       _timer.Stop ();
+                       cout << "ST: finished.\n";
+                       return;
+               }
+
+               switch (_current_command->type) {
+                       case Command::OPEN:
+                               cout << "ST: load " << _current_command->string_param << "\n";
+                               load_dcp (_current_command->string_param);
+                               ++_current_command;
+                               break;
+                       case Command::PLAY:
+                               cout << "ST: play\n";
+                               _controls->play ();
+                               ++_current_command;
+                               break;
+                       case Command::WAIT:
+                               if (_wait_remaining) {
+                                       _wait_remaining = *_wait_remaining - STRESS_TEST_CHECK_INTERVAL;
+                                       if (_wait_remaining < 0) {
+                                               cout << "ST: wait done.\n";
+                                               _wait_remaining = optional<int>();
+                                               ++_current_command;
+                                       }
+                               } else {
+                                       _wait_remaining = _current_command->int_param;
+                                       cout << "ST: waiting for " << *_wait_remaining << ".\n";
+                               }
+                               break;
+                       case Command::STOP:
+                               cout << "ST: stop\n";
+                               _controls->stop ();
+                               ++_current_command;
+                               break;
+                       case Command::NONE:
+                               ++_current_command;
+                               break;
+                       case Command::SEEK:
+                               /* int_param here is a number between 0 and 4095, corresponding to the possible slider positions */
+                               cout << "ST: seek to " << _current_command->int_param << "\n";
+                               _controls->seek (_current_command->int_param);
+                               ++_current_command;
+                               break;
+               }
+       }
+#endif
+
 #ifdef DCPOMATIC_VARIANT_SWAROOP
        void monitor_checker_state_changed ()
        {
@@ -1001,11 +1125,20 @@ private:
        wxMenuItem* _tools_verify;
        wxMenuItem* _view_full_screen;
        wxMenuItem* _view_dual_screen;
+#ifdef DCPOMATIC_PLAYER_STRESS_TEST
+       wxTimer _timer;
+       list<Command> _commands;
+       list<Command>::const_iterator _current_command;
+       optional<int> _wait_remaining;
+#endif
 };
 
 static const wxCmdLineEntryDesc command_line_description[] = {
        { wxCMD_LINE_PARAM, 0, 0, "DCP to load or create", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
        { wxCMD_LINE_OPTION, "c", "config", "Directory containing config.xml", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
+#ifdef DCPOMATIC_PLAYER_STRESS_TEST
+       { wxCMD_LINE_OPTION, "s", "stress", "File containing description of stress test", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
+#endif
        { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
 };
 
@@ -1044,7 +1177,11 @@ public:
        App ()
                : wxApp ()
                , _frame (0)
-       {}
+       {
+#ifdef DCPOMATIC_LINUX
+               XInitThreads ();
+#endif
+       }
 
 private:
 
@@ -1117,6 +1254,16 @@ private:
                                }
                        }
 
+#ifdef DCPOMATIC_PLAYER_STRESS_TEST
+                       if (_stress) {
+                               try {
+                                       _frame->stress (_stress.get());
+                               } catch (exception& e) {
+                                       error_dialog (0, wxString::Format("Could not load stress test file %s", std_to_wx(*_stress)));
+                               }
+                       }
+#endif
+
                        Bind (wxEVT_IDLE, boost::bind (&App::idle, this));
 
                        if (Config::instance()->check_for_updates ()) {
@@ -1150,6 +1297,12 @@ private:
                if (parser.Found("c", &config)) {
                        Config::override_path = wx_to_std (config);
                }
+#ifdef DCPOMATIC_PLAYER_STRESS_TEST
+               wxString stress;
+               if (parser.Found("s", &stress)) {
+                       _stress = wx_to_std (stress);
+               }
+#endif
 
                return true;
        }
@@ -1210,6 +1363,9 @@ private:
 
        DOMFrame* _frame;
        string _dcp_to_load;
+#ifdef DCPOMATIC_PLAYER_STRESS_TEST
+       boost::optional<string> _stress;
+#endif
 };
 
 IMPLEMENT_APP (App)
diff --git a/src/tools/stress b/src/tools/stress
new file mode 100755 (executable)
index 0000000..f861753
--- /dev/null
@@ -0,0 +1,57 @@
+#!/usr/bin/python3.7
+
+import argparse
+import subprocess
+import sys
+import random
+
+def hms_to_seconds(h):
+    s = h.split(':')
+    assert(1 <= len(s) and len(s) <= 3)
+    if len(s) == 1:
+        return int(h)
+    elif len(s) == 2:
+        return int(s[0]) * 60 + int(s[1])
+    elif len(s) == 3:
+        return ((int(s[0]) * 60 + int(s[1])) * 60) + int(s[2])
+
+def seek(dcp_seconds):
+    print("O %s" % args.dcp)
+    print("P")
+    test_seconds = hms_to_seconds(args.length)
+    while test_seconds > 0:
+        wait = random.randint(500, dcp_seconds * 1000)
+        # Wait some milliseconds
+        print("W %d" % wait)
+        # Seek
+        print("S %d" % random.randint(0, 4095))
+        # Make sure we're stil playing
+        print("P")
+        test_seconds -= wait / 1000 
+
+def repeat(dcp_seconds):
+    print("O %s" % args.dcp)
+    test_seconds = hms_to_seconds(args.length)
+    while test_seconds > 0:
+        print("P")
+        print("W %d" % (dcp_seconds * 1000))
+        test_seconds -= dcp_seconds
+
+parser = argparse.ArgumentParser()
+parser.add_argument('-d', '--dcp', help='DCP to make a script for', required=True)
+parser.add_argument('-t', '--type', help='script type: seek - seek a lot, repeat - play back DCP over and over', required=True)
+parser.add_argument('-l', '--length', help='approximate test length in H:M:S', required=True)
+args = parser.parse_args()
+
+for l in subprocess.run(['dcpinfo', args.dcp], capture_output=True).stdout.splitlines():
+    if l.startswith(b'Total:'):
+        b = l.split(b':')
+        dcp_seconds = (int(b[1]) * 60 + int(b[2])) * 60 + int(b[3])
+if args.type == 'seek':
+    seek(dcp_seconds)
+elif args.type == 'repeat':
+    repeat(dcp_seconds)
+else:
+    print('Unknown type %s' % args.type, file=sys.stderr)
+    sys.exit(1)
+
index 8fd23cfb36b2c028674e614651d26ce293b64186..3b2c0a04ca6eb5b3aaf778563e36a3dcef0f068e 100644 (file)
@@ -68,6 +68,8 @@ def build(bld):
         obj.uselib += ' WXWIDGETS'
         if not bld.env.TARGET_OSX:
             obj.uselib += ' GL GLU'
+        if bld.env.TARGET_LINUX:
+            obj.uselib += ' X11'
         obj.includes = ['..']
         obj.use    = ['libdcpomatic2', 'libdcpomatic2-wx']
         obj.source = '%s.cc' % t
index 061262cddcc96eff127cac8dfcf5b8661e587f77..afcf5fa876f97b35d0cf66bf9765cf379e0afa13 100644 (file)
@@ -53,6 +53,7 @@ ClosedCaptionsDialog::ClosedCaptionsDialog (wxWindow* parent, FilmViewer* viewer
         , _display (new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(640, (640 / 10) + 64)))
        , _track (new wxChoice(this, wxID_ANY))
        , _current_in_lines (false)
+       , _timer (this)
 {
        _lines.resize (CLOSED_CAPTION_LINES);
 
@@ -65,18 +66,30 @@ ClosedCaptionsDialog::ClosedCaptionsDialog (wxWindow* parent, FilmViewer* viewer
        sizer->Add (track_sizer, 0, wxALL, DCPOMATIC_SIZER_GAP);
        sizer->Add (_display, 1, wxEXPAND);
 
+       Bind (wxEVT_SHOW, boost::bind(&ClosedCaptionsDialog::shown, this, _1));
+       Bind (wxEVT_TIMER, boost::bind(&ClosedCaptionsDialog::update, this));
        _display->Bind (wxEVT_PAINT, boost::bind(&ClosedCaptionsDialog::paint, this));
        _track->Bind (wxEVT_CHOICE, boost::bind(&ClosedCaptionsDialog::track_selected, this));
 
        SetSizerAndFit (sizer);
 }
 
+void
+ClosedCaptionsDialog::shown (wxShowEvent ev)
+{
+       if (ev.IsShown ()) {
+               _timer.Start (40);
+       } else {
+               _timer.Stop ();
+       }
+}
+
 void
 ClosedCaptionsDialog::track_selected ()
 {
        _current = optional<TextRingBuffers::Data> ();
        _viewer->slow_refresh ();
-       update (_last_update);
+       update ();
 }
 
 void
@@ -131,9 +144,9 @@ private:
 };
 
 void
-ClosedCaptionsDialog::update (DCPTime time)
+ClosedCaptionsDialog::update ()
 {
-       _last_update = time;
+       DCPTime const time = _viewer->time ();
 
        if (_current_in_lines && _current && _current->period.to > time) {
                /* Current one is fine */
index fb4e9d59b40c5569e90a02c5013f28bd2a051531..5c366ca7b0dbe2cc9d9f61e8eba4002b5e71c6ff 100644 (file)
@@ -31,11 +31,12 @@ class ClosedCaptionsDialog : public wxDialog
 public:
        explicit ClosedCaptionsDialog (wxWindow* parent, FilmViewer* viewer);
 
-       void update (dcpomatic::DCPTime);
        void clear ();
        void set_film_and_butler (boost::shared_ptr<Film>, boost::weak_ptr<Butler>);
 
 private:
+       void shown (wxShowEvent);
+       void update ();
        void paint ();
        void track_selected ();
 
@@ -47,5 +48,5 @@ private:
        std::vector<wxString> _lines;
        std::vector<DCPTextTrack> _tracks;
        boost::weak_ptr<Butler> _butler;
-       dcpomatic::DCPTime _last_update;
+       wxTimer _timer;
 };
index b173b43ff6c68ac6bc9817414a7cb55baf112eb5..27139f1f5a8608b2b6c63c71e711863033373a34 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2018 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2018-2019 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
@@ -67,6 +67,7 @@ Controls::Controls (wxWindow* parent, shared_ptr<FilmViewer> viewer, bool editor
        , _forward_button (new Button (this, wxT(">")))
        , _frame_number (new StaticText (this, wxT("")))
        , _timecode (new StaticText (this, wxT("")))
+       , _timer (this)
 {
        _v_sizer = new wxBoxSizer (wxVERTICAL);
        SetSizer (_v_sizer);
@@ -119,14 +120,7 @@ Controls::Controls (wxWindow* parent, shared_ptr<FilmViewer> viewer, bool editor
        _slider->Bind           (wxEVT_SCROLL_THUMBTRACK,    boost::bind(&Controls::slider_moved,    this, false));
        _slider->Bind           (wxEVT_SCROLL_PAGEUP,        boost::bind(&Controls::slider_moved,    this, true));
        _slider->Bind           (wxEVT_SCROLL_PAGEDOWN,      boost::bind(&Controls::slider_moved,    this, true));
-       _slider->Bind           (wxEVT_SCROLL_CHANGED,       boost::bind(&Controls::slider_released, this));
-#ifdef DCPOMATIC_OSX
-       /* _CHANGED is not received on OS X (at least, not when the
-          slider is dragged), so use this instead.  Perhaps all
-          platforms could just use _THUMBRELEASE.
-       */
        _slider->Bind           (wxEVT_SCROLL_THUMBRELEASE,  boost::bind(&Controls::slider_released, this));
-#endif
        _rewind_button->Bind    (wxEVT_LEFT_DOWN,            boost::bind(&Controls::rewind_clicked,  this, _1));
        _back_button->Bind      (wxEVT_LEFT_DOWN,            boost::bind(&Controls::back_clicked,    this, _1));
        _forward_button->Bind   (wxEVT_LEFT_DOWN,            boost::bind(&Controls::forward_clicked, this, _1));
@@ -137,10 +131,12 @@ Controls::Controls (wxWindow* parent, shared_ptr<FilmViewer> viewer, bool editor
                _jump_to_selected->SetValue (Config::instance()->jump_to_selected ());
        }
 
-       _viewer->PositionChanged.connect (boost::bind(&Controls::position_changed, this));
        _viewer->Started.connect (boost::bind(&Controls::started, this));
        _viewer->Stopped.connect (boost::bind(&Controls::stopped, this));
 
+       Bind (wxEVT_TIMER, boost::bind(&Controls::update_position, this));
+       _timer.Start (80, wxTIMER_CONTINUOUS);
+
        set_film (_viewer->film());
 
        setup_sensitivity ();
@@ -172,7 +168,7 @@ Controls::stopped ()
 }
 
 void
-Controls::position_changed ()
+Controls::update_position ()
 {
        if (!_slider_being_moved) {
                update_position_label ();
@@ -410,3 +406,13 @@ Controls::film_change (ChangeType type, Film::Property p)
                }
        }
 }
+
+#ifdef DCPOMATIC_PLAYER_STRESS_TEST
+void
+Controls::seek (int slider)
+{
+       _slider->SetValue (slider);
+       slider_moved (false);
+       slider_released ();
+}
+#endif
index dfa11e6d74ecc67e8526d9f4c8f43760bfe7d4c6..1b6a379cce5a8fc6db9e8b1fadb0b3c373660a76 100644 (file)
@@ -53,6 +53,11 @@ public:
 
        virtual void log (wxString) {}
        virtual void set_film (boost::shared_ptr<Film> film);
+#ifdef DCPOMATIC_PLAYER_STRESS_TEST
+       virtual void play () {};
+       virtual void stop () {};
+       void seek (int slider);
+#endif
        boost::shared_ptr<Film> film () const;
        void back_frame ();
        void forward_frame ();
@@ -87,7 +92,7 @@ private:
        void image_changed (boost::weak_ptr<PlayerVideo>);
        void outline_content_changed ();
        void eye_changed ();
-       void position_changed ();
+       void update_position ();
        void film_change (ChangeType, Film::Property);
 
        typedef std::pair<boost::shared_ptr<dcp::CPL>, boost::filesystem::path> CPL;
@@ -105,6 +110,8 @@ private:
 
        ClosedCaptionsDialog* _closed_captions_dialog;
 
+       wxTimer _timer;
+
        boost::signals2::scoped_connection _film_change_connection;
        boost::signals2::scoped_connection _config_changed_connection;
 };
index 509cc9426dd279ed14e960f6b12a8a4f5f2263dc..9c3a9c81e0ca8d5b11a10c4ac7cbda386b655c73 100644 (file)
@@ -86,16 +86,12 @@ FilmViewer::FilmViewer (wxWindow* p)
        , _playing (false)
        , _suspended (0)
        , _latency_history_count (0)
-       , _dropped (0)
        , _closed_captions_dialog (new ClosedCaptionsDialog(p, this))
        , _outline_content (false)
-       , _eyes (EYES_LEFT)
        , _pad_black (false)
 #ifdef DCPOMATIC_VARIANT_SWAROOP
        , _background_image (false)
 #endif
-       , _state_timer ("viewer")
-       , _gets (0)
        , _idle_get (false)
 {
        switch (Config::instance()->video_view_type()) {
@@ -108,7 +104,6 @@ FilmViewer::FilmViewer (wxWindow* p)
        }
 
        _video_view->Sized.connect (boost::bind(&FilmViewer::video_view_sized, this));
-       _timer.Bind (wxEVT_TIMER, boost::bind(&FilmViewer::timer, this));
 
        set_film (shared_ptr<Film> ());
 
@@ -123,7 +118,7 @@ FilmViewer::~FilmViewer ()
 
 /** Ask for ::get() to be called next time we are idle */
 void
-FilmViewer::request_idle_get ()
+FilmViewer::request_idle_display_next_frame ()
 {
        if (_idle_get) {
                return;
@@ -141,7 +136,7 @@ FilmViewer::idle_handler ()
                return;
        }
 
-       if (get(true)) {
+       if (_video_view->display_next_frame(true)) {
                _idle_get = false;
        } else {
                /* get() could not complete quickly so we'll try again later */
@@ -157,17 +152,14 @@ FilmViewer::set_film (shared_ptr<Film> film)
        }
 
        _film = film;
-       _video_position = DCPTime ();
-       _player_video.first.reset ();
-       _player_video.second = DCPTime ();
 
-       _video_view->set_image (shared_ptr<Image>());
+       _video_view->clear ();
        _closed_captions_dialog->clear ();
 
        if (!_film) {
                _player.reset ();
                recreate_butler ();
-               refresh_view ();
+               _video_view->update ();
                return;
        }
 
@@ -187,8 +179,13 @@ FilmViewer::set_film (shared_ptr<Film> film)
        _player->set_play_referenced ();
 
        _film->Change.connect (boost::bind (&FilmViewer::film_change, this, _1, _2));
+       _film->LengthChange.connect (boost::bind(&FilmViewer::film_length_change, this));
        _player->Change.connect (boost::bind (&FilmViewer::player_change, this, _1, _2, _3));
 
+       film_change (CHANGE_TYPE_DONE, Film::VIDEO_FRAME_RATE);
+       film_change (CHANGE_TYPE_DONE, Film::THREE_D);
+       film_length_change ();
+
        /* Keep about 1 second's worth of history samples */
        _latency_history_count = _film->audio_frame_rate() / _audio_block_size;
 
@@ -229,147 +226,17 @@ FilmViewer::recreate_butler ()
        resume ();
 }
 
-void
-FilmViewer::refresh_view ()
-{
-       _state_timer.set ("update-view");
-       _video_view->update ();
-       _state_timer.unset ();
-}
-
-/** Try to get a frame from the butler and display it.
- *  @param lazy true to return false quickly if no video is available quickly (i.e. we are waiting for the butler).
- *  false to ask the butler to block until it has video (unless it is suspended).
- *  @return true on success, false if we did nothing because it would have taken too long.
- */
-bool
-FilmViewer::get (bool lazy)
-{
-       DCPOMATIC_ASSERT (_butler);
-       ++_gets;
-
-       do {
-               Butler::Error e;
-               _player_video = _butler->get_video (!lazy, &e);
-               if (!_player_video.first && e == Butler::AGAIN) {
-                       if (lazy) {
-                               /* No video available; return saying we failed */
-                               return false;
-                       } else {
-                               /* Player was suspended; come back later */
-                               signal_manager->when_idle (boost::bind(&FilmViewer::get, this, false));
-                               return false;
-                       }
-               }
-       } while (
-               _player_video.first &&
-               _film->three_d() &&
-               _eyes != _player_video.first->eyes() &&
-               _player_video.first->eyes() != EYES_BOTH
-               );
-
-       try {
-               _butler->rethrow ();
-       } catch (DecodeError& e) {
-               error_dialog (_video_view->get(), e.what());
-       }
-
-       display_player_video ();
-       PositionChanged ();
-
-       return true;
-}
-
-void
-FilmViewer::display_player_video ()
-{
-       if (!_player_video.first) {
-               _video_view->set_image (shared_ptr<Image>());
-               refresh_view ();
-               return;
-       }
-
-       if (_playing && !_suspended && (time() - _player_video.second) > one_video_frame()) {
-               /* Too late; just drop this frame before we try to get its image (which will be the time-consuming
-                  part if this frame is J2K).
-               */
-               _video_position = _player_video.second;
-               ++_dropped;
-               return;
-       }
-
-       /* In an ideal world, what we would do here is:
-        *
-        * 1. convert to XYZ exactly as we do in the DCP creation path.
-        * 2. convert back to RGB for the preview display, compensating
-        *    for the monitor etc. etc.
-        *
-        * but this is inefficient if the source is RGB.  Since we don't
-        * (currently) care too much about the precise accuracy of the preview's
-        * colour mapping (and we care more about its speed) we try to short-
-        * circuit this "ideal" situation in some cases.
-        *
-        * The content's specified colour conversion indicates the colourspace
-        * which the content is in (according to the user).
-        *
-        * PlayerVideo::image (bound to PlayerVideo::force) will take the source
-        * image and convert it (from whatever the user has said it is) to RGB.
-        */
-
-       _state_timer.set ("get image");
-
-       _video_view->set_image (
-               _player_video.first->image(bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24), false, true)
-               );
-
-       _state_timer.set ("ImageChanged");
-       ImageChanged (_player_video.first);
-       _state_timer.unset ();
-
-       _video_position = _player_video.second;
-       _inter_position = _player_video.first->inter_position ();
-       _inter_size = _player_video.first->inter_size ();
-
-       refresh_view ();
-
-       _closed_captions_dialog->update (time());
-}
-
-void
-FilmViewer::timer ()
-{
-       if (!_film || !_playing || _suspended) {
-               return;
-       }
-
-       get (false);
-       DCPTime const next = _video_position + one_video_frame();
-
-       if (next >= _film->length()) {
-               stop ();
-               Finished ();
-               return;
-       }
-
-       LOG_DEBUG_PLAYER("%1 -> %2; delay %3", next.seconds(), time().seconds(), max((next.seconds() - time().seconds()) * 1000, 1.0));
-       _timer.Start (max ((next.seconds() - time().seconds()) * 1000, 1.0), wxTIMER_ONE_SHOT);
-
-       if (_butler) {
-               _butler->rethrow ();
-       }
-}
-
 void
 FilmViewer::set_outline_content (bool o)
 {
        _outline_content = o;
-       refresh_view ();
+       _video_view->update ();
 }
 
 void
 FilmViewer::set_eyes (Eyes e)
 {
-       _eyes = e;
+       _video_view->set_eyes (e);
        slow_refresh ();
 }
 
@@ -380,7 +247,6 @@ FilmViewer::video_view_sized ()
        if (!quick_refresh()) {
                slow_refresh ();
        }
-       PositionChanged ();
 }
 
 void
@@ -424,13 +290,14 @@ FilmViewer::suspend ()
 void
 FilmViewer::resume ()
 {
+       DCPOMATIC_ASSERT (_suspended > 0);
        --_suspended;
        if (_playing && !_suspended) {
                if (_audio.isStreamOpen()) {
-                       _audio.setStreamTime (_video_position.seconds());
+                       _audio.setStreamTime (_video_view->position().seconds());
                        _audio.startStream ();
                }
-               timer ();
+               _video_view->start ();
        }
 }
 
@@ -447,14 +314,24 @@ FilmViewer::start ()
                return;
        }
 
+       /* We are about to set up the audio stream from the position of the video view.
+          If there is `lazy' seek in progress we need to wait for it to go through so that
+          _video_view->position() gives us a sensible answer.
+        */
+       while (_idle_get) {
+               idle_handler ();
+       }
+
+       /* Take the video view's idea of position as our `playhead' and start the
+          audio stream (which is the timing reference) there.
+         */
        if (_audio.isStreamOpen()) {
-               _audio.setStreamTime (_video_position.seconds());
+               _audio.setStreamTime (_video_view->position().seconds());
                _audio.startStream ();
        }
 
        _playing = true;
-       _dropped = 0;
-       timer ();
+       _video_view->start ();
        Started (position());
 }
 
@@ -471,7 +348,10 @@ FilmViewer::stop ()
        }
 
        _playing = false;
+       _video_view->stop ();
        Stopped (position());
+
+       _video_view->rethrow ();
        return true;
 }
 
@@ -504,22 +384,35 @@ FilmViewer::player_change (ChangeType type, int property, bool frequent)
        if (!refreshed) {
                slow_refresh ();
        }
-       PositionChanged ();
 }
 
 void
 FilmViewer::film_change (ChangeType type, Film::Property p)
 {
-       if (type == CHANGE_TYPE_DONE && p == Film::AUDIO_CHANNELS) {
+       if (type != CHANGE_TYPE_DONE) {
+               return;
+       }
+
+       if (p == Film::AUDIO_CHANNELS) {
                recreate_butler ();
+       } else if (p == Film::VIDEO_FRAME_RATE) {
+               _video_view->set_video_frame_rate (_film->video_frame_rate());
+       } else if (p == Film::THREE_D) {
+               _video_view->set_three_d (_film->three_d());
        }
 }
 
+void
+FilmViewer::film_length_change ()
+{
+       _video_view->set_length (_film->length());
+}
+
 /** Re-get the current frame slowly by seeking */
 void
 FilmViewer::slow_refresh ()
 {
-       seek (_video_position, true);
+       seek (_video_view->position(), true);
 }
 
 /** Try to re-get the current frame quickly by resetting the metadata
@@ -529,16 +422,10 @@ FilmViewer::slow_refresh ()
 bool
 FilmViewer::quick_refresh ()
 {
-       if (!_player_video.first) {
-               return false;
-       }
-
-       if (!_player_video.first->reset_metadata (_film, _player->video_container_size(), _film->frame_size())) {
-               return false;
+       if (!_video_view || !_film) {
+               return true;
        }
-
-       display_player_video ();
-       return true;
+       return _video_view->refresh_metadata (_film, _player->video_container_size(), _film->frame_size());
 }
 
 void
@@ -584,10 +471,15 @@ FilmViewer::seek (DCPTime t, bool accurate)
        _butler->seek (t, accurate);
 
        if (!_playing) {
-               request_idle_get ();
+               /* We're not playing, so let the GUI thread get on and
+                  come back later to get the next frame after the seek.
+               */
+               request_idle_display_next_frame ();
        } else {
-               /* Make sure we get a frame so that _video_position is set up before we resume */
-               while (!get(true)) {}
+               /* We're going to start playing again straight away
+                  so wait for the seek to finish.
+               */
+               while (!_video_view->display_next_frame(false)) {}
        }
 
        resume ();
@@ -598,7 +490,7 @@ FilmViewer::config_changed (Config::Property p)
 {
 #ifdef DCPOMATIC_VARIANT_SWAROOP
        if (p == Config::PLAYER_BACKGROUND_IMAGE) {
-               refresh_view ();
+               _video_view->update ();
                return;
        }
 #endif
@@ -665,18 +557,24 @@ FilmViewer::uncorrected_time () const
                return DCPTime::from_seconds (const_cast<RtAudio*>(&_audio)->getStreamTime());
        }
 
-       return _video_position;
+       return _video_view->position();
 }
 
-DCPTime
-FilmViewer::time () const
+optional<DCPTime>
+FilmViewer::audio_time () const
 {
-       if (_audio.isStreamRunning ()) {
-               return DCPTime::from_seconds (const_cast<RtAudio*>(&_audio)->getStreamTime ()) -
-                       DCPTime::from_frames (average_latency(), _film->audio_frame_rate());
+       if (!_audio.isStreamRunning()) {
+               return optional<DCPTime>();
        }
 
-       return _video_position;
+       return DCPTime::from_seconds (const_cast<RtAudio*>(&_audio)->getStreamTime ()) -
+               DCPTime::from_frames (average_latency(), _film->audio_frame_rate());
+}
+
+DCPTime
+FilmViewer::time () const
+{
+       return audio_time().get_value_or(_video_view->position());
 }
 
 int
@@ -749,7 +647,7 @@ FilmViewer::show_closed_captions ()
 void
 FilmViewer::seek_by (DCPTime by, bool accurate)
 {
-       seek (_video_position + by, accurate);
+       seek (_video_view->position() + by, accurate);
 }
 
 void
@@ -757,3 +655,34 @@ FilmViewer::set_pad_black (bool p)
 {
        _pad_black = p;
 }
+
+/** Called when a player has finished the current film.
+ *  May be called from a non-UI thread.
+ */
+void
+FilmViewer::finished ()
+{
+       emit (boost::bind(&FilmViewer::ui_finished, this));
+}
+
+/** Called by finished() in the UI thread */
+void
+FilmViewer::ui_finished ()
+{
+       stop ();
+       Finished ();
+}
+
+int
+FilmViewer::dropped () const
+{
+       return _video_view->dropped ();
+}
+
+int
+FilmViewer::gets () const
+{
+       return _video_view->gets ();
+}
+
+
index 1f20c3321f0ccc999f285c01692edd000010e872..60cde60d00139b10fb6a6546995e1eca66b50196 100644 (file)
@@ -27,6 +27,7 @@
 #include "lib/config.h"
 #include "lib/player_text.h"
 #include "lib/timer.h"
+#include "lib/signaller.h"
 #include <RtAudio.h>
 #include <wx/wx.h>
 
@@ -42,7 +43,7 @@ class ClosedCaptionsDialog;
 /** @class FilmViewer
  *  @brief A wx widget to view a Film.
  */
-class FilmViewer
+class FilmViewer : public Signaller
 {
 public:
        FilmViewer (wxWindow *);
@@ -69,7 +70,7 @@ public:
        void seek_by (dcpomatic::DCPTime by, bool accurate);
        /** @return our `playhead' position; this may not lie exactly on a frame boundary */
        dcpomatic::DCPTime position () const {
-               return _video_position;
+               return _video_view->position();
        }
        dcpomatic::DCPTime one_video_frame () const;
 
@@ -77,6 +78,7 @@ public:
        bool stop ();
        void suspend ();
        void resume ();
+
        bool playing () const {
                return _playing;
        }
@@ -90,16 +92,18 @@ public:
 
        void slow_refresh ();
 
-       int dropped () const {
-               return _dropped;
-       }
+       dcpomatic::DCPTime time () const;
+       boost::optional<dcpomatic::DCPTime> audio_time () const;
+
+       int dropped () const;
+       int gets () const;
 
        int audio_callback (void* out, unsigned int frames);
 
 #ifdef DCPOMATIC_VARIANT_SWAROOP
        void set_background_image (bool b) {
                _background_image = b;
-               refresh_view ();
+               _video_view->update ();
        }
 
        bool background_image () const {
@@ -108,39 +112,28 @@ public:
 #endif
 
        StateTimer const & state_timer () const {
-               return _state_timer;
-       }
-
-       StateTimer& state_timer () {
-               return _state_timer;
-       }
-
-       int gets () const {
-               return _gets;
+               return _video_view->state_timer ();
        }
 
-       /* Some accessors that VideoView classes need */
+       /* Some accessors and utility methods that VideoView classes need */
        dcp::Size out_size () const {
                return _out_size;
        }
-       dcp::Size inter_size () const {
-               return _inter_size;
-       }
-       Position<int> inter_position () const {
-               return _inter_position;
-       }
        bool outline_content () const {
                return _outline_content;
        }
        bool pad_black () const {
                return _pad_black;
        }
-       dcpomatic::DCPTime video_position () const {
-               return _video_position;
+       boost::shared_ptr<Butler> butler () const {
+               return _butler;
        }
+       ClosedCaptionsDialog* closed_captions_dialog () const {
+               return _closed_captions_dialog;
+       }
+       void finished ();
 
        boost::signals2::signal<void (boost::weak_ptr<PlayerVideo>)> ImageChanged;
-       boost::signals2::signal<void ()> PositionChanged;
        boost::signals2::signal<void (dcpomatic::DCPTime)> Started;
        boost::signals2::signal<void (dcpomatic::DCPTime)> Stopped;
        /** While playing back we reached the end of the film (emitted from GUI thread) */
@@ -149,38 +142,30 @@ public:
        boost::signals2::signal<bool ()> PlaybackPermitted;
 
 private:
+
        void video_view_sized ();
-       void timer ();
        void calculate_sizes ();
        void player_change (ChangeType type, int, bool);
-       bool get (bool lazy);
        void idle_handler ();
-       void request_idle_get ();
-       void display_player_video ();
+       void request_idle_display_next_frame ();
        void film_change (ChangeType, Film::Property);
        void recreate_butler ();
        void config_changed (Config::Property);
+       void film_length_change ();
+       void ui_finished ();
 
-       dcpomatic::DCPTime time () const;
        dcpomatic::DCPTime uncorrected_time () const;
        Frame average_latency () const;
 
-       void refresh_view ();
        bool quick_refresh ();
 
        boost::shared_ptr<Film> _film;
        boost::shared_ptr<Player> _player;
 
        VideoView* _video_view;
-       wxTimer _timer;
        bool _coalesce_player_changes;
        std::list<int> _pending_player_changes;
 
-       std::pair<boost::shared_ptr<PlayerVideo>, dcpomatic::DCPTime> _player_video;
-       dcpomatic::DCPTime _video_position;
-       Position<int> _inter_position;
-       dcp::Size _inter_size;
-
        /** Size of our output (including padding if we have any) */
        dcp::Size _out_size;
 
@@ -196,13 +181,11 @@ private:
        mutable boost::mutex _latency_history_mutex;
        int _latency_history_count;
 
-       int _dropped;
        boost::optional<int> _dcp_decode_reduction;
 
        ClosedCaptionsDialog* _closed_captions_dialog;
 
        bool _outline_content;
-       Eyes _eyes;
        /** true to pad the viewer panel with black, false to use
            the normal window background colour.
        */
@@ -212,9 +195,6 @@ private:
        bool _background_image;
 #endif
 
-       StateTimer _state_timer;
-       int _gets;
-
        /** true if an get() is required next time we are idle */
        bool _idle_get;
 
index a0f83db6d4390dfba4d056d5d4eb13da67048164..d47ad87f48cdfbe788bd9151d04920c7cd5bb0ac 100644 (file)
 
 #include "gl_video_view.h"
 #include "film_viewer.h"
+#include "wx_util.h"
 #include "lib/image.h"
 #include "lib/dcpomatic_assert.h"
 #include "lib/exceptions.h"
+#include "lib/cross.h"
+#include "lib/player_video.h"
+#include "lib/butler.h"
 #include <boost/bind.hpp>
 #include <iostream>
 
@@ -51,12 +55,20 @@ using boost::optional;
 
 GLVideoView::GLVideoView (FilmViewer* viewer, wxWindow *parent)
        : VideoView (viewer)
+       , _have_storage (false)
        , _vsync_enabled (false)
+       , _thread (0)
+       , _playing (false)
+       , _one_shot (false)
 {
        _canvas = new wxGLCanvas (parent, wxID_ANY, 0, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE);
-       _context = new wxGLContext (_canvas);
-       _canvas->Bind (wxEVT_PAINT, boost::bind(&GLVideoView::paint, this));
+       _canvas->Bind (wxEVT_PAINT, boost::bind(&GLVideoView::update, this));
        _canvas->Bind (wxEVT_SIZE, boost::bind(boost::ref(Sized)));
+       _canvas->Bind (wxEVT_CREATE, boost::bind(&GLVideoView::create, this));
+
+       _canvas->Bind (wxEVT_TIMER, boost::bind(&GLVideoView::check_for_butler_errors, this));
+       _timer.reset (new wxTimer(_canvas));
+       _timer->Start (2000);
 
 #if defined(DCPOMATIC_LINUX) && defined(DCPOMATIC_HAVE_GLX_SWAP_INTERVAL_EXT)
        if (_canvas->IsExtensionSupported("GLX_EXT_swap_control")) {
@@ -93,12 +105,25 @@ GLVideoView::GLVideoView (FilmViewer* viewer, wxWindow *parent)
 
 GLVideoView::~GLVideoView ()
 {
+       _thread->interrupt ();
+       _thread->join ();
+       delete _thread;
+
        glDeleteTextures (1, &_id);
-       delete _context;
+}
+
+void
+GLVideoView::check_for_butler_errors ()
+{
+       try {
+               _viewer->butler()->rethrow ();
+       } catch (DecodeError& e) {
+               error_dialog (get(), e.what());
+       }
 }
 
 static void
-       check_gl_error (char const * last)
+check_gl_error (char const * last)
 {
        GLenum const e = glGetError ();
        if (e != GL_NO_ERROR) {
@@ -106,28 +131,20 @@ static void
        }
 }
 
-void
-GLVideoView::paint ()
-{
-        _viewer->state_timer().set("paint-panel");
-       _canvas->SetCurrent (*_context);
-       wxPaintDC dc (_canvas);
-       draw ();
-       _viewer->state_timer().unset();
-}
-
 void
 GLVideoView::update ()
 {
-       if (!_canvas->IsShownOnScreen()) {
-               return;
+       {
+               boost::mutex::scoped_lock lm (_canvas_mutex);
+               if (!_canvas->IsShownOnScreen()) {
+                       return;
+               }
        }
-       wxClientDC dc (_canvas);
-       draw ();
+       request_one_shot ();
 }
 
 void
-GLVideoView::draw ()
+GLVideoView::draw (Position<int> inter_position, dcp::Size inter_size)
 {
        glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        check_gl_error ("glClear");
@@ -142,25 +159,33 @@ GLVideoView::draw ()
        check_gl_error ("glDisable GL_DEPTH_TEST");
        glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
-       if (_canvas->GetSize().x < 64 || _canvas->GetSize().y < 0) {
+       wxSize canvas_size;
+       {
+               boost::mutex::scoped_lock lm (_canvas_mutex);
+               canvas_size = _canvas->GetSize ();
+       }
+
+       if (canvas_size.GetWidth() < 64 || canvas_size.GetHeight() < 0) {
                return;
        }
 
-       glViewport (0, 0, _canvas->GetSize().x, _canvas->GetSize().y);
+       glViewport (0, 0, canvas_size.GetWidth(), canvas_size.GetHeight());
        check_gl_error ("glViewport");
        glMatrixMode (GL_PROJECTION);
        glLoadIdentity ();
 
-       gluOrtho2D (0, _canvas->GetSize().x, _canvas->GetSize().y, 0);
+       gluOrtho2D (0, canvas_size.GetWidth(), canvas_size.GetHeight(), 0);
        check_gl_error ("gluOrtho2d");
        glMatrixMode (GL_MODELVIEW);
        glLoadIdentity ();
 
        glTranslatef (0, 0, 0);
 
+       dcp::Size const out_size = _viewer->out_size ();
+
        if (_size) {
+               /* Render our image (texture) */
                glBegin (GL_QUADS);
-
                glTexCoord2f (0, 1);
                glVertex2f (0, _size->height);
                glTexCoord2f (1, 1);
@@ -169,13 +194,19 @@ GLVideoView::draw ()
                glVertex2f (_size->width, 0);
                glTexCoord2f (0, 0);
                glVertex2f (0, 0);
-
+               glEnd ();
+       } else {
+               /* No image, so just fill with black */
+               glBegin (GL_QUADS);
+               glColor3ub (0, 0, 0);
+               glVertex2f (0, 0);
+               glVertex2f (out_size.width, 0);
+               glVertex2f (out_size.width, out_size.height);
+               glVertex2f (0, out_size.height);
+               glVertex2f (0, 0);
                glEnd ();
        }
 
-       dcp::Size const out_size = _viewer->out_size ();
-       wxSize const canvas_size = _canvas->GetSize ();
-
        if (!_viewer->pad_black() && out_size.width < canvas_size.GetWidth()) {
                glBegin (GL_QUADS);
                /* XXX: these colours are right for GNOME; may need adjusting for other OS */
@@ -209,8 +240,6 @@ GLVideoView::draw ()
        if (_viewer->outline_content()) {
                glColor3ub (255, 0, 0);
                glBegin (GL_LINE_LOOP);
-               Position<int> inter_position = _viewer->inter_position ();
-               dcp::Size inter_size = _viewer->inter_size ();
                glVertex2f (inter_position.x, inter_position.y + (canvas_size.GetHeight() - out_size.height) / 2);
                glVertex2f (inter_position.x + inter_size.width, inter_position.y + (canvas_size.GetHeight() - out_size.height) / 2);
                glVertex2f (inter_position.x + inter_size.width, inter_position.y + (canvas_size.GetHeight() - out_size.height) / 2 + inter_size.height);
@@ -220,6 +249,8 @@ GLVideoView::draw ()
        }
 
        glFlush();
+
+       boost::mutex::scoped_lock lm (_canvas_mutex);
        _canvas->SwapBuffers();
 }
 
@@ -234,11 +265,21 @@ GLVideoView::set_image (shared_ptr<const Image> image)
        DCPOMATIC_ASSERT (image->pixel_format() == AV_PIX_FMT_RGB24);
        DCPOMATIC_ASSERT (!image->aligned());
 
+       if (image->size() != _size) {
+               _have_storage = false;
+       }
+
        _size = image->size ();
        glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
        check_gl_error ("glPixelStorei");
-       glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB8, _size->width, _size->height, 0, GL_RGB, GL_UNSIGNED_BYTE, image->data()[0]);
-       check_gl_error ("glTexImage2D");
+       if (_have_storage) {
+               glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, _size->width, _size->height, GL_RGB, GL_UNSIGNED_BYTE, image->data()[0]);
+               check_gl_error ("glTexSubImage2D");
+       } else {
+               glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB8, _size->width, _size->height, 0, GL_RGB, GL_UNSIGNED_BYTE, image->data()[0]);
+               _have_storage = true;
+               check_gl_error ("glTexImage2D");
+       }
 
        glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -248,3 +289,104 @@ GLVideoView::set_image (shared_ptr<const Image> image)
        glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        check_gl_error ("glTexParameterf");
 }
+
+void
+GLVideoView::start ()
+{
+       VideoView::start ();
+
+       boost::mutex::scoped_lock lm (_playing_mutex);
+       _playing = true;
+       _playing_condition.notify_all ();
+}
+
+void
+GLVideoView::stop ()
+{
+       boost::mutex::scoped_lock lm (_playing_mutex);
+       _playing = false;
+}
+
+void
+GLVideoView::thread ()
+try
+{
+       {
+               boost::mutex::scoped_lock lm (_canvas_mutex);
+               _context = new wxGLContext (_canvas); //local
+               _canvas->SetCurrent (*_context);
+       }
+
+       while (true) {
+               boost::mutex::scoped_lock lm (_playing_mutex);
+               while (!_playing && !_one_shot) {
+                       _playing_condition.wait (lm);
+               }
+               _one_shot = false;
+               lm.unlock ();
+
+               Position<int> inter_position;
+               dcp::Size inter_size;
+               if (length() != dcpomatic::DCPTime()) {
+                       dcpomatic::DCPTime const next = position() + one_video_frame();
+
+                       if (next >= length()) {
+                               _viewer->finished ();
+                               continue;
+                       }
+
+                       get_next_frame (false);
+                       shared_ptr<PlayerVideo> pv = player_video().first;
+                       if (pv) {
+                               set_image (pv->image(bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24), false, true));
+                               inter_position = pv->inter_position();
+                               inter_size = pv->inter_size();
+                       }
+               }
+               draw (inter_position, inter_size);
+
+               while (true) {
+                       optional<int> n = time_until_next_frame();
+                       if (!n || *n > 5) {
+                               break;
+                       }
+                       get_next_frame (true);
+                       add_dropped ();
+               }
+
+               boost::this_thread::interruption_point ();
+               dcpomatic_sleep_milliseconds (time_until_next_frame().get_value_or(0));
+       }
+
+       /* XXX: leaks _context, but that seems preferable to deleting it here
+        * without also deleting the wxGLCanvas.
+        */
+}
+catch (boost::thread_interrupted& e)
+{
+       store_current ();
+}
+
+bool
+GLVideoView::display_next_frame (bool non_blocking)
+{
+       bool const r = get_next_frame (non_blocking);
+       request_one_shot ();
+       return r;
+}
+
+void
+GLVideoView::request_one_shot ()
+{
+       boost::mutex::scoped_lock lm (_playing_mutex);
+       _one_shot = true;
+       _playing_condition.notify_all ();
+}
+
+void
+GLVideoView::create ()
+{
+       if (!_thread) {
+               _thread = new boost::thread (boost::bind(&GLVideoView::thread, this));
+       }
+}
index ba4c7cfdc92df3a32afbc9642f3de5d8264b8fdc..2f3c8c2a1c40ccb715f1aa296d5f177ce5ed5d29 100644 (file)
 */
 
 #include "video_view.h"
+#include "lib/signaller.h"
+#include "lib/position.h"
 #include <wx/wx.h>
 #include <wx/glcanvas.h>
 #include <dcp/util.h>
 #include <boost/shared_ptr.hpp>
+#include <boost/thread.hpp>
+#include <boost/thread/condition.hpp>
 #undef None
 #undef Success
 
@@ -37,18 +41,39 @@ public:
                return _canvas;
        }
        void update ();
+       void start ();
+       void stop ();
+
+       bool display_next_frame (bool);
 
        bool vsync_enabled () const {
                return _vsync_enabled;
        }
 
 private:
-        void paint ();
-        void draw ();
+       void draw (Position<int> inter_position, dcp::Size inter_size);
+       void thread ();
+       void request_one_shot ();
+       void create ();
+       void check_for_butler_errors ();
 
+       /* Mutex for use of _canvas; it's only contended when our ::thread
+          is started up so this may be overkill.
+        */
+       boost::mutex _canvas_mutex;
        wxGLCanvas* _canvas;
-        wxGLContext* _context;
-        GLuint _id;
-        boost::optional<dcp::Size> _size;
+       wxGLContext* _context;
+
+       GLuint _id;
+       boost::optional<dcp::Size> _size;
+       bool _have_storage;
        bool _vsync_enabled;
+       boost::thread* _thread;
+
+       boost::mutex _playing_mutex;
+       boost::condition _playing_condition;
+       bool _playing;
+       bool _one_shot;
+
+       boost::shared_ptr<wxTimer> _timer;
 };
index 66af8f19f58b75b3b30666211977a7f000ac8f36..ae13fe1bcde1d10502acb594bd3cd906f29b5d3f 100644 (file)
 #include "simple_video_view.h"
 #include "film_viewer.h"
 #include "wx_util.h"
+#include "closed_captions_dialog.h"
 #include "lib/image.h"
+#include "lib/dcpomatic_log.h"
+#include "lib/butler.h"
 #include <dcp/util.h>
 #include <wx/wx.h>
 #include <boost/bind.hpp>
@@ -29,6 +32,7 @@
 using std::max;
 using std::string;
 using boost::optional;
+using boost::shared_ptr;
 using namespace dcpomatic;
 
 SimpleVideoView::SimpleVideoView (FilmViewer* viewer, wxWindow* parent)
@@ -45,12 +49,14 @@ SimpleVideoView::SimpleVideoView (FilmViewer* viewer, wxWindow* parent)
 
        _panel->Bind (wxEVT_PAINT, boost::bind (&SimpleVideoView::paint, this));
        _panel->Bind (wxEVT_SIZE, boost::bind(boost::ref(Sized)));
+
+       _timer.Bind (wxEVT_TIMER, boost::bind(&SimpleVideoView::timer, this));
 }
 
 void
 SimpleVideoView::paint ()
 {
-        _viewer->state_timer().set("paint-panel");
+        _state_timer.set("paint-panel");
        wxPaintDC dc (_panel);
 
        dcp::Size const out_size = _viewer->out_size ();
@@ -79,10 +85,10 @@ SimpleVideoView::paint ()
 
 #ifdef DCPOMATIC_VARIANT_SWAROOP
                DCPTime const period = DCPTime::from_seconds(Config::instance()->player_watermark_period() * 60);
-               int64_t n = _viewer->video_position().get() / period.get();
+               int64_t n = position().get() / period.get();
                DCPTime from(n * period.get());
                DCPTime to = from + DCPTime::from_seconds(Config::instance()->player_watermark_duration() / 1000.0);
-               if (from <= _viewer->video_position() && _viewer->video_position() <= to) {
+               if (from <= position() && position() <= to) {
                        if (!_in_watermark) {
                                _in_watermark = true;
                                _watermark_x = rand() % panel_size.GetWidth();
@@ -119,19 +125,131 @@ SimpleVideoView::paint ()
        }
 
        if (_viewer->outline_content()) {
-               Position<int> inter_position = _viewer->inter_position ();
-               dcp::Size inter_size = _viewer->inter_size ();
                wxPen p (wxColour (255, 0, 0), 2);
                dc.SetPen (p);
                dc.SetBrush (*wxTRANSPARENT_BRUSH);
-               dc.DrawRectangle (inter_position.x, inter_position.y + (panel_size.GetHeight() - out_size.height) / 2, inter_size.width, inter_size.height);
+               dc.DrawRectangle (_inter_position.x, _inter_position.y + (panel_size.GetHeight() - out_size.height) / 2, _inter_size.width, _inter_size.height);
        }
-        _viewer->state_timer().unset();
+        _state_timer.unset();
 }
 
 void
-SimpleVideoView::update ()
+SimpleVideoView::refresh_panel ()
 {
+       _state_timer.set ("refresh-panel");
        _panel->Refresh ();
        _panel->Update ();
+       _state_timer.unset ();
+}
+
+void
+SimpleVideoView::timer ()
+{
+       if (!_viewer->playing()) {
+               return;
+       }
+
+       display_next_frame (false);
+       DCPTime const next = position() + _viewer->one_video_frame();
+
+       if (next >= length()) {
+               _viewer->finished ();
+               return;
+       }
+
+       LOG_DEBUG_PLAYER("%1 -> %2; delay %3", next.seconds(), _viewer->time().seconds(), max((next.seconds() - _viewer->time().seconds()) * 1000, 1.0));
+       _timer.Start (time_until_next_frame().get_value_or(0), wxTIMER_ONE_SHOT);
+
+       if (_viewer->butler()) {
+               _viewer->butler()->rethrow ();
+       }
+}
+
+void
+SimpleVideoView::start ()
+{
+       VideoView::start ();
+       timer ();
+}
+
+/** Try to get a frame from the butler and display it.
+ *  @param non_blocking true to return false quickly if no video is available quickly (i.e. we are waiting for the butler).
+ *  false to ask the butler to block until it has video (unless it is suspended).
+ *  @return true on success, false if we did nothing because it would have taken too long.
+ */
+bool
+SimpleVideoView::display_next_frame (bool non_blocking)
+{
+       bool r = get_next_frame (non_blocking);
+       if (!r) {
+               if (non_blocking) {
+                       /* No video available; return saying we failed */
+                       return false;
+               } else {
+                       /* Player was suspended; come back later */
+                       signal_manager->when_idle (boost::bind(&SimpleVideoView::display_next_frame, this, false));
+                       return false;
+               }
+       }
+
+       update ();
+
+       try {
+               _viewer->butler()->rethrow ();
+       } catch (DecodeError& e) {
+               error_dialog (get(), e.what());
+       }
+
+       return true;
+}
+
+void
+SimpleVideoView::update ()
+{
+       if (!player_video().first) {
+               set_image (shared_ptr<Image>());
+               refresh_panel ();
+               return;
+       }
+
+       if (_viewer->playing() && (_viewer->time() - player_video().second) > one_video_frame()) {
+               /* Too late; just drop this frame before we try to get its image (which will be the time-consuming
+                  part if this frame is J2K).
+               */
+               add_dropped ();
+               return;
+       }
+
+       /* In an ideal world, what we would do here is:
+        *
+        * 1. convert to XYZ exactly as we do in the DCP creation path.
+        * 2. convert back to RGB for the preview display, compensating
+        *    for the monitor etc. etc.
+        *
+        * but this is inefficient if the source is RGB.  Since we don't
+        * (currently) care too much about the precise accuracy of the preview's
+        * colour mapping (and we care more about its speed) we try to short-
+        * circuit this "ideal" situation in some cases.
+        *
+        * The content's specified colour conversion indicates the colourspace
+        * which the content is in (according to the user).
+        *
+        * PlayerVideo::image (bound to PlayerVideo::force) will take the source
+        * image and convert it (from whatever the user has said it is) to RGB.
+        */
+
+       _state_timer.set ("get image");
+
+       set_image (
+               player_video().first->image(bind(&PlayerVideo::force, _1, AV_PIX_FMT_RGB24), false, true)
+               );
+
+       _state_timer.set ("ImageChanged");
+       _viewer->ImageChanged (player_video().first);
+       _state_timer.unset ();
+
+       _inter_position = player_video().first->inter_position ();
+       _inter_size = player_video().first->inter_size ();
+
+       refresh_panel ();
 }
index 686a1a1f3cc83c686569a617dafd605e677aac39..a6a5cf47f1783b44235c9d771c6d359ff642b6d6 100644 (file)
@@ -19,6 +19,8 @@
 */
 
 #include "video_view.h"
+#include "lib/position.h"
+#include <dcp/types.h>
 #include <wx/wx.h>
 
 class FilmViewer;
@@ -37,10 +39,17 @@ public:
        }
 
        void update ();
+       void start ();
+       bool display_next_frame (bool non_blocking);
 
 private:
+       void refresh_panel ();
        void paint ();
+       void timer ();
 
        wxPanel* _panel;
        boost::shared_ptr<const Image> _image;
+       wxTimer _timer;
+       Position<int> _inter_position;
+       dcp::Size _inter_size;
 };
index 956f82c961111619137d5616fcb454a36a4484f1..e9a31c86b059d94fb2c802b5f46d63ae8488e421 100644 (file)
@@ -74,3 +74,19 @@ StandardControls::setup_sensitivity ()
        bool const active_job = _active_job && *_active_job != "examine_content";
        _play_button->Enable (_film && !_film->content().empty() && !active_job);
 }
+
+#ifdef DCPOMATIC_PLAYER_STRESS_TEST
+void
+StandardControls::play ()
+{
+       _play_button->SetValue (true);
+       play_clicked ();
+}
+
+void
+StandardControls::stop ()
+{
+       _play_button->SetValue (false);
+       play_clicked ();
+}
+#endif
index b485c48cc744e6439c8c82aa8f9cf79998337e45..f79e4a17896443a367b1d9fb37f516ffda855063 100644 (file)
@@ -25,6 +25,11 @@ class StandardControls : public Controls
 public:
        StandardControls (wxWindow* parent, boost::shared_ptr<FilmViewer> viewer, bool editor_controls);
 
+#ifdef DCPOMATIC_PLAYER_STRESS_TEST
+       void play ();
+       void stop ();
+#endif
+
 private:
        void check_play_state ();
        void play_clicked ();
index 5ce6c45fc7a637f6e6c1e99124d596f7698cb422..add9bf3e0ae5937f9bbdce005dae8f01212a7501 100644 (file)
@@ -54,6 +54,7 @@ SwaroopControls::SwaroopControls (wxWindow* parent, shared_ptr<FilmViewer> viewe
        , _previous_button (new Button(this, "Previous"))
        , _current_disable_timeline (false)
        , _current_disable_next (false)
+       , _timer (this)
 {
        _button_sizer->Add (_previous_button, 0, wxEXPAND);
        _button_sizer->Add (_play_button, 0, wxEXPAND);
@@ -112,10 +113,13 @@ SwaroopControls::SwaroopControls (wxWindow* parent, shared_ptr<FilmViewer> viewe
        _spl_view->Bind        (wxEVT_LIST_ITEM_SELECTED,   boost::bind(&SwaroopControls::spl_selection_changed, this));
        _spl_view->Bind        (wxEVT_LIST_ITEM_DESELECTED, boost::bind(&SwaroopControls::spl_selection_changed, this));
        _viewer->Finished.connect (boost::bind(&SwaroopControls::viewer_finished, this));
-       _viewer->PositionChanged.connect (boost::bind(&SwaroopControls::viewer_position_changed, this));
        _refresh_spl_view->Bind (wxEVT_BUTTON, boost::bind(&SwaroopControls::update_playlist_directory, this));
        _refresh_content_view->Bind (wxEVT_BUTTON, boost::bind(&ContentView::update, _content_view));
 
+       /* Write position every two minutes if we're playing */
+       Bind (wxEVT_TIMER, boost::bind(&SwaroopControls::write_position, this));
+       _timer.Start (2 * 60 * 1000, wxTIMER_CONTINUOUS);
+
        _content_view->update ();
        update_playlist_directory ();
 
@@ -153,10 +157,9 @@ SwaroopControls::check_restart ()
 }
 
 void
-SwaroopControls::viewer_position_changed ()
+SwaroopControls::write_position ()
 {
-       /* Write position every two minutes if we're playing */
-       if (!_selected_playlist || !_viewer->playing() || _viewer->position().get() % (2 * 60 * DCPTime::HZ)) {
+       if (!_selected_playlist || !_viewer->playing()) {
                return;
        }
 
index 11dbcfc1013025fbec4329c65f91d702699e41ad..a8bb41dea75d80258fd33fb43f93473c9fb6f284 100644 (file)
@@ -54,7 +54,7 @@ private:
        void setup_sensitivity ();
        void config_changed (int);
        void viewer_finished ();
-       void viewer_position_changed ();
+       void write_position ();
        void reset_film ();
        void update_current_content ();
        bool can_do_previous ();
@@ -84,4 +84,6 @@ private:
        std::vector<SPL> _playlists;
        boost::optional<int> _selected_playlist;
        int _selected_playlist_position;
+
+       wxTimer _timer;
 };
index daeeb0a51280916c4bb5b594c59fe86f6627c201..9f71847f5c626dfb7e39690c91010e19d4f54252 100644 (file)
@@ -82,6 +82,7 @@ Timeline::Timeline (wxWindow* parent, ContentPanel* cp, shared_ptr<Film> film, w
        , _y_scroll_rate (16)
        , _pixels_per_track (48)
        , _first_resize (true)
+       , _timer (this)
 {
 #ifndef __WXOSX__
        _labels_canvas->SetDoubleBuffered (true);
@@ -116,16 +117,15 @@ Timeline::Timeline (wxWindow* parent, ContentPanel* cp, shared_ptr<Film> film, w
        _film_changed_connection = film->Change.connect (bind (&Timeline::film_change, this, _1, _2));
        _film_content_change_connection = film->ContentChange.connect (bind (&Timeline::film_content_change, this, _1, _3, _4));
 
-       shared_ptr<FilmViewer> vp = viewer.lock ();
-       DCPOMATIC_ASSERT (vp);
-       _viewer_position_change_connection = vp->PositionChanged.connect (bind(&Timeline::position_change, this));
+       Bind (wxEVT_TIMER, boost::bind(&Timeline::update_playhead, this));
+       _timer.Start (200, wxTIMER_CONTINUOUS);
 
        setup_scrollbars ();
        _labels_canvas->ShowScrollbars (wxSHOW_SB_NEVER, wxSHOW_SB_NEVER);
 }
 
 void
-Timeline::position_change ()
+Timeline::update_playhead ()
 {
        Refresh ();
 }
index ef887dab8242878563dde77e08dd4a53d32d4caf..44a8973716bdd3dca7f3df7fe761f5fd1a019f40 100644 (file)
@@ -104,7 +104,7 @@ private:
        void set_pixels_per_second (double pps);
        void set_pixels_per_track (int h);
        void zoom_all ();
-       void position_change ();
+       void update_playhead ();
 
        boost::shared_ptr<TimelineView> event_to_view (wxMouseEvent &);
        TimelineContentViewList selected_views () const;
@@ -137,11 +137,11 @@ private:
        int _y_scroll_rate;
        int _pixels_per_track;
        bool _first_resize;
+       wxTimer _timer;
 
        static double const _minimum_pixels_per_second;
        static int const _minimum_pixels_per_track;
 
        boost::signals2::scoped_connection _film_changed_connection;
        boost::signals2::scoped_connection _film_content_change_connection;
-       boost::signals2::scoped_connection _viewer_position_change_connection;
 };
diff --git a/src/wx/video_view.cc b/src/wx/video_view.cc
new file mode 100644 (file)
index 0000000..4edc2cd
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+    Copyright (C) 2019 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "video_view.h"
+#include "wx_util.h"
+#include "film_viewer.h"
+#include "lib/butler.h"
+#include <boost/optional.hpp>
+
+using boost::shared_ptr;
+using boost::optional;
+
+VideoView::VideoView (FilmViewer* viewer)
+       : _viewer (viewer)
+#ifdef DCPOMATIC_VARIANT_SWAROOP
+       , _in_watermark (false)
+#endif
+       , _state_timer ("viewer")
+       , _video_frame_rate (0)
+       , _eyes (EYES_LEFT)
+       , _three_d (false)
+       , _dropped (0)
+       , _gets (0)
+{
+
+}
+
+void
+VideoView::clear ()
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       _player_video.first.reset ();
+       _player_video.second = dcpomatic::DCPTime ();
+}
+
+/** Could be called from any thread.
+ *  @param non_blocking true to return false quickly if no video is available quickly.
+ *  @return false if we gave up because it would take too long, otherwise true.
+ */
+bool
+VideoView::get_next_frame (bool non_blocking)
+{
+       if (length() == dcpomatic::DCPTime()) {
+               return true;
+       }
+
+       shared_ptr<Butler> butler = _viewer->butler ();
+       if (!butler) {
+               return false;
+       }
+       add_get ();
+
+       boost::mutex::scoped_lock lm (_mutex);
+
+       do {
+               Butler::Error e;
+               _player_video = butler->get_video (!non_blocking, &e);
+               if (!_player_video.first && e == Butler::AGAIN) {
+                       return false;
+               }
+       } while (
+               _player_video.first &&
+               _three_d &&
+               _eyes != _player_video.first->eyes() &&
+               _player_video.first->eyes() != EYES_BOTH
+               );
+
+       return true;
+}
+
+dcpomatic::DCPTime
+VideoView::one_video_frame () const
+{
+       return dcpomatic::DCPTime::from_frames (1, video_frame_rate());
+}
+
+/** @return Time in ms until the next frame is due, or empty if nothing is due */
+optional<int>
+VideoView::time_until_next_frame () const
+{
+       if (length() == dcpomatic::DCPTime()) {
+               /* There's no content, so this doesn't matter */
+               return optional<int>();
+       }
+
+       dcpomatic::DCPTime const next = position() + one_video_frame();
+       dcpomatic::DCPTime const time = _viewer->audio_time().get_value_or(position());
+       if (next < time) {
+               return 0;
+       }
+       return (next.seconds() - time.seconds()) * 1000;
+}
+
+void
+VideoView::start ()
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       _dropped = 0;
+}
+
+bool
+VideoView::refresh_metadata (shared_ptr<const Film> film, dcp::Size video_container_size, dcp::Size film_frame_size)
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       if (!_player_video.first) {
+               return false;
+       }
+
+       if (!_player_video.first->reset_metadata (film, video_container_size, film_frame_size)) {
+               return false;
+       }
+
+       update ();
+       return true;
+}
+
index 892ffab125d08560fed8542937d36a364322f77f..f9e0670435ed13edaa2e919cc373e11d12a0a48d 100644 (file)
 #ifndef DCPOMATIC_VIDEO_VIEW_H
 #define DCPOMATIC_VIDEO_VIEW_H
 
+#include "lib/dcpomatic_time.h"
+#include "lib/timer.h"
+#include "lib/types.h"
+#include "lib/exception_store.h"
 #include <boost/shared_ptr.hpp>
 #include <boost/signals2.hpp>
+#include <boost/thread.hpp>
+#include <boost/noncopyable.hpp>
 
 class Image;
 class wxWindow;
 class FilmViewer;
+class PlayerVideo;
 
-class VideoView
+class VideoView : public ExceptionStore, public boost::noncopyable
 {
 public:
-       VideoView (FilmViewer* viewer)
-               : _viewer (viewer)
-#ifdef DCPOMATIC_VARIANT_SWAROOP
-               , _in_watermark (false)
-#endif
-       {}
-
+       VideoView (FilmViewer* viewer);
        virtual ~VideoView () {}
 
-       virtual void set_image (boost::shared_ptr<const Image> image) = 0;
+       /** @return the thing displaying the image */
        virtual wxWindow* get () const = 0;
+       /** Re-make and display the image from the current _player_video */
        virtual void update () = 0;
+       /** Called when playback starts */
+       virtual void start ();
+       /** Called when playback stops */
+       virtual void stop () {}
+       /** Get the next frame and display it; used after seek */
+       virtual bool display_next_frame (bool) = 0;
+
+       void clear ();
+       bool refresh_metadata (boost::shared_ptr<const Film> film, dcp::Size video_container_size, dcp::Size film_frame_size);
 
+       /** Emitted from the GUI thread when our display changes in size */
        boost::signals2::signal<void()> Sized;
 
+
+       /* Accessors for FilmViewer */
+
+       int dropped () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _dropped;
+       }
+
+       int gets () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _gets;
+       }
+
+       StateTimer const & state_timer () const {
+               return _state_timer;
+       }
+
+       dcpomatic::DCPTime position () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _player_video.second;
+       }
+
+
+       /* Setters for FilmViewer so it can tell us our state and
+        * we can then use (thread) safely.
+        */
+
+       void set_video_frame_rate (int r) {
+               boost::mutex::scoped_lock lm (_mutex);
+               _video_frame_rate = r;
+       }
+
+       void set_length (dcpomatic::DCPTime len) {
+               boost::mutex::scoped_lock lm (_mutex);
+               _length = len;
+       }
+
+       void set_eyes (Eyes eyes) {
+               boost::mutex::scoped_lock lm (_mutex);
+               _eyes = eyes;
+       }
+
+       void set_three_d (bool t) {
+               boost::mutex::scoped_lock lm (_mutex);
+               _three_d = t;
+       }
+
 protected:
+       bool get_next_frame (bool non_blocking);
+       boost::optional<int> time_until_next_frame () const;
+       dcpomatic::DCPTime one_video_frame () const;
+
+       int video_frame_rate () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _video_frame_rate;
+       }
+
+       dcpomatic::DCPTime length () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _length;
+       }
+
+       std::pair<boost::shared_ptr<PlayerVideo>, dcpomatic::DCPTime> player_video () const {
+               boost::mutex::scoped_lock lm (_mutex);
+               return _player_video;
+       }
+
+       void add_dropped () {
+               boost::mutex::scoped_lock lm (_mutex);
+               ++_dropped;
+       }
+
+       void add_get () {
+               boost::mutex::scoped_lock lm (_mutex);
+               ++_gets;
+       }
+
        FilmViewer* _viewer;
 
 #ifdef DCPOMATIC_VARIANT_SWAROOP
@@ -54,6 +142,22 @@ protected:
        int _watermark_x;
        int _watermark_y;
 #endif
+
+       StateTimer _state_timer;
+
+private:
+       /** Mutex protecting all the state in this class */
+       mutable boost::mutex _mutex;
+
+       std::pair<boost::shared_ptr<PlayerVideo>, dcpomatic::DCPTime> _player_video;
+       int _video_frame_rate;
+       /** length of the film we are playing, or 0 if there is none */
+       dcpomatic::DCPTime _length;
+       Eyes _eyes;
+       bool _three_d;
+
+       int _dropped;
+       int _gets;
 };
 
 #endif
index f4fc4927e31562f8105a3ada3ac65e076e11755d..5dbf75ecb93a4eb41f73a25e4b0d9963ea8d2bbf 100644 (file)
@@ -145,6 +145,7 @@ sources = """
           update_dialog.cc
           verify_dcp_dialog.cc
           video_panel.cc
+          video_view.cc
           video_waveform_dialog.cc
           video_waveform_plot.cc
           wx_util.cc
diff --git a/wscript b/wscript
index 9f45778e9e99999186c2c551c88752fdaaf728d2..c61b86782229a692d02e6b5fd0f914dc4f3d6f5c 100644 (file)
--- a/wscript
+++ b/wscript
@@ -73,6 +73,7 @@ def options(opt):
     opt.add_option('--workaround-gssapi', action='store_true', default=False, help='link to gssapi_krb5')
     opt.add_option('--force-cpp11',       action='store_true', default=False, help='force use of C++11')
     opt.add_option('--variant',           help='build variant (swaroop-studio, swaroop-theater)', choices=['swaroop-studio', 'swaroop-theater'])
+    opt.add_option('--enable-player-stress-test', action='store_true', default=False, help='build the player with stress testing enabled') 
     opt.add_option('--use-lld',           action='store_true', default=False, help='use lld linker')
 
 def configure(conf):
@@ -133,6 +134,9 @@ def configure(conf):
         if conf.options.variant.startswith('swaroop-'):
             conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_VARIANT_SWAROOP')
 
+    if conf.options.enable_player_stress_test:
+        conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_PLAYER_STRESS_TEST')
+
     if conf.options.use_lld:
         try:
             conf.find_program('ld.lld')
@@ -444,6 +448,9 @@ def configure(conf):
     conf.env['CXXFLAGS_AVCODEC'] = []
     conf.env['CXXFLAGS_AVUTIL'] = []
 
+    if conf.env.TARGET_LINUX:
+        conf.env.LIB_X11 = ['X11']
+
     # Boost
     if conf.options.static_boost:
         conf.env.STLIB_BOOST_THREAD = ['boost_thread']