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, haveBeauty;
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=haveBeauty=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");
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 asprintf(&help_str," -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 (!strncasecmp(Name,"KeyCmd", 6) && strlen(Name)==7 && isdigit(Name[6]))
694 strn0cpy(MPlayerSetup.KeyCmd[Name[6]-'0'],Value,sizeof(MPlayerSetup.KeyCmd[0]));
699 bool cPluginMPlayer::ExternalPlay(const char *path, bool test)
701 char real[PATH_MAX+1];
702 if(realpath(path,real)) {
703 cFileSource *src=MPlaySources.FindSource(real);
705 cFileObj *item=new cFileObj(src,0,0,otFile);
707 item->SplitAndSet(real);
708 if(item->GuessType()) {
711 cMPlayerControl::SetFile(item,true);
712 cControl::Launch(new cMPlayerControl);
718 else dsyslog("MPlayer service: cannot play '%s'",path);
720 else dsyslog("MPlayer service: GuessType() failed for '%s'",path);
724 else dsyslog("MPlayer service: cannot find source for '%s', real '%s'",path,real);
726 else if(errno!=ENOENT && errno!=ENOTDIR)
727 esyslog("ERROR: realpath: %s: %s",path,strerror(errno));
731 bool cPluginMPlayer::Service(const char *Id, void *Data)
733 if(!strcasecmp(Id,"MPlayer-Play-v1")) {
735 struct MPlayerServiceData *msd=(struct MPlayerServiceData *)Data;
736 msd->result=ExternalPlay(msd->data.filename,false);
740 else if(!strcasecmp(Id,"MPlayer-Test-v1")) {
742 struct MPlayerServiceData *msd=(struct MPlayerServiceData *)Data;
743 msd->result=ExternalPlay(msd->data.filename,true);
750 const char **cPluginMPlayer::SVDRPHelpPages(void)
752 static const char *HelpPages[] = {
754 " Triggers playback of file 'filename'.",
756 " Tests is playback of file 'filename' is possible.",
762 cString cPluginMPlayer::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
764 if(!strcasecmp(Command,"PLAY")) {
766 if(ExternalPlay(Option,false)) return "Playback triggered";
767 else { ReplyCode=550; return "Playback failed"; }
769 else { ReplyCode=501; return "Missing filename"; }
771 else if(!strcasecmp(Command,"TEST")) {
773 if(ExternalPlay(Option,true)) return "Playback possible";
774 else { ReplyCode=550; return "Playback not possible"; }
776 else { ReplyCode=501; return "Missing filename"; }
781 VDRPLUGINCREATOR(cPluginMPlayer); // Don't touch this!