nathan@0
|
1 |
/*
|
nathan@0
|
2 |
* MP3/MPlayer plugin to VDR (C++)
|
nathan@0
|
3 |
*
|
nathan@22
|
4 |
* (C) 2001-2009 Stefan Huelswitt <s.huelswitt@gmx.de>
|
nathan@0
|
5 |
*
|
nathan@0
|
6 |
* This code is free software; you can redistribute it and/or
|
nathan@0
|
7 |
* modify it under the terms of the GNU General Public License
|
nathan@0
|
8 |
* as published by the Free Software Foundation; either version 2
|
nathan@0
|
9 |
* of the License, or (at your option) any later version.
|
nathan@0
|
10 |
*
|
nathan@0
|
11 |
* This code is distributed in the hope that it will be useful,
|
nathan@0
|
12 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
nathan@0
|
13 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
nathan@0
|
14 |
* GNU General Public License for more details.
|
nathan@0
|
15 |
*
|
nathan@0
|
16 |
* You should have received a copy of the GNU General Public License
|
nathan@0
|
17 |
* along with this program; if not, write to the Free Software
|
nathan@0
|
18 |
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
nathan@0
|
19 |
* Or, point your browser to http://www.gnu.org/copyleft/gpl.html
|
nathan@0
|
20 |
*/
|
nathan@0
|
21 |
|
nathan@0
|
22 |
#include <ctype.h>
|
nathan@0
|
23 |
#include <stdlib.h>
|
nathan@0
|
24 |
#include <stdio.h>
|
nathan@0
|
25 |
#include <stdarg.h>
|
nathan@0
|
26 |
#include <string.h>
|
nathan@0
|
27 |
#include <fcntl.h>
|
nathan@0
|
28 |
#include <errno.h>
|
nathan@0
|
29 |
#include <sys/ioctl.h>
|
nathan@0
|
30 |
#include <sys/types.h>
|
nathan@0
|
31 |
#include <sys/poll.h>
|
nathan@0
|
32 |
#include <unistd.h>
|
nathan@0
|
33 |
#include <signal.h>
|
nathan@0
|
34 |
#include <wait.h>
|
nathan@0
|
35 |
#include <math.h>
|
nathan@0
|
36 |
#include <locale.h>
|
nathan@0
|
37 |
|
nathan@0
|
38 |
#include <vdr/device.h>
|
nathan@0
|
39 |
#include <vdr/videodir.h>
|
nathan@0
|
40 |
|
nathan@0
|
41 |
#include "common.h"
|
nathan@0
|
42 |
#include "data.h"
|
nathan@0
|
43 |
#include "player-mplayer.h"
|
nathan@0
|
44 |
#include "setup-mplayer.h"
|
nathan@0
|
45 |
|
nathan@0
|
46 |
//#define DEBUG_SLAVE
|
nathan@0
|
47 |
|
nathan@0
|
48 |
#define MPLAYER_VOL_STEP 3.0
|
nathan@0
|
49 |
|
nathan@0
|
50 |
const char *MPlayerCmd = "mplayer.sh";
|
nathan@0
|
51 |
int MPlayerAid=-1;
|
nathan@0
|
52 |
const char *globalResumeDir = 0;
|
nathan@0
|
53 |
|
nathan@0
|
54 |
// -- cMPlayerStatus -----------------------------------------------------------
|
nathan@0
|
55 |
|
nathan@0
|
56 |
cMPlayerStatus *status;
|
nathan@0
|
57 |
|
nathan@0
|
58 |
cMPlayerStatus::cMPlayerStatus(void)
|
nathan@0
|
59 |
{
|
nathan@0
|
60 |
mute=changed=false;
|
nathan@0
|
61 |
volume=0;
|
nathan@0
|
62 |
}
|
nathan@0
|
63 |
|
nathan@0
|
64 |
bool cMPlayerStatus::GetVolume(int &Volume, bool &Mute)
|
nathan@0
|
65 |
{
|
nathan@0
|
66 |
Lock();
|
nathan@0
|
67 |
bool r=changed;
|
nathan@0
|
68 |
// convert according cDvbDevice::SetVolumeDevice();
|
nathan@0
|
69 |
// take into account that VDR does non-linear changes, while
|
nathan@0
|
70 |
// MPlayer does linear volume changes.
|
nathan@0
|
71 |
Volume=((2*volume-volume*volume/255)*100+128)/256;
|
nathan@0
|
72 |
Mute=mute;
|
nathan@0
|
73 |
changed=false;
|
nathan@0
|
74 |
Unlock();
|
nathan@0
|
75 |
return r;
|
nathan@0
|
76 |
}
|
nathan@0
|
77 |
|
nathan@0
|
78 |
void cMPlayerStatus::SetVolume(int Volume, bool Absolute)
|
nathan@0
|
79 |
{
|
nathan@0
|
80 |
Lock();
|
nathan@0
|
81 |
if(Absolute && Volume==0) mute=true;
|
nathan@0
|
82 |
else {
|
nathan@0
|
83 |
if(!Absolute)
|
nathan@0
|
84 |
volume+=Volume;
|
nathan@0
|
85 |
else
|
nathan@0
|
86 |
volume=Volume;
|
nathan@0
|
87 |
if(volume>0) mute=false;
|
nathan@0
|
88 |
}
|
nathan@0
|
89 |
d(printf("status: volume=%d mute=%d\n",volume,mute))
|
nathan@0
|
90 |
changed=true;
|
nathan@0
|
91 |
Unlock();
|
nathan@0
|
92 |
}
|
nathan@0
|
93 |
|
nathan@0
|
94 |
// --- cResumeEntry ------------------------------------------------------------
|
nathan@0
|
95 |
|
nathan@0
|
96 |
class cResumeEntry : public cListObject {
|
nathan@0
|
97 |
public:
|
nathan@0
|
98 |
char *name;
|
nathan@0
|
99 |
float pos;
|
nathan@0
|
100 |
//
|
nathan@0
|
101 |
cResumeEntry(void);
|
nathan@0
|
102 |
~cResumeEntry();
|
nathan@0
|
103 |
};
|
nathan@0
|
104 |
|
nathan@0
|
105 |
cResumeEntry::cResumeEntry(void)
|
nathan@0
|
106 |
{
|
nathan@0
|
107 |
name=0;
|
nathan@0
|
108 |
}
|
nathan@0
|
109 |
|
nathan@0
|
110 |
cResumeEntry::~cResumeEntry()
|
nathan@0
|
111 |
{
|
nathan@0
|
112 |
free(name);
|
nathan@0
|
113 |
}
|
nathan@0
|
114 |
|
nathan@0
|
115 |
// --- cMPlayerResume ----------------------------------------------------------
|
nathan@0
|
116 |
|
nathan@0
|
117 |
#define RESUME_FILE ".mplayer.resume"
|
nathan@0
|
118 |
#define GLOBAL_RESUME_FILE "global.mplayer.resume"
|
nathan@0
|
119 |
|
nathan@0
|
120 |
class cMPlayerResume : public cList<cResumeEntry> {
|
nathan@0
|
121 |
private:
|
nathan@0
|
122 |
char *resfile;
|
nathan@0
|
123 |
bool modified, global;
|
nathan@0
|
124 |
cFileObj *resobj;
|
nathan@0
|
125 |
//
|
nathan@0
|
126 |
bool OpenResume(const cFileObj *file);
|
nathan@0
|
127 |
bool SaveResume(void);
|
nathan@0
|
128 |
void Purge(void);
|
nathan@0
|
129 |
cResumeEntry *FindResume(const cFileObj *file);
|
nathan@0
|
130 |
public:
|
nathan@0
|
131 |
cMPlayerResume(void);
|
nathan@0
|
132 |
~cMPlayerResume();
|
nathan@0
|
133 |
void SetResume(const cFileObj *file, float pos);
|
nathan@0
|
134 |
bool GetResume(const cFileObj *file, float &pos);
|
nathan@0
|
135 |
};
|
nathan@0
|
136 |
|
nathan@0
|
137 |
cMPlayerResume::cMPlayerResume(void)
|
nathan@0
|
138 |
{
|
nathan@0
|
139 |
resfile=0; resobj=0;
|
nathan@0
|
140 |
}
|
nathan@0
|
141 |
|
nathan@0
|
142 |
cMPlayerResume::~cMPlayerResume()
|
nathan@0
|
143 |
{
|
nathan@0
|
144 |
SaveResume();
|
nathan@0
|
145 |
free(resfile);
|
nathan@0
|
146 |
delete resobj;
|
nathan@0
|
147 |
}
|
nathan@0
|
148 |
|
nathan@0
|
149 |
void cMPlayerResume::SetResume(const cFileObj *file, float pos)
|
nathan@0
|
150 |
{
|
nathan@0
|
151 |
if(pos<0.001) pos=0.0;
|
nathan@0
|
152 |
else if(pos>99.0) pos=99.0;
|
nathan@0
|
153 |
cResumeEntry *re;
|
nathan@0
|
154 |
if(OpenResume(file) && (re=FindResume(file))) {
|
nathan@0
|
155 |
d(printf("resume: setting resume %f (update)\n",pos))
|
nathan@0
|
156 |
}
|
nathan@0
|
157 |
else {
|
nathan@0
|
158 |
re=new cResumeEntry;
|
nathan@0
|
159 |
re->name=strdup(global ? file->FullPath() : file->Name());
|
nathan@0
|
160 |
Add(re);
|
nathan@0
|
161 |
d(printf("resume: setting resume %f (new)\n",pos))
|
nathan@0
|
162 |
}
|
nathan@0
|
163 |
re->pos=pos;
|
nathan@0
|
164 |
modified=true;
|
nathan@0
|
165 |
}
|
nathan@0
|
166 |
|
nathan@0
|
167 |
bool cMPlayerResume::GetResume(const cFileObj *file, float &pos)
|
nathan@0
|
168 |
{
|
nathan@0
|
169 |
cResumeEntry *re;
|
nathan@0
|
170 |
if(OpenResume(file) && (re=FindResume(file))) {
|
nathan@0
|
171 |
pos=re->pos;
|
nathan@0
|
172 |
return true;
|
nathan@0
|
173 |
}
|
nathan@0
|
174 |
return false;
|
nathan@0
|
175 |
}
|
nathan@0
|
176 |
|
nathan@0
|
177 |
bool cMPlayerResume::OpenResume(const cFileObj *file)
|
nathan@0
|
178 |
{
|
nathan@0
|
179 |
if(!resfile) {
|
nathan@0
|
180 |
Clear();
|
nathan@0
|
181 |
modified=global=false;
|
nathan@0
|
182 |
free(resfile); resfile=0;
|
nathan@0
|
183 |
delete resobj; resobj=new cFileObj(file);
|
nathan@29
|
184 |
char *s=aprintf(file->Subdir() ? "%s/%s":"%s",file->Source()->BaseDir(),file->Subdir());
|
nathan@0
|
185 |
if(MPlayerSetup.ResumeMode==1 ||
|
nathan@0
|
186 |
(access(s,W_OK) && (errno==EACCES || errno==EROFS))) {
|
nathan@0
|
187 |
global=true;
|
nathan@0
|
188 |
resfile=AddPath(globalResumeDir?globalResumeDir:VideoDirectory,GLOBAL_RESUME_FILE);
|
nathan@0
|
189 |
d(printf("resume: using global file\n"))
|
nathan@0
|
190 |
}
|
nathan@0
|
191 |
else {
|
nathan@0
|
192 |
resfile=AddPath(s,RESUME_FILE);
|
nathan@0
|
193 |
}
|
nathan@0
|
194 |
free(s);
|
nathan@0
|
195 |
d(printf("resume: resume file is '%s'\n",resfile))
|
nathan@0
|
196 |
FILE *f=fopen(resfile,"r");
|
nathan@0
|
197 |
if(f) {
|
nathan@0
|
198 |
d(printf("resume: successfully opened resume file\n"))
|
nathan@0
|
199 |
char line[768];
|
nathan@0
|
200 |
while(fgets(line,sizeof(line),f)) {
|
nathan@0
|
201 |
char name[512];
|
nathan@0
|
202 |
float p;
|
nathan@0
|
203 |
if(sscanf(line,"%f:%511[^\n]",&p,name)==2) {
|
nathan@0
|
204 |
cResumeEntry *re=new cResumeEntry;
|
nathan@0
|
205 |
re->name=strdup(name);
|
nathan@0
|
206 |
re->pos=p;
|
nathan@0
|
207 |
Add(re);
|
nathan@0
|
208 |
}
|
nathan@0
|
209 |
}
|
nathan@0
|
210 |
fclose(f);
|
nathan@0
|
211 |
return true;
|
nathan@0
|
212 |
}
|
nathan@0
|
213 |
else {
|
nathan@0
|
214 |
d(printf("resume: assuming empty resume file\n"))
|
nathan@0
|
215 |
return false;
|
nathan@0
|
216 |
}
|
nathan@0
|
217 |
}
|
nathan@0
|
218 |
return true;
|
nathan@0
|
219 |
}
|
nathan@0
|
220 |
|
nathan@0
|
221 |
bool cMPlayerResume::SaveResume(void)
|
nathan@0
|
222 |
{
|
nathan@0
|
223 |
if(resfile && modified) {
|
nathan@0
|
224 |
Purge();
|
nathan@0
|
225 |
d(printf("resume: saving resume file\n"))
|
nathan@0
|
226 |
cSafeFile f(resfile);
|
nathan@0
|
227 |
if(f.Open()) {
|
nathan@0
|
228 |
for(cResumeEntry *re=First(); re; re=Next(re))
|
nathan@0
|
229 |
fprintf(f,"%06.2f:%s\n",re->pos,re->name);
|
nathan@0
|
230 |
f.Close();
|
nathan@0
|
231 |
return true;
|
nathan@0
|
232 |
}
|
nathan@0
|
233 |
else
|
nathan@0
|
234 |
d(printf("resume: failed to save resume file\n"))
|
nathan@0
|
235 |
}
|
nathan@0
|
236 |
return false;
|
nathan@0
|
237 |
}
|
nathan@0
|
238 |
|
nathan@0
|
239 |
void cMPlayerResume::Purge(void)
|
nathan@0
|
240 |
{
|
nathan@0
|
241 |
d(printf("resume: purging from resume file\n"))
|
nathan@0
|
242 |
for(cResumeEntry *re=First(); re;) {
|
nathan@0
|
243 |
bool del=false;
|
nathan@0
|
244 |
if(re->pos<1.0 || re->pos>99.0) {
|
nathan@0
|
245 |
del=true;
|
nathan@0
|
246 |
d(printf("resume: purging due to position: %s\n",re->name))
|
nathan@0
|
247 |
}
|
nathan@0
|
248 |
else if(!global) {
|
nathan@0
|
249 |
resobj->SetName(re->name);
|
nathan@0
|
250 |
if(access(resobj->FullPath(),F_OK)<0) {
|
nathan@0
|
251 |
del=true;
|
nathan@0
|
252 |
d(printf("resume: purging due to access: %s\n",re->name))
|
nathan@0
|
253 |
}
|
nathan@0
|
254 |
}
|
nathan@0
|
255 |
if(del) {
|
nathan@0
|
256 |
cResumeEntry *n=Next(re);
|
nathan@0
|
257 |
Del(re);
|
nathan@0
|
258 |
modified=true;
|
nathan@0
|
259 |
re=n;
|
nathan@0
|
260 |
}
|
nathan@0
|
261 |
else
|
nathan@0
|
262 |
re=Next(re);
|
nathan@0
|
263 |
}
|
nathan@0
|
264 |
}
|
nathan@0
|
265 |
|
nathan@0
|
266 |
cResumeEntry *cMPlayerResume::FindResume(const cFileObj *file)
|
nathan@0
|
267 |
{
|
nathan@0
|
268 |
if(resfile) {
|
nathan@0
|
269 |
d(printf("resume: searching resume position for '%s'\n",file->Name()))
|
nathan@0
|
270 |
const char *s=global ? file->FullPath() : file->Name();
|
nathan@0
|
271 |
for(cResumeEntry *re=First(); re; re=Next(re))
|
nathan@0
|
272 |
if(!strcasecmp(re->name,s)) {
|
nathan@0
|
273 |
d(printf("resume: found resume position %.1f%%\n",re->pos))
|
nathan@0
|
274 |
return re;
|
nathan@0
|
275 |
}
|
nathan@0
|
276 |
}
|
nathan@0
|
277 |
d(printf("resume: no resume position found\n"))
|
nathan@0
|
278 |
return 0;
|
nathan@0
|
279 |
}
|
nathan@0
|
280 |
|
nathan@0
|
281 |
// --- cMPlayerPlayer ----------------------------------------------------------
|
nathan@0
|
282 |
|
nathan@0
|
283 |
cMPlayerPlayer::cMPlayerPlayer(const cFileObj *File, bool Rewind)
|
nathan@0
|
284 |
:cPlayer(pmExtern_THIS_SHOULD_BE_AVOIDED)
|
nathan@0
|
285 |
{
|
nathan@0
|
286 |
started=slave=brokenPipe=false; run=true; pid=-1; pipefl=0;
|
nathan@0
|
287 |
playMode=pmPlay; index=saveIndex=total=-1; nextTime=nextPos=0;
|
nathan@0
|
288 |
currentName=0;
|
nathan@0
|
289 |
file=new cFileObj(File);
|
nathan@0
|
290 |
rewind=Rewind;
|
nathan@0
|
291 |
resume=MPlayerSetup.ResumeMode ? new cMPlayerResume : 0;
|
nathan@0
|
292 |
}
|
nathan@0
|
293 |
|
nathan@0
|
294 |
cMPlayerPlayer::~cMPlayerPlayer()
|
nathan@0
|
295 |
{
|
nathan@0
|
296 |
Detach();
|
nathan@0
|
297 |
ClosePipe();
|
nathan@0
|
298 |
delete file;
|
nathan@0
|
299 |
delete resume;
|
nathan@0
|
300 |
free(currentName);
|
nathan@0
|
301 |
}
|
nathan@0
|
302 |
|
nathan@0
|
303 |
void cMPlayerPlayer::ClosePipe(void)
|
nathan@0
|
304 |
{
|
nathan@0
|
305 |
if(pipefl&1) close(inpipe[0]);
|
nathan@0
|
306 |
if(pipefl&2) close(inpipe[1]);
|
nathan@0
|
307 |
if(pipefl&4) close(outpipe[0]);
|
nathan@0
|
308 |
if(pipefl&8) close(outpipe[1]);
|
nathan@0
|
309 |
pipefl=0;
|
nathan@0
|
310 |
}
|
nathan@0
|
311 |
|
nathan@0
|
312 |
void cMPlayerPlayer::Activate(bool On)
|
nathan@0
|
313 |
{
|
nathan@0
|
314 |
if(On) {
|
nathan@0
|
315 |
if(file && !started) {
|
nathan@0
|
316 |
if(Fork()) started=true;
|
nathan@0
|
317 |
}
|
nathan@0
|
318 |
}
|
nathan@0
|
319 |
else if(started) {
|
nathan@0
|
320 |
run=false;
|
nathan@0
|
321 |
if(Active()) {
|
nathan@0
|
322 |
if(slave) {
|
nathan@0
|
323 |
Play(); // MPlayer ignores "quit" while paused
|
nathan@0
|
324 |
MPlayerControl("quit");
|
nathan@23
|
325 |
cTimeMs until(3000); // wait some time until MPlayer is gone
|
nathan@0
|
326 |
d(printf("mplayer: waiting for child exit"))
|
nathan@0
|
327 |
while(Active()) {
|
nathan@23
|
328 |
if(until.TimedOut()) {
|
nathan@0
|
329 |
kill(pid,SIGKILL); // kill it anyways
|
nathan@0
|
330 |
d(printf(" SIGKILL"))
|
nathan@0
|
331 |
break;
|
nathan@0
|
332 |
}
|
nathan@23
|
333 |
cCondWait::SleepMs(250);
|
nathan@0
|
334 |
d(printf(".")) d(fflush(stdout))
|
nathan@0
|
335 |
}
|
nathan@0
|
336 |
d(printf("\n"))
|
nathan@0
|
337 |
}
|
nathan@0
|
338 |
else {
|
nathan@0
|
339 |
kill(pid,SIGTERM);
|
nathan@0
|
340 |
d(printf("mplayer: waiting for child exit (non-slave)\n"))
|
nathan@0
|
341 |
}
|
nathan@0
|
342 |
waitpid(pid,0,0);
|
nathan@0
|
343 |
}
|
nathan@0
|
344 |
ClosePipe();
|
nathan@0
|
345 |
Cancel(2);
|
nathan@0
|
346 |
started=slave=false;
|
nathan@0
|
347 |
}
|
nathan@0
|
348 |
}
|
nathan@0
|
349 |
|
nathan@0
|
350 |
bool cMPlayerPlayer::Active(void)
|
nathan@0
|
351 |
{
|
nathan@0
|
352 |
return waitpid(pid,0,WNOHANG)==0;
|
nathan@0
|
353 |
}
|
nathan@0
|
354 |
|
nathan@0
|
355 |
bool cMPlayerPlayer::Fork(void)
|
nathan@0
|
356 |
{
|
nathan@0
|
357 |
if(MPlayerSetup.SlaveMode) {
|
nathan@0
|
358 |
if(pipe(inpipe)==-1) {
|
nathan@0
|
359 |
esyslog("ERROR: pipe failed for inpipe: (%d) %s",errno,strerror(errno));
|
nathan@0
|
360 |
return false;
|
nathan@0
|
361 |
}
|
nathan@0
|
362 |
pipefl|=1+2;
|
nathan@0
|
363 |
if(pipe(outpipe)==-1) {
|
nathan@0
|
364 |
esyslog("ERROR: pipe failed for outpipe: (%d) %s",errno,strerror(errno));
|
nathan@0
|
365 |
return false;
|
nathan@0
|
366 |
}
|
nathan@0
|
367 |
pipefl|=4+8;
|
nathan@0
|
368 |
brokenPipe=false;
|
nathan@0
|
369 |
}
|
nathan@0
|
370 |
|
nathan@0
|
371 |
pid=fork();
|
nathan@0
|
372 |
if(pid==-1) {
|
nathan@0
|
373 |
esyslog("ERROR: fork failed: (%d) %s",errno,strerror(errno));
|
nathan@0
|
374 |
return false;
|
nathan@0
|
375 |
}
|
nathan@0
|
376 |
if(pid==0) { // child
|
nathan@0
|
377 |
dsyslog("mplayer: mplayer child started (pid=%d)", getpid());
|
nathan@0
|
378 |
|
nathan@0
|
379 |
if(MPlayerSetup.SlaveMode) {
|
nathan@0
|
380 |
if(dup2(inpipe[0],STDIN_FILENO)<0 ||
|
nathan@0
|
381 |
dup2(outpipe[1],STDOUT_FILENO)<0 ||
|
nathan@0
|
382 |
dup2(outpipe[1],STDERR_FILENO)<0) {
|
nathan@0
|
383 |
esyslog("ERROR: dup2() failed in MPlayer child: (%d) %s",errno,strerror(errno));
|
nathan@0
|
384 |
exit(127);
|
nathan@0
|
385 |
}
|
nathan@0
|
386 |
}
|
nathan@0
|
387 |
else {
|
nathan@0
|
388 |
int nfd=open("/dev/null",O_RDONLY);
|
nathan@0
|
389 |
if(nfd<0 || dup2(nfd,STDIN_FILENO)<0)
|
nathan@0
|
390 |
esyslog("ERROR: redirect of STDIN failed in MPlayer child: (%d) %s",errno,strerror(errno));
|
nathan@0
|
391 |
}
|
nathan@0
|
392 |
for(int i=getdtablesize()-1; i>STDERR_FILENO; i--) close(i);
|
nathan@0
|
393 |
|
nathan@0
|
394 |
char cmd[64+PATH_MAX*2], aid[20];
|
nathan@0
|
395 |
char *fname=Quote(file->FullPath());
|
nathan@0
|
396 |
if(MPlayerAid>=0) snprintf(aid,sizeof(aid)," AID %d",MPlayerAid);
|
nathan@0
|
397 |
else aid[0]=0;
|
nathan@0
|
398 |
snprintf(cmd,sizeof(cmd),"%s \"%s\" %s%s",MPlayerCmd,fname,MPlayerSetup.SlaveMode?"SLAVE":"",aid);
|
nathan@0
|
399 |
free(fname);
|
nathan@31
|
400 |
// give index of primary dvb adapter device to mplayer via environment variable
|
nathan@31
|
401 |
char dvb[4];
|
nathan@31
|
402 |
snprintf(dvb,sizeof(dvb),"%d",cDevice::PrimaryDevice()->CardIndex()+1);
|
nathan@31
|
403 |
setenv("DVB_DEVICE",dvb,1);
|
nathan@0
|
404 |
execle("/bin/sh","sh","-c",cmd,(char *)0,environ);
|
nathan@0
|
405 |
esyslog("ERROR: exec failed for %s: (%d) %s",cmd,errno,strerror(errno));
|
nathan@0
|
406 |
exit(127);
|
nathan@0
|
407 |
}
|
nathan@0
|
408 |
|
nathan@0
|
409 |
if(MPlayerSetup.SlaveMode) {
|
nathan@0
|
410 |
close(inpipe[0]); pipefl&=~1;
|
nathan@0
|
411 |
close(outpipe[1]); pipefl&=~8;
|
nathan@0
|
412 |
fcntl(outpipe[0],F_SETFL,O_NONBLOCK);
|
nathan@0
|
413 |
run=slave=true;
|
nathan@0
|
414 |
mpVolume=100; // MPlayer startup defaults
|
nathan@0
|
415 |
mpMute=false;
|
nathan@0
|
416 |
Start();
|
nathan@0
|
417 |
}
|
nathan@0
|
418 |
return true;
|
nathan@0
|
419 |
}
|
nathan@0
|
420 |
|
nathan@0
|
421 |
#define BSIZE 1024
|
nathan@0
|
422 |
#define TIME_INT 20
|
nathan@0
|
423 |
#define POS_INT 1
|
nathan@0
|
424 |
|
nathan@0
|
425 |
void cMPlayerPlayer::Action(void)
|
nathan@0
|
426 |
{
|
nathan@0
|
427 |
dsyslog("mplayer: player thread started (pid=%d)", getpid());
|
nathan@0
|
428 |
|
nathan@0
|
429 |
// set locale for correct parsing of MPlayer output.
|
nathan@0
|
430 |
// I don't know if this affects other parts of VDR.
|
nathan@0
|
431 |
const char * const oldLocale=setlocale(LC_NUMERIC,"C");
|
nathan@0
|
432 |
|
nathan@0
|
433 |
pollfd pfd[1];
|
nathan@0
|
434 |
pfd[0].fd=outpipe[0];
|
nathan@0
|
435 |
pfd[0].events=POLLIN;
|
nathan@0
|
436 |
|
nathan@0
|
437 |
float curPos=-1.0, resPos=-1.0;
|
nathan@0
|
438 |
if(resume && !rewind) resume->GetResume(file,resPos);
|
nathan@0
|
439 |
|
nathan@0
|
440 |
char buff[BSIZE+2]; // additional space for fake newline
|
nathan@0
|
441 |
int c=0;
|
nathan@0
|
442 |
bool force=true, slavePatch=false, trustedTotal=false, playBack=false;
|
nathan@0
|
443 |
while(run) {
|
nathan@0
|
444 |
if(playMode==pmPlay && playBack) {
|
nathan@0
|
445 |
int t=time(0);
|
nathan@0
|
446 |
if(t>=nextTime) {
|
nathan@0
|
447 |
MPlayerControl("get_time_length");
|
nathan@0
|
448 |
nextTime=t+(total>0 ? TIME_INT : POS_INT);
|
nathan@0
|
449 |
}
|
nathan@0
|
450 |
if(t>=nextPos) {
|
nathan@0
|
451 |
if(!slavePatch) MPlayerControl("get_percent_pos");
|
nathan@0
|
452 |
nextPos=t+POS_INT;
|
nathan@0
|
453 |
}
|
nathan@0
|
454 |
}
|
nathan@0
|
455 |
|
nathan@0
|
456 |
poll(pfd,1,300);
|
nathan@0
|
457 |
int r=read(outpipe[0],buff+c,BSIZE-c);
|
nathan@0
|
458 |
if(r>0) c+=r;
|
nathan@0
|
459 |
if(c>0) {
|
nathan@0
|
460 |
buff[c]=0; // make sure buffer is NULL terminated
|
nathan@0
|
461 |
char *p;
|
nathan@0
|
462 |
do {
|
nathan@0
|
463 |
p=strpbrk(buff,"\n\r");
|
nathan@0
|
464 |
if(!p && c==BSIZE) { // Full buffer, but no newline found.
|
nathan@0
|
465 |
p=&buff[c]; // Have to fake one.
|
nathan@0
|
466 |
buff[c]='\n'; c++; buff[c]=0;
|
nathan@0
|
467 |
}
|
nathan@0
|
468 |
if(p) {
|
nathan@0
|
469 |
#ifdef DEBUG
|
nathan@0
|
470 |
char cc=*p;
|
nathan@0
|
471 |
#endif
|
nathan@0
|
472 |
*p++=0;
|
nathan@0
|
473 |
float ftime=-1.0, fpos=-1.0;
|
nathan@0
|
474 |
int itime;
|
nathan@0
|
475 |
if(strncmp(buff,"Starting playback",17)==0 ||
|
nathan@0
|
476 |
strncmp(buff,"Starte Wiedergabe",17)==0) {
|
nathan@0
|
477 |
if(!playBack) {
|
nathan@0
|
478 |
playBack=true;
|
nathan@0
|
479 |
nextTime=nextPos=0;
|
nathan@0
|
480 |
d(printf("PLAYBACK STARTED\n"))
|
nathan@0
|
481 |
if(resPos>=0.0) {
|
nathan@0
|
482 |
if(!currentName ||
|
nathan@0
|
483 |
!strcmp(currentName,file->FullPath()) ||
|
nathan@0
|
484 |
!strcmp(currentName,file->Path()))
|
nathan@0
|
485 |
MPlayerControl("seek %.1f 1",resPos);
|
nathan@0
|
486 |
else
|
nathan@0
|
487 |
d(printf("mplayer: no resume, seems to be playlist\n"))
|
nathan@0
|
488 |
}
|
nathan@0
|
489 |
}
|
nathan@0
|
490 |
}
|
nathan@0
|
491 |
else if(strncmp(buff,"Playing ",8)==0 ||
|
nathan@0
|
492 |
strncmp(buff,"Spiele ",7)==0) {
|
nathan@0
|
493 |
nextTime=nextPos=0;
|
nathan@0
|
494 |
index=saveIndex=total=-1;
|
nathan@0
|
495 |
trustedTotal=false;
|
nathan@0
|
496 |
LOCK_THREAD;
|
nathan@0
|
497 |
free(currentName);
|
nathan@0
|
498 |
currentName=strdup(::index(buff,' ')+1);
|
nathan@0
|
499 |
if(currentName[0]) {
|
nathan@0
|
500 |
int l=strlen(currentName);
|
nathan@0
|
501 |
if(currentName[l-1]=='.') currentName[l-1]=0; // skip trailing dot
|
nathan@0
|
502 |
}
|
nathan@0
|
503 |
d(printf("PLAYING %s\n",currentName))
|
nathan@0
|
504 |
}
|
nathan@0
|
505 |
else if(sscanf(buff,"ANS_LENGTH=%d",&itime)==1) {
|
nathan@0
|
506 |
if(itime>0) {
|
nathan@0
|
507 |
total=SecondsToFrames(itime);
|
nathan@0
|
508 |
trustedTotal=true;
|
nathan@0
|
509 |
#ifdef DEBUG_SLAVE
|
nathan@0
|
510 |
printf("sl: ANS_LENGTH=%s (%s)\n",IndexToHMSF(total),buff);
|
nathan@0
|
511 |
#endif
|
nathan@0
|
512 |
}
|
nathan@0
|
513 |
}
|
nathan@0
|
514 |
else if(sscanf(buff,"ANS_PERCENT_POSITION=%d",&itime)==1) {
|
nathan@0
|
515 |
if(itime>0) {
|
nathan@0
|
516 |
curPos=itime;
|
nathan@0
|
517 |
if(total>=0) {
|
nathan@0
|
518 |
index=total*itime/100;
|
nathan@0
|
519 |
#ifdef DEBUG_SLAVE
|
nathan@0
|
520 |
printf("sl: ANS_PERCENT_POS=%s (%s)\n",IndexToHMSF(index),buff);
|
nathan@0
|
521 |
#endif
|
nathan@0
|
522 |
}
|
nathan@0
|
523 |
}
|
nathan@0
|
524 |
}
|
nathan@0
|
525 |
else if(sscanf(buff,"SLAVE: time=%f position=%f",&ftime,&fpos)==2) {
|
nathan@0
|
526 |
curPos=fpos;
|
nathan@0
|
527 |
const float fr=(float)SecondsToFrames(1);
|
nathan@0
|
528 |
itime=(int)(ftime*fr);
|
nathan@0
|
529 |
if(saveIndex<0 || itime>saveIndex) { // prevent index jump-back
|
nathan@0
|
530 |
saveIndex=index=itime;
|
nathan@0
|
531 |
if(!trustedTotal) total=(int)(ftime*fr*100.0/fpos);
|
nathan@0
|
532 |
#ifdef DEBUG_SLAVE
|
nathan@0
|
533 |
printf("sl: SLAVE=%s/%s [%d] (%s)\n",IndexToHMSF(index),IndexToHMSF(total),trustedTotal,buff);
|
nathan@0
|
534 |
#endif
|
nathan@0
|
535 |
}
|
nathan@0
|
536 |
slavePatch=playBack=true;
|
nathan@0
|
537 |
}
|
nathan@0
|
538 |
#ifdef DEBUG
|
nathan@0
|
539 |
else printf("%s%c",buff,cc);
|
nathan@0
|
540 |
#endif
|
nathan@0
|
541 |
c-=(p-buff);
|
nathan@0
|
542 |
memmove(buff,p,c+1);
|
nathan@0
|
543 |
}
|
nathan@0
|
544 |
} while(c>0 && p);
|
nathan@0
|
545 |
}
|
nathan@0
|
546 |
if(playBack) {
|
nathan@0
|
547 |
SetMPlayerVolume(force);
|
nathan@0
|
548 |
force=false;
|
nathan@0
|
549 |
}
|
nathan@0
|
550 |
}
|
nathan@0
|
551 |
|
nathan@0
|
552 |
if(resume && curPos>=0.0) resume->SetResume(file,curPos);
|
nathan@0
|
553 |
|
nathan@0
|
554 |
// restore old locale
|
nathan@0
|
555 |
if(oldLocale) setlocale(LC_NUMERIC,oldLocale);
|
nathan@0
|
556 |
|
nathan@0
|
557 |
dsyslog("mplayer: player thread ended (pid=%d)", getpid());
|
nathan@0
|
558 |
}
|
nathan@0
|
559 |
|
nathan@0
|
560 |
void cMPlayerPlayer::SetMPlayerVolume(bool force)
|
nathan@0
|
561 |
{
|
nathan@0
|
562 |
int volume;
|
nathan@0
|
563 |
bool mute;
|
nathan@0
|
564 |
Lock();
|
nathan@0
|
565 |
if(status->GetVolume(volume,mute) || force) {
|
nathan@0
|
566 |
if(mute) {
|
nathan@0
|
567 |
if(!mpMute) { MPlayerControl("mute"); mpMute=true; }
|
nathan@0
|
568 |
}
|
nathan@0
|
569 |
else {
|
nathan@0
|
570 |
if(mpMute) { MPlayerControl("mute"); mpMute=false; }
|
nathan@0
|
571 |
if(volume!=mpVolume) {
|
nathan@0
|
572 |
MPlayerControl("volume %d 1",volume);
|
nathan@0
|
573 |
mpVolume=volume;
|
nathan@0
|
574 |
}
|
nathan@0
|
575 |
}
|
nathan@0
|
576 |
d(printf("mplayer: volume=%d mpVolume=%d mpMute=%d\n",volume,mpVolume,mpMute))
|
nathan@0
|
577 |
}
|
nathan@0
|
578 |
Unlock();
|
nathan@0
|
579 |
}
|
nathan@0
|
580 |
|
nathan@0
|
581 |
void cMPlayerPlayer::MPlayerControl(const char *format, ...)
|
nathan@0
|
582 |
{
|
nathan@0
|
583 |
if(slave) {
|
nathan@0
|
584 |
va_list ap;
|
nathan@0
|
585 |
va_start(ap,format);
|
nathan@0
|
586 |
char *buff=0;
|
nathan@29
|
587 |
if(vasprintf(&buff,format,ap)<0);
|
nathan@0
|
588 |
Lock();
|
nathan@0
|
589 |
// check for writeable pipe i.e. prevent broken pipe signal
|
nathan@0
|
590 |
if(!brokenPipe) {
|
nathan@0
|
591 |
struct pollfd pfd;
|
nathan@0
|
592 |
pfd.fd=inpipe[1]; pfd.events=POLLOUT; pfd.revents=0;
|
nathan@0
|
593 |
int r=poll(&pfd,1,50);
|
nathan@0
|
594 |
if(r>0) {
|
nathan@0
|
595 |
if(pfd.revents & ~POLLOUT) {
|
nathan@0
|
596 |
d(printf("mplayer: %s%s%s%sin MPlayerControl\n",pfd.revents&POLLOUT?"POLLOUT ":"",pfd.revents&POLLERR?"POLLERR ":"",pfd.revents&POLLHUP?"POLLHUP ":"",pfd.revents&POLLNVAL?"POLLNVAL ":""))
|
nathan@0
|
597 |
brokenPipe=true;
|
nathan@0
|
598 |
}
|
nathan@0
|
599 |
else if(pfd.revents & POLLOUT) {
|
nathan@0
|
600 |
r=write(inpipe[1],buff,strlen(buff));
|
nathan@0
|
601 |
if(r<0) {
|
nathan@0
|
602 |
d(printf("mplayer: pipe write(1) failed: %s\n",strerror(errno)))
|
nathan@0
|
603 |
brokenPipe=true;
|
nathan@0
|
604 |
}
|
nathan@0
|
605 |
else {
|
nathan@0
|
606 |
r=write(inpipe[1],"\n",1);
|
nathan@0
|
607 |
if(r<0) {
|
nathan@0
|
608 |
d(printf("mplayer: pipe write(2) failed: %s\n",strerror(errno)))
|
nathan@0
|
609 |
brokenPipe=true;
|
nathan@0
|
610 |
}
|
nathan@0
|
611 |
}
|
nathan@0
|
612 |
}
|
nathan@0
|
613 |
}
|
nathan@0
|
614 |
else if(r==0) d(printf("mplayer: poll timed out in MPlayerControl (hugh?)\n"))
|
nathan@0
|
615 |
else d(printf("mplayer: poll failed in MPlayerControl: %s\n",strerror(errno)))
|
nathan@0
|
616 |
}
|
nathan@0
|
617 |
else d(printf("mplayer: cmd pipe is broken\n"))
|
nathan@0
|
618 |
Unlock();
|
nathan@0
|
619 |
d(printf("mplayer: slave cmd: %s\n",buff))
|
nathan@0
|
620 |
free(buff);
|
nathan@0
|
621 |
va_end(ap);
|
nathan@0
|
622 |
}
|
nathan@0
|
623 |
}
|
nathan@0
|
624 |
|
nathan@0
|
625 |
void cMPlayerPlayer::Pause(void)
|
nathan@0
|
626 |
{
|
nathan@0
|
627 |
if(slave) {
|
nathan@0
|
628 |
if(playMode==pmPaused) Play();
|
nathan@0
|
629 |
else if(playMode==pmPlay) {
|
nathan@0
|
630 |
playMode=pmPaused;
|
nathan@0
|
631 |
MPlayerControl("pause");
|
nathan@0
|
632 |
}
|
nathan@0
|
633 |
}
|
nathan@0
|
634 |
}
|
nathan@0
|
635 |
|
nathan@0
|
636 |
void cMPlayerPlayer::Play(void)
|
nathan@0
|
637 |
{
|
nathan@0
|
638 |
if(slave) {
|
nathan@0
|
639 |
if(playMode==pmPaused) {
|
nathan@0
|
640 |
playMode=pmPlay;
|
nathan@0
|
641 |
MPlayerControl("pause");
|
nathan@0
|
642 |
}
|
nathan@0
|
643 |
}
|
nathan@0
|
644 |
}
|
nathan@0
|
645 |
|
nathan@0
|
646 |
void cMPlayerPlayer::Goto(int Index, bool percent, bool still)
|
nathan@0
|
647 |
{
|
nathan@0
|
648 |
if(slave) {
|
nathan@0
|
649 |
if(playMode==pmPaused) Play();
|
nathan@0
|
650 |
if(percent) MPlayerControl("seek %d 1",Index);
|
nathan@0
|
651 |
else MPlayerControl("seek %+d 0",Index-(index/SecondsToFrames(1)));
|
nathan@0
|
652 |
if(still) Pause();
|
nathan@0
|
653 |
saveIndex=-1;
|
nathan@0
|
654 |
}
|
nathan@0
|
655 |
}
|
nathan@0
|
656 |
|
nathan@0
|
657 |
void cMPlayerPlayer::SkipSeconds(int secs)
|
nathan@0
|
658 |
{
|
nathan@0
|
659 |
if(slave) {
|
nathan@0
|
660 |
bool p=false;
|
nathan@0
|
661 |
if(playMode==pmPaused) { Play(); p=true; }
|
nathan@0
|
662 |
MPlayerControl("seek %+d 0",secs);
|
nathan@0
|
663 |
if(p) Pause();
|
nathan@0
|
664 |
saveIndex=-1;
|
nathan@0
|
665 |
}
|
nathan@0
|
666 |
}
|
nathan@0
|
667 |
|
nathan@0
|
668 |
void cMPlayerPlayer::KeyCmd(const char *cmd)
|
nathan@0
|
669 |
{
|
nathan@0
|
670 |
if(slave) MPlayerControl(cmd);
|
nathan@0
|
671 |
}
|
nathan@0
|
672 |
|
nathan@0
|
673 |
bool cMPlayerPlayer::GetIndex(int &Current, int &Total, bool SnapToIFrame)
|
nathan@0
|
674 |
{
|
nathan@0
|
675 |
Current=index; Total=total;
|
nathan@0
|
676 |
return true;
|
nathan@0
|
677 |
}
|
nathan@0
|
678 |
|
nathan@0
|
679 |
bool cMPlayerPlayer::GetReplayMode(bool &Play, bool &Forward, int &Speed)
|
nathan@0
|
680 |
{
|
nathan@0
|
681 |
Play=(playMode==pmPlay);
|
nathan@0
|
682 |
Forward=true;
|
nathan@0
|
683 |
Speed=-1;
|
nathan@0
|
684 |
return true;
|
nathan@0
|
685 |
}
|
nathan@0
|
686 |
|
nathan@0
|
687 |
char *cMPlayerPlayer::GetCurrentName(void)
|
nathan@0
|
688 |
{
|
nathan@0
|
689 |
LOCK_THREAD;
|
nathan@0
|
690 |
return currentName ? strdup(currentName) : 0;
|
nathan@0
|
691 |
}
|
nathan@0
|
692 |
|