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