/* * MP3/MPlayer plugin to VDR (C++) * * (C) 2001-2009 Stefan Huelswitt * * This code is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * Or, point your browser to http://www.gnu.org/copyleft/gpl.html */ #include #include #include #include #include "common.h" #include #include #include #include #include #include "setup.h" #include "setup-mp3.h" #include "data-mp3.h" #include "data-src.h" #include "player-mp3.h" #include "menu.h" #include "menu-async.h" #include "decoder.h" #include "i18n.h" #include "version.h" #include "service.h" #ifdef DEBUG #include #endif const char *sourcesSub=0; cFileSources MP3Sources; static const char *plugin_name=0; const char *i18n_name=0; // --- cMenuSetupMP3 -------------------------------------------------------- class cMenuSetupMP3 : public cMenuSetupPage { private: cMP3Setup data; // const char *cddb[3], *disp[2], *scan[3], *bgr[3]; const char *aout[AUDIOOUTMODES]; int amode, amodes[AUDIOOUTMODES]; protected: virtual void Store(void); public: cMenuSetupMP3(void); }; cMenuSetupMP3::cMenuSetupMP3(void) { static const char allowed[] = { "abcdefghijklmnopqrstuvwxyz0123456789-_" }; int numModes=0; aout[numModes]=trVDR("DVB"); amodes[numModes]=AUDIOOUTMODE_DVB; numModes++; #ifdef WITH_OSS aout[numModes]=tr("OSS"); amodes[numModes]=AUDIOOUTMODE_OSS; numModes++; #endif data=MP3Setup; amode=0; for(int i=0; iHalt(); mgr->Flush(); //XXX remove later } bool cMP3Control::SetPlayList(cPlayList *plist) { bool res; cControl *control=cControl::Control(); // is there a running MP3 player? if(control && typeid(*control)==typeid(cMP3Control)) { // add songs to running playlist if(!MP3Setup.EnqueueSongs) mgr->Flush(); mgr->Add(plist); res=true; } else { mgr->Flush(); mgr->Add(plist); cControl::Launch(new cMP3Control); res=false; } delete plist; return res; } void cMP3Control::ShowTimed(int Seconds) { if(!visible) { ShowProgress(true); timeoutShow=(Seconds>0) ? time(0)+Seconds : 0; } } void cMP3Control::Hide(void) { HideStatus(); timeoutShow=0; if(visible) { delete osd; osd=0; delete disp; disp=0; visible=bigwin=false; #if APIVERSNUM >= 10500 SetNeedsFastResponse(false); #else needsFastResponse=false; #endif } } void cMP3Control::ShowStatus(bool force) { if((asyncStatus.Changed() || (force && !statusActive)) && !jumpactive) { const char *text=asyncStatus.Begin(); if(text) { if(MP3Setup.ReplayDisplay || !osd) { if(statusActive) Skins.Message(mtStatus,0); Skins.Message(mtStatus,text); } else { if(!statusActive) osd->SaveRegion(0,bh-2*fh,bw-1,bh-fh-1); osd->DrawText(0,bh-2*fh,text,clrBlack,clrCyan,font,bw,fh,taCenter); osd->Flush(); } statusActive=true; } else HideStatus(); asyncStatus.Finish(); } } void cMP3Control::HideStatus(void) { if(statusActive) { if(MP3Setup.ReplayDisplay || !osd) Skins.Message(mtStatus,0); else { osd->RestoreRegion(); osd->Flush(); } } statusActive=false; } #define CTAB 11 // some tabbing values for the progress display #define CTAB2 5 void cMP3Control::Write(int x, int y, int w, const char *text, int fg, int bg) { if(osd) { x*=fw; if(x<0) x+=bw; y*=fh; if(y<0) y+=bh; osd->DrawText(x,y,text,fg,bg,font,w*fw); } } void cMP3Control::Fill(int x, int y, int w, int h, int fg) { if(osd) { x*=fw; if(x<0) x+=bw; y*=fh; if(y<0) y+=bh; osd->DrawRectangle(x,y,x+w*fw-1,y+h*fh-1,fg); } } void cMP3Control::Flush(void) { if(MP3Setup.ReplayDisplay) Skins.Flush(); else if(osd) osd->Flush(); } void cMP3Control::ShowProgress(bool open, bool bigWin) { int index, total; if(player->GetIndex(index,total) && total>=0) { if(!visible && open) { HideStatus(); if(MP3Setup.ReplayDisplay) { disp=Skins.Current()->DisplayReplay(false); if(!disp) return; bigWin=false; } else { int bt, bp; fw=font->Width(' ')*2; fh=font->Height(); if(bigWin) { bw=Setup.OSDWidth; bh=Setup.OSDHeight; bt=0; bp=2*fh; rows=(bh-bp-fh/3)/fh; } else { bw=Setup.OSDWidth; bh=bp=2*fh; bt=Setup.OSDHeight-bh; rows=0; } bwc=bw/fw+1; //d(printf("mp3: bw=%d bh=%d bt=%d bp=%d bwc=%d rows=%d fw=%d fh=%d\n", // bw,bh,bt,bp,bwc,rows,fw,fh)) osd=cOsdProvider::NewOsd(Setup.OSDLeft,Setup.OSDTop+bt); if(!osd) return; if(bigWin) { tArea Areas[] = { { 0,0,bw-1,bh-bp-1,2 }, { 0,bh-bp,bw-1,bh-1,4 } }; osd->SetAreas(Areas,sizeof(Areas)/sizeof(tArea)); } else { tArea Areas[] = { { 0,0,bw-1,bh-1,4 } }; osd->SetAreas(Areas,sizeof(Areas)/sizeof(tArea)); } osd->DrawRectangle(0,0,bw-1,bh-1,clrGray50); osd->Flush(); } ShowStatus(true); bigwin=bigWin; visible=true; #if APIVERSNUM >= 10500 SetNeedsFastResponse(true); #else needsFastResponse=true; #endif fliptime=listtime=0; flipint=0; flip=-1; top=lastTop=-1; lastIndex=lastTotal=-1; delete lastMode; lastMode=0; } cMP3PlayInfo *mode=new cMP3PlayInfo; bool valid=mgr->Info(-1,mode); bool changed=(!lastMode || mode->Hash!=lastMode->Hash); if(changed) { d(printf("mp3-ctrl: mode change detected\n")) } if(valid) { // send progress to status monitor if(changed || mode->Loop!=lastMode->Loop || mode->Shuffle!=lastMode->Shuffle) 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); } if(visible) { // refresh the OSD progress display bool flush=false; if(MP3Setup.ReplayDisplay) { if(!statusActive) { if(total>0) disp->SetProgress(index,total); disp->SetCurrent(IndexToHMSF(index)); disp->SetTotal(IndexToHMSF(total)); bool Play, Forward; int Speed; if(GetReplayMode(Play,Forward,Speed)) disp->SetMode(Play, Forward, Speed); flush=true; } } else { if(!selecting && changed && !statusActive) { Write(0,-2,CTAB,cString::sprintf("(%d/%d)",mode->Num,mode->MaxNum)); flush=true; } if(!lastMode || mode->Loop!=lastMode->Loop) { if(mode->Loop) Write(-4,-1,0,"L",clrBlack,clrYellow); else Fill(-4,-1,2,1,clrGray50); flush=true; } if(!lastMode || mode->Shuffle!=lastMode->Shuffle) { if(mode->Shuffle) Write(-2,-1,0,"S",clrWhite,clrRed); else Fill(-2,-1,2,1,clrGray50); flush=true; } index/=framesPerSecond; total/=framesPerSecond; if(index!=lastIndex || total!=lastTotal) { if(total>0) { cProgressBar ProgressBar(bw-(CTAB+CTAB2)*fw,fh,index,total); osd->DrawBitmap(CTAB*fw,bh-fh,ProgressBar); } Write(0,-1,11,cString::sprintf(total?"%02d:%02d/%02d:%02d":"%02d:%02d",index/60,index%60,total/60,total%60)); flush=true; } } if(!jumpactive) { bool doflip=false; if(MP3Setup.ReplayDisplay && (!lastMode || mode->Loop!=lastMode->Loop || mode->Shuffle!=lastMode->Shuffle)) doflip=true; if(!valid || changed) { fliptime=time(0); flip=0; doflip=true; } else if(time(0)>fliptime+flipint) { fliptime=time(0); flip++; if(flip>=MP3Setup.DisplayMode) flip=0; doflip=true; } if(doflip) { cString buff; switch(flip) { default: flip=0; // fall through case 0: buff=TitleArtist(mode->Title,mode->Artist); flipint=6; break; case 1: if(mode->Album[0]) { buff=cString::sprintf(mode->Year>0?"from: %s (%d)":"from: %s",mode->Album,mode->Year); flipint=4; break; } flip++; // fall through case 2: if(mode->MaxBitrate>0) buff=cString::sprintf("%.1f kHz, %d-%d kbps, %s",mode->SampleFreq/1000.0,mode->Bitrate/1000,mode->MaxBitrate/1000,mode->SMode); else buff=cString::sprintf("%.1f kHz, %d kbps, %s",mode->SampleFreq/1000.0,mode->Bitrate/1000,mode->SMode); flipint=3; break; } if(MP3Setup.ReplayDisplay) { disp->SetTitle(cString::sprintf("[%c%c] (%d/%d) %s",mode->Loop?'L':'.',mode->Shuffle?'S':'.',mode->Num,mode->MaxNum,*buff)); flush=true; } else { if(!statusActive) { DisplayInfo(buff); flush=true; } else { d(printf("mp3-ctrl: display info skip due to status active\n")) } } } } if(bigwin) { bool all=(top!=lastTop || changed); if(all || time(0)>listtime+2) { int num=(top>0 && mode->Num==lastMode->Num) ? top : mode->Num - rows/2; if(num+rows>mode->MaxNum) num=mode->MaxNum-rows+1; if(num<1) num=1; top=num; for(int i=0 ; iMaxNum ; i++,num++) { cMP3PlayInfo pi; mgr->Info(num,&pi); if(!pi.Title[0]) break; cString buff=cString::sprintf("%d.\t%s",num,*TitleArtist(pi.Title,pi.Artist)); int fg=clrWhite, bg=clrGray50; int hash=MakeHash(buff); if(num==mode->Num) { fg=clrBlack; bg=clrCyan; hash=(hash^77) + 23; } if(all || hash!=hashlist[i]) { const char *s=rindex(buff,'\t'); if(s) { Write(5,i,bwc-5,s,fg,bg); buff.Truncate(s-buff); Write(0,i,5,buff,fg,bg); } else Write(0,i,bwc,buff,fg,bg); flush=true; hashlist[i]=hash; } } listtime=time(0); lastTop=top; } } if(flush) Flush(); } lastIndex=index; lastTotal=total; delete lastMode; lastMode=mode; } } void cMP3Control::DisplayInfo(const char *s) { if(s) Write(CTAB,-2,bwc-CTAB,s); else Fill(CTAB,-2,bwc-CTAB,1,clrGray50); } void cMP3Control::JumpDisplay(void) { char buf[64]; const char *j=trVDR("Jump: "), u=jumpsecs?'s':'m'; if(!jumpmm) sprintf(buf,"%s- %c", j,u); else sprintf(buf,"%s%d- %c",j,jumpmm,u); if(MP3Setup.ReplayDisplay) { disp->SetJump(buf); } else { DisplayInfo(buf); } } void cMP3Control::JumpProcess(eKeys Key) { int n=Key-k0, d=jumpsecs?1:60; switch (Key) { case k0 ... k9: if(jumpmm*10+n <= lastTotal/d) jumpmm=jumpmm*10+n; JumpDisplay(); break; case kBlue: jumpsecs=!jumpsecs; JumpDisplay(); break; case kPlay: case kUp: jumpmm-=lastIndex/d; // fall through case kFastRew: case kFastFwd: case kLeft: case kRight: player->SkipSeconds(jumpmm*d * ((Key==kLeft || Key==kFastRew) ? -1:1)); // fall through default: jumpactive=false; break; } if(!jumpactive) { if(jumphide) Hide(); else if(MP3Setup.ReplayDisplay) disp->SetJump(0); } } void cMP3Control::Jump(void) { jumpmm=0; jumphide=jumpsecs=false; if(!visible) { ShowTimed(); if(!visible) return; jumphide=true; } JumpDisplay(); jumpactive=true; fliptime=0; flip=-1; } eOSState cMP3Control::ProcessKey(eKeys Key) { if(!player->Active()) return osEnd; if(timeoutShow && time(0)>timeoutShow) Hide(); ShowProgress(); ShowStatus(Key==kNone && !Skins.IsOpen()); if(jumpactive && Key!=kNone) { JumpProcess(Key); return osContinue; } switch(Key) { case kUp: case kUp|k_Repeat: case kNext: case kNext|k_Repeat: mgr->Next(); player->Play(); break; case kDown: case kDown|k_Repeat: case kPrev: case kPrev|k_Repeat: if(!player->PrevCheck()) mgr->Prev(); player->Play(); break; case kLeft: case kLeft|k_Repeat: if(bigwin) { if(top>0) { top-=rows; if(top<1) top=1; } break; } // fall through case kFastRew: case kFastRew|k_Repeat: if(!player->IsStream()) player->SkipSeconds(-JUMPSIZE); break; case kRight: case kRight|k_Repeat: if(bigwin) { if(top>0) top+=rows; break; } // fall through case kFastFwd: case kFastFwd|k_Repeat: if(!player->IsStream()) player->SkipSeconds(JUMPSIZE); break; case kRed: if(!player->IsStream()) Jump(); break; case kGreen: if(lastMode) { if(time(0)>greentime) { if(lastMode->Loop || (!lastMode->Loop && !lastMode->Shuffle)) mgr->ToggleLoop(); if(lastMode->Shuffle) mgr->ToggleShuffle(); } else { if(!lastMode->Loop) mgr->ToggleLoop(); else if(!lastMode->Shuffle) mgr->ToggleShuffle(); else mgr->ToggleLoop(); } greentime=time(0)+MULTI_TIMEOUT; } break; case kPlay: player->Play(); break; case kPause: case kYellow: if(!player->IsStream()) player->Pause(); break; case kStop: case kBlue: Hide(); Stop(); return osEnd; case kBack: Hide(); cRemote::CallPlugin(plugin_name); return osBack; case k0 ... k9: number=number*10+Key-k0; if(lastMode && number>0 && number<=lastMode->MaxNum) { if(!visible) ShowTimed(SELECTHIDE_TIMEOUT); else if(timeoutShow>0) timeoutShow=time(0)+SELECTHIDE_TIMEOUT; selecting=true; lastkeytime.Set(SELECT_TIMEOUT); char buf[32]; if(MP3Setup.ReplayDisplay) { snprintf(buf,sizeof(buf),"%s%d-/%d",trVDR("Jump: "),number,lastMode->MaxNum); disp->SetJump(buf); } else { snprintf(buf,sizeof(buf),"(%d-/%d)",number,lastMode->MaxNum); Write(0,-2,CTAB,buf); } Flush(); break; } number=0; lastkeytime.Set(); // fall through case kNone: if(selecting && lastkeytime.TimedOut()) { if(number>0) { mgr->Goto(number); player->Play(); } if(lastMode) lastMode->Hash=-1; number=0; selecting=false; if(MP3Setup.ReplayDisplay && disp) disp->SetJump(0); } break; case kOk: if(time(0)>oktime || MP3Setup.ReplayDisplay) { visible ? Hide() : ShowTimed(); } else { if(visible && !bigwin) { Hide(); ShowProgress(true,true); } else { Hide(); ShowTimed(); } } oktime=time(0)+MULTI_TIMEOUT; ShowStatus(true); break; default: return osUnknown; } return osContinue; } // --- cMenuID3Info ------------------------------------------------------------ class cMenuID3Info : public cOsdMenu { private: cOsdItem *Item(const char *name, const char *text); cOsdItem *Item(const char *name, const char *format, const float num); void Build(cSongInfo *info, const char *name); public: cMenuID3Info(cSong *song); cMenuID3Info(cSongInfo *si, const char *name); virtual eOSState ProcessKey(eKeys Key); }; cMenuID3Info::cMenuID3Info(cSong *song) :cOsdMenu(tr("ID3 information"),12) { Build(song->Info(),song->Name()); } cMenuID3Info::cMenuID3Info(cSongInfo *si, const char *name) :cOsdMenu(tr("ID3 information"),12) { Build(si,name); } void cMenuID3Info::Build(cSongInfo *si, const char *name) { if(si) { Item(tr("Filename"),name); if(si->HasInfo() && si->Total>0) { char *buf=aprintf("%02d:%02d",si->Total/60,si->Total%60); Item(tr("Length"),buf); free(buf); Item(tr("Title"),si->Title); Item(tr("Artist"),si->Artist); Item(tr("Album"),si->Album); Item(tr("Year"),0,(float)si->Year); Item(tr("Samplerate"),"%.1f kHz",si->SampleFreq/1000.0); Item(tr("Bitrate"),"%.f kbit/s",si->Bitrate/1000.0); Item(trVDR("Channels"),0,(float)si->Channels); } Display(); } } cOsdItem *cMenuID3Info::Item(const char *name, const char *format, const float num) { cOsdItem *item; if(num>=0.0) { char *buf=aprintf(format?format:"%.f",num); item=Item(name,buf); free(buf); } else item=Item(name,""); return item; } cOsdItem *cMenuID3Info::Item(const char *name, const char *text) { char *buf=aprintf("%s:\t%s",name,text?text:""); cOsdItem *item = new cOsdItem(buf,osBack); item->SetSelectable(false); free(buf); Add(item); return item; } eOSState cMenuID3Info::ProcessKey(eKeys Key) { eOSState state = cOsdMenu::ProcessKey(Key); if(state==osUnknown) { switch(Key) { case kRed: case kGreen: case kYellow: case kBlue: return osContinue; case kMenu: return osEnd; default: break; } } return state; } // --- cMenuInstantBrowse ------------------------------------------------------- class cMenuInstantBrowse : public cMenuBrowse { private: const char *selecttext, *alltext; virtual void SetButtons(void); virtual eOSState ID3Info(void); public: cMenuInstantBrowse(cFileSource *Source, const char *Selecttext, const char *Alltext); virtual eOSState ProcessKey(eKeys Key); }; cMenuInstantBrowse::cMenuInstantBrowse(cFileSource *Source, const char *Selecttext, const char *Alltext) :cMenuBrowse(Source,true,true,tr("Directory browser"),excl_br) { selecttext=Selecttext; alltext=Alltext; SetButtons(); } void cMenuInstantBrowse::SetButtons(void) { SetHelp(selecttext, currentdir?tr("Parent"):0, currentdir?0:alltext, tr("ID3 info")); Display(); } eOSState cMenuInstantBrowse::ID3Info(void) { cFileObj *item=CurrentItem(); if(item && item->Type()==otFile) { cSong *song=new cSong(item); cSongInfo *si; if(song && (si=song->Info())) { AddSubMenu(new cMenuID3Info(si,item->Path())); } delete song; } return osContinue; } eOSState cMenuInstantBrowse::ProcessKey(eKeys Key) { eOSState state=cOsdMenu::ProcessKey(Key); if(state==osUnknown) { switch (Key) { case kYellow: lastselect=new cFileObj(source,0,0,otBase); return osBack; default: break; } } if(state==osUnknown) state=cMenuBrowse::ProcessStdKey(Key,state); return state; } // --- cMenuPlayListItem ------------------------------------------------------- class cMenuPlayListItem : public cOsdItem { private: bool showID3; cSong *song; public: cMenuPlayListItem(cSong *Song, bool showid3); cSong *Song(void) { return song; } virtual void Set(void); void Set(bool showid3); }; cMenuPlayListItem::cMenuPlayListItem(cSong *Song, bool showid3) { song=Song; Set(showid3); } void cMenuPlayListItem::Set(bool showid3) { showID3=showid3; Set(); } void cMenuPlayListItem::Set(void) { char *buffer; cSongInfo *si=song->Info(false); if(showID3 && !si) si=song->Info(); if(showID3 && si && si->Title) buffer=aprintf("%d.\t%s",song->Index()+1,*TitleArtist(si->Title,si->Artist)); else buffer=aprintf("%d.\t<%s>",song->Index()+1,song->Name()); SetText(buffer,false); } // --- cMenuPlayList ------------------------------------------------------ class cMenuPlayList : public cOsdMenu { private: cPlayList *playlist; bool browsing, showid3; void Buttons(void); void Refresh(bool all = false); void Add(void); virtual void Move(int From, int To); eOSState Remove(void); eOSState ShowID3(void); eOSState ID3Info(void); public: cMenuPlayList(cPlayList *Playlist); virtual eOSState ProcessKey(eKeys Key); }; cMenuPlayList::cMenuPlayList(cPlayList *Playlist) :cOsdMenu(tr("Playlist editor"),4) { browsing=showid3=false; playlist=Playlist; if(MP3Setup.EditorMode) showid3=true; cSong *mp3 = playlist->First(); while(mp3) { cOsdMenu::Add(new cMenuPlayListItem(mp3,showid3)); mp3 = playlist->cList::Next(mp3); } Buttons(); Display(); } void cMenuPlayList::Buttons(void) { SetHelp(tr("Add"), showid3?tr("Filenames"):tr("ID3 names"), tr("Remove"), trVDR("Button$Mark")); } void cMenuPlayList::Refresh(bool all) { cMenuPlayListItem *cur=(cMenuPlayListItem *)((all || Count()<2) ? First() : Get(Current())); while(cur) { cur->Set(showid3); cur=(cMenuPlayListItem *)Next(cur); } } void cMenuPlayList::Add(void) { cFileObj *item=cMenuInstantBrowse::GetSelected(); if(item) { Status(tr("Scanning directory...")); cInstantPlayList *newpl=new cInstantPlayList(item); if(newpl->Load()) { if(newpl->Count()) { if(newpl->Count()==1 || Interface->Confirm(tr("Add recursivly?"))) { cSong *mp3=newpl->First(); while(mp3) { cSong *n=new cSong(mp3); if(Count()>0) { cMenuPlayListItem *current=(cMenuPlayListItem *)Get(Current()); playlist->Add(n,current->Song()); cOsdMenu::Add(new cMenuPlayListItem(n,showid3),true,current); } else { playlist->Add(n); cOsdMenu::Add(new cMenuPlayListItem(n,showid3),true); } mp3=newpl->cList::Next(mp3); } playlist->Save(); Refresh(); Display(); } } else Error(tr("Empty directory!")); } else Error(tr("Error scanning directory!")); delete newpl; Status(0); } } void cMenuPlayList::Move(int From, int To) { playlist->Move(From,To); playlist->Save(); cOsdMenu::Move(From,To); Refresh(true); Display(); } eOSState cMenuPlayList::ShowID3(void) { showid3=!showid3; Buttons(); Refresh(true); Display(); return osContinue; } eOSState cMenuPlayList::ID3Info(void) { if(Count()>0) { cMenuPlayListItem *current = (cMenuPlayListItem *)Get(Current()); AddSubMenu(new cMenuID3Info(current->Song())); } return osContinue; } eOSState cMenuPlayList::Remove(void) { if(Count()>0) { cMenuPlayListItem *current = (cMenuPlayListItem *)Get(Current()); if(Interface->Confirm(tr("Remove entry?"))) { playlist->Del(current->Song()); playlist->Save(); cOsdMenu::Del(Current()); Refresh(); Display(); } } return osContinue; } eOSState cMenuPlayList::ProcessKey(eKeys Key) { eOSState state = cOsdMenu::ProcessKey(Key); if(browsing && !HasSubMenu() && state==osContinue) { Add(); browsing=false; } if(state==osUnknown) { switch(Key) { case kOk: return ID3Info(); case kRed: browsing=true; return AddSubMenu(new cMenuInstantBrowse(MP3Sources.GetSource(),tr("Add"),tr("Add all"))); case kGreen: return ShowID3(); case kYellow: return Remove(); case kBlue: Mark(); return osContinue; case kMenu: return osEnd; default: break; } } return state; } // --- cPlaylistRename -------------------------------------------------------- class cPlaylistRename : public cOsdMenu { private: static char *newname; const char *oldname; char data[64]; public: cPlaylistRename(const char *Oldname); virtual eOSState ProcessKey(eKeys Key); static const char *GetNewname(void) { return newname; } }; char *cPlaylistRename::newname = NULL; cPlaylistRename::cPlaylistRename(const char *Oldname) :cOsdMenu(tr("Rename playlist"), 15) { free(newname); newname=0; oldname=Oldname; char *buf=aprintf("%s\t%s",tr("Old name:"),oldname); cOsdItem *old = new cOsdItem(buf,osContinue); old->SetSelectable(false); Add(old); free(buf); data[0]=0; Add(new cMenuEditStrItem( tr("New name"), data, sizeof(data)-1, tr(FileNameChars)),true); } eOSState cPlaylistRename::ProcessKey(eKeys Key) { eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case kOk: if(data[0] && strcmp(data,oldname)) newname=strdup(data); return osBack; case kRed: case kGreen: case kYellow: case kBlue: return osContinue; default: break; } } return state; } // --- cMenuMP3Item ----------------------------------------------------- class cMenuMP3Item : public cOsdItem { private: cPlayList *playlist; virtual void Set(void); public: cMenuMP3Item(cPlayList *PlayList); cPlayList *List(void) { return playlist; } }; cMenuMP3Item::cMenuMP3Item(cPlayList *PlayList) { playlist=PlayList; Set(); } void cMenuMP3Item::Set(void) { char *buffer=aprintf(" %s",playlist->BaseName()); SetText(buffer,false); } // --- cMenuMP3 -------------------------------------------------------- class cMenuMP3 : public cOsdMenu { private: cPlayLists *lists; bool renaming, sourcing, instanting; int buttonnum; eOSState Play(void); eOSState Edit(void); eOSState New(void); eOSState Delete(void); eOSState Rename(bool second); eOSState Source(bool second); eOSState Instant(bool second); void ScanLists(void); eOSState SetButtons(int num); public: cMenuMP3(void); ~cMenuMP3(void); virtual eOSState ProcessKey(eKeys Key); }; cMenuMP3::cMenuMP3(void) :cOsdMenu(tr("MP3")) { renaming=sourcing=instanting=false; lists=new cPlayLists; ScanLists(); SetButtons(1); if(MP3Setup.MenuMode) Instant(false); } cMenuMP3::~cMenuMP3(void) { delete lists; } eOSState cMenuMP3::SetButtons(int num) { switch(num) { case 1: SetHelp(trVDR("Button$Edit"), tr("Source"), tr("Browse"), ">>"); break; case 2: SetHelp("<<", trVDR("Button$New"), trVDR("Button$Delete"), tr("Rename")); break; } buttonnum=num; Display(); return osContinue; } void cMenuMP3::ScanLists(void) { Clear(); Status(tr("Scanning playlists...")); bool res=lists->Load(MP3Sources.GetSource()); Status(0); if(res) { cPlayList *plist=lists->First(); while(plist) { Add(new cMenuMP3Item(plist)); plist=lists->Next(plist); } } else Error(tr("Error scanning playlists!")); } eOSState cMenuMP3::Delete(void) { if(Count()>0) { if(Interface->Confirm(tr("Delete playlist?")) && Interface->Confirm(tr("Are you sure?")) ) { cPlayList *plist = ((cMenuMP3Item *)Get(Current()))->List(); if(plist->Delete()) { lists->Del(plist); cOsdMenu::Del(Current()); Display(); } else Error(tr("Error deleting playlist!")); } } return osContinue; } eOSState cMenuMP3::New(void) { cPlayList *plist=new cPlayList(MP3Sources.GetSource(),0,0); char name[32]; int i=0; do { if(i) sprintf(name,"%s%d",tr("unnamed"),i++); else { strcpy(name,tr("unnamed")); i++; } } while(plist->TestName(name)); if(plist->Create(name)) { lists->Add(plist); Add(new cMenuMP3Item(plist), true); isyslog("MP3: playlist %s added", plist->Name()); return AddSubMenu(new cMenuPlayList(plist)); } Error(tr("Error creating playlist!")); delete plist; return osContinue; } eOSState cMenuMP3::Rename(bool second) { if(HasSubMenu() || Count() == 0) return osContinue; cPlayList *plist = ((cMenuMP3Item *)Get(Current()))->List(); if(!second) { renaming=true; return AddSubMenu(new cPlaylistRename(plist->BaseName())); } renaming=false; const char *newname=cPlaylistRename::GetNewname(); if(newname) { if(plist->Rename(newname)) { RefreshCurrent(); DisplayCurrent(true); } else Error(tr("Error renaming playlist!")); } return osContinue; } eOSState cMenuMP3::Edit(void) { if(HasSubMenu() || Count() == 0) return osContinue; cPlayList *plist = ((cMenuMP3Item *)Get(Current()))->List(); if(!plist->Load()) Error(tr("Error loading playlist!")); else if(!plist->IsWinAmp()) { isyslog("MP3: editing playlist %s", plist->Name()); return AddSubMenu(new cMenuPlayList(plist)); } else Error(tr("Can't edit a WinAmp playlist!")); return osContinue; } eOSState cMenuMP3::Play(void) { if(HasSubMenu() || Count() == 0) return osContinue; Status(tr("Loading playlist...")); cPlayList *newpl=new cPlayList(((cMenuMP3Item *)Get(Current()))->List()); if(newpl->Load() && newpl->Count()) { isyslog("mp3: playback started with playlist %s", newpl->Name()); cMP3Control::SetPlayList(newpl); if(MP3Setup.KeepSelect) { Status(0); return osContinue; } return osEnd; } Status(0); delete newpl; Error(tr("Error loading playlist!")); return osContinue; } eOSState cMenuMP3::Source(bool second) { if(HasSubMenu()) return osContinue; if(!second) { sourcing=true; return AddSubMenu(new cMenuSource(&MP3Sources,tr("MP3 source"))); } sourcing=false; cFileSource *src=cMenuSource::GetSelected(); if(src) { MP3Sources.SetSource(src); ScanLists(); Display(); } return osContinue; } eOSState cMenuMP3::Instant(bool second) { if(HasSubMenu()) return osContinue; if(!second) { instanting=true; return AddSubMenu(new cMenuInstantBrowse(MP3Sources.GetSource(),trVDR("Button$Play"),tr("Play all"))); } instanting=false; cFileObj *item=cMenuInstantBrowse::GetSelected(); if(item) { Status(tr("Building playlist...")); cInstantPlayList *newpl = new cInstantPlayList(item); if(newpl->Load() && newpl->Count()) { isyslog("mp3: playback started with instant playlist %s", newpl->Name()); cMP3Control::SetPlayList(newpl); if(MP3Setup.KeepSelect) { Status(0); return Instant(false); } return osEnd; } Status(0); delete newpl; Error(tr("Error building playlist!")); } return osContinue; } eOSState cMenuMP3::ProcessKey(eKeys Key) { eOSState state = cOsdMenu::ProcessKey(Key); if(!HasSubMenu() && state==osContinue) { // eval the return value from submenus if(renaming) return Rename(true); if(sourcing) return Source(true); if(instanting) return Instant(true); } if(state == osUnknown) { switch(Key) { case kOk: return Play(); case kRed: return (buttonnum==1 ? Edit() : SetButtons(1)); case kGreen: return (buttonnum==1 ? Source(false) : New()); case kYellow: return (buttonnum==1 ? Instant(false) : Delete()); case kBlue: return (buttonnum==1 ? SetButtons(2) : Rename(false)); case kMenu: return osEnd; default: break; } } return state; } // --- PropagateImage ---------------------------------------------------------- void PropagateImage(const char *image) { cPlugin *graphtft=cPluginManager::GetPlugin("graphtft"); if(graphtft) graphtft->SetupParse("CoverImage",image ? image:""); } // --- cPluginMP3 -------------------------------------------------------------- static const char *DESCRIPTION = trNOOP("A versatile audio player"); static const char *MAINMENUENTRY = "MP3"; class cPluginMp3 : public cPlugin { private: bool ExternalPlay(const char *path, bool test); public: cPluginMp3(void); virtual ~cPluginMp3(); virtual const char *Version(void) { return PluginVersion; } virtual const char *Description(void) { return tr(DESCRIPTION); } virtual const char *CommandLineHelp(void); virtual bool ProcessArgs(int argc, char *argv[]); virtual bool Initialize(void); virtual void Housekeeping(void); virtual const char *MainMenuEntry(void); virtual cOsdObject *MainMenuAction(void); virtual cMenuSetupPage *SetupMenu(void); virtual bool SetupParse(const char *Name, const char *Value); virtual bool Service(const char *Id, void *Data); virtual const char **SVDRPHelpPages(void); virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode); }; cPluginMp3::cPluginMp3(void) { // Initialize any member varaiables here. // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT! } cPluginMp3::~cPluginMp3() { InfoCache.Shutdown(); delete mgr; } const char *cPluginMp3::CommandLineHelp(void) { static char *help_str=0; free(help_str); // for easier orientation, this is column 80| help_str=aprintf( " -m CMD, --mount=CMD use CMD to mount/unmount/eject mp3 sources\n" " (default: %s)\n" " -n CMD, --network=CMD execute CMD before & after network access\n" " (default: %s)\n" " -C DIR, --cache=DIR store ID3 cache file in DIR\n" " (default: %s)\n" " -B DIR, --cddb=DIR search CDDB files in DIR\n" " (default: %s)\n" " -D DEV, --dsp=DEV device for OSS output\n" " (default: %s)\n" " -i CMD, --iconv=CMD use CMD to convert background images\n" " (default: %s)\n" " -I IMG, --defimage=IMG use IMG as default background image\n" " (default: none)\n" " -c DIR, --icache=DIR cache converted images in DIR\n" " (default: %s)\n" " -S SUB, --sources=SUB search sources config in SUB subdirectory\n" " (default: %s)\n", mountscript, netscript ? netscript:"none", cachedir ? cachedir:"video dir", #ifdef HAVE_SNDFILE cddbpath, #else "none", #endif #ifdef WITH_OSS dspdevice, #else "none", #endif imageconv, imagecache, sourcesSub ? sourcesSub:"empty" ); return help_str; } bool cPluginMp3::ProcessArgs(int argc, char *argv[]) { static struct option long_options[] = { { "mount", required_argument, NULL, 'm' }, { "network", required_argument, NULL, 'n' }, { "cddb", required_argument, NULL, 'B' }, { "dsp", required_argument, NULL, 'D' }, { "cache", required_argument, NULL, 'C' }, { "icache", required_argument, NULL, 'c' }, { "iconv", required_argument, NULL, 'i' }, { "defimage", required_argument, NULL, 'I' }, { "sources", required_argument, NULL, 'S' }, { NULL } }; int c, option_index = 0; while((c=getopt_long(argc,argv,"c:i:m:n:B:C:D:S:",long_options,&option_index))!=-1) { switch (c) { case 'i': imageconv=optarg; break; case 'c': imagecache=optarg; break; case 'm': mountscript=optarg; break; case 'n': netscript=optarg; break; case 'C': cachedir=optarg; break; case 'I': def_usr_img=optarg; break; case 'S': sourcesSub=optarg; break; case 'B': #ifdef HAVE_SNDFILE cddbpath=optarg; break; #else fprintf(stderr, "mp3: libsndfile support has not been compiled in!\n"); return false; #endif case 'D': #ifdef WITH_OSS dspdevice=optarg; break; #else fprintf(stderr, "mp3: OSS output has not been compiled in!\n"); return false; #endif default: return false; } } return true; } bool cPluginMp3::Initialize(void) { if(!CheckVDRVersion(1,4,5,"mp3")) return false; plugin_name="mp3"; #if APIVERSNUM < 10507 i18n_name="mp3"; #else i18n_name="vdr-mp3"; #endif MP3Sources.Load(AddDirectory(ConfigDirectory(sourcesSub),"mp3sources.conf")); if(MP3Sources.Count()<1) { esyslog("ERROR: you should have defined at least one source in mp3sources.conf"); fprintf(stderr,"No source(s) defined in mp3sources.conf\n"); return false; } InfoCache.Load(); #if APIVERSNUM < 10507 RegisterI18n(Phrases); #endif #if APIVERSNUM < 10503 cCharSetConv::SetSystemCharacterTableX(I18nCharSets()[Setup.OSDLanguage]); #endif mgr=new cPlayManager; if(!mgr) { esyslog("ERROR: creating playmanager failed"); fprintf(stderr,"Creating playmanager failed\n"); return false; } d(printf("mp3: using %s\n",mad_version)) d(printf("mp3: compiled with %s\n",MAD_VERSION)) return true; } void cPluginMp3::Housekeeping(void) { InfoCache.Save(); } const char *cPluginMp3::MainMenuEntry(void) { return MP3Setup.HideMainMenu ? 0 : tr(MAINMENUENTRY); } cOsdObject *cPluginMp3::MainMenuAction(void) { return new cMenuMP3; } cMenuSetupPage *cPluginMp3::SetupMenu(void) { return new cMenuSetupMP3; } bool cPluginMp3::SetupParse(const char *Name, const char *Value) { if (!strcasecmp(Name, "InitLoopMode")) MP3Setup.InitLoopMode = atoi(Value); else if (!strcasecmp(Name, "InitShuffleMode")) MP3Setup.InitShuffleMode = atoi(Value); else if (!strcasecmp(Name, "AudioMode")) MP3Setup.AudioMode = atoi(Value); else if (!strcasecmp(Name, "BgrScan")) MP3Setup.BgrScan = atoi(Value); else if (!strcasecmp(Name, "EditorMode")) MP3Setup.EditorMode = atoi(Value); else if (!strcasecmp(Name, "DisplayMode")) MP3Setup.DisplayMode = atoi(Value); else if (!strcasecmp(Name, "BackgrMode")) MP3Setup.BackgrMode = atoi(Value); else if (!strcasecmp(Name, "MenuMode")) MP3Setup.MenuMode = atoi(Value); else if (!strcasecmp(Name, "TargetLevel")) MP3Setup.TargetLevel = atoi(Value); else if (!strcasecmp(Name, "LimiterLevel")) MP3Setup.LimiterLevel = atoi(Value); else if (!strcasecmp(Name, "Only48kHz")) MP3Setup.Only48kHz = atoi(Value); else if (!strcasecmp(Name, "UseProxy")) MP3Setup.UseProxy = atoi(Value); else if (!strcasecmp(Name, "ProxyHost")) strn0cpy(MP3Setup.ProxyHost,Value,MAX_HOSTNAME); else if (!strcasecmp(Name, "ProxyPort")) MP3Setup.ProxyPort = atoi(Value); else if (!strcasecmp(Name, "UseCddb")) MP3Setup.UseCddb = atoi(Value); else if (!strcasecmp(Name, "CddbHost")) strn0cpy(MP3Setup.CddbHost,Value,MAX_HOSTNAME); else if (!strcasecmp(Name, "CddbPort")) MP3Setup.CddbPort = atoi(Value); else if (!strcasecmp(Name, "AbortAtEOL")) MP3Setup.AbortAtEOL = atoi(Value); else if (!strcasecmp(Name, "AudioOutMode")) { MP3Setup.AudioOutMode = atoi(Value); #ifndef WITH_OSS if(MP3Setup.AudioOutMode==AUDIOOUTMODE_OSS) { esyslog("WARNING: AudioOutMode OSS not supported, falling back to DVB"); MP3Setup.AudioOutMode=AUDIOOUTMODE_DVB; } #endif } else if (!strcasecmp(Name, "ReplayDisplay")) MP3Setup.ReplayDisplay = atoi(Value); else if (!strcasecmp(Name, "HideMainMenu")) MP3Setup.HideMainMenu = atoi(Value); else if (!strcasecmp(Name, "KeepSelect")) MP3Setup.KeepSelect = atoi(Value); else if (!strcasecmp(Name, "TitleArtistOrder")) MP3Setup.TitleArtistOrder = atoi(Value); else return false; return true; } bool cPluginMp3::ExternalPlay(const char *path, bool test) { char real[PATH_MAX+1]; if(realpath(path,real)) { cFileSource *src=MP3Sources.FindSource(real); if(src) { cFileObj *item=new cFileObj(src,0,0,otFile); if(item) { item->SplitAndSet(real); if(item->GuessType()) { if(item->Exists()) { cInstantPlayList *pl=new cInstantPlayList(item); if(pl && pl->Load() && pl->Count()) { if(!test) cMP3Control::SetPlayList(pl); else delete pl; delete item; return true; } else dsyslog("MP3 service: error building playlist"); delete pl; } else dsyslog("MP3 service: cannot play '%s'",path); } else dsyslog("MP3 service: GuessType() failed for '%s'",path); delete item; } } else dsyslog("MP3 service: cannot find source for '%s', real '%s'",path,real); } else if(errno!=ENOENT && errno!=ENOTDIR) esyslog("ERROR: realpath: %s: %s",path,strerror(errno)); return false; } bool cPluginMp3::Service(const char *Id, void *Data) { if(!strcasecmp(Id,"MP3-Play-v1")) { if(Data) { struct MPlayerServiceData *msd=(struct MPlayerServiceData *)Data; msd->result=ExternalPlay(msd->data.filename,false); } return true; } else if(!strcasecmp(Id,"MP3-Test-v1")) { if(Data) { struct MPlayerServiceData *msd=(struct MPlayerServiceData *)Data; msd->result=ExternalPlay(msd->data.filename,true); } return true; } return false; } const char **cPluginMp3::SVDRPHelpPages(void) { static const char *HelpPages[] = { "PLAY \n" " Triggers playback of file 'filename'.", "TEST \n" " Tests is playback of file 'filename' is possible.", "CURR\n" " Returns filename of song currently being replayed.", NULL }; return HelpPages; } cString cPluginMp3::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode) { if(!strcasecmp(Command,"PLAY")) { if(*Option) { if(ExternalPlay(Option,false)) return "Playback triggered"; else { ReplyCode=550; return "Playback failed"; } } else { ReplyCode=501; return "Missing filename"; } } else if(!strcasecmp(Command,"TEST")) { if(*Option) { if(ExternalPlay(Option,true)) return "Playback possible"; else { ReplyCode=550; return "Playback not possible"; } } else { ReplyCode=501; return "Missing filename"; } } else if(!strcasecmp(Command,"CURR")) { cControl *control=cControl::Control(); if(control && typeid(*control)==typeid(cMP3Control)) { cMP3PlayInfo mode; if(mgr->Info(-1,&mode)) return mode.Filename; else return ""; } else { ReplyCode=550; return "No running playback"; } } return NULL; } VDRPLUGINCREATOR(cPluginMp3); // Don't touch this!