diff --git a/services_live.h b/services_live.h
new file mode 100755
index 0000000..a9985af
--- /dev/null
+++ b/services_live.h
@@ -0,0 +1,46 @@
+#ifndef __LIVE_SERVICES_LIVE_H
+#define __LIVE_SERVICES_LIVE_H
+
+class cLiveImageProvider {
+ public:
+ virtual std::string getImageUrl(const std::string &imagePath, bool fullPath = true) = 0;
+ ///< input: imagePath on file system.
+ ///< if fullPath = true: e.g. /var/cache/vdr/plugins/tvscraper/movies/300803_poster.jpg
+ ///< if fullPath = false: e.g. movies/300803_poster.jpg
+ ///< for images returned by the "old" tvscraper interface: use the default fullPath = true
+ ///< output:
+ ///< in case of no error:
+ ///< URL to the image, e.g. http://rpi.fritz.box:8008/tvscraper/movies/300803_poster.jpg
+ ///< in case of errors:
+ ///< If fullPath = true, the input (imagePath) will be returned
+ ///< If fullPath = false, the full file system path will be returned (if possible)
+ ///< Also, an error message will be written to the system log
+ ///< possible errors:
+ ///< missing server url. This url must be provided to live with -u URL, --url=URL
+ ///< missing directory for scraper images:
+ ///< tvscraper makes this directory available to live with the service interface
+ ///< for others, e.g. scraper2vdr: provide this to live with -t
, --tvscraperimages=
+ ///< image path %s does not start with %s: Will only occur if fullPath == true
+ ///< imagePath does not start with directory for scraper images.
+ ///< example:
+ ///< directory for scraper images: /var/cache/vdr/plugins/tvscraper/
+ ///< imagePath = /tmp/test.img
+ ///< In this example, this error will occur.
+ ///< This is a restriction of live, implemented for security reasons:
+ ///< Only files under the scraper images path will be delivered
+ virtual ~cLiveImageProvider() {}
+};
+// service to return cLiveImageProvider instance
+class cGetLiveImageProvider {
+public:
+ cPlugin *call(cPlugin *pLive = NULL) {
+ if (!pLive) return cPluginManager::CallFirstService("GetLiveImageProvider", this);
+ return pLive->Service("GetLiveImageProvider", this)?pLive:NULL;
+ }
+//IN: Use constructor to set these values
+// No input parameters
+//OUT
+ std::unique_ptr m_liveImageProvider;
+};
+
+#endif // __LIVE_SERVICES_LIVE_H
diff --git a/services_tvscraper.h b/services_tvscraper.h
new file mode 100755
index 0000000..7b56d9e
--- /dev/null
+++ b/services_tvscraper.h
@@ -0,0 +1,362 @@
+#ifndef __TVSCRAPER_SERVICES_H
+#define __TVSCRAPER_SERVICES_H
+#include
+#include
+#include
+#include
+#include
+#include
+
+/*********************************************************************
+* Helper Structures
+*********************************************************************/
+enum tvType {
+ tSeries,
+ tMovie,
+ tNone,
+};
+
+class cTvMedia {
+public:
+ cTvMedia(void) {
+ path = "";
+ width = height = 0;
+ };
+ std::string path;
+ int width;
+ int height;
+};
+
+class cActor {
+public:
+ cActor(void) {
+ name = "";
+ role = "";
+ };
+ std::string name;
+ std::string role;
+ cTvMedia actorThumb;
+};
+
+class cEpisode {
+public:
+ cEpisode(void) {
+ number = 0;
+ season = 0;
+ name = "";
+ firstAired = "";
+ guestStars = "";
+ overview = "";
+ rating = 0.0;
+ };
+ int number;
+ int season;
+ std::string name;
+ std::string firstAired;
+ std::string guestStars;
+ std::string overview;
+ float rating;
+ cTvMedia episodeImage;
+};
+
+/*********************************************************************
+* Data Structures for Service Calls
+*********************************************************************/
+
+// Data structure for service "GetEventType"
+class ScraperGetEventType {
+public:
+ ScraperGetEventType(void) {
+ event = NULL;
+ recording = NULL;
+ type = tNone;
+ movieId = 0;
+ seriesId = 0;
+ episodeId = 0;
+ };
+// in
+ const cEvent *event; // check type for this event
+ const cRecording *recording; // or for this recording
+//out
+ tvType type; //typeSeries or typeMovie
+ int movieId;
+ int seriesId;
+ int episodeId;
+};
+
+// Data structures for full series and episode information
+// service "GetMovie"
+class cMovie {
+public:
+ cMovie(void) {
+ title = "";
+ originalTitle = "";
+ tagline = "";
+ overview = "";
+ adult = false;
+ collectionName = "";
+ budget = 0;
+ revenue = 0;
+ genres = "";
+ homepage = "";
+ releaseDate = "";
+ runtime = 0;
+ popularity = 0.0;
+ voteAverage = 0.0;
+ };
+//IN
+ int movieId; // movieId fetched from ScraperGetEventType
+//OUT
+ std::string title;
+ std::string originalTitle;
+ std::string tagline;
+ std::string overview;
+ bool adult;
+ std::string collectionName;
+ int budget;
+ int revenue;
+ std::string genres;
+ std::string homepage;
+ std::string releaseDate;
+ int runtime;
+ float popularity;
+ float voteAverage;
+ cTvMedia poster;
+ cTvMedia fanart;
+ cTvMedia collectionPoster;
+ cTvMedia collectionFanart;
+ std::vector actors;
+};
+
+// Data structure for full series and episode information
+// service "GetSeries"
+class cSeries {
+public:
+ cSeries(void) {
+ seriesId = 0;
+ episodeId = 0;
+ name = "";
+ overview = "";
+ firstAired = "";
+ network = "";
+ genre = "";
+ rating = 0.0;
+ status = "";
+ };
+//IN
+ int seriesId; // seriesId fetched from ScraperGetEventType
+ int episodeId; // episodeId fetched from ScraperGetEventType
+//OUT
+ std::string name;
+ std::string overview;
+ std::string firstAired;
+ std::string network;
+ std::string genre;
+ float rating;
+ std::string status;
+ cEpisode episode;
+ std::vector actors;
+ std::vector posters;
+ std::vector banners;
+ std::vector fanarts;
+ cTvMedia seasonPoster;
+};
+
+// Data structure for service "GetPosterBannerV2"
+class ScraperGetPosterBannerV2 {
+public:
+ ScraperGetPosterBannerV2(void) {
+ type = tNone;
+ event = NULL;
+ recording = NULL;
+ };
+// in
+ const cEvent *event; // check type for this event
+ const cRecording *recording; // check type for this recording
+//out
+ tvType type; //typeSeries or typeMovie
+ cTvMedia poster;
+ cTvMedia banner;
+};
+
+// Data structure for service "GetPoster"
+class ScraperGetPoster {
+public:
+// in
+ const cEvent *event; // check type for this event
+ const cRecording *recording; // or for this recording
+//out
+ cTvMedia poster;
+};
+
+// Data structure for service "GetPosterThumb"
+class ScraperGetPosterThumb {
+public:
+// in
+ const cEvent *event; // check type for this event
+ const cRecording *recording; // or for this recording
+//out
+ cTvMedia poster;
+};
+
+// NEW interface, used by live =========================================================
+
+// Data structure for service "GetScraperImageDir"
+class cGetScraperImageDir {
+public:
+//in: nothing, no input required
+//out
+ std::string scraperImageDir; // this was given to the plugin with --dir, or is the default cache directory for the plugin. It will always end with a '/'
+ cPlugin *call(cPlugin *pScraper = NULL) {
+ if (!pScraper) return cPluginManager::CallFirstService("GetScraperImageDir", this);
+ else return pScraper->Service("GetScraperImageDir", this)?pScraper:NULL;
+ }
+};
+
+// Data structure for service "GetScraperUpdateTimes"
+class cGetScraperUpdateTimes {
+public:
+//in: nothing, no input required
+//out
+ time_t m_EPG_UpdateTime;
+ time_t m_recordingsUpdateTime;
+ cPlugin *call(cPlugin *pScraper = NULL) {
+ if (!pScraper) return cPluginManager::CallFirstService("GetScraperUpdateTimes", this);
+ else return pScraper->Service("GetScraperUpdateTimes", this)?pScraper:NULL;
+ }
+};
+
+// ===========================================================================
+// the following enums & classes are for cScraperVideo (see below)
+// ===========================================================================
+
+enum class eCharacterType {
+ director = 1, // Regisseur
+ writer = 2, // Autor
+ actor = 3,
+ guestStar = 4,
+ crew = 5,
+ creator = 6,
+ producer = 7,
+ showrunner = 8,
+ musicalGuest = 9,
+ host = 10,
+ executiveProducer = 11,
+ screenplay = 21, // Drehbuchautor
+ originalMusicComposer = 31, // Komponist
+ others = 51,
+};
+
+class cCharacter {
+ public:
+ virtual eCharacterType getType() = 0;
+ virtual const std::string &getPersonName() = 0; // "real name" of the person
+ virtual const std::string &getCharacterName() = 0; // name of character in video
+ virtual const cTvMedia &getImage() = 0;
+ virtual ~cCharacter() {}
+};
+
+// ===========================================================================
+// the following enums & classes are for cScraperVideo->getImage(...) (see below)
+// you can use them to select the desired orientation and "image level" (see below) of the image returned
+// ===========================================================================
+
+enum class eOrientation {
+ none = 0,
+ banner = 1,
+ landscape = 2,
+ portrait = 3,
+};
+
+// class cOrientations: combine orientations, with priorities.
+// Example: You want a landscape, but, as fallback, also accept a banner. If none of these is available, even a portrait is better than no image:
+// cOrientations(eOrientation::landscape, eOrientation::banner, eOrientation::portrait);
+class cOrientations {
+ public:
+ cOrientations(eOrientation first = eOrientation::none, eOrientation second = eOrientation::none, eOrientation third = eOrientation::none) {
+ m_orientations = (int)first | (int(second)<<3) | (int(third)<<6);
+ }
+ private:
+ friend class cOrientationsInt;
+ int m_orientations;
+};
+
+enum class eImageLevel {
+ none = 0,
+ episodeMovie = 1, // for TV Shows: episode. For movies: movie (itself)
+ seasonMovie = 2, // for TV Shows: season. For movies: movie (itself)
+ tvShowCollection = 3, // for TV Shows: itself. For movies: collection
+ anySeasonCollection = 4, // for TV Shows: any season. For movies: collection
+};
+
+// class cImageLevels: combine image levels, with priorities.
+// example: image for event/recording: cImageLevels(eImageLevel::episodeMovie, eImageLevel::seasonMovie, eImageLevel::TV_ShowCollection, eImageLevel::anySeasonCollection)
+// if you don't want the episode still: cImageLevels(eImageLevel::seasonMovie, eImageLevel::TV_ShowCollection, eImageLevel::anySeasonCollection)
+class cImageLevels {
+ public:
+ cImageLevels(eImageLevel first = eImageLevel::none, eImageLevel second = eImageLevel::none, eImageLevel third = eImageLevel::none, eImageLevel forth = eImageLevel::none){
+ m_imageLevels = (int)first | (int(second)<<3) | (int(third)<<6) | (int(forth)<<9);
+ }
+ private:
+ friend class cImageLevelsInt;
+ int m_imageLevels;
+};
+
+// The following class will be returned by the service handler method cGetScraperVideo
+// note: the event/recording object used to create an instance must be valid during all method calls.
+// VDR will grant such validity for about 5 seconds.
+
+// if you don't need a specific information, just pass NULL
+// parameter fullPath: If this is false, the image paths are relative to the path returned by "GetScraperImageDir"
+class cScraperVideo
+{
+ public:
+// during creation of this instance, a movie/series/episode is identified
+// with the following methods you can request the "IDs of the identified object"
+ virtual tvType getVideoType() = 0; // if this is tNone, nothing was identified by scraper. Still, some information (image, duration deviation ...) might be available
+ virtual int getDbId() = 0; // if > 0: TMDB (themoviedb) ID; if < 0: tvdb (thetvdb) ID
+ virtual int getEpisodeNumber() = 0; // return 0 if episode was not identified
+ virtual int getSeasonNumber() = 0; // return 0 if episode was not identified
+
+// getOverview provides the "most important" attributes
+ virtual bool getOverview(std::string *title, std::string *episodeName, std::string *releaseDate, int *runtime, std::string *imdbId, int *collectionId, std::string *collectionName = NULL) = 0; // return false if no scraper data is available
+
+// "single image, or several images"
+ virtual cTvMedia getImage(cImageLevels imageLevels = cImageLevels(), cOrientations imageOrientations = cOrientations(), bool fullPath = true) = 0;
+ virtual std::vector getImages(eOrientation orientation, int maxImages = 3, bool fullPath = true) = 0;
+
+// "characters, including actors"
+ virtual std::vector> getCharacters(bool fullPath = true) = 0;
+
+// other attributes, available even if getVideoType() == tNone
+ virtual int getDurationDeviation() = 0;
+ virtual int getHD() = 0; // 0: SD. 1: HD. 2: UHD
+ virtual int getLanguage() = 0; // return -1 in case of errors
+
+// the other attributes of a movie or TV show:
+// note: runtime will be provided here only for movies. For tv shows, the runtime is provided with getEpisode
+ virtual bool getMovieOrTv(std::string *title, std::string *originalTitle, std::string *tagline, std::string *overview, std::vector *genres, std::string *homepage, std::string *releaseDate, bool *adult, int *runtime, float *popularity, float *voteAverage, int *voteCount, std::vector *productionCountries, std::string *imdbId, int *budget, int *revenue, int *collectionId, std::string *collectionName, std::string *status, std::vector *networks, int *lastSeason) = 0;
+
+// episode attributes. return true if getVideoType() == tSeries && episode is identified
+ virtual bool getEpisode(std::string *name, std::string *overview, int *absoluteNumber, std::string *firstAired, int *runtime, float *voteAverage, int *voteCount, std::string *imdbId) = 0;
+ virtual ~cScraperVideo() {}
+};
+
+// service to return cScraperVideo instance
+class cGetScraperVideo {
+public:
+ cGetScraperVideo(const cEvent *event = NULL, const cRecording *recording = NULL):
+ m_event(event), m_recording(recording) { }
+
+ cPlugin *call(cPlugin *pScraper = NULL) {
+ if (!pScraper) return cPluginManager::CallFirstService("GetScraperVideo", this);
+ return pScraper->Service("GetScraperVideo", this)?pScraper:NULL;
+ }
+//IN: Use constructor to set these values
+ const cEvent *m_event; // must be NULL for recordings ; provide data for this event
+ const cRecording *m_recording; // must be NULL for events ; or for this recording
+//OUT
+ std::unique_ptr m_scraperVideo;
+};
+
+#endif // __TVSCRAPER_SERVICES_H
diff --git a/vnsiclient.c b/vnsiclient.c
old mode 100644
new mode 100755
index 61e1062..b92b870
--- a/vnsiclient.c
+++ b/vnsiclient.c
@@ -570,6 +570,9 @@ bool cVNSIClient::processRequest(cRequestPacket &req)
result = processRECORDINGS_GetEdl(req);
break;
+ case VNSI_RECORDINGS_SETLASTPLAYEDPOSITION:
+ result = processRECORDINGS_SetLastPlayedPosition(req);
+ break;
/** OPCODE 120 - 139: VNSI network functions for epg access and manipulating */
case VNSI_EPG_GETFORCHANNEL:
@@ -2302,6 +2305,98 @@ bool cVNSIClient::processRECORDINGS_GetList(cRequestPacket &req) /* OPCODE 102 *
uint32_t uid = cRecordingsCache::GetInstance().Register(recording, false);
resp.add_U32(uid);
+ if (m_protocolVersion >= 14)
+ {
+ std::string posterUrl = "";
+ std::string fanartUrl = "";
+ int season = 0;
+ int episode = 0;
+ std::string firstAired = "";
+
+ // first try scraper2vdr
+ static cPlugin *pScraper = cPluginManager::GetPlugin("scraper2vdr");
+ if (!pScraper) // if it doesn't exit, try tvscraper
+ pScraper = cPluginManager::GetPlugin("tvscraper");
+
+ ScraperGetEventType call;
+ call.recording = recording;
+ int seriesId = 0;
+ int episodeId = 0;
+ int movieId = 0;
+
+ if (pScraper->Service("GetEventType", &call)) {
+ seriesId = call.seriesId;
+ episodeId = call.episodeId;
+ movieId = call.movieId;
+ }
+
+ if (seriesId > 0) {
+ cSeries series;
+ series.seriesId = seriesId;
+ series.episodeId = episodeId;
+ if (pScraper->Service("GetSeries", &series)) {
+ firstAired = series.firstAired;
+ if (series.posters.size() > 0)
+ posterUrl = series.posters[0].path;
+ if (series.fanarts.size() > 0)
+ fanartUrl = series.fanarts[0].path;
+
+ // Episodes
+ if (series.episode.number > 0) {
+ season = series.episode.season;
+ episode = series.episode.number;
+ firstAired = series.episode.firstAired;
+
+ std::string episodeImageUrl = series.episode.episodeImage.path;
+ if (!episodeImageUrl.empty())
+ posterUrl = episodeImageUrl;
+
+ }
+ }
+ } else if (movieId > 0) {
+ cMovie movie;
+ movie.movieId = movieId;
+ if (pScraper->Service("GetMovie", &movie)) {
+ posterUrl = movie.poster.path;
+ fanartUrl = movie.fanart.path;
+ firstAired = movie.releaseDate;
+ }
+ }
+
+ // Try to use Live plugin service to return urls for images
+ // This is needed when VDR and Kodi are in different devices
+ cGetLiveImageProvider getLiveImageProvider;
+ bool serviceAvailable = getLiveImageProvider.call() != NULL;
+
+ if (serviceAvailable) {
+ if (!posterUrl.empty())
+ posterUrl = getLiveImageProvider.m_liveImageProvider->getImageUrl(posterUrl);
+ if (!fanartUrl.empty())
+ fanartUrl = getLiveImageProvider.m_liveImageProvider->getImageUrl(fanartUrl);
+ }
+
+ resp.add_String(posterUrl.c_str());
+ resp.add_String(fanartUrl.c_str());
+ resp.add_U32(season);
+ resp.add_U32(episode);
+ resp.add_String(firstAired.c_str());
+
+ cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording());
+ int resumePosition = ResumeFile.Read();
+ int fps = recording->FramesPerSecond();
+
+ // If ResumePosition exists add it to video info
+ if (resumePosition > 1)
+ {
+ int resumePositionSecs = resumePosition / fps;
+ resp.add_U32(resumePositionSecs);
+ }
+ else // Unwatched = set position 0
+ {
+ resp.add_U32(0);
+ }
+ }
+
free(fullname);
}
@@ -2475,6 +2570,49 @@ bool cVNSIClient::processRECORDINGS_GetEdl(cRequestPacket &req) /* OPCODE 105 */
return true;
}
+bool cVNSIClient::processRECORDINGS_SetLastPlayedPosition(cRequestPacket &req) /* OPCODE 106 */
+{
+ cString recName;
+ const cRecording* recording = nullptr;
+
+ uint32_t uid = req.extract_U32();
+ recording = cRecordingsCache::GetInstance().Lookup(uid);
+ int resumePosition = req.extract_U32();
+
+ DEBUGLOG("SetLastPlayedPosition called: ResumePosition from Kodi %i, Recording \"%s\"", resumePosition, recording->FileName());
+
+ cResponsePacket resp;
+ resp.init(req.getRequestID());
+
+ if (recording)
+ {
+ int fps = recording->FramesPerSecond();
+ cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording());
+
+ if (resumePosition == 0)
+ {
+ ResumeFile.Delete();
+ DEBUGLOG("SetLastPlayedPosition: ResumePosition 0, ResumeFile deleted, Recording \"%s\" ", recording->FileName());
+ }
+ else if (resumePosition > 0)
+ {
+ int resumePositionFrames = fps * resumePosition;
+ ResumeFile.Save(resumePositionFrames);
+ DEBUGLOG("SetLastPlayedPosition: ResumePosition set to %i, Recording \"%s\"", resumePosition, recording->FileName());
+ }
+ resp.add_U32(VNSI_RET_OK);
+ }
+ else
+ {
+ ERRORLOG("Error in recording name \"%s\"", (const char*)recName);
+ resp.add_U32(VNSI_RET_DATAUNKNOWN);
+ }
+
+ resp.finalise();
+ m_socket.write(resp.getPtr(), resp.getLen());
+
+ return true;
+}
/** OPCODE 120 - 139: VNSI network functions for epg access and manipulating */
@@ -2610,6 +2748,107 @@ bool cVNSIClient::processEPG_GetForChannel(cRequestPacket &req) /* OPCODE 120 */
resp.add_String(m_toUTF8.Convert(thisEventSubTitle));
resp.add_String(m_toUTF8.Convert(thisEventDescription));
+ if (m_protocolVersion >= 14)
+ {
+ std::string posterUrl = "";
+ int season = 0;
+ int episode = 0;
+ std::string firstAired = "";
+ int rating = 0;
+ std::string originalTitle = "";
+ std::string actors = "";
+
+ // first try scraper2vdr
+ static cPlugin *pScraper = cPluginManager::GetPlugin("scraper2vdr");
+ if (!pScraper) // if it doesn't exit, try tvscraper
+ pScraper = cPluginManager::GetPlugin("tvscraper");
+
+ ScraperGetEventType call;
+ call.event = event;
+ int seriesId = 0;
+ int episodeId = 0;
+ int movieId = 0;
+
+ if (pScraper->Service("GetEventType", &call)) {
+ seriesId = call.seriesId;
+ episodeId = call.episodeId;
+ movieId = call.movieId;
+ }
+
+ if (seriesId > 0) {
+ cSeries series;
+ series.seriesId = seriesId;
+ series.episodeId = episodeId;
+ if (pScraper->Service("GetSeries", &series)) {
+ rating = (int)series.rating;
+ firstAired = series.firstAired;
+ if (series.posters.size() > 0)
+ posterUrl = series.posters[0].path;
+
+ // Episodes
+ if (series.episode.number > 0) {
+ season = series.episode.season;
+ episode = series.episode.number;
+ rating = (int)series.episode.rating;
+ firstAired = series.episode.firstAired;
+
+ std::string episodeImageUrl = series.episode.episodeImage.path;
+ if (!episodeImageUrl.empty())
+ posterUrl = episodeImageUrl;
+
+ }
+
+ // Actors
+ bool firstActor = true;
+ for (unsigned int i = 0; i < series.actors.size(); i++) {
+ if (!firstActor)
+ actors += ", " + series.actors[i].name;
+ else
+ actors += series.actors[i].name;
+ firstActor = false;
+ }
+ }
+ } else if (movieId > 0) {
+ cMovie movie;
+ movie.movieId = movieId;
+ if (pScraper->Service("GetMovie", &movie)) {
+ posterUrl = movie.poster.path;
+ firstAired = movie.releaseDate;
+ rating = (int)movie.voteAverage;
+ originalTitle = movie.originalTitle;
+
+ // Actors
+ bool firstActor = true;
+ for (unsigned int i = 0; i < movie.actors.size(); i++) {
+ if (!firstActor)
+ actors += ", " + movie.actors[i].name;
+ else
+ actors += movie.actors[i].name;
+ firstActor = false;
+ }
+ }
+ }
+
+ // Try to use Live plugin service to return urls for images
+ // This is needed when VDR and Kodi are in different devices
+ cGetLiveImageProvider getLiveImageProvider;
+ bool serviceAvailable = getLiveImageProvider.call() != NULL;
+
+ if (serviceAvailable) {
+ if (!posterUrl.empty())
+ posterUrl = getLiveImageProvider.m_liveImageProvider->getImageUrl(posterUrl);
+ }
+
+ resp.add_String(posterUrl.c_str());
+ resp.add_U32(season);
+ resp.add_U32(episode);
+ resp.add_String(firstAired.c_str());
+ resp.add_U32(rating);
+ resp.add_String(originalTitle.c_str());
+ resp.add_String(actors.c_str());
+
+ }
+
atLeastOneEvent = true;
}
@@ -3300,4 +3539,4 @@ cString cVNSIClient::CreatePiconRef(const cChannel* channel)
hash);
return serviceref;
-}
+}
\ No newline at end of file
diff --git a/vnsiclient.h b/vnsiclient.h
old mode 100644
new mode 100755
index 08b8bf0..7bd0700
--- a/vnsiclient.h
+++ b/vnsiclient.h
@@ -43,6 +43,9 @@
#include
#include
+#include "services_tvscraper.h"
+#include "services_live.h"
+
#define VNSI_EPG_AGAIN 1
#define VNSI_EPG_PAUSE 2
@@ -195,6 +198,7 @@ class cVNSIClient : public cThread
bool processRECORDINGS_Delete(cRequestPacket &r);
bool processRECORDINGS_Move(cRequestPacket &r);
bool processRECORDINGS_GetEdl(cRequestPacket &r);
+ bool processRECORDINGS_SetLastPlayedPosition(cRequestPacket &r);
bool processRECORDINGS_DELETED_Supported(cRequestPacket &r);
bool processRECORDINGS_DELETED_GetCount(cRequestPacket &r);
bool processRECORDINGS_DELETED_GetList(cRequestPacket &r);
diff --git a/vnsicommand.h b/vnsicommand.h
old mode 100644
new mode 100755
index 6b3e2be..7c85fdc
--- a/vnsicommand.h
+++ b/vnsicommand.h
@@ -26,7 +26,7 @@
#pragma once
/** Current VNSI Protocol Version number */
-#define VNSI_PROTOCOLVERSION 13
+#define VNSI_PROTOCOLVERSION 14
/** Start of RDS support protocol Version */
#define VNSI_RDS_PROTOCOLVERSION 8
@@ -111,6 +111,7 @@
#define VNSI_RECORDINGS_RENAME 103
#define VNSI_RECORDINGS_DELETE 104
#define VNSI_RECORDINGS_GETEDL 105
+#define VNSI_RECORDINGS_SETLASTPLAYEDPOSITION 106
/* OPCODE 120 - 139: VNSI network functions for epg access and manipulating */
#define VNSI_EPG_GETFORCHANNEL 120