Move things round a bit.
[dcpomatic.git] / src / lib / player.cc
1 /*
2     Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <sstream>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <signal.h>
24 #include <fcntl.h>
25 #include <poll.h>
26 #include <boost/thread.hpp>
27 #include <boost/algorithm/string.hpp>
28 #include "player.h"
29 #include "film_state.h"
30 #include "filter.h"
31 #include "screen.h"
32 #include "exceptions.h"
33
34 using namespace std;
35 using namespace boost;
36
37 Player::Player (shared_ptr<const FilmState> fs, shared_ptr<const Screen> screen, Split split)
38         : _stdout_reader_should_run (true)
39         , _position (0)
40         , _paused (false)
41 {
42         assert (fs->format);
43         
44         if (pipe (_mplayer_stdin) < 0) {
45                 throw PlayError ("could not create pipe");
46         }
47
48         if (pipe (_mplayer_stdout) < 0) {
49                 throw PlayError ("could not create pipe");
50         }
51
52         if (pipe (_mplayer_stderr) < 0) {
53                 throw PlayError ("could not create pipe");
54         }
55         
56         int const p = fork ();
57         if (p < 0) {
58                 throw PlayError ("could not fork for mplayer");
59         } else if (p == 0) {
60                 close (_mplayer_stdin[1]);
61                 dup2 (_mplayer_stdin[0], STDIN_FILENO);
62                 
63                 close (_mplayer_stdout[0]);
64                 dup2 (_mplayer_stdout[1], STDOUT_FILENO);
65                 
66                 close (_mplayer_stderr[0]);
67                 dup2 (_mplayer_stderr[1], STDERR_FILENO);
68
69                 char* p[] = { strdup ("TERM=xterm"), strdup ("DISPLAY=:0"), 0 };
70                 environ = p;
71
72                 stringstream s;
73                 s << "/usr/local/bin/mplayer";
74
75                 s << " -vo x11 -noaspect -noautosub -nosub -vo x11 -noborder -slave -quiet -input nodefault-bindings:conf=/dev/null";
76                 s << " -sws " << fs->scaler->mplayer_id ();
77
78                 stringstream vf;
79                 
80                 Position position = screen->position (fs->format);
81                 Size screen_size = screen->size (fs->format);
82                 Size const cropped_size = fs->cropped_size (fs->size);
83                 switch (split) {
84                 case SPLIT_NONE:
85                         vf << crop_string (Position (fs->left_crop, fs->top_crop), cropped_size);
86                         s << " -geometry " << position.x << ":" << position.y;
87                         break;
88                 case SPLIT_LEFT:
89                 {
90                         Size split_size = cropped_size;
91                         split_size.width /= 2;
92                         vf << crop_string (Position (fs->left_crop, fs->top_crop), split_size);
93                         screen_size.width /= 2;
94                         s << " -geometry " << position.x << ":" << position.y;
95                         break;
96                 }
97                 case SPLIT_RIGHT:
98                 {
99                         Size split_size = cropped_size;
100                         split_size.width /= 2;
101                         vf << crop_string (Position (fs->left_crop + split_size.width, fs->top_crop), split_size);
102                         screen_size.width /= 2;
103                         s << " -geometry " << (position.x + screen_size.width) << ":" << position.y;
104                         break;
105                 }
106                 }
107
108                 vf << ",scale=" << screen_size.width << ":" << screen_size.height;
109                 
110                 pair<string, string> filters = Filter::ffmpeg_strings (fs->filters);
111                 
112                 if (!filters.first.empty()) {
113                         vf << "," << filters.first;
114                 }
115                 
116                 if (!filters.second.empty ()) {
117                         vf << ",pp=" << filters.second;
118                 }
119                 
120                 s << " -vf " << vf.str();
121                 s << " \"" << fs->content_path() << "\" ";
122
123                 string cmd (s.str ());
124
125                 vector<string> b = split_at_spaces_considering_quotes (cmd);
126                 
127                 char** cl = new char*[b.size() + 1];
128                 for (vector<string>::size_type i = 0; i < b.size(); ++i) {
129                         cl[i] = strdup (b[i].c_str ());
130                 }
131                 cl[b.size()] = 0;
132                 
133                 execv (cl[0], cl);
134
135                 stringstream e;
136                 e << "exec of mplayer failed " << strerror (errno);
137                 throw PlayError (e.str ());
138                 
139         } else {
140                 _mplayer_pid = p;
141                 command ("pause");
142
143                 _stdout_reader = new boost::thread (boost::bind (&Player::stdout_reader, this));
144         }
145 }
146
147 Player::~Player ()
148 {
149         _stdout_reader_should_run = false;
150         _stdout_reader->join ();
151         delete _stdout_reader;
152         
153         close (_mplayer_stdin[0]);
154         close (_mplayer_stdout[1]);
155         kill (_mplayer_pid, SIGTERM);
156 }
157
158 void
159 Player::command (string c)
160 {
161         char buf[64];
162         snprintf (buf, sizeof (buf), "%s\n", c.c_str ());
163         write (_mplayer_stdin[1], buf, strlen (buf));
164 }
165
166 void
167 Player::stdout_reader ()
168 {
169         while (_stdout_reader_should_run) {
170                 char buf[1024];
171                 int r = read (_mplayer_stdout[0], buf, sizeof (buf));
172                 if (r > 0) {
173                         stringstream s (buf);
174                         while (s.good ()) {
175                                 string line;
176                                 getline (s, line);
177
178                                 vector<string> b;
179                                 split (b, line, is_any_of ("="));
180                                 if (b.size() < 2) {
181                                         continue;
182                                 }
183
184                                 if (b[0] == "ANS_time_pos") {
185                                         set_position (atof (b[1].c_str ()));
186                                 } else if (b[0] == "ANS_pause") {
187                                         set_paused (b[1] == "yes");
188                                 }
189                         }
190                 }
191
192                 usleep (5e5);
193
194                 snprintf (buf, sizeof (buf), "pausing_keep_force get_property time_pos\npausing_keep_force get_property pause\n");
195                 write (_mplayer_stdin[1], buf, strlen (buf));
196         }
197 }
198
199 void
200 Player::set_position (float p)
201 {
202         /* XXX: could be an atomic */
203         boost::mutex::scoped_lock lm (_state_mutex);
204         _position = p;
205 }
206
207 void
208 Player::set_paused (bool p)
209 {
210         /* XXX: could be an atomic */
211         boost::mutex::scoped_lock lm (_state_mutex);
212         _paused = p;
213 }
214
215 float
216 Player::position () const
217 {
218         boost::mutex::scoped_lock lm (_state_mutex);
219         return _position;
220 }
221
222 bool
223 Player::paused () const
224 {
225         boost::mutex::scoped_lock lm (_state_mutex);
226         return _paused;
227 }