From 39e0fc535c82ed83b7a3d08385a6cd5e2ca8141d Mon Sep 17 00:00:00 2001 From: Dis90 Date: Tue, 8 Mar 2022 20:25:21 +0200 Subject: [PATCH] Support for backend artwork, extra info and resumepoints Also bump protocol version to 14 --- .../resource.language.en_gb/strings.po | 10 +- pvr.vdr.vnsi/resources/settings.xml | 4 + src/ClientInstance.cpp | 157 +++++++++++++++++- src/ClientInstance.h | 9 + src/Settings.cpp | 19 +++ src/Settings.h | 3 + src/vnsicommand.h | 3 +- 7 files changed, 199 insertions(+), 6 deletions(-) mode change 100644 => 100755 pvr.vdr.vnsi/resources/language/resource.language.en_gb/strings.po mode change 100644 => 100755 pvr.vdr.vnsi/resources/settings.xml mode change 100644 => 100755 src/ClientInstance.cpp mode change 100644 => 100755 src/ClientInstance.h mode change 100644 => 100755 src/Settings.cpp mode change 100644 => 100755 src/Settings.h diff --git a/pvr.vdr.vnsi/resources/language/resource.language.en_gb/strings.po b/pvr.vdr.vnsi/resources/language/resource.language.en_gb/strings.po old mode 100644 new mode 100755 index de7b8048..e0c914f9 --- a/pvr.vdr.vnsi/resources/language/resource.language.en_gb/strings.po +++ b/pvr.vdr.vnsi/resources/language/resource.language.en_gb/strings.po @@ -232,7 +232,15 @@ msgctxt "#30050" msgid "Read chunksize for recordings" msgstr "" -#empty strings from id 30051 to 30099 +msgctxt "#30051" +msgid "Store recording resume location in VDR" +msgstr "" + +msgctxt "#30052" +msgid "When disabled resume location and watched status will be managed only in Kodi" +msgstr "" + +#empty strings from id 30053 to 30099 msgctxt "#30100" msgid "VDR OSD" diff --git a/pvr.vdr.vnsi/resources/settings.xml b/pvr.vdr.vnsi/resources/settings.xml old mode 100644 new mode 100755 index de634a2c..6c11d224 --- a/pvr.vdr.vnsi/resources/settings.xml +++ b/pvr.vdr.vnsi/resources/settings.xml @@ -79,6 +79,10 @@ 65536 + + false + + diff --git a/src/ClientInstance.cpp b/src/ClientInstance.cpp old mode 100644 new mode 100755 index 154acb97..66f4c745 --- a/src/ClientInstance.cpp +++ b/src/ClientInstance.cpp @@ -217,6 +217,12 @@ PVR_ERROR CVNSIClientInstance::GetCapabilities(kodi::addon::PVRCapabilities& cap capabilities.SetSupportsRecordingsLifetimeChange(false); capabilities.SetSupportsDescrambleInfo(false); + if (GetProtocol() >= 14) + { + capabilities.SetSupportsRecordingPlayCount(CVNSISettings::Get().GetBackendResume()); + capabilities.SetSupportsLastPlayedPosition(CVNSISettings::Get().GetBackendResume()); + } + return PVR_ERROR_NO_ERROR; } @@ -571,8 +577,32 @@ PVR_ERROR CVNSIClientInstance::GetEPGForChannel(int channelUid, tag.SetTitle(vresp->extract_String()); tag.SetPlotOutline(vresp->extract_String()); tag.SetPlot(vresp->extract_String()); - tag.SetOriginalTitle(""); - tag.SetCast(""); + + if (GetProtocol() >= 14) + { + tag.SetIconPath(vresp->extract_String()); + int season = vresp->extract_U32(); + int episode = vresp->extract_U32(); + if (season > 0) + tag.SetSeriesNumber(season); + if (episode > 0) + tag.SetEpisodeNumber(episode); + tag.SetFirstAired(vresp->extract_String()); + tag.SetStarRating(vresp->extract_U32()); + tag.SetOriginalTitle(vresp->extract_String()); + tag.SetCast(vresp->extract_String()); + tag.SetDirector(vresp->extract_String()); + tag.SetWriter(vresp->extract_String()); + tag.SetIMDBNumber(vresp->extract_String()); + } + else + { + tag.SetSeriesNumber(EPG_TAG_INVALID_SERIES_EPISODE); + tag.SetEpisodeNumber(EPG_TAG_INVALID_SERIES_EPISODE); + tag.SetOriginalTitle(""); + tag.SetCast(""); + } + tag.SetDirector(""); tag.SetWriter(""); tag.SetYear(0); @@ -580,8 +610,6 @@ PVR_ERROR CVNSIClientInstance::GetEPGForChannel(int channelUid, if (!tag.GetPlotOutline().empty()) tag.SetEpisodeName(tag.GetPlotOutline()); tag.SetFlags(EPG_TAG_FLAG_UNDEFINED); - tag.SetSeriesNumber(EPG_TAG_INVALID_SERIES_EPISODE); - tag.SetEpisodeNumber(EPG_TAG_INVALID_SERIES_EPISODE); tag.SetEpisodePartNumber(EPG_TAG_INVALID_SERIES_EPISODE); results.Add(tag); @@ -654,6 +682,9 @@ PVR_ERROR CVNSIClientInstance::GetAvailableRecordings(kodi::addon::PVRRecordings return PVR_ERROR_UNKNOWN; } + m_lastPlayed.clear(); + m_playCount.clear(); + std::string strRecordingId; while (vresp->getRemainingLength() >= 5 * 4 + 5) { @@ -693,6 +724,38 @@ PVR_ERROR CVNSIClientInstance::GetAvailableRecordings(kodi::addon::PVRRecordings tag.SetPlot(vresp->extract_String()); tag.SetDirectory(vresp->extract_String()); tag.SetRecordingId(std::to_string(vresp->extract_U32())); + + if (GetProtocol() >= 14) + { + tag.SetThumbnailPath(vresp->extract_String()); + tag.SetFanartPath(vresp->extract_String()); + int season = vresp->extract_U32(); + int episode = vresp->extract_U32(); + if (season > 0) + tag.SetSeriesNumber(season); + if (episode > 0) + tag.SetEpisodeNumber(episode); + + tag.SetFirstAired(vresp->extract_String()); + + int LastPlayedPosition = vresp->extract_U32(); + + if (CVNSISettings::Get().GetBackendResume()) + { + tag.SetPlayCount(0); + tag.SetLastPlayedPosition(LastPlayedPosition); + if (tag.GetLastPlayedPosition() > 0) + { + if (tag.GetLastPlayedPosition() >= tag.GetDuration() - 60) + { + tag.SetPlayCount(1); + tag.SetLastPlayedPosition(0); + } + } + m_lastPlayed[std::stoi(tag.GetRecordingId())] = tag.GetLastPlayedPosition(); + m_playCount[std::stoi(tag.GetRecordingId())] = tag.GetPlayCount(); + } + } results.Add(tag); } @@ -935,6 +998,92 @@ PVR_ERROR CVNSIClientInstance::GetRecordingEdl(const kodi::addon::PVRRecording& } } +// Code for GetRecordingLastPlayedPosition, SetRecordingLastPlayedPosition and SetRecordingPlayCount +// mostly taken from https://github.com/kodi-pvr/pvr.nextpvr/blob/Matrix/src/Recordings.cpp +PVR_ERROR CVNSIClientInstance::GetRecordingLastPlayedPosition(const kodi::addon::PVRRecording& recording, int& position) +{ + position = m_lastPlayed[std::stoi(recording.GetRecordingId())]; + if (position == recording.GetDuration()) + position = 0; + return PVR_ERROR_NO_ERROR; +} + +//============================================================================== +/// SetRecordingLastPlayedPosition will be called when +/// Set watched - postion = 0, play count incremented. Note it is not called if +/// the watched positions is already 0 +/// Resume reset - position = 0 +/// Recording start position = 0 at start of file +/// Recording end actual position or when end is in ignorepercentatend zone the +/// position is -1 with play count incremented +/// Set unwatched - Not called by core +/// +PVR_ERROR CVNSIClientInstance::SetRecordingLastPlayedPosition(const kodi::addon::PVRRecording& recording, int lastplayedposition) +{ + try + { + cRequestPacket vrp; + vrp.init(VNSI_RECORDINGS_SETLASTPLAYEDPOSITION); + + int originalPosition = lastplayedposition; + int current = m_playCount[std::stoi(recording.GetRecordingId())]; + if (recording.GetPlayCount() > current && lastplayedposition == 0) + { + // Kodi rolled the play count but didn't send EOF + lastplayedposition = recording.GetDuration(); + m_playCount[std::stoi(recording.GetRecordingId())] = recording.GetPlayCount(); + } + + if ( m_lastPlayed[std::stoi(recording.GetRecordingId())] != lastplayedposition ) + { + if (lastplayedposition == -1) + { + lastplayedposition = recording.GetDuration(); + } + } + vrp.add_U32(std::stoi(recording.GetRecordingId())); + vrp.add_U32(lastplayedposition); + + auto vresp = ReadResult(&vrp); + if (vresp == nullptr || vresp->noResponse()) + { + kodi::Log(ADDON_LOG_ERROR, "%s - Can't get response packed", __func__); + return PVR_ERROR_UNKNOWN; + } + + return PVR_ERROR_NO_ERROR; + } + catch (std::exception e) + { + kodi::Log(ADDON_LOG_ERROR, "%s - %s", __func__, e.what()); + return PVR_ERROR_SERVER_ERROR; + } +} + +//============================================================================== +/// SetRecordingPlayCount will be called when +/// Set watched - play count increases +/// Set unwatched - count set to 0, +/// Recording start - no change in count +/// Recording end when end is in playcountminimumpercent zone then the count +/// is incremented +/// +PVR_ERROR CVNSIClientInstance::SetRecordingPlayCount(const kodi::addon::PVRRecording& recording, int count) +{ + int current = m_playCount[std::stoi(recording.GetRecordingId())]; + kodi::Log(ADDON_LOG_DEBUG, "Playcount %s %d %d", recording.GetTitle().c_str(), count, current); + if (count < current) + { + // unwatch count is zero. + SetRecordingLastPlayedPosition(recording, 0); + m_playCount[std::stoi(recording.GetRecordingId())] = count; + } + else + { + + } + return PVR_ERROR_NO_ERROR; +} /*******************************************/ /** PVR Timer Functions **/ diff --git a/src/ClientInstance.h b/src/ClientInstance.h old mode 100644 new mode 100755 index cd1c1709..2b182fc5 --- a/src/ClientInstance.h +++ b/src/ClientInstance.h @@ -92,6 +92,11 @@ class ATTR_DLL_LOCAL CVNSIClientInstance : public kodi::addon::CInstancePVRClien PVR_ERROR RenameRecording(const kodi::addon::PVRRecording& recording) override; PVR_ERROR GetRecordingEdl(const kodi::addon::PVRRecording& recording, std::vector& edl) override; + PVR_ERROR GetRecordingLastPlayedPosition(const kodi::addon::PVRRecording& recording, + int& position) override; + PVR_ERROR SetRecordingLastPlayedPosition(const kodi::addon::PVRRecording& recording, + int lastplayedposition) override; + PVR_ERROR SetRecordingPlayCount(const kodi::addon::PVRRecording& recording, int count) override; //--==----==----==----==----==----==----==----==----==----==----==----==----== @@ -175,4 +180,8 @@ class ATTR_DLL_LOCAL CVNSIClientInstance : public kodi::addon::CInstancePVRClien std::atomic m_running = {false}; std::thread m_thread; std::thread m_startInformThread; + + // update these at end of counting loop can be called during action + std::map m_lastPlayed; + std::map m_playCount; }; diff --git a/src/Settings.cpp b/src/Settings.cpp old mode 100644 new mode 100755 index 6e974025..3718bdcb --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -118,6 +118,15 @@ bool CVNSISettings::Load() m_iChunkSize = DEFAULT_CHUNKSIZE; } + // Read setting "backendresume" from settings.xml + if (!kodi::addon::CheckSettingBoolean("backendresume", m_bBackendResume)) + { + // If setting is unknown fallback to defaults + kodi::Log(ADDON_LOG_ERROR, + "Couldn't get 'backendresume' setting, falling back to 'false' as default"); + m_bBackendResume = DEFAULT_BACKENDRESUME; + } + return true; } @@ -195,6 +204,16 @@ ADDON_STATUS CVNSISettings::SetSetting(const std::string& settingName, settingValue.GetInt()); m_iChunkSize = settingValue.GetInt(); } + else if (settingName == "backendresume") + { + kodi::Log(ADDON_LOG_INFO, "Changed Setting 'backendresume' from %u to %u", + m_bBackendResume, settingValue.GetBoolean()); + if (m_bBackendResume != settingValue.GetBoolean()) + { + m_bBackendResume = settingValue.GetBoolean(); + return ADDON_STATUS_NEED_RESTART; + } + } return ADDON_STATUS_OK; } diff --git a/src/Settings.h b/src/Settings.h old mode 100644 new mode 100755 index 377ea37b..c27f1c40 --- a/src/Settings.h +++ b/src/Settings.h @@ -18,6 +18,7 @@ #define DEFAULT_TIMEOUT 3 #define DEFAULT_AUTOGROUPS false #define DEFAULT_CHUNKSIZE 65536 +#define DEFAULT_BACKENDRESUME false class ATTR_DLL_LOCAL CVNSISettings { @@ -38,6 +39,7 @@ class ATTR_DLL_LOCAL CVNSISettings int GetTimeshift() const { return m_iTimeshift; } const std::string& GetIconPath() const { return m_szIconPath; } int GetChunkSize() const { return m_iChunkSize; } + bool GetBackendResume() const { return m_bBackendResume; } private: CVNSISettings() = default; @@ -56,4 +58,5 @@ class ATTR_DLL_LOCAL CVNSISettings int m_iTimeshift = 1; std::string m_szIconPath; /*!< path to channel icons */ int m_iChunkSize = DEFAULT_CHUNKSIZE; + bool m_bBackendResume = DEFAULT_BACKENDRESUME; }; diff --git a/src/vnsicommand.h b/src/vnsicommand.h index b0e14fd4..1b32777d 100644 --- a/src/vnsicommand.h +++ b/src/vnsicommand.h @@ -9,7 +9,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 @@ -90,6 +90,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