summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--Config.cpp86
-rw-r--r--Config.h80
-rw-r--r--ControlServer.cpp17
-rw-r--r--ControlServer.h37
-rw-r--r--Directory.cpp34
-rw-r--r--Directory.h51
-rw-r--r--Makefile193
-rw-r--r--Media.cpp54
-rw-r--r--Media.h62
-rw-r--r--MediaContainer.cpp46
-rw-r--r--MediaContainer.h63
-rw-r--r--PVideo.cpp59
-rw-r--r--PVideo.h67
-rw-r--r--PlexHTTPRequestHandler.cpp182
-rw-r--r--PlexHTTPRequestHandler.h63
-rw-r--r--PlexHelper.cpp16
-rw-r--r--PlexHelper.h17
-rw-r--r--PlexReqHandlerFactory.cpp33
-rw-r--r--PlexReqHandlerFactory.h28
-rw-r--r--PlexServer.cpp41
-rw-r--r--PlexServer.h81
-rw-r--r--Plexservice.cpp205
-rw-r--r--Plexservice.h65
-rw-r--r--SubscriptionManager.cpp118
-rw-r--r--SubscriptionManager.h79
-rw-r--r--XmlObject.cpp87
-rw-r--r--XmlObject.h34
-rw-r--r--cPlexOsdItem.cpp34
-rw-r--r--cPlexOsdItem.h39
-rw-r--r--misc.h157
-rw-r--r--play_service.h31
-rw-r--r--player.c984
-rw-r--r--player.h160
-rw-r--r--plex.cpp1335
-rw-r--r--plex.h222
-rw-r--r--plexgdm.cpp130
-rw-r--r--plexgdm.h75
-rw-r--r--user.cpp31
-rw-r--r--user.h42
-rw-r--r--video.c775
-rw-r--r--video.h62
42 files changed, 5981 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index b8bd026..8bd4302 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,9 @@
*.exe
*.out
*.app
+
+# Other files
+.dependencies
+*.pot
+*.project
+*~
diff --git a/Config.cpp b/Config.cpp
new file mode 100644
index 0000000..ff4b721
--- /dev/null
+++ b/Config.cpp
@@ -0,0 +1,86 @@
+#include "Config.h"
+
+
+std::string Config::GetUUID() {
+ if(s_uuid.empty()) {
+ using Poco::UUIDGenerator;
+ using Poco::UUID;
+ UUIDGenerator &generator = UUIDGenerator::defaultGenerator();
+ UUID uuid(generator.createRandom());
+ s_uuid = uuid.toString();
+ }
+ return s_uuid;
+}
+
+void Config::SetUUID(const char* uuid) {
+ if (uuid) s_uuid = std::string(uuid);
+}
+
+
+std::string Config::GetHostname() {
+ if(s_hostname.empty()) {
+ char hostname[1024];
+ gethostname(hostname, 1024);
+ s_hostname = std::string(hostname);
+ }
+ return s_hostname;
+}
+
+std::string Config::GetLanguage() {
+ return "de";
+}
+
+std::string Config::GetUsername() {
+ return s_username;
+}
+
+std::string Config::GetPassword() {
+ return s_password;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// cMenuSetupPage
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** Process key for setup menu.
+*/
+eOSState cMyMenuSetupPage::ProcessKey(eKeys key)
+{
+ return cMenuSetupPage::ProcessKey(key);
+}
+
+/**
+** Constructor setup menu.
+**
+** Import global config variables into setup.
+*/
+cMyMenuSetupPage::cMyMenuSetupPage(void)
+{
+ strn0cpy(Username, Config::GetInstance().s_username.c_str(), STRING_SIZE);
+ strn0cpy(Password, Config::GetInstance().s_password.c_str(), STRING_SIZE);
+ strn0cpy(Uuid, Config::GetInstance().GetUUID().c_str(), STRING_SIZE);
+
+ Add(new cMenuEditBoolItem(tr("Hide main menu entry"), (int*)&Config::GetInstance().HideMainMenuEntry, trVDR("no"), trVDR("yes")));
+ Add(new cMenuEditBoolItem(tr("Disable remote"), (int*)&Config::GetInstance().DisableRemote, trVDR("no"), trVDR("yes")));
+ Add(new cMenuEditStrItem(tr("Plex Username"), Username, STRING_SIZE));
+ Add(new cMenuEditStrItem(tr("Plex Password"), Password, STRING_SIZE));
+ cMenuEditStrItem* devUUID = new cMenuEditStrItem(tr("Current UUID"), Uuid, STRING_SIZE);
+ devUUID->SetSelectable(false);
+ Add(devUUID);
+}
+
+/**
+** Store setup.
+*/
+void cMyMenuSetupPage::Store(void)
+{
+ Config::GetInstance().s_username = std::string(Username);
+ Config::GetInstance().s_password = std::string(Password);
+
+ SetupStore("HideMainMenuEntry", Config::GetInstance().HideMainMenuEntry);
+ SetupStore("DisableRemote", Config::GetInstance().DisableRemote);
+ SetupStore("Username", Config::GetInstance().s_username.c_str());
+ SetupStore("Password", Config::GetInstance().s_password.c_str());
+ SetupStore("UUID", Config::GetInstance().GetUUID().c_str());
+} \ No newline at end of file
diff --git a/Config.h b/Config.h
new file mode 100644
index 0000000..3bc805a
--- /dev/null
+++ b/Config.h
@@ -0,0 +1,80 @@
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#include <Poco/UUID.h>
+#include <Poco/UUIDGenerator.h>
+#include <string>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+//VDR
+#include <vdr/osd.h>
+#include <vdr/menuitems.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.1"
+#ifdef GIT_REV
+ "-GIT" GIT_REV
+#endif
+ ;
+
+#define STRING_SIZE 256
+
+class Config
+{
+
+public:
+ static Config& GetInstance() {
+ static Config instance;
+ return instance;
+ }
+
+ std::string s_username = "username";
+ std::string s_password = "password";
+
+ bool HideMainMenuEntry;
+ bool DisableRemote;
+
+ std::string GetUUID();
+ void SetUUID(const char* uuid);
+ std::string GetHostname();
+ std::string GetLanguage();
+ std::string GetUsername();
+ std::string GetPassword();
+
+
+private:
+ Config() {}
+ std::string s_uuid;
+ std::string s_hostname;
+};
+
+
+//////////////////////////////////////////////////////////////////////////////
+// cMenuSetupPage
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** Play plugin menu setup page class.
+*/
+class cMyMenuSetupPage:public cMenuSetupPage
+{
+ protected:
+ char Username[STRING_SIZE];
+ char Password[STRING_SIZE];
+ char Uuid[STRING_SIZE];
+
+ virtual void Store(void);
+
+ public:
+ cMyMenuSetupPage(void);
+ virtual eOSState ProcessKey(eKeys); // handle input
+};
+
+#endif // CONFIG_H
diff --git a/ControlServer.cpp b/ControlServer.cpp
new file mode 100644
index 0000000..439dc7a
--- /dev/null
+++ b/ControlServer.cpp
@@ -0,0 +1,17 @@
+#include "ControlServer.h"
+
+namespace plexclient
+{
+
+void ControlServer::Start() {
+ // start the HTTPServer
+ m_pSrv->start();
+}
+
+void ControlServer::Stop() {
+ // Stop the HTTPServer
+ m_pSrv->stop();
+}
+
+
+}
diff --git a/ControlServer.h b/ControlServer.h
new file mode 100644
index 0000000..55f8299
--- /dev/null
+++ b/ControlServer.h
@@ -0,0 +1,37 @@
+#ifndef CONTROLSERVER_H
+#define CONTROLSERVER_H
+
+#include <Poco/Net/HTTPServer.h>
+#include <Poco/Net/HTTPRequestHandler.h>
+#include <Poco/Net/HTTPRequestHandlerFactory.h>
+#include <Poco/Net/HTTPServerParams.h>
+#include <Poco/Net/ServerSocket.h>
+
+#include "PlexHTTPRequestHandler.h"
+#include "PlexReqHandlerFactory.h"
+
+namespace plexclient
+{
+
+class ControlServer
+{
+
+public:
+ static ControlServer& GetInstance() {
+ static ControlServer instance;
+ return instance;
+ }
+ void Start();
+ void Stop();
+
+private:
+ ControlServer() {};
+
+ Poco::Net::ServerSocket *m_pSvs = new Poco::Net::ServerSocket(3200);;
+ Poco::Net::HTTPServer *m_pSrv = new Poco::Net::HTTPServer(new PlexReqHandlerFactory, *m_pSvs, new Poco::Net::HTTPServerParams);
+
+};
+
+}
+
+#endif // CONTROLSERVER_H
diff --git a/Directory.cpp b/Directory.cpp
new file mode 100644
index 0000000..c54580f
--- /dev/null
+++ b/Directory.cpp
@@ -0,0 +1,34 @@
+#include "Directory.h"
+
+namespace plexclient
+{
+
+Directory::Directory(Poco::XML::Node* pNode)
+{
+ if(Poco::icompare(pNode->nodeName(), "Directory") == 0) {
+
+ Poco::XML::AutoPtr<Poco::XML::NamedNodeMap> pAttribs = pNode->attributes();
+
+ m_bAllowSync = GetNodeValueAsBool(pAttribs->getNamedItem("allowSync"));
+ m_sArt = GetNodeValue(pAttribs->getNamedItem("art"));
+ m_sThumb = GetNodeValue(pAttribs->getNamedItem("thumb"));
+ m_sKey = GetNodeValue(pAttribs->getNamedItem("key"));
+ m_sTitle = GetNodeValue(pAttribs->getNamedItem("title"));
+ m_sComposite = GetNodeValue(pAttribs->getNamedItem("composite"));
+ m_sLanguage = GetNodeValue(pAttribs->getNamedItem("language"));
+ m_sUuid = GetNodeValue(pAttribs->getNamedItem("uuid"));
+ m_tUpdatedAt = GetNodeValueAsTimeStamp(pAttribs->getNamedItem("updatedAt"));
+ m_tCreatedAt = GetNodeValueAsTimeStamp(pAttribs->getNamedItem("createdAt"));
+ m_eType = GetNodeValueAsMediaType(pAttribs->getNamedItem("type"));
+
+ pAttribs->release();
+ }
+}
+
+Directory::~Directory()
+{
+}
+
+
+}
+
diff --git a/Directory.h b/Directory.h
new file mode 100644
index 0000000..fb54bc5
--- /dev/null
+++ b/Directory.h
@@ -0,0 +1,51 @@
+#ifndef DIRECTORY_H
+#define DIRECTORY_H
+
+#include <Poco/DOM/DOMParser.h>
+#include <Poco/DOM/Document.h>
+#include <Poco/DOM/NamedNodeMap.h>
+#include <Poco/DOM/NodeIterator.h>
+#include <Poco/DOM/NodeFilter.h>
+#include <Poco/DOM/AutoPtr.h>
+#include <Poco/Exception.h>
+#include <Poco/Timestamp.h>
+#include <Poco/String.h>
+
+#include "XmlObject.h"
+
+using Poco::XML::DOMParser;
+using Poco::XML::Document;
+using Poco::XML::NodeIterator;
+using Poco::XML::NodeFilter;
+using Poco::XML::Node;
+using Poco::XML::AutoPtr;
+using Poco::Exception;
+
+namespace plexclient
+{
+
+class Directory: XmlObject
+{
+public:
+ Directory(Poco::XML::Node* pNode);
+ ~Directory();
+
+public:
+ bool m_bAllowSync;
+ std::string m_sTitle;
+ std::string m_sComposite;
+ std::string m_sLanguage;
+ std::string m_sUuid;
+ std::string m_sArt;
+ std::string m_sThumb;
+ Poco::Timestamp m_tUpdatedAt;
+ Poco::Timestamp m_tCreatedAt;
+ std::string m_sKey;
+ MediaType m_eType;
+
+};
+
+}
+
+
+#endif // DIRECTORY_H
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..1344831
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,193 @@
+#
+# Makefile for a Video Disk Recorder plugin
+#
+# $Id: ac85bc689caa43a6c25d7e0573733b36fab6167e $
+
+# The official name of this plugin.
+# This name will be used in the '-P...' option of VDR to load the plugin.
+# By default the main source file also carries this name.
+
+PLUGIN = plex
+
+LIBS += -lPocoUtil -lPocoNet -lPocoNetSSL -lPocoXML -lPocoFoundation
+
+### Configuration (edit this for your needs)
+
+ # support avfs a virtual file system
+# FIXME: AVFS isn't working, corrupts memory
+#AVFS ?= $(shell test -x /usr/bin/avfs-config && echo 1)
+ # use ffmpeg libswscale
+SWSCALE ?= $(shell pkg-config --exists libswscale && echo 1)
+ # support png images
+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
+
+ifeq ($(AVFS),1)
+CONFIG += -DUSE_AVFS
+_CFLAGS += $(shell /usr/bin/avfs-config --cflags)
+LIBS += $(shell /usr/bin/avfs-config --libs)
+endif
+ifeq ($(SWSCALE),1)
+CONFIG += -DUSE_SWSCALE
+_CFLAGS += $(shell pkg-config --cflags libswscale)
+LIBS += $(shell pkg-config --libs libswscale)
+endif
+ifeq ($(PNG),1)
+CONFIG += -DUSE_PNG
+_CFLAGS += $(shell pkg-config --cflags libpng)
+LIBS += $(shell pkg-config --libs libpng)
+endif
+ifeq ($(JPG),1)
+CONFIG += -DUSE_JPG
+_CFLAGS += -I/usr/include
+LIBS += -Ljpeg
+
+endif
+
+
+_CFLAGS += $(shell pkg-config --cflags xcb xcb-image xcb-keysyms xcb-icccm)
+LIBS += -lrt $(shell pkg-config --libs xcb xcb-image xcb-keysyms xcb-icccm)
+
+### The version number of this plugin (taken from the main source file):
+
+VERSION = $(shell grep 'static const char \*const VERSION *=' $(PLUGIN).cpp | awk '{ print $$7 }' | sed -e 's/[";]//g')
+GIT_REV = $(shell git describe --always 2>/dev/null)
+
+### The directory environment:
+
+# Use package data if installed...otherwise assume we're under the VDR source directory:
+PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell pkg-config --variable=$(1) vdr || pkg-config --variable=$(1) ../../../vdr.pc))
+LIBDIR = $(call PKGCFG,libdir)
+LOCDIR = $(call PKGCFG,locdir)
+PLGCFG = $(call PKGCFG,plgcfg)
+#
+TMPDIR ?= /tmp
+
+### The compiler options:
+
+export CFLAGS = $(call PKGCFG,cflags)
+export CXXFLAGS = $(call PKGCFG,cxxflags)
+
+CXXFLAGS += -std=gnu++0x -Wunused-variable -Wunused-parameter
+
+ifeq ($(CFLAGS),)
+$(error CFLAGS not set)
+endif
+ifeq ($(CXXFLAGS),)
+$(error CXXFLAGS not set)
+endif
+
+### The version number of VDR's plugin API:
+
+APIVERSION = $(call PKGCFG,apiversion)
+
+### Allow user defined options to overwrite defaults:
+
+-include $(PLGCFG)
+
+### The name of the distribution archive:
+
+ARCHIVE = $(PLUGIN)-$(VERSION)
+PACKAGE = vdr-$(ARCHIVE)
+
+### The name of the shared object file:
+
+SOFILE = libvdr-$(PLUGIN).so
+
+### Includes and Defines (add further entries here):
+
+INCLUDES +=
+
+DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"' -D_GNU_SOURCE $(CONFIG) \
+ $(if $(GIT_REV), -DGIT_REV='"$(GIT_REV)"')
+
+### Make it standard
+
+override CXXFLAGS += $(_CFLAGS) $(DEFINES) $(INCLUDES) \
+ -g -W -Wall -Wextra -Winit-self -Werror=overloaded-virtual
+override CFLAGS += $(_CFLAGS) $(DEFINES) $(INCLUDES) \
+ -g -W -Wall -Wextra -Winit-self -Wdeclaration-after-statement
+
+### The object files (add further files here):
+
+OBJS = $(patsubst %.c,%.o,$(wildcard *.c))
+OBJS += $(patsubst %.cpp,%.o,$(wildcard *.cpp))
+
+SRCS = $(wildcard $(OBJS:.o=.c)) $(PLUGIN).cpp
+
+### The main target:
+
+all: $(SOFILE) i18n
+
+### Dependencies:
+
+MAKEDEP = $(CXX) -MM -MG
+DEPFILE = .dependencies
+$(DEPFILE): Makefile
+ @$(MAKEDEP) $(CXXFLAGS) $(SRCS) > $@
+
+-include $(DEPFILE)
+
+### Internationalization (I18N):
+
+PODIR = po
+I18Npo = $(wildcard $(PODIR)/*.po)
+I18Nmo = $(addsuffix .mo, $(foreach file, $(I18Npo), $(basename $(file))))
+I18Nmsgs = $(addprefix $(DESTDIR)$(LOCDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
+I18Npot = $(PODIR)/$(PLUGIN).pot
+
+%.mo: %.po
+ 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 $^`
+
+%.po: $(I18Npot)
+ msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $<
+ @touch $@
+
+$(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
+ install -D -m644 $< $@
+
+.PHONY: i18n
+i18n: $(I18Nmo) $(I18Npot)
+
+install-i18n: $(I18Nmsgs)
+
+### Targets:
+
+$(OBJS): Makefile
+
+$(SOFILE): $(OBJS)
+ $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $(OBJS) $(LIBS) -o $@
+
+install-lib: $(SOFILE)
+ install -D $^ $(DESTDIR)$(LIBDIR)/$^.$(APIVERSION)
+
+install: install-lib install-i18n
+
+dist: $(I18Npo) clean
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @mkdir $(TMPDIR)/$(ARCHIVE)
+ @cp -a * $(TMPDIR)/$(ARCHIVE)
+ @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE)
+ @-rm -rf $(TMPDIR)/$(ARCHIVE)
+ @echo Distribution package created as $(PACKAGE).tgz
+
+clean:
+ @-rm -f $(PODIR)/*.mo $(PODIR)/*.pot
+ @-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~
+
+## Private Targets:
+
+HDRS= $(wildcard *.h)
+
+indent:
+ for i in $(SRCS) $(HDRS); do \
+ indent $$i; \
+ unexpand -a $$i | sed -e s/constconst/const/ > $$i.up; \
+ mv $$i.up $$i; \
+ done
diff --git a/Media.cpp b/Media.cpp
new file mode 100644
index 0000000..d3c53a2
--- /dev/null
+++ b/Media.cpp
@@ -0,0 +1,54 @@
+#include "Media.h"
+
+namespace plexclient
+{
+
+Media::Media(Poco::XML::Node* pNode)
+{
+ NodeIterator it(pNode, Poco::XML::NodeFilter::SHOW_ALL);
+ Poco::XML::Node* pChildNode = it.nextNode();
+
+ while(pChildNode) {
+ if(Poco::icompare(pChildNode->nodeName(), "Media") == 0) {
+
+ Poco::XML::AutoPtr<Poco::XML::NamedNodeMap> pAttribs = pChildNode->attributes();
+ m_sVideoResolution = GetNodeValue(pAttribs->getNamedItem("videoResolution"));
+ m_iId = GetNodeValueAsInt(pAttribs->getNamedItem("id"));
+ m_lDuration = GetNodeValueAsLong(pAttribs->getNamedItem("duration"));
+ m_iBitrate = GetNodeValueAsInt(pAttribs->getNamedItem("bitrate"));
+ m_iWidth = GetNodeValueAsInt(pAttribs->getNamedItem("width"));
+ m_iHeight = GetNodeValueAsInt(pAttribs->getNamedItem("height"));
+ m_sAspectRatio = GetNodeValue(pAttribs->getNamedItem("aspectRatio"));
+ m_iAudioChannels = GetNodeValueAsInt(pAttribs->getNamedItem("audioChannels"));
+ m_sAudioCodec = GetNodeValue(pAttribs->getNamedItem("audioCodec"));
+ m_sVideoCodec = GetNodeValue(pAttribs->getNamedItem("videoCodec"));
+ m_sContainer = GetNodeValue(pAttribs->getNamedItem("container"));
+ m_sVideoFrameRate = GetNodeValue(pAttribs->getNamedItem("videoFrameRate"));
+
+
+ pAttribs->release();
+
+ }
+ if(Poco::icompare(pChildNode->nodeName(), "Part") == 0) {
+
+ Poco::XML::AutoPtr<Poco::XML::NamedNodeMap> pAttribs = pChildNode->attributes();
+ m_sPartKey = GetNodeValue(pAttribs->getNamedItem("key"));
+ m_iPartId = GetNodeValueAsInt(pAttribs->getNamedItem("id"));
+ m_lPartDuration = GetNodeValueAsLong(pAttribs->getNamedItem("duration"));
+ m_sPartFile = GetNodeValue(pAttribs->getNamedItem("file"));
+ m_lPartSize = GetNodeValueAsLong(pAttribs->getNamedItem("size"));
+ m_sPartContainer = GetNodeValue(pAttribs->getNamedItem("container"));
+
+ pAttribs->release();
+ }
+ pChildNode = it.nextNode();
+ }
+}
+
+Media::~Media()
+{
+}
+
+
+}
+
diff --git a/Media.h b/Media.h
new file mode 100644
index 0000000..f2394cd
--- /dev/null
+++ b/Media.h
@@ -0,0 +1,62 @@
+#ifndef MEDIA_H
+#define MEDIA_H
+
+#include <Poco/DOM/DOMParser.h>
+#include <Poco/DOM/Document.h>
+#include <Poco/DOM/NamedNodeMap.h>
+#include <Poco/DOM/NodeIterator.h>
+#include <Poco/DOM/NodeFilter.h>
+#include <Poco/DOM/NodeList.h>
+#include <Poco/DOM/AutoPtr.h>
+#include <Poco/Exception.h>
+#include <Poco/Timestamp.h>
+#include <Poco/String.h>
+
+#include <vector>
+#include <iostream>
+
+#include "XmlObject.h" // Base class: model::XmlObject
+
+using Poco::XML::DOMParser;
+using Poco::XML::Document;
+using Poco::XML::NodeIterator;
+using Poco::XML::NodeFilter;
+using Poco::XML::Node;
+using Poco::XML::AutoPtr;
+using Poco::Exception;
+
+namespace plexclient
+{
+
+class Media: XmlObject
+{
+public:
+ Media(Poco::XML::Node* pNode);
+ ~Media();
+
+public:
+ std::string m_sVideoResolution;
+ int m_iId;
+ long m_lDuration;
+ int m_iBitrate;
+ int m_iWidth;
+ int m_iHeight;
+ std::string m_sAspectRatio;
+ int m_iAudioChannels;
+ std::string m_sAudioCodec;
+ std::string m_sVideoCodec;
+ std::string m_sContainer;
+ std::string m_sVideoFrameRate;
+
+ std::string m_sPartKey;
+ int m_iPartId;
+ long m_lPartDuration;
+ std::string m_sPartFile;
+ long m_lPartSize;
+ std::string m_sPartContainer;
+
+};
+
+}
+
+#endif // MEDIA_H
diff --git a/MediaContainer.cpp b/MediaContainer.cpp
new file mode 100644
index 0000000..08b386f
--- /dev/null
+++ b/MediaContainer.cpp
@@ -0,0 +1,46 @@
+#include "MediaContainer.h"
+
+namespace plexclient
+{
+MediaContainer::MediaContainer(std::istream* response)
+{
+ try {
+ InputSource src(*response);
+ DOMParser parser;
+ Poco::XML::AutoPtr<Document> pDoc = parser.parse(&src);
+
+ NodeIterator it(pDoc, Poco::XML::NodeFilter::SHOW_ALL);
+ Poco::XML::Node* pNode = it.nextNode();
+
+ while(pNode) {
+ if(Poco::icompare(pNode->nodeName(), "MediaContainer") == 0) {
+ Poco::XML::NamedNodeMap* pAttribs = pNode->attributes();
+
+ m_sTitle = GetNodeValue(pAttribs->getNamedItem("title1"));
+ m_sTitle2 = GetNodeValue(pAttribs->getNamedItem("title2"));
+ m_sThumb = GetNodeValue(pAttribs->getNamedItem("thumb"));
+ m_sViewGroup = GetNodeValue(pAttribs->getNamedItem("viewGroup"));
+ m_sLibrarySectionTitle = GetNodeValue(pAttribs->getNamedItem("librarySectionTitle"));
+ m_sLibrarySectionUUID = GetNodeValue(pAttribs->getNamedItem("librarySectionUUID"));
+ m_iLibrarySectionID = GetNodeValueAsInt(pAttribs->getNamedItem("librarySectionID"));
+ m_sMediaTagPrefix = GetNodeValue(pAttribs->getNamedItem("mediaTagPrefix"));
+ m_iSize = GetNodeValueAsInt(pAttribs->getNamedItem("size"));
+ m_bAllowSync = GetNodeValueAsBool(pAttribs->getNamedItem("allowSync"));
+ m_sArt = GetNodeValue(pAttribs->getNamedItem("art"));
+
+ pAttribs->release();
+ } else if(Poco::icompare(pNode->nodeName(), "Directory") == 0) {
+ m_vDirectories.push_back(Directory(pNode));
+ } else if(Poco::icompare(pNode->nodeName(), "Video") == 0) {
+ m_vVideos.push_back(Video(pNode));
+ }
+
+ pNode = it.nextNode();
+ }
+
+ } catch(Exception &exc) {
+ std::cerr << exc.displayText() << std::endl;
+ }
+}
+
+}
diff --git a/MediaContainer.h b/MediaContainer.h
new file mode 100644
index 0000000..2633086
--- /dev/null
+++ b/MediaContainer.h
@@ -0,0 +1,63 @@
+#ifndef ALLSECTIONS_H
+#define ALLSECTIONS_H
+
+#include <Poco/DOM/DOMParser.h>
+#include <Poco/DOM/Document.h>
+#include <Poco/DOM/NamedNodeMap.h>
+#include <Poco/DOM/NodeIterator.h>
+#include <Poco/DOM/NodeFilter.h>
+#include <Poco/DOM/AutoPtr.h>
+#include <Poco/SAX/InputSource.h>
+#include <Poco/Exception.h>
+#include <Poco/String.h>
+
+#include <vector>
+#include <iostream>
+
+#include "XmlObject.h"
+#include "Directory.h"
+#include "PVideo.h"
+
+using Poco::XML::DOMParser;
+using Poco::XML::InputSource;
+using Poco::XML::Document;
+using Poco::XML::NodeIterator;
+using Poco::XML::NodeFilter;
+using Poco::XML::Node;
+using Poco::XML::AutoPtr;
+using Poco::Exception;
+
+namespace plexclient
+{
+
+class MediaContainer: XmlObject
+{
+public:
+ MediaContainer(std::istream *response);
+
+ ~MediaContainer();
+
+protected:
+
+
+public:
+ std::vector<Directory> m_vDirectories;
+ std::vector<Video> m_vVideos;
+
+ bool m_bAllowSync;
+ std::string m_sArt;
+ std::string m_sThumb;
+ std::string m_sTitle;
+ std::string m_sTitle2;
+ std::string m_sViewGroup;
+ int m_iLibrarySectionID;
+ std::string m_sLibrarySectionTitle;
+ std::string m_sLibrarySectionUUID;
+ std::string m_sMediaTagPrefix;
+ int m_iSize;
+
+};
+
+}
+
+#endif // ALLSECTIONS_H
diff --git a/PVideo.cpp b/PVideo.cpp
new file mode 100644
index 0000000..b22bb66
--- /dev/null
+++ b/PVideo.cpp
@@ -0,0 +1,59 @@
+#include "PVideo.h"
+
+namespace plexclient
+{
+
+Video::Video(Poco::XML::Node* pNode)
+{
+
+ NodeIterator it(pNode, Poco::XML::NodeFilter::SHOW_ALL);
+ Poco::XML::Node* pChildNode = it.nextNode();
+
+ while(pChildNode) {
+ if(Poco::icompare(pChildNode->nodeName(), "Video") == 0) {
+
+ Poco::XML::AutoPtr<Poco::XML::NamedNodeMap> pAttribs = pNode->attributes();
+
+ m_iRatingKey = GetNodeValueAsInt(pAttribs->getNamedItem("ratingKey"));
+ m_sStudio = GetNodeValue(pAttribs->getNamedItem("studio"));
+ m_tType = GetNodeValueAsMediaType(pAttribs->getNamedItem("type"));
+ m_sTitle = GetNodeValue(pAttribs->getNamedItem("title"));
+ m_sOriginalTitle = GetNodeValue(pAttribs->getNamedItem("originalTitle"));
+ m_sContentRating = GetNodeValue(pAttribs->getNamedItem("contentRating"));
+ m_sSummary = GetNodeValue(pAttribs->getNamedItem("summary"));
+ m_lViewoffset = GetNodeValueAsLong(pAttribs->getNamedItem("viewOffset"));
+ m_tLastViewedAt = GetNodeValueAsTimeStamp(pAttribs->getNamedItem("lastViewedAt"));
+ m_iYear = GetNodeValueAsInt(pAttribs->getNamedItem("year"));
+ m_sThumb = GetNodeValue(pAttribs->getNamedItem("thumb"));
+ m_sArt = GetNodeValue(pAttribs->getNamedItem("art"));
+ m_iDuration = GetNodeValueAsLong(pAttribs->getNamedItem("duration"));
+ m_tAddedAt = GetNodeValueAsTimeStamp(pAttribs->getNamedItem("addedAt"));
+ m_tUpdatedAt = GetNodeValueAsTimeStamp(pAttribs->getNamedItem("updatedAt"));
+
+ pAttribs->release();
+
+ } else if(Poco::icompare(pChildNode->nodeName(), "Media") == 0) {
+ m_pMedia = new Media(pChildNode);
+ } else if(Poco::icompare(pChildNode->nodeName(), "Genre") == 0) {
+ m_vGenre.push_back(GetNodeValue(pChildNode));
+ } else if(Poco::icompare(pChildNode->nodeName(), "Writer") == 0) {
+ m_vWriter.push_back(GetNodeValue(pChildNode));
+ } else if(Poco::icompare(pChildNode->nodeName(), "Director") == 0) {
+ m_vDirector.push_back(GetNodeValue(pChildNode));
+ } else if(Poco::icompare(pChildNode->nodeName(), "Country") == 0) {
+ m_vCountry.push_back(GetNodeValue(pChildNode));
+ } else if(Poco::icompare(pChildNode->nodeName(), "Role") == 0) {
+ m_vRole.push_back(GetNodeValue(pChildNode));
+ } else if(Poco::icompare(pChildNode->nodeName(), "Collection") == 0) {
+ m_sCollection = GetNodeValue(pChildNode);
+ }
+ pChildNode = it.nextNode();
+ }
+}
+
+Video::~Video()
+{
+}
+
+
+}
diff --git a/PVideo.h b/PVideo.h
new file mode 100644
index 0000000..ae61da4
--- /dev/null
+++ b/PVideo.h
@@ -0,0 +1,67 @@
+#ifndef VIDEO_H
+#define VIDEO_H
+
+#include <Poco/DOM/DOMParser.h>
+#include <Poco/DOM/Document.h>
+#include <Poco/DOM/NamedNodeMap.h>
+#include <Poco/DOM/NodeIterator.h>
+#include <Poco/DOM/NodeFilter.h>
+#include <Poco/DOM/NodeList.h>
+#include <Poco/DOM/AutoPtr.h>
+#include <Poco/Exception.h>
+#include <Poco/Timestamp.h>
+#include <Poco/String.h>
+
+#include <vector>
+#include <iostream>
+
+#include "XmlObject.h"
+#include "Media.h"
+
+using Poco::XML::DOMParser;
+using Poco::XML::Document;
+using Poco::XML::NodeIterator;
+using Poco::XML::NodeFilter;
+using Poco::XML::Node;
+using Poco::XML::AutoPtr;
+using Poco::Exception;
+
+namespace plexclient
+{
+
+class Video: XmlObject
+{
+public:
+ Video(Poco::XML::Node* pNode);
+ ~Video();
+
+public:
+ int m_iRatingKey;
+ std::string m_sStudio;
+ MediaType m_tType;
+ std::string m_sTitle;
+ std::string m_sOriginalTitle;
+ std::string m_sContentRating;
+ std::string m_sSummary;
+ long m_lViewoffset;
+ Poco::Timestamp m_tLastViewedAt;
+ int m_iYear;
+ std::string m_sThumb;
+ std::string m_sArt;
+ long m_iDuration;
+ Poco::Timestamp m_tAddedAt;
+ Poco::Timestamp m_tUpdatedAt;
+
+ std::vector<std::string> m_vGenre;
+ std::vector<std::string> m_vWriter;
+ std::vector<std::string> m_vDirector;
+ std::vector<std::string> m_vCountry;
+ std::vector<std::string> m_vRole;
+ std::string m_sCollection;
+ Media *m_pMedia;
+
+};
+
+}
+
+#endif // VIDEO_H
diff --git a/PlexHTTPRequestHandler.cpp b/PlexHTTPRequestHandler.cpp
new file mode 100644
index 0000000..c0c01b1
--- /dev/null
+++ b/PlexHTTPRequestHandler.cpp
@@ -0,0 +1,182 @@
+#include "PlexHTTPRequestHandler.h"
+#include <vdr/remote.h>
+#include <unistd.h>
+
+namespace plexclient
+{
+
+void PlexHTTPRequestHandler::AddHeaders(Poco::Net::HTTPServerResponse& response, Poco::Net::HTTPServerRequest& request) {
+ if(request.getMethod() == Poco::Net::HTTPRequest::HTTP_OPTIONS) {
+ response.setContentType("text/plain");
+
+ response.add("X-Plex-Client-Identifier", Config::GetInstance().GetUUID());
+ response.add("Connection", "Close");
+ response.add("Access-Control-Max-Age", "1209600");
+ response.add("Access-Control-Allow-Origin", "*");
+ response.add("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT, HEAD");
+ response.add("Access-Control-Allow-Headers", "x-plex-version, x-plex-platform-version, "
+ "x-plex-username, x-plex-client-identifier, "
+ "x-plex-target-client-identifier, x-plex-device-name, "
+ "x-plex-platform, x-plex-product, accept, x-plex-device");
+ } else {
+ response.setContentType("application/x-www-form-urlencoded");
+
+ response.add("Access-Control-Allow-Origin", "*");
+ response.add("X-Plex-Version", VERSION);
+ response.add("X-Plex-Client-Identifier", Config::GetInstance().GetUUID());
+ response.add("X-Plex-Product", "player");
+ response.add("X-Plex-Product", "PlexVDR");
+ response.add("X-Plex-Device-Name", Config::GetInstance().GetHostname());
+ response.add("X-Plex-Platform", "VDR");
+ response.add("X-Plex-Model", "Linux");
+ response.add("X-Plex-Device", "PC");
+ response.add("X-Plex-Username", Config::GetInstance().GetUsername());
+ }
+ //header.MessageHeader(header);
+}
+
+std::map<std::string, std::string> PlexHTTPRequestHandler::ParseQuery(std::string query) {
+ std::map<std::string, std::string> querymap;
+ Poco::StringTokenizer queryTokens(query, "&");
+ for(auto& token : queryTokens) {
+ Poco::StringTokenizer subTokens(token, "=");
+ querymap[subTokens[0]] = subTokens[1];
+ }
+ return querymap;
+}
+
+std::string PlexHTTPRequestHandler::GetOKMsg() {
+ return "<?xml version=\"1.0\" encoding=\"utf-8\"?> <Response code=\"200\" status=\"OK\" />";
+}
+
+void PlexHTTPRequestHandler::handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response){
+ AddHeaders(response, request);
+ std::ostream& ostr = response.send();
+ ostr << GetOKMsg();
+}
+
+void SubscribeRequestHandler::handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response){
+ // parse query
+ Poco::URI uri(request.getURI());
+ auto query = ParseQuery(uri.getQuery()); // port=32400&commandID=0&protocol=http
+ std::string path = uri.getPath(); // /player/timeline/subscribe
+
+ std::string uuid = request.get("X-Plex-Client-Identifier");
+ int port = std::stoi(query["port"]);
+ int command = std::stoi(query["commandID"]);
+ SubscriptionManager::GetInstance().AddSubscriber(Subscriber(query["protocol"], request.getHost(), port, uuid, command));
+
+ AddHeaders(response, request);
+
+ std::ostream& ostr = response.send();
+ ostr << GetOKMsg();
+ response.setStatus(Poco::Net::HTTPResponse::HTTP_REASON_OK);
+}
+
+void ResourceRequestHandler::handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response){
+ AddHeaders(response, request);
+
+ std::ostream& ostr = response.send();
+ ostr << "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<MediaContainer>"
+ "<Player title=\"" << Config::GetInstance().GetHostname() << "\""
+ " protocol=\"plex\""
+ " protocolVersion=\"1\""
+ " protocolCapabilities=\"navigation,playback,timeline\""
+ " machineIdentifier=\"" << Config::GetInstance().GetUUID() << "\""
+ " product=\"PlexVDR\""
+ " platform=\"Linux\""
+ " platformVersion=\"" << VERSION << "\""
+ " deviceClass=\"HTPC\""
+ "/> </MediaContainer>";
+
+ response.setStatus(Poco::Net::HTTPResponse::HTTP_REASON_OK);
+
+
+ std::cout << "Resources Response sent..." << std::endl;
+}
+
+void PlayerRequestHandler::handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response){
+ Poco::URI uri(request.getURI());
+ //Poco::StringTokenizer pathTokens(uri, "/");
+ auto query = ParseQuery(uri.getQuery());
+
+ if(query.find("wait") != query.end() && std::stoi(query["wait"]) == 1) {
+ usleep(900 * 1000);
+ }
+
+ if(request.getMethod() == Poco::Net::HTTPRequest::HTTP_OPTIONS) {
+ AddHeaders(response, request);
+ //response.setStatus(Poco::Net::HTTPResponse::HTTP_REASON_OK);
+ std::ostream& ostr = response.send(); // Stream must not be empty!
+ ostr << " ";
+ response.setStatus(Poco::Net::HTTPResponse::HTTP_REASON_OK);
+ std::cout << "OPTION Reply send" << std::endl;
+ return;
+ }
+
+ if(request.getURI().find("/poll")!= std::string::npos) {
+ response.add("X-Plex-Client-Identifier",Config::GetInstance().GetUUID());
+ response.add("Access-Control-Expose-Headers","X-Plex-Client-Identifier");
+ response.add("Access-Control-Allow-Origin","*");
+ response.setContentType("text/xml");
+
+ std::ostream& ostr = response.send();
+ ostr << SubscriptionManager::GetInstance().GetMsg(query["commandID"]);
+ response.setStatus(Poco::Net::HTTPResponse::HTTP_REASON_OK);
+
+ }
+ else if(request.getURI().find("/playback/playMedia")!= std::string::npos) {
+ std::cout << "playMedia_1" << std::endl;
+ AddHeaders(response, request);
+
+ std::cout << "playMedia_2" << std::endl;
+ std::string protocol = query["protocol"];
+ std::string address = query["address"];
+ std::string port = query["port"];
+ std::string key = query["key"];
+ std::cout << "playMedia_3" << std::endl;
+
+ std::string fullUrl = protocol + "://" + address + ":" + port + key; // Metainfo
+ std::cout << "FullUrl: " << fullUrl << std::endl;
+
+ MediaContainer *pCont = Plexservice::GetMediaContainer(fullUrl);
+ std::string filePath = pCont->m_vVideos[0].m_pMedia->m_sPartKey;
+ Poco::URI fileuri(fullUrl);
+ fileuri.setPath(filePath);
+
+ // MUSS im Maintread des Plugins/VDR gestartet werden
+ ActionManager::GetInstance().AddAction(fileuri.toString());
+
+ std::ostream& ostr = response.send();
+ ostr << GetOKMsg();
+ response.setStatus(Poco::Net::HTTPResponse::HTTP_REASON_OK);
+ SubscriptionManager::GetInstance().Notify();
+ }
+ else if(request.getURI().find("/navigation")!= std::string::npos) {
+ if(request.getURI().find("/moveUp")!= std::string::npos) {
+ cRemote::Put(eKeys::kUp);
+ }
+ else if(request.getURI().find("/moveDown")!= std::string::npos) {
+ cRemote::Put(eKeys::kDown);
+ }
+ else if(request.getURI().find("/moveLeft")!= std::string::npos) {
+ cRemote::Put(eKeys::kLeft);
+ }
+ else if(request.getURI().find("/moveRight")!= std::string::npos) {
+ cRemote::Put(eKeys::kRight);
+ }
+ else if(request.getURI().find("/select")!= std::string::npos) {
+ cRemote::Put(eKeys::kOk);
+ }
+ else if(request.getURI().find("/home")!= std::string::npos) {
+ cRemote::Put(eKeys::kMenu);
+ }
+ else if(request.getURI().find("/back")!= std::string::npos) {
+ cRemote::Put(eKeys::kBack);
+ }
+ }
+
+}
+
+}
diff --git a/PlexHTTPRequestHandler.h b/PlexHTTPRequestHandler.h
new file mode 100644
index 0000000..52837fe
--- /dev/null
+++ b/PlexHTTPRequestHandler.h
@@ -0,0 +1,63 @@
+#ifndef PLEXHTTPREQUSTHANDLER_H
+#define PLEXHTTPREQUSTHANDLER_H
+
+#include <Poco/Net/HTTPRequestHandler.h> // Base class: Poco::Net::HTTPRequestHandler
+#include <Poco/Net/HTTPServerRequest.h>
+#include <Poco/Net/HTTPServerResponse.h>
+#include <Poco/Net/MessageHeader.h>
+#include <Poco/URI.h>
+#include <Poco/StringTokenizer.h>
+
+#include <iostream>
+#include <sstream>
+
+#include "plex.h"
+#include "Config.h"
+#include "SubscriptionManager.h"
+#include "Plexservice.h"
+#include "MediaContainer.h"
+#include "Directory.h"
+#include "PVideo.h"
+#include "Media.h"
+
+#include "plexgdm.h"
+
+
+
+namespace plexclient
+{
+
+class PlexHTTPRequestHandler : public Poco::Net::HTTPRequestHandler
+{
+public:
+ virtual void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response);
+
+protected:
+ std::string GetOKMsg();
+ void AddHeaders(Poco::Net::HTTPServerResponse& response, Poco::Net::HTTPServerRequest& request);
+ std::map<std::string, std::string> ParseQuery(std::string query);
+};
+
+class SubscribeRequestHandler : public PlexHTTPRequestHandler
+{
+public:
+ virtual void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response);
+};
+
+class ResourceRequestHandler : public PlexHTTPRequestHandler
+{
+public:
+ virtual void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response);
+};
+
+class PlayerRequestHandler : public PlexHTTPRequestHandler
+{
+public:
+ virtual void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response);
+};
+
+
+}
+
+
+#endif // PLEXHTTPREQUSTHANDLER_H
diff --git a/PlexHelper.cpp b/PlexHelper.cpp
new file mode 100644
index 0000000..b1421c5
--- /dev/null
+++ b/PlexHelper.cpp
@@ -0,0 +1,16 @@
+#include "PlexHelper.h"
+
+namespace plexclient
+{
+
+PlexHelper::PlexHelper()
+{
+}
+
+PlexHelper::~PlexHelper()
+{
+}
+
+
+}
+
diff --git a/PlexHelper.h b/PlexHelper.h
new file mode 100644
index 0000000..e42fc1d
--- /dev/null
+++ b/PlexHelper.h
@@ -0,0 +1,17 @@
+#ifndef PLEXHELPER_H
+#define PLEXHELPER_H
+
+namespace plexclient
+{
+
+class PlexHelper
+{
+public:
+ PlexHelper();
+ ~PlexHelper();
+
+};
+
+}
+
+#endif // PLEXHELPER_H
diff --git a/PlexReqHandlerFactory.cpp b/PlexReqHandlerFactory.cpp
new file mode 100644
index 0000000..1b68bcb
--- /dev/null
+++ b/PlexReqHandlerFactory.cpp
@@ -0,0 +1,33 @@
+#include "PlexReqHandlerFactory.h"
+
+namespace plexclient
+{
+
+PlexReqHandlerFactory::PlexReqHandlerFactory()
+{
+}
+
+PlexReqHandlerFactory::~PlexReqHandlerFactory()
+{
+}
+
+Poco::Net::HTTPRequestHandler* PlexReqHandlerFactory::createRequestHandler(const Poco::Net::HTTPServerRequest& request)
+{
+ if(request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET) {
+ std::cout << "GET Request: " << request.getURI() << " from: " << request.clientAddress().toString() << std::endl;
+ }
+ else if(request.getMethod() == Poco::Net::HTTPRequest::HTTP_OPTIONS) {
+ std::cout << "OPTIONS Request: " << request.getURI() << " from: " << request.clientAddress().toString() << std::endl;
+ }
+ else if(request.getMethod() == Poco::Net::HTTPRequest::HTTP_HEAD) {
+ std::cout << "HEAD Request: " << request.getURI() << " from: " << request.clientAddress().toString() << std::endl;
+ }
+
+ if(request.getURI().find("/subscribe")!= std::string::npos) return new SubscribeRequestHandler();
+ else if(request.getURI().find("/resources")!= std::string::npos) return new ResourceRequestHandler();
+ else if(request.getURI().find("/player")!= std::string::npos) return new PlayerRequestHandler();
+
+ return new PlexHTTPRequestHandler();
+}
+
+}
diff --git a/PlexReqHandlerFactory.h b/PlexReqHandlerFactory.h
new file mode 100644
index 0000000..82ceb8a
--- /dev/null
+++ b/PlexReqHandlerFactory.h
@@ -0,0 +1,28 @@
+#ifndef PLEXREQHANDLERFACTORY_H
+#define PLEXREQHANDLERFACTORY_H
+
+#include <Poco/Net/HTTPRequestHandlerFactory.h> // Base class: Poco::Net::HTTPRequestHandlerFactory
+#include <Poco/Net/HTTPRequestHandler.h>
+#include <Poco/Net/HTTPServerRequest.h>
+
+#include "PlexHTTPRequestHandler.h"
+
+#include "Config.h"
+
+namespace plexclient
+{
+
+class PlexReqHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory
+{
+public:
+ PlexReqHandlerFactory();
+ ~PlexReqHandlerFactory();
+
+public:
+ virtual Poco::Net::HTTPRequestHandler* createRequestHandler(const Poco::Net::HTTPServerRequest& request);
+};
+
+}
+
+
+#endif // PLEXREQHANDLERFACTORY_H
diff --git a/PlexServer.cpp b/PlexServer.cpp
new file mode 100644
index 0000000..94ba0e8
--- /dev/null
+++ b/PlexServer.cpp
@@ -0,0 +1,41 @@
+#include "PlexServer.h"
+
+namespace plexclient
+{
+
+PlexServer::PlexServer(std::string data, std::string ip)
+{
+ m_sIpAddress = ip;
+ std::istringstream f(data);
+ std::string s;
+ while(std::getline(f, s)) {
+ int pos = s.find(':');
+ if(pos > 0) {
+ std::string name = Poco::trim(s.substr(0, pos));
+ std::string val = Poco::trim(s.substr(pos+1));
+ if(name == "Content-Type") {
+ m_sContentType = val;
+ } else if (name == "Resource-Identifier") {
+ m_sUuid = val;
+ } else if (name == "Name") {
+ m_sServerName = val;
+ } else if (name == "Port") {
+ m_nPort = atoi(val.c_str());
+ } else if (name == "Updated-At") {
+ m_nUpdated = atol(val.c_str());
+ } else if (name == "Version") {
+ m_sVersion = val;
+ }
+ }
+ }
+}
+
+std::string PlexServer::GetUri() {
+ return std::string("http://") + m_sIpAddress + ":" + std::to_string(m_nPort);
+}
+
+PlexServer::~PlexServer()
+{
+}
+
+}
diff --git a/PlexServer.h b/PlexServer.h
new file mode 100644
index 0000000..61438d3
--- /dev/null
+++ b/PlexServer.h
@@ -0,0 +1,81 @@
+#ifndef PLEXSERVER_H
+#define PLEXSERVER_H
+
+#include <stdlib.h>
+#include <string>
+#include <sstream>
+#include <iostream>
+#include <vector>
+
+#include <Poco/String.h>
+#include <Poco/Net/HTTPClientSession.h>
+#include <Poco/Net/HTTPRequest.h>
+#include <Poco/Net/MessageHeader.h>
+#include <Poco/URI.h>
+
+namespace plexclient
+{
+
+class PlexServer
+{
+public:
+ PlexServer(std::string data, std::string ip);
+ ~PlexServer();
+
+ int GetMaster() const {
+ return m_nMaster;
+ }
+
+ int GetOwned() const {
+ return m_nOwned;
+ }
+ int GetPort() const {
+ return m_nPort;
+ }
+ const std::string& GetContentType() const {
+ return m_sContentType;
+ }
+ const std::string& GetDiscovery() const {
+ return m_sDiscovery;
+ }
+ const std::string& GetRole() const {
+ return m_sRole;
+ }
+ const std::string& GetServerName() const {
+ return m_sServerName;
+ }
+ long GetUpdated() const {
+ return m_nUpdated;
+ }
+ const std::string& GetUuid() const {
+ return m_sUuid;
+ }
+ const std::string& GetVersion() const {
+ return m_sVersion;
+ }
+ const std::string& GetIpAdress() const {
+ return m_sIpAddress;
+ }
+
+ std::string GetUri();
+
+ void DiscoverSettings();
+
+private:
+ std::string m_sDiscovery;
+
+ int m_nOwned = 0;
+ int m_nMaster = 0;
+ std::string m_sRole;
+ std::string m_sContentType;
+ std::string m_sUuid;
+ std::string m_sServerName;
+ std::string m_sIpAddress;
+ int m_nPort;
+ long m_nUpdated;
+ std::string m_sVersion;
+
+};
+
+}
+#endif // PLEXSERVER_H
diff --git a/Plexservice.cpp b/Plexservice.cpp
new file mode 100644
index 0000000..cf6a252
--- /dev/null
+++ b/Plexservice.cpp
@@ -0,0 +1,205 @@
+#include "Plexservice.h"
+#include <memory>
+
+namespace plexclient
+{
+
+Plexservice::Plexservice(PlexServer *server)
+{
+ pServer = server;
+}
+
+Plexservice::~Plexservice()
+{
+}
+
+Poco::Net::HTTPClientSession* Plexservice::GetHttpSession(bool createNew)
+{
+ if(pServer == 0) {
+ return NULL;
+ }
+ if (m_pPlexSession == 0 || createNew) {
+ if (createNew) {
+ //delete m_pPlexSession;
+ }
+ m_pPlexSession = new Poco::Net::HTTPClientSession(pServer->GetIpAdress(), pServer->GetPort());
+ m_pPlexSession->setKeepAlive(true);
+ }
+ return m_pPlexSession;
+}
+
+std::string Plexservice::GetMyPlexToken()
+{
+ //todo: cache token in file or db
+ if(m_sToken.empty()) {
+ std::ostringstream ss;
+ Poco::Base64Encoder b64(ss);
+
+ b64 << Config::GetInstance().GetUsername() << ":" << Config::GetInstance().GetPassword();
+
+ b64.close();
+ m_sToken = ss.str();
+ b64.~Base64Encoder();
+
+ Poco::Net::Context::Ptr context = new Poco::Net::Context(
+ Poco::Net::Context::CLIENT_USE, "", "", "", Poco::Net::Context::VERIFY_NONE, // VERIFY_NONE...?!
+ 9, false, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
+
+ try {
+ Poco::Net::HTTPSClientSession plexSession("plex.tv", 443, context);
+ Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, "/users/sign_in.xml", Poco::Net::HTTPMessage::HTTP_1_1);
+
+ request.add("X-Plex-Platform", "VDR");
+ request.add("X-Plex-Platform-Version", "2.0.4");
+ request.add("X-Plex-Provides", "player");
+ request.add("X-Plex-Product", "plex-vdr");
+ request.add("X-Plex-Version", "0.0.1a");
+ request.add("X-Plex-Device", "Linux");
+ request.add("X-Plex-Client-Identifier", "plex-vdr");
+ request.add("Authorization", Poco::format("Basic %s", m_sToken));
+
+ plexSession.sendRequest(request);
+
+ Poco::Net::HTTPResponse response;
+ std::istream &rs = plexSession.receiveResponse(response);
+ // parse the XML Response
+ user *u = new user(&rs);
+ m_sToken = u->authenticationToken;
+ plexSession.detachSocket();
+ } catch (Poco::Exception &exc) {
+ std::cerr << exc.displayText() << std::endl;
+ }
+
+ }
+ return m_sToken;
+}
+
+void Plexservice::Authenticate()
+{
+ if(m_sToken.empty()) {
+ GetMyPlexToken();
+ }
+ try {
+ GetHttpSession(true);
+ Poco::Net::HTTPRequest *pRequest = CreateRequest("/?X-Plex-Token=" + m_sToken);
+
+ m_pPlexSession->sendRequest(*pRequest);
+ Poco::Net::HTTPResponse response;
+ /*std::istream &rs = */
+ m_pPlexSession->receiveResponse(response);
+
+ // TODO: process response
+ //Poco::StreamCopier::copyStream(rs, std::cout);
+ delete pRequest;
+ } catch (Poco::Exception &exc) {
+ std::cerr << exc.displayText() << std::endl;
+ }
+}
+/*
+void Plexservice::DiscoverAllServers()
+{
+ // try automatic discovery via multicast
+ plexgdm gdmClient = plexgdm();
+ gdmClient.discover();
+ pServer = gdmClient.GetPServer();
+ if (pServer == 0) {
+ perror("No Plexserver found");
+ }
+}
+ */
+
+PlexServer* Plexservice::GetServer() {
+ return pServer;
+}
+
+MediaContainer* Plexservice::GetAllSections()
+{
+ return GetSection("");
+}
+
+MediaContainer* Plexservice::GetSection(std::string section)
+{
+ if(m_sToken.empty()) {
+ GetMyPlexToken();
+ }
+
+ GetHttpSession(true);
+
+ Poco::Net::HTTPRequest *pRequest;
+ if(section[0]=='/') { // Full URI?
+ pRequest = CreateRequest(Poco::format("%s?X-Plex-Token=%s", section, m_sToken));
+ } else if(false == section.empty()) {
+ pRequest = CreateRequest(Poco::format("/library/sections/%s?X-Plex-Token=%s", section, m_sToken));
+ } else {
+ pRequest = CreateRequest("/library/sections/?X-Plex-Token=" + m_sToken);
+ }
+
+ m_pPlexSession->sendRequest(*pRequest);
+ Poco::Net::HTTPResponse response;
+ std::istream &rs = m_pPlexSession->receiveResponse(response);
+
+ std::cout << "URI: " << m_pPlexSession->getHost() << "[" << pRequest->getURI() << "]" << std::endl;
+
+ MediaContainer* pAllsections = new MediaContainer(&rs);
+ //Poco::StreamCopier::copyStream(rs, std::cout);
+ delete pRequest;
+ return pAllsections;
+}
+
+Poco::Net::HTTPRequest* Plexservice::CreateRequest(std::string path)
+{
+
+ Poco::Net::HTTPRequest *pRequest = new Poco::Net::HTTPRequest(Poco::Net::HTTPRequest::HTTP_GET,
+ path, Poco::Net::HTTPMessage::HTTP_1_1);
+
+ pRequest->add("User-Agent", USERAGENT);
+
+ pRequest->add("X-Plex-Client-Capabilities", "protocols=shoutcast,http-video;videoDecoders=h264{profile:high&resolution:1080&level:51};audioDecoders=mp3,aac");
+ pRequest->add("X-Plex-Client-Identifier", Config::GetInstance().GetUUID());
+ pRequest->add("X-Plex-Device", "PC");
+ pRequest->add("X-Plex-Device-Name", Config::GetInstance().GetHostname());
+ pRequest->add("X-Plex-Language", Config::GetInstance().GetLanguage());
+ pRequest->add("X-Plex-Model", "Linux");
+ pRequest->add("X-Plex-Platform", "VDR");
+ pRequest->add("X-Plex-Product", "plex-vdr");
+ pRequest->add("X-Plex-Provides", "player");
+ pRequest->add("X-Plex-Version", "0.0.1a");
+
+ return pRequest;
+}
+
+MediaContainer* Plexservice::GetMediaContainer(std::string fullUrl) {
+
+ Poco::URI fileuri(fullUrl);
+
+ std::unique_ptr<Poco::Net::HTTPRequest> pRequest(new Poco::Net::HTTPRequest(Poco::Net::HTTPRequest::HTTP_GET,
+ fileuri.getPath(), Poco::Net::HTTPMessage::HTTP_1_1));
+
+ pRequest->add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17");
+
+ pRequest->add("X-Plex-Client-Capabilities", "protocols=shoutcast,http-video;videoDecoders=h264{profile:high&resolution:1080&level:51};audioDecoders=mp3,aac");
+ pRequest->add("X-Plex-Client-Identifier", Config::GetInstance().GetUUID());
+ pRequest->add("X-Plex-Device", "PC");
+ pRequest->add("X-Plex-Device-Name", Config::GetInstance().GetHostname());
+ pRequest->add("X-Plex-Language", Config::GetInstance().GetLanguage());
+ pRequest->add("X-Plex-Model", "Linux");
+ pRequest->add("X-Plex-Platform", "VDR");
+ pRequest->add("X-Plex-Product", "plex-vdr");
+ pRequest->add("X-Plex-Provides", "player");
+ pRequest->add("X-Plex-Version", "0.0.1a");
+
+ auto session = new Poco::Net::HTTPClientSession(fileuri.getHost(), fileuri.getPort());
+
+ session->sendRequest(*pRequest);
+ Poco::Net::HTTPResponse response;
+ std::istream &rs = session->receiveResponse(response);
+
+ std::cout << "URI: " << session->getHost() << "[" << pRequest->getURI() << "]" << std::endl;
+
+ MediaContainer* pAllsections = new MediaContainer(&rs);
+ //Poco::StreamCopier::copyStream(rs, std::cout);
+ return pAllsections;
+}
+
+}
+
diff --git a/Plexservice.h b/Plexservice.h
new file mode 100644
index 0000000..a935ef2
--- /dev/null
+++ b/Plexservice.h
@@ -0,0 +1,65 @@
+#ifndef PLEXSERVICE_H
+#define PLEXSERVICE_H
+
+//#include "plexgdm.h"
+#include "PlexServer.h"
+
+#include <sstream>
+#include <iostream>
+#include <string>
+#include <vector>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <Poco/Base64Encoder.h>
+#include <Poco/Net/HTTPClientSession.h>
+#include <Poco/Net/HTTPSClientSession.h>
+#include <Poco/Net/HTTPRequest.h>
+#include <Poco/Net/HTTPResponse.h>
+#include <Poco/Net/MessageHeader.h>
+#include <Poco/Net/Context.h>
+#include <Poco/Exception.h>
+#include <Poco/Format.h>
+#include <Poco/URI.h>
+
+#include <Poco/StreamCopier.h>
+
+#include "Config.h"
+#include "user.h"
+#include "MediaContainer.h"
+
+namespace plexclient
+{
+
+class Plexservice
+{
+public:
+ Plexservice(PlexServer *server);
+ ~Plexservice();
+
+ void DisplaySections();
+ MediaContainer* GetAllSections();
+ MediaContainer* GetSection(std::string section);
+ void GetAuthDetails();
+ std::string GetMyPlexToken();
+ void Authenticate();
+ //void DiscoverFirstServer();
+ PlexServer* GetServer();
+
+ static MediaContainer* GetMediaContainer(std::string fullUrl);
+
+private:
+ const std::string USERAGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17";
+
+ Poco::Net::HTTPClientSession *m_pPlexSession = 0;
+ PlexServer *pServer = 0;
+ std::string m_sToken;
+
+ Poco::Net::HTTPClientSession* GetHttpSession(bool createNew = false);
+ Poco::Net::HTTPRequest* CreateRequest(std::string path);
+
+};
+
+}
+
+#endif // PLEXSERVICE_H
diff --git a/SubscriptionManager.cpp b/SubscriptionManager.cpp
new file mode 100644
index 0000000..47f917d
--- /dev/null
+++ b/SubscriptionManager.cpp
@@ -0,0 +1,118 @@
+#include "SubscriptionManager.h"
+#include "Config.h"
+
+#include <memory>
+
+#include <Poco/Net/HTTPClientSession.h>
+#include <Poco/Net/HTTPSClientSession.h>
+#include <Poco/Net/HTTPRequest.h>
+#include <Poco/Net/HTTPResponse.h>
+#include <Poco/Net/MessageHeader.h>
+
+namespace plexclient
+{
+
+SubscriptionManager::SubscriptionManager()
+{
+
+}
+
+void SubscriptionManager::Notify() {
+ mutex.lock();
+
+ //TODO: Implement
+ for(auto subs : m_mSubcribers) {
+ subs.second.SendUpdate(GetMsg(std::to_string(subs.second.m_iCommandId)), false);
+ }
+
+ mutex.unlock();
+}
+
+void SubscriptionManager::AddSubscriber(Subscriber subs) {
+ mutex.lock();
+ m_mSubcribers[subs.GetUuid()] = subs;
+ std::cout << "AddSubscriber: " << subs.to_string() << std::endl;
+ mutex.unlock();
+}
+
+void SubscriptionManager::RemoveSubscriber(std::string uuid) {
+ mutex.lock();
+ if(m_mSubcribers.find(uuid) != m_mSubcribers.end()) {
+ m_mSubcribers.erase(uuid);
+ }
+ mutex.unlock();
+}
+
+std::string SubscriptionManager::GetMsg(std::string commandId) {
+ PlayerGetCurrentPosition();
+ int time = PlayerCurrent;
+
+ bool paused = PlayerPaused;
+
+ std::stringstream msg;
+ msg << "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<MediaContainer commandID=\"" << commandId << "\""
+ " location=\"navigation\">";
+
+ msg << "<Timeline location=\"navigation\" state=\"";
+ //if(paused) msg << "paused";
+ //else if (paused) msg << "paused";
+ msg << "stopped";
+ msg << "\" time=\"" << time << "\" type=\"video\" />";
+ msg << "<Timeline location=\"navigation\" state=\"stopped\" time=\"0\" type=\"photo\" />";
+ msg << "<Timeline location=\"navigation\" state=\"stopped\" time=\"0\" type=\"music\" />";
+
+ msg << "</MediaContainer>";
+ return msg.str();
+}
+
+Subscriber::Subscriber(std::string protocol, std::string host, int port, std::string uuid, int commandId) {
+ m_sProtocol = protocol;
+ m_sHost = host;
+ m_iPort = port;
+ m_sUuid = uuid;
+ m_iCommandId = commandId;
+}
+
+void Subscriber::SendUpdate(std::string msg, bool isNav) {
+ ++m_iAge;
+
+ Poco::Net::HTTPRequest *pRequest = new Poco::Net::HTTPRequest(Poco::Net::HTTPRequest::HTTP_POST,
+ "/:/timeline", Poco::Net::HTTPMessage::HTTP_1_1);
+
+ pRequest->add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17");
+
+ pRequest->add("X-Plex-Client-Capabilities", "protocols=shoutcast,http-video;videoDecoders=h264{profile:high&resolution:1080&level:51};audioDecoders=mp3,aac");
+ pRequest->add("X-Plex-Client-Identifier", Config::GetInstance().GetUUID());
+ pRequest->add("X-Plex-Device", "PC");
+ pRequest->add("X-Plex-Device-Name", Config::GetInstance().GetHostname());
+ pRequest->add("X-Plex-Language", Config::GetInstance().GetLanguage());
+ pRequest->add("X-Plex-Model", "Linux");
+ pRequest->add("X-Plex-Platform", "VDR");
+ pRequest->add("X-Plex-Product", "plex-vdr");
+ pRequest->add("X-Plex-Provides", "player");
+ pRequest->add("X-Plex-Version", "0.0.1a");
+
+ auto session = new Poco::Net::HTTPClientSession(m_sHost, m_iPort);
+ std::ostream& oustr = session->sendRequest(*pRequest);
+ oustr << msg;
+}
+
+ActionManager::ActionManager() {}
+
+void ActionManager::AddAction(std::string file) {
+ m_sAction = file;
+ m_isAction = true;
+}
+
+std::string ActionManager::GetAction() {
+ m_isAction = false;
+ return m_sAction;
+}
+
+bool ActionManager::IsAction() {
+ return m_isAction;
+}
+
+}
+
diff --git a/SubscriptionManager.h b/SubscriptionManager.h
new file mode 100644
index 0000000..a411256
--- /dev/null
+++ b/SubscriptionManager.h
@@ -0,0 +1,79 @@
+#ifndef SUBSCRIPTIONMANAGER_H
+#define SUBSCRIPTIONMANAGER_H
+
+#include <string>
+#include <iostream>
+#include <map>
+#include <mutex>
+#include <sstream>
+
+#include "player.h"
+
+namespace plexclient
+{
+
+class Subscriber
+{
+ public:
+ int m_iCommandId;
+ Subscriber() {};
+ Subscriber(std::string protocol, std::string host, int port, std::string uuid, int commandId);
+
+ std::string GetUuid() {
+ return m_sUuid;
+ }
+
+ void SendUpdate(std::string msg, bool isNav);
+
+ virtual std::string to_string() {
+ return "Subscriber-> Host: " + m_sHost + "; Port:" + std::to_string(m_iPort) + "; Uuid:" + m_sUuid + "; CmdID:" + std::to_string(m_iCommandId);
+ }
+
+private:
+ std::string m_sProtocol;
+ std::string m_sHost;
+ int m_iPort;
+ std::string m_sUuid;
+
+ int m_iAge;
+};
+
+
+class SubscriptionManager
+{
+public:
+ static SubscriptionManager& GetInstance() {
+ static SubscriptionManager instance;
+ return instance;
+ }
+ void AddSubscriber(Subscriber subs);
+ void RemoveSubscriber(std::string uuid);
+ std::string GetMsg(std::string commandId);
+ void Notify();
+
+ private:
+ SubscriptionManager();
+ std::mutex mutex;
+ std::map<std::string, Subscriber> m_mSubcribers;
+};
+
+class ActionManager
+{
+ public:
+ static ActionManager& GetInstance() {
+ static ActionManager instance;
+ return instance;
+ }
+ void AddAction(std::string file);
+ std::string GetAction();
+ bool IsAction();
+
+ private:
+ ActionManager();
+ std::string m_sAction = "";
+ bool m_isAction = false;
+};
+
+}
+
+#endif // SUBSCRIPTIONMANAGER_H
diff --git a/XmlObject.cpp b/XmlObject.cpp
new file mode 100644
index 0000000..41a9d39
--- /dev/null
+++ b/XmlObject.cpp
@@ -0,0 +1,87 @@
+#include "XmlObject.h"
+
+namespace plexclient
+{
+
+XmlObject::XmlObject()
+{
+}
+
+XmlObject::~XmlObject()
+{
+}
+
+std::string XmlObject::GetNodeValue(Poco::XML::Node* pNode)
+{
+ std::string value;
+ if(pNode != 0) {
+ value = pNode->getNodeValue();
+ }
+ return value;
+}
+
+int XmlObject::GetNodeValueAsInt(Poco::XML::Node* pNode)
+{
+ int value = 0;
+ if(pNode != 0) {
+ try {
+ value = std::stoi(pNode->getNodeValue());
+ } catch(Poco::Exception) {}
+ }
+ return value;
+}
+
+long XmlObject::GetNodeValueAsLong(Poco::XML::Node* pNode)
+{
+ long value = 0;
+ if(pNode != 0) {
+ try {
+ value = std::stol(pNode->getNodeValue());
+ } catch(Poco::Exception) {}
+ }
+ return value;
+}
+
+bool XmlObject::GetNodeValueAsBool(Poco::XML::Node* pNode)
+{
+ bool value = false;
+ if(pNode != 0) {
+ value = pNode->getNodeValue() == "1";
+ }
+ return value;
+}
+
+Poco::Timestamp XmlObject::GetNodeValueAsTimeStamp(Poco::XML::Node* pNode)
+{
+ Poco::Timestamp value;
+ if(pNode != 0) {
+ try {
+ long lValue = std::stol(pNode->nodeValue());
+ value = Poco::Timestamp(lValue);
+ } catch (Poco::Exception) {}
+ }
+ return value;
+}
+
+MediaType XmlObject::GetNodeValueAsMediaType(Poco::XML::Node* pNode)
+{
+ MediaType type = MediaType::UNDEF;
+
+ if(pNode != 0) {
+ std::string sType = pNode->nodeValue();
+ if (Poco::icompare(sType, "photo") == 0) {
+ type = MediaType::PHOTO;
+ } else if (Poco::icompare(sType, "movie") == 0) {
+ type = MediaType::MOVIE;
+ } else if (Poco::icompare(sType, "music") == 0) {
+ type = MediaType::MUSIC;
+ } else if (Poco::icompare(sType, "show") == 0) {
+ type = MediaType::SHOW;
+ } else if (Poco::icompare(sType, "season") == 0) {
+ type = MediaType::SHOW;
+ }
+ }
+ return type;
+}
+
+}
diff --git a/XmlObject.h b/XmlObject.h
new file mode 100644
index 0000000..d227af7
--- /dev/null
+++ b/XmlObject.h
@@ -0,0 +1,34 @@
+#ifndef XMLOBJECT_H
+#define XMLOBJECT_H
+
+#include <Poco/DOM/Document.h>
+#include <Poco/Timestamp.h>
+#include <Poco/String.h>
+#include <Poco/Exception.h>
+
+namespace plexclient
+{
+
+enum MediaType {UNDEF = 0, PHOTO, MOVIE, MUSIC, SHOW, SEASON};
+
+class XmlObject
+{
+public:
+ XmlObject();
+ ~XmlObject();
+
+protected:
+ std::string GetNodeValue(Poco::XML::Node* pNode);
+ int GetNodeValueAsInt(Poco::XML::Node* pNode);
+ long GetNodeValueAsLong(Poco::XML::Node* pNode);
+ bool GetNodeValueAsBool(Poco::XML::Node* pNode);
+ Poco::Timestamp GetNodeValueAsTimeStamp(Poco::XML::Node* pNode);
+ MediaType GetNodeValueAsMediaType(Poco::XML::Node* pNode);
+
+private:
+
+};
+
+}
+
+#endif // XMLOBJECT_H
diff --git a/cPlexOsdItem.cpp b/cPlexOsdItem.cpp
new file mode 100644
index 0000000..6ee51c1
--- /dev/null
+++ b/cPlexOsdItem.cpp
@@ -0,0 +1,34 @@
+#include "cPlexOsdItem.h"
+
+cPlexOsdItem::cPlexOsdItem(const char* title) :cOsdItem(title) {
+ item = 0;
+ dir = 0;
+ m_bVideo = false;
+ m_bDir = false;
+}
+
+cPlexOsdItem::cPlexOsdItem(const char* title, plexclient::Video* obj) :cOsdItem(title) {
+ item = obj;
+ dir = 0;
+ m_bVideo = true;
+ m_bDir = false;
+}
+
+cPlexOsdItem::cPlexOsdItem(const char* title, plexclient::Directory* obj) :cOsdItem(title) {
+ dir = obj;
+ item = 0;
+ m_bDir = true;
+ m_bVideo = false;
+}
+
+plexclient::Video* cPlexOsdItem::GetAttachedVideo() {
+ return item;
+}
+
+plexclient::Directory* cPlexOsdItem::GetAttachedDirectory() {
+ return dir;
+}
+
+cPlexOsdItem::~cPlexOsdItem()
+{
+} \ No newline at end of file
diff --git a/cPlexOsdItem.h b/cPlexOsdItem.h
new file mode 100644
index 0000000..5ca144c
--- /dev/null
+++ b/cPlexOsdItem.h
@@ -0,0 +1,39 @@
+#ifndef CPLEXOSDITEM_H
+#define CPLEXOSDITEM_H
+
+#include <vdr/osd.h> // Base class: cOsdItem
+#include <vdr/interface.h>
+#include <vdr/plugin.h>
+
+#include "PVideo.h"
+#include "Directory.h"
+
+class cPlexOsdItem : public cOsdItem
+{
+private:
+ plexclient::Video *item;
+ plexclient::Directory *dir;
+ bool m_bVideo;
+ bool m_bDir;
+
+public:
+ cPlexOsdItem(const char* title);
+ cPlexOsdItem(const char* title, plexclient::Video* obj);
+ cPlexOsdItem(const char* title, plexclient::Directory* obj);
+ ~cPlexOsdItem();
+ plexclient::Video* GetAttachedVideo();
+ plexclient::Directory* GetAttachedDirectory();
+
+ bool IsVideo() const {
+ return m_bVideo;
+ }
+ bool IsDir() const {
+ return m_bDir;
+ }
+
+ //virtual eOSState ProcessKey(eKeys Key);
+ //virtual void Set(void);
+ //virtual void SetMenuItem(cSkinDisplayMenu* DisplayMenu, int Index, bool Current, bool Selectable);
+};
+
+#endif // CPLEXOSDITEM_H
diff --git a/misc.h b/misc.h
new file mode 100644
index 0000000..bb8fc6d
--- /dev/null
+++ b/misc.h
@@ -0,0 +1,157 @@
+///
+/// @file misc.h @brief Misc function header file
+///
+/// Copyright (c) 2009 - 2012 by Lutz Sammer. All Rights Reserved.
+///
+/// Contributor(s):
+/// Copied from vdr-softhddevice.
+///
+/// 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: 3ced30cd6fa89227270c2f769654c65c62d3ef85 $
+//////////////////////////////////////////////////////////////////////////////
+
+/// @addtogroup misc
+/// @{
+
+#include <syslog.h>
+#include <stdarg.h>
+#include <time.h> // clock_gettime
+
+//////////////////////////////////////////////////////////////////////////////
+// Defines
+//////////////////////////////////////////////////////////////////////////////
+
+//////////////////////////////////////////////////////////////////////////////
+// Declares
+//////////////////////////////////////////////////////////////////////////////
+
+//////////////////////////////////////////////////////////////////////////////
+// Variables
+//////////////////////////////////////////////////////////////////////////////
+
+extern int SysLogLevel; ///< how much information wanted
+
+//////////////////////////////////////////////////////////////////////////////
+// Prototypes
+//////////////////////////////////////////////////////////////////////////////
+
+static inline void Syslog(const int, const char *format, ...)
+ __attribute__ ((format(printf, 2, 3)));
+
+//////////////////////////////////////////////////////////////////////////////
+// Inlines
+//////////////////////////////////////////////////////////////////////////////
+
+#ifdef DEBUG
+#define DebugLevel 4 /// private debug level
+#else
+#define DebugLevel 0 /// private debug level
+#endif
+
+/**
+** Syslog output function.
+**
+** - 0 fatal errors and errors
+** - 1 warnings
+** - 2 info
+** - 3 important debug and fixme's
+*/
+static inline void Syslog(const int level, const char *format, ...)
+{
+ if (SysLogLevel > level || DebugLevel > level) {
+ va_list ap;
+
+ va_start(ap, format);
+ vsyslog(LOG_ERR, format, ap);
+ va_end(ap);
+ }
+}
+
+/**
+** Show error.
+*/
+#define Error(fmt...) Syslog(0, fmt)
+
+/**
+** Show fatal error.
+*/
+#define Fatal(fmt...) do { Error(fmt); abort(); } while (0)
+
+/**
+** Show warning.
+*/
+#define Warning(fmt...) Syslog(1, fmt)
+
+/**
+** Show info.
+*/
+#define Info(fmt...) Syslog(2, fmt)
+
+/**
+** Show debug.
+*/
+#ifdef DEBUG
+#define Debug(level, fmt...) Syslog(level, fmt)
+#else
+#define Debug(level, fmt...) /* disabled */
+#endif
+
+#ifndef AV_NOPTS_VALUE
+#define AV_NOPTS_VALUE INT64_C(0x8000000000000000)
+#endif
+
+/**
+** Nice time-stamp string.
+**
+** @param ts dvb time stamp
+*/
+static inline const char *Timestamp2String(int64_t ts)
+{
+ static char buf[4][16];
+ static int idx;
+
+ if (ts == (int64_t) AV_NOPTS_VALUE) {
+ return "--:--:--.---";
+ }
+ idx = (idx + 1) % 3;
+ snprintf(buf[idx], sizeof(buf[idx]), "%2d:%02d:%02d.%03d",
+ (int)(ts / (90 * 3600000)), (int)((ts / (90 * 60000)) % 60),
+ (int)((ts / (90 * 1000)) % 60), (int)((ts / 90) % 1000));
+
+ return buf[idx];
+}
+
+/**
+** Get ticks in ms.
+**
+** @returns ticks in ms,
+*/
+static inline uint32_t GetMsTicks(void)
+{
+#ifdef CLOCK_MONOTONIC
+ struct timespec tspec;
+
+ clock_gettime(CLOCK_MONOTONIC, &tspec);
+ return (tspec.tv_sec * 1000) + (tspec.tv_nsec / (1000 * 1000));
+#else
+ struct timeval tval;
+
+ if (gettimeofday(&tval, NULL) < 0) {
+ return 0;
+ }
+ return (tval.tv_sec * 1000) + (tval.tv_usec / 1000);
+#endif
+}
+
+/// @}
diff --git a/play_service.h b/play_service.h
new file mode 100644
index 0000000..0084603
--- /dev/null
+++ b/play_service.h
@@ -0,0 +1,31 @@
+///
+/// @file play_service.h @brief Play service header file.
+///
+/// Copyright (c) 2012 by durchflieger. 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: d903996c670a05e72a189a0a5de30724eb2ea903 $
+//////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#define PLEX_OSD_3DMODE_SERVICE "Plex-Osd3DModeService-v1.0"
+#define PLEX_PLAY_FILE_SERVICE "Plex-PlayFileService-v1.0"
+
+typedef struct
+{
+ int Mode;
+} Play_Osd3DModeService_v1_0_t;
diff --git a/player.c b/player.c
new file mode 100644
index 0000000..56cb827
--- /dev/null
+++ b/player.c
@@ -0,0 +1,984 @@
+///
+/// @file player.c @brief A play plugin for VDR.
+///
+/// Copyright (c) 2012, 2013 by Johns. All Rights Reserved.
+///
+/// Contributor(s): Dennis Bendlin
+///
+/// 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: 23f35b1a9358694e2b022aed1eff081887bc3f16 $
+//////////////////////////////////////////////////////////////////////////////
+
+#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
+
+ /// DVD-Drive for mplayer
+static const char *ConfigMplayerDevice = "/dev/dvd";
+static uint32_t ConfigColorKey = 0x00020507; ///< color key
+
+//////////////////////////////////////////////////////////////////////////////
+// Osd
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** Open OSD.
+*/
+void OsdOpen(void)
+{
+ Debug(3, "plex: %s\n", __FUNCTION__);
+
+ VideoWindowShow();
+}
+
+/**
+** Close OSD.
+*/
+void OsdClose(void)
+{
+ Debug(3, "plex: %s\n", __FUNCTION__);
+
+ VideoWindowHide();
+ VideoWindowClear();
+}
+
+/**
+** Clear OSD.
+*/
+void OsdClear(void)
+{
+ Debug(3, "plex: %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, "plex: %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 int PlayerVolume = -1; ///< volume 0 - 100
+
+char PlayerDvdNav; ///< dvdnav active
+char PlayerPaused; ///< player paused
+char PlayerSpeed; ///< player playback speed
+int PlayerCurrent; ///< current postion in seconds
+int PlayerTotal; ///< total length in seconds
+char PlayerTitle[256]; ///< title from meta data
+char PlayerFilename[256]; ///< filename
+
+//////////////////////////////////////////////////////////////////////////////
+// Slave
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** Parse player output.
+**
+** @param data line pointer (\0 terminated)
+** @param size line length
+*/
+static void PlayerParseLine(const char *data, int size)
+{
+ Debug(4, "play/parse: |%.*s|\n", size, data);
+ (void)size;
+
+ // data is \0 terminated
+ if (!strncasecmp(data, "DVDNAV_TITLE_IS_MENU", 20)) {
+ PlayerDvdNav = 1;
+ } else if (!strncasecmp(data, "DVDNAV_TITLE_IS_MOVIE", 21)) {
+ PlayerDvdNav = 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);
+ }
+ } else if (!strncasecmp(data, "ANS_META_TITLE=", 14)) {
+ if (sscanf(data, "ANS_META_TITLE='%[^\t\n]", PlayerTitle) == 1) {
+ PlayerTitle[strlen(PlayerTitle) - 1] = 0;
+ Debug(3, "PlayerTitle= %s\n", PlayerTitle);
+ }
+ } else if (!strncasecmp(data, "ANS_FILENAME=", 12)) {
+ if (sscanf(data, "ANS_FILENAME='%[^\t\n]", PlayerFilename) == 1) {
+ PlayerFilename[strlen(PlayerFilename) - 1] = 0;
+ Debug(3, "PlayerFilename= %s\n", PlayerFilename);
+ }
+ } else if (!strncasecmp(data, "ANS_LENGTH=", 10)) {
+ if (sscanf(data, "ANS_LENGTH=%d", &PlayerTotal) == 1) {
+ Debug(3, "PlayerTotal=%d\n", PlayerTotal);
+ }
+ } else if (!strncasecmp(data, "ANS_TIME_POSITION=", 17)) {
+ if (sscanf(data, "ANS_TIME_POSITION=%d", &PlayerCurrent) == 1) {
+ Debug(3, "PlayerCurrent=%d\n", PlayerCurrent);
+ }
+ }
+}
+
+/**
+** 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=4:cplayer=2:identify=4";
+#endif
+ if (ConfigOsdOverlay) {
+ args[4] = "-noontop";
+ } else {
+ args[4] = "-ontop";
+ }
+ args[5] = "-noborder";
+ if (ConfigDisableRemote) {
+ args[6] = "-lirc";
+ } else {
+ 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";
+ args[13] = "-sid"; // subtitle selection
+ args[14] = "0";
+ args[15] = "-slang";
+ args[16] = "de,en"; // FIXME: use VDR config
+ args[17] = "-alang";
+ args[18] = "de,en"; // FIXME: use VDR config
+ argn = 19;
+ if (ConfigMplayerDevice) { // dvd-device
+ args[argn++] = "-dvd-device";
+ args[argn++] = ConfigMplayerDevice;
+ }
+ 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 - 3) { // argument overflow
+ Error(_("play: too many arguments for mplayer\n"));
+ // argn = 1;
+ break;
+ }
+ }
+ }
+ args[argn++] = "--";
+ 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
+ setpgid(pid, 0);
+
+ if (ConfigUseSlave) {
+ close(PlayerPipeIn[0]);
+ close(PlayerPipeOut[1]);
+ }
+
+ Debug(3, "play: child pid=%d\n", pid);
+}
+
+/**
+** Close pipes.
+*/
+static void PlayerClosePipes(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");
+
+ // Need: thread for video poll: while (PlayerIsRunning())
+ for (;;) {
+ if (ConfigUseSlave && PlayerIsRunning()) {
+ 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.
+**
+** @param formst printf format string
+*/
+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.
+**
+** @param speed mplayer speed
+*/
+void PlayerSendSetSpeed(int speed)
+{
+ if (ConfigUseSlave) {
+ SendCommand("pausing_keep speed_set %d\n", speed);
+ }
+}
+
+/**
+** Send player seek.
+**
+** @param seconds seek in seconds relative to current position
+*/
+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);
+ }
+}
+
+/**
+** Send switch audio track.
+*/
+void PlayerSendSwitchAudio(void)
+{
+ if (ConfigUseSlave) {
+ SendCommand("pausing_keep switch_audio\n");
+ }
+}
+
+/**
+** Send select subtitle.
+*/
+void PlayerSendSubSelect(void)
+{
+ if (ConfigUseSlave) {
+ SendCommand("pausing_keep sub_select\n");
+ }
+}
+
+/**
+** Send up for dvdnav.
+*/
+void PlayerSendDvdNavUp(void)
+{
+ if (ConfigUseSlave) {
+ SendCommand("pausing_keep dvdnav up\n");
+ }
+}
+
+/**
+** Send down for dvdnav.
+*/
+void PlayerSendDvdNavDown(void)
+{
+ if (ConfigUseSlave) {
+ SendCommand("pausing_keep dvdnav down\n");
+ }
+}
+
+/**
+** Send left for dvdnav.
+*/
+void PlayerSendDvdNavLeft(void)
+{
+ if (ConfigUseSlave) {
+ SendCommand("pausing_keep dvdnav left\n");
+ }
+}
+
+/**
+** Send right for dvdnav.
+*/
+void PlayerSendDvdNavRight(void)
+{
+ if (ConfigUseSlave) {
+ SendCommand("pausing_keep dvdnav right\n");
+ }
+}
+
+/**
+** Send select for dvdnav.
+*/
+void PlayerSendDvdNavSelect(void)
+{
+ if (ConfigUseSlave) {
+ SendCommand("pausing_keep dvdnav select\n");
+ }
+}
+
+/**
+** Send prev for dvdnav.
+*/
+void PlayerSendDvdNavPrev(void)
+{
+ if (ConfigUseSlave) {
+ SendCommand("pausing_keep dvdnav prev\n");
+ }
+}
+
+/**
+** Send menu for dvdnav.
+*/
+void PlayerSendDvdNavMenu(void)
+{
+ if (ConfigUseSlave) {
+ SendCommand("pausing_keep dvdnav menu\n");
+ }
+}
+
+/**
+** Get length in seconds.
+*/
+void PlayerGetLength(void)
+{
+ if (ConfigUseSlave) {
+ SendCommand("get_time_length\n");
+ }
+}
+
+/**
+** Get current position in seconds.
+*/
+void PlayerGetCurrentPosition(void)
+{
+ if (ConfigUseSlave) {
+ SendCommand("get_time_pos\n");
+ }
+}
+
+/**
+** Get title from meta data.
+*/
+void PlayerGetMetaTitle(void)
+{
+ if (ConfigUseSlave) {
+ SendCommand("get_meta_title\n");
+ }
+}
+
+/**
+** Get filename.
+*/
+void PlayerGetFilename(void)
+{
+ if (ConfigUseSlave) {
+ SendCommand("get_file_name\n");
+ }
+}
+
+/**
+** 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;
+
+ PlayerDvdNav = 0;
+
+ if (ConfigOsdOverlay) { // overlay wanted
+ VideoSetColorKey(ConfigColorKey);
+ VideoInit(ConfigX11Display);
+ EnableDummyDevice();
+ }
+ PlayerForkAndExec(filename);
+
+ if (ConfigOsdOverlay || ConfigUseSlave) {
+ PlayerThreadInit();
+ }
+}
+
+/**
+** Stop external player.
+*/
+void PlayerStop(void)
+{
+ PlayerThreadExit();
+
+ //
+ // stop mplayer, if it is still running.
+ //
+ if (PlayerIsRunning()) {
+ int waittime;
+ int timeout;
+
+ waittime = 0;
+ timeout = 500; // 0.5s
+
+ kill(PlayerPid, SIGTERM);
+ // wait for player finishing, with timeout
+ while (PlayerIsRunning() && waittime++ < timeout) {
+ usleep(1 * 1000);
+ }
+ if (PlayerIsRunning()) { // still running
+ waittime = 0;
+ timeout = 500; // 0.5s
+
+ kill(PlayerPid, SIGKILL);
+ // wait for player finishing, with timeout
+ while (PlayerIsRunning() && waittime++ < timeout) {
+ usleep(1 * 1000);
+ }
+ if (PlayerIsRunning()) {
+ Error(_("play: can't stop player\n"));
+ }
+ }
+ }
+ PlayerPid = 0;
+ PlayerClosePipes();
+
+ 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;
+}
+
+/**
+** Set player volume.
+**
+** @param volume new volume (0..255)
+*/
+void PlayerSetVolume(int volume)
+{
+ Debug(3, "player: set volume=%d\n", volume);
+
+ if (PlayerVolume != volume) {
+ PlayerVolume = volume;
+ if (PlayerPid) {
+ PlayerSendVolume();
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Device/Plugin C part
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** Return command line help string.
+*/
+const char *CommandLineHelp(void)
+{
+ return " -% device\tmplayer dvd device\n"
+ " -/\t/dir\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, "-%:/:a:b:d:fg:k:m:M:osv:")) {
+ case '%': // dvd-device
+ ConfigMplayerDevice = optarg;
+ continue;
+ 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..d4acedd
--- /dev/null
+++ b/player.h
@@ -0,0 +1,160 @@
+///
+/// @file player.h @brief A play plugin header file.
+///
+/// Copyright (c) 2012, 2013 by Johns. All Rights Reserved.
+///
+/// Contributor(s): Dennis Bendlin
+///
+/// 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: 0c86be2575387d12e692ffa0cc8ce515bbc6f5e5 $
+//////////////////////////////////////////////////////////////////////////////
+
+#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;
+ ///< Disable remote during external play
+ extern char ConfigDisableRemote;
+ extern const char *X11DisplayName; ///< x11 display name
+ extern char PlayerDvdNav; ///< dvdnav active
+ extern char PlayerPaused; ///< player paused
+ extern char PlayerSpeed; ///< player playback speed
+ extern int PlayerCurrent; ///< current postion in seconds
+ extern int PlayerTotal; ///< total length in seconds
+ extern char PlayerTitle[256]; ///< title from meta data
+ extern char PlayerFilename[256]; ///< filename
+
+ /// 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);
+
+ /// Set player volume
+ extern void PlayerSetVolume(int);
+
+ /// Player send quit command
+ extern void PlayerSendQuit(void);
+ /// Player send toggle pause command
+ extern void PlayerSendPause(void);
+ /// Player send set play speed
+ extern void PlayerSendSetSpeed(int);
+ /// Player send seek
+ extern void PlayerSendSeek(int);
+ /// Player send switch audio track
+ extern void PlayerSendSwitchAudio(void);
+ /// Player send select subtitle
+ extern void PlayerSendSubSelect(void);
+ /// Player send dvd-nav up
+ extern void PlayerSendDvdNavUp(void);
+ /// Player send dvd-nav down
+ extern void PlayerSendDvdNavDown(void);
+ /// Player send dvd-nav left
+ extern void PlayerSendDvdNavLeft(void);
+ /// Player send dvd-nav right
+ extern void PlayerSendDvdNavRight(void);
+ /// Player send dvd-nav menu select
+ extern void PlayerSendDvdNavSelect(void);
+ /// Player send dvd-nav menu prev
+ extern void PlayerSendDvdNavPrev(void);
+ /// Player send dvd-nav prev
+ extern void PlayerSendDvdNavMenu(void);
+ /// Get length in seconds.
+ extern void PlayerGetLength(void);
+ /// Get current position in seconds.
+ extern void PlayerGetCurrentPosition(void);
+ /// Get title from meta data.
+ extern void PlayerGetMetaTitle(void);
+ /// Get filename.
+ extern void PlayerGetFilename(void);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/plex.cpp b/plex.cpp
new file mode 100644
index 0000000..dcd1142
--- /dev/null
+++ b/plex.cpp
@@ -0,0 +1,1335 @@
+#include "ControlServer.h"
+#include "SubscriptionManager.h"
+#include "plex.h"
+
+extern "C"
+{
+#include "video.h"
+#include "player.h"
+}
+
+static const char *const PLUGINNAME = "plex";
+//////////////////////////////////////////////////////////////////////////////
+
+
+ /// vdr-plugin description.
+static const char *const DESCRIPTION = trNOOP("VDR Plex Plugin");
+
+ /// vdr-plugin text of main menu entry
+static const char *MAINMENUENTRY = trNOOP("Plex");
+
+//////////////////////////////////////////////////////////////////////////////
+
+static char ConfigHideMainMenuEntry; ///< hide main menu entry
+char ConfigDisableRemote; ///< disable remote during external play
+
+static volatile int DoMakePrimary; ///< switch primary device to this
+
+//////////////////////////////////////////////////////////////////////////////
+// C Callbacks
+//////////////////////////////////////////////////////////////////////////////
+
+
+/**
+** 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 = static_cast<cMyRemote*>(remote);
+ } else {
+ dsyslog("[plex]%s: remote '%s' not found\n", __FUNCTION__, keymap);
+ csoft = new cMyRemote(keymap);
+ }
+
+ //dsyslog("[plex]%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
+ }
+}
+
+/**
+** Disable remotes.
+*/
+void RemoteDisable(void)
+{
+ dsyslog("[plex]: remote disabled\n");
+ cRemote::SetEnabled(false);
+}
+
+/**
+** Enable remotes.
+*/
+void RemoteEnable(void)
+{
+ dsyslog("[plex]: remote enabled\n");
+ cRemote::SetEnabled(false);
+ cRemote::SetEnabled(true);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// 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
+
+//////////////////////////////////////////////////////////////////////////////
+// cPlayer
+//////////////////////////////////////////////////////////////////////////////
+
+
+/**
+** Player constructor.
+**
+** @param filename path and name of file to play
+*/
+cMyPlayer::cMyPlayer(const char *filename)
+:cPlayer(pmExtern_THIS_SHOULD_BE_AVOIDED)
+{
+ dsyslog("[plex]%s: '%s'\n", __FUNCTION__, filename);
+
+ PlayerSetVolume(cDevice::CurrentVolume());
+ dsyslog("[plex]: initial volume %d\n", cDevice::CurrentVolume());
+
+ FileName = strdup(filename);
+ if (ConfigDisableRemote) {
+ RemoteDisable();
+ }
+}
+
+/**
+** Player destructor.
+*/
+cMyPlayer::~cMyPlayer()
+{
+ dsyslog("[plex]%s: end\n", __FUNCTION__);
+
+ PlayerStop();
+ free(FileName);
+ if (ConfigDisableRemote) {
+ RemoteEnable();
+ }
+ // FIXME: wait until primary device is switched?
+ dsyslog("[plex]: device %d->%d\n",
+ cDevice::PrimaryDevice()->DeviceNumber(), DoMakePrimary);
+}
+
+/**
+** Player attached or detached.
+**
+** @param on flag turn player on or off
+*/
+void cMyPlayer::Activate(bool on)
+{
+ dsyslog("[plex]%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;
+ if (PlayerSpeed == 1) {
+ speed = -1;
+ } else {
+ speed = PlayerSpeed;
+ }
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// cStatus
+//////////////////////////////////////////////////////////////////////////////
+
+cMyStatus *Status; ///< status monitor for volume
+
+/**
+** Status constructor.
+*/
+cMyStatus::cMyStatus(void)
+{
+ Volume = cDevice::CurrentVolume();
+
+ dsyslog("[plex]: status volume %d\n", Volume);
+}
+
+/**
+** Called if volume is set.
+*/
+void cMyStatus::SetVolume(int volume, bool absolute)
+{
+ dsyslog("[plex]: volume %d %s\n", volume, absolute ? "abs" : "rel");
+
+ if (absolute) {
+ Volume = volume;
+ } else {
+ Volume += volume;
+ }
+
+ PlayerSetVolume(Volume);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// cControl
+//////////////////////////////////////////////////////////////////////////////
+
+
+
+/**
+** Show replay mode.
+*/
+void cMyControl::ShowReplayMode(void)
+{
+ dsyslog("[plex]%s: %d - %d\n", __FUNCTION__, Setup.ShowReplayMode,
+ cOsd::IsOpen());
+
+ // 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)
+{
+ if (Display || (!cOsd::IsOpen())) {
+ bool play;
+ bool forward;
+ int speed;
+
+ if (GetReplayMode(play, forward, speed)) {
+ if (!Display) {
+ Display = Skins.Current()->DisplayReplay(false);
+ }
+
+ if (!infoVisible) {
+ infoVisible = true;
+ timeoutShow = time(0) + Setup.ChannelInfoTime;
+ PlayerGetLength();
+ PlayerGetMetaTitle();
+ PlayerGetFilename();
+ }
+
+ PlayerGetCurrentPosition();
+ if (strcmp(PlayerTitle, "") != 0) {
+ Display->SetTitle(PlayerTitle);
+ } else {
+ Display->SetTitle(PlayerFilename);
+ }
+ Display->SetProgress(PlayerCurrent, PlayerTotal);
+ Display->SetMode(play, forward, speed);
+ Display->SetCurrent(IndexToHMSF(PlayerCurrent, false, 1));
+ Display->SetTotal(IndexToHMSF(PlayerTotal, false, 1));
+ }
+ SetNeedsFastResponse(true);
+ Skins.Flush();
+ }
+}
+
+/**
+** Show control.
+*/
+void cMyControl::Show(void)
+{
+ dsyslog("[plex]%s:\n", __FUNCTION__);
+ if (Setup.ShowReplayMode)
+ ShowReplayMode();
+ else
+ 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
+ infoVisible = false;
+
+ //LastSkipKey = kNone;
+ //LastSkipSeconds = REPLAYCONTROLSKIPSECONDS;
+ //LastSkipTimeout.Set(0);
+ cStatus::MsgReplaying(this, filename, filename, true);
+
+ cDevice::PrimaryDevice()->ClrAvailableTracks(true);
+}
+
+/**
+** Control destructor.
+*/
+cMyControl::~cMyControl()
+{
+ dsyslog("[plex]%s\n", __FUNCTION__);
+
+ delete Player;
+
+ //delete Display;
+ delete Status;
+
+ Hide();
+ cStatus::MsgReplaying(this, NULL, NULL, false);
+ //Stop();
+}
+
+/**
+** Hide control.
+*/
+void cMyControl::Hide(void)
+{
+ dsyslog("[plex]%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;
+
+ if (key != kNone) {
+ dsyslog("[plex]%s: key=%d\n", __FUNCTION__, key);
+ }
+
+ if (!PlayerIsRunning()) { // check if player is still alive
+ dsyslog("[plex]: player died\n");
+ Hide();
+ //FIXME: Stop();
+ cControl::Shutdown();
+ return osEnd;
+ }
+
+ if (infoVisible) { // if RecordingInfo visible then update
+ if (timeoutShow && time(0) > timeoutShow) {
+ Hide();
+ timeoutShow = 0;
+ infoVisible = false;
+ } else
+ ShowProgress();
+ }
+ //state=cOsdMenu::ProcessKey(key);
+ state = osContinue;
+ switch ((int)key) { // cast to shutup g++ warnings
+ case kUp:
+ if (PlayerDvdNav) {
+ PlayerSendDvdNavUp();
+ break;
+ }
+ case kPlay:
+ Hide();
+ if (PlayerSpeed != 1) {
+ PlayerSendSetSpeed(PlayerSpeed = 1);
+ }
+ if (PlayerPaused) {
+ PlayerSendPause();
+ PlayerPaused ^= 1;
+ }
+ Show();
+ break;
+
+ case kDown:
+ if (PlayerDvdNav) {
+ PlayerSendDvdNavDown();
+ break;
+ }
+ case kPause:
+ PlayerSendPause();
+ PlayerPaused ^= 1;
+ Show();
+ break;
+
+ case kFastRew | k_Release:
+ case kLeft | k_Release:
+ if (Setup.MultiSpeedMode) {
+ break;
+ }
+ // FIXME:
+ break;
+ case kLeft:
+ if (PlayerDvdNav) {
+ PlayerSendDvdNavLeft();
+ break;
+ }
+ case kFastRew:
+ if (PlayerSpeed > 1) {
+ PlayerSendSetSpeed(PlayerSpeed /= 2);
+ } else {
+ PlayerSendSeek(-10);
+ }
+ Show();
+ break;
+ case kRight:
+ if (PlayerDvdNav) {
+ PlayerSendDvdNavRight();
+ break;
+ }
+ case kFastFwd:
+ if (PlayerSpeed < 32) {
+ PlayerSendSetSpeed(PlayerSpeed *= 2);
+ }
+ Show();
+ 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:
+ dsyslog("[plex]: player stopped\n");
+ Hide();
+ // FIXME: Stop();
+ cControl::Shutdown();
+ return osEnd;
+
+ case kOk:
+ if (PlayerDvdNav) {
+ PlayerSendDvdNavSelect();
+ // FIXME: PlayerDvdNav = 0;
+ break;
+ }
+ if (infoVisible) {
+ Hide();
+ infoVisible = false;
+ } else
+ Show();
+ break;
+
+ case kBack:
+ if (PlayerDvdNav > 1) {
+ PlayerSendDvdNavPrev();
+ break;
+ }
+ PlayerSendQuit();
+ // FIXME: need to select old directory and index
+ // FIXME: this shows a half drawn OSD
+ cRemote::CallPlugin(PLUGINNAME);
+ return osBack;
+
+ case kMenu: // VDR: eats the keys
+ case k5:
+ if (PlayerDvdNav) {
+ PlayerSendDvdNavMenu();
+ break;
+ }
+ break;
+
+ case kAudio: // VDR: eats the keys
+ case k7:
+ // FIXME: audio menu
+ PlayerSendSwitchAudio();
+ break;
+ case kSubtitles: // VDR: eats the keys
+ case k9:
+ // FIXME: subtitle menu
+ PlayerSendSubSelect();
+ break;
+
+ default:
+ break;
+ }
+
+ return state;
+}
+
+/**
+** Play a file.
+**
+** @param filename path and file name
+*/
+static void PlayFile(const char *filename)
+{
+ dsyslog("[plex]: 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
+static int DirStackSize; ///< size of directory stack
+static int DirStackUsed; ///< entries used of directory stack
+static char **DirStack; ///< current path directory stack
+
+*/
+
+
+cPlexBrowser::cPlexBrowser(const char *title, plexclient::Plexservice* pServ) :cOsdMenu(title) {
+
+ dsyslog("[plex]%s:\n", __FUNCTION__);
+ pService = pServ;
+ pCont = pService->GetAllSections();
+ CreateMenu();
+}
+
+void cPlexBrowser::CreateMenu() {
+ // Clear Menu
+ Clear();
+ // Directory or Video?
+ if(pCont->m_vDirectories.size() > 0) {
+ v_Dir = &pCont->m_vDirectories;
+
+ for(auto& pDir : pCont->m_vDirectories) {
+ if(pDir.m_eType != plexclient::MediaType::MUSIC && pDir.m_eType != plexclient::MediaType::PHOTO) {
+ Add(new cPlexOsdItem( pDir.m_sTitle.c_str(), &pDir) );
+ }
+ }
+ }
+
+
+ if (pCont->m_vVideos.size() > 0) {
+ for(auto& v_Vid : pCont->m_vVideos) {
+ Add(new cPlexOsdItem( v_Vid.m_sTitle.c_str(), &v_Vid) );
+ }
+ }
+
+ if(Count() < 1) {
+ Add(new cPlexOsdItem("Empty"));
+ }
+
+ Display();
+}
+
+
+eOSState cPlexBrowser::ProcessKey(eKeys key) {
+ eOSState state;
+
+ // call standard function
+ state = cOsdMenu::ProcessKey(key);
+ if (state || key != kNone) {
+ dsyslog("[plex]%s: state=%d key=%d\n", __FUNCTION__, state, key);
+ }
+
+ switch (state) {
+ case osUnknown:
+ switch (key) {
+ case kOk:
+ return ProcessSelected();
+ case kBack:
+ return LevelUp();
+ default:
+ break;
+ }
+ break;
+ case osBack:
+ state = LevelUp();
+ if (state == osEnd) { // top level reached
+ return osPlugin;
+ }
+ default:
+ break;
+ }
+ return state;
+}
+
+eOSState cPlexBrowser::LevelUp() {
+ if(m_vStack.size() <= 1){
+ m_vStack.clear();
+ ShowBrowser = 0;
+ return osEnd;
+ }
+
+ m_vStack.pop_back();
+
+ std::string uri;
+ for(unsigned int i = 0; i < m_vStack.size(); i++) {
+ if(m_vStack[i][0]=='/') {
+ uri = m_vStack[i];
+ continue;
+ }
+ uri += m_vStack[i];
+ if(i+1 < m_vStack.size()) {
+ uri += "/";
+ }
+ }
+ std::cout << "m_sSection: " << uri << std::endl;
+
+ pCont = pService->GetSection(uri);
+
+ CreateMenu();
+ return osContinue;
+}
+
+eOSState cPlexBrowser::ProcessSelected() {
+ int current;
+ cPlexOsdItem *item;
+ std::string fullUri;
+ //char *filename;
+ //char *tmp;
+
+ current = Current(); // get current menu item index
+ item = static_cast<cPlexOsdItem*>(Get(current));
+
+
+ if(item->IsVideo()) {
+ plexclient::Video* pVid = item->GetAttachedVideo();
+ fullUri = pService->GetServer()->GetUri() + pVid->m_pMedia->m_sPartKey;
+ PlayFile(fullUri.c_str());
+ return osEnd;
+ }
+
+
+ if(item->IsDir()) {
+ plexclient::Directory* pDir = item->GetAttachedDirectory();
+
+ m_vStack.push_back(pDir->m_sKey);
+ std::string uri;
+ for(unsigned int i = 0; i < m_vStack.size(); i++) {
+ if(m_vStack[i][0]=='/') {
+ uri = m_vStack[i];
+ continue;
+ }
+ uri += m_vStack[i];
+ if(i+1 < m_vStack.size()) {
+ uri += "/";
+ }
+ }
+ std::cout << "m_sSection: " << uri << std::endl;
+
+ pCont = pService->GetSection(uri);
+
+ CreateMenu();
+ return osContinue;
+ }
+
+ //return osEnd;
+ return osContinue;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// cOsdMenu
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** Play menu constructor.
+*/
+cPlayMenu::cPlayMenu(const char *title, plexclient::plexgdm* gdm, int c0, int c1, int c2, int c3, int c4)
+:cOsdMenu(title, c0, c1, c2, c3, c4)
+{
+ SetHasHotkeys();
+
+ plexclient::PlexServer *server = gdm->GetPServer();
+
+ if(server) {
+ std::string serverName = server->GetServerName();
+ Add(new cOsdItem(hk(serverName.c_str()), osUser1));
+ }
+ else {
+ Add(new cOsdItem("No Plex Media Server found."));
+ }
+}
+
+/**
+** Play menu destructor.
+*/
+cPlayMenu::~cPlayMenu()
+{
+}
+
+/**
+** Handle play plugin menu key event.
+**
+** @param key key event
+*/
+eOSState cPlayMenu::ProcessKey(eKeys key)
+{
+ eOSState state;
+
+ if (key != kNone) {
+ dsyslog("[plex]%s: key=%d\n", __FUNCTION__, key);
+ }
+ // call standard function
+ state = cOsdMenu::ProcessKey(key);
+
+ switch (state) {
+ case osUser1:
+ ShowBrowser = 1;
+ return osPlugin; // restart with OSD browser
+ default:
+ break;
+ }
+ return state;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// cOsd
+//////////////////////////////////////////////////////////////////////////////
+
+
+
+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("[plex]%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("[plex]%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("[plex]%s:\n", __FUNCTION__);
+ SetActive(false);
+ // done by SetActive: OsdClose();
+}
+
+/**
+** Actually commits all data to the OSD hardware.
+*/
+void cMyOsd::Flush(void)
+{
+ cPixmapMemory *pm;
+
+ dsyslog("[plex]%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("[plex]%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("[plex]: 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("[plex]%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("[plex]%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
+//////////////////////////////////////////////////////////////////////////////
+
+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("[plex]%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("[plex]%s:\n", __FUNCTION__);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// cDevice
+//////////////////////////////////////////////////////////////////////////////
+
+/**
+** Device constructor.
+*/
+cMyDevice::cMyDevice(void)
+{
+ dsyslog("[plex]%s\n", __FUNCTION__);
+}
+
+/**
+** Device destructor. (never called!)
+*/
+cMyDevice::~cMyDevice(void)
+{
+ dsyslog("[plex]%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("[plex]%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("[plex]: GetOsdSize invalid pointer(s)\n"));
+ return;
+ }
+ ::GetOsdSize(&width, &height, &pixel_aspect);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// cPlugin
+//////////////////////////////////////////////////////////////////////////////
+
+static cMyDevice *MyDevice; ///< dummy device needed for osd
+
+/**
+** 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("[plex]%s:\n", __FUNCTION__);
+}
+
+/**
+** Clean up after yourself!
+*/
+cMyPlugin::~cMyPlugin(void)
+{
+ dsyslog("[plex]%s:\n", __FUNCTION__);
+ pPlexgdm->stopRegistration();
+ plexclient::ControlServer::GetInstance().Stop();
+ delete(pPlexgdm);
+ delete(pService);
+}
+
+/**
+** 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("[plex]%s:\n", __FUNCTION__);
+
+ // FIXME: can delay until needed?
+ //Status = new cMyStatus; // start monitoring
+ // FIXME: destructs memory
+
+ // First Startup? Save UUID
+ SetupStore("UUID", Config::GetInstance().GetUUID().c_str());
+
+ pPlexgdm = new plexclient::plexgdm();
+ pPlexgdm->discover();
+ plexclient::PlexServer *pServer = pPlexgdm->GetPServer();
+ if (pServer != 0) {
+ pPlexgdm->clientDetails(Config::GetInstance().GetUUID(), DESCRIPTION, "3200", "VDR", VERSION);
+ pPlexgdm->Start();
+
+ pService = new plexclient::Plexservice(pServer);
+ pService->GetMyPlexToken();
+ pService->Authenticate();
+
+ plexclient::ControlServer::GetInstance().Start();
+
+ } else {
+ perror("No Plexserver found");
+ }
+
+ MyDevice = new cMyDevice;
+
+ return true;
+}
+
+/**
+** Create main menu entry.
+*/
+const char *cMyPlugin::MainMenuEntry(void)
+{
+ return ConfigHideMainMenuEntry ? NULL : tr(MAINMENUENTRY);
+}
+
+/**
+** Perform the action when selected from the main VDR menu.
+*/
+cOsdObject *cMyPlugin::MainMenuAction(void)
+{
+ //dsyslog("[plex]%s:\n", __FUNCTION__);
+
+ if (ShowBrowser) {
+ return new cPlexBrowser("Newest", this->pService);
+ }
+ return new cPlayMenu("Plex", pPlexgdm);
+}
+
+/**
+** 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, PLEX_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("[plex]%s:\n", __FUNCTION__);
+
+ if (DoMakePrimary) {
+ dsyslog("[plex]: switching primary device to %d\n", DoMakePrimary);
+ cDevice::SetPrimaryDevice(DoMakePrimary);
+ DoMakePrimary = 0;
+ }
+ // Start Tasks, e.g. Play Video
+ if(plexclient::ActionManager::GetInstance().IsAction()) {
+ std::string file = plexclient::ActionManager::GetInstance().GetAction();
+ PlayFile(file.c_str());
+ }
+}
+
+/**
+** Return our setup menu.
+*/
+cMenuSetupPage *cMyPlugin::SetupMenu(void)
+{
+ //dsyslog("[plex]%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("[plex]%s: '%s' = '%s'\n", __FUNCTION__, name, value);
+
+ if (strcasecmp(name, "HideMainMenuEntry") == 0) Config::GetInstance().HideMainMenuEntry = atoi(value) ? true : false;
+ else if (strcasecmp(name, "DisableRemote") == 0) Config::GetInstance().DisableRemote = atoi(value) ? true : false;
+ else if (strcasecmp(name, "Username") == 0) Config::GetInstance().s_username = std::string(value);
+ else if (strcasecmp(name, "Password") == 0) Config::GetInstance().s_password = std::string(value);
+ else if (strcasecmp(name, "UUID") == 0) Config::GetInstance().SetUUID(value);
+ else return false;
+
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+int OldPrimaryDevice; ///< old primary device
+
+/**
+** Enable dummy device.
+*/
+extern "C" void EnableDummyDevice(void)
+{
+ OldPrimaryDevice = cDevice::PrimaryDevice()->DeviceNumber() + 1;
+
+ dsyslog("[plex]: primary device %d to new %d\n", OldPrimaryDevice,
+ MyDevice->DeviceNumber() + 1);
+
+ //if (!cDevice::SetPrimaryDevice(MyDevice->DeviceNumber() + 1)) {
+ DoMakePrimary = MyDevice->DeviceNumber() + 1;
+ cOsdProvider::Shutdown();
+ //}
+}
+
+/**
+** Disable dummy device.
+*/
+extern "C" void DisableDummyDevice(void)
+{
+ dsyslog("[plex]: primary device %d to old %d\n",
+ cDevice::PrimaryDevice()->DeviceNumber() + 1, OldPrimaryDevice);
+
+ //if (!cDevice::SetPrimaryDevice(OldPrimaryDevice)) {
+ DoMakePrimary = OldPrimaryDevice;
+ OldPrimaryDevice = 0;
+ cOsdProvider::Shutdown();
+ //}
+}
+
+VDRPLUGINCREATOR(cMyPlugin); // Don't touch this!
diff --git a/plex.h b/plex.h
new file mode 100644
index 0000000..5b0ff71
--- /dev/null
+++ b/plex.h
@@ -0,0 +1,222 @@
+#ifndef PLEX_H
+#define PLEX_H
+
+#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>
+#include <vdr/menuitems.h>
+
+#include "Config.h"
+#include "Plexservice.h"
+
+#include "plexgdm.h"
+#include "play_service.h"
+#include "cPlexOsdItem.h"
+
+#include <iostream>
+#include <string>
+#include <vector>
+#include <iterator>
+#include <algorithm>
+
+
+/**
+** 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);
+ }
+};
+
+/**
+** 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 &);
+};
+
+/**
+** Status class.
+**
+** To get volume changes.
+*/
+class cMyStatus:public cStatus
+{
+ private:
+ int Volume; ///< current volume
+
+ public:
+ cMyStatus(void); ///< my status constructor
+
+ protected:
+ virtual void SetVolume(int, bool); ///< volume changed
+};
+
+/**
+** 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
+ bool infoVisible; ///< RecordingInfo visible
+ time_t timeoutShow; ///< timeout shown control
+
+ public:
+ cMyControl(const char *); ///< player control constructor
+ virtual ~ cMyControl(); ///< player control destructor
+
+ virtual eOSState ProcessKey(eKeys); ///< handle keyboard input
+
+};
+
+/*
+ * Plex Browser
+ */
+
+class cPlexBrowser :public cOsdMenu
+{
+private:
+ plexclient::Plexservice* pService;
+ plexclient::MediaContainer *pCont;
+ std::vector<plexclient::Video> *v_Vid;
+ std::vector<plexclient::Directory> *v_Dir;
+ std::vector<std::string> m_vStack;
+ std::string m_sSection;
+ std::string m_sActualPos;
+ /// Create a browser menu for current directory
+ void CreateMenu();
+ /// Handle menu level up
+ eOSState LevelUp(void);
+ /// Handle menu item selection
+ eOSState ProcessSelected();
+
+public:
+ cPlexBrowser(const char *title, plexclient::Plexservice* pServ);
+
+ /// File browser destructor
+ //virtual ~ cPlexBrowser();
+ /// Process keyboard input
+ virtual eOSState ProcessKey(eKeys);
+
+};
+
+/**
+** Play plugin menu class.
+*/
+class cPlayMenu:public cOsdMenu
+{
+ private:
+ public:
+ cPlayMenu(const char *, plexclient::plexgdm* gdm, int = 0, int = 0, int = 0, int = 0, int = 0);
+ virtual ~ cPlayMenu();
+ virtual eOSState ProcessKey(eKeys);
+};
+
+/**
+** 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
+};
+
+/**
+** 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);
+};
+
+/**
+** Dummy device class.
+*/
+class cMyDevice:public cDevice
+{
+ public:
+ cMyDevice(void);
+ virtual ~ cMyDevice(void);
+
+ virtual void GetOsdSize(int &, int &, double &);
+
+ protected:
+ virtual void MakePrimaryDevice(bool);
+};
+
+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 &);
+
+ private:
+ plexclient::Plexservice* pService;
+ plexclient::plexgdm* pPlexgdm;
+};
+
+#endif \ No newline at end of file
diff --git a/plexgdm.cpp b/plexgdm.cpp
new file mode 100644
index 0000000..bca152c
--- /dev/null
+++ b/plexgdm.cpp
@@ -0,0 +1,130 @@
+#include "plexgdm.h"
+
+namespace plexclient
+{
+
+plexgdm::plexgdm()
+{
+ m_discoverAdress = Poco::Net::SocketAddress(_multicastAddress, 32414);
+ m_clientRegisterGroup = Poco::Net::SocketAddress(_multicastAddress, 32413);
+
+ m_discoveryComplete = false;
+ m_registrationIsRunning = false;
+ m_clientRegistered = false;
+ m_discoveryInterval = 120;
+ m_discoveryIsRunning = false;
+}
+
+plexgdm::~plexgdm()
+{
+}
+
+void plexgdm::clientDetails(std::string c_id, std::string c_name, std::string c_port, std::string c_product, std::string c_version)
+{
+ _clientData = "Content-Type: plex/media-player\n"
+ "Resource-Identifier: " + c_id + "\n"
+ "Device-Class: HTPC\n"
+ "Name: " + c_name + "\n"
+ "Port: " + c_port + "\n"
+ "Product: " + c_product + "\n"
+ "Protocol: plex\n"
+ "Protocol-Capabilities: navigation,playback,timeline\n"
+ "Protocol-Version: 1\n"
+ "Version: " + c_version;
+ _clientId = c_id;
+}
+
+std::string plexgdm::getClientDetails()
+{
+ if (_clientData.empty()) throw "client_Data not initialized";
+ return _clientData;
+}
+
+void plexgdm::Action()
+{
+ char buffer[1024];
+ m_registrationIsRunning = true;
+ cMutexLock lock(&m_mutex);
+
+ Poco::Net::MulticastSocket update_sock(
+ Poco::Net::SocketAddress( Poco::Net::IPAddress(), m_discoverAdress.port() )
+ );
+
+ update_sock.setReuseAddress(true);
+ update_sock.setTimeToLive(255);
+ update_sock.setBlocking(false);
+ // Send initial Client Registration
+
+ std::string s = Poco::format("HELLO %s\n%s", _clientHeader, _clientData);
+ //std::cout << "SendTo:\n" << s << std::endl;
+ update_sock.sendTo(s.c_str(), s.length(), m_clientRegisterGroup,0);
+
+
+ while(m_registrationIsRunning) {
+ //std::cout << "cUpd running " << m_registrationIsRunning << std::endl;
+ Poco::Net::SocketAddress sender;
+ int n = 0;
+ try {
+ n = update_sock.receiveFrom(buffer, sizeof(buffer), sender);
+ } catch (Poco::TimeoutException &e) {
+ n = 0;
+ }
+ if(n > 0) {
+ std::string buf(buffer, n);
+ if (buf.find("M-SEARCH * HTTP/1.") != std::string::npos) {
+ std::cout << "Detected client discovery request from " << sender.host().toString() << " Replying..." << std::endl;
+ s = Poco::format("HTTP/1.0 200 OK\n%s", _clientData);
+ update_sock.sendTo(s.c_str(), s.length(), sender);
+ m_clientRegistered = true;
+ }
+ }
+ m_waitCondition.TimedWait(m_mutex, 600);
+ }
+ // Client loop stopped
+
+ // unregister from Server
+ s = Poco::format("BYE %s\n%s", _clientHeader, _clientData);
+ //std::cout << "Unregister: \n" << s << std::endl;
+ update_sock.sendTo(s.c_str(), s.length(), m_clientRegisterGroup,0);
+
+ m_clientRegistered = false;
+
+}
+
+void plexgdm::discover()
+{
+ // TODO: Discover multiple servers
+ char buffer[1024];
+ Poco::Net::MulticastSocket socket(
+ Poco::Net::SocketAddress( Poco::Net::IPAddress(), m_discoverAdress.port() )
+ );
+ socket.setReceiveTimeout(0.6);
+ //socket.setTimeToLive(0.6);
+ socket.sendTo(_discoverMessage.c_str(), _discoverMessage.length(), m_discoverAdress, 0);
+
+ socket.joinGroup(m_discoverAdress.host());
+ Poco::Net::SocketAddress sender;
+ int n = socket.receiveFrom(buffer, sizeof(buffer), sender);
+ std::string buf(buffer, n);
+ //std::cout << "Discover received from: " << sender.host().toString() << "\nData:\n" << buf;
+
+ socket.close();
+ m_discoveryComplete = true;
+ // check for a valid response
+ if(buf.find("200 OK") != std::string::npos) {
+ m_pServer = new PlexServer(buf, sender.host().toString());
+ }
+}
+
+void plexgdm::stopRegistration()
+{
+ std::cout << "stop Reg" << std::endl;
+ if(m_registrationIsRunning) {
+ m_registrationIsRunning = false;
+ m_waitCondition.Broadcast();
+ Cancel(3);
+ }
+}
+
+
+}
diff --git a/plexgdm.h b/plexgdm.h
new file mode 100644
index 0000000..e46f3b6
--- /dev/null
+++ b/plexgdm.h
@@ -0,0 +1,75 @@
+#ifndef PLEXGDM_H
+#define PLEXGDM_H
+
+#include <iostream>
+#include <string>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <Poco/Net/SocketAddress.h>
+#include <Poco/Net/MulticastSocket.h>
+#include <Poco/Format.h>
+
+#include <vdr/thread.h>
+
+#include "PlexServer.h"
+
+namespace plexclient
+{
+
+class plexgdm : public cThread
+{
+public:
+ plexgdm();
+ ~plexgdm();
+ void clientDetails(std::string c_id, std::string c_name, std::string c_port, std::string c_product, std::string c_version);
+ std::string getClientDetails();
+ PlexServer* getServerList();
+ void discover();;
+ void checkClientRegistration();
+
+ void Action(void);
+
+ //void startAll();
+ void startRegistration();
+
+ //void stopAll();
+ void stopRegistration();
+
+
+ PlexServer* GetPServer() {
+ return m_pServer;
+ }
+
+protected:
+
+
+ private:
+
+ cMutex m_mutex;
+ cCondVar m_waitCondition;
+
+ Poco::Net::SocketAddress m_discoverAdress;
+ Poco::Net::SocketAddress m_clientRegisterGroup;
+
+ volatile int m_discoveryInterval;
+
+ volatile bool m_discoveryComplete;
+ volatile bool m_clientRegistered;
+ volatile bool m_discoveryIsRunning;
+ volatile bool m_registrationIsRunning;
+
+ std::string _discoverMessage = "M-SEARCH * HTTP/1.0";
+ std::string _clientHeader = "* HTTP/1.0";
+ std::string _clientData;
+ std::string _clientId;
+ std::string _multicastAddress = "239.0.0.250";
+ int _clientUpdatePort = 32412;
+ PlexServer *m_pServer;
+};
+
+}
+#endif // PLEXGDM_H
diff --git a/user.cpp b/user.cpp
new file mode 100644
index 0000000..ea2880b
--- /dev/null
+++ b/user.cpp
@@ -0,0 +1,31 @@
+#include "user.h"
+
+namespace plexclient
+{
+
+user::user(std::istream *response)
+{
+ try {
+ InputSource src(*response);
+ DOMParser parser;
+ Poco::XML::AutoPtr<Document> pDoc = parser.parse(&src);
+
+ NodeIterator it(pDoc, Poco::XML::NodeFilter::SHOW_ALL);
+ Node* pNode = it.nextNode();
+
+ while(pNode) {
+ if(Poco::icompare(pNode->nodeName(),"authentication-token") == 0) {
+ pNode = it.nextNode();
+ authenticationToken = pNode->nodeValue();
+ break;
+ }
+ pNode = it.nextNode();
+ }
+
+ } catch(Exception &exc) {
+ std::cerr << exc.displayText() << std::endl;
+ }
+
+}
+
+}
diff --git a/user.h b/user.h
new file mode 100644
index 0000000..f264209
--- /dev/null
+++ b/user.h
@@ -0,0 +1,42 @@
+#ifndef USER_H
+#define USER_H
+
+#include <Poco/DOM/DOMParser.h>
+#include <Poco/DOM/Document.h>
+#include <Poco/DOM/NodeIterator.h>
+#include <Poco/DOM/NodeFilter.h>
+#include <Poco/DOM/AutoPtr.h>
+#include <Poco/SAX/InputSource.h>
+#include <Poco/Exception.h>
+#include <Poco/String.h>
+#include <iostream>
+
+using Poco::XML::DOMParser;
+using Poco::XML::InputSource;
+using Poco::XML::Document;
+using Poco::XML::NodeIterator;
+using Poco::XML::NodeFilter;
+using Poco::XML::Node;
+using Poco::XML::AutoPtr;
+using Poco::Exception;
+
+namespace plexclient
+{
+
+class user
+{
+public:
+ user(std::istream *response);
+
+ ~user();
+
+ std::string authenticationToken;
+
+protected:
+
+private:
+};
+
+}
+
+#endif // USER_H
diff --git a/video.c b/video.c
new file mode 100644
index 0000000..7fe2fff
--- /dev/null
+++ b/video.c
@@ -0,0 +1,775 @@
+///
+/// @file video.c @brief Video module
+///
+/// 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: 54d117902655b23d2eff9ecaf59ce4eab5f46d42 $
+//////////////////////////////////////////////////////////////////////////////
+
+///
+/// @defgroup Video The video module.
+///
+/// This module contains all video rendering functions.
+///
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <libintl.h>
+#define _(str) gettext(str) ///< gettext shortcut
+#define _N(str) str ///< gettext_noop shortcut
+
+#include "misc.h"
+#include "video.h"
+
+//////////////////////////////////////////////////////////////////////////////
+// X11 / XCB
+//////////////////////////////////////////////////////////////////////////////
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_icccm.h>
+#include <xcb/xcb_image.h>
+#include <xcb/xcb_pixel.h>
+#include <xcb/xcb_event.h>
+#include <xcb/xcb_keysyms.h>
+#include <X11/keysym.h> // keysym XK_
+#include <X11/XF86keysym.h> // XF86XK_
+#include <poll.h>
+
+static xcb_connection_t *Connection; ///< xcb connection
+static xcb_colormap_t VideoColormap; ///< video colormap
+static xcb_window_t VideoOsdWindow; ///< video osd window
+static xcb_window_t VideoPlayWindow; ///< video player window
+static xcb_screen_t const *VideoScreen; ///< video screen
+static uint32_t VideoBlankTick; ///< blank cursor timer
+static xcb_pixmap_t VideoPixmap; ///< blank cursor pixmap
+static xcb_cursor_t VideoBlankCursor; ///< empty invisible cursor
+
+static uint32_t VideoColorKey; ///< color key pixel value
+
+static int VideoWindowX; ///< video output window x coordinate
+static int VideoWindowY; ///< video outout window y coordinate
+static unsigned VideoWindowWidth; ///< video output window width
+static unsigned VideoWindowHeight; ///< video output window height
+
+static char Osd3DMode; ///< 3D OSD mode
+
+///
+/// Create X11 window.
+///
+/// @param parent parent of new window
+/// @param visual visual of parent
+/// @param depth depth of parent
+///
+/// @returns created X11 window id
+///
+static xcb_window_t VideoCreateWindow(xcb_window_t parent,
+ xcb_visualid_t visual, uint8_t depth)
+{
+ uint32_t values[5];
+ xcb_window_t window;
+
+ Debug(3, "video: visual %#0x depth %d\n", visual, depth);
+
+ //
+ // create color map
+ //
+ if (VideoColormap == XCB_NONE) {
+ VideoColormap = xcb_generate_id(Connection);
+ xcb_create_colormap(Connection, XCB_COLORMAP_ALLOC_NONE, VideoColormap,
+ parent, visual);
+ }
+ //
+ // create blank cursor
+ //
+ if (VideoBlankCursor == XCB_NONE) {
+ VideoPixmap = xcb_generate_id(Connection);
+ xcb_create_pixmap(Connection, 1, VideoPixmap, parent, 1, 1);
+ VideoBlankCursor = xcb_generate_id(Connection);
+ xcb_create_cursor(Connection, VideoBlankCursor, VideoPixmap,
+ VideoPixmap, 0, 0, 0, 0, 0, 0, 1, 1);
+ VideoBlankTick = 0;
+ }
+
+ values[0] = VideoColorKey; // ARGB
+ values[1] = VideoColorKey;
+ values[2] =
+ XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE |
+ XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
+ XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_EXPOSURE |
+ XCB_EVENT_MASK_STRUCTURE_NOTIFY;
+ values[3] = VideoColormap;
+ values[4] = VideoBlankCursor;
+ window = xcb_generate_id(Connection);
+ xcb_create_window(Connection, depth, window, parent, VideoWindowX,
+ VideoWindowY, VideoWindowWidth, VideoWindowHeight, 0,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT, visual,
+ XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK |
+ XCB_CW_COLORMAP | XCB_CW_CURSOR, values);
+
+ // define only available with xcb-utils-0.3.8
+#ifdef XCB_ICCCM_NUM_WM_SIZE_HINTS_ELEMENTS
+ // FIXME: utf _NET_WM_NAME
+ xcb_icccm_set_wm_name(Connection, window, XCB_ATOM_STRING, 8,
+ sizeof("play control") - 1, "play control");
+ xcb_icccm_set_wm_icon_name(Connection, window, XCB_ATOM_STRING, 8,
+ sizeof("play control") - 1, "play control");
+#endif
+ // define only available with xcb-utils-0.3.6
+#ifdef XCB_NUM_WM_HINTS_ELEMENTS
+ // FIXME: utf _NET_WM_NAME
+ xcb_set_wm_name(Connection, window, XCB_ATOM_STRING,
+ sizeof("play control") - 1, "play control");
+ xcb_set_wm_icon_name(Connection, window, XCB_ATOM_STRING,
+ sizeof("play control") - 1, "play control");
+#endif
+
+ // FIXME: size hints
+
+ // window above parent
+ values[0] = parent;
+ values[1] = XCB_STACK_MODE_ABOVE;
+ xcb_configure_window(Connection, window,
+ XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
+
+ return window;
+}
+
+///
+/// Enable OSD 3d mode.
+///
+/// @param mode turn 3d mode on/off
+///
+void VideoSetOsd3DMode(int mode)
+{
+ Osd3DMode = mode;
+}
+
+///
+/// Draw a ARGB image.
+///
+/// @param x x position of image in osd
+/// @param y y position of image in osd
+/// @param width width of image
+/// @param height height of image
+/// @param argb argb image
+///
+void VideoDrawARGB(int x, int y, int width, int height, const uint8_t * argb)
+{
+ xcb_image_t *xcb_image;
+ xcb_gcontext_t gc;
+ int sx;
+ int sy;
+ int fs;
+
+ if (!Connection) {
+ Debug(3, "play: FIXME: must restore osd provider\n");
+ return;
+ }
+
+ if (x + y < 1 && (unsigned)height == VideoWindowHeight
+ && (unsigned)width == VideoWindowWidth) {
+ fs = 1;
+ } else {
+ fs = 0;
+ }
+
+ gc = xcb_generate_id(Connection);
+ xcb_create_gc(Connection, gc, VideoOsdWindow, 0, NULL);
+
+ switch (Osd3DMode) {
+ case 1: // SBS
+ xcb_image =
+ xcb_image_create_native(Connection, width / 2, height,
+ XCB_IMAGE_FORMAT_Z_PIXMAP, VideoScreen->root_depth, NULL, 0L,
+ NULL);
+ break;
+ case 2: // TB
+ xcb_image =
+ xcb_image_create_native(Connection, width, height / 2,
+ XCB_IMAGE_FORMAT_Z_PIXMAP, VideoScreen->root_depth, NULL, 0L,
+ NULL);
+ break;
+ default:
+ xcb_image =
+ xcb_image_create_native(Connection, width, height,
+ XCB_IMAGE_FORMAT_Z_PIXMAP, VideoScreen->root_depth, NULL, 0L,
+ NULL);
+ }
+
+ // fast 32it versions
+ if (xcb_image->bpp == 32) {
+ if (xcb_image->byte_order == XCB_IMAGE_ORDER_LSB_FIRST) {
+ for (sy = 0; sy < height; ++sy) {
+ for (sx = 0; sx < width; ++sx) {
+ uint32_t pixel;
+
+ if (argb[(width * sy + sx) * 4 + 3] < 200) {
+ pixel = VideoColorKey;
+ } else {
+ pixel = argb[(width * sy + sx) * 4 + 0] << 0;
+ pixel |= argb[(width * sy + sx) * 4 + 1] << 8;
+ pixel |= argb[(width * sy + sx) * 4 + 2] << 16;
+ }
+ switch (Osd3DMode) {
+ case 1: // SBS
+ xcb_image_put_pixel_Z32L(xcb_image, sx / 2, sy,
+ pixel);
+ break;
+ case 2: // TB
+ xcb_image_put_pixel_Z32L(xcb_image, sx, sy / 2,
+ pixel);
+ break;
+ default:
+ xcb_image_put_pixel_Z32L(xcb_image, sx, sy, pixel);
+ }
+ }
+ }
+ } else {
+ Error(_("play: unsupported put_image\n"));
+ }
+ } else {
+ for (sy = 0; sy < height; ++sy) {
+ for (sx = 0; sx < width; ++sx) {
+ uint32_t pixel;
+
+ if (argb[(width * sy + sx) * 4 + 3] < 200) {
+ pixel = argb[(width * sy + sx) * 4 + 3] << 0;
+ pixel |= argb[(width * sy + sx) * 4 + 3] << 8;
+ pixel |= argb[(width * sy + sx) * 4 + 3] << 16;
+ } else {
+ pixel = argb[(width * sy + sx) * 4 + 0] << 0;
+ pixel |= argb[(width * sy + sx) * 4 + 1] << 8;
+ pixel |= argb[(width * sy + sx) * 4 + 2] << 16;
+ }
+ switch (Osd3DMode) {
+ case 1: // SBS
+ xcb_image_put_pixel(xcb_image, sx / 2, sy, pixel);
+ break;
+ case 2: // TB
+ xcb_image_put_pixel(xcb_image, sx, sy / 2, pixel);
+ break;
+ default:
+ xcb_image_put_pixel(xcb_image, sx, sy, pixel);
+ }
+ }
+ }
+ }
+
+ // render xcb_image to color data pixmap
+ switch (Osd3DMode) {
+ case 1: // SBS
+ if (fs) {
+ xcb_image_put(Connection, VideoOsdWindow, gc, xcb_image, x, y,
+ 0);
+ xcb_image_put(Connection, VideoOsdWindow, gc, xcb_image,
+ x + VideoWindowWidth / 2, y, 0);
+ } else {
+ xcb_image_put(Connection, VideoOsdWindow, gc, xcb_image, x / 2,
+ y, 0);
+ xcb_image_put(Connection, VideoOsdWindow, gc, xcb_image,
+ x / 2 + VideoWindowWidth / 2, y, 0);
+ }
+ break;
+ case 2: // TB
+ if (fs) {
+ xcb_image_put(Connection, VideoOsdWindow, gc, xcb_image, x, y,
+ 0);
+ xcb_image_put(Connection, VideoOsdWindow, gc, xcb_image, x,
+ y + VideoWindowHeight / 2, 0);
+ } else {
+ xcb_image_put(Connection, VideoOsdWindow, gc, xcb_image, x,
+ y / 2, 0);
+ xcb_image_put(Connection, VideoOsdWindow, gc, xcb_image, x,
+ y / 2 + VideoWindowHeight / 2, 0);
+ }
+ break;
+ default:
+ xcb_image_put(Connection, VideoOsdWindow, gc, xcb_image, x, y, 0);
+ }
+ // release xcb_image
+ xcb_image_destroy(xcb_image);
+ xcb_free_gc(Connection, gc);
+ xcb_flush(Connection);
+}
+
+///
+/// Show window.
+///
+void VideoWindowShow(void)
+{
+ if (!Connection) {
+ Debug(3, "play: FIXME: must restore osd provider\n");
+ return;
+ }
+ xcb_map_window(Connection, VideoOsdWindow);
+}
+
+///
+/// Hide window.
+///
+void VideoWindowHide(void)
+{
+ if (!Connection) {
+ Debug(3, "play: FIXME: must restore osd provider\n");
+ return;
+ }
+ xcb_unmap_window(Connection, VideoOsdWindow);
+}
+
+///
+/// Clear window.
+///
+void VideoWindowClear(void)
+{
+ if (!Connection) {
+ Debug(3, "play: FIXME: must restore osd provider\n");
+ return;
+ }
+ xcb_clear_area(Connection, 0, VideoOsdWindow, 0, 0, VideoWindowWidth,
+ VideoWindowHeight);
+ xcb_flush(Connection);
+}
+
+static xcb_key_symbols_t *XcbKeySymbols; ///< Keyboard symbols
+static uint16_t NumLockMask; ///< mod mask for num-lock
+static uint16_t ShiftLockMask; ///< mod mask for shift-lock
+static uint16_t CapsLockMask; ///< mod mask for caps-lock
+static uint16_t ModeSwitchMask; ///< mod mask for mode-switch
+
+///
+/// Handle key press event.
+///
+static void VideoKeyPress(const xcb_key_press_event_t * event)
+{
+ char buf[2];
+ xcb_keysym_t ks0;
+ xcb_keysym_t ks1;
+ xcb_keysym_t keysym;
+ xcb_keycode_t keycode;
+ unsigned modifier;
+
+ if (!XcbKeySymbols) {
+ XcbKeySymbols = xcb_key_symbols_alloc(Connection);
+ if (!XcbKeySymbols) {
+ Error(_("play/event: can't read key symbols\n"));
+ return;
+ }
+ NumLockMask = ShiftLockMask = CapsLockMask = ModeSwitchMask = 0;
+
+ // FIXME: lock and mode keys are not prepared!
+ }
+
+ keycode = event->detail;
+ modifier = event->state;
+ // handle mode-switch
+ if (modifier & ModeSwitchMask) {
+ ks0 = xcb_key_symbols_get_keysym(XcbKeySymbols, keycode, 2);
+ ks1 = xcb_key_symbols_get_keysym(XcbKeySymbols, keycode, 3);
+ } else {
+ ks0 = xcb_key_symbols_get_keysym(XcbKeySymbols, keycode, 0);
+ ks1 = xcb_key_symbols_get_keysym(XcbKeySymbols, keycode, 1);
+ }
+ // use first keysym, if second keysym didn't exists
+ if (ks1 == XCB_NO_SYMBOL) {
+ ks1 = ks0;
+ }
+ // see xcb-util-0.3.6/keysyms/keysyms.c:
+ if (!(modifier & XCB_MOD_MASK_SHIFT) && !(modifier & XCB_MOD_MASK_LOCK)) {
+ keysym = ks0;
+ } else {
+ // FIXME: more cases
+
+ keysym = ks0;
+ }
+
+ // FIXME: use xcb_lookup_string
+ switch (keysym) {
+ case XK_space:
+ FeedKeyPress("XKeySym", "space", 0, 0);
+ break;
+ case XK_exclam ... XK_slash:
+ case XK_0 ... XK_9:
+ case XK_A ... XK_Z:
+ case XK_a ... XK_z:
+ buf[0] = keysym;
+ buf[1] = '\0';
+ FeedKeyPress("XKeySym", buf, 0, 0);
+ break;
+
+ case XK_BackSpace:
+ FeedKeyPress("XKeySym", "BackSpace", 0, 0);
+ break;
+ case XK_Tab:
+ FeedKeyPress("XKeySym", "Tab", 0, 0);
+ break;
+ case XK_Return:
+ FeedKeyPress("XKeySym", "Return", 0, 0);
+ break;
+ case XK_Escape:
+ FeedKeyPress("XKeySym", "Escape", 0, 0);
+ break;
+ case XK_Delete:
+ FeedKeyPress("XKeySym", "Delete", 0, 0);
+ break;
+
+ case XK_Home:
+ FeedKeyPress("XKeySym", "Home", 0, 0);
+ break;
+ case XK_Left:
+ FeedKeyPress("XKeySym", "Left", 0, 0);
+ break;
+ case XK_Up:
+ FeedKeyPress("XKeySym", "Up", 0, 0);
+ break;
+ case XK_Right:
+ FeedKeyPress("XKeySym", "Right", 0, 0);
+ break;
+ case XK_Down:
+ FeedKeyPress("XKeySym", "Down", 0, 0);
+ break;
+ case XK_Page_Up:
+ FeedKeyPress("XKeySym", "Page_Up", 0, 0);
+ break;
+ case XK_Page_Down:
+ FeedKeyPress("XKeySym", "Page_Down", 0, 0);
+ break;
+ case XK_End:
+ FeedKeyPress("XKeySym", "End", 0, 0);
+ break;
+ case XK_Begin:
+ FeedKeyPress("XKeySym", "Begin", 0, 0);
+ break;
+
+ case XK_F1:
+ FeedKeyPress("XKeySym", "F1", 0, 0);
+ break;
+ case XK_F2:
+ FeedKeyPress("XKeySym", "F2", 0, 0);
+ break;
+ case XK_F3:
+ FeedKeyPress("XKeySym", "F3", 0, 0);
+ break;
+ case XK_F4:
+ FeedKeyPress("XKeySym", "F4", 0, 0);
+ break;
+ case XK_F5:
+ FeedKeyPress("XKeySym", "F5", 0, 0);
+ break;
+ case XK_F6:
+ FeedKeyPress("XKeySym", "F6", 0, 0);
+ break;
+ case XK_F7:
+ FeedKeyPress("XKeySym", "F7", 0, 0);
+ break;
+ case XK_F8:
+ FeedKeyPress("XKeySym", "F8", 0, 0);
+ break;
+ case XK_F9:
+ FeedKeyPress("XKeySym", "F9", 0, 0);
+ break;
+ case XK_F10:
+ FeedKeyPress("XKeySym", "F10", 0, 0);
+ break;
+ case XK_F11:
+ FeedKeyPress("XKeySym", "F11", 0, 0);
+ break;
+ case XK_F12:
+ FeedKeyPress("XKeySym", "F12", 0, 0);
+ break;
+
+ case XF86XK_Red:
+ FeedKeyPress("XKeySym", "Red", 0, 0);
+ break;
+ case XF86XK_Green:
+ FeedKeyPress("XKeySym", "Green", 0, 0);
+ break;
+ case XF86XK_Yellow:
+ FeedKeyPress("XKeySym", "Yellow", 0, 0);
+ break;
+ case XF86XK_Blue:
+ FeedKeyPress("XKeySym", "Blue", 0, 0);
+ break;
+
+ case XF86XK_HomePage:
+ FeedKeyPress("XKeySym", "XF86HomePage", 0, 0);
+ break;
+ case XF86XK_AudioLowerVolume:
+ FeedKeyPress("XKeySym", "XF86AudioLowerVolume", 0, 0);
+ break;
+ case XF86XK_AudioMute:
+ FeedKeyPress("XKeySym", "XF86AudioMute", 0, 0);
+ break;
+ case XF86XK_AudioRaiseVolume:
+ FeedKeyPress("XKeySym", "XF86AudioRaiseVolume", 0, 0);
+ break;
+ case XF86XK_AudioPlay:
+ FeedKeyPress("XKeySym", "XF86AudioPlay", 0, 0);
+ break;
+ case XF86XK_AudioStop:
+ FeedKeyPress("XKeySym", "XF86AudioStop", 0, 0);
+ break;
+ case XF86XK_AudioPrev:
+ FeedKeyPress("XKeySym", "XF86AudioPrev", 0, 0);
+ break;
+ case XF86XK_AudioNext:
+ FeedKeyPress("XKeySym", "XF86AudioNext", 0, 0);
+ break;
+
+ default:
+ Debug(3, "play/event: keycode %d\n", event->detail);
+ break;
+
+ }
+}
+
+///
+/// Poll video events.
+///
+/// @param timeout timeout in milliseconds
+///
+void VideoPollEvents(int timeout)
+{
+ struct pollfd fds[1];
+ xcb_generic_event_t *event;
+ int n;
+ int delay;
+
+ if (!Connection) {
+ Debug(3, "play: poll without connection\n");
+ return;
+ }
+
+ fds[0].fd = xcb_get_file_descriptor(Connection);
+ fds[0].events = POLLIN | POLLPRI;
+
+ delay = timeout;
+ for (;;) {
+ xcb_flush(Connection);
+
+ // wait for events or timeout
+ // FIXME: this can poll forever
+ if ((n = poll(fds, 1, delay)) <= 0) {
+ // error or timeout
+ if (n) { // error
+ Error(_("play/event: poll failed: %s\n"), strerror(errno));
+ }
+ return;
+ }
+ if (fds[0].revents & (POLLIN | POLLPRI)) {
+ if ((event = xcb_poll_for_event(Connection))) {
+
+ switch (XCB_EVENT_RESPONSE_TYPE(event)) {
+#if 0
+ // background pixmap no need to redraw
+ case XCB_EXPOSE:
+ // collapse multi expose
+ if (!((xcb_expose_event_t *) event)->count) {
+ xcb_clear_area(Connection, 0, Window, 0, 0, 64,
+ 64);
+ // flush the request
+ xcb_flush(Connection);
+ }
+ break;
+#endif
+ case XCB_MAP_NOTIFY:
+ Debug(3, "video/event: MapNotify\n");
+ // hide cursor after mapping
+ xcb_change_window_attributes(Connection,
+ VideoOsdWindow, XCB_CW_CURSOR, &VideoBlankCursor);
+ xcb_change_window_attributes(Connection,
+ VideoPlayWindow, XCB_CW_CURSOR, &VideoBlankCursor);
+ break;
+ case XCB_DESTROY_NOTIFY:
+ return;
+ case XCB_KEY_PRESS:
+ VideoKeyPress((xcb_key_press_event_t *) event);
+ break;
+ case XCB_KEY_RELEASE:
+ case XCB_BUTTON_PRESS:
+ case XCB_BUTTON_RELEASE:
+ break;
+ case XCB_MOTION_NOTIFY:
+ break;
+
+ case 0:
+ // error_code
+ Debug(3, "play/event: error %x\n",
+ event->response_type);
+ break;
+ default:
+ // unknown event type, ignore it
+ Debug(3, "play/event: unknown %x\n",
+ event->response_type);
+ break;
+ }
+
+ free(event);
+ } else {
+ // no event, can happen, but we must check for close
+ if (xcb_connection_has_error(Connection)) {
+ return;
+ }
+ }
+ }
+ }
+}
+
+///
+/// Get OSD size.
+///
+/// @param[out] width OSD width
+/// @param[out] height OSD height
+///
+void VideoGetOsdSize(int *width, int *height)
+{
+ *width = 1920;
+ *height = 1080; // unknown default
+ if (VideoWindowWidth && VideoWindowHeight) {
+ *width = VideoWindowWidth;
+ *height = VideoWindowHeight;
+ }
+}
+
+///
+/// Get player video window id.
+///
+int VideoGetPlayWindow(void)
+{
+ return VideoPlayWindow;
+}
+
+///
+/// Set video geometry.
+///
+/// @todo write/search the good version
+///
+void VideoSetGeometry(const char *geometry)
+{
+ sscanf(geometry, "%dx%d%d%d", &VideoWindowWidth, &VideoWindowHeight,
+ &VideoWindowX, &VideoWindowY);
+}
+
+///
+/// Set video color key.
+///
+/// Should be called before VideoInit().
+///
+void VideoSetColorKey(uint32_t color_key)
+{
+ VideoColorKey = color_key;
+}
+
+///
+/// Initialize video.
+///
+int VideoInit(const char *display)
+{
+ const char *display_name;
+ xcb_connection_t *connection;
+ xcb_screen_iterator_t iter;
+ int screen_nr;
+ int i;
+
+ display_name = display ? display : getenv("DISPLAY");
+
+ // Open the connection to the X server.
+ connection = xcb_connect(display_name, &screen_nr);
+ if (!connection || xcb_connection_has_error(connection)) {
+ fprintf(stderr, "play: can't connect to X11 server on %s\n",
+ display_name);
+ return -1;
+ }
+ Connection = connection;
+
+ // Get the requested screen number
+ iter = xcb_setup_roots_iterator(xcb_get_setup(connection));
+ for (i = 0; i < screen_nr; ++i) {
+ xcb_screen_next(&iter);
+ }
+ VideoScreen = iter.data;
+
+ //
+ // Default window size
+ //
+ if (!VideoWindowHeight) {
+ if (VideoWindowWidth) {
+ VideoWindowHeight = (VideoWindowWidth * 9) / 16;
+ } else { // default to fullscreen
+ VideoWindowHeight = VideoScreen->height_in_pixels;
+ VideoWindowWidth = VideoScreen->width_in_pixels;
+ }
+ }
+ if (!VideoWindowWidth) {
+ VideoWindowWidth = (VideoWindowHeight * 16) / 9;
+ }
+
+ VideoPlayWindow =
+ VideoCreateWindow(VideoScreen->root, VideoScreen->root_visual,
+ VideoScreen->root_depth);
+ xcb_map_window(Connection, VideoPlayWindow);
+ VideoOsdWindow =
+ VideoCreateWindow(VideoPlayWindow, VideoScreen->root_visual,
+ VideoScreen->root_depth);
+ Debug(3, "play: osd %x, play %x\n", VideoOsdWindow, VideoPlayWindow);
+
+ VideoWindowClear();
+ // done by clear: xcb_flush(Connection);
+
+ return 0;
+}
+
+///
+/// Cleanup video.
+///
+void VideoExit(void)
+{
+ if (VideoOsdWindow != XCB_NONE) {
+ xcb_destroy_window(Connection, VideoOsdWindow);
+ VideoOsdWindow = XCB_NONE;
+ }
+ if (VideoPlayWindow != XCB_NONE) {
+ xcb_destroy_window(Connection, VideoPlayWindow);
+ VideoPlayWindow = XCB_NONE;
+ }
+ if (VideoColormap != XCB_NONE) {
+ xcb_free_colormap(Connection, VideoColormap);
+ VideoColormap = XCB_NONE;
+ }
+ if (VideoBlankCursor != XCB_NONE) {
+ xcb_free_cursor(Connection, VideoBlankCursor);
+ VideoBlankCursor = XCB_NONE;
+ }
+ if (VideoPixmap != XCB_NONE) {
+ xcb_free_pixmap(Connection, VideoPixmap);
+ VideoPixmap = XCB_NONE;
+ }
+ if (XcbKeySymbols != XCB_NONE) {
+ xcb_key_symbols_free(XcbKeySymbols);
+ XcbKeySymbols = XCB_NONE;
+ }
+
+ if (Connection) {
+ xcb_flush(Connection);
+ xcb_disconnect(Connection);
+ Connection = NULL;
+ }
+}
diff --git a/video.h b/video.h
new file mode 100644
index 0000000..246dc87
--- /dev/null
+++ b/video.h
@@ -0,0 +1,62 @@
+///
+/// @file video.h @brief Video module header file
+///
+/// Copyright (c) 2012 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: bc7bb8f7e869de413ebadbe58e4af1b603b6531e $
+//////////////////////////////////////////////////////////////////////////////
+
+/// @addtogroup Video
+/// @{
+
+ /// C callback feed key press
+extern void FeedKeyPress(const char *, const char *, int, int);
+
+ /// Show window.
+extern void VideoWindowShow(void);
+
+ /// Hide window.
+extern void VideoWindowHide(void);
+
+ /// Clear window.
+extern void VideoWindowClear(void);
+
+ /// Poll video events.
+extern void VideoPollEvents(int);
+
+ /// Get player window id.
+extern int VideoGetPlayWindow(void);
+
+ /// Set Osd 3D Mode
+extern void VideoSetOsd3DMode(int);
+
+ /// Draw an OSD ARGB image.
+extern void VideoDrawARGB(int, int, int, int, const uint8_t *);
+
+ /// Get OSD size.
+extern void VideoGetOsdSize(int *, int *);
+
+ /// Set video geometry.
+extern void VideoSetGeometry(const char *);
+
+ /// Set video color key.
+extern void VideoSetColorKey(uint32_t);
+
+extern int VideoInit(const char *); ///< Setup video module.
+extern void VideoExit(void); ///< Cleanup and exit video module.
+
+/// @}