diff --git a/pvr.nextpvr/resources/instance-settings.xml b/pvr.nextpvr/resources/instance-settings.xml
index cc122d14..41c80f81 100644
--- a/pvr.nextpvr/resources/instance-settings.xml
+++ b/pvr.nextpvr/resources/instance-settings.xml
@@ -164,6 +164,11 @@
false
+
+ 2
+ false
+
+
@@ -220,6 +225,16 @@
false
+
+ 2
+ true
+
+
+
+ true
+
+
+
2
false
diff --git a/pvr.nextpvr/resources/language/resource.language.en_gb/strings.po b/pvr.nextpvr/resources/language/resource.language.en_gb/strings.po
index 02049f21..d529166b 100644
--- a/pvr.nextpvr/resources/language/resource.language.en_gb/strings.po
+++ b/pvr.nextpvr/resources/language/resource.language.en_gb/strings.po
@@ -441,3 +441,19 @@ msgstr ""
msgctxt "#30218"
msgid "Repeating (all episodes)"
msgstr ""
+
+msgctxt "#30219"
+msgid "Download recording poster"
+msgstr ""
+
+msgctxt "#30719"
+msgid "Download backend poster or extract thumbnail from recording"
+msgstr ""
+
+msgctxt "#30220"
+msgid "Enable multi-stream recordings"
+msgstr ""
+
+msgctxt "#30720"
+msgid "Extract extra recording metadata from backend"
+msgstr ""
diff --git a/src/InstanceSettings.cpp b/src/InstanceSettings.cpp
index 67b181d7..6682703c 100644
--- a/src/InstanceSettings.cpp
+++ b/src/InstanceSettings.cpp
@@ -125,7 +125,11 @@ void InstanceSettings::ReadFromAddon()
m_comskip = ReadBoolSetting("comskip", true);
- enum eHeartbeat m_heartbeat = ReadEnumSetting("heartbeat", eHeartbeat::Default);
+ m_multiStream = ReadBoolSetting("multistream", false);
+ if (m_multiStream)
+ m_recordingPoster = ReadBoolSetting("poster", true);
+
+c: enum eHeartbeat m_heartbeat = ReadEnumSetting("heartbeat", eHeartbeat::Default);
if (m_heartbeat == eHeartbeat::Default)
m_heartbeatInterval = DEFAULT_HEARTBEAT;
@@ -323,6 +327,12 @@ ADDON_STATUS InstanceSettings::SetValue(const std::string& settingName, const ko
return SetSetting(settingName, settingValue, m_separateSeasons, ADDON_STATUS_NEED_RESTART, ADDON_STATUS_OK);
else if (settingName == "showroot")
return SetSetting(settingName, settingValue, m_showRoot, ADDON_STATUS_NEED_RESTART, ADDON_STATUS_OK);
+ else if (settingName == "comskip")
+ return SetSetting(settingName, settingValue, m_comskip, ADDON_STATUS_NEED_RESTART, ADDON_STATUS_OK);
+ else if (settingName == "poster")
+ return SetSetting(settingName, settingValue, m_recordingPoster, ADDON_STATUS_NEED_RESTART, ADDON_STATUS_OK);
+ else if (settingName == "multistream")
+ return SetSetting(settingName, settingValue, m_multiStream, ADDON_STATUS_NEED_RESTART, ADDON_STATUS_OK);
else if (settingName == "genrestring")
return SetSetting(settingName, settingValue, m_genreString, ADDON_STATUS_NEED_SETTINGS, ADDON_STATUS_OK);
else if (settingName == "host_mac")
diff --git a/src/InstanceSettings.h b/src/InstanceSettings.h
index fe2d37dc..104b1e3b 100644
--- a/src/InstanceSettings.h
+++ b/src/InstanceSettings.h
@@ -80,6 +80,7 @@ namespace NextPVR
int m_timeoutWOL = 0;
bool m_connectionConfirmed = false;
bool m_backendResume = true;
+ bool m_multiStream = false;
//General
int m_backendVersion = 0;
@@ -113,6 +114,7 @@ namespace NextPVR
bool m_showRoot = false;
int m_chunkRecording = 32;
bool m_comskip = true;
+ bool m_recordingPoster = true;
//Timers
int m_defaultPrePadding = 0;
diff --git a/src/Recordings.cpp b/src/Recordings.cpp
index d7a27da3..b5550fb8 100644
--- a/src/Recordings.cpp
+++ b/src/Recordings.cpp
@@ -32,8 +32,6 @@ Recordings::Recordings(const std::shared_ptr& settings, Reques
}
-
-
PVR_ERROR Recordings::GetRecordingsAmount(bool deleted, int& amount)
{
// need something more optimal, but this will do for now...
@@ -320,8 +318,9 @@ bool Recordings::UpdatePvrRecording(const tinyxml2::XMLNode* pRecordingNode, kod
buffer.clear();
XMLUtils::GetString(pRecordingNode, "id", buffer);
tag.SetRecordingId(buffer);
+ bool series = ParseNextPVRSubtitle(pRecordingNode, tag);
- if (ParseNextPVRSubtitle(pRecordingNode, tag))
+ if (series)
{
if (m_settings->m_separateSeasons && multipleSeasons && tag.GetSeriesNumber() != PVR_RECORDING_INVALID_SERIES_EPISODE)
{
@@ -439,7 +438,8 @@ bool Recordings::UpdatePvrRecording(const tinyxml2::XMLNode* pRecordingNode, kod
else
artworkPath = kodi::tools::StringUtils::Format("%s/service?method=channel.show.artwork&name=%s", m_settings->m_urlBase, name.c_str());
tag.SetFanartPath(artworkPath + "&prefer=fanart");
- tag.SetThumbnailPath(artworkPath + "&prefer=poster");
+ if (m_settings->m_recordingPoster || status == "Failed" || tag.GetSizeInBytes() == 0 || tag.GetChannelType() == PVR_RECORDING_CHANNEL_TYPE_RADIO)
+ tag.SetThumbnailPath(artworkPath + "&prefer=poster");
}
if (XMLUtils::GetAdditiveString(pRecordingNode->FirstChildElement("genres"), "genre", EPG_STRING_TOKEN_SEPARATOR, buffer, true))
{
@@ -539,7 +539,7 @@ bool Recordings::ParseNextPVRSubtitle(const tinyxml2::XMLNode *pRecordingNode, k
PVR_ERROR Recordings::DeleteRecording(const kodi::addon::PVRRecording& recording)
{
- if (recording.GetRecordingTime() < time(nullptr) && recording.GetRecordingTime() + recording.GetDuration() > time(nullptr))
+ if (recording.GetRecordingTime() < time(nullptr) && recording.GetDuration() > 0 && recording.GetRecordingTime() + recording.GetDuration() > time(nullptr))
return PVR_ERROR_RECORDING_RUNNING;
const std::string request = "recording.delete&recording_id=" + recording.GetRecordingId();
diff --git a/src/buffers/RecordingBuffer.cpp b/src/buffers/RecordingBuffer.cpp
index 9d1ca3e9..f4770984 100644
--- a/src/buffers/RecordingBuffer.cpp
+++ b/src/buffers/RecordingBuffer.cpp
@@ -30,8 +30,8 @@ int RecordingBuffer::Duration(void)
if (m_recordingTime)
{
std::unique_lock lock(m_mutex);
- int diff = static_cast(time(nullptr) - m_recordingTime) - 15;
- if (diff > m_Duration)
+ int currentDuration = static_cast(time(nullptr) - m_recordingTime) - 15;
+ if (currentDuration > m_Duration)
{
tinyxml2::XMLDocument doc;
if (m_request.DoMethodRequest("recording.list&recording_id=" + m_recordingID, doc) == tinyxml2::XML_SUCCESS)
@@ -43,26 +43,28 @@ int RecordingBuffer::Duration(void)
if (status != "Recording")
{
- diff = m_Duration;
+ currentDuration = m_Duration;
m_recordingTime = 0;
}
else
{
+ // see what happens in one minute;
m_Duration += 60;
}
}
}
- else if (diff > 0)
+ else if (currentDuration > 0)
{
m_isLive = true;
- diff += 15;
+ currentDuration += 15;
}
else
{
+ // no longer in-progress
m_isLive = false;
- diff = 0;
+ currentDuration = 0;
}
- return diff;
+ return currentDuration;
}
else
{
@@ -70,11 +72,11 @@ int RecordingBuffer::Duration(void)
}
}
-bool RecordingBuffer::Open(const std::string inputUrl, const kodi::addon::PVRRecording& recording)
+bool RecordingBuffer::Open(const std::string inputUrl, const kodi::addon::PVRRecording& recording, int64_t streamId)
{
m_Duration = recording.GetDuration();
- kodi::Log(ADDON_LOG_DEBUG, "RecordingBuffer::Open %d %lld", recording.GetDuration(), recording.GetRecordingTime());
+ kodi::Log(ADDON_LOG_DEBUG, "RecordingBuffer::Open %d %lld streamId %d", recording.GetDuration(), recording.GetRecordingTime(), streamId);
if (recording.GetDuration() + recording.GetRecordingTime() > time(nullptr))
{
m_recordingTime = recording.GetRecordingTime() + m_settings->m_serverTimeOffset;
diff --git a/src/buffers/RecordingBuffer.h b/src/buffers/RecordingBuffer.h
index 36e17352..8be99d9f 100644
--- a/src/buffers/RecordingBuffer.h
+++ b/src/buffers/RecordingBuffer.h
@@ -74,11 +74,11 @@ namespace timeshift {
return PVR_ERROR_NO_ERROR;
}
- bool Open(const std::string inputUrl, const kodi::addon::PVRRecording& recording);
+ bool Open(const std::string inputUrl, const kodi::addon::PVRRecording& recording, int64_t streamId);
std::atomic m_isLive;
// recording start time
- time_t m_recordingTime;
+ time_t m_recordingTime = 0;
};
}
diff --git a/src/pvrclient-nextpvr.cpp b/src/pvrclient-nextpvr.cpp
index d5542294..424abff8 100644
--- a/src/pvrclient-nextpvr.cpp
+++ b/src/pvrclient-nextpvr.cpp
@@ -109,7 +109,6 @@ cPVRClientNextPVR::cPVRClientNextPVR(const CNextPVRAddon& base, const kodi::addo
m_supportsLiveTimeshift = false;
m_lastRecordingUpdateTime = std::numeric_limits::max(); // time of last recording check - force forever
m_timeshiftBuffer = new timeshift::DummyBuffer(m_settings, m_request);
- m_recordingBuffer = new timeshift::RecordingBuffer(m_settings, m_request);
m_realTimeBuffer = new timeshift::DummyBuffer(m_settings, m_request);
m_livePlayer = nullptr;
m_nowPlaying = NotPlaying;
@@ -123,7 +122,15 @@ cPVRClientNextPVR::~cPVRClientNextPVR()
{
// this is likley only needed for transcoding but include all cases
if (m_nowPlaying == Recording)
- CloseRecordedStream(-1);
+ {
+ std::lock_guard lock(m_multiStreamMutex);
+ std::map::iterator itr = m_multistreamRecording.begin();
+ while (itr != m_multistreamRecording.end())
+ {
+ CloseRecordedStream(itr->first);
+ itr = m_multistreamRecording.begin();
+ }
+ }
else
CloseLiveStream();
}
@@ -136,7 +143,6 @@ cPVRClientNextPVR::~cPVRClientNextPVR()
if (m_bConnected)
Disconnect();
delete m_timeshiftBuffer;
- delete m_recordingBuffer;
delete m_realTimeBuffer;
m_recordings.m_hostFilenames.clear();
m_channels.m_channelDetails.clear();
@@ -705,6 +711,7 @@ PVR_ERROR cPVRClientNextPVR::GetSignalStatus(int channelUid, kodi::addon::PVRSig
bool cPVRClientNextPVR::CanPauseStream(void)
{
+ // not called for recordings
if (IsServerStreaming())
{
if (m_nowPlaying == Recording)
@@ -720,10 +727,29 @@ void cPVRClientNextPVR::PauseStream(bool bPaused)
if (IsServerStreaming())
{
if (m_nowPlaying == Recording)
- m_recordingBuffer->PauseStream(bPaused);
+ {
+ std::lock_guard lock(m_multiStreamMutex);
+ m_multistreamRecording[m_streamCount]->PauseStream(bPaused);
+ }
+ else
+ m_livePlayer->PauseStream(bPaused);
+ }
+}
+
+
+PVR_ERROR cPVRClientNextPVR::PauseRecordedStream(int64_t streamId, bool bPaused)
+{
+ if (IsServerStreaming())
+ {
+ if (m_nowPlaying == Recording)
+ {
+ std::lock_guard lock(m_multiStreamMutex);
+ m_multistreamRecording[streamId]->PauseStream(bPaused);
+ }
else
m_livePlayer->PauseStream(bPaused);
}
+ return PVR_ERROR_NO_ERROR;
}
bool cPVRClientNextPVR::CanSeekStream(void)
@@ -743,45 +769,58 @@ bool cPVRClientNextPVR::CanSeekStream(void)
bool cPVRClientNextPVR::OpenRecordedStream(const kodi::addon::PVRRecording& recording, int64_t& streamId)
{
kodi::addon::PVRRecording copyRecording = recording;
- m_nowPlaying = Recording;
copyRecording.SetDirectory(m_recordings.m_hostFilenames[recording.GetRecordingId()]);
const std::string line = kodi::tools::StringUtils::Format("%s/live?recording=%s&client=XBMC-%s", m_settings->m_urlBase, recording.GetRecordingId().c_str(), m_request.GetSID());
- return m_recordingBuffer->Open(line, copyRecording);
+ std::lock_guard lock(m_multiStreamMutex);
+ m_nowPlaying = Recording;
+ m_multistreamRecording.emplace(++m_streamCount, new timeshift::RecordingBuffer(m_settings, m_request));
+ streamId = m_streamCount;
+ bool ret = m_multistreamRecording[streamId]->Open(line, copyRecording, streamId);
+ if (!ret)
+ {
+ CloseRecordedStream(streamId);
+ }
+ return ret;
}
void cPVRClientNextPVR::CloseRecordedStream(int64_t streamId)
{
- if (IsServerStreamingRecording())
+ if (IsServerStreamingRecording(streamId))
{
- m_recordingBuffer->Close();
- m_recordingBuffer->SetDuration(0);
+ std::lock_guard lock(m_multiStreamMutex);
+ m_multistreamRecording[streamId]->Close();
+ m_multistreamRecording.erase(streamId);
}
- m_nowPlaying = NotPlaying;
+ if (m_multistreamRecording.size() == 0)
+ m_nowPlaying = NotPlaying;
+ kodi::Log(ADDON_LOG_DEBUG, "Closed streamId %d remaining %d", streamId, m_multistreamRecording.size());
}
int cPVRClientNextPVR::ReadRecordedStream(int64_t streamId, unsigned char* pBuffer, unsigned int iBufferSize)
{
- if (IsServerStreamingRecording())
+ if (IsServerStreamingRecording(streamId))
{
- return m_recordingBuffer->Read(pBuffer, iBufferSize);
+ std::lock_guard lock(m_multiStreamMutex);
+ return m_multistreamRecording[streamId]->Read(pBuffer, iBufferSize);
}
return -1;
}
int64_t cPVRClientNextPVR::SeekRecordedStream(int64_t streamId, int64_t iPosition, int iWhence)
{
- if (IsServerStreamingRecording())
+ if (IsServerStreamingRecording(streamId))
{
- return m_recordingBuffer->Seek(iPosition, iWhence);
+ std::lock_guard lock(m_multiStreamMutex);
+ return m_multistreamRecording[streamId]->Seek(iPosition, iWhence);
}
return -1;
}
int64_t cPVRClientNextPVR::LengthRecordedStream(int64_t streamId)
{
- if (IsServerStreamingRecording())
+ if (IsServerStreamingRecording(streamId))
{
- return m_recordingBuffer->Length();
+ return m_multistreamRecording[streamId]->Length();
}
return -1;
}
@@ -800,19 +839,55 @@ bool cPVRClientNextPVR::IsRealTimeStream()
if (IsServerStreaming())
{
if (m_nowPlaying == Recording)
- return m_recordingBuffer->IsRealTimeStream();
+ {
+ std::lock_guard lock(m_multiStreamMutex);
+ return m_multistreamRecording[m_streamCount]->IsRealTimeStream();
+ }
else
return m_livePlayer->IsRealTimeStream();
}
return false;
}
-
PVR_ERROR cPVRClientNextPVR::GetStreamTimes(kodi::addon::PVRStreamTimes& stimes)
{
if (IsServerStreaming())
{
if (m_nowPlaying == Recording)
- return m_recordingBuffer->GetStreamTimes(stimes);
+ {
+ std::lock_guard lock(m_multiStreamMutex);
+ return m_multistreamRecording[m_streamCount]->GetStreamTimes(stimes);
+ }
+ else
+ return m_livePlayer->GetStreamTimes(stimes);
+ }
+ return PVR_ERROR_UNKNOWN;
+}
+
+
+PVR_ERROR cPVRClientNextPVR::IsRecordedStreamRealTime(int64_t streamId, bool& isRealTime)
+{
+ if (IsServerStreaming())
+ {
+ if (m_nowPlaying == Recording)
+ {
+ std::lock_guard lock(m_multiStreamMutex);
+ isRealTime = m_multistreamRecording[streamId]->IsRealTimeStream();
+ }
+ else
+ return PVR_ERROR_INVALID_PARAMETERS;
+ }
+ return PVR_ERROR_NO_ERROR;
+}
+
+PVR_ERROR cPVRClientNextPVR::GetRecordedStreamTimes(int64_t streamId, kodi::addon::PVRStreamTimes& stimes)
+{
+ if (IsServerStreaming())
+ {
+ if (m_nowPlaying == Recording)
+ {
+ std::lock_guard lock(m_multiStreamMutex);
+ return m_multistreamRecording[streamId]->GetStreamTimes(stimes);
+ }
else
return m_livePlayer->GetStreamTimes(stimes);
}
@@ -836,11 +911,11 @@ PVR_ERROR cPVRClientNextPVR::GetStreamReadChunkSize(int& chunksize)
bool cPVRClientNextPVR::IsServerStreaming()
{
- if (IsServerStreamingLive(false) || IsServerStreamingRecording(false))
+ if (IsServerStreamingLive(false) || m_multistreamRecording.size() > 0)
{
return true;
}
- kodi::Log(ADDON_LOG_ERROR, "Unknown streaming state %d %d %d", m_nowPlaying, m_recordingBuffer->GetDuration(), !m_livePlayer);
+ kodi::Log(ADDON_LOG_ERROR, "Unknown streaming state %d %d %d", m_nowPlaying, m_multistreamRecording.size(), !m_livePlayer);
return false;
}
@@ -851,29 +926,22 @@ bool cPVRClientNextPVR::IsServerStreamingLive(bool log)
return true;
}
if (log)
- kodi::Log(ADDON_LOG_ERROR, "Unknown live streaming state %d %d %d", m_nowPlaying, m_recordingBuffer->GetDuration(), !m_livePlayer);
+ kodi::Log(ADDON_LOG_ERROR, "Unknown live streaming state %d %d %d", m_nowPlaying, m_multistreamRecording.size(), !m_livePlayer);
return false;
}
-bool cPVRClientNextPVR::IsServerStreamingRecording(bool log)
+bool cPVRClientNextPVR::IsServerStreamingRecording(int64_t streamId, bool log)
{
- if (m_nowPlaying == Recording && m_recordingBuffer->GetDuration() > 0)
+ if (m_nowPlaying == Recording && m_multistreamRecording.size() > 0)
{
- return true;
+ std::lock_guard lock(m_multiStreamMutex);
+ return m_multistreamRecording.find(streamId) != m_multistreamRecording.end();
}
if (log)
- kodi::Log(ADDON_LOG_ERROR, "Unknown recording streaming state %d %d %d", m_nowPlaying, m_recordingBuffer->GetDuration(), !m_livePlayer);
+ kodi::Log(ADDON_LOG_ERROR, "Unknown recording streaming state %d %d %d", m_nowPlaying, m_multistreamRecording.size(), !m_livePlayer);
return false;
}
-/*
-PVR_ERROR cPVRClientNextPVR::GetBackendName(std::string& name)
-{
- name = m_settings->m_hostname;
- return PVR_ERROR_NO_ERROR;
-}
-*/
-
PVR_ERROR cPVRClientNextPVR::CallChannelMenuHook(const kodi::addon::PVRMenuhook& menuhook, const kodi::addon::PVRChannel& item)
{
return m_menuhook.CallChannelMenuHook(menuhook, item);
@@ -1047,5 +1115,6 @@ PVR_ERROR cPVRClientNextPVR::GetCapabilities(kodi::addon::PVRCapabilities& capab
capabilities.SetSupportsDescrambleInfo(false);
capabilities.SetSupportsRecordingPlayCount(m_settings->m_backendResume);
capabilities.SetSupportsProviders(false);
+ capabilities.SetSupportsMultipleRecordedStreams(!m_settings->m_recordingPoster);
return PVR_ERROR_NO_ERROR;
}
diff --git a/src/pvrclient-nextpvr.h b/src/pvrclient-nextpvr.h
index 02aa0284..5b9b322f 100644
--- a/src/pvrclient-nextpvr.h
+++ b/src/pvrclient-nextpvr.h
@@ -76,15 +76,18 @@ class ATTR_DLL_LOCAL cPVRClientNextPVR : public kodi::addon::CInstancePVRClient
int64_t LengthLiveStream() override;
bool CanPauseStream() override;
void PauseStream(bool paused) override;
+ PVR_ERROR PauseRecordedStream(int64_t streamId, bool paused) override;
bool CanSeekStream() override;
bool IsTimeshifting();
bool IsRealTimeStream() override;
PVR_ERROR GetStreamTimes(kodi::addon::PVRStreamTimes& times) override;
+ PVR_ERROR IsRecordedStreamRealTime(int64_t streamId, bool& isRealTime) override;
+ PVR_ERROR GetRecordedStreamTimes(int64_t streamId, kodi::addon::PVRStreamTimes& times) override;
PVR_ERROR GetStreamReadChunkSize(int& chunksize) override;
bool IsRadio() { return m_nowPlaying == Radio; };
bool IsServerStreaming();
bool IsServerStreamingLive(bool log = true);
- bool IsServerStreamingRecording(bool log = true);
+ bool IsServerStreamingRecording(int64_t streamId, bool log = true);
/* Record stream handling */
bool OpenRecordedStream(const kodi::addon::PVRRecording& recinfo, int64_t& streamId) override;
@@ -147,7 +150,9 @@ class ATTR_DLL_LOCAL cPVRClientNextPVR : public kodi::addon::CInstancePVRClient
timeshift::Buffer* m_timeshiftBuffer;
timeshift::Buffer* m_livePlayer;
timeshift::Buffer* m_realTimeBuffer;
- timeshift::RecordingBuffer* m_recordingBuffer;
+ std::map m_multistreamRecording;
+ mutable std::recursive_mutex m_multiStreamMutex;
+ int64_t m_streamCount = -1;
//Matrix changes
std::shared_ptr m_settings;