67aba36f2ee90a8888947a790a96fd53892704fb
[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 "filter.h"
30 #include "screen.h"
31 #include "exceptions.h"
32
33 using namespace std;
34 using namespace boost;
35
36 Player::Player (shared_ptr<const FilmState> fs, shared_ptr<const Screen> screen, Split split)
37         : _stdout_reader_should_run (true)
38         , _position (0)
39         , _paused (false)
40 {
41         assert (fs->format);
42         
43         if (pipe (_mplayer_stdin) < 0) {
44                 throw PlayError ("could not create pipe");
45         }
46
47         if (pipe (_mplayer_stdout) < 0) {
48                 throw PlayError ("could not create pipe");
49         }
50
51         if (pipe (_mplayer_stderr) < 0) {
52                 throw PlayError ("could not create pipe");
53         }
54         
55         int const p = fork ();
56         if (p < 0) {
57                 throw PlayError ("could not fork for mplayer");
58         } else if (p == 0) {
59                 close (_mplayer_stdin[1]);
60                 dup2 (_mplayer_stdin[0], STDIN_FILENO);
61                 
62                 close (_mplayer_stdout[0]);
63                 dup2 (_mplayer_stdout[1], STDOUT_FILENO);
64                 
65                 close (_mplayer_stderr[0]);
66                 dup2 (_mplayer_stderr[1], STDERR_FILENO);
67
68                 char* p[] = { strdup ("TERM=xterm"), strdup ("DISPLAY=:0"), 0 };
69                 environ = p;
70
71                 stringstream s;
72                 s << "/usr/local/bin/mplayer";
73
74                 s << " -vo x11 -noaspect -noautosub -nosub -vo x11 -noborder -slave -quiet -input nodefault-bindings:conf=/dev/null";
75                 s << " -sws " << fs->scaler->mplayer_id ();
76
77                 stringstream vf;
78                 
79                 Position position = screen->position (fs->format);
80                 Size screen_size = screen->size (fs->format);
81                 Size const cropped_size = fs->cropped_size (fs->size);
82                 switch (split) {
83                 case SPLIT_NONE:
84                         vf << crop_string (Position (fs->left_crop, fs->top_crop), cropped_size);
85                         s << " -geometry " << position.x << ":" << position.y;
86                         break;
87                 case SPLIT_LEFT:
88                 {
89                         Size split_size = cropped_size;
90                         split_size.width /= 2;
91                         vf << crop_string (Position (fs->left_crop, fs->top_crop), split_size);
92                         screen_size.width /= 2;
93                         s << " -geometry " << position.x << ":" << position.y;
94                         break;
95                 }
96                 case SPLIT_RIGHT:
97                 {
98                         Size split_size = cropped_size;
99                         split_size.width /= 2;
100                         vf << crop_string (Position (fs->left_crop + split_size.width, fs->top_crop), split_size);
101                         screen_size.width /= 2;
102                         s << " -geometry " << (position.x + screen_size.width) << ":" << position.y;
103                         break;
104                 }
105                 }
106
107                 vf << ",scale=" << screen_size.width << ":" << screen_size.height;
108                 
109                 pair<string, string> filters = Filter::ffmpeg_strings (fs->filters);
110                 
111                 if (!filters.first.empty()) {
112                         vf << "," << filters.first;
113                 }
114                 
115                 if (!filters.second.empty ()) {
116                         vf << ",pp=" << filters.second;
117                 }
118                 
119                 s << " -vf " << vf.str();
120                 s << " \"" << fs->content_path() << "\" ";
121
122                 string cmd (s.str ());
123
124                 vector<string> b = split_at_spaces_considering_quotes (cmd);
125                 
126                 char** cl = new char*[b.size() + 1];
127                 for (vector<string>::size_type i = 0; i < b.size(); ++i) {
128                         cl[i] = strdup (b[i].c_str ());
129                 }
130                 cl[b.size()] = 0;
131                 
132                 execv (cl[0], cl);
133
134                 stringstream e;
135                 e << "exec of mplayer failed " << strerror (errno);
136                 throw PlayError (e.str ());
137                 
138         } else {
139                 _mplayer_pid = p;
140                 command ("pause");
141
142                 _stdout_reader = new boost::thread (boost::bind (&Player::stdout_reader, this));
143         }
144 }
145
146 Player::~Player ()
147 {
148         _stdout_reader_should_run = false;
149         _stdout_reader->join ();
150         delete _stdout_reader;
151         
152         close (_mplayer_stdin[0]);
153         close (_mplayer_stdout[1]);
154         kill (_mplayer_pid, SIGTERM);
155 }
156
157 void
158 Player::command (string c)
159 {
160         char buf[64];
161         snprintf (buf, sizeof (buf), "%s\n", c.c_str ());
162         write (_mplayer_stdin[1], buf, strlen (buf));
163 }
164
165 void
166 Player::stdout_reader ()
167 {
168         while (_stdout_reader_should_run) {
169                 char buf[1024];
170                 int r = read (_mplayer_stdout[0], buf, sizeof (buf));
171                 if (r > 0) {
172                         stringstream s (buf);
173                         while (s.good ()) {
174                                 string line;
175                                 getline (s, line);
176
177                                 vector<string> b;
178                                 split (b, line, is_any_of ("="));
179                                 if (b.size() < 2) {
180                                         continue;
181                                 }
182
183                                 if (b[0] == "ANS_time_pos") {
184                                         set_position (atof (b[1].c_str ()));
185                                 } else if (b[0] == "ANS_pause") {
186                                         set_paused (b[1] == "yes");
187                                 }
188                         }
189                 }
190
191                 usleep (5e5);
192
193                 snprintf (buf, sizeof (buf), "pausing_keep_force get_property time_pos\npausing_keep_force get_property pause\n");
194                 write (_mplayer_stdin[1], buf, strlen (buf));
195         }
196 }
197
198 void
199 Player::set_position (float p)
200 {
201         /* XXX: could be an atomic */
202         boost::mutex::scoped_lock lm (_state_mutex);
203         _position = p;
204 }
205
206 void
207 Player::set_paused (bool p)
208 {
209         /* XXX: could be an atomic */
210         boost::mutex::scoped_lock lm (_state_mutex);
211         _paused = p;
212 }
213
214 float
215 Player::position () const
216 {
217         boost::mutex::scoped_lock lm (_state_mutex);
218         return _position;
219 }
220
221 bool
222 Player::paused () const
223 {
224         boost::mutex::scoped_lock lm (_state_mutex);
225         return _paused;
226 }