2 * MP3/MPlayer plugin to VDR (C++)
4 * (C) 2001-2010 Stefan Huelswitt <s.huelswitt@gmx.de>
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.
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.
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
29 #include <vdr/plugin.h>
30 #include <vdr/player.h>
31 #include <vdr/status.h>
33 #include <vdr/osdbase.h>
34 #include <vdr/menuitems.h>
35 #include <vdr/skins.h>
36 #include <vdr/remote.h>
40 #include "setup-mplayer.h"
42 #include "player-mplayer.h"
49 const char *sourcesSub=0;
50 cFileSources MPlaySources;
52 static const char *plugin_name=0;
53 const char *i18n_name=0;
55 // --- cMenuSetupMPlayer --------------------------------------------------------
57 class cMenuSetupMPlayer : public cMenuSetupPage {
62 virtual void Store(void);
64 cMenuSetupMPlayer(void);
67 cMenuSetupMPlayer::cMenuSetupMPlayer(void)
70 SetSection(tr("MPlayer"));
71 Add(new cMenuEditBoolItem(tr("Setup.MPlayer$Control mode"), &data.SlaveMode, tr("Traditional"), tr("Slave")));
72 Add(new cMenuEditBoolItem(tr("Setup.MPlayer$Prev/Next keys"),&data.PrevNextKeyMode, tr("Chapter"), tr("Playlist")));
73 res[0]=tr("disabled");
74 res[1]=tr("global only");
75 res[2]=tr("local first");
76 Add(new cMenuEditStraItem(tr("Setup.MPlayer$Resume mode"), &data.ResumeMode, 3, res));
77 Add(new cMenuEditBoolItem(tr("Hide mainmenu entry"), &data.HideMainMenu));
78 for(int i=0; i<10; i++) {
80 snprintf(name,sizeof(name),"%s %d",tr("Setup.MPlayer$Slave command key"),i);
81 static const char allowed[] = { "abcdefghijklmnopqrstuvwxyz0123456789!\"§$%&/()=?{}[]\\+*~#',;.:-_<>|@´`^°" };
82 Add(new cMenuEditStrItem(name, data.KeyCmd[i],MAX_KEYCMD,allowed));
86 void cMenuSetupMPlayer::Store(void)
89 SetupStore("ControlMode", MPlayerSetup.SlaveMode);
90 SetupStore("HideMainMenu",MPlayerSetup.HideMainMenu);
91 SetupStore("ResumeMode", MPlayerSetup.ResumeMode);
92 SetupStore("PrevNextMode",MPlayerSetup.PrevNextKeyMode);
93 for(int i=0; i<10; i++) {
95 snprintf(name,sizeof(name),"KeyCmd%d",i);
96 SetupStore(name,MPlayerSetup.KeyCmd[i]);
100 // --- cMPlayerControl ---------------------------------------------------------
102 class cMPlayerControl : public cControl {
104 static cFileObj *file;
106 cMPlayerPlayer *player;
107 cSkinDisplayReplay *display;
108 bool visible, modeOnly;
110 int lastCurrent, lastTotal;
113 bool jumpactive, jumphide, jumpmode;
117 void ShowTimed(int Seconds=0);
118 void ShowProgress(void);
120 void ShowTitle(void);
122 void JumpProcess(eKeys Key);
123 void JumpDisplay(void);
125 cMPlayerControl(void);
126 virtual ~cMPlayerControl();
127 virtual eOSState ProcessKey(eKeys Key);
128 virtual void Show(void) { ShowTimed(); }
129 virtual void Hide(void);
130 static void SetFile(const cFileObj *File, bool Rewind);
133 cFileObj *cMPlayerControl::file=0;
134 bool cMPlayerControl::rewind=false;
136 cMPlayerControl::cMPlayerControl(void)
137 :cControl(player=new cMPlayerPlayer(file,rewind))
139 visible=modeOnly=jumpactive=false;
145 cMPlayerControl::~cMPlayerControl()
148 cStatus::MsgReplaying(this,0,0,false);
152 void cMPlayerControl::SetFile(const cFileObj *File, bool Rewind)
155 file=File ? new cFileObj(File) : 0;
159 void cMPlayerControl::Stop(void)
161 delete player; player=0;
164 void cMPlayerControl::ShowTimed(int Seconds)
169 timeoutShow = Seconds>0 ? time(0)+Seconds : 0;
173 void cMPlayerControl::Hide(void)
176 delete display; display=0;
177 visible=modeOnly=false;
178 #if APIVERSNUM >= 10500
179 SetNeedsFastResponse(false);
181 needsFastResponse=false;
186 void cMPlayerControl::ShowTitle(void)
190 if(player) path=player->GetCurrentName();
192 path=file->FullPath();
196 const char *name=rindex(path,'/');
197 if(name) name++; else name=path;
198 if(!lastReplayMsg || strcmp(lastReplayMsg,path)) {
199 cStatus::MsgReplaying(this,name,path,true);
201 lastReplayMsg=strdup(path);
204 if(display) display->SetTitle(name);
207 if(release) free((void *)path);
210 void cMPlayerControl::ShowProgress(void)
214 if(GetIndex(Current,Total) && Total>0) {
217 display=Skins.Current()->DisplayReplay(false);
218 visible=true; modeOnly=false;
219 #if APIVERSNUM >= 10500
220 SetNeedsFastResponse(true);
222 needsFastResponse=true;
224 lastCurrent=lastTotal=-1;
228 if(abs(Current-lastCurrent)>12) {
229 if(Total>0) display->SetProgress(Current, Total);
230 display->SetCurrent(IndexToHMSF(Current));
231 display->SetTotal(IndexToHMSF(Total));
234 if(GetReplayMode(Play,Forward,Speed))
235 display->SetMode(Play, Forward, Speed);
238 lastCurrent=Current; lastTotal=Total;
246 void cMPlayerControl::ShowMode(void)
248 if(Setup.ShowReplayMode && !jumpactive) {
251 if(GetReplayMode(Play, Forward, Speed)) {
252 bool NormalPlay = (Play && Speed == -1);
255 if(NormalPlay) return;
256 display = Skins.Current()->DisplayReplay(true);
257 visible=modeOnly=true;
260 if(modeOnly && !timeoutShow && NormalPlay) timeoutShow=time(0)+SELECTHIDE_TIMEOUT;
262 display->SetMode(Play, Forward, Speed);
267 void cMPlayerControl::JumpDisplay(void)
270 const char *j=trVDR("Jump: "), u=jumpmode?'%':'m';
271 if(!jumpval) sprintf(buf,"%s- %c", j,u);
272 else sprintf(buf,"%s%d- %c",j,jumpval,u);
273 display->SetJump(buf);
276 void cMPlayerControl::JumpProcess(eKeys Key)
282 const int max=jumpmode?100:lastTotal;
283 if(jumpval*10+n <= max) jumpval=jumpval*10+n;
288 jumpmode=!jumpmode; jumpval=0;
293 player->Goto(jumpval*(jumpmode?1:60),jumpmode,false);
301 player->SkipSeconds(jumpval*60 * ((Key==kLeft || Key==kFastRew) ? -1:1));
317 void cMPlayerControl::Jump(void)
319 jumpval=0; jumphide=jumpmode=false;
321 ShowTimed(); if(!visible) return;
328 eOSState cMPlayerControl::ProcessKey(eKeys Key)
330 if(!player->Active()) { Hide(); Stop(); return osEnd; }
332 if(!player->SlaveMode()) {
333 if(Key==kBlue) { Hide(); Stop(); return osEnd; }
337 if(timeoutShow && time(0)>timeoutShow) {
342 if(modeOnly) ShowMode();
348 if(jumpactive && Key != kNone) {
353 bool DoShowMode = true;
356 case kUp: player->Play(); break;
359 case kDown: player->Pause(); break;
361 case kFastRew|k_Repeat:
364 case kLeft: player->SkipSeconds(-10); break;
366 case kFastFwd|k_Repeat:
368 case kRight|k_Repeat:
369 case kRight: player->SkipSeconds(10); break;
371 case kRed: Jump(); break;
373 case kGreen|k_Repeat:
374 case kGreen: player->SkipSeconds(-60); break;
375 case kYellow|k_Repeat:
376 case kYellow: player->SkipSeconds(60); break;
378 case kNext: player->SkipTrack(1,MPlayerSetup.PrevNextKeyMode!=0); break;
379 case kPrev: player->SkipTrack(-1,MPlayerSetup.PrevNextKeyMode!=0); break;
382 cRemote::CallPlugin(plugin_name);
385 case kBlue: Hide(); Stop(); return osEnd;
390 case kOk: if(visible && !modeOnly) { Hide(); DoShowMode=true; }
394 player->KeyCmd("switch_audio");
406 const char *cmd=MPlayerSetup.KeyCmd[Key-k0];
407 if(cmd[0]) player->KeyCmd(cmd);
415 if(DoShowMode) ShowMode();
420 // --- cMenuMPlayAid -----------------------------------------------------------
422 class cMenuMPlayAid : public cOsdMenu {
425 virtual eOSState ProcessKey(eKeys Key);
428 cMenuMPlayAid::cMenuMPlayAid(void)
429 :cOsdMenu(tr("MPlayer Audio ID"),20)
431 Add(new cMenuEditIntItem(tr("Audiostream ID"),&MPlayerAid,-1,255));
435 eOSState cMenuMPlayAid::ProcessKey(eKeys Key)
437 eOSState state=cOsdMenu::ProcessKey(Key);
438 if(state==osUnknown) {
440 case kOk: state=osBack; break;
447 // --- cMenuMPlayBrowse ---------------------------------------------------------
449 class cMenuMPlayBrowse : public cMenuBrowse {
451 bool sourcing, aidedit;
452 eOSState Source(bool second);
453 eOSState Summary(void);
455 virtual void SetButtons(void);
457 cMenuMPlayBrowse(void);
458 virtual eOSState ProcessKey(eKeys Key);
461 static const char *excl_sum[] = { ".*","*.summary","*.txt","*.nfo",0 };
463 cMenuMPlayBrowse::cMenuMPlayBrowse(void)
464 :cMenuBrowse(MPlaySources.GetSource(),false,false,tr("MPlayer browser"),excl_sum)
466 sourcing=aidedit=false;
470 void cMenuMPlayBrowse::SetButtons(void)
472 static char blue[12];
473 snprintf(blue,sizeof(blue),MPlayerAid>=0 ? "AID:%d" : "AID:def",MPlayerAid);
474 SetHelp(trVDR("Button$Play"), MPlayerSetup.ResumeMode ? trVDR("Button$Rewind"):0, tr("Source"), blue);
478 eOSState cMenuMPlayBrowse::Source(bool second)
480 if(HasSubMenu()) return osContinue;
484 return AddSubMenu(new cMenuSource(&MPlaySources,tr("MPlayer source")));
487 cFileSource *src=cMenuSource::GetSelected();
489 MPlaySources.SetSource(src);
496 eOSState cMenuMPlayBrowse::Summary(void)
498 cFileObj *item=CurrentItem();
499 if(item && item->Type()==otFile) {
500 static const char *exts[] = { ".summary",".txt",".nfo",0 };
501 for(int i=0; exts[i]; i++) {
503 strn0cpy(buff,item->FullPath(),sizeof(buff)-20);
504 char *e=&buff[strlen(buff)];
505 strn0cpy(e,exts[i],20);
506 int fd=open(buff,O_RDONLY);
508 if(fd<0 && (e=rindex(buff,'.'))) {
509 strn0cpy(e,exts[i],20);
510 fd=open(buff,O_RDONLY);
513 int r=read(fd,buff,sizeof(buff)-1);
517 return AddSubMenu(new cMenuText(tr("Summary"),buff));
525 eOSState cMenuMPlayBrowse::ProcessKey(eKeys Key)
527 eOSState state=cOsdMenu::ProcessKey(Key);
528 if(state==osContinue && !HasSubMenu()) {
529 if(sourcing) return Source(true);
530 if(aidedit) { aidedit=false; SetButtons(); }
533 if(state==osUnknown) {
537 cFileObj *item=CurrentItem();
538 if(item && item->Type()==otFile) {
539 lastselect=new cFileObj(item);
543 else state=osContinue;
551 state=AddSubMenu(new cMenuMPlayAid);
560 if(state==osUnknown) state=cMenuBrowse::ProcessStdKey(Key,state);
561 if(state==osBack && lastselect) {
562 cMPlayerControl::SetFile(lastselect,rew);
563 cControl::Launch(new cMPlayerControl);
569 // --- cPluginMPlayer ----------------------------------------------------------
571 static const char *DESCRIPTION = trNOOP("Media replay via MPlayer");
572 static const char *MAINMENUENTRY = "MPlayer";
574 class cPluginMPlayer : public cPlugin {
576 bool ExternalPlay(const char *path, bool test);
578 cPluginMPlayer(void);
579 virtual ~cPluginMPlayer();
580 virtual const char *Version(void) { return PluginVersion; }
581 virtual const char *Description(void) { return tr(DESCRIPTION); }
582 virtual const char *CommandLineHelp(void);
583 virtual bool ProcessArgs(int argc, char *argv[]);
584 virtual bool Initialize(void);
585 virtual const char *MainMenuEntry(void);
586 virtual cOsdMenu *MainMenuAction(void);
587 virtual cMenuSetupPage *SetupMenu(void);
588 virtual bool SetupParse(const char *Name, const char *Value);
589 virtual bool Service(const char *Id, void *Data);
590 virtual const char **SVDRPHelpPages(void);
591 virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode);
594 cPluginMPlayer::cPluginMPlayer(void)
596 // Initialize any member variables here.
597 // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
598 // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
602 cPluginMPlayer::~cPluginMPlayer()
607 const char *cPluginMPlayer::CommandLineHelp(void)
609 static char *help_str=0;
611 free(help_str); // for easier orientation, this is column 80|
612 help_str=aprintf( " -m CMD, --mount=CMD use CMD to mount/unmount/eject mp3 sources\n"
614 " -M CMD, --mplayer=CMD use CMD when calling MPlayer\n"
616 " -S SUB, --sources=SUB search sources config in SUB subdirectory\n"
618 " -R DIR, --resume=DIR store global resume file in DIR\n"
622 sourcesSub ? sourcesSub:"none",
623 globalResumeDir ? globalResumeDir:"video dir"
628 bool cPluginMPlayer::ProcessArgs(int argc, char *argv[])
630 static struct option long_options[] = {
631 { "mount", required_argument, NULL, 'm' },
632 { "mplayer", required_argument, NULL, 'M' },
633 { "sources", required_argument, NULL, 'S' },
634 { "resume", required_argument, NULL, 'R' },
638 int c, option_index = 0;
639 while((c=getopt_long(argc,argv,"m:M:S:R:",long_options,&option_index))!=-1) {
641 case 'm': mountscript=optarg; break;
642 case 'M': MPlayerCmd=optarg; break;
643 case 'S': sourcesSub=optarg; break;
644 case 'R': globalResumeDir=optarg; break;
645 default: return false;
651 bool cPluginMPlayer::Initialize(void)
653 if(!CheckVDRVersion(1,4,5,"mplayer")) return false;
654 plugin_name="mplayer";
655 #if APIVERSNUM < 10507
658 i18n_name="vdr-mplayer";
660 MPlaySources.Load(AddDirectory(ConfigDirectory(sourcesSub),"mplayersources.conf"));
661 if(MPlaySources.Count()<1) {
662 esyslog("ERROR: you must have defined at least one source in mplayersources.conf");
663 fprintf(stderr,"No source(s) defined in mplayersources.conf\n");
666 #if APIVERSNUM < 10507
667 RegisterI18n(Phrases);
669 if(!(status=new cMPlayerStatus)) return false;
673 const char *cPluginMPlayer::MainMenuEntry(void)
675 return MPlayerSetup.HideMainMenu ? 0 : tr(MAINMENUENTRY);
678 cOsdMenu *cPluginMPlayer::MainMenuAction(void)
680 return new cMenuMPlayBrowse;
683 cMenuSetupPage *cPluginMPlayer::SetupMenu(void)
685 return new cMenuSetupMPlayer;
688 bool cPluginMPlayer::SetupParse(const char *Name, const char *Value)
690 if( !strcasecmp(Name, "ControlMode")) MPlayerSetup.SlaveMode = atoi(Value);
691 else if (!strcasecmp(Name, "HideMainMenu")) MPlayerSetup.HideMainMenu = atoi(Value);
692 else if (!strcasecmp(Name, "ResumeMode")) MPlayerSetup.ResumeMode = atoi(Value);
693 else if (!strcasecmp(Name, "PrevNextMode")) MPlayerSetup.PrevNextKeyMode = atoi(Value);
694 else if (!strncasecmp(Name,"KeyCmd", 6) && strlen(Name)==7 && isdigit(Name[6]))
695 strn0cpy(MPlayerSetup.KeyCmd[Name[6]-'0'],Value,sizeof(MPlayerSetup.KeyCmd[0]));
700 bool cPluginMPlayer::ExternalPlay(const char *path, bool test)
702 char real[PATH_MAX+1];
703 if(realpath(path,real)) {
704 cFileSource *src=MPlaySources.FindSource(real);
706 cFileObj *item=new cFileObj(src,0,0,otFile);
708 item->SplitAndSet(real);
709 if(item->GuessType()) {
712 cMPlayerControl::SetFile(item,true);
713 cControl::Launch(new cMPlayerControl);
719 else dsyslog("MPlayer service: cannot play '%s'",path);
721 else dsyslog("MPlayer service: GuessType() failed for '%s'",path);
725 else dsyslog("MPlayer service: cannot find source for '%s', real '%s'",path,real);
727 else if(errno!=ENOENT && errno!=ENOTDIR)
728 esyslog("ERROR: realpath: %s: %s",path,strerror(errno));
732 bool cPluginMPlayer::Service(const char *Id, void *Data)
734 if(!strcasecmp(Id,"MPlayer-Play-v1")) {
736 struct MPlayerServiceData *msd=(struct MPlayerServiceData *)Data;
737 msd->result=ExternalPlay(msd->data.filename,false);
741 else if(!strcasecmp(Id,"MPlayer-Test-v1")) {
743 struct MPlayerServiceData *msd=(struct MPlayerServiceData *)Data;
744 msd->result=ExternalPlay(msd->data.filename,true);
751 const char **cPluginMPlayer::SVDRPHelpPages(void)
753 static const char *HelpPages[] = {
755 " Triggers playback of file 'filename'.",
757 " Tests is playback of file 'filename' is possible.",
763 cString cPluginMPlayer::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
765 if(!strcasecmp(Command,"PLAY")) {
767 if(ExternalPlay(Option,false)) return "Playback triggered";
768 else { ReplyCode=550; return "Playback failed"; }
770 else { ReplyCode=501; return "Missing filename"; }
772 else if(!strcasecmp(Command,"TEST")) {
774 if(ExternalPlay(Option,true)) return "Playback possible";
775 else { ReplyCode=550; return "Playback not possible"; }
777 else { ReplyCode=501; return "Missing filename"; }
782 VDRPLUGINCREATOR(cPluginMPlayer); // Don't touch this!