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