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