From 61fc2e291bc502be719c6795eb9e3e448683fb98 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 16 Oct 2024 19:37:23 +0200 Subject: [PATCH] wip --- pvr.hts/addon.xml.in | 2 +- src/Tvheadend.cpp | 267 +++++++++++++++++++++++++------------- src/Tvheadend.h | 33 ++--- src/tvheadend/HTSPVFS.cpp | 3 +- src/tvheadend/HTSPVFS.h | 4 + 5 files changed, 199 insertions(+), 110 deletions(-) diff --git a/pvr.hts/addon.xml.in b/pvr.hts/addon.xml.in index c383d67d..40f1e04f 100644 --- a/pvr.hts/addon.xml.in +++ b/pvr.hts/addon.xml.in @@ -1,7 +1,7 @@ @ADDON_DEPENDS@ diff --git a/src/Tvheadend.cpp b/src/Tvheadend.cpp index 1af01fda..9c056c98 100644 --- a/src/Tvheadend.cpp +++ b/src/Tvheadend.cpp @@ -36,14 +36,12 @@ CTvheadend::CTvheadend(const kodi::addon::IInstanceInfo& instance) m_customTimerProps( {CUSTOM_PROP_ID_DVR_CONFIGURATION, CUSTOM_PROP_ID_DVR_COMMENT}, *m_conn, m_dvrConfigs), m_streamchange(false), - m_vfs(new HTSPVFS(m_settings, *m_conn)), m_queue(static_cast(-1)), m_asyncState(m_settings->GetResponseTimeout()), m_timeRecordings(*m_conn, m_dvrConfigs), m_autoRecordings(m_settings, *m_conn, m_dvrConfigs), m_epgMaxDays(EpgMaxFutureDays()), - m_playingLiveStream(false), - m_playingRecording(nullptr) + m_playingLiveStream(false) { m_dmx.reserve(m_settings->GetTotalTuners()); for (int i = 0; i < 1 || i < m_settings->GetTotalTuners(); i++) @@ -61,7 +59,6 @@ CTvheadend::~CTvheadend() delete dmx; delete m_conn; - delete m_vfs; } void CTvheadend::Start() @@ -123,6 +120,7 @@ PVR_ERROR CTvheadend::GetCapabilities(kodi::addon::PVRCapabilities& capabilities capabilities.SetSupportsRecordingSize(m_conn->GetProtocol() >= 35); capabilities.SetSupportsProviders(m_conn->GetProtocol() >= 38); + capabilities.SetSupportsMultipleRecordedStreams(true); return PVR_ERROR_NO_ERROR; } @@ -1723,54 +1721,136 @@ PVR_ERROR CTvheadend::OnSystemWake() * VFS * *************************************************************************/ -bool CTvheadend::OpenRecordedStream(const kodi::addon::PVRRecording& rec) +PVR_ERROR CTvheadend::OpenRecordedStream(const kodi::addon::PVRRecording& recording, + int64_t& fileId) { if (!m_asyncState.WaitForState(ASYNC_EPG)) - return false; + return PVR_ERROR_SERVER_TIMEOUT; + + const auto vfs{std::make_shared(m_settings, *m_conn)}; + if (!vfs->Open(recording)) + return PVR_ERROR_SERVER_ERROR; + + fileId = vfs->GetFileId(); - bool ret = m_vfs->Open(rec); + std::lock_guard lock(m_mutex); + m_vfs.insert({fileId, vfs}); + return PVR_ERROR_NO_ERROR; +} - if (ret) +PVR_ERROR CTvheadend::CloseRecordedStream(int64_t fileId) +{ + std::shared_ptr vfs; { std::lock_guard lock(m_mutex); - const auto& it = m_recordings.find(std::stoul(rec.GetRecordingId())); - if (it != m_recordings.end()) - { - m_playingRecording = &(it->second); - } + auto it = m_vfs.find(fileId); + if (it == m_vfs.end()) + return PVR_ERROR_INVALID_PARAMETERS; + + vfs = (*it).second; + m_vfs.erase(it); } - return ret; + vfs->Close(); + return PVR_ERROR_NO_ERROR; } -void CTvheadend::CloseRecordedStream() +PVR_ERROR CTvheadend::ReadRecordedStream(int64_t fileId, + unsigned char* buffer, + unsigned int size, + int& bytesRead) { - m_vfs->Close(); + std::shared_ptr vfs; + bool isRecordingInProgress{false}; + { + std::lock_guard lock(m_mutex); - std::lock_guard lock(m_mutex); - m_playingRecording = nullptr; -} + auto it = m_vfs.find(fileId); + if (it == m_vfs.end()) + return PVR_ERROR_INVALID_PARAMETERS; -int CTvheadend::ReadRecordedStream(unsigned char* buf, unsigned int len) -{ - return m_vfs->Read(buf, len, VfsIsActiveRecording()); + vfs = (*it).second; + + auto it2 = m_recordings.find(std::stoul(vfs->GetRecordingId())); + if (it2 == m_recordings.end()) + return PVR_ERROR_INVALID_PARAMETERS; + + isRecordingInProgress = ((*it2).second.GetState() == PVR_TIMER_STATE_RECORDING); + } + + bytesRead = vfs->Read(buffer, size, isRecordingInProgress); + return bytesRead != -1 ? PVR_ERROR_NO_ERROR : PVR_ERROR_SERVER_ERROR; } -int64_t CTvheadend::SeekRecordedStream(int64_t position, int whence) +PVR_ERROR CTvheadend::SeekRecordedStream(int64_t fileId, + int64_t position, + int whence, + int64_t& newPosition) { - return m_vfs->Seek(position, whence, VfsIsActiveRecording()); + std::shared_ptr vfs; + bool isRecordingInProgress{false}; + { + std::lock_guard lock(m_mutex); + + auto it = m_vfs.find(fileId); + if (it == m_vfs.end()) + return PVR_ERROR_INVALID_PARAMETERS; + + vfs = (*it).second; + + auto it2 = m_recordings.find(std::stoul(vfs->GetRecordingId())); + if (it2 == m_recordings.end()) + return PVR_ERROR_INVALID_PARAMETERS; + + isRecordingInProgress = ((*it2).second.GetState() == PVR_TIMER_STATE_RECORDING); + } + + newPosition = vfs->Seek(position, whence, isRecordingInProgress); + return newPosition != -1 ? PVR_ERROR_NO_ERROR : PVR_ERROR_SERVER_ERROR; } -int64_t CTvheadend::LengthRecordedStream() +PVR_ERROR CTvheadend::LengthRecordedStream(int64_t fileId, int64_t& length) { - return m_vfs->Size(); + std::shared_ptr vfs; + { + std::lock_guard lock(m_mutex); + + auto it = m_vfs.find(fileId); + if (it == m_vfs.end()) + return PVR_ERROR_INVALID_PARAMETERS; + + vfs = (*it).second; + } + + length = vfs->Size(); + return length != -1 ? PVR_ERROR_NO_ERROR : PVR_ERROR_SERVER_ERROR; } -void CTvheadend::PauseStream(bool paused) +PVR_ERROR CTvheadend::PauseRecordedStream(int64_t fileId, bool paused) { - if (VfsIsActiveRecording()) - m_vfs->PauseStream(paused); + std::shared_ptr vfs; + bool isRecordingInProgress{false}; + { + std::lock_guard lock(m_mutex); + + auto it = m_vfs.find(fileId); + if (it == m_vfs.end()) + return PVR_ERROR_INVALID_PARAMETERS; + + vfs = (*it).second; + + auto it2 = m_recordings.find(std::stoul(vfs->GetRecordingId())); + if (it2 == m_recordings.end()) + return PVR_ERROR_INVALID_PARAMETERS; + + isRecordingInProgress = ((*it2).second.GetState() == PVR_TIMER_STATE_RECORDING); + } + + if (isRecordingInProgress) + vfs->PauseStream(paused); + + return PVR_ERROR_NO_ERROR; } PVR_ERROR CTvheadend::GetStreamReadChunkSize(int& chunksize) @@ -1784,10 +1864,24 @@ PVR_ERROR CTvheadend::GetStreamReadChunkSize(int& chunksize) bool CTvheadend::IsRealTimeStream() { - if (m_playingRecording) - return m_vfs->IsRealTimeStream(); - else - return m_dmx_active->IsRealTimeStream(); + return m_dmx_active->IsRealTimeStream(); +} + +PVR_ERROR CTvheadend::IsRecordedStreamRealTime(int64_t fileId, bool& isRealTime) +{ + std::shared_ptr vfs; + { + std::lock_guard lock(m_mutex); + + auto it = m_vfs.find(fileId); + if (it == m_vfs.end()) + return PVR_ERROR_INVALID_PARAMETERS; + + vfs = (*it).second; + } + + isRealTime = vfs->IsRealTimeStream(); + return PVR_ERROR_NO_ERROR; } /* ************************************************************************** @@ -2048,7 +2142,8 @@ void CTvheadend::SyncInitCompleted() for (auto* dmx : m_dmx) dmx->RebuildState(); - m_vfs->RebuildState(); + for (const auto& vfs : m_vfs) + vfs.second->RebuildState(); } /* check state engine */ @@ -2113,22 +2208,8 @@ void CTvheadend::SyncDvrCompleted() return; /* Recordings */ - { - std::lock_guard lock(m_mutex); - - // save id of currently playing recording, if any - uint32_t id = m_playingRecording ? m_playingRecording->GetId() : 0; - - utilities::erase_if(m_recordings, - [](const RecordingMapEntry& entry) { return entry.second.IsDirty(); }); - - if (m_playingRecording) - { - const auto& it = m_recordings.find(id); - if (it == m_recordings.end()) - m_playingRecording = nullptr; - } - } + utilities::erase_if(m_recordings, + [](const RecordingMapEntry& entry) { return entry.second.IsDirty(); }); /* Time-based repeating timers */ m_timeRecordings.SyncDvrCompleted(); @@ -2500,13 +2581,6 @@ void CTvheadend::ParseRecordingAddOrUpdate(htsmsg_t* msg, bool bAdd) rec.SetId(id); rec.SetDirty(false); - { - std::lock_guard lock(m_mutex); - - if (m_playingRecording && m_playingRecording->GetId() == id) - m_playingRecording = &rec; - } - // Set the time the recording was scheduled to start. This may differ from the actual start. int64_t start = 0; if (!htsmsg_get_s64(msg, "start", &start)) @@ -2876,10 +2950,6 @@ void CTvheadend::ParseRecordingDelete(htsmsg_t* msg) /* Erase */ { std::lock_guard lock(m_mutex); - - if (m_playingRecording && m_playingRecording->GetId() == u32) - m_playingRecording = nullptr; - m_recordings.erase(u32); } @@ -3348,43 +3418,56 @@ PVR_ERROR CTvheadend::GetStreamTimes(kodi::addon::PVRStreamTimes& times) return m_dmx_active->GetStreamTimes(times); } - std::lock_guard lock(m_mutex); + return PVR_ERROR_INVALID_PARAMETERS; +} + +PVR_ERROR CTvheadend::GetRecordedStreamTimes(int64_t fileId, kodi::addon::PVRStreamTimes& times) +{ + Recording recording; + { + std::lock_guard lock(m_mutex); + + auto it = m_vfs.find(fileId); + if (it == m_vfs.end()) + return PVR_ERROR_INVALID_PARAMETERS; - if (m_playingRecording) + auto it2 = m_recordings.find(std::stoul((*it).second->GetRecordingId())); + if (it2 == m_recordings.end()) + return PVR_ERROR_INVALID_PARAMETERS; + + recording = (*it2).second; + } + + if (recording.GetState() == PVR_TIMER_STATE_RECORDING) { - if (m_playingRecording->GetState() == PVR_TIMER_STATE_RECORDING) + if (recording.GetFilesStart() > 0) { - if (m_playingRecording->GetFilesStart() > 0) - { - times.SetPTSEnd((std::time(nullptr) - m_playingRecording->GetFilesStart()) * - STREAM_TIME_BASE); - } - else - { - // Older tvh versions do not expose real recording start/stop time. - // Remark: Following calculation does not always work. Returned end time might be to large, as the - // recording might actually have started later than scheduled start time (server came up too late etc). - times.SetPTSEnd((m_playingRecording->GetStartExtra() * 60 + std::time(nullptr) - - m_playingRecording->GetStart()) * - STREAM_TIME_BASE); - } + times.SetPTSEnd((std::time(nullptr) - recording.GetFilesStart()) * STREAM_TIME_BASE); } else { - if (m_playingRecording->GetFilesStart() > 0 && m_playingRecording->GetFilesStop() > 0) - { - times.SetPTSEnd((m_playingRecording->GetFilesStop() - m_playingRecording->GetFilesStart()) * - STREAM_TIME_BASE); - } - else - { - // Older tvh versions do not expose real recording start/stop time. - // Remark: Kodi is handling finished recording's times very well on its own - in difference to - // in-progress recording's times. Returning not implemented will make Kodi handle the stream times. - return PVR_ERROR_NOT_IMPLEMENTED; - } + // Older tvh versions do not expose real recording start/stop time. + // Remark: Following calculation does not always work. Returned end time might be to large, + // as the recording might actually have started later than scheduled start time (server came + // up too late etc). + times.SetPTSEnd((recording.GetStartExtra() * 60 + std::time(nullptr) - recording.GetStart()) * + STREAM_TIME_BASE); } - return PVR_ERROR_NO_ERROR; } - return PVR_ERROR_INVALID_PARAMETERS; + else + { + if (recording.GetFilesStart() > 0 && recording.GetFilesStop() > 0) + { + times.SetPTSEnd((recording.GetFilesStop() - recording.GetFilesStart()) * STREAM_TIME_BASE); + } + else + { + // Older tvh versions do not expose real recording start/stop time. + // Remark: Kodi is handling finished recording's times very well on its own - in difference to + // in-progress recording's times. Returning not implemented will make Kodi handle the stream + // times. + return PVR_ERROR_NOT_IMPLEMENTED; + } + } + return PVR_ERROR_NO_ERROR; } diff --git a/src/Tvheadend.h b/src/Tvheadend.h index e77a188a..c37bb8c0 100644 --- a/src/Tvheadend.h +++ b/src/Tvheadend.h @@ -209,14 +209,6 @@ class ATTR_DLL_LOCAL CTvheadend : public kodi::addon::CInstancePVRClient, void ParseEventDelete(htsmsg_t* m); bool ParseEvent(htsmsg_t* msg, bool bAdd, tvheadend::entity::Event& evt); - /* - * VFS - */ - bool VfsIsActiveRecording() const - { - return m_playingRecording && m_playingRecording->GetState() == PVR_TIMER_STATE_RECORDING; - } - public: /* * Connection (pass-thru) @@ -254,14 +246,24 @@ class ATTR_DLL_LOCAL CTvheadend : public kodi::addon::CInstancePVRClient, /* * VFS (pass-thru) */ - bool OpenRecordedStream(const kodi::addon::PVRRecording& rec) override; - void CloseRecordedStream() override; - int ReadRecordedStream(unsigned char* buf, unsigned int len) override; - int64_t SeekRecordedStream(int64_t position, int whence) override; - int64_t LengthRecordedStream() override; - void PauseStream(bool paused) override; PVR_ERROR GetStreamReadChunkSize(int& chunksize) override; + PVR_ERROR OpenRecordedStream(const kodi::addon::PVRRecording& recording, + int64_t& fileId) override; + PVR_ERROR CloseRecordedStream(int64_t fileId) override; + PVR_ERROR ReadRecordedStream(int64_t fileId, + unsigned char* buffer, + unsigned int size, + int& bytesRead) override; + PVR_ERROR SeekRecordedStream(int64_t fileId, + int64_t position, + int whence, + int64_t& newPosition) override; + PVR_ERROR LengthRecordedStream(int64_t fileId, int64_t& length) override; + PVR_ERROR IsRecordedStreamRealTime(int64_t fileId, bool& isRealTime) override; + PVR_ERROR PauseRecordedStream(int64_t fileId, bool paused) override; + PVR_ERROR GetRecordedStreamTimes(int64_t fileId, kodi::addon::PVRStreamTimes& times) override; + /* * stream times (live streams and recordings) */ @@ -288,7 +290,7 @@ class ATTR_DLL_LOCAL CTvheadend : public kodi::addon::CInstancePVRClient, std::vector m_dmx; tvheadend::HTSPDemuxer* m_dmx_active; bool m_streamchange; - tvheadend::HTSPVFS* m_vfs; + std::map> m_vfs; bool m_stateRebuilt{false}; HTSPMessageQueue m_queue; @@ -311,5 +313,4 @@ class ATTR_DLL_LOCAL CTvheadend : public kodi::addon::CInstancePVRClient, int m_epgMaxDays; bool m_playingLiveStream; - tvheadend::entity::Recording* m_playingRecording; }; diff --git a/src/tvheadend/HTSPVFS.cpp b/src/tvheadend/HTSPVFS.cpp index 1292fe38..737a1351 100644 --- a/src/tvheadend/HTSPVFS.cpp +++ b/src/tvheadend/HTSPVFS.cpp @@ -71,7 +71,8 @@ bool HTSPVFS::Open(const kodi::addon::PVRRecording& rec) Close(); /* Cache details */ - m_path = kodi::tools::StringUtils::Format("dvr/%s", rec.GetRecordingId().c_str()); + m_recordingId = rec.GetRecordingId(); + m_path = kodi::tools::StringUtils::Format("dvr/%s", m_recordingId.c_str()); m_fileStart = rec.GetRecordingTime(); /* Send open */ diff --git a/src/tvheadend/HTSPVFS.h b/src/tvheadend/HTSPVFS.h index 2cd91717..1fbf9d18 100644 --- a/src/tvheadend/HTSPVFS.h +++ b/src/tvheadend/HTSPVFS.h @@ -33,6 +33,9 @@ class HTSPVFS HTSPVFS(const std::shared_ptr& settings, HTSPConnection& conn); ~HTSPVFS(); + uint32_t GetFileId() const { return m_fileId; } + const std::string& GetRecordingId() const { return m_recordingId; } + void RebuildState(); bool Open(const kodi::addon::PVRRecording& rec); @@ -51,6 +54,7 @@ class HTSPVFS std::shared_ptr m_settings; HTSPConnection& m_conn; + std::string m_recordingId; std::string m_path; uint32_t m_fileId; int64_t m_offset;