diff options
-rw-r--r-- | .gitignore | 6 | ||||
-rw-r--r-- | Config.cpp | 86 | ||||
-rw-r--r-- | Config.h | 80 | ||||
-rw-r--r-- | ControlServer.cpp | 17 | ||||
-rw-r--r-- | ControlServer.h | 37 | ||||
-rw-r--r-- | Directory.cpp | 34 | ||||
-rw-r--r-- | Directory.h | 51 | ||||
-rw-r--r-- | Makefile | 193 | ||||
-rw-r--r-- | Media.cpp | 54 | ||||
-rw-r--r-- | Media.h | 62 | ||||
-rw-r--r-- | MediaContainer.cpp | 46 | ||||
-rw-r--r-- | MediaContainer.h | 63 | ||||
-rw-r--r-- | PVideo.cpp | 59 | ||||
-rw-r--r-- | PVideo.h | 67 | ||||
-rw-r--r-- | PlexHTTPRequestHandler.cpp | 182 | ||||
-rw-r--r-- | PlexHTTPRequestHandler.h | 63 | ||||
-rw-r--r-- | PlexHelper.cpp | 16 | ||||
-rw-r--r-- | PlexHelper.h | 17 | ||||
-rw-r--r-- | PlexReqHandlerFactory.cpp | 33 | ||||
-rw-r--r-- | PlexReqHandlerFactory.h | 28 | ||||
-rw-r--r-- | PlexServer.cpp | 41 | ||||
-rw-r--r-- | PlexServer.h | 81 | ||||
-rw-r--r-- | Plexservice.cpp | 205 | ||||
-rw-r--r-- | Plexservice.h | 65 | ||||
-rw-r--r-- | SubscriptionManager.cpp | 118 | ||||
-rw-r--r-- | SubscriptionManager.h | 79 | ||||
-rw-r--r-- | XmlObject.cpp | 87 | ||||
-rw-r--r-- | XmlObject.h | 34 | ||||
-rw-r--r-- | cPlexOsdItem.cpp | 34 | ||||
-rw-r--r-- | cPlexOsdItem.h | 39 | ||||
-rw-r--r-- | misc.h | 157 | ||||
-rw-r--r-- | play_service.h | 31 | ||||
-rw-r--r-- | player.c | 984 | ||||
-rw-r--r-- | player.h | 160 | ||||
-rw-r--r-- | plex.cpp | 1335 | ||||
-rw-r--r-- | plex.h | 222 | ||||
-rw-r--r-- | plexgdm.cpp | 130 | ||||
-rw-r--r-- | plexgdm.h | 75 | ||||
-rw-r--r-- | user.cpp | 31 | ||||
-rw-r--r-- | user.h | 42 | ||||
-rw-r--r-- | video.c | 775 | ||||
-rw-r--r-- | video.h | 62 |
42 files changed, 5981 insertions, 0 deletions
@@ -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() +{ +} + + +} + @@ -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 @@ -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! @@ -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; + } + +} + +} @@ -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 @@ -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; + } +} @@ -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. + +/// @} |