2 * MP3/MPlayer plugin to VDR (C++)
4 * (C) 2001-2009 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 res[0]=tr("disabled");
73 res[1]=tr("global only");
74 res[2]=tr("local first");
75 Add(new cMenuEditStraItem(tr("Setup.MPlayer$Resume mode"), &data.ResumeMode, 3, res));
76 Add(new cMenuEditBoolItem(tr("Hide mainmenu entry"), &data.HideMainMenu));
77 for(int i=0; i<10; i++) {
79 snprintf(name,sizeof(name),"%s %d",tr("Setup.MPlayer$Slave command key"),i);
80 static const char allowed[] = { "abcdefghijklmnopqrstuvwxyz0123456789!\"§$%&/()=?{}[]\\+*~#',;.:-_<>|@´`^°" };
81 Add(new cMenuEditStrItem(name, data.KeyCmd[i],MAX_KEYCMD,allowed));
85 void cMenuSetupMPlayer::Store(void)
88 SetupStore("ControlMode", MPlayerSetup.SlaveMode);
89 SetupStore("HideMainMenu",MPlayerSetup.HideMainMenu);
90 SetupStore("ResumeMode", MPlayerSetup.ResumeMode);
91 for(int i=0; i<10; i++) {
93 snprintf(name,sizeof(name),"KeyCmd%d",i);
94 SetupStore(name,MPlayerSetup.KeyCmd[i]);
98 // --- cMPlayerControl ---------------------------------------------------------
100 class cMPlayerControl : public cControl {
102 static cFileObj *file;
104 cMPlayerPlayer *player;
105 cSkinDisplayReplay *display;
106 bool visible, modeOnly;
108 int lastCurrent, lastTotal;
111 bool jumpactive, jumphide, jumpmode;
115 void ShowTimed(int Seconds=0);
116 void ShowProgress(void);
118 void ShowTitle(void);
120 void JumpProcess(eKeys Key);
121 void JumpDisplay(void);
123 cMPlayerControl(void);
124 virtual ~cMPlayerControl();
125 virtual eOSState ProcessKey(eKeys Key);
126 virtual void Show(void) { ShowTimed(); }
127 virtual void Hide(void);
128 static void SetFile(const cFileObj *File, bool Rewind);
131 cFileObj *cMPlayerControl::file=0;
132 bool cMPlayerControl::rewind=false;
134 cMPlayerControl::cMPlayerControl(void)
135 :cControl(player=new cMPlayerPlayer(file,rewind))
137 visible=modeOnly=jumpactive=false;
143 cMPlayerControl::~cMPlayerControl()
146 cStatus::MsgReplaying(this,0,0,false);
150 void cMPlayerControl::SetFile(const cFileObj *File, bool Rewind)
153 file=File ? new cFileObj(File) : 0;
157 void cMPlayerControl::Stop(void)
159 delete player; player=0;
162 void cMPlayerControl::ShowTimed(int Seconds)
167 timeoutShow = Seconds>0 ? time(0)+Seconds : 0;
171 void cMPlayerControl::Hide(void)
174 delete display; display=0;
175 visible=modeOnly=false;
176 #if APIVERSNUM >= 10500
177 SetNeedsFastResponse(false);
179 needsFastResponse=false;
184 void cMPlayerControl::ShowTitle(void)
188 if(player) path=player->GetCurrentName();
190 path=file->FullPath();
194 const char *name=rindex(path,'/');
195 if(name) name++; else name=path;
196 if(!lastReplayMsg || strcmp(lastReplayMsg,path)) {
197 cStatus::MsgReplaying(this,name,path,true);
199 lastReplayMsg=strdup(path);
202 if(display) display->SetTitle(name);
205 if(release) free((void *)path);
208 void cMPlayerControl::ShowProgress(void)
212 if(GetIndex(Current,Total) && Total>0) {
215 display=Skins.Current()->DisplayReplay(false);
216 visible=true; modeOnly=false;
217 #if APIVERSNUM >= 10500
218 SetNeedsFastResponse(true);
220 needsFastResponse=true;
222 lastCurrent=lastTotal=-1;
226 if(abs(Current-lastCurrent)>12) {
227 if(Total>0) display->SetProgress(Current, Total);
228 display->SetCurrent(IndexToHMSF(Current));
229 display->SetTotal(IndexToHMSF(Total));
232 if(GetReplayMode(Play,Forward,Speed))
233 display->SetMode(Play, Forward, Speed);
236 lastCurrent=Current; lastTotal=Total;
244 void cMPlayerControl::ShowMode(void)
246 if(Setup.ShowReplayMode && !jumpactive) {
249 if(GetReplayMode(Play, Forward, Speed)) {
250 bool NormalPlay = (Play && Speed == -1);
253 if(NormalPlay) return;
254 display = Skins.Current()->DisplayReplay(true);
255 visible=modeOnly=true;
258 if(modeOnly && !timeoutShow && NormalPlay) timeoutShow=time(0)+SELECTHIDE_TIMEOUT;
260 display->SetMode(Play, Forward, Speed);
265 void cMPlayerControl::JumpDisplay(void)
268 const char *j=trVDR("Jump: "), u=jumpmode?'%':'m';
269 if(!jumpval) sprintf(buf,"%s- %c", j,u);
270 else sprintf(buf,"%s%d- %c",j,jumpval,u);
271 display->SetJump(buf);
274 void cMPlayerControl::JumpProcess(eKeys Key)
280 const int max=jumpmode?100:lastTotal;
281 if(jumpval*10+n <= max) jumpval=jumpval*10+n;
286 jumpmode=!jumpmode; jumpval=0;
291 player->Goto(jumpval*(jumpmode?1:60),jumpmode,false);
299 player->SkipSeconds(jumpval*60 * ((Key==kLeft || Key==kFastRew) ? -1:1));
315 void cMPlayerControl::Jump(void)
317 jumpval=0; jumphide=jumpmode=false;
319 ShowTimed(); if(!visible) return;
326 eOSState cMPlayerControl::ProcessKey(eKeys Key)
328 if(!player->Active()) { Hide(); Stop(); return osEnd; }
330 if(!player->SlaveMode()) {
331 if(Key==kBlue) { Hide(); Stop(); return osEnd; }
335 if(timeoutShow && time(0)>timeoutShow) {
340 if(modeOnly) ShowMode();
346 if(jumpactive && Key != kNone) {
351 bool DoShowMode = true;
354 case kUp: player->Play(); break;
357 case kDown: player->Pause(); break;
359 case kFastRew|k_Repeat:
362 case kLeft: player->SkipSeconds(-10); break;
364 case kFastFwd|k_Repeat:
366 case kRight|k_Repeat:
367 case kRight: player->SkipSeconds(10); break;
369 case kRed: Jump(); break;
371 case kGreen|k_Repeat: // temporary use
372 case kGreen: player->SkipSeconds(-60); break;
373 case kYellow|k_Repeat:
374 case kYellow: player->SkipSeconds(60); break;
375 // case kGreen|k_Repeat: // reserved for future use
376 // case kGreen: player->SkipPrev(); break;
377 // case kYellow|k_Repeat:
378 // case kYellow: player->SkipNext(); 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");
397 player->KeyCmd("seek_chapter +1");
400 player->KeyCmd("seek_chapter -1");
412 const char *cmd=MPlayerSetup.KeyCmd[Key-k0];
413 if(cmd[0]) player->KeyCmd(cmd);
421 if(DoShowMode) ShowMode();
426 // --- cMenuMPlayAid -----------------------------------------------------------
428 class cMenuMPlayAid : public cOsdMenu {
431 virtual eOSState ProcessKey(eKeys Key);
434 cMenuMPlayAid::cMenuMPlayAid(void)
435 :cOsdMenu(tr("MPlayer Audio ID"),20)
437 Add(new cMenuEditIntItem(tr("Audiostream ID"),&MPlayerAid,-1,255));
441 eOSState cMenuMPlayAid::ProcessKey(eKeys Key)
443 eOSState state=cOsdMenu::ProcessKey(Key);
444 if(state==osUnknown) {
446 case kOk: state=osBack; break;
453 // --- cMenuMPlayBrowse ---------------------------------------------------------
455 class cMenuMPlayBrowse : public cMenuBrowse {
457 bool sourcing, aidedit;
458 eOSState Source(bool second);
459 eOSState Summary(void);
461 virtual void SetButtons(void);
463 cMenuMPlayBrowse(void);
464 virtual eOSState ProcessKey(eKeys Key);
467 static const char *excl_sum[] = { ".*","*.summary","*.txt","*.nfo",0 };
469 cMenuMPlayBrowse::cMenuMPlayBrowse(void)
470 :cMenuBrowse(MPlaySources.GetSource(),false,false,tr("MPlayer browser"),excl_sum)
472 sourcing=aidedit=false;
476 void cMenuMPlayBrowse::SetButtons(void)
478 static char blue[12];
479 snprintf(blue,sizeof(blue),MPlayerAid>=0 ? "AID:%d" : "AID:def",MPlayerAid);
480 SetHelp(trVDR("Button$Play"), MPlayerSetup.ResumeMode ? trVDR("Button$Rewind"):0, tr("Source"), blue);
484 eOSState cMenuMPlayBrowse::Source(bool second)
486 if(HasSubMenu()) return osContinue;
490 return AddSubMenu(new cMenuSource(&MPlaySources,tr("MPlayer source")));
493 cFileSource *src=cMenuSource::GetSelected();
495 MPlaySources.SetSource(src);
502 eOSState cMenuMPlayBrowse::Summary(void)
504 cFileObj *item=CurrentItem();
505 if(item && item->Type()==otFile) {
506 static const char *exts[] = { ".summary",".txt",".nfo",0 };
507 for(int i=0; exts[i]; i++) {
509 strn0cpy(buff,item->FullPath(),sizeof(buff)-20);
510 char *e=&buff[strlen(buff)];
511 strn0cpy(e,exts[i],20);
512 int fd=open(buff,O_RDONLY);
514 if(fd<0 && (e=rindex(buff,'.'))) {
515 strn0cpy(e,exts[i],20);
516 fd=open(buff,O_RDONLY);
519 int r=read(fd,buff,sizeof(buff)-1);
523 return AddSubMenu(new cMenuText(tr("Summary"),buff));
531 eOSState cMenuMPlayBrowse::ProcessKey(eKeys Key)
533 eOSState state=cOsdMenu::ProcessKey(Key);
534 if(state==osContinue && !HasSubMenu()) {
535 if(sourcing) return Source(true);
536 if(aidedit) { aidedit=false; SetButtons(); }
539 if(state==osUnknown) {
543 cFileObj *item=CurrentItem();
544 if(item && item->Type()==otFile) {
545 lastselect=new cFileObj(item);
549 else state=osContinue;
557 state=AddSubMenu(new cMenuMPlayAid);
566 if(state==osUnknown) state=cMenuBrowse::ProcessStdKey(Key,state);
567 if(state==osBack && lastselect) {
568 cMPlayerControl::SetFile(lastselect,rew);
569 cControl::Launch(new cMPlayerControl);
575 // --- cPluginMPlayer ----------------------------------------------------------
577 static const char *DESCRIPTION = trNOOP("Media replay via MPlayer");
578 static const char *MAINMENUENTRY = "MPlayer";
580 class cPluginMPlayer : public cPlugin {
582 bool ExternalPlay(const char *path, bool test);
584 cPluginMPlayer(void);
585 virtual ~cPluginMPlayer();
586 virtual const char *Version(void) { return PluginVersion; }
587 virtual const char *Description(void) { return tr(DESCRIPTION); }
588 virtual const char *CommandLineHelp(void);
589 virtual bool ProcessArgs(int argc, char *argv[]);
590 virtual bool Initialize(void);
591 virtual const char *MainMenuEntry(void);
592 virtual cOsdMenu *MainMenuAction(void);
593 virtual cMenuSetupPage *SetupMenu(void);
594 virtual bool SetupParse(const char *Name, const char *Value);
595 virtual bool Service(const char *Id, void *Data);
596 virtual const char **SVDRPHelpPages(void);
597 virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode);
600 cPluginMPlayer::cPluginMPlayer(void)
602 // Initialize any member variables here.
603 // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
604 // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
608 cPluginMPlayer::~cPluginMPlayer()
613 const char *cPluginMPlayer::CommandLineHelp(void)
615 static char *help_str=0;
617 free(help_str); // for easier orientation, this is column 80|
618 help_str=aprintf( " -m CMD, --mount=CMD use CMD to mount/unmount/eject mp3 sources\n"
620 " -M CMD, --mplayer=CMD use CMD when calling MPlayer\n"
622 " -S SUB, --sources=SUB search sources config in SUB subdirectory\n"
624 " -R DIR, --resume=DIR store global resume file in DIR\n"
628 sourcesSub ? sourcesSub:"none",
629 globalResumeDir ? globalResumeDir:"video dir"
634 bool cPluginMPlayer::ProcessArgs(int argc, char *argv[])
636 static struct option long_options[] = {
637 { "mount", required_argument, NULL, 'm' },
638 { "mplayer", required_argument, NULL, 'M' },
639 { "sources", required_argument, NULL, 'S' },
640 { "resume", required_argument, NULL, 'R' },
644 int c, option_index = 0;
645 while((c=getopt_long(argc,argv,"m:M:S:R:",long_options,&option_index))!=-1) {
647 case 'm': mountscript=optarg; break;
648 case 'M': MPlayerCmd=optarg; break;
649 case 'S': sourcesSub=optarg; break;
650 case 'R': globalResumeDir=optarg; break;
651 default: return false;
657 bool cPluginMPlayer::Initialize(void)
659 if(!CheckVDRVersion(1,4,5,"mplayer")) return false;
660 plugin_name="mplayer";
661 #if APIVERSNUM < 10507
664 i18n_name="vdr-mplayer";
666 MPlaySources.Load(AddDirectory(ConfigDirectory(sourcesSub),"mplayersources.conf"));
667 if(MPlaySources.Count()<1) {
668 esyslog("ERROR: you must have defined at least one source in mplayersources.conf");
669 fprintf(stderr,"No source(s) defined in mplayersources.conf\n");
672 #if APIVERSNUM < 10507
673 RegisterI18n(Phrases);
675 if(!(status=new cMPlayerStatus)) return false;
679 const char *cPluginMPlayer::MainMenuEntry(void)
681 return MPlayerSetup.HideMainMenu ? 0 : tr(MAINMENUENTRY);
684 cOsdMenu *cPluginMPlayer::MainMenuAction(void)
686 return new cMenuMPlayBrowse;
689 cMenuSetupPage *cPluginMPlayer::SetupMenu(void)
691 return new cMenuSetupMPlayer;
694 bool cPluginMPlayer::SetupParse(const char *Name, const char *Value)
696 if( !strcasecmp(Name, "ControlMode")) MPlayerSetup.SlaveMode = atoi(Value);
697 else if (!strcasecmp(Name, "HideMainMenu")) MPlayerSetup.HideMainMenu = atoi(Value);
698 else if (!strcasecmp(Name, "ResumeMode")) MPlayerSetup.ResumeMode = atoi(Value);
699 else if (!strncasecmp(Name,"KeyCmd", 6) && strlen(Name)==7 && isdigit(Name[6]))
700 strn0cpy(MPlayerSetup.KeyCmd[Name[6]-'0'],Value,sizeof(MPlayerSetup.KeyCmd[0]));
705 bool cPluginMPlayer::ExternalPlay(const char *path, bool test)
707 char real[PATH_MAX+1];
708 if(realpath(path,real)) {
709 cFileSource *src=MPlaySources.FindSource(real);
711 cFileObj *item=new cFileObj(src,0,0,otFile);
713 item->SplitAndSet(real);
714 if(item->GuessType()) {
717 cMPlayerControl::SetFile(item,true);
718 cControl::Launch(new cMPlayerControl);
724 else dsyslog("MPlayer service: cannot play '%s'",path);
726 else dsyslog("MPlayer service: GuessType() failed for '%s'",path);
730 else dsyslog("MPlayer service: cannot find source for '%s', real '%s'",path,real);
732 else if(errno!=ENOENT && errno!=ENOTDIR)
733 esyslog("ERROR: realpath: %s: %s",path,strerror(errno));
737 bool cPluginMPlayer::Service(const char *Id, void *Data)
739 if(!strcasecmp(Id,"MPlayer-Play-v1")) {
741 struct MPlayerServiceData *msd=(struct MPlayerServiceData *)Data;
742 msd->result=ExternalPlay(msd->data.filename,false);
746 else if(!strcasecmp(Id,"MPlayer-Test-v1")) {
748 struct MPlayerServiceData *msd=(struct MPlayerServiceData *)Data;
749 msd->result=ExternalPlay(msd->data.filename,true);
756 const char **cPluginMPlayer::SVDRPHelpPages(void)
758 static const char *HelpPages[] = {
760 " Triggers playback of file 'filename'.",
762 " Tests is playback of file 'filename' is possible.",
768 cString cPluginMPlayer::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
770 if(!strcasecmp(Command,"PLAY")) {
772 if(ExternalPlay(Option,false)) return "Playback triggered";
773 else { ReplyCode=550; return "Playback failed"; }
775 else { ReplyCode=501; return "Missing filename"; }
777 else if(!strcasecmp(Command,"TEST")) {
779 if(ExternalPlay(Option,true)) return "Playback possible";
780 else { ReplyCode=550; return "Playback not possible"; }
782 else { ReplyCode=501; return "Missing filename"; }
787 VDRPLUGINCREATOR(cPluginMPlayer); // Don't touch this!