mp3.c
author nathan
Sat, 29 Dec 2007 14:49:09 +0100
branchtrunk
changeset 2 4c1f7b705009
parent 0 474a1293c3c0
child 6 111ef8181229
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 <stdlib.h>
    23 #include <getopt.h>
    24 #include <strings.h>
    25 #include <typeinfo>
    26 
    27 #include "common.h"
    28 
    29 #include <vdr/menuitems.h>
    30 #include <vdr/status.h>
    31 #include <vdr/plugin.h>
    32 #if APIVERSNUM >= 10307
    33 #include <vdr/interface.h>
    34 #include <vdr/skins.h>
    35 #endif
    36 
    37 #include "setup.h"
    38 #include "setup-mp3.h"
    39 #include "data-mp3.h"
    40 #include "data-src.h"
    41 #include "player-mp3.h"
    42 #include "menu.h"
    43 #include "menu-async.h"
    44 #include "decoder.h"
    45 #include "i18n.h"
    46 #include "version.h"
    47 #include "service.h"
    48 
    49 #ifdef DEBUG
    50 #include <mad.h>
    51 #endif
    52 
    53 const char *sourcesSub=0;
    54 cFileSources MP3Sources;
    55 
    56 static const char *plugin_name=0;
    57 
    58 // --- cMenuSetupMP3 --------------------------------------------------------
    59 
    60 class cMenuSetupMP3 : public cMenuSetupPage {
    61 private:
    62   cMP3Setup data;
    63   //
    64   const char *cddb[3], *disp[2], *scan[3], *bgr[3];
    65   const char *aout[AUDIOOUTMODES];
    66   int amode, amodes[AUDIOOUTMODES];
    67 protected:
    68   virtual void Store(void);
    69 public:
    70   cMenuSetupMP3(void);
    71   };
    72 
    73 cMenuSetupMP3::cMenuSetupMP3(void)
    74 {
    75   static const char allowed[] = { "abcdefghijklmnopqrstuvwxyz0123456789-_" };
    76   int numModes=0;
    77   aout[numModes]=trVDR("DVB"); amodes[numModes]=AUDIOOUTMODE_DVB; numModes++;
    78 #ifdef WITH_OSS
    79   aout[numModes]=tr("OSS"); amodes[numModes]=AUDIOOUTMODE_OSS; numModes++;
    80 #endif
    81   data=MP3Setup;
    82   amode=0;
    83   for(int i=0; i<numModes; i++)
    84     if(amodes[i]==data.AudioOutMode) { amode=i; break; }
    85 
    86   SetSection(tr("MP3"));
    87   Add(new cMenuEditStraItem(tr("Setup.MP3$Audio output mode"),     &amode,numModes,aout));
    88   Add(new cMenuEditBoolItem(tr("Setup.MP3$Audio mode"),            &data.AudioMode, tr("Round"), tr("Dither")));
    89   Add(new cMenuEditBoolItem(tr("Setup.MP3$Use 48kHz mode only"),   &data.Only48kHz));
    90 #if APIVERSNUM >= 10307
    91   disp[0]=tr("classic");
    92   disp[1]=tr("via skin");
    93   Add(new cMenuEditStraItem(tr("Setup.MP3$Replay display"),        &data.ReplayDisplay, 2, disp));
    94 #endif
    95   Add(new cMenuEditIntItem( tr("Setup.MP3$Display mode"),          &data.DisplayMode, 1, 3));
    96   bgr[0]=tr("Black");
    97   bgr[1]=tr("Live");
    98   bgr[2]=tr("Images");
    99   Add(new cMenuEditStraItem(tr("Setup.MP3$Background mode"),       &data.BackgrMode, 3, bgr));
   100   Add(new cMenuEditBoolItem(tr("Setup.MP3$Initial loop mode"),     &data.InitLoopMode));
   101   Add(new cMenuEditBoolItem(tr("Setup.MP3$Initial shuffle mode"),  &data.InitShuffleMode));
   102   Add(new cMenuEditBoolItem(tr("Setup.MP3$Abort player at end of list"),&data.AbortAtEOL));
   103   scan[0]=tr("disabled");
   104   scan[1]=tr("ID3 only");
   105   scan[2]=tr("ID3 & Level");
   106   Add(new cMenuEditStraItem(tr("Setup.MP3$Background scan"),       &data.BgrScan, 3, scan));
   107   Add(new cMenuEditBoolItem(tr("Setup.MP3$Editor display mode"),   &data.EditorMode, tr("Filenames"), tr("ID3 names")));
   108   Add(new cMenuEditBoolItem(tr("Setup.MP3$Mainmenu mode"),         &data.MenuMode, tr("Playlists"), tr("Browser")));
   109   Add(new cMenuEditBoolItem(tr("Setup.MP3$Keep selection menu"),   &data.KeepSelect));
   110   Add(new cMenuEditBoolItem(tr("Setup.MP3$Title/Artist order"),    &data.TitleArtistOrder, tr("Normal"), tr("Reversed")));
   111   Add(new cMenuEditBoolItem(tr("Hide mainmenu entry"),             &data.HideMainMenu));
   112   Add(new cMenuEditIntItem( tr("Setup.MP3$Normalizer level"),      &data.TargetLevel, 0, MAX_TARGET_LEVEL));
   113   Add(new cMenuEditIntItem( tr("Setup.MP3$Limiter level"),         &data.LimiterLevel, MIN_LIMITER_LEVEL, 100));
   114   Add(new cMenuEditBoolItem(tr("Setup.MP3$Use HTTP proxy"),        &data.UseProxy));
   115   Add(new cMenuEditStrItem( tr("Setup.MP3$HTTP proxy host"),       data.ProxyHost,MAX_HOSTNAME,allowed));
   116   Add(new cMenuEditIntItem( tr("Setup.MP3$HTTP proxy port"),       &data.ProxyPort,1,65535));
   117   cddb[0]=tr("disabled");
   118   cddb[1]=tr("local only");
   119   cddb[2]=tr("local&remote");
   120   Add(new cMenuEditStraItem(tr("Setup.MP3$CDDB for CD-Audio"),     &data.UseCddb,3,cddb));
   121   Add(new cMenuEditStrItem( tr("Setup.MP3$CDDB server"),           data.CddbHost,MAX_HOSTNAME,allowed));
   122   Add(new cMenuEditIntItem( tr("Setup.MP3$CDDB port"),             &data.CddbPort,1,65535));
   123 }
   124 
   125 void cMenuSetupMP3::Store(void)
   126 {
   127   data.AudioOutMode=amodes[amode];
   128 
   129   MP3Setup=data;
   130   SetupStore("InitLoopMode",     MP3Setup.InitLoopMode   );
   131   SetupStore("InitShuffleMode",  MP3Setup.InitShuffleMode);
   132   SetupStore("AudioMode",        MP3Setup.AudioMode      );
   133   SetupStore("AudioOutMode",     MP3Setup.AudioOutMode   );
   134   SetupStore("BgrScan",          MP3Setup.BgrScan        );
   135   SetupStore("EditorMode",       MP3Setup.EditorMode     );
   136   SetupStore("DisplayMode",      MP3Setup.DisplayMode    );
   137   SetupStore("BackgrMode",       MP3Setup.BackgrMode     );
   138   SetupStore("MenuMode",         MP3Setup.MenuMode       );
   139   SetupStore("TargetLevel",      MP3Setup.TargetLevel    );
   140   SetupStore("LimiterLevel",     MP3Setup.LimiterLevel   );
   141   SetupStore("Only48kHz",        MP3Setup.Only48kHz      );
   142   SetupStore("UseProxy",         MP3Setup.UseProxy       );
   143   SetupStore("ProxyHost",        MP3Setup.ProxyHost      );
   144   SetupStore("ProxyPort",        MP3Setup.ProxyPort      );
   145   SetupStore("UseCddb",          MP3Setup.UseCddb        );
   146   SetupStore("CddbHost",         MP3Setup.CddbHost       );
   147   SetupStore("CddbPort",         MP3Setup.CddbPort       );
   148   SetupStore("AbortAtEOL",       MP3Setup.AbortAtEOL     );
   149 #if APIVERSNUM >= 10307
   150   SetupStore("ReplayDisplay",    MP3Setup.ReplayDisplay  );
   151 #endif
   152   SetupStore("HideMainMenu",     MP3Setup.HideMainMenu   );
   153   SetupStore("KeepSelect",       MP3Setup.KeepSelect     );
   154   SetupStore("TitleArtistOrder", MP3Setup.TitleArtistOrder);
   155 }
   156 
   157 // --- cAsyncStatus ------------------------------------------------------------
   158 
   159 cAsyncStatus asyncStatus;
   160 
   161 cAsyncStatus::cAsyncStatus(void)
   162 {
   163   text=0;
   164   changed=false;
   165 }
   166 
   167 cAsyncStatus::~cAsyncStatus()
   168 {
   169   free((void *)text);
   170 }
   171 
   172 void cAsyncStatus::Set(const char *Text)
   173 {
   174   Lock();
   175   free((void *)text);
   176   text=Text ? strdup(Text) : 0;
   177   changed=true;
   178   Unlock();
   179 }
   180 
   181 const char *cAsyncStatus::Begin(void)
   182 {
   183   Lock();
   184   return text;
   185 }
   186 
   187 void cAsyncStatus::Finish(void)
   188 {
   189   changed=false;
   190   Unlock();
   191 }
   192 
   193 // --- --------------------------------------------------------------------
   194 
   195 static const char *TitleArtist(const char *title, const char *artist)
   196 {
   197   static char buf[256]; // clearly not multi-thread save!
   198   char *fmt;
   199   if(artist && artist[0]) {
   200     if(MP3Setup.TitleArtistOrder) fmt="%2$s - %1$s";
   201     else  fmt="%s - %s";
   202     }
   203   else fmt="%s";
   204   snprintf(buf,sizeof(buf),fmt,title,artist);
   205   return buf;
   206 }
   207 
   208 // --- cMP3Control --------------------------------------------------------
   209 
   210 #if APIVERSNUM >= 10307
   211 #define clrBackground clrGray50
   212 #define eDvbColor int
   213 #define MAXROWS 120
   214 #define INLINE
   215 #else
   216 #define MAXROWS MAXOSDHEIGHT
   217 #define INLINE inline
   218 #endif
   219 
   220 class cMP3Control : public cControl {
   221 private:
   222 #if APIVERSNUM >= 10307
   223   cOsd *osd;
   224   const cFont *font;
   225   cSkinDisplayReplay *disp;
   226 #else
   227   bool statusInterfaceOpen;
   228 #endif
   229   int bw, bh, bwc, fw, fh;
   230   //
   231   cMP3Player *player;
   232   bool visible, shown, bigwin, statusActive;
   233   time_t timeoutShow, greentime, oktime;
   234   int lastkeytime, number;
   235   bool selecting, selecthide;
   236   //
   237   cMP3PlayInfo *lastMode;
   238   time_t fliptime, listtime;
   239   int hashlist[MAXROWS];
   240   int flip, flipint, top, rows;
   241   int lastIndex, lastTotal, lastTop;
   242   int framesPerSecond;
   243   //
   244   bool jumpactive, jumphide, jumpsecs;
   245   int jumpmm;
   246   //
   247   void ShowTimed(int Seconds=0);
   248   void ShowProgress(bool open=false, bool bigWin=false);
   249   void ShowStatus(bool force);
   250   void HideStatus(void);
   251   void DisplayInfo(const char *s=0);
   252   void JumpDisplay(void);
   253   void JumpProcess(eKeys Key);
   254   void Jump(void);
   255   void Stop(void);
   256   INLINE void Write(int x, int y, int w, const char *text, eDvbColor fg=clrWhite, eDvbColor bg=clrBackground);
   257   INLINE void Fill(int x, int y, int w, int h, eDvbColor fg);
   258   inline void Flush(void);
   259 public:
   260   cMP3Control(void);
   261   virtual ~cMP3Control();
   262   virtual eOSState ProcessKey(eKeys Key);
   263   virtual void Show(void) { ShowTimed(); }
   264   virtual void Hide(void);
   265   bool Visible(void) { return visible; }
   266   static bool SetPlayList(cPlayList *plist);
   267   };
   268 
   269 cMP3Control::cMP3Control(void)
   270 :cControl(player=new cMP3Player)
   271 {
   272   visible=shown=bigwin=selecting=selecthide=jumpactive=jumphide=statusActive=false;
   273   timeoutShow=greentime=oktime=0;
   274   lastkeytime=number=0;
   275   lastMode=0;
   276   framesPerSecond=SecondsToFrames(1);
   277 #if APIVERSNUM >= 10307
   278   osd=0; disp=0;
   279   font=cFont::GetFont(fontOsd);
   280 #else
   281   statusInterfaceOpen=false;
   282 #endif
   283 #if APIVERSNUM >= 10338
   284   cStatus::MsgReplaying(this,"MP3",0,true);
   285 #else
   286   cStatus::MsgReplaying(this,"MP3");
   287 #endif
   288 }
   289 
   290 cMP3Control::~cMP3Control()
   291 {
   292   delete lastMode;
   293   Hide();
   294   Stop();
   295 }
   296 
   297 void cMP3Control::Stop(void)
   298 {
   299 #if APIVERSNUM >= 10338
   300   cStatus::MsgReplaying(this,0,0,false);
   301 #else
   302   cStatus::MsgReplaying(this,0);
   303 #endif
   304   delete player; player=0;
   305   mgr->Halt();
   306   mgr->Flush(); //XXX remove later
   307 }
   308 
   309 bool cMP3Control::SetPlayList(cPlayList *plist)
   310 {
   311   bool res;
   312   cControl *control=cControl::Control();
   313   // is there a running MP3 player?
   314   if(control && typeid(*control)==typeid(cMP3Control)) {
   315     // add songs to running playlist
   316     mgr->Add(plist);
   317     res=true;
   318     }
   319   else {
   320     mgr->Flush();
   321     mgr->Add(plist);
   322     cControl::Launch(new cMP3Control);
   323     res=false;
   324     }
   325   delete plist;
   326   return res;
   327 }
   328 
   329 void cMP3Control::ShowTimed(int Seconds)
   330 {
   331   if(!visible) {
   332     ShowProgress(true);
   333     if(Seconds>0) timeoutShow=time(0)+Seconds;
   334     }
   335 }
   336 
   337 void cMP3Control::Hide(void)
   338 {
   339   HideStatus();
   340   if(visible) {
   341 #if APIVERSNUM >= 10307
   342     delete osd; osd=0;
   343     delete disp; disp=0;
   344 #else
   345     Interface->Close();
   346 #endif
   347     visible=bigwin=false;
   348 #if APIVERSNUM >= 10500
   349     SetNeedsFastResponse(false);
   350 #else
   351     needsFastResponse=false;
   352 #endif
   353     }
   354 }
   355 
   356 void cMP3Control::ShowStatus(bool force)
   357 {
   358   if((asyncStatus.Changed() || (force && !statusActive)) && !jumpactive) {
   359     const char *text=asyncStatus.Begin();
   360     if(text) {
   361 #if APIVERSNUM >= 10307
   362       if(MP3Setup.ReplayDisplay || !osd) {
   363         if(statusActive) Skins.Message(mtStatus,0);
   364         Skins.Message(mtStatus,text);
   365         }
   366       else {
   367         if(!statusActive) osd->SaveRegion(0,bh-2*fh,bw-1,bh-fh-1);
   368         osd->DrawText(0,bh-2*fh,text,clrBlack,clrCyan,font,bw,fh,taCenter);
   369         osd->Flush();
   370         }
   371 #else
   372       if(!Interface->IsOpen()) {
   373         Interface->Open(0,-1);
   374         statusInterfaceOpen=true;
   375         }
   376       Interface->Status(text);
   377       Interface->Flush();
   378 #endif
   379       statusActive=true;
   380       }
   381     else
   382       HideStatus();
   383     asyncStatus.Finish();
   384     }
   385 }
   386 
   387 void cMP3Control::HideStatus(void)
   388 {
   389   if(statusActive) {
   390 #if APIVERSNUM >= 10307
   391     if(MP3Setup.ReplayDisplay || !osd)
   392       Skins.Message(mtStatus,0);
   393     else {
   394       osd->RestoreRegion();
   395       osd->Flush();
   396       }
   397 #else
   398     if(statusInterfaceOpen) {
   399       Interface->Close();
   400       statusInterfaceOpen=false;
   401       }
   402     else {
   403       Interface->Status(0);
   404       Interface->Flush();
   405       }
   406 #endif
   407     }
   408   statusActive=false;
   409 }
   410 
   411 #define CTAB    11 // some tabbing values for the progress display
   412 #define CTAB2   5
   413 
   414 void cMP3Control::Write(int x, int y, int w, const char *text, eDvbColor fg, eDvbColor bg)
   415 {
   416 #if APIVERSNUM >= 10307
   417   if(osd) {
   418     //d(printf("write x=%d y=%d w=%d ->",x,y,w))
   419     x*=fw; if(x<0) x+=bw;
   420     y*=fh; if(y<0) y+=bh;
   421     osd->DrawText(x,y,text,fg,bg,font,w*fw);
   422     //d(printf(" x=%d y=%d w=%d\n",x,y,w*fw))
   423     }
   424 #else
   425   if(w>0) Fill(x,y,w,1,bg);
   426   Interface->Write(x,y,text,fg,bg);
   427 #endif
   428 }
   429 
   430 void cMP3Control::Fill(int x, int y, int w, int h, eDvbColor fg)
   431 {
   432 #if APIVERSNUM >= 10307
   433   if(osd) {
   434     //d(printf("fill x=%d y=%d w=%d h=%d ->",x,y,w,h))
   435     x*=fw; if(x<0) x+=bw;
   436     y*=fh; if(y<0) y+=bh;
   437     osd->DrawRectangle(x,y,x+w*fw-1,y+h*fh-1,fg);
   438     //d(printf(" x=%d y=%d x2=%d y2=%d\n",x,y,x+h*fh-1,y+w*fw-1))
   439     }
   440 #else
   441   Interface->Fill(x,y,w,h,fg);
   442 #endif
   443 }
   444 
   445 void cMP3Control::Flush(void)
   446 {
   447 #if APIVERSNUM >= 10307
   448   if(MP3Setup.ReplayDisplay) Skins.Flush();
   449   else if(osd) osd->Flush();
   450 #else
   451   Interface->Flush();
   452 #endif
   453 }
   454 
   455 void cMP3Control::ShowProgress(bool open, bool bigWin)
   456 {
   457   int index, total;
   458 
   459   if(player->GetIndex(index,total) && total>=0) {
   460     if(!visible && open) {
   461       HideStatus();
   462 #if APIVERSNUM >= 10307
   463       if(MP3Setup.ReplayDisplay) {
   464         disp=Skins.Current()->DisplayReplay(false);
   465         if(!disp) return;
   466         bigWin=false;
   467         }
   468       else {
   469         int bt, bp;
   470         fw=font->Width(' ')*2;
   471         fh=font->Height();
   472         if(bigWin) {
   473           bw=Setup.OSDWidth;
   474           bh=Setup.OSDHeight;
   475           bt=0;
   476           bp=2*fh;
   477           rows=(bh-bp-fh/3)/fh;
   478           }
   479         else {
   480           bw=Setup.OSDWidth;
   481           bh=bp=2*fh;
   482           bt=Setup.OSDHeight-bh;
   483           rows=0;
   484           }
   485         bwc=bw/fw+1;
   486         //d(printf("mp3: bw=%d bh=%d bt=%d bp=%d bwc=%d rows=%d fw=%d fh=%d\n",
   487         //  bw,bh,bt,bp,bwc,rows,fw,fh))
   488         osd=cOsdProvider::NewOsd(Setup.OSDLeft,Setup.OSDTop+bt);
   489         if(!osd) return;
   490         if(bigWin) {
   491           tArea Areas[] = { { 0,0,bw-1,bh-bp-1,2 }, { 0,bh-bp,bw-1,bh-1,4 } };
   492           osd->SetAreas(Areas,sizeof(Areas)/sizeof(tArea));
   493           }
   494         else {
   495           tArea Areas[] = { { 0,0,bw-1,bh-1,4 } };
   496           osd->SetAreas(Areas,sizeof(Areas)/sizeof(tArea));
   497           }
   498         osd->DrawRectangle(0,0,bw-1,bh-1,clrGray50);
   499         osd->Flush();
   500         }
   501 #else
   502       fw=cOsd::CellWidth();
   503       fh=cOsd::LineHeight();
   504       bh=bigWin ? Setup.OSDheight : -2;
   505       bw=bwc=Setup.OSDwidth;
   506       rows=Setup.OSDheight-3;
   507       Interface->Open(bw,bh);
   508       Interface->Clear();
   509 #endif
   510       ShowStatus(true);
   511       bigwin=bigWin;
   512       visible=true;
   513 #if APIVERSNUM >= 10500
   514       SetNeedsFastResponse(true);
   515 #else
   516       needsFastResponse=true;
   517 #endif
   518       fliptime=listtime=0; flipint=0; flip=-1; top=lastTop=-1; lastIndex=lastTotal=-1;
   519       delete lastMode; lastMode=0;
   520       }
   521 
   522     cMP3PlayInfo *mode=new cMP3PlayInfo;
   523     bool valid=mgr->Info(-1,mode);
   524     bool changed=(!lastMode || mode->Hash!=lastMode->Hash);
   525     char buf[256];
   526     if(changed) { d(printf("mp3-ctrl: mode change detected\n")) }
   527 
   528     if(valid) { // send progress to status monitor
   529       if(changed || mode->Loop!=lastMode->Loop || mode->Shuffle!=lastMode->Shuffle) {
   530         snprintf(buf,sizeof(buf),"[%c%c] (%d/%d) %s",
   531                   mode->Loop?'L':'.',mode->Shuffle?'S':'.',mode->Num,mode->MaxNum,TitleArtist(mode->Title,mode->Artist));
   532 #if APIVERSNUM >= 10338
   533         cStatus::MsgReplaying(this,buf,mode->Filename[0]?mode->Filename:0,true);
   534 #else
   535         cStatus::MsgReplaying(this,buf);
   536 #endif
   537         }
   538       }
   539 
   540     if(visible) { // refresh the OSD progress display
   541       bool flush=false;
   542 
   543 #if APIVERSNUM >= 10307
   544       if(MP3Setup.ReplayDisplay) {
   545         if(!statusActive) {
   546           if(total>0) disp->SetProgress(index,total);
   547           disp->SetCurrent(IndexToHMSF(index));
   548           disp->SetTotal(IndexToHMSF(total));
   549           bool Play, Forward;
   550           int Speed;
   551           if(GetReplayMode(Play,Forward,Speed)) 
   552             disp->SetMode(Play, Forward, Speed);
   553           flush=true;
   554           }
   555         }
   556       else {
   557 #endif
   558         if(!selecting && changed && !statusActive) {
   559           snprintf(buf,sizeof(buf),"(%d/%d)",mode->Num,mode->MaxNum);
   560           Write(0,-2,CTAB,buf);
   561           flush=true;
   562           }
   563 
   564         if(!lastMode || mode->Loop!=lastMode->Loop) {
   565           if(mode->Loop) Write(-4,-1,0,"L",clrBlack,clrYellow);
   566           else Fill(-4,-1,2,1,clrBackground);
   567           flush=true;
   568           }
   569         if(!lastMode || mode->Shuffle!=lastMode->Shuffle) {
   570           if(mode->Shuffle) Write(-2,-1,0,"S",clrWhite,clrRed);
   571           else Fill(-2,-1,2,1,clrBackground);
   572           flush=true;
   573           }
   574 
   575         index/=framesPerSecond; total/=framesPerSecond;
   576         if(index!=lastIndex || total!=lastTotal) {
   577           if(total>0) {
   578 #if APIVERSNUM >= 10307
   579             cProgressBar ProgressBar(bw-(CTAB+CTAB2)*fw,fh,index,total);
   580             osd->DrawBitmap(CTAB*fw,bh-fh,ProgressBar);
   581 #else
   582             cProgressBar ProgressBar((bw-CTAB-CTAB2)*fw,fh,index,total);
   583             Interface->SetBitmap(CTAB*fw,(abs(bh)-1)*fh,ProgressBar);
   584 #endif
   585             }
   586           snprintf(buf,sizeof(buf),total?"%02d:%02d/%02d:%02d":"%02d:%02d",index/60,index%60,total/60,total%60);
   587           Write(0,-1,11,buf);
   588           flush=true;
   589           }
   590 #if APIVERSNUM >= 10307
   591         }
   592 #endif
   593 
   594       if(!jumpactive) {
   595         bool doflip=false;
   596         if(MP3Setup.ReplayDisplay && (!lastMode || mode->Loop!=lastMode->Loop || mode->Shuffle!=lastMode->Shuffle))
   597           doflip=true;
   598         if(!valid || changed) {
   599           fliptime=time(0); flip=0;
   600 	  doflip=true;
   601 	  }
   602         else if(time(0)>fliptime+flipint) {
   603 	  fliptime=time(0);
   604 	  flip++; if(flip>=MP3Setup.DisplayMode) flip=0;
   605           doflip=true;
   606 	  }
   607         if(doflip) {
   608           buf[0]=0;
   609           switch(flip) {
   610 	    default:
   611 	      flip=0;
   612 	      // fall through
   613 	    case 0:
   614 	      snprintf(buf,sizeof(buf),"%s",TitleArtist(mode->Title,mode->Artist));
   615 	      flipint=6;
   616 	      break;
   617 	    case 1:
   618               if(mode->Album[0]) {
   619       	        snprintf(buf,sizeof(buf),mode->Year>0?"from: %s (%d)":"from: %s",mode->Album,mode->Year);
   620 	        flipint=4;
   621 	        }
   622               else fliptime=0;
   623               break;
   624 	    case 2:
   625               if(mode->MaxBitrate>0)
   626                 snprintf(buf,sizeof(buf),"%.1f kHz, %d-%d kbps, %s",mode->SampleFreq/1000.0,mode->Bitrate/1000,mode->MaxBitrate/1000,mode->SMode);
   627               else
   628                 snprintf(buf,sizeof(buf),"%.1f kHz, %d kbps, %s",mode->SampleFreq/1000.0,mode->Bitrate/1000,mode->SMode);
   629 	      flipint=3;
   630 	      break;
   631 	    }
   632           if(buf[0]) {
   633 #if APIVERSNUM >= 10307
   634             if(MP3Setup.ReplayDisplay) {
   635               char buf2[256];
   636               snprintf(buf2,sizeof(buf2),"[%c%c] (%d/%d) %s",
   637                        mode->Loop?'L':'.',mode->Shuffle?'S':'.',mode->Num,mode->MaxNum,buf);
   638               disp->SetTitle(buf2);
   639               flush=true;
   640               }
   641             else {
   642 #endif
   643               if(!statusActive) {
   644                 DisplayInfo(buf);
   645                 flush=true;
   646                 }
   647               else { d(printf("mp3-ctrl: display info skip due to status active\n")) }
   648 #if APIVERSNUM >= 10307
   649               }
   650 #endif
   651             }
   652           }
   653         }
   654 
   655       if(bigwin) {
   656         bool all=(top!=lastTop || changed);
   657         if(all || time(0)>listtime+2) {
   658           int num=(top>0 && mode->Num==lastMode->Num) ? top : mode->Num - rows/2;
   659           if(num+rows>mode->MaxNum) num=mode->MaxNum-rows+1;
   660           if(num<1) num=1;
   661           top=num;
   662           for(int i=0 ; i<rows && i<MAXROWS && num<=mode->MaxNum ; i++,num++) {
   663             cMP3PlayInfo pi;
   664             mgr->Info(num,&pi); if(!pi.Title[0]) break;
   665             snprintf(buf,sizeof(buf),"%d.\t%s",num,TitleArtist(pi.Title,pi.Artist));
   666             eDvbColor fg=clrWhite, bg=clrBackground;
   667             int hash=MakeHash(buf);
   668             if(num==mode->Num) { fg=clrBlack; bg=clrCyan; hash=(hash^77) + 23; }
   669             if(all || hash!=hashlist[i]) {
   670               char *s=rindex(buf,'\t');
   671               if(s) {
   672                 *s++=0;
   673                 Write(0,i,5,buf,fg,bg);
   674                 Write(5,i,bwc-5,s,fg,bg);
   675                 }
   676               else
   677                 Write(0,i,bwc,buf,fg,bg);
   678               flush=true;
   679               hashlist[i]=hash;
   680               }
   681             }
   682           listtime=time(0); lastTop=top;
   683           }
   684         }
   685 
   686       if(flush) Flush();
   687       }
   688 
   689     lastIndex=index; lastTotal=total;
   690     delete lastMode; lastMode=mode;
   691     }
   692 }
   693 
   694 void cMP3Control::DisplayInfo(const char *s)
   695 {
   696   if(s) Write(CTAB,-2,bwc-CTAB,s);
   697   else Fill(CTAB,-2,bwc-CTAB,1,clrBackground);
   698 }
   699 
   700 void cMP3Control::JumpDisplay(void)
   701 {
   702   char buf[64];
   703   const char *j=trVDR("Jump: "), u=jumpsecs?'s':'m';
   704   if(!jumpmm) sprintf(buf,"%s- %c",  j,u);
   705   else        sprintf(buf,"%s%d- %c",j,jumpmm,u);
   706 #if APIVERSNUM >= 10307
   707   if(MP3Setup.ReplayDisplay) {
   708     disp->SetJump(buf);
   709     }
   710   else {
   711 #endif
   712     DisplayInfo(buf);
   713 #if APIVERSNUM >= 10307
   714     }
   715 #endif
   716 }
   717 
   718 void cMP3Control::JumpProcess(eKeys Key)
   719 {
   720  int n=Key-k0, d=jumpsecs?1:60;
   721   switch (Key) {
   722     case k0 ... k9:
   723       if(jumpmm*10+n <= lastTotal/d) jumpmm=jumpmm*10+n;
   724       JumpDisplay();
   725       break;
   726     case kBlue:
   727       jumpsecs=!jumpsecs;
   728       JumpDisplay();
   729       break;
   730     case kPlay:
   731     case kUp:
   732       jumpmm-=lastIndex/d;
   733       // fall through
   734     case kFastRew:
   735     case kFastFwd:
   736     case kLeft:
   737     case kRight:
   738       player->SkipSeconds(jumpmm*d * ((Key==kLeft || Key==kFastRew) ? -1:1));
   739       // fall through
   740     default:
   741       jumpactive=false;
   742       break;
   743     }
   744 
   745   if(!jumpactive) {
   746     if(jumphide) Hide();
   747 #if APIVERSNUM >= 10307
   748     else if(MP3Setup.ReplayDisplay) disp->SetJump(0);
   749 #endif
   750     }
   751 }
   752 
   753 void cMP3Control::Jump(void)
   754 {
   755   jumpmm=0; jumphide=jumpsecs=false;
   756   if(!visible) {
   757     ShowTimed(); if(!visible) return;
   758     jumphide=true;
   759     }
   760   JumpDisplay();
   761   jumpactive=true; fliptime=0; flip=-1;
   762 }
   763 
   764 eOSState cMP3Control::ProcessKey(eKeys Key)
   765 {
   766   if(!player->Active()) return osEnd;
   767 
   768   if(visible && timeoutShow && time(0)>timeoutShow) { Hide(); timeoutShow=0; }
   769   ShowProgress();
   770 #if APIVERSNUM >= 10307
   771   ShowStatus(Key==kNone && !Skins.IsOpen());
   772 #else
   773   ShowStatus(Key==kNone && !Interface->IsOpen());
   774 #endif
   775 
   776   if(jumpactive && Key!=kNone) { JumpProcess(Key); return osContinue; }
   777 
   778   switch(Key) {
   779     case kUp:
   780     case kUp|k_Repeat:
   781 #if APIVERSNUM >= 10347
   782     case kNext:
   783     case kNext|k_Repeat:    
   784 #endif
   785       mgr->Next(); player->Play();
   786       break;
   787     case kDown:
   788     case kDown|k_Repeat:
   789 #if APIVERSNUM >= 10347
   790     case kPrev:
   791     case kPrev|k_Repeat:
   792 #endif
   793       if(!player->PrevCheck()) mgr->Prev();
   794       player->Play();
   795       break;
   796     case kLeft:
   797     case kLeft|k_Repeat:
   798       if(bigwin) {
   799         if(top>0) { top-=rows; if(top<1) top=1; }
   800         break;
   801         }
   802       // fall through
   803     case kFastRew:
   804     case kFastRew|k_Repeat:
   805       if(!player->IsStream()) player->SkipSeconds(-JUMPSIZE);
   806       break;
   807     case kRight:
   808     case kRight|k_Repeat:
   809       if(bigwin) {
   810         if(top>0) top+=rows;
   811         break;
   812         }
   813       // fall through
   814     case kFastFwd:
   815     case kFastFwd|k_Repeat:
   816       if(!player->IsStream()) player->SkipSeconds(JUMPSIZE);
   817       break;
   818     case kRed:
   819       if(!player->IsStream()) Jump();
   820       break;
   821     case kGreen:
   822       if(lastMode) {
   823         if(time(0)>greentime) {
   824           if(lastMode->Loop || (!lastMode->Loop && !lastMode->Shuffle)) mgr->ToggleLoop();
   825           if(lastMode->Shuffle) mgr->ToggleShuffle();
   826           }
   827         else {
   828           if(!lastMode->Loop) mgr->ToggleLoop();
   829           else if(!lastMode->Shuffle) mgr->ToggleShuffle();
   830           else mgr->ToggleLoop();
   831           }
   832         greentime=time(0)+MULTI_TIMEOUT;
   833         }
   834       break;
   835     case kPlay:
   836       player->Play();
   837       break;
   838     case kPause:
   839     case kYellow:
   840       if(!player->IsStream()) player->Pause();
   841       break;
   842     case kStop:
   843     case kBlue:
   844       Hide();
   845       Stop();
   846       return osEnd;
   847     case kBack:
   848       Hide();
   849 #if APIVERSNUM >= 10332
   850       cRemote::CallPlugin(plugin_name);
   851       return osBack;
   852 #else
   853       return osEnd;
   854 #endif
   855 
   856     case k0 ... k9:
   857       number=number*10+Key-k0;
   858       if(lastMode && number>0 && number<=lastMode->MaxNum) {
   859         if(!visible) { ShowTimed(); selecthide=true; }
   860         selecting=true; lastkeytime=time_ms();
   861         char buf[32];
   862 #if APIVERSNUM >= 10307
   863         if(MP3Setup.ReplayDisplay) {
   864           snprintf(buf,sizeof(buf),"%s%d-/%d",trVDR("Jump: "),number,lastMode->MaxNum);
   865           disp->SetJump(buf);
   866           }
   867         else {
   868 #endif
   869           snprintf(buf,sizeof(buf),"(%d-/%d)",number,lastMode->MaxNum);
   870           Write(0,-2,CTAB,buf);
   871 #if APIVERSNUM >= 10307
   872           }
   873 #endif
   874         Flush();
   875         break;
   876         }
   877       number=0; lastkeytime=0;
   878       // fall through
   879     case kNone:
   880       if(selecting && time_ms()-lastkeytime>SELECT_TIMEOUT) {
   881         if(number>0) { mgr->Goto(number); player->Play();  }
   882         if(selecthide) timeoutShow=time(0)+SELECTHIDE_TIMEOUT;
   883         if(lastMode) lastMode->Hash=-1;
   884         number=0; selecting=selecthide=false;
   885 #if APIVERSNUM >= 10307
   886         if(MP3Setup.ReplayDisplay) disp->SetJump(0);
   887 #endif
   888         }
   889       break;
   890     case kOk:
   891       if(time(0)>oktime || MP3Setup.ReplayDisplay) {
   892         visible ? Hide() : ShowTimed();
   893         }
   894       else {
   895         if(visible && !bigwin) { Hide(); ShowProgress(true,true); }
   896         else { Hide(); ShowTimed(); }
   897         }
   898       oktime=time(0)+MULTI_TIMEOUT;
   899       ShowStatus(true);
   900       break;
   901     default:
   902       return osUnknown;
   903     }
   904   return osContinue;
   905 }
   906 
   907 // --- cMenuID3Info ------------------------------------------------------------
   908 
   909 class cMenuID3Info : public cOsdMenu {
   910 private:
   911   cOsdItem *Item(const char *name, const char *text);
   912   cOsdItem *Item(const char *name, const char *format, const float num);
   913   void Build(cSongInfo *info, const char *name);
   914 public:
   915   cMenuID3Info(cSong *song);
   916   cMenuID3Info(cSongInfo *si, const char *name);
   917   virtual eOSState ProcessKey(eKeys Key);
   918   };
   919 
   920 cMenuID3Info::cMenuID3Info(cSong *song)
   921 :cOsdMenu(tr("ID3 information"),12)
   922 {
   923   Build(song->Info(),song->Name());
   924 }
   925 
   926 cMenuID3Info::cMenuID3Info(cSongInfo *si, const char *name)
   927 :cOsdMenu(tr("ID3 information"),12)
   928 {
   929   Build(si,name);
   930 }
   931 
   932 void cMenuID3Info::Build(cSongInfo *si, const char *name)
   933 {
   934   if(si) {
   935     Item(tr("Filename"),name);
   936     if(si->HasInfo() && si->Total>0) {
   937       char *buf=0;
   938       asprintf(&buf,"%02d:%02d",si->Total/60,si->Total%60);
   939       Item(tr("Length"),buf);
   940       free(buf);
   941       Item(tr("Title"),si->Title);
   942       Item(tr("Artist"),si->Artist);
   943       Item(tr("Album"),si->Album);
   944       Item(tr("Year"),0,(float)si->Year);
   945       Item(tr("Samplerate"),"%.1f kHz",si->SampleFreq/1000.0);
   946       Item(tr("Bitrate"),"%.f kbit/s",si->Bitrate/1000.0);
   947       Item(trVDR("Channels"),0,(float)si->Channels);
   948       }
   949     Display();
   950     }
   951 }
   952 
   953 cOsdItem *cMenuID3Info::Item(const char *name, const char *format, const float num)
   954 {
   955   cOsdItem *item;
   956   if(num>=0.0) {
   957     char *buf=0;
   958     asprintf(&buf,format?format:"%.f",num);
   959     item=Item(name,buf);
   960     free(buf);
   961     }
   962   else item=Item(name,"");
   963   return item;
   964 }
   965 
   966 cOsdItem *cMenuID3Info::Item(const char *name, const char *text)
   967 {
   968   char *buf=0;
   969   asprintf(&buf,"%s:\t%s",name,text?text:"");
   970   cOsdItem *item = new cOsdItem(buf,osBack);
   971 #if APIVERSNUM >= 10307
   972   item->SetSelectable(false);
   973 #else
   974   item->SetColor(clrWhite, clrBackground);
   975 #endif
   976   free(buf);
   977   Add(item); return item;
   978 }
   979 
   980 eOSState cMenuID3Info::ProcessKey(eKeys Key)
   981 {
   982   eOSState state = cOsdMenu::ProcessKey(Key);
   983 
   984   if(state==osUnknown) {
   985      switch(Key) {
   986        case kRed:
   987        case kGreen:
   988        case kYellow:
   989        case kBlue:   return osContinue;
   990        case kMenu:   return osEnd;
   991        default: break;
   992        }
   993      }
   994   return state;
   995 }
   996 
   997 // --- cMenuInstantBrowse -------------------------------------------------------
   998 
   999 class cMenuInstantBrowse : public cMenuBrowse {
  1000 private:
  1001   const char *selecttext, *alltext;
  1002   virtual void SetButtons(void);
  1003   virtual eOSState ID3Info(void);
  1004 public:
  1005   cMenuInstantBrowse(cFileSource *Source, const char *Selecttext, const char *Alltext);
  1006   virtual eOSState ProcessKey(eKeys Key);
  1007   };
  1008 
  1009 cMenuInstantBrowse::cMenuInstantBrowse(cFileSource *Source, const char *Selecttext, const char *Alltext)
  1010 :cMenuBrowse(Source,true,true,tr("Directory browser"),excl_br)
  1011 {
  1012   selecttext=Selecttext; alltext=Alltext;
  1013   SetButtons();
  1014 }
  1015 
  1016 void cMenuInstantBrowse::SetButtons(void)
  1017 {
  1018   SetHelp(selecttext, currentdir?tr("Parent"):0, currentdir?0:alltext, tr("ID3 info"));
  1019   Display();
  1020 }
  1021 
  1022 eOSState cMenuInstantBrowse::ID3Info(void)
  1023 {
  1024   cFileObj *item=CurrentItem();
  1025   if(item && item->Type()==otFile) {
  1026     cSong *song=new cSong(item);
  1027     cSongInfo *si;
  1028     if(song && (si=song->Info())) {
  1029       AddSubMenu(new cMenuID3Info(si,item->Path()));
  1030       }
  1031     delete song;
  1032     }
  1033   return osContinue;
  1034 }
  1035 
  1036 eOSState cMenuInstantBrowse::ProcessKey(eKeys Key)
  1037 {
  1038   eOSState state=cOsdMenu::ProcessKey(Key);
  1039   if(state==osUnknown) {
  1040      switch (Key) {
  1041        case kYellow: lastselect=new cFileObj(source,0,0,otBase);
  1042                      return osBack;
  1043        default: break;
  1044        }
  1045      }
  1046   if(state==osUnknown) state=cMenuBrowse::ProcessStdKey(Key,state);
  1047   return state;
  1048 }
  1049 
  1050 // --- cMenuPlayListItem -------------------------------------------------------
  1051 
  1052 class cMenuPlayListItem : public cOsdItem {
  1053   private:
  1054   bool showID3;
  1055   cSong *song;
  1056 public:
  1057   cMenuPlayListItem(cSong *Song, bool showid3);
  1058   cSong *Song(void) { return song; }
  1059   virtual void Set(void);
  1060   void Set(bool showid3);
  1061   };
  1062 
  1063 cMenuPlayListItem::cMenuPlayListItem(cSong *Song, bool showid3)
  1064 {
  1065   song=Song;
  1066   Set(showid3);
  1067 }
  1068 
  1069 void cMenuPlayListItem::Set(bool showid3)
  1070 {
  1071   showID3=showid3;
  1072   Set();
  1073 }
  1074 
  1075 void cMenuPlayListItem::Set(void)
  1076 {
  1077   char *buffer=0;
  1078   cSongInfo *si=song->Info(false);
  1079   if(showID3 && !si) si=song->Info();
  1080   if(showID3 && si && si->Title)
  1081     asprintf(&buffer, "%d.\t%s",song->Index()+1,TitleArtist(si->Title,si->Artist));
  1082   else
  1083     asprintf(&buffer, "%d.\t<%s>",song->Index()+1,song->Name());
  1084   SetText(buffer,false);
  1085 }
  1086 
  1087 // --- cMenuPlayList ------------------------------------------------------
  1088 
  1089 class cMenuPlayList : public cOsdMenu {
  1090 private:
  1091   cPlayList *playlist;
  1092   bool browsing, showid3;
  1093   void Buttons(void);
  1094   void Refresh(bool all = false);
  1095   void Add(void);
  1096   virtual void Move(int From, int To);
  1097   eOSState Remove(void);
  1098   eOSState ShowID3(void);
  1099   eOSState ID3Info(void);
  1100 public:
  1101   cMenuPlayList(cPlayList *Playlist);
  1102   virtual eOSState ProcessKey(eKeys Key);
  1103   };
  1104 
  1105 cMenuPlayList::cMenuPlayList(cPlayList *Playlist)
  1106 :cOsdMenu(tr("Playlist editor"),4)
  1107 {
  1108   browsing=showid3=false;
  1109   playlist=Playlist;
  1110   if(MP3Setup.EditorMode) showid3=true;
  1111 
  1112   cSong *mp3 = playlist->First();
  1113   while(mp3) {
  1114     cOsdMenu::Add(new cMenuPlayListItem(mp3,showid3));
  1115     mp3 = playlist->cList<cSong>::Next(mp3);
  1116     }
  1117   Buttons(); Display();
  1118 }
  1119 
  1120 void cMenuPlayList::Buttons(void)
  1121 {
  1122   SetHelp(tr("Add"), showid3?tr("Filenames"):tr("ID3 names"), tr("Remove"), trVDR(BUTTON"Mark"));
  1123 }
  1124 
  1125 void cMenuPlayList::Refresh(bool all)
  1126 {
  1127   cMenuPlayListItem *cur=(cMenuPlayListItem *)((all || Count()<2) ? First() : Get(Current()));
  1128   while(cur) {
  1129     cur->Set(showid3);
  1130     cur=(cMenuPlayListItem *)Next(cur);
  1131     }
  1132 }
  1133 
  1134 void cMenuPlayList::Add(void)
  1135 {
  1136   cFileObj *item=cMenuInstantBrowse::GetSelected();
  1137   if(item) {
  1138     Status(tr("Scanning directory..."));
  1139     cInstantPlayList *newpl=new cInstantPlayList(item);
  1140     if(newpl->Load()) {
  1141       if(newpl->Count()) {
  1142         if(newpl->Count()==1 || Interface->Confirm(tr("Add recursivly?"))) {
  1143           cSong *mp3=newpl->First();
  1144           while(mp3) {
  1145             cSong *n=new cSong(mp3);
  1146             if(Count()>0) {
  1147               cMenuPlayListItem *current=(cMenuPlayListItem *)Get(Current());
  1148               playlist->Add(n,current->Song());
  1149               cOsdMenu::Add(new cMenuPlayListItem(n,showid3),true,current);
  1150               }
  1151             else {
  1152               playlist->Add(n);
  1153               cOsdMenu::Add(new cMenuPlayListItem(n,showid3),true);
  1154               }
  1155             mp3=newpl->cList<cSong>::Next(mp3);
  1156             }
  1157           playlist->Save();
  1158           Refresh(); Display();
  1159           }
  1160         }
  1161       else Error(tr("Empty directory!"));
  1162       }
  1163     else Error(tr("Error scanning directory!"));
  1164     delete newpl;
  1165     Status(0);
  1166     }
  1167 }
  1168 
  1169 void cMenuPlayList::Move(int From, int To)
  1170 {
  1171   playlist->Move(From,To); playlist->Save();
  1172   cOsdMenu::Move(From,To);
  1173   Refresh(true); Display();
  1174 }
  1175 
  1176 eOSState cMenuPlayList::ShowID3(void)
  1177 {
  1178   showid3=!showid3;
  1179   Buttons(); Refresh(true); Display();
  1180   return osContinue;
  1181 }
  1182 
  1183 eOSState cMenuPlayList::ID3Info(void)
  1184 {
  1185   if(Count()>0) {
  1186     cMenuPlayListItem *current = (cMenuPlayListItem *)Get(Current());
  1187     AddSubMenu(new cMenuID3Info(current->Song()));
  1188     }
  1189   return osContinue;
  1190 }
  1191 
  1192 eOSState cMenuPlayList::Remove(void)
  1193 {
  1194   if(Count()>0) {
  1195     cMenuPlayListItem *current = (cMenuPlayListItem *)Get(Current());
  1196     if(Interface->Confirm(tr("Remove entry?"))) {
  1197       playlist->Del(current->Song()); playlist->Save();
  1198       cOsdMenu::Del(Current());
  1199       Refresh(); Display();
  1200       }
  1201     }
  1202   return osContinue;
  1203 }
  1204 
  1205 eOSState cMenuPlayList::ProcessKey(eKeys Key)
  1206 {
  1207   eOSState state = cOsdMenu::ProcessKey(Key);
  1208 
  1209   if(browsing && !HasSubMenu() && state==osContinue) { Add(); browsing=false; }
  1210 
  1211   if(state==osUnknown) {
  1212      switch(Key) {
  1213        case kOk:     return ID3Info();
  1214        case kRed:    browsing=true;
  1215                      return AddSubMenu(new cMenuInstantBrowse(MP3Sources.GetSource(),tr("Add"),tr("Add all")));
  1216        case kGreen:  return ShowID3();
  1217        case kYellow: return Remove();
  1218        case kBlue:   Mark(); return osContinue;
  1219        case kMenu:   return osEnd;
  1220        default: break;
  1221        }
  1222      }
  1223   return state;
  1224 }
  1225 
  1226 // --- cPlaylistRename --------------------------------------------------------
  1227 
  1228 class cPlaylistRename : public cOsdMenu {
  1229 private:
  1230   static char *newname;
  1231   const char *oldname;
  1232   char data[64];
  1233 public:
  1234   cPlaylistRename(const char *Oldname);
  1235   virtual eOSState ProcessKey(eKeys Key);
  1236   static const char *GetNewname(void) { return newname; }
  1237   };
  1238 
  1239 char *cPlaylistRename::newname = NULL;
  1240 
  1241 cPlaylistRename::cPlaylistRename(const char *Oldname)
  1242 :cOsdMenu(tr("Rename playlist"), 15)
  1243 {
  1244   free(newname); newname=0;
  1245 
  1246   oldname=Oldname;
  1247   char *buf=NULL;
  1248   asprintf(&buf,"%s\t%s",tr("Old name:"),oldname);
  1249   cOsdItem *old = new cOsdItem(buf,osContinue);
  1250 #if APIVERSNUM >= 10307
  1251   old->SetSelectable(false);
  1252 #else
  1253   old->SetColor(clrWhite, clrBackground);
  1254 #endif
  1255   Add(old);
  1256   free(buf);
  1257 
  1258   data[0]=0;
  1259   Add(new cMenuEditStrItem( tr("New name"), data, sizeof(data)-1, tr(FileNameChars)),true);
  1260 }
  1261 
  1262 eOSState cPlaylistRename::ProcessKey(eKeys Key)
  1263 {
  1264   eOSState state = cOsdMenu::ProcessKey(Key);
  1265 
  1266   if (state == osUnknown) {
  1267      switch (Key) {
  1268        case kOk:     if(data[0] && strcmp(data,oldname)) newname=strdup(data);
  1269                      return osBack;
  1270        case kRed:
  1271        case kGreen:
  1272        case kYellow:
  1273        case kBlue:   return osContinue;
  1274        default: break;
  1275        }
  1276      }
  1277   return state;
  1278 }
  1279 
  1280 // --- cMenuMP3Item -----------------------------------------------------
  1281 
  1282 class cMenuMP3Item : public cOsdItem {
  1283   private:
  1284   cPlayList *playlist;
  1285   virtual void Set(void);
  1286 public:
  1287   cMenuMP3Item(cPlayList *PlayList);
  1288   cPlayList *List(void) { return playlist; }
  1289   };
  1290 
  1291 cMenuMP3Item::cMenuMP3Item(cPlayList *PlayList)
  1292 {
  1293   playlist=PlayList;
  1294   Set();
  1295 }
  1296 
  1297 void cMenuMP3Item::Set(void)
  1298 {
  1299   char *buffer=0;
  1300   asprintf(&buffer," %s",playlist->BaseName());
  1301   SetText(buffer,false);
  1302 }
  1303 
  1304 // --- cMenuMP3 --------------------------------------------------------
  1305 
  1306 class cMenuMP3 : public cOsdMenu {
  1307 private:
  1308   cPlayLists *lists;
  1309   bool renaming, sourcing, instanting;
  1310   int buttonnum;
  1311   eOSState Play(void);
  1312   eOSState Edit(void);
  1313   eOSState New(void);
  1314   eOSState Delete(void);
  1315   eOSState Rename(bool second);
  1316   eOSState Source(bool second);
  1317   eOSState Instant(bool second);
  1318   void ScanLists(void);
  1319   eOSState SetButtons(int num);
  1320 public:
  1321   cMenuMP3(void);
  1322   ~cMenuMP3(void);
  1323   virtual eOSState ProcessKey(eKeys Key);
  1324   };
  1325 
  1326 cMenuMP3::cMenuMP3(void)
  1327 :cOsdMenu(tr("MP3"))
  1328 {
  1329   renaming=sourcing=instanting=false;
  1330   lists=new cPlayLists;
  1331   ScanLists(); SetButtons(1);
  1332   if(MP3Setup.MenuMode) Instant(false);
  1333 }
  1334 
  1335 cMenuMP3::~cMenuMP3(void)
  1336 {
  1337   delete lists;
  1338 }
  1339 
  1340 eOSState cMenuMP3::SetButtons(int num)
  1341 {
  1342   switch(num) {
  1343     case 1:
  1344       SetHelp(trVDR(BUTTON"Edit"), tr("Source"), tr("Browse"), ">>");
  1345       break;
  1346     case 2:
  1347       SetHelp("<<", trVDR(BUTTON"New"), trVDR(BUTTON"Delete"), tr("Rename"));
  1348       break;
  1349     }
  1350   buttonnum=num; Display();
  1351   return osContinue;
  1352 }
  1353 
  1354 void cMenuMP3::ScanLists(void)
  1355 {
  1356   Clear();
  1357   Status(tr("Scanning playlists..."));
  1358   bool res=lists->Load(MP3Sources.GetSource());
  1359   Status(0);
  1360   if(res) {
  1361     cPlayList *plist=lists->First();
  1362     while(plist) {
  1363       Add(new cMenuMP3Item(plist));
  1364       plist=lists->Next(plist);
  1365       }
  1366     }
  1367   else Error(tr("Error scanning playlists!"));
  1368 }
  1369 
  1370 eOSState cMenuMP3::Delete(void)
  1371 {
  1372   if(Count()>0) {
  1373     if(Interface->Confirm(tr("Delete playlist?")) &&
  1374        Interface->Confirm(tr("Are you sure?")) ) {
  1375       cPlayList *plist = ((cMenuMP3Item *)Get(Current()))->List();
  1376       if(plist->Delete()) {
  1377         lists->Del(plist);
  1378         cOsdMenu::Del(Current());
  1379         Display();
  1380         }
  1381       else Error(tr("Error deleting playlist!"));
  1382       }
  1383     }
  1384   return osContinue;
  1385 }
  1386 
  1387 eOSState cMenuMP3::New(void)
  1388 {
  1389   cPlayList *plist=new cPlayList(MP3Sources.GetSource(),0,0);
  1390   char name[32];
  1391   int i=0;
  1392   do {
  1393     if(i) sprintf(name,"%s%d",tr("unnamed"),i++);
  1394     else { strcpy(name,tr("unnamed")); i++; }
  1395     } while(plist->TestName(name));
  1396 
  1397   if(plist->Create(name)) {
  1398     lists->Add(plist);
  1399     Add(new cMenuMP3Item(plist), true);
  1400 
  1401     isyslog("MP3: playlist %s added", plist->Name());
  1402     return AddSubMenu(new cMenuPlayList(plist));
  1403     }
  1404   Error(tr("Error creating playlist!"));
  1405   delete plist;
  1406   return osContinue;
  1407 }
  1408 
  1409 eOSState cMenuMP3::Rename(bool second)
  1410 {
  1411   if(HasSubMenu() || Count() == 0) return osContinue;
  1412 
  1413   cPlayList *plist = ((cMenuMP3Item *)Get(Current()))->List();
  1414   if(!second) {
  1415     renaming=true;
  1416     return AddSubMenu(new cPlaylistRename(plist->BaseName()));
  1417     }
  1418   renaming=false;
  1419   const char *newname=cPlaylistRename::GetNewname();
  1420   if(newname) {
  1421     if(plist->Rename(newname)) {
  1422       RefreshCurrent();
  1423       DisplayCurrent(true);
  1424       }
  1425     else Error(tr("Error renaming playlist!"));
  1426     }
  1427   return osContinue;
  1428 }
  1429 
  1430 eOSState cMenuMP3::Edit(void)
  1431 {
  1432   if(HasSubMenu() || Count() == 0) return osContinue;
  1433 
  1434   cPlayList *plist = ((cMenuMP3Item *)Get(Current()))->List();
  1435   if(!plist->Load()) Error(tr("Error loading playlist!"));
  1436   else if(!plist->IsWinAmp()) {
  1437     isyslog("MP3: editing playlist %s", plist->Name());
  1438     return AddSubMenu(new cMenuPlayList(plist));
  1439     }
  1440   else Error(tr("Can't edit a WinAmp playlist!"));
  1441   return osContinue;
  1442 }
  1443 
  1444 eOSState cMenuMP3::Play(void)
  1445 {
  1446   if(HasSubMenu() || Count() == 0) return osContinue;
  1447 
  1448   Status(tr("Loading playlist..."));
  1449   cPlayList *newpl=new cPlayList(((cMenuMP3Item *)Get(Current()))->List());
  1450   if(newpl->Load() && newpl->Count()) {
  1451     isyslog("mp3: playback started with playlist %s", newpl->Name());
  1452     cMP3Control::SetPlayList(newpl);
  1453     if(MP3Setup.KeepSelect) { Status(0); return osContinue; }
  1454     return osEnd;
  1455     }
  1456   Status(0);
  1457   delete newpl;
  1458   Error(tr("Error loading playlist!"));
  1459   return osContinue;
  1460 }
  1461 
  1462 eOSState cMenuMP3::Source(bool second)
  1463 {
  1464   if(HasSubMenu()) return osContinue;
  1465 
  1466   if(!second) {
  1467     sourcing=true;
  1468     return AddSubMenu(new cMenuSource(&MP3Sources,tr("MP3 source")));
  1469     }
  1470   sourcing=false;
  1471   cFileSource *src=cMenuSource::GetSelected();
  1472   if(src) {
  1473     MP3Sources.SetSource(src);
  1474     ScanLists();
  1475     Display();
  1476     }
  1477   return osContinue;
  1478 }
  1479 
  1480 eOSState cMenuMP3::Instant(bool second)
  1481 {
  1482   if(HasSubMenu()) return osContinue;
  1483 
  1484   if(!second) {
  1485     instanting=true;
  1486     return AddSubMenu(new cMenuInstantBrowse(MP3Sources.GetSource(),trVDR(BUTTON"Play"),tr("Play all")));
  1487     }
  1488   instanting=false;
  1489   cFileObj *item=cMenuInstantBrowse::GetSelected();
  1490   if(item) {
  1491     Status(tr("Building playlist..."));
  1492     cInstantPlayList *newpl = new cInstantPlayList(item);
  1493     if(newpl->Load() && newpl->Count()) {
  1494       isyslog("mp3: playback started with instant playlist %s", newpl->Name());
  1495       cMP3Control::SetPlayList(newpl);
  1496       if(MP3Setup.KeepSelect) { Status(0); return Instant(false); }
  1497       return osEnd;
  1498       }
  1499     Status(0);
  1500     delete newpl;
  1501     Error(tr("Error building playlist!"));
  1502     }
  1503   return osContinue;
  1504 }
  1505 
  1506 eOSState cMenuMP3::ProcessKey(eKeys Key)
  1507 {
  1508   eOSState state = cOsdMenu::ProcessKey(Key);
  1509 
  1510   if(!HasSubMenu() && state==osContinue) { // eval the return value from submenus
  1511     if(renaming) return Rename(true);
  1512     if(sourcing) return Source(true);
  1513     if(instanting) return Instant(true);
  1514     }
  1515 
  1516   if(state == osUnknown) {
  1517     switch(Key) {
  1518       case kOk:     return Play();
  1519       case kRed:    return (buttonnum==1 ? Edit() : SetButtons(1)); 
  1520       case kGreen:  return (buttonnum==1 ? Source(false) : New());
  1521       case kYellow: return (buttonnum==1 ? Instant(false) : Delete());
  1522       case kBlue:   return (buttonnum==1 ? SetButtons(2) : Rename(false));
  1523       case kMenu:   return osEnd;
  1524       default:      break;
  1525       }
  1526     }
  1527   return state;
  1528 }
  1529 
  1530 // --- PropagateImage ----------------------------------------------------------
  1531 
  1532 void PropagateImage(const char *image)
  1533 {
  1534   cPlugin *graphtft=cPluginManager::GetPlugin("graphtft");
  1535   if(graphtft) graphtft->SetupParse("CoverImage",image ? image:"");
  1536 }
  1537 
  1538 // --- cPluginMP3 --------------------------------------------------------------
  1539 
  1540 static const char *VERSION        = PLUGIN_VERSION;
  1541 static const char *DESCRIPTION    = trNOOP("A versatile audio player");
  1542 static const char *MAINMENUENTRY  = "MP3";
  1543 
  1544 class cPluginMp3 : public cPlugin {
  1545 private:
  1546 #if APIVERSNUM >= 10330
  1547   bool ExternalPlay(const char *path, bool test);
  1548 #endif
  1549 public:
  1550   cPluginMp3(void);
  1551   virtual ~cPluginMp3();
  1552   virtual const char *Version(void) { return VERSION; }
  1553   virtual const char *Description(void) { return tr(DESCRIPTION); }
  1554   virtual const char *CommandLineHelp(void);
  1555   virtual bool ProcessArgs(int argc, char *argv[]);
  1556 #if APIVERSNUM >= 10131
  1557   virtual bool Initialize(void);
  1558 #else
  1559   virtual bool Start(void);
  1560 #endif
  1561   virtual void Housekeeping(void);
  1562   virtual const char *MainMenuEntry(void);
  1563   virtual cOsdObject *MainMenuAction(void);
  1564   virtual cMenuSetupPage *SetupMenu(void);
  1565   virtual bool SetupParse(const char *Name, const char *Value);
  1566 #if APIVERSNUM >= 10330
  1567   virtual bool Service(const char *Id, void *Data);
  1568 #if APIVERSNUM >= 10331
  1569   virtual const char **SVDRPHelpPages(void);
  1570   virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode);
  1571 #endif
  1572 #endif
  1573   };
  1574 
  1575 cPluginMp3::cPluginMp3(void)
  1576 {
  1577   // Initialize any member varaiables here.
  1578   // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
  1579   // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
  1580 }
  1581 
  1582 cPluginMp3::~cPluginMp3()
  1583 {
  1584   InfoCache.Shutdown();
  1585   delete mgr;
  1586 }
  1587 
  1588 const char *cPluginMp3::CommandLineHelp(void)
  1589 {
  1590   static char *help_str=0;
  1591   
  1592   free(help_str);    //                                     for easier orientation, this is column 80|
  1593   asprintf(&help_str,"  -m CMD,   --mount=CMD    use CMD to mount/unmount/eject mp3 sources\n"
  1594                      "                           (default: %s)\n"
  1595                      "  -n CMD,   --network=CMD  execute CMD before & after network access\n"
  1596                      "                           (default: %s)\n"
  1597                      "  -C DIR,   --cache=DIR    store ID3 cache file in DIR\n"
  1598                      "                           (default: %s)\n"
  1599                      "  -B DIR,   --cddb=DIR     search CDDB files in DIR\n"
  1600                      "                           (default: %s)\n"
  1601                      "  -D DEV,   --dsp=DEV      device for OSS output\n"
  1602                      "                           (default: %s)\n"
  1603                      "  -i CMD,   --iconv=CMD    use CMD to convert background images\n"
  1604                      "                           (default: %s)\n"
  1605                      "  -c DIR,   --icache=DIR   cache converted images in DIR\n"
  1606                      "                           (default: %s)\n"
  1607                      "  -S SUB,   --sources=SUB  search sources config in SUB subdirectory\n"
  1608                      "                           (default: %s)\n",
  1609                      
  1610                      mountscript,
  1611                      netscript ? netscript:"none",
  1612                      cachedir ? cachedir:"video dir",
  1613 #ifdef HAVE_SNDFILE
  1614                      cddbpath,
  1615 #else
  1616                      "none",
  1617 #endif
  1618 #ifdef WITH_OSS
  1619                      dspdevice,
  1620 #else
  1621                      "none",
  1622 #endif
  1623                      imageconv,
  1624                      imagecache,
  1625                      sourcesSub ? sourcesSub:"empty"
  1626                      );
  1627   return help_str;
  1628 }
  1629 
  1630 bool cPluginMp3::ProcessArgs(int argc, char *argv[])
  1631 {
  1632   static struct option long_options[] = {
  1633       { "mount",    required_argument, NULL, 'm' },
  1634       { "network",  required_argument, NULL, 'n' },
  1635       { "cddb",     required_argument, NULL, 'B' },
  1636       { "dsp",      required_argument, NULL, 'D' },
  1637       { "cache",    required_argument, NULL, 'C' },
  1638       { "icache",   required_argument, NULL, 'c' },
  1639       { "iconv",    required_argument, NULL, 'i' },
  1640       { "sources",  required_argument, NULL, 'S' },
  1641       { NULL }
  1642     };
  1643 
  1644   int c, option_index = 0;
  1645   while((c=getopt_long(argc,argv,"c:i:m:n:B:C:D:S:",long_options,&option_index))!=-1) {
  1646     switch (c) {
  1647       case 'i': imageconv=optarg; break;
  1648       case 'c': imagecache=optarg; break;
  1649       case 'm': mountscript=optarg; break;
  1650       case 'n': netscript=optarg; break;
  1651       case 'C': cachedir=optarg; break;
  1652       case 'S': sourcesSub=optarg; break;
  1653       case 'B':
  1654 #ifdef HAVE_SNDFILE
  1655                 cddbpath=optarg; break;
  1656 #else
  1657                 fprintf(stderr, "mp3: libsndfile support has not been compiled in!\n"); return false;
  1658 #endif
  1659       case 'D':
  1660 #ifdef WITH_OSS
  1661                 dspdevice=optarg; break;
  1662 #else
  1663                 fprintf(stderr, "mp3: OSS output has not been compiled in!\n"); return false;
  1664 #endif
  1665       default:  return false;
  1666       }
  1667     }
  1668   return true;
  1669 }
  1670 
  1671 #if APIVERSNUM >= 10131
  1672 bool cPluginMp3::Initialize(void)
  1673 #else
  1674 bool cPluginMp3::Start(void)
  1675 #endif
  1676 {
  1677   if(!CheckVDRVersion(1,1,29,"mp3")) return false;
  1678   plugin_name="mp3";
  1679 #if APIVERSNUM < 10507
  1680   i18n_name="mp3";
  1681 #else
  1682   i18n_name="vdr-mp3";
  1683 #endif
  1684   MP3Sources.Load(AddDirectory(ConfigDirectory(sourcesSub),"mp3sources.conf"));
  1685   if(MP3Sources.Count()<1) {
  1686      esyslog("ERROR: you should have defined at least one source in mp3sources.conf");
  1687      fprintf(stderr,"No source(s) defined in mp3sources.conf\n");
  1688      return false;
  1689      }
  1690   InfoCache.Load();
  1691 #if APIVERSNUM < 10507
  1692   RegisterI18n(Phrases);
  1693 #endif
  1694   mgr=new cPlayManager;
  1695   if(!mgr) {
  1696     esyslog("ERROR: creating playmanager failed");
  1697     fprintf(stderr,"Creating playmanager failed\n");
  1698     return false;
  1699     }
  1700   d(printf("mp3: using %s\n",mad_version))
  1701   d(printf("mp3: compiled with %s\n",MAD_VERSION))
  1702   return true;
  1703 }
  1704 
  1705 void cPluginMp3::Housekeeping(void)
  1706 {
  1707   InfoCache.Save();
  1708 }
  1709 
  1710 const char *cPluginMp3::MainMenuEntry(void)
  1711 {
  1712   return MP3Setup.HideMainMenu ? 0 : tr(MAINMENUENTRY);
  1713 }
  1714 
  1715 cOsdObject *cPluginMp3::MainMenuAction(void)
  1716 {
  1717   return new cMenuMP3;
  1718 }
  1719 
  1720 cMenuSetupPage *cPluginMp3::SetupMenu(void)
  1721 {
  1722   return new cMenuSetupMP3;
  1723 }
  1724 
  1725 bool cPluginMp3::SetupParse(const char *Name, const char *Value)
  1726 {
  1727   if      (!strcasecmp(Name, "InitLoopMode"))     MP3Setup.InitLoopMode    = atoi(Value);
  1728   else if (!strcasecmp(Name, "InitShuffleMode"))  MP3Setup.InitShuffleMode = atoi(Value);
  1729   else if (!strcasecmp(Name, "AudioMode"))        MP3Setup.AudioMode       = atoi(Value);
  1730   else if (!strcasecmp(Name, "BgrScan"))          MP3Setup.BgrScan         = atoi(Value);
  1731   else if (!strcasecmp(Name, "EditorMode"))       MP3Setup.EditorMode      = atoi(Value);
  1732   else if (!strcasecmp(Name, "DisplayMode"))      MP3Setup.DisplayMode     = atoi(Value);
  1733   else if (!strcasecmp(Name, "BackgrMode"))       MP3Setup.BackgrMode      = atoi(Value);
  1734   else if (!strcasecmp(Name, "MenuMode"))         MP3Setup.MenuMode        = atoi(Value);
  1735   else if (!strcasecmp(Name, "TargetLevel"))      MP3Setup.TargetLevel     = atoi(Value);
  1736   else if (!strcasecmp(Name, "LimiterLevel"))     MP3Setup.LimiterLevel    = atoi(Value);
  1737   else if (!strcasecmp(Name, "Only48kHz"))        MP3Setup.Only48kHz       = atoi(Value);
  1738   else if (!strcasecmp(Name, "UseProxy"))         MP3Setup.UseProxy        = atoi(Value);
  1739   else if (!strcasecmp(Name, "ProxyHost"))        strn0cpy(MP3Setup.ProxyHost,Value,MAX_HOSTNAME);
  1740   else if (!strcasecmp(Name, "ProxyPort"))        MP3Setup.ProxyPort       = atoi(Value);
  1741   else if (!strcasecmp(Name, "UseCddb"))          MP3Setup.UseCddb         = atoi(Value);
  1742   else if (!strcasecmp(Name, "CddbHost"))         strn0cpy(MP3Setup.CddbHost,Value,MAX_HOSTNAME);
  1743   else if (!strcasecmp(Name, "CddbPort"))         MP3Setup.CddbPort        = atoi(Value);
  1744   else if (!strcasecmp(Name, "AbortAtEOL"))       MP3Setup.AbortAtEOL      = atoi(Value);
  1745   else if (!strcasecmp(Name, "AudioOutMode")) {
  1746     MP3Setup.AudioOutMode = atoi(Value);
  1747 #ifndef WITH_OSS
  1748     if(MP3Setup.AudioOutMode==AUDIOOUTMODE_OSS) {
  1749       esyslog("WARNING: AudioOutMode OSS not supported, falling back to DVB");
  1750       MP3Setup.AudioOutMode=AUDIOOUTMODE_DVB;
  1751       }
  1752 #endif
  1753     }
  1754 #if APIVERSNUM >= 10307
  1755   else if (!strcasecmp(Name, "ReplayDisplay"))      MP3Setup.ReplayDisplay = atoi(Value);
  1756 #endif
  1757   else if (!strcasecmp(Name, "HideMainMenu"))       MP3Setup.HideMainMenu  = atoi(Value);
  1758   else if (!strcasecmp(Name, "KeepSelect"))         MP3Setup.KeepSelect    = atoi(Value);
  1759   else if (!strcasecmp(Name, "TitleArtistOrder"))   MP3Setup.TitleArtistOrder = atoi(Value);
  1760   else return false;
  1761   return true;
  1762 }
  1763 
  1764 #if APIVERSNUM >= 10330
  1765 
  1766 bool cPluginMp3::ExternalPlay(const char *path, bool test)
  1767 {
  1768   char real[PATH_MAX+1];
  1769   if(realpath(path,real)) {
  1770     cFileSource *src=MP3Sources.FindSource(real);
  1771     if(src) {
  1772       cFileObj *item=new cFileObj(src,0,0,otFile);
  1773       if(item) {
  1774         item->SplitAndSet(real);
  1775         if(item->GuessType()) {
  1776           if(item->Exists()) {
  1777             cInstantPlayList *pl=new cInstantPlayList(item);
  1778             if(pl && pl->Load() && pl->Count()) {
  1779               if(!test) cMP3Control::SetPlayList(pl);
  1780               else delete pl;
  1781               delete item;
  1782               return true;
  1783               }
  1784             else dsyslog("MP3 service: error building playlist");
  1785             delete pl;
  1786             }
  1787           else dsyslog("MP3 service: cannot play '%s'",path);
  1788           }
  1789         else dsyslog("MP3 service: GuessType() failed for '%s'",path);
  1790         delete item;
  1791         }
  1792       }
  1793     else dsyslog("MP3 service: cannot find source for '%s', real '%s'",path,real);
  1794     }
  1795   else if(errno!=ENOENT && errno!=ENOTDIR)
  1796     esyslog("ERROR: realpath: %s: %s",path,strerror(errno));
  1797   return false;
  1798 }
  1799 
  1800 bool cPluginMp3::Service(const char *Id, void *Data)
  1801 {
  1802   if(!strcasecmp(Id,"MP3-Play-v1")) {
  1803     if(Data) {
  1804       struct MPlayerServiceData *msd=(struct MPlayerServiceData *)Data;
  1805       msd->result=ExternalPlay(msd->data.filename,false);
  1806       }
  1807     return true;
  1808     }
  1809   else if(!strcasecmp(Id,"MP3-Test-v1")) {
  1810     if(Data) {
  1811       struct MPlayerServiceData *msd=(struct MPlayerServiceData *)Data;
  1812       msd->result=ExternalPlay(msd->data.filename,true);
  1813       }
  1814     return true;
  1815     }
  1816   return false;
  1817 }
  1818 
  1819 #if APIVERSNUM >= 10331
  1820 
  1821 const char **cPluginMp3::SVDRPHelpPages(void)
  1822 {
  1823   static const char *HelpPages[] = {
  1824     "PLAY <filename>\n"
  1825     "    Triggers playback of file 'filename'.",
  1826     "TEST <filename>\n"
  1827     "    Tests is playback of file 'filename' is possible.",
  1828     "CURR\n"
  1829     "    Returns filename of song currently being replayed.",
  1830     NULL
  1831     };
  1832   return HelpPages;
  1833 }
  1834 
  1835 cString cPluginMp3::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
  1836 {
  1837   if(!strcasecmp(Command,"PLAY")) {
  1838     if(*Option) {
  1839       if(ExternalPlay(Option,false)) return "Playback triggered";
  1840       else { ReplyCode=550; return "Playback failed"; }
  1841       }
  1842     else { ReplyCode=501; return "Missing filename"; }
  1843     }
  1844   else if(!strcasecmp(Command,"TEST")) {
  1845     if(*Option) {
  1846       if(ExternalPlay(Option,true)) return "Playback possible";
  1847       else { ReplyCode=550; return "Playback not possible"; }
  1848       }
  1849     else { ReplyCode=501; return "Missing filename"; }
  1850     }
  1851   else if(!strcasecmp(Command,"CURR")) {
  1852     cControl *control=cControl::Control();
  1853     if(control && typeid(*control)==typeid(cMP3Control)) {
  1854       cMP3PlayInfo mode;
  1855       if(mgr->Info(-1,&mode)) return mode.Filename;
  1856       else return "<unknown>";
  1857       }
  1858     else { ReplyCode=550; return "No running playback"; }
  1859     }
  1860   return NULL;
  1861 }
  1862 
  1863 #endif // 1.3.31
  1864 #endif // 1.3.30
  1865 
  1866 VDRPLUGINCREATOR(cPluginMp3); // Don't touch this!