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