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