Use an enum instead of a bool to specify blocking/non-blocking.
[dcpomatic.git] / src / wx / video_view.cc
1 /*
2     Copyright (C) 2019-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22 #include "video_view.h"
23 #include "wx_util.h"
24 #include "film_viewer.h"
25 #include "lib/butler.h"
26 #include "lib/dcpomatic_log.h"
27 #include <boost/optional.hpp>
28 #include <sys/time.h>
29
30
31 using std::shared_ptr;
32 using boost::optional;
33
34
35 static constexpr int TOO_MANY_DROPPED_FRAMES = 20;
36 static constexpr int TOO_MANY_DROPPED_PERIOD = 5.0;
37
38
39 VideoView::VideoView (FilmViewer* viewer)
40         : _viewer (viewer)
41         , _state_timer ("viewer")
42 {
43
44 }
45
46
47 void
48 VideoView::clear ()
49 {
50         boost::mutex::scoped_lock lm (_mutex);
51         _player_video.first.reset ();
52         _player_video.second = dcpomatic::DCPTime ();
53 }
54
55
56 /** Could be called from any thread.
57  *  @param non_blocking true to return false quickly if no video is available quickly.
58  *  @return FAIL if there's no frame, AGAIN if the method should be called again, or SUCCESS
59  *  if there is a frame.
60  */
61 VideoView::NextFrameResult
62 VideoView::get_next_frame (bool non_blocking)
63 {
64         if (length() == dcpomatic::DCPTime()) {
65                 return FAIL;
66         }
67
68         auto butler = _viewer->butler ();
69         if (!butler) {
70                 return FAIL;
71         }
72         add_get ();
73
74         boost::mutex::scoped_lock lm (_mutex);
75
76         do {
77                 Butler::Error e;
78                 auto pv = butler->get_video (non_blocking ? Butler::Behaviour::NON_BLOCKING : Butler::Behaviour::BLOCKING, &e);
79                 if (e.code == Butler::Error::Code::DIED) {
80                         LOG_ERROR ("Butler died with %1", e.summary());
81                 }
82                 if (!pv.first) {
83                         return e.code == Butler::Error::Code::AGAIN ? AGAIN : FAIL;
84                 }
85                 _player_video = pv;
86         } while (
87                 _player_video.first &&
88                 _three_d &&
89                 _eyes != _player_video.first->eyes() &&
90                 _player_video.first->eyes() != Eyes::BOTH
91                 );
92
93         if (_player_video.first && _player_video.first->error()) {
94                 ++_errored;
95         }
96
97         return SUCCESS;
98 }
99
100
101 dcpomatic::DCPTime
102 VideoView::one_video_frame () const
103 {
104         return dcpomatic::DCPTime::from_frames (1, video_frame_rate());
105 }
106
107
108 /** @return Time in ms until the next frame is due, or empty if nothing is due */
109 optional<int>
110 VideoView::time_until_next_frame () const
111 {
112         if (length() == dcpomatic::DCPTime()) {
113                 /* There's no content, so this doesn't matter */
114                 return optional<int>();
115         }
116
117         auto const next = position() + one_video_frame();
118         auto const time = _viewer->audio_time().get_value_or(position());
119         if (next < time) {
120                 return 0;
121         }
122         return (next.seconds() - time.seconds()) * 1000;
123 }
124
125
126 void
127 VideoView::start ()
128 {
129         boost::mutex::scoped_lock lm (_mutex);
130         _dropped = 0;
131         _errored = 0;
132         gettimeofday(&_dropped_check_period_start, nullptr);
133 }
134
135
136 bool
137 VideoView::reset_metadata (shared_ptr<const Film> film, dcp::Size player_video_container_size)
138 {
139         auto pv = player_video ();
140         if (!pv.first) {
141                 return false;
142         }
143
144         if (!pv.first->reset_metadata(film, player_video_container_size)) {
145                 return false;
146         }
147
148         update ();
149         return true;
150 }
151
152
153 void
154 VideoView::add_dropped ()
155 {
156         bool too_many = false;
157
158         {
159                 boost::mutex::scoped_lock lm (_mutex);
160                 ++_dropped;
161                 if (_dropped > TOO_MANY_DROPPED_FRAMES) {
162                         struct timeval now;
163                         gettimeofday (&now, nullptr);
164                         double const elapsed = seconds(now) - seconds(_dropped_check_period_start);
165                         too_many = elapsed < TOO_MANY_DROPPED_PERIOD;
166                         _dropped = 0;
167                         _dropped_check_period_start = now;
168                 }
169         }
170
171         if (too_many) {
172                 emit (boost::bind(boost::ref(TooManyDropped)));
173         }
174 }
175
176
177 wxColour
178 VideoView::pad_colour () const
179 {
180         if (_viewer->pad_black()) {
181                 return wxColour(0, 0, 0);
182         } else if (gui_is_dark()) {
183                 return wxColour(50, 50, 50);
184         } else {
185                 return wxColour(240, 240, 240);
186         }
187 }
188