summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile11
-rw-r--r--play.cpp1654
-rw-r--r--player.c753
-rw-r--r--player.h113
4 files changed, 2527 insertions, 4 deletions
diff --git a/Makefile b/Makefile
index e7a6706..fa498f3 100644
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,8 @@ PLUGIN = play
### Configuration (edit this for your needs)
# support avfs a virtual file system
-AVFS ?= $(shell test -x /usr/bin/avfs-config && echo 1)
+# FIXME: AVFS isn't working, corrupts memory
+#AVFS ?= $(shell test -x /usr/bin/avfs-config && echo 1)
# use ffmpeg libswscale
SWCALE ?= $(shell pkg-config --exists libswscale && echo 1)
# support png images
@@ -20,7 +21,7 @@ PNG ?= $(shell pkg-config --exists libpng && echo 1)
# support jpg images
JPG ?= $(shell test -r /usr/include/jpeglib.h && echo 1)
-CONFIG := # -DDEBUG # uncomment to build DEBUG
+CONFIG := #-DDEBUG # uncomment to build DEBUG
ifeq ($(AVFS),1)
CONFIG += -DUSE_AVFS
@@ -106,7 +107,7 @@ override CFLAGS += $(_CFLAGS) $(DEFINES) $(INCLUDES) \
### The object files (add further files here):
-OBJS = $(PLUGIN).o dia.o video.o readdir.o
+OBJS = $(PLUGIN).o player.o video.o readdir.o
SRCS = $(wildcard $(OBJS:.o=.c)) $(PLUGIN).cpp
@@ -135,7 +136,9 @@ I18Npot = $(PODIR)/$(PLUGIN).pot
msgfmt -c -o $@ $<
$(I18Npot): $(SRCS)
- xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='<see README>' -o $@ `ls $^`
+ xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP \
+ -k_ -k_N --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) \
+ --msgid-bugs-address='<see README>' -o $@ `ls $^`
%.po: $(I18Npot)
msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $<
diff --git a/play.cpp b/play.cpp
new file mode 100644
index 0000000..8fc1808
--- /dev/null
+++ b/play.cpp
@@ -0,0 +1,1654 @@
+///
+/// @file play.cpp @brief A play plugin for VDR.
+///
+/// Copyright (c) 2012, 2013 by Johns. All Rights Reserved.
+///
+/// Contributor(s):
+///
+/// License: AGPLv3
+///
+/// This program is free software: you can redistribute it and/or modify
+/// it under the terms of the GNU Affero General Public License as
+/// published by the Free Software Foundation, either version 3 of the
+/// License.
+///
+/// This program 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 Affero General Public License for more details.
+///
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+
+#include <vdr/interface.h>
+#include <vdr/plugin.h>
+#include <vdr/player.h>
+#include <vdr/osd.h>
+#include <vdr/shutdown.h>
+#include <vdr/status.h>
+#include <vdr/videodir.h>
+
+#ifdef HAVE_CONFIG
+#include "config.h"
+#endif
+
+#include "play_service.h"
+extern "C"
+{
+#include "readdir.h"
+#include "video.h"
+#include "player.h"
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+ /// vdr-plugin version number.
+ /// Makefile extracts the version number for generating the file name
+ /// for the distribution archive.
+static const char *const VERSION = "0.0.14"
+#ifdef GIT_REV
+ "-GIT" GIT_REV
+#endif
+ ;
+
+ /// vdr-plugin description.
+static const char *const DESCRIPTION = trNOOP("A play plugin");
+
+ /// vdr-plugin text of main menu entry
+static const char *MAINMENUENTRY = trNOOP("Play");
+
+//////////////////////////////////////////////////////////////////////////////
+
+static char ConfigHideMainMenuEntry; ///< hide main menu entry
+
+//////////////////////////////////////////////////////////////////////////////
+// C Callbacks
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** Device plugin remote class.
+*/
+class cMyRemote:public cRemote
+{
+ public:
+
+ /**
+ ** Soft device remote class constructor.
+ **
+ ** @param name remote name
+ */
+ cMyRemote(const char *name):cRemote(name)
+ {
+ }
+
+ /**
+ ** Put keycode into vdr event queue.
+ **
+ ** @param code key code
+ ** @param repeat flag key repeated
+ ** @param release flag key released
+ */
+ bool Put(const char *code, bool repeat = false, bool release = false) {
+ return cRemote::Put(code, repeat, release);
+ }
+};
+
+/**
+** Feed key press as remote input (called from C part).
+**
+** @param keymap target keymap "XKeymap" name
+** @param key pressed/released key name
+** @param repeat repeated key flag
+** @param release released key flag
+*/
+extern "C" void FeedKeyPress(const char *keymap, const char *key, int repeat,
+ int release)
+{
+ cRemote *remote;
+ cMyRemote *csoft;
+
+ if (!keymap || !key) {
+ return;
+ }
+ // find remote
+ for (remote = Remotes.First(); remote; remote = Remotes.Next(remote)) {
+ if (!strcmp(remote->Name(), keymap)) {
+ break;
+ }
+ }
+ // if remote not already exists, create it
+ if (remote) {
+ csoft = (cMyRemote *) remote;
+ } else {
+ dsyslog("[play]%s: remote '%s' not found\n", __FUNCTION__, keymap);
+ csoft = new cMyRemote(keymap);
+ }
+
+ //dsyslog("[play]%s %s, %s\n", __FUNCTION__, keymap, key);
+ if (key[1]) { // no single character
+ csoft->Put(key, repeat, release);
+ } else if (!csoft->Put(key, repeat, release)) {
+ cRemote::Put(KBDKEY(key[0])); // feed it for edit mode
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// C Callbacks for diashow
+//////////////////////////////////////////////////////////////////////////////
+
+#if 0
+
+/**
+** Draw rectangle.
+*/
+extern "C" void DrawRectangle(int x1, int y1, int x2, int y2, uint32_t argb)
+{
+ //GlobalDiashow->Osd->DrawRectangle(x1, y1, x2, y2, argb);
+}
+
+/**
+** Draw text.
+**
+** @param FIXME:
+*/
+extern "C" void DrawText(int x, int y, const char *s, uint32_t fg, uint32_t bg,
+ int w, int h, int align)
+{
+ const cFont *font;
+
+ font = cFont::GetFont(fontOsd);
+ //GlobalDiashow->Osd->DrawText(x, y, s, fg, bg, font, w, h, align);
+}
+
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+// Play
+//////////////////////////////////////////////////////////////////////////////
+
+static char DvdNav; ///< dvdnav active
+static int PlayerVolume = -1; ///< volume 0 - 100
+static char PlayerPaused; ///< player paused
+static char PlayerSpeed; ///< player playback speed
+
+#define PlayerSendQuit()
+#define PlayerSendPause()
+#define PlayerSendVolume()
+#define PlayerSendSetSpeed(x)
+#define PlayerSendSeek(x)
+#define SendCommand(x)
+
+//////////////////////////////////////////////////////////////////////////////
+// cPlayer
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** Player class.
+*/
+class cMyPlayer:public cPlayer
+{
+ private:
+ char *FileName; ///< file to play
+
+ public:
+ cMyPlayer(const char *); ///< player constructor
+ virtual ~ cMyPlayer(); ///< player destructor
+ void Activate(bool); ///< player attached/detached
+ /// get current replay mode
+ virtual bool GetReplayMode(bool &, bool &, int &);
+};
+
+/**
+** Player constructor.
+**
+** @param filename path and name of file to play
+*/
+cMyPlayer::cMyPlayer(const char *filename)
+:cPlayer(pmExtern_THIS_SHOULD_BE_AVOIDED)
+{
+ dsyslog("play/%s: '%s'\n", __FUNCTION__, filename);
+
+ PlayerVolume = cDevice::CurrentVolume();
+ dsyslog("play: initial volume %d\n", PlayerVolume);
+
+ FileName = strdup(filename);
+}
+
+/**
+** Player destructor.
+*/
+cMyPlayer::~cMyPlayer()
+{
+ dsyslog("%s: end\n", __FUNCTION__);
+
+ PlayerStop();
+ free(FileName);
+}
+
+/**
+** Player attached or detached.
+*/
+void cMyPlayer::Activate(bool on)
+{
+ dsyslog("[play]%s: '%s' %d\n", __FUNCTION__, FileName, on);
+
+ if (on) {
+ PlayerStart(FileName);
+ } else {
+ PlayerStop();
+ }
+}
+
+/**
+** Get current replay mode.
+*/
+bool cMyPlayer::GetReplayMode(bool & play, bool & forward, int &speed)
+{
+ play = !PlayerPaused;
+ forward = true;
+ speed = play ? PlayerSpeed : -1;
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// cStatus
+//////////////////////////////////////////////////////////////////////////////
+
+class cMyStatus:public cStatus
+{
+ public:
+ cMyStatus(void);
+
+ protected:
+ virtual void SetVolume(int, bool); ///< volume changed
+
+ //bool GetVolume(int &, bool &);
+};
+
+static int Volume; ///< current volume
+cMyStatus *Status; ///< status monitor for volume
+
+/**
+** Status constructor.
+*/
+cMyStatus::cMyStatus(void)
+{
+ Volume = 0;
+}
+
+/**
+** Called if volume is set.
+*/
+void cMyStatus::SetVolume(int volume, bool absolute)
+{
+ dsyslog("play: volume %d %s\n", volume, absolute ? "abs" : "rel");
+
+ if (absolute) {
+ Volume = volume;
+ } else {
+ Volume += volume;
+ }
+
+ if (Volume != PlayerVolume) {
+ PlayerVolume = Volume;
+ PlayerSendVolume();
+ }
+}
+
+/**
+** Get volume.
+bool cMyStatus::GetVolume(int &volume, bool &mute)
+{
+}
+*/
+
+//////////////////////////////////////////////////////////////////////////////
+// cControl
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** Our player control class.
+*/
+class cMyControl:public cControl
+{
+ private:
+ cMyPlayer * Player; ///< our player
+ cSkinDisplayReplay *Display; ///< our osd display
+ void ShowReplayMode(void); ///< display replay mode
+ void ShowProgress(void); ///< display progress bar
+ virtual void Show(void); ///< show replay control
+ virtual void Hide(void); ///< hide replay control
+
+ public:
+ cMyControl(const char *);
+ virtual ~ cMyControl();
+
+ virtual eOSState ProcessKey(eKeys); ///< handle keyboard input
+
+};
+
+/**
+** Show replay mode.
+*/
+void cMyControl::ShowReplayMode(void)
+{
+ // use vdr setup
+ if (Display || (Setup.ShowReplayMode && !cOsd::IsOpen())) {
+ bool play;
+ bool forward;
+ int speed;
+
+ if (GetReplayMode(play, forward, speed)) {
+ if (!Display) {
+ // no need to show normal play
+ if (play && forward && speed == 1) {
+ return;
+ }
+ Display = Skins.Current()->DisplayReplay(true);
+ }
+ Display->SetMode(play, forward, speed);
+ }
+ }
+}
+
+/**
+** Show progress.
+*/
+void cMyControl::ShowProgress(void)
+{
+ // FIXME:
+}
+
+/**
+** Show control.
+*/
+void cMyControl::Show(void)
+{
+ dsyslog("%s:\n", __FUNCTION__);
+ if (!Display) {
+ ShowProgress();
+ }
+}
+
+/**
+** Control constructor.
+**
+** @param filename pathname of file to play.
+*/
+cMyControl::cMyControl(const char *filename)
+:cControl(Player = new cMyPlayer(filename))
+{
+ Display = NULL;
+ Status = new cMyStatus; // start monitoring volume
+
+ //LastSkipKey = kNone;
+ //LastSkipSeconds = REPLAYCONTROLSKIPSECONDS;
+ //LastSkipTimeout.Set(0);
+ cStatus::MsgReplaying(this, filename, filename, true);
+
+ cDevice::PrimaryDevice()->ClrAvailableTracks(true);
+}
+
+/**
+** Control destructor.
+*/
+cMyControl::~cMyControl()
+{
+ dsyslog("%s\n", __FUNCTION__);
+
+ delete Player;
+ delete Display;
+ delete Status;
+
+ Hide();
+ cStatus::MsgReplaying(this, NULL, NULL, false);
+ //Stop();
+}
+
+/**
+** Hide control.
+*/
+void cMyControl::Hide(void)
+{
+ dsyslog("%s:\n", __FUNCTION__);
+
+ if (Display) {
+ delete Display;
+
+ Display = NULL;
+ SetNeedsFastResponse(false);
+ }
+}
+
+/**
+** Process keyboard input.
+**
+** @param key pressed or releaded key
+*/
+eOSState cMyControl::ProcessKey(eKeys key)
+{
+ eOSState state;
+
+ dsyslog("%s: %d\n", __FUNCTION__, key);
+
+ if (!PlayerIsRunning()) { // check if player is still alive
+ dsyslog("play: player died\n");
+ Hide();
+ //FIXME: Stop();
+ return osEnd;
+ }
+ //state=cOsdMenu::ProcessKey(key);
+ state = osContinue;
+ switch ((int)key) { // cast to shutup g++ warnings
+ case kUp:
+ if (DvdNav) {
+ SendCommand("pausing_keep dvdnav up\n");
+ break;
+ }
+ case kPlay:
+ Hide();
+ if (PlayerSpeed != 1) {
+ PlayerSendSetSpeed(PlayerSpeed = 1);
+ }
+ if (PlayerPaused) {
+ PlayerSendPause();
+ PlayerPaused ^= 1;
+ }
+ ShowReplayMode();
+ break;
+
+ case kDown:
+ if (DvdNav) {
+ SendCommand("pausing_keep dvdnav down\n");
+ break;
+ }
+ case kPause:
+ PlayerSendPause();
+ PlayerPaused ^= 1;
+ ShowReplayMode();
+ break;
+
+ case kFastRew | k_Release:
+ case kLeft | k_Release:
+ if (Setup.MultiSpeedMode) {
+ break;
+ }
+ // FIXME:
+ break;
+ case kLeft:
+ if (DvdNav) {
+ SendCommand("pausing_keep dvdnav left\n");
+ break;
+ }
+ case kFastRew:
+ if (PlayerSpeed > 1) {
+ PlayerSendSetSpeed(PlayerSpeed /= 2);
+ } else {
+ PlayerSendSeek(-10);
+ }
+ ShowReplayMode();
+ break;
+ case kRight:
+ if (DvdNav) {
+ SendCommand("pausing_keep dvdnav right\n");
+ break;
+ }
+ case kFastFwd:
+ if (PlayerSpeed < 32) {
+ PlayerSendSetSpeed(PlayerSpeed *= 2);
+ }
+ ShowReplayMode();
+ break;
+
+ case kRed:
+ // FIXME: TimeSearch();
+ break;
+
+#ifdef USE_JUMPINGSECONDS
+ case kGreen | k_Repeat:
+ PlayerSendSeek(-Setup.JumpSecondsRepeat);
+ break;
+ case kGreen:
+ PlayerSendSeek(-Setup.JumpSeconds);
+ break;
+ case k1 | k_Repeat:
+ case k1:
+ PlayerSendSeek(-Setup.JumpSecondsSlow);
+ break;
+ case k3 | k_Repeat:
+ case k3:
+ PlayerSendSeek(Setup.JumpSecondsSlow);
+ break;
+ case kYellow | k_Repeat:
+ PlayerSendSeek(Setup.JumpSecondsRepeat);
+ break;
+ case kYellow:
+ PlayerSendSeek(Setup.JumpSeconds);
+ break;
+#else
+ case kGreen | k_Repeat:
+ case kGreen:
+ PlayerSendSeek(-60);
+ break;
+ case kYellow | k_Repeat:
+ case kYellow:
+ PlayerSendSeek(+60);
+ break;
+#endif /* JUMPINGSECONDS */
+#ifdef USE_LIEMIKUUTIO
+#ifndef USE_JUMPINGSECONDS
+ case k1 | k_Repeat:
+ case k1:
+ PlayerSendSeek(-20);
+ break;
+ case k3 | k_Repeat:
+ case k3:
+ PlayerSendSeek(+20);
+ break;
+#endif /* JUMPINGSECONDS */
+#endif
+
+ case kStop:
+ case kBlue:
+ Hide();
+ // FIXME: Stop();
+ return osEnd;
+
+ case kOk:
+ if (DvdNav) {
+ SendCommand("pausing_keep dvdnav select\n");
+ // FIXME: DvdNav = 0;
+ break;
+ }
+ // FIXME: full mode
+ ShowReplayMode();
+ break;
+
+ case kBack:
+ if (DvdNav > 1) {
+ SendCommand("pausing_keep dvdnav prev\n");
+ break;
+ }
+ PlayerSendQuit();
+ // FIXME: need to select old directory and index
+ cRemote::CallPlugin("play");
+ return osBack;
+
+ case kMenu:
+ if (DvdNav) {
+ SendCommand("pausing_keep dvdnav menu\n");
+ break;
+ }
+ break;
+
+ case kAudio: // VDR: eats the keys
+ case k7:
+ // FIXME: audio menu
+ SendCommand("pausing_keep switch_audio\n");
+ break;
+ case kSubtitles: // VDR: eats the keys
+ case k9:
+ // FIXME: subtitle menu
+ SendCommand("pausing_keep sub_select\n");
+ break;
+
+ default:
+ break;
+ }
+
+ return state;
+}
+
+/**
+** Play a file.
+**
+** @param filename path and file name
+*/
+static void PlayFile(const char *filename)
+{
+ dsyslog("play: play file '%s'\n", filename);
+ cControl::Launch(new cMyControl(filename));
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// cOsdMenu
+//////////////////////////////////////////////////////////////////////////////
+
+static char ShowBrowser; ///< flag show browser
+static const char *BrowserStartDir; ///< browser start directory
+static const NameFilter *BrowserFilters; ///< browser name filters
+
+/**
+** Table of supported video suffixes.
+*/
+static const NameFilter VideoFilters[] = {
+#define FILTER(x) { sizeof(x) - 1, x }
+ FILTER(".ts"), FILTER(".avi"), FILTER(".flv"), FILTER(".iso"),
+ FILTER(".m4v"), FILTER(".mkv"), FILTER(".mov"), FILTER(".mp4"),
+ FILTER(".mpg"), FILTER(".vdr"), FILTER(".vob"), FILTER(".wmv"),
+#undef FILTER
+ {
+ 0, NULL}
+};
+
+/**
+** Table of supported audio suffixes.
+*/
+static const NameFilter AudioFilters[] = {
+#define FILTER(x) { sizeof(x) - 1, x }
+ FILTER(".flac"), FILTER(".mp3"), FILTER(".ogg"), FILTER(".wav"),
+#undef FILTER
+ {
+ 0, NULL}
+};
+
+/**
+** Table of supported image suffixes.
+*/
+static const NameFilter ImageFilters[] = {
+#define FILTER(x) { sizeof(x) - 1, x }
+ FILTER(".cbr"), FILTER(".cbz"), FILTER(".zip"), FILTER(".rar"),
+ FILTER(".jpg"), FILTER(".png"),
+#undef FILTER
+ {
+ 0, NULL}
+};
+
+/**
+** Menu class.
+*/
+class cBrowser:public cOsdMenu
+{
+ private:
+ int DirStackCount; ///< elements in directory stack
+ char **DirStack; ///< current path directory stack
+ const NameFilter *Filter; ///< current filter
+
+ /// Create a browser menu for current directory
+ void CreateMenu(void);
+ /// Create a browser menu for new directory
+ void NewDir(const char *, const NameFilter *);
+ /// Handle menu level up
+ eOSState LevelUp(void);
+ /// Handle menu item selection
+ eOSState Selected(void);
+
+ public:
+ cBrowser(const char *, const char *, const NameFilter *);
+ virtual ~ cBrowser();
+ virtual eOSState ProcessKey(eKeys);
+};
+
+/**
+** Add item to menu. Called from C.
+**
+** @param obj cBrowser object
+** @param text menu text
+*/
+extern "C" void cBrowser__Add(void *obj, const char *text)
+{
+ cBrowser *menu;
+
+ // fucking stupid C++ can't assign void* without warning:
+ menu = (typeof(menu)) obj;
+ menu->Add(new cOsdItem(text));
+}
+
+/**
+** Create browser directory menu.
+*/
+void cBrowser::CreateMenu(void)
+{
+ // FIXME: should show only directory name in title
+ //SetTitle(DirStack[0]);
+ Skins.Message(mtStatus, tr("Scanning directory..."));
+
+ if (DirStackCount > 1) {
+ // FIXME: should show only path
+ Add(new cOsdItem(DirStack[0]));
+ }
+ ReadDirectory(DirStack[0], 1, NULL, cBrowser__Add, this);
+ ReadDirectory(DirStack[0], 0, Filter, cBrowser__Add, this);
+ // FIXME: handle errors!
+
+ Display(); // display build menu
+ Skins.Message(mtStatus, NULL); // clear read message
+}
+
+/**
+** Create directory menu.
+**
+** @param path directory path file name
+** @param filter name selection filter
+*/
+void cBrowser::NewDir(const char *path, const NameFilter * filter)
+{
+ int n;
+ char *pathname;
+
+ n = strlen(path);
+#if 1
+ // FIXME: force caller to do
+ if (path[n - 1] == '/') { // force '/' terminated
+ pathname = strdup(path);
+ } else {
+ pathname = (char *)malloc(n + 2);
+ stpcpy(stpcpy(pathname, path), "/");
+ }
+#endif
+
+ // push on directory stack
+ DirStack =
+ (typeof(DirStack)) realloc(DirStack,
+ (DirStackCount + 1) * sizeof(*DirStack));
+ memmove(DirStack + 1, DirStack, DirStackCount * sizeof(*DirStack));
+ DirStackCount++;
+ DirStack[0] = pathname;
+ Filter = filter;
+
+ CreateMenu();
+}
+
+/**
+** Menu constructor.
+**
+** @param title menu title
+** @param path directory path file name
+** @param filter name selection filter
+*/
+cBrowser::cBrowser(const char *title, const char *path,
+ const NameFilter * filter)
+:cOsdMenu(title)
+{
+ dsyslog("[play]%s:\n", __FUNCTION__);
+
+ DirStack = NULL;
+ DirStackCount = 0;
+ Filter = filter;
+
+ NewDir(path, filter);
+}
+
+/**
+** Menu destructor.
+*/
+cBrowser::~cBrowser()
+{
+ int i;
+
+ dsyslog("[play]%s:\n", __FUNCTION__);
+
+ // free the stored directory stack
+ for (i = 0; i < DirStackCount; ++i) {
+ free(DirStack[i]);
+ }
+ free(DirStack);
+}
+
+/**
+** Handle level up.
+*/
+eOSState cBrowser::LevelUp(void)
+{
+ char *down;
+ char *name;
+
+ if (DirStackCount <= 1) { // top level reached
+ return osEnd;
+ }
+ // go level up
+ --DirStackCount;
+ down = DirStack[0];
+ memmove(DirStack, DirStack + 1, DirStackCount * sizeof(*DirStack));
+
+ Clear();
+ CreateMenu();
+
+ // select item, where we gone down
+ down[strlen(down) - 1] = '\0'; // remove trailing '/'
+ name = strrchr(down, '/');
+ if (name) {
+ cOsdItem *item;
+ const char *text;
+ int i;
+
+ for (i = 0; (item = Get(i)); ++i) {
+ text = item->Text();
+ if (!strcmp(text, name + 1)) {
+ SetCurrent(item);
+ // FIXME: Display already called!
+ Display(); // display build menu
+ break;
+ }
+ }
+ }
+
+ free(down);
+
+ return osContinue;
+}
+
+/**
+** Handle selected item.
+*/
+eOSState cBrowser::Selected(void)
+{
+ int current;
+ const cOsdItem *item;
+ const char *text;
+ char *filename;
+ char *tmp;
+
+ current = Current(); // get current menu item index
+ item = Get(current);
+ text = item->Text();
+
+ if (current == 0 && DirStackCount > 1) {
+ return LevelUp();
+ }
+ // +2: \0 + #
+ filename = (char *)malloc(strlen(DirStack[0]) + strlen(text) + 2);
+ // path is '/' terminated
+ tmp = stpcpy(stpcpy(filename, DirStack[0]), text);
+ if (!IsDirectory(filename)) {
+ if (IsArchive(filename)) { // handle archives
+ stpcpy(tmp, "#");
+ Clear();
+ NewDir(filename, Filter);
+ free(filename);
+ // FIXME: if dir fails use keep old!
+ return osContinue;
+ }
+ PlayFile(filename);
+ free(filename);
+ return osEnd;
+ }
+ // handle DVD image
+ if (!strcmp(text, "AUDIO_TS") || !strcmp(text, "VIDEO_TS")) {
+ free(filename);
+ tmp = (char *)malloc(sizeof("dvdnav:///") + strlen(DirStack[0]));
+ strcpy(stpcpy(tmp, "dvdnav:///"), DirStack[0]);
+ PlayFile(tmp);
+ free(tmp);
+ return osEnd;
+ }
+ stpcpy(tmp, "/"); // append '/'
+ Clear();
+ NewDir(filename, Filter);
+ free(filename);
+ // FIXME: if dir fails use keep old!
+ return osContinue;
+}
+
+/**
+** Handle Menu key event.
+**
+** @param key key event
+*/
+eOSState cBrowser::ProcessKey(eKeys key)
+{
+ eOSState state;
+
+ //
+ // call standard function
+ state = cOsdMenu::ProcessKey(key);
+ dsyslog("[play]%s: %x - %x\n", __FUNCTION__, state, key);
+
+ switch (state) {
+ case osUnknown:
+ switch (key) {
+ case kOk:
+ return Selected();
+ case kBack:
+ return LevelUp();
+ default:
+ break;
+ }
+ break;
+ case osBack:
+ state = LevelUp();
+ if (state == osEnd) { // top level reached
+ ShowBrowser = 0;
+ return osPlugin;
+ }
+ default:
+ break;
+ }
+ return state;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// cOsdMenu
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** Play plugin menu class.
+*/
+class cPlayMenu:public cOsdMenu
+{
+ private:
+ public:
+ cPlayMenu(const char *, int = 0, int = 0, int = 0, int = 0, int = 0);
+ virtual ~ cPlayMenu();
+ virtual eOSState ProcessKey(eKeys);
+};
+
+/**
+** Play menu constructor.
+*/
+cPlayMenu::cPlayMenu(const char *title, int c0, int c1, int c2, int c3, int c4)
+:cOsdMenu(title, c0, c1, c2, c3, c4)
+{
+ SetHasHotkeys();
+
+ Add(new cOsdItem(hk(tr("Browse")), osUser1));
+ Add(new cOsdItem(hk(tr("Play optical disc")), osUser2));
+ Add(new cOsdItem(""));
+ Add(new cOsdItem(""));
+ Add(new cOsdItem(hk(tr("Play audio CD")), osUser5));
+ Add(new cOsdItem(hk(tr("Play video DVD")), osUser6));
+ Add(new cOsdItem(hk(tr("Browse audio")), osUser7));
+ Add(new cOsdItem(hk(tr("Browse image")), osUser8));
+ Add(new cOsdItem(hk(tr("Browse video")), osUser9));
+}
+
+/**
+** Play menu destructor.
+*/
+cPlayMenu::~cPlayMenu()
+{
+}
+
+/**
+** Handle play plugin menu key event.
+**
+** @param key key event
+*/
+eOSState cPlayMenu::ProcessKey(eKeys key)
+{
+ eOSState state;
+
+ dsyslog("[play]%s: %x\n", __FUNCTION__, key);
+
+ // call standard function
+ state = cOsdMenu::ProcessKey(key);
+
+ switch (state) {
+ case osUser1:
+ ShowBrowser = 1;
+ BrowserStartDir = ConfigBrowserRoot;
+ BrowserFilters = NULL;
+ return osPlugin; // restart with OSD browser
+
+ case osUser3: // play audio cdrom
+ PlayFile("cdda://");
+ return osEnd;
+ case osUser4: // play dvd
+ PlayFile("dvdnav://");
+ return osEnd;
+
+ case osUser5:
+ ShowBrowser = 1;
+ BrowserStartDir = ConfigBrowserRoot;
+ BrowserFilters = AudioFilters;
+ return osPlugin; // restart with OSD browser
+ case osUser6:
+ ShowBrowser = 1;
+ BrowserStartDir = ConfigBrowserRoot;
+ BrowserFilters = ImageFilters;
+ return osPlugin; // restart with OSD browser
+ case osUser7:
+ ShowBrowser = 1;
+ BrowserStartDir = ConfigBrowserRoot;
+ BrowserFilters = VideoFilters;
+ return osPlugin; // restart with OSD browser
+
+#if 0
+ case osUser9:
+ free(ShowDiashow);
+ ShowDiashow = strdup(VideoDirectory);
+ return osPlugin; // restart with OSD browser
+#endif
+
+ default:
+ break;
+ }
+ return state;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// cOsd
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** My device plugin OSD class.
+*/
+class cMyOsd:public cOsd
+{
+ public:
+ static volatile char Dirty; ///< flag force redraw everything
+ int OsdLevel; ///< current osd level
+
+ cMyOsd(int, int, uint); ///< osd constructor
+ virtual ~ cMyOsd(void); ///< osd destructor
+ virtual void Flush(void); ///< commits all data to the hardware
+ virtual void SetActive(bool); ///< sets OSD to be the active one
+};
+
+volatile char cMyOsd::Dirty; ///< flag force redraw everything
+
+/**
+** Sets this OSD to be the active one.
+**
+** @param on true on, false off
+**
+** @note only needed as workaround for text2skin plugin with
+** undrawn areas.
+*/
+void cMyOsd::SetActive(bool on)
+{
+ dsyslog("[play]%s: %d\n", __FUNCTION__, on);
+
+ if (Active() == on) {
+ return; // already active, no action
+ }
+ cOsd::SetActive(on);
+
+ // ignore sub-title, if menu is open
+ if (OsdLevel >= OSD_LEVEL_SUBTITLES && IsOpen()) {
+ return;
+ }
+
+ if (on) {
+ Dirty = 1;
+ // only flush here if there are already bitmaps
+ //if (GetBitmap(0)) {
+ // Flush();
+ //}
+ OsdOpen();
+ } else {
+ OsdClose();
+ }
+}
+
+/**
+** Constructor OSD.
+**
+** Initializes the OSD with the given coordinates.
+**
+** @param left x-coordinate of osd on display
+** @param top y-coordinate of osd on display
+** @param level level of the osd (smallest is shown)
+*/
+cMyOsd::cMyOsd(int left, int top, uint level)
+:cOsd(left, top, level)
+{
+ /* FIXME: OsdWidth/OsdHeight not correct!
+ dsyslog("[play]%s: %dx%d+%d+%d, %d\n", __FUNCTION__, OsdWidth(),
+ OsdHeight(), left, top, level);
+ */
+
+ OsdLevel = level;
+ SetActive(true);
+}
+
+/**
+** OSD Destructor.
+**
+** Shuts down the OSD.
+*/
+cMyOsd::~cMyOsd(void)
+{
+ dsyslog("[play]%s:\n", __FUNCTION__);
+ SetActive(false);
+ // done by SetActive: OsdClose();
+}
+
+/**
+** Actually commits all data to the OSD hardware.
+*/
+void cMyOsd::Flush(void)
+{
+ cPixmapMemory *pm;
+
+ dsyslog("[play]%s: level %d active %d\n", __FUNCTION__, OsdLevel,
+ Active());
+
+ if (!Active()) { // this osd is not active
+ return;
+ }
+ // don't draw sub-title if menu is active
+ if (OsdLevel >= OSD_LEVEL_SUBTITLES && IsOpen()) {
+ return;
+ }
+ //
+ // VDR draws subtitle without clearing the old
+ //
+ if (OsdLevel >= OSD_LEVEL_SUBTITLES) {
+ OsdClear();
+ cMyOsd::Dirty = 1;
+ dsyslog("[softhddev]%s: subtitle clear\n", __FUNCTION__);
+ }
+
+ if (!IsTrueColor()) {
+ cBitmap *bitmap;
+ int i;
+
+ // draw all bitmaps
+ for (i = 0; (bitmap = GetBitmap(i)); ++i) {
+ uint8_t *argb;
+ int x;
+ int y;
+ int w;
+ int h;
+ int x1;
+ int y1;
+ int x2;
+ int y2;
+
+ // get dirty bounding box
+ if (Dirty) { // forced complete update
+ x1 = 0;
+ y1 = 0;
+ x2 = bitmap->Width() - 1;
+ y2 = bitmap->Height() - 1;
+ } else if (!bitmap->Dirty(x1, y1, x2, y2)) {
+ continue; // nothing dirty continue
+ }
+ // convert and upload only dirty areas
+ w = x2 - x1 + 1;
+ h = y2 - y1 + 1;
+ if (1) { // just for the case it makes trouble
+ int width;
+ int height;
+ double video_aspect;
+
+ ::GetOsdSize(&width, &height, &video_aspect);
+ if (w > width) {
+ w = width;
+ x2 = x1 + width - 1;
+ }
+ if (h > height) {
+ h = height;
+ y2 = y1 + height - 1;
+ }
+ }
+#ifdef DEBUG
+ if (w > bitmap->Width() || h > bitmap->Height()) {
+ esyslog(tr("[play]: dirty area too big\n"));
+ abort();
+ }
+#endif
+ argb = (uint8_t *) malloc(w * h * sizeof(uint32_t));
+ for (y = y1; y <= y2; ++y) {
+ for (x = x1; x <= x2; ++x) {
+ ((uint32_t *) argb)[x - x1 + (y - y1) * w] =
+ bitmap->GetColor(x, y);
+ }
+ }
+ dsyslog("[play]%s: draw %dx%d%+d%+d bm\n", __FUNCTION__, w, h,
+ Left() + bitmap->X0() + x1, Top() + bitmap->Y0() + y1);
+ OsdDrawARGB(Left() + bitmap->X0() + x1, Top() + bitmap->Y0() + y1,
+ w, h, argb);
+
+ bitmap->Clean();
+ // FIXME: reuse argb
+ free(argb);
+ }
+ cMyOsd::Dirty = 0;
+ return;
+ }
+
+ LOCK_PIXMAPS;
+ while ((pm = RenderPixmaps())) {
+ int x;
+ int y;
+ int w;
+ int h;
+
+ x = Left() + pm->ViewPort().X();
+ y = Top() + pm->ViewPort().Y();
+ w = pm->ViewPort().Width();
+ h = pm->ViewPort().Height();
+
+ dsyslog("[play]%s: draw %dx%d%+d%+d %p\n", __FUNCTION__, w, h, x, y,
+ pm->Data());
+ OsdDrawARGB(x, y, w, h, pm->Data());
+
+ delete pm;
+ }
+ cMyOsd::Dirty = 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// cOsdProvider
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** My device plugin OSD provider class.
+*/
+class cMyOsdProvider:public cOsdProvider
+{
+ private:
+ static cOsd *Osd;
+
+ public:
+ virtual cOsd * CreateOsd(int, int, uint);
+ virtual bool ProvidesTrueColor(void);
+ cMyOsdProvider(void);
+};
+
+cOsd *cMyOsdProvider::Osd; ///< single osd
+
+/**
+** Create a new OSD.
+**
+** @param left x-coordinate of OSD
+** @param top y-coordinate of OSD
+** @param level layer level of OSD
+*/
+cOsd *cMyOsdProvider::CreateOsd(int left, int top, uint level)
+{
+ dsyslog("[play]%s: %d, %d, %d\n", __FUNCTION__, left, top, level);
+
+ return Osd = new cMyOsd(left, top, level);
+}
+
+/**
+** Check if this OSD provider is able to handle a true color OSD.
+**
+** @returns true we are able to handle a true color OSD.
+*/
+bool cMyOsdProvider::ProvidesTrueColor(void)
+{
+ return true;
+}
+
+/**
+** Create cOsdProvider class.
+*/
+cMyOsdProvider::cMyOsdProvider(void)
+: cOsdProvider()
+{
+ dsyslog("[play]%s:\n", __FUNCTION__);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// cMenuSetupPage
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** Play plugin menu setup page class.
+*/
+class cMyMenuSetupPage:public cMenuSetupPage
+{
+ protected:
+ ///
+ /// local copies of global setup variables:
+ /// @{
+ int HideMainMenuEntry;
+
+ /// @}
+ virtual void Store(void);
+
+ public:
+ cMyMenuSetupPage(void);
+ virtual eOSState ProcessKey(eKeys); // handle input
+};
+
+/**
+** Process key for setup menu.
+*/
+eOSState cMyMenuSetupPage::ProcessKey(eKeys key)
+{
+ eOSState state;
+
+ state = cMenuSetupPage::ProcessKey(key);
+
+ return state;
+}
+
+/**
+** Constructor setup menu.
+**
+** Import global config variables into setup.
+*/
+cMyMenuSetupPage::cMyMenuSetupPage(void)
+{
+ HideMainMenuEntry = ConfigHideMainMenuEntry;
+
+ Add(new cMenuEditBoolItem(tr("Hide main menu entry"), &HideMainMenuEntry,
+ trVDR("no"), trVDR("yes")));
+}
+
+/**
+** Store setup.
+*/
+void cMyMenuSetupPage::Store(void)
+{
+ SetupStore("HideMainMenuEntry", ConfigHideMainMenuEntry =
+ HideMainMenuEntry);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// cDevice
+//////////////////////////////////////////////////////////////////////////////
+
+class cMyDevice:public cDevice
+{
+ public:
+ cMyDevice(void);
+ virtual ~ cMyDevice(void);
+
+ virtual void GetOsdSize(int &, int &, double &);
+
+ protected:
+ virtual void MakePrimaryDevice(bool);
+};
+
+/**
+** Device constructor.
+*/
+cMyDevice::cMyDevice(void)
+{
+ dsyslog("[play]%s\n", __FUNCTION__);
+}
+
+/**
+** Device destructor. (never called!)
+*/
+cMyDevice::~cMyDevice(void)
+{
+ dsyslog("[play]%s:\n", __FUNCTION__);
+}
+
+/**
+** Informs a device that it will be the primary device.
+**
+** @param on flag if becoming or loosing primary
+*/
+void cMyDevice::MakePrimaryDevice(bool on)
+{
+ dsyslog("[play]%s: %d\n", __FUNCTION__, on);
+
+ cDevice::MakePrimaryDevice(on);
+ if (on) {
+ new cMyOsdProvider();
+ }
+}
+
+/**
+** Returns the width, height and pixel_aspect ratio the OSD.
+**
+** FIXME: Called every second, for nothing (no OSD displayed)?
+*/
+void cMyDevice::GetOsdSize(int &width, int &height, double &pixel_aspect)
+{
+ if (!&width || !&height || !&pixel_aspect) {
+ esyslog(tr("[play]: GetOsdSize invalid pointer(s)\n"));
+ return;
+ }
+ ::GetOsdSize(&width, &height, &pixel_aspect);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// cPlugin
+//////////////////////////////////////////////////////////////////////////////
+
+static cMyDevice *MyDevice; ///< dummy device needed for osd
+static volatile int DoMakePrimary; ///< switch primary device to this
+
+class cMyPlugin:public cPlugin
+{
+ public:
+ cMyPlugin(void);
+ virtual ~ cMyPlugin(void);
+ virtual const char *Version(void);
+ virtual const char *Description(void);
+ virtual const char *CommandLineHelp(void);
+ virtual bool ProcessArgs(int, char *[]);
+ virtual bool Initialize(void);
+ virtual void MainThreadHook(void);
+ virtual const char *MainMenuEntry(void);
+ virtual cOsdObject *MainMenuAction(void);
+ virtual cMenuSetupPage *SetupMenu(void);
+ virtual bool SetupParse(const char *, const char *);
+ virtual bool Service(const char *, void * = NULL);
+ virtual const char **SVDRPHelpPages(void);
+ virtual cString SVDRPCommand(const char *, const char *, int &);
+};
+
+/**
+** Initialize any member variables here.
+**
+** @note DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
+** VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
+*/
+cMyPlugin::cMyPlugin(void)
+{
+ dsyslog("[play]%s:\n", __FUNCTION__);
+}
+
+/**
+** Clean up after yourself!
+*/
+cMyPlugin::~cMyPlugin(void)
+{
+ dsyslog("[play]%s:\n", __FUNCTION__);
+}
+
+/**
+** Return plugin version number.
+**
+** @returns version number as constant string.
+*/
+const char *cMyPlugin::Version(void)
+{
+ return VERSION;
+}
+
+/**
+** Return plugin short description.
+**
+** @returns short description as constant string.
+*/
+const char *cMyPlugin::Description(void)
+{
+ return tr(DESCRIPTION);
+}
+
+/**
+** Return a string that describes all known command line options.
+**
+** @returns command line help as constant string.
+*/
+const char *cMyPlugin::CommandLineHelp(void)
+{
+ return::CommandLineHelp();
+}
+
+/**
+** Process the command line arguments.
+*/
+bool cMyPlugin::ProcessArgs(int argc, char *argv[])
+{
+ return::ProcessArgs(argc, argv);
+}
+
+/**
+** Start any background activities the plugin shall perform.
+*/
+bool cMyPlugin::Initialize(void)
+{
+ //dsyslog("[play]%s:\n", __FUNCTION__);
+
+ // FIXME: can delay until needed?
+ //Status = new cMyStatus; // start monitoring
+ // FIXME: destructs memory
+
+ MyDevice = new cMyDevice;
+ return true;
+}
+
+/**
+** Create main menu entry.
+*/
+const char *cMyPlugin::MainMenuEntry(void)
+{
+ //dsyslog("[play]%s:\n", __FUNCTION__);
+
+ return ConfigHideMainMenuEntry ? NULL : tr(MAINMENUENTRY);
+}
+
+/**
+** Perform the action when selected from the main VDR menu.
+*/
+cOsdObject *cMyPlugin::MainMenuAction(void)
+{
+ //dsyslog("[play]%s:\n", __FUNCTION__);
+
+#if 0
+ printf("plugin %s %d\n", ShowDiashow, ShowBrowser);
+ if (ShowDiashow) {
+ return new cDiashow(ShowDiashow);
+ }
+#endif
+ if (ShowBrowser) {
+ return new cBrowser("Browse", BrowserStartDir, BrowserFilters);
+ }
+ return new cPlayMenu("Play");
+}
+
+/**
+** Receive requests or messages.
+**
+** @param id unique identification string that identifies the
+** service protocol
+** @param data custom data structure
+*/
+bool cMyPlugin::Service(const char *id, void *data)
+{
+ if (strcmp(id, PLAY_OSD_3DMODE_SERVICE) == 0) {
+ VideoSetOsd3DMode(0);
+ Play_Osd3DModeService_v1_0_t *r =
+ (Play_Osd3DModeService_v1_0_t *) data;
+ VideoSetOsd3DMode(r->Mode);
+ return true;
+ }
+ return false;
+}
+
+/**
+** Return SVDRP commands help pages.
+**
+** return a pointer to a list of help strings for all of the plugin's
+** SVDRP commands.
+*/
+const char **cMyPlugin::SVDRPHelpPages(void)
+{
+ static const char *HelpPages[] = {
+ "3DOF\n" " TURN OFF 3D", "3DTB\n" " TURN ON 3D TB",
+ "3DSB\n" " TURN ON 3D SBS", NULL
+ };
+ return HelpPages;
+}
+
+/**
+** Handle SVDRP commands.
+**
+** @param command SVDRP command
+** @param option all command arguments
+** @param reply_code reply code
+*/
+cString cMyPlugin::SVDRPCommand(const char *command,
+ __attribute__ ((unused)) const char *option,
+ __attribute__ ((unused)) int &reply_code)
+{
+ if (!strcasecmp(command, "3DOF")) {
+ VideoSetOsd3DMode(0);
+ return "3d off";
+ }
+ if (!strcasecmp(command, "3DSB")) {
+ VideoSetOsd3DMode(1);
+ return "3d sbs";
+ }
+ if (!strcasecmp(command, "3DTB")) {
+ VideoSetOsd3DMode(2);
+ return "3d tb";
+ }
+ return NULL;
+}
+
+/**
+** Called for every plugin once during every cycle of VDR's main program
+** loop.
+*/
+void cMyPlugin::MainThreadHook(void)
+{
+ // dsyslog("[play]%s:\n", __FUNCTION__);
+
+ if (DoMakePrimary) {
+ dsyslog("[play]: switching primary device to %d\n", DoMakePrimary);
+ cDevice::SetPrimaryDevice(DoMakePrimary);
+ DoMakePrimary = 0;
+ }
+}
+
+/**
+** Return our setup menu.
+*/
+cMenuSetupPage *cMyPlugin::SetupMenu(void)
+{
+ //dsyslog("[play]%s:\n", __FUNCTION__);
+
+ return new cMyMenuSetupPage;
+}
+
+/**
+** Parse setup parameters
+**
+** @param name paramter name (case sensetive)
+** @param value value as string
+**
+** @returns true if the parameter is supported.
+*/
+bool cMyPlugin::SetupParse(const char *name, const char *value)
+{
+ dsyslog("[play]%s: '%s' = '%s'\n", __FUNCTION__, name, value);
+
+ if (!strcasecmp(name, "HideMainMenuEntry")) {
+ ConfigHideMainMenuEntry = atoi(value);
+ return true;
+ }
+#if 0
+ if (!strncasecmp(name, "Dia.", 4)) {
+ return DiaConfigParse(name + 4, value);
+ }
+#endif
+
+ return false;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+int OldPrimaryDevice; ///< old primary device
+
+/**
+** Enable dummy device.
+*/
+extern "C" void EnableDummyDevice(void)
+{
+ OldPrimaryDevice = cDevice::PrimaryDevice()->DeviceNumber() + 1;
+ DoMakePrimary = MyDevice->DeviceNumber() + 1;
+}
+
+/**
+** Disable dummy device.
+*/
+extern "C" void DisableDummyDevice(void)
+{
+ DoMakePrimary = OldPrimaryDevice;
+ OldPrimaryDevice = 0;
+}
+
+VDRPLUGINCREATOR(cMyPlugin); // Don't touch this!
diff --git a/player.c b/player.c
new file mode 100644
index 0000000..33af937
--- /dev/null
+++ b/player.c
@@ -0,0 +1,753 @@
+///
+/// @file player.c @brief A play plugin for VDR.
+///
+/// Copyright (c) 2012, 2013 by Johns. All Rights Reserved.
+///
+/// Contributor(s):
+///
+/// License: AGPLv3
+///
+/// This program is free software: you can redistribute it and/or modify
+/// it under the terms of the GNU Affero General Public License as
+/// published by the Free Software Foundation, either version 3 of the
+/// License.
+///
+/// This program 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 Affero General Public License for more details.
+///
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <poll.h>
+#include <pthread.h>
+
+#include <libintl.h>
+#define _(str) gettext(str) ///< gettext shortcut
+#define _N(str) str ///< gettext_noop shortcut
+
+#include "player.h"
+#include "video.h"
+#include "misc.h"
+
+//////////////////////////////////////////////////////////////////////////////
+
+const char *ConfigBrowserRoot = "/"; ///< browser starting point
+
+static char ConfigOsdOverlay; ///< show osd overlay
+static char ConfigUseSlave; ///< external player use slave mode
+static char ConfigFullscreen; ///< external player uses fullscreen
+static char *ConfigVideoOut; ///< video out device
+static char *ConfigAudioOut; ///< audio out device
+static char *ConfigAudioMixer; ///< audio mixer device
+static char *ConfigMixerChannel; ///< audio mixer channel
+static const char *ConfigMplayer = "/usr/bin/mplayer"; ///< mplayer executable
+static const char *ConfigMplayerArguments; ///< extra mplayer arguments
+static const char *ConfigX11Display = ":0.0"; ///< x11 display
+static uint32_t ConfigColorKey = 0x00020507; ///< color key
+
+//////////////////////////////////////////////////////////////////////////////
+// Osd
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** Open OSD.
+*/
+void OsdOpen(void)
+{
+ Debug(3, "play: %s\n", __FUNCTION__);
+
+ VideoWindowShow();
+}
+
+/**
+** Close OSD.
+*/
+void OsdClose(void)
+{
+ Debug(3, "play: %s\n", __FUNCTION__);
+
+ VideoWindowHide();
+ VideoWindowClear();
+}
+
+/**
+** Clear OSD.
+*/
+void OsdClear(void)
+{
+ Debug(3, "play: %s\n", __FUNCTION__);
+
+ VideoWindowClear();
+}
+
+/**
+** Get OSD size and aspect.
+**
+** @param width[OUT] width of OSD
+** @param height[OUT] height of OSD
+** @param aspect[OUT] aspect ratio (4/3, 16/9, ...) of OSD
+*/
+void GetOsdSize(int *width, int *height, double *aspect)
+{
+ VideoGetOsdSize(width, height);
+ *aspect = 16.0 / 9.0 / (double)*width * (double)*height;
+}
+
+/**
+** Draw osd pixmap
+*/
+void OsdDrawARGB(int x, int y, int w, int h, const uint8_t * argb)
+{
+ Debug(3, "play: %s %d,%d %d,%d\n", __FUNCTION__, x, y, w, h);
+
+ VideoDrawARGB(x, y, w, h, argb);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// External player
+//////////////////////////////////////////////////////////////////////////////
+
+static pid_t PlayerPid; ///< player pid
+static pthread_t PlayerThread; ///< player decode thread
+
+static char PlayerPipeBuf[4096]; ///< pipe buffer
+static int PlayerPipeCnt; ///< pipe buffer count
+static int PlayerPipeIdx; ///< pipe buffer index
+static int PlayerPipeOut[2]; ///< player write pipe
+static int PlayerPipeIn[2]; ///< player read pipe
+
+static char DvdNav; ///< dvdnav active
+
+static int PlayerVolume = -1; ///< volume 0 - 100
+static char PlayerPaused; ///< player paused
+static char PlayerSpeed; ///< player playback speed
+
+//////////////////////////////////////////////////////////////////////////////
+// Slave
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** Parse player output.
+**
+** @param data line pointer
+** @param size line length
+*/
+static void PlayerParseLine(const char *data, int size)
+{
+ Debug(4, "play/parse: |%.*s|\n", size, data);
+
+ // data is \0 terminated
+ if (!strncasecmp(data, "DVDNAV_TITLE_IS_MENU", 20)) {
+ DvdNav = 1;
+ } else if (!strncasecmp(data, "DVDNAV_TITLE_IS_MOVIE", 21)) {
+ DvdNav = 2;
+ } else if (!strncasecmp(data, "ID_DVD_VOLUME_ID=", 17)) {
+ Debug(3, "DVD_VOLUME = %s\n", data + 17);
+ } else if (!strncasecmp(data, "ID_AID_", 7)) {
+ char lang[64];
+ int aid;
+
+ if (sscanf(data, "ID_AID_%d_LANG=%s", &aid, lang) == 2) {
+ Debug(3, "AID(%d) = %s\n", aid, lang);
+ }
+ } else if (!strncasecmp(data, "ID_SID_", 7)) {
+ char lang[64];
+ int sid;
+
+ if (sscanf(data, "ID_SID_%d_LANG=%s", &sid, lang) == 2) {
+ Debug(3, "SID(%d) = %s\n", sid, lang);
+ }
+ }
+}
+
+/**
+** Poll input pipe.
+*/
+static void PlayerPollPipe(void)
+{
+ struct pollfd poll_fds[1];
+ int n;
+ int i;
+ int l;
+
+ // check if something to read
+ poll_fds[0].fd = PlayerPipeOut[0];
+ poll_fds[0].events = POLLIN;
+
+ switch (poll(poll_fds, 1, 0)) {
+ case 0: // timeout
+ return;
+ case -1: // error
+ Error(_("play/player: poll failed: %s\n"), strerror(errno));
+ return;
+ default: // ready
+ break;
+ }
+
+ // fill buffer
+ if ((n = read(PlayerPipeOut[0], PlayerPipeBuf + PlayerPipeCnt,
+ sizeof(PlayerPipeBuf) - PlayerPipeCnt)) < 0) {
+ Error(_("play/player: read failed: %s\n"), strerror(errno));
+ return;
+ }
+
+ PlayerPipeCnt += n;
+ l = 0;
+ for (i = PlayerPipeIdx; i < PlayerPipeCnt; ++i) {
+ if (PlayerPipeBuf[i] == '\n') {
+ PlayerPipeBuf[i] = '\0';
+ PlayerParseLine(PlayerPipeBuf + PlayerPipeIdx, i - PlayerPipeIdx);
+ PlayerPipeIdx = i + 1;
+ l = PlayerPipeIdx;
+ }
+ }
+
+ if (l) { // remove consumed bytes
+ if (PlayerPipeCnt - l) {
+ memmove(PlayerPipeBuf, PlayerPipeBuf + l, PlayerPipeCnt - l);
+ }
+ PlayerPipeCnt -= l;
+ PlayerPipeIdx -= l;
+ } else if (PlayerPipeCnt == sizeof(PlayerPipeBuf)) {
+ // no '\n' in buffer use it as single line
+ PlayerPipeBuf[sizeof(PlayerPipeBuf) - 1] = '\0';
+ PlayerParseLine(PlayerPipeBuf + PlayerPipeIdx, PlayerPipeCnt);
+ PlayerPipeIdx = 0;
+ PlayerPipeCnt = 0;
+ }
+}
+
+#define MPLAYER_MAX_ARGS 64 ///< number of arguments supported
+
+/**
+** Execute external player.
+**
+** @param filename path and name of file to play
+*/
+static void PlayerExec(const char *filename)
+{
+ const char *args[MPLAYER_MAX_ARGS];
+ int argn;
+ char wid_buf[32];
+ char volume_buf[32];
+ int i;
+
+ if (ConfigUseSlave) { // connect pipe to stdin/stdout
+ dup2(PlayerPipeIn[0], STDIN_FILENO);
+ close(PlayerPipeIn[0]);
+ close(PlayerPipeIn[1]);
+ close(PlayerPipeOut[0]);
+ dup2(PlayerPipeOut[1], STDOUT_FILENO);
+ dup2(PlayerPipeOut[1], STDERR_FILENO);
+ close(PlayerPipeOut[1]);
+ }
+ // close all file handles
+ for (i = getdtablesize() - 1; i > STDERR_FILENO; i--) {
+ close(i);
+ }
+
+ // export DISPLAY=
+
+ args[0] = ConfigMplayer;
+ args[1] = "-quiet";
+ args[2] = "-msglevel";
+ // FIXME: play with the options
+#ifdef DEBUG
+ args[3] = "all=6:global=4:cplayer=4:identify=4";
+#else
+ args[3] = "all=2:global=2:cplayer=2:identify=4";
+#endif
+ if (ConfigOsdOverlay) {
+ args[4] = "-noontop";
+ } else {
+ args[4] = "-ontop";
+ }
+ args[5] = "-noborder";
+ args[6] = "-nolirc";
+ args[7] = "-nojoystick"; // disable all unwanted inputs
+ args[8] = "-noar";
+ args[9] = "-nomouseinput";
+ args[10] = "-nograbpointer";
+ args[11] = "-noconsolecontrols";
+ args[12] = "-fixed-vo";
+ argn = 13;
+ // FIXME: dvd-device
+ if (!strncasecmp(filename, "cdda://", 7)) {
+ args[argn++] = "-cache"; // cdrom needs cache
+ args[argn++] = "1000";
+ } else {
+ args[argn++] = "-nocache"; // dvdnav needs nocache
+ }
+ if (ConfigUseSlave) {
+ args[argn++] = "-slave";
+ //args[argn++] = "-idle";
+ }
+ if (ConfigOsdOverlay) { // no mplayer osd with overlay
+ args[argn++] = "-osdlevel";
+ args[argn++] = "0";
+ }
+ if (ConfigFullscreen) {
+ args[argn++] = "-fs";
+ args[argn++] = "-zoom";
+ } else {
+ args[argn++] = "-nofs";
+ }
+ if (VideoGetPlayWindow()) {
+ snprintf(wid_buf, sizeof(wid_buf), "%d", VideoGetPlayWindow());
+ args[argn++] = "-wid";
+ args[argn++] = wid_buf;
+ }
+ if (ConfigVideoOut) {
+ args[argn++] = "-vo";
+ args[argn++] = ConfigVideoOut;
+ // add options based on selected video out
+ if (!strncmp(ConfigVideoOut, "vdpau", 5)) {
+ args[argn++] = "-vc";
+ args[argn++] =
+ "ffmpeg12vdpau,ffwmv3vdpau,ffvc1vdpau,ffh264vdpau,ffodivxvdpau,";
+ } else if (!strncmp(ConfigVideoOut, "vaapi", 5)) {
+ args[argn++] = "-va";
+ args[argn++] = "vaapi";
+ }
+ }
+ if (ConfigAudioOut) {
+ args[argn++] = "-ao";
+ args[argn++] = ConfigAudioOut;
+ // FIXME: -ac hwac3,hwdts,hwmpa,
+ }
+ if (ConfigAudioMixer) {
+ args[argn++] = "-mixer";
+ args[argn++] = ConfigAudioMixer;
+ }
+ if (ConfigMixerChannel) {
+ args[argn++] = "-mixer-channel";
+ args[argn++] = ConfigMixerChannel;
+ }
+ if (ConfigX11Display) {
+ args[argn++] = "-display";
+ args[argn++] = ConfigX11Display;
+ }
+ if (PlayerVolume != -1) {
+ // FIXME: here could be a problem with LANG
+ snprintf(volume_buf, sizeof(volume_buf), "%.2f",
+ (PlayerVolume * 100.0) / 255);
+ args[argn++] = "-volume";
+ args[argn++] = volume_buf;
+ }
+ // split X server arguments string into words
+ if (ConfigMplayerArguments) {
+ const char *sval;
+ char *buf;
+ char *s;
+
+ sval = ConfigMplayerArguments;
+#ifndef __FreeBSD__
+ s = buf = strdupa(sval);
+#else
+ s = buf = alloca(strlen(sval) + 1);
+ strcpy(buf, sval);
+#endif
+ while ((sval = strsep(&s, " \t"))) {
+ args[argn++] = sval;
+
+ if (argn == MPLAYER_MAX_ARGS - 2) { // argument overflow
+ Error(_("play: too many arguments for mplayer\n"));
+ // argn = 1;
+ break;
+ }
+ }
+ }
+ args[argn++] = filename;
+ args[argn] = NULL;
+#ifdef DEBUG
+ if (argn + 1 >= (int)(sizeof(args) / sizeof(*args))) {
+ Debug(3, "play: too many arguments %d\n", argn);
+ }
+ for (i = 0; i < argn; ++i) {
+ Debug(3, "%s", args[i]);
+ }
+#endif
+
+ execvp(args[0], (char *const *)args);
+
+ // shouldn't be reached
+ Error(_("play: execvp of '%s' failed: %s\n"), args[0], strerror(errno));
+ exit(-1);
+}
+
+/**
+** Execute external player.
+**
+** @param filename path and name of file to play
+*/
+static void PlayerForkAndExec(const char *filename)
+{
+ pid_t pid;
+
+ if (ConfigUseSlave) {
+ if (pipe(PlayerPipeIn)) {
+ Error(_("play: pipe failed: %s\n"), strerror(errno));
+ return;
+ }
+ if (pipe(PlayerPipeOut)) {
+ Error(_("play: pipe failed: %s\n"), strerror(errno));
+ return;
+ }
+ }
+
+ if ((pid = fork()) == -1) {
+ Error(_("play: fork failed: %s\n"), strerror(errno));
+ return;
+ }
+ if (!pid) { // child
+ PlayerExec(filename);
+ return;
+ }
+ PlayerPid = pid; // parent
+
+ if (ConfigUseSlave) {
+ close(PlayerPipeIn[0]);
+ close(PlayerPipeOut[1]);
+ }
+
+ Debug(3, "play: child pid=%d\n", pid);
+}
+
+/**
+** Close pipes.
+*/
+void PlaserClosePipes(void)
+{
+ if (ConfigUseSlave) {
+ if (PlayerPipeIn[0] != -1) {
+ close(PlayerPipeIn[0]);
+ }
+ if (PlayerPipeOut[1] != -1) {
+ close(PlayerPipeOut[1]);
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Thread
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** External player handler thread.
+**
+** @param dummy dummy pointer
+*/
+static void *PlayerHandlerThread(void *dummy)
+{
+ Debug(3, "play: player thread started\n");
+
+ while (PlayerIsRunning()) {
+ if (ConfigUseSlave) {
+ PlayerPollPipe();
+ // FIXME: wait only if pipe not ready
+ }
+ if (ConfigOsdOverlay) {
+ VideoPollEvents(10);
+ } else {
+ usleep(10 * 1000);
+ }
+ }
+
+ Debug(3, "play: player thread stopped\n");
+ PlayerThread = 0;
+
+ return dummy;
+}
+
+/**
+** Initialize player threads.
+*/
+static void PlayerThreadInit(void)
+{
+ pthread_create(&PlayerThread, NULL, PlayerHandlerThread, NULL);
+ pthread_setname_np(PlayerThread, "play player");
+}
+
+/**
+** Exit and cleanup player threads.
+*/
+static void PlayerThreadExit(void)
+{
+ if (PlayerThread) {
+ void *retval;
+
+ Debug(3, "play: player thread canceled\n");
+ if (pthread_cancel(PlayerThread)) {
+ Error(_("play: can't queue cancel player thread\n"));
+ }
+ if (pthread_join(PlayerThread, &retval) || retval != PTHREAD_CANCELED) {
+ Error(_("play: can't cancel player thread\n"));
+ }
+ PlayerThread = 0;
+ }
+}
+
+/**
+** Send command to player.
+*/
+static void SendCommand(const char *format, ...)
+{
+ va_list va;
+ char buf[256];
+ int n;
+
+ if (!PlayerPid) {
+ return;
+ }
+ if (PlayerPipeIn[1] == -1) {
+ Error(_("play: no pipe to send command available\n"));
+ return;
+ }
+ va_start(va, format);
+ n = vsnprintf(buf, sizeof(buf), format, va);
+ va_end(va);
+
+ Debug(3, "play: send '%.*s'\n", n - 1, buf);
+
+ // FIXME: poll pipe if ready
+ if (write(PlayerPipeIn[1], buf, n) != n) {
+ fprintf(stderr, "play: write failed\n");
+ }
+}
+
+/**
+** Send player quit.
+*/
+void PlayerSendQuit(void)
+{
+ if (ConfigUseSlave) {
+ SendCommand("quit\n");
+ }
+}
+
+/**
+** Send player pause.
+*/
+void PlayerSendPause(void)
+{
+ if (ConfigUseSlave) {
+ SendCommand("pause\n");
+ }
+}
+
+/**
+** Send player speed_set.
+*/
+void PlayerSendSetSpeed(int speed)
+{
+ if (ConfigUseSlave) {
+ SendCommand("pausing_keep speed_set %d\n", speed);
+ }
+}
+
+/**
+** Send player seek.
+*/
+void PlayerSendSeek(int seconds)
+{
+ if (ConfigUseSlave) {
+ SendCommand("pausing_keep seek %+d 0\n", seconds);
+ }
+}
+
+/**
+** Send player volume.
+*/
+void PlayerSendVolume(void)
+{
+ if (ConfigUseSlave) {
+ // FIXME: %.2f could have a problem with LANG
+ SendCommand("pausing_keep volume %.2f 1\n",
+ (PlayerVolume * 100.0) / 255);
+ }
+}
+
+/**
+** Start external player.
+**
+** @param filename path and name of file to play
+*/
+void PlayerStart(const char *filename)
+{
+ PlayerPipeCnt = 0; // reset to defaults
+ PlayerPipeIdx = 0;
+
+ PlayerPipeIn[0] = -1;
+ PlayerPipeIn[1] = -1;
+ PlayerPipeOut[0] = -1;
+ PlayerPipeOut[1] = -1;
+ PlayerPid = 0;
+
+ PlayerPaused = 0;
+ PlayerSpeed = 1;
+
+ DvdNav = 0;
+
+ if (ConfigOsdOverlay) { // overlay wanted
+ VideoSetColorKey(ConfigColorKey);
+ VideoInit(ConfigX11Display);
+ EnableDummyDevice();
+ }
+ PlayerForkAndExec(filename);
+
+ if (ConfigOsdOverlay || ConfigUseSlave) {
+ PlayerThreadInit();
+ }
+}
+
+/**
+** Stop external player.
+*/
+void PlayerStop(void)
+{
+ PlayerThreadExit();
+
+ if (ConfigOsdOverlay) {
+ DisableDummyDevice();
+ VideoExit();
+ }
+}
+
+/**
+** Is external player still running?
+*/
+int PlayerIsRunning(void)
+{
+ pid_t wpid;
+ int status;
+
+ if (!PlayerPid) { // no player
+ return 0;
+ }
+
+ wpid = waitpid(PlayerPid, &status, WNOHANG);
+ if (wpid <= 0) {
+ return 1;
+ }
+ if (WIFEXITED(status)) {
+ Debug(3, "play: player exited (%d)\n", WEXITSTATUS(status));
+ }
+ if (WIFSIGNALED(status)) {
+ Debug(3, "play: player killed (%d)\n", WTERMSIG(status));
+ }
+ PlayerPid = 0;
+ return 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Device/Plugin C part
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** Return command line help string.
+*/
+const char *CommandLineHelp(void)
+{
+ return " -/\t\tbrowser root directory\n"
+ " -a audio\tmplayer -ao (alsa:device=hw=0.0) overwrites mplayer.conf\n"
+ " -d display\tX11 display (default :0.0) overwrites $DISPLAY\n"
+ " -f\t\tmplayer fullscreen playback\n"
+ " -g geometry\tx11 window geometry wxh+x+y\n"
+ " -k colorkey\tvideo color key (default=0x020507, mplayer2=0x76B901)\n"
+ " -m mplayer\tfilename of mplayer executable\n"
+ " -M args\targuments for mplayer\n"
+ " -o\t\tosd overlay experiments\n" " -s\t\tmplayer slave mode\n"
+ " -v video\tmplayer -vo (vdpau:deint=4:hqscaling=1) overwrites mplayer.conf\n";
+}
+
+/**
+** Process the command line arguments.
+**
+** @param argc number of arguments
+** @param argv arguments vector
+*/
+int ProcessArgs(int argc, char *const argv[])
+{
+ const char *s;
+
+ //
+ // Parse arguments.
+ //
+#ifdef __FreeBSD__
+ if (!strcmp(*argv, "play")) {
+ ++argv;
+ --argc;
+ }
+#endif
+ if ((s = getenv("DISPLAY"))) {
+ ConfigX11Display = s;
+ }
+
+ for (;;) {
+ switch (getopt(argc, argv, "-/:b:d:fg:k:m:M:osv:")) {
+ case '/': // browser root
+ ConfigBrowserRoot = optarg;
+ continue;
+ case 'a': // audio out
+ ConfigAudioOut = optarg;
+ continue;
+ case 'd': // display x11
+ ConfigX11Display = optarg;
+ continue;
+ case 'f': // fullscreen mode
+ ConfigFullscreen = 1;
+ continue;
+ case 'g': // geometry
+ VideoSetGeometry(optarg);
+ continue;
+ case 'k': // color key
+ ConfigColorKey = strtol(optarg, NULL, 0);
+ continue;
+ case 'm': // mplayer executable
+ ConfigMplayer = optarg;
+ continue;
+ case 'M': // mplayer extra arguments
+ ConfigMplayerArguments = optarg;
+ continue;
+ case 'o': // osd / overlay
+ ConfigOsdOverlay = 1;
+ continue;
+ case 's': // slave mode
+ ConfigUseSlave = 1;
+ continue;
+ case 'v': // video out
+ ConfigVideoOut = optarg;
+ continue;
+ case EOF:
+ break;
+ case '-':
+ fprintf(stderr, _("We need no long options\n"));
+ return 0;
+ case ':':
+ fprintf(stderr, _("Missing argument for option '%c'\n"),
+ optopt);
+ return 0;
+ default:
+ fprintf(stderr, _("Unkown option '%c'\n"), optopt);
+ return 0;
+ }
+ break;
+ }
+ while (optind < argc) {
+ fprintf(stderr, _("Unhandled argument '%s'\n"), argv[optind++]);
+ }
+
+ return 1;
+}
diff --git a/player.h b/player.h
new file mode 100644
index 0000000..cce0265
--- /dev/null
+++ b/player.h
@@ -0,0 +1,113 @@
+///
+/// @file player.h @brief A play plugin header file.
+///
+/// Copyright (c) 2012, 2013 by Johns. All Rights Reserved.
+///
+/// Contributor(s):
+///
+/// License: AGPLv3
+///
+/// This program is free software: you can redistribute it and/or modify
+/// it under the terms of the GNU Affero General Public License as
+/// published by the Free Software Foundation, either version 3 of the
+/// License.
+///
+/// This program 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 Affero General Public License for more details.
+///
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+ /// C callback feed key press
+ extern void FeedKeyPress(const char *, const char *, int, int);
+
+ /// C callback enable dummy device
+ extern void EnableDummyDevice(void);
+ /// C callback disable dummy device
+ extern void DisableDummyDevice(void);
+
+ /// C plugin get osd size and ascpect
+ extern void GetOsdSize(int *, int *, double *);
+
+ /// C plugin open osd
+ extern void OsdOpen(void);
+ /// C plugin close osd
+ extern void OsdClose(void);
+ /// C plugin clear osd
+ extern void OsdClear(void);
+ /// C plugin draw osd pixmap
+ extern void OsdDrawARGB(int, int, int, int, const uint8_t *);
+
+ /// C plugin play audio packet
+ extern int PlayAudio(const uint8_t *, int, uint8_t);
+ /// C plugin play TS audio packet
+ extern int PlayTsAudio(const uint8_t *, int);
+ /// C plugin set audio volume
+ extern void SetVolumeDevice(int);
+
+ /// C plugin play video packet
+ extern int PlayVideo(const uint8_t *, int);
+ /// C plugin play TS video packet
+ extern void PlayTsVideo(const uint8_t *, int);
+ /// C plugin grab an image
+ extern uint8_t *GrabImage(int *, int, int, int, int);
+
+ /// C plugin set play mode
+ extern int SetPlayMode(int);
+ /// C plugin get current system time counter
+ extern int64_t GetSTC(void);
+ /// C plugin get video stream size and aspect
+ extern void GetVideoSize(int *, int *, double *);
+ /// C plugin set trick speed
+ extern void TrickSpeed(int);
+ /// C plugin clears all video and audio data from the device
+ extern void Clear(void);
+ /// C plugin sets the device into play mode
+ extern void Play(void);
+ /// C plugin sets the device into "freeze frame" mode
+ extern void Freeze(void);
+ /// C plugin mute audio
+ extern void Mute(void);
+ /// C plugin display I-frame as a still picture.
+ extern void StillPicture(const uint8_t *, int);
+ /// C plugin poll if ready
+ extern int Poll(int);
+ /// C plugin flush output buffers
+ extern int Flush(int);
+
+ /// C plugin command line help
+ extern const char *CommandLineHelp(void);
+ /// C plugin process the command line arguments
+ extern int ProcessArgs(int, char *const[]);
+
+ /// C plugin exit + cleanup
+ extern void PlayExit(void);
+ /// C plugin start code
+ extern int Start(void);
+ /// C plugin stop code
+ extern void Stop(void);
+ /// C plugin house keeping
+ extern void Housekeeping(void);
+ /// C plugin main thread hook
+ extern void MainThreadHook(void);
+
+ /// Browser root=start directory
+ extern const char *ConfigBrowserRoot;
+ extern const char *X11DisplayName; ///< x11 display name
+
+ /// Start external player
+ extern void PlayerStart(const char *name);
+ /// Stop external player
+ extern void PlayerStop(void);
+ /// Is external player still running
+ extern int PlayerIsRunning(void);
+
+#ifdef __cplusplus
+}
+#endif