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