Support subs and tidy up a few things.
[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::pair;
32 using std::shared_ptr;
33 using boost::optional;
34
35
36 static constexpr int TOO_MANY_DROPPED_FRAMES = 20;
37 static constexpr int TOO_MANY_DROPPED_PERIOD = 5.0;
38
39
40 VideoView::VideoView (FilmViewer* viewer)
41         : _viewer (viewer)
42         , _state_timer ("viewer")
43 {
44
45 }
46
47
48 void
49 VideoView::clear ()
50 {
51         boost::mutex::scoped_lock lm (_mutex);
52         _player_video.first.reset ();
53         _player_video.second = dcpomatic::DCPTime ();
54 }
55
56
57 /** Could be called from any thread.
58  *  @param non_blocking true to return false quickly if no video is available quickly.
59  *  @return FAIL if there's no frame, AGAIN if the method should be called again, or SUCCESS
60  *  if there is a frame.
61  */
62 VideoView::NextFrameResult
63 VideoView::get_next_frame (bool non_blocking)
64 {
65         if (length() == dcpomatic::DCPTime()) {
66                 return FAIL;
67         }
68
69         auto butler = _viewer->butler ();
70         if (!butler) {
71                 return FAIL;
72         }
73         add_get ();
74
75         boost::mutex::scoped_lock lm (_mutex);
76
77         do {
78                 Butler::Error e;
79                 auto pv = butler->get_video (!non_blocking, &e);
80                 if (e.code == Butler::Error::DIED) {
81                         LOG_ERROR ("Butler died with %1", e.summary());
82                 }
83                 if (!pv.first) {
84                         return e.code == Butler::Error::AGAIN ? AGAIN : FAIL;
85                 }
86                 _player_video = pv;
87         } while (
88                 _player_video.first &&
89                 _three_d &&
90                 _eyes != _player_video.first->eyes() &&
91                 _player_video.first->eyes() != Eyes::BOTH
92                 );
93
94         if (_player_video.first && _player_video.first->error()) {
95                 ++_errored;
96         }
97
98         return SUCCESS;
99 }
100
101
102 dcpomatic::DCPTime
103 VideoView::one_video_frame () const
104 {
105         return dcpomatic::DCPTime::from_frames (1, video_frame_rate());
106 }
107
108
109 /** @return Time in ms until the next frame is due, or empty if nothing is due */
110 optional<int>
111 VideoView::time_until_next_frame () const
112 {
113         if (length() == dcpomatic::DCPTime()) {
114                 /* There's no content, so this doesn't matter */
115                 return optional<int>();
116         }
117
118         auto const next = position() + one_video_frame();
119         auto const time = _viewer->audio_time().get_value_or(position());
120         if (next < time) {
121                 return 0;
122         }
123         return (next.seconds() - time.seconds()) * 1000;
124 }
125
126
127 void
128 VideoView::start ()
129 {
130         boost::mutex::scoped_lock lm (_mutex);
131         _dropped = 0;
132         _errored = 0;
133         gettimeofday(&_dropped_check_period_start, nullptr);
134 }
135
136
137 bool
138 VideoView::reset_metadata (shared_ptr<const Film> film, dcp::Size player_video_container_size)
139 {
140         auto pv = player_video ();
141         if (!pv.first) {
142                 return false;
143         }
144
145         if (!pv.first->reset_metadata(film, player_video_container_size)) {
146                 return false;
147         }
148
149         update ();
150         return true;
151 }
152
153
154 void
155 VideoView::add_dropped ()
156 {
157         bool too_many = false;
158
159         {
160                 boost::mutex::scoped_lock lm (_mutex);
161                 ++_dropped;
162                 if (_dropped > TOO_MANY_DROPPED_FRAMES) {
163                         struct timeval now;
164                         gettimeofday (&now, nullptr);
165                         double const elapsed = seconds(now) - seconds(_dropped_check_period_start);
166                         too_many = elapsed < TOO_MANY_DROPPED_PERIOD;
167                         _dropped = 0;
168                         _dropped_check_period_start = now;
169                 }
170         }
171
172         if (too_many) {
173                 emit (boost::bind(boost::ref(TooManyDropped)));
174         }
175 }
176
177
178 wxColour
179 VideoView::pad_colour () const
180 {
181         if (_viewer->pad_black()) {
182                 return wxColour(0, 0, 0);
183         } else if (gui_is_dark()) {
184                 return wxColour(50, 50, 50);
185         } else {
186                 return wxColour(240, 240, 240);
187         }
188 }
189