player-mplayer.c
author nathan
Mon, 17 Aug 2009 20:56:48 +0800
branchtrunk
changeset 29 640ce9201139
parent 23 3b14b8aacaa0
child 31 566c0f412764
permissions -rw-r--r--
fix gcc 4.x warnings
     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     execle("/bin/sh","sh","-c",cmd,(char *)0,environ);
   401     esyslog("ERROR: exec failed for %s: (%d) %s",cmd,errno,strerror(errno));
   402     exit(127);
   403     }
   404 
   405   if(MPlayerSetup.SlaveMode) {
   406     close(inpipe[0]); pipefl&=~1;
   407     close(outpipe[1]); pipefl&=~8;
   408     fcntl(outpipe[0],F_SETFL,O_NONBLOCK);
   409     run=slave=true;
   410     mpVolume=100; // MPlayer startup defaults
   411     mpMute=false;
   412     Start();
   413     }
   414   return true;
   415 }
   416 
   417 #define BSIZE    1024
   418 #define TIME_INT 20
   419 #define POS_INT  1
   420 
   421 void cMPlayerPlayer::Action(void)
   422 {
   423   dsyslog("mplayer: player thread started (pid=%d)", getpid());
   424 
   425   // set locale for correct parsing of MPlayer output.
   426   // I don't know if this affects other parts of VDR.
   427   const char * const oldLocale=setlocale(LC_NUMERIC,"C");
   428 
   429   pollfd pfd[1];
   430   pfd[0].fd=outpipe[0];
   431   pfd[0].events=POLLIN;
   432 
   433   float curPos=-1.0, resPos=-1.0;
   434   if(resume && !rewind) resume->GetResume(file,resPos);
   435 
   436   char buff[BSIZE+2]; // additional space for fake newline
   437   int c=0;
   438   bool force=true, slavePatch=false, trustedTotal=false, playBack=false;
   439   while(run) {
   440     if(playMode==pmPlay && playBack) {
   441       int t=time(0);
   442       if(t>=nextTime) {
   443         MPlayerControl("get_time_length");
   444         nextTime=t+(total>0 ? TIME_INT : POS_INT);
   445         }
   446       if(t>=nextPos) {
   447         if(!slavePatch) MPlayerControl("get_percent_pos");
   448         nextPos=t+POS_INT;
   449         }
   450       }
   451 
   452     poll(pfd,1,300);
   453     int r=read(outpipe[0],buff+c,BSIZE-c);
   454     if(r>0) c+=r;
   455     if(c>0) {
   456       buff[c]=0; // make sure buffer is NULL terminated
   457       char *p;
   458       do {
   459         p=strpbrk(buff,"\n\r");
   460         if(!p && c==BSIZE) { // Full buffer, but no newline found.
   461           p=&buff[c];        // Have to fake one.
   462           buff[c]='\n'; c++; buff[c]=0;
   463           }
   464         if(p) {
   465 #ifdef DEBUG
   466           char cc=*p;
   467 #endif
   468           *p++=0;
   469           float ftime=-1.0, fpos=-1.0;
   470           int itime;
   471           if(strncmp(buff,"Starting playback",17)==0 ||
   472              strncmp(buff,"Starte Wiedergabe",17)==0) {
   473             if(!playBack) {
   474               playBack=true;
   475               nextTime=nextPos=0;
   476               d(printf("PLAYBACK STARTED\n"))
   477               if(resPos>=0.0) {
   478                 if(!currentName ||
   479                    !strcmp(currentName,file->FullPath()) ||
   480                    !strcmp(currentName,file->Path()))
   481                   MPlayerControl("seek %.1f 1",resPos);
   482                 else
   483                   d(printf("mplayer: no resume, seems to be playlist\n"))
   484                 }
   485               }
   486             }
   487           else if(strncmp(buff,"Playing ",8)==0 ||
   488                   strncmp(buff,"Spiele ",7)==0) {
   489             nextTime=nextPos=0;
   490             index=saveIndex=total=-1;
   491             trustedTotal=false;
   492             LOCK_THREAD;
   493             free(currentName);
   494             currentName=strdup(::index(buff,' ')+1);
   495             if(currentName[0]) {
   496               int l=strlen(currentName);
   497               if(currentName[l-1]=='.') currentName[l-1]=0; // skip trailing dot
   498               }
   499             d(printf("PLAYING %s\n",currentName))
   500             }
   501           else if(sscanf(buff,"ANS_LENGTH=%d",&itime)==1) {
   502             if(itime>0) {
   503               total=SecondsToFrames(itime);
   504               trustedTotal=true;
   505 #ifdef DEBUG_SLAVE
   506               printf("sl: ANS_LENGTH=%s (%s)\n",IndexToHMSF(total),buff);
   507 #endif
   508               }
   509             }
   510           else if(sscanf(buff,"ANS_PERCENT_POSITION=%d",&itime)==1) {
   511             if(itime>0) {
   512               curPos=itime;
   513               if(total>=0) {
   514                 index=total*itime/100;
   515 #ifdef DEBUG_SLAVE
   516                 printf("sl: ANS_PERCENT_POS=%s (%s)\n",IndexToHMSF(index),buff);
   517 #endif
   518                 }
   519               }
   520             }
   521           else if(sscanf(buff,"SLAVE: time=%f position=%f",&ftime,&fpos)==2) {
   522             curPos=fpos;
   523             const float fr=(float)SecondsToFrames(1);
   524             itime=(int)(ftime*fr);
   525             if(saveIndex<0 || itime>saveIndex) { // prevent index jump-back
   526               saveIndex=index=itime;
   527               if(!trustedTotal) total=(int)(ftime*fr*100.0/fpos);
   528 #ifdef DEBUG_SLAVE
   529               printf("sl: SLAVE=%s/%s [%d] (%s)\n",IndexToHMSF(index),IndexToHMSF(total),trustedTotal,buff);
   530 #endif
   531               }
   532             slavePatch=playBack=true;
   533             }
   534 #ifdef DEBUG
   535           else printf("%s%c",buff,cc);
   536 #endif
   537           c-=(p-buff);
   538           memmove(buff,p,c+1);
   539           }
   540         } while(c>0 && p);
   541       }
   542     if(playBack) {
   543       SetMPlayerVolume(force);
   544       force=false;
   545       }
   546     }
   547 
   548   if(resume && curPos>=0.0) resume->SetResume(file,curPos);
   549 
   550   // restore old locale
   551   if(oldLocale) setlocale(LC_NUMERIC,oldLocale);
   552 
   553   dsyslog("mplayer: player thread ended (pid=%d)", getpid());
   554 }
   555 
   556 void cMPlayerPlayer::SetMPlayerVolume(bool force)
   557 {
   558   int volume;
   559   bool mute;
   560   Lock();
   561   if(status->GetVolume(volume,mute) || force) {
   562     if(mute) {
   563       if(!mpMute) { MPlayerControl("mute"); mpMute=true; }
   564       }
   565     else {
   566       if(mpMute) { MPlayerControl("mute"); mpMute=false; }
   567       if(volume!=mpVolume) {
   568         MPlayerControl("volume %d 1",volume);
   569         mpVolume=volume;
   570         }
   571       }
   572     d(printf("mplayer: volume=%d mpVolume=%d mpMute=%d\n",volume,mpVolume,mpMute))
   573     }
   574   Unlock();
   575 }
   576 
   577 void cMPlayerPlayer::MPlayerControl(const char *format, ...)
   578 {
   579   if(slave) {
   580     va_list ap;
   581     va_start(ap,format);
   582     char *buff=0;
   583     if(vasprintf(&buff,format,ap)<0);
   584     Lock();
   585     // check for writeable pipe i.e. prevent broken pipe signal
   586     if(!brokenPipe) {
   587       struct pollfd pfd;
   588       pfd.fd=inpipe[1]; pfd.events=POLLOUT; pfd.revents=0;
   589       int r=poll(&pfd,1,50);
   590       if(r>0) {
   591         if(pfd.revents & ~POLLOUT) {
   592           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 ":""))
   593           brokenPipe=true;
   594           }
   595         else if(pfd.revents & POLLOUT) {
   596           r=write(inpipe[1],buff,strlen(buff));
   597           if(r<0) {
   598             d(printf("mplayer: pipe write(1) failed: %s\n",strerror(errno)))
   599             brokenPipe=true;
   600             }
   601           else {
   602             r=write(inpipe[1],"\n",1);
   603             if(r<0) {
   604               d(printf("mplayer: pipe write(2) failed: %s\n",strerror(errno)))
   605               brokenPipe=true;
   606               }
   607             }
   608           }
   609         }
   610       else if(r==0) d(printf("mplayer: poll timed out in MPlayerControl (hugh?)\n"))
   611       else d(printf("mplayer: poll failed in MPlayerControl: %s\n",strerror(errno)))
   612       }
   613     else d(printf("mplayer: cmd pipe is broken\n"))
   614     Unlock();
   615     d(printf("mplayer: slave cmd: %s\n",buff))
   616     free(buff);
   617     va_end(ap);
   618     }
   619 }
   620 
   621 void cMPlayerPlayer::Pause(void)
   622 {
   623   if(slave) {
   624     if(playMode==pmPaused) Play();
   625     else if(playMode==pmPlay) {
   626       playMode=pmPaused;
   627       MPlayerControl("pause");
   628       }
   629     }
   630 }
   631 
   632 void cMPlayerPlayer::Play(void)
   633 {
   634   if(slave) {
   635     if(playMode==pmPaused) {
   636       playMode=pmPlay;
   637       MPlayerControl("pause");
   638       }
   639     }
   640 }
   641 
   642 void cMPlayerPlayer::Goto(int Index, bool percent, bool still)
   643 {
   644   if(slave) {
   645     if(playMode==pmPaused) Play();
   646     if(percent) MPlayerControl("seek %d 1",Index);
   647     else        MPlayerControl("seek %+d 0",Index-(index/SecondsToFrames(1)));
   648     if(still) Pause();
   649     saveIndex=-1;
   650     }
   651 }
   652 
   653 void cMPlayerPlayer::SkipSeconds(int secs)
   654 {
   655   if(slave) {
   656     bool p=false;
   657     if(playMode==pmPaused) { Play(); p=true; }
   658     MPlayerControl("seek %+d 0",secs);
   659     if(p) Pause();
   660     saveIndex=-1;
   661     }
   662 }
   663 
   664 void cMPlayerPlayer::KeyCmd(const char *cmd)
   665 {
   666   if(slave) MPlayerControl(cmd);
   667 }
   668 
   669 bool cMPlayerPlayer::GetIndex(int &Current, int &Total, bool SnapToIFrame)
   670 {
   671   Current=index; Total=total;
   672   return true;
   673 }
   674 
   675 bool cMPlayerPlayer::GetReplayMode(bool &Play, bool &Forward, int &Speed)
   676 {
   677   Play=(playMode==pmPlay);
   678   Forward=true;
   679   Speed=-1;
   680   return true;
   681 }
   682 
   683 char *cMPlayerPlayer::GetCurrentName(void)
   684 {
   685   LOCK_THREAD;
   686   return currentName ? strdup(currentName) : 0;
   687 }
   688