2 * MP3/MPlayer plugin to VDR (C++)
4 * (C) 2001-2009 Stefan Huelswitt <s.huelswitt@gmx.de>
6 * This code is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This code 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.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
29 #include <sys/ioctl.h>
30 #include <sys/types.h>
38 #include <vdr/device.h>
39 #include <vdr/videodir.h>
43 #include "player-mplayer.h"
44 #include "setup-mplayer.h"
48 #define MPLAYER_VOL_STEP 3.0
50 const char *MPlayerCmd = "mplayer.sh";
52 const char *globalResumeDir = 0;
54 // -- cMPlayerStatus -----------------------------------------------------------
56 cMPlayerStatus *status;
58 cMPlayerStatus::cMPlayerStatus(void)
64 bool cMPlayerStatus::GetVolume(int &Volume, bool &Mute)
68 // convert according cDvbDevice::SetVolumeDevice();
69 // take into account that VDR does non-linear changes, while
70 // MPlayer does linear volume changes.
71 Volume=((2*volume-volume*volume/255)*100+128)/256;
78 void cMPlayerStatus::SetVolume(int Volume, bool Absolute)
81 if(Absolute && Volume==0) mute=true;
87 if(volume>0) mute=false;
89 d(printf("status: volume=%d mute=%d\n",volume,mute))
94 // --- cResumeEntry ------------------------------------------------------------
96 class cResumeEntry : public cListObject {
105 cResumeEntry::cResumeEntry(void)
110 cResumeEntry::~cResumeEntry()
115 // --- cMPlayerResume ----------------------------------------------------------
117 #define RESUME_FILE ".mplayer.resume"
118 #define GLOBAL_RESUME_FILE "global.mplayer.resume"
120 class cMPlayerResume : public cList<cResumeEntry> {
123 bool modified, global;
126 bool OpenResume(const cFileObj *file);
127 bool SaveResume(void);
129 cResumeEntry *FindResume(const cFileObj *file);
131 cMPlayerResume(void);
133 void SetResume(const cFileObj *file, float pos);
134 bool GetResume(const cFileObj *file, float &pos);
137 cMPlayerResume::cMPlayerResume(void)
142 cMPlayerResume::~cMPlayerResume()
149 void cMPlayerResume::SetResume(const cFileObj *file, float pos)
151 if(pos<0.001) pos=0.0;
152 else if(pos>99.0) pos=99.0;
154 if(OpenResume(file) && (re=FindResume(file))) {
155 d(printf("resume: setting resume %f (update)\n",pos))
159 re->name=strdup(global ? file->FullPath() : file->Name());
161 d(printf("resume: setting resume %f (new)\n",pos))
167 bool cMPlayerResume::GetResume(const cFileObj *file, float &pos)
170 if(OpenResume(file) && (re=FindResume(file))) {
177 bool cMPlayerResume::OpenResume(const cFileObj *file)
181 modified=global=false;
182 free(resfile); resfile=0;
183 delete resobj; resobj=new cFileObj(file);
184 char *s=aprintf(file->Subdir() ? "%s/%s":"%s",file->Source()->BaseDir(),file->Subdir());
185 if(MPlayerSetup.ResumeMode==1 ||
186 (access(s,W_OK) && (errno==EACCES || errno==EROFS))) {
188 resfile=AddPath(globalResumeDir?globalResumeDir:VideoDirectory,GLOBAL_RESUME_FILE);
189 d(printf("resume: using global file\n"))
192 resfile=AddPath(s,RESUME_FILE);
195 d(printf("resume: resume file is '%s'\n",resfile))
196 FILE *f=fopen(resfile,"r");
198 d(printf("resume: successfully opened resume file\n"))
200 while(fgets(line,sizeof(line),f)) {
203 if(sscanf(line,"%f:%511[^\n]",&p,name)==2) {
204 cResumeEntry *re=new cResumeEntry;
205 re->name=strdup(name);
214 d(printf("resume: assuming empty resume file\n"))
221 bool cMPlayerResume::SaveResume(void)
223 if(resfile && modified) {
225 d(printf("resume: saving resume file\n"))
226 cSafeFile f(resfile);
228 for(cResumeEntry *re=First(); re; re=Next(re))
229 fprintf(f,"%06.2f:%s\n",re->pos,re->name);
234 d(printf("resume: failed to save resume file\n"))
239 void cMPlayerResume::Purge(void)
241 d(printf("resume: purging from resume file\n"))
242 for(cResumeEntry *re=First(); re;) {
244 if(re->pos<1.0 || re->pos>99.0) {
246 d(printf("resume: purging due to position: %s\n",re->name))
249 resobj->SetName(re->name);
250 if(access(resobj->FullPath(),F_OK)<0) {
252 d(printf("resume: purging due to access: %s\n",re->name))
256 cResumeEntry *n=Next(re);
266 cResumeEntry *cMPlayerResume::FindResume(const cFileObj *file)
269 d(printf("resume: searching resume position for '%s'\n",file->Name()))
270 const char *s=global ? file->FullPath() : file->Name();
271 for(cResumeEntry *re=First(); re; re=Next(re))
272 if(!strcasecmp(re->name,s)) {
273 d(printf("resume: found resume position %.1f%%\n",re->pos))
277 d(printf("resume: no resume position found\n"))
281 // --- cMPlayerPlayer ----------------------------------------------------------
283 cMPlayerPlayer::cMPlayerPlayer(const cFileObj *File, bool Rewind)
284 :cPlayer(pmExtern_THIS_SHOULD_BE_AVOIDED)
286 started=slave=brokenPipe=false; run=true; pid=-1; pipefl=0;
287 playMode=pmPlay; index=saveIndex=total=-1; nextTime=nextPos=0;
289 file=new cFileObj(File);
291 resume=MPlayerSetup.ResumeMode ? new cMPlayerResume : 0;
294 cMPlayerPlayer::~cMPlayerPlayer()
303 void cMPlayerPlayer::ClosePipe(void)
305 if(pipefl&1) close(inpipe[0]);
306 if(pipefl&2) close(inpipe[1]);
307 if(pipefl&4) close(outpipe[0]);
308 if(pipefl&8) close(outpipe[1]);
312 void cMPlayerPlayer::Activate(bool On)
315 if(file && !started) {
316 if(Fork()) started=true;
323 Play(); // MPlayer ignores "quit" while paused
324 MPlayerControl("quit");
325 cTimeMs until(3000); // wait some time until MPlayer is gone
326 d(printf("mplayer: waiting for child exit"))
328 if(until.TimedOut()) {
329 kill(pid,SIGKILL); // kill it anyways
330 d(printf(" SIGKILL"))
333 cCondWait::SleepMs(250);
334 d(printf(".")) d(fflush(stdout))
340 d(printf("mplayer: waiting for child exit (non-slave)\n"))
350 bool cMPlayerPlayer::Active(void)
352 return waitpid(pid,0,WNOHANG)==0;
355 bool cMPlayerPlayer::Fork(void)
357 if(MPlayerSetup.SlaveMode) {
358 if(pipe(inpipe)==-1) {
359 esyslog("ERROR: pipe failed for inpipe: (%d) %s",errno,strerror(errno));
363 if(pipe(outpipe)==-1) {
364 esyslog("ERROR: pipe failed for outpipe: (%d) %s",errno,strerror(errno));
373 esyslog("ERROR: fork failed: (%d) %s",errno,strerror(errno));
376 if(pid==0) { // child
377 dsyslog("mplayer: mplayer child started (pid=%d)", getpid());
379 if(MPlayerSetup.SlaveMode) {
380 if(dup2(inpipe[0],STDIN_FILENO)<0 ||
381 dup2(outpipe[1],STDOUT_FILENO)<0 ||
382 dup2(outpipe[1],STDERR_FILENO)<0) {
383 esyslog("ERROR: dup2() failed in MPlayer child: (%d) %s",errno,strerror(errno));
388 int nfd=open("/dev/null",O_RDONLY);
389 if(nfd<0 || dup2(nfd,STDIN_FILENO)<0)
390 esyslog("ERROR: redirect of STDIN failed in MPlayer child: (%d) %s",errno,strerror(errno));
392 for(int i=getdtablesize()-1; i>STDERR_FILENO; i--) close(i);
394 char cmd[64+PATH_MAX*2], aid[20];
395 char *fname=Quote(file->FullPath());
396 if(MPlayerAid>=0) snprintf(aid,sizeof(aid)," AID %d",MPlayerAid);
398 snprintf(cmd,sizeof(cmd),"%s \"%s\" %s%s",MPlayerCmd,fname,MPlayerSetup.SlaveMode?"SLAVE":"",aid);
400 // give index of primary dvb adapter device to mplayer via environment variable
402 snprintf(dvb,sizeof(dvb),"%d",cDevice::PrimaryDevice()->CardIndex()+1);
403 setenv("DVB_DEVICE",dvb,1);
404 execle("/bin/sh","sh","-c",cmd,(char *)0,environ);
405 esyslog("ERROR: exec failed for %s: (%d) %s",cmd,errno,strerror(errno));
409 if(MPlayerSetup.SlaveMode) {
410 close(inpipe[0]); pipefl&=~1;
411 close(outpipe[1]); pipefl&=~8;
412 fcntl(outpipe[0],F_SETFL,O_NONBLOCK);
414 mpVolume=100; // MPlayer startup defaults
425 void cMPlayerPlayer::Action(void)
427 dsyslog("mplayer: player thread started (pid=%d)", getpid());
429 // set locale for correct parsing of MPlayer output.
430 // I don't know if this affects other parts of VDR.
431 const char * const oldLocale=setlocale(LC_NUMERIC,"C");
434 pfd[0].fd=outpipe[0];
435 pfd[0].events=POLLIN;
437 float curPos=-1.0, resPos=-1.0;
438 if(resume && !rewind) resume->GetResume(file,resPos);
440 char buff[BSIZE+2]; // additional space for fake newline
442 bool force=true, slavePatch=false, trustedTotal=false, playBack=false;
444 if(playMode==pmPlay && playBack) {
447 MPlayerControl("get_time_length");
448 nextTime=t+(total>0 ? TIME_INT : POS_INT);
451 if(!slavePatch) MPlayerControl("get_percent_pos");
457 int r=read(outpipe[0],buff+c,BSIZE-c);
460 buff[c]=0; // make sure buffer is NULL terminated
463 p=strpbrk(buff,"\n\r");
464 if(!p && c==BSIZE) { // Full buffer, but no newline found.
465 p=&buff[c]; // Have to fake one.
466 buff[c]='\n'; c++; buff[c]=0;
473 float ftime=-1.0, fpos=-1.0;
475 if(strncmp(buff,"Starting playback",17)==0 ||
476 strncmp(buff,"Starte Wiedergabe",17)==0) {
480 d(printf("PLAYBACK STARTED\n"))
483 !strcmp(currentName,file->FullPath()) ||
484 !strcmp(currentName,file->Path()))
485 MPlayerControl("seek %.1f 1",resPos);
487 d(printf("mplayer: no resume, seems to be playlist\n"))
491 else if(strncmp(buff,"Playing ",8)==0 ||
492 strncmp(buff,"Spiele ",7)==0) {
494 index=saveIndex=total=-1;
498 currentName=strdup(::index(buff,' ')+1);
500 int l=strlen(currentName);
501 if(currentName[l-1]=='.') currentName[l-1]=0; // skip trailing dot
503 d(printf("PLAYING %s\n",currentName))
505 else if(sscanf(buff,"ANS_LENGTH=%d",&itime)==1) {
507 total=SecondsToFrames(itime);
510 printf("sl: ANS_LENGTH=%s (%s)\n",IndexToHMSF(total),buff);
514 else if(sscanf(buff,"ANS_PERCENT_POSITION=%d",&itime)==1) {
518 index=total*itime/100;
520 printf("sl: ANS_PERCENT_POS=%s (%s)\n",IndexToHMSF(index),buff);
525 else if(sscanf(buff,"SLAVE: time=%f position=%f",&ftime,&fpos)==2) {
527 const float fr=(float)SecondsToFrames(1);
528 itime=(int)(ftime*fr);
529 if(saveIndex<0 || itime>saveIndex) { // prevent index jump-back
530 saveIndex=index=itime;
531 if(!trustedTotal) total=(int)(ftime*fr*100.0/fpos);
533 printf("sl: SLAVE=%s/%s [%d] (%s)\n",IndexToHMSF(index),IndexToHMSF(total),trustedTotal,buff);
536 slavePatch=playBack=true;
539 else printf("%s%c",buff,cc);
547 SetMPlayerVolume(force);
552 if(resume && curPos>=0.0) resume->SetResume(file,curPos);
554 // restore old locale
555 if(oldLocale) setlocale(LC_NUMERIC,oldLocale);
557 dsyslog("mplayer: player thread ended (pid=%d)", getpid());
560 void cMPlayerPlayer::SetMPlayerVolume(bool force)
565 if(status->GetVolume(volume,mute) || force) {
567 if(!mpMute) { MPlayerControl("mute"); mpMute=true; }
570 if(mpMute) { MPlayerControl("mute"); mpMute=false; }
571 if(volume!=mpVolume) {
572 MPlayerControl("volume %d 1",volume);
576 d(printf("mplayer: volume=%d mpVolume=%d mpMute=%d\n",volume,mpVolume,mpMute))
581 void cMPlayerPlayer::MPlayerControl(const char *format, ...)
587 if(vasprintf(&buff,format,ap)<0);
589 // check for writeable pipe i.e. prevent broken pipe signal
592 pfd.fd=inpipe[1]; pfd.events=POLLOUT; pfd.revents=0;
593 int r=poll(&pfd,1,50);
595 if(pfd.revents & ~POLLOUT) {
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 ":""))
599 else if(pfd.revents & POLLOUT) {
600 r=write(inpipe[1],buff,strlen(buff));
602 d(printf("mplayer: pipe write(1) failed: %s\n",strerror(errno)))
606 r=write(inpipe[1],"\n",1);
608 d(printf("mplayer: pipe write(2) failed: %s\n",strerror(errno)))
614 else if(r==0) d(printf("mplayer: poll timed out in MPlayerControl (hugh?)\n"))
615 else d(printf("mplayer: poll failed in MPlayerControl: %s\n",strerror(errno)))
617 else d(printf("mplayer: cmd pipe is broken\n"))
619 d(printf("mplayer: slave cmd: %s\n",buff))
625 void cMPlayerPlayer::Pause(void)
628 if(playMode==pmPaused) Play();
629 else if(playMode==pmPlay) {
631 MPlayerControl("pause");
636 void cMPlayerPlayer::Play(void)
639 if(playMode==pmPaused) {
641 MPlayerControl("pause");
646 void cMPlayerPlayer::Goto(int Index, bool percent, bool still)
649 if(playMode==pmPaused) Play();
650 if(percent) MPlayerControl("seek %d 1",Index);
651 else MPlayerControl("seek %+d 0",Index-(index/SecondsToFrames(1)));
657 void cMPlayerPlayer::SkipSeconds(int secs)
661 if(playMode==pmPaused) { Play(); p=true; }
662 MPlayerControl("seek %+d 0",secs);
668 void cMPlayerPlayer::KeyCmd(const char *cmd)
670 if(slave) MPlayerControl(cmd);
673 bool cMPlayerPlayer::GetIndex(int &Current, int &Total, bool SnapToIFrame)
675 Current=index; Total=total;
679 bool cMPlayerPlayer::GetReplayMode(bool &Play, bool &Forward, int &Speed)
681 Play=(playMode==pmPlay);
687 char *cMPlayerPlayer::GetCurrentName(void)
690 return currentName ? strdup(currentName) : 0;