From 22a4c6ae880607eb6f830342c8280665e3a402e9 Mon Sep 17 00:00:00 2001 From: CastagnaIT Date: Sun, 11 Jun 2023 18:16:17 +0200 Subject: [PATCH] [AdaptiveTree] Rework of manifest update params --- inputstream.adaptive/addon.xml.in | 2 +- src/Session.cpp | 30 ++++++++------- src/Session.h | 2 +- src/common/AdaptiveStream.cpp | 14 +++---- src/common/AdaptiveTree.cpp | 8 ++-- src/common/AdaptiveTree.h | 26 ++++++------- src/parser/DASHTree.cpp | 28 +++++++------- src/parser/HLSTree.cpp | 11 +++--- src/parser/SmoothTree.cpp | 2 +- src/test/TestDASHTree.cpp | 63 +++++++++---------------------- src/test/TestHelper.cpp | 43 +++++++++++++++++++++ src/test/TestHelper.h | 23 +++++++++++ src/utils/PropertiesUtils.cpp | 27 +++++++++++-- src/utils/PropertiesUtils.h | 8 ++-- 14 files changed, 173 insertions(+), 114 deletions(-) diff --git a/inputstream.adaptive/addon.xml.in b/inputstream.adaptive/addon.xml.in index f8143ae02..56a288c7c 100644 --- a/inputstream.adaptive/addon.xml.in +++ b/inputstream.adaptive/addon.xml.in @@ -10,7 +10,7 @@ name="adaptive" extension="" tags="true" - listitemprops="license_type|license_key|license_data|license_flags|manifest_type|server_certificate|manifest_update_parameter|manifest_params|manifest_headers|stream_params|stream_headers|original_audio_language|play_timeshift_buffer|pre_init_data|stream_selection_type|chooser_bandwidth_max|chooser_resolution_max|chooser_resolution_secure_max|live_delay" + listitemprops="license_type|license_key|license_data|license_flags|manifest_type|server_certificate|manifest_update_parameter|manifest_upd_params|manifest_params|manifest_headers|stream_params|stream_headers|original_audio_language|play_timeshift_buffer|pre_init_data|stream_selection_type|chooser_bandwidth_max|chooser_resolution_max|chooser_resolution_secure_max|live_delay" library_@PLATFORM@="@LIBRARY_FILENAME@"/> @PLATFORM@ diff --git a/src/Session.cpp b/src/Session.cpp index 8db7d3059..a31ab9ed9 100644 --- a/src/Session.cpp +++ b/src/Session.cpp @@ -238,20 +238,24 @@ bool CSession::Initialize() } std::string manifestUrl = m_manifestUrl; + std::string manifestUpdateParam = m_kodiProps.m_manifestUpdParams; - //! @todo: In the next version of kodi, remove this hack of adding the $START_NUMBER$ parameter - //! to the manifest url which is forcibly cut and copied to the manifest update request url, - //! this seem used by YouTube addon only, adaptations are relatively simple - std::string manifestUpdateParam = m_kodiProps.m_manifestUpdateParam; - if (manifestUpdateParam.empty() && STRING::Contains(manifestUrl, "$START_NUMBER$")) + if (manifestUpdateParam.empty() && !m_kodiProps.m_manifestUpdateParam.empty()) { - LOG::Log(LOGWARNING, - "The misuse of adding params with $START_NUMBER$ placeholder to the " - "manifest url has been deprecated and will be removed on next Kodi version.\n" - "Please use \"manifest_update_parameter\" Kodi property to set manifest update " - "parameters, see Wiki integration page."); - manifestUpdateParam = URL::GetParametersFromPlaceholder(manifestUrl, "$START_NUMBER$"); - manifestUrl.resize(manifestUrl.size() - manifestUpdateParam.size()); + //! @todo: In the next version of kodi, remove this hack of adding the $START_NUMBER$ parameter + //! to the manifest url which is forcibly cut and copied to the manifest update request url, + //! this seem used by YouTube addon only, adaptations are relatively simple + std::string manifestUpdateParam = m_kodiProps.m_manifestUpdateParam; + if (manifestUpdateParam.empty() && STRING::Contains(manifestUrl, "$START_NUMBER$")) + { + LOG::Log(LOGWARNING, + "The misuse of adding params with $START_NUMBER$ placeholder to the " + "manifest url has been deprecated and will be removed on next Kodi version.\n" + "Please use \"manifest_upd_params\" Kodi property to set manifest update " + "parameters, see Wiki integration page."); + manifestUpdateParam = URL::GetParametersFromPlaceholder(manifestUrl, "$START_NUMBER$"); + manifestUrl.resize(manifestUrl.size() - manifestUpdateParam.size()); + } } CURL::HTTPResponse manifestResp; @@ -1272,7 +1276,7 @@ bool CSession::SeekTime(double seekTime, unsigned int streamId, bool preceeding) seekTime -= chapterTime; // don't try to seek past the end of the stream, leave a sensible amount so we can buffer properly - if (m_adaptiveTree->has_timeshift_buffer_) + if (m_adaptiveTree->IsLive()) { double maxSeek{0}; uint64_t curTime; diff --git a/src/Session.h b/src/Session.h index edf855ee4..46fe1a16c 100644 --- a/src/Session.h +++ b/src/Session.h @@ -220,7 +220,7 @@ class ATTR_DLL_LOCAL CSession : public adaptive::AdaptiveStreamObserver /*! \brief Report if the current content is dynamic/live * \return True if live, false if VOD */ - bool IsLive() const { return m_adaptiveTree->has_timeshift_buffer_; }; + bool IsLive() const { return m_adaptiveTree->IsLive(); }; /*! \brief Get the type of manifest being played * \return ManifestType - MPD/ISM/HLS diff --git a/src/common/AdaptiveStream.cpp b/src/common/AdaptiveStream.cpp index affad5aa6..d488e3ff0 100644 --- a/src/common/AdaptiveStream.cpp +++ b/src/common/AdaptiveStream.cpp @@ -407,17 +407,15 @@ void AdaptiveStream::worker() thread_data_->signal_dl_.notify_one(); lckdl.unlock(); - bool isLive = tree_.has_timeshift_buffer_; - //! @todo: for live content we should calculate max attempts and sleep timing //! based on segment duration / playlist updates timing - size_t maxAttempts = isLive ? 10 : 6; - std::chrono::milliseconds msSleep = isLive ? 1000ms : 500ms; + size_t maxAttempts = tree_.IsLive() ? 10 : 6; + std::chrono::milliseconds msSleep = tree_.IsLive() ? 1000ms : 500ms; //! @todo: Some streaming software offers subtitle tracks with missing fragments, usually live tv //! When a programme is broadcasted that has subtitles, subtitles fragments are offered, //! Ensure we continue with the next segment after one retry on errors - if (current_adp_->GetStreamType() == StreamType::SUBTITLE && isLive) + if (current_adp_->GetStreamType() == StreamType::SUBTITLE && tree_.IsLive()) maxAttempts = 2; size_t downloadAttempts = 1; @@ -659,13 +657,13 @@ bool AdaptiveStream::start_stream() if (!current_rep_->current_segment_) { - if (!play_timeshift_buffer_ && tree_.has_timeshift_buffer_ && + if (!play_timeshift_buffer_ && tree_.IsLive() && current_rep_->SegmentTimeline().GetSize() > 1 && tree_.m_periods.size() == 1) { if (!last_rep_) { std::size_t pos; - if (tree_.has_timeshift_buffer_ || tree_.available_time_ >= tree_.stream_start_) + if (tree_.IsLive() || tree_.available_time_ >= tree_.stream_start_) { pos = current_rep_->SegmentTimeline().GetSize() - 1; } @@ -914,7 +912,7 @@ bool AdaptiveStream::ensureSegment() if (tree_.SecondsSinceRepUpdate(newRep) > 1) { tree_.prepareRepresentation( - current_period_, current_adp_, newRep, tree_.has_timeshift_buffer_); + current_period_, current_adp_, newRep, tree_.IsLive()); } size_t nextsegmentPos = static_cast(nextsegno - newRep->GetStartNumber()); diff --git a/src/common/AdaptiveTree.cpp b/src/common/AdaptiveTree.cpp index 6104ba838..3fb1048b4 100644 --- a/src/common/AdaptiveTree.cpp +++ b/src/common/AdaptiveTree.cpp @@ -42,7 +42,7 @@ namespace adaptive void AdaptiveTree::Configure(const UTILS::PROPERTIES::KodiProperties& kodiProps, CHOOSER::IRepresentationChooser* reprChooser, std::string_view supportedKeySystem, - std::string_view manifestUpdateParam) + std::string_view manifestUpdParams) { m_reprChooser = reprChooser; m_supportedKeySystem = supportedKeySystem; @@ -56,7 +56,7 @@ namespace adaptive m_manifestParams = kodiProps.m_manifestParams; m_manifestHeaders = kodiProps.m_manifestHeaders; - m_manifestUpdateParam = manifestUpdateParam; + m_manifestUpdParams = manifestUpdParams; // Convenience way to share common addon settings we avoid // calling the API many times to improve parsing performance @@ -81,7 +81,7 @@ namespace adaptive LOG::Log(LOGINFO, "Manifest successfully parsed (Periods: %zu, Streams in first period: %zu, Type: %s)", m_periods.size(), m_currentPeriod->GetAdaptationSets().size(), - has_timeshift_buffer_ ? "live" : "VOD"); + m_isLive ? "live" : "VOD"); } void AdaptiveTree::FreeSegments(CPeriod* period, CRepresentation* repr) @@ -106,7 +106,7 @@ namespace adaptive uint32_t fragmentDuration, uint32_t movie_timescale) { - if (!has_timeshift_buffer_ || HasManifestUpdates() || repr->HasSegmentsUrl()) + if (!m_isLive || HasManifestUpdates() || repr->HasSegmentsUrl()) return; // Check if its the last frame we watch diff --git a/src/common/AdaptiveTree.h b/src/common/AdaptiveTree.h index 565719f1f..c11906d13 100644 --- a/src/common/AdaptiveTree.h +++ b/src/common/AdaptiveTree.h @@ -59,19 +59,12 @@ class ATTR_DLL_LOCAL AdaptiveTree std::string manifest_url_; std::string base_url_; - //! @todo: m_manifestUpdateParam is used to force enabling manifest updates and to - //! be able to set also parameters to the manifest update url, we should start decouple - //! the "full" use case and the parameters case with appropriate separate properties, - //! in a future "full" use case should be dropped and possible broken dash live manifest fixed - std::string m_manifestUpdateParam; - std::optional initial_sequence_; // HLS only uint64_t m_totalTimeSecs{0}; // Total playing time in seconds uint64_t stream_start_{0}; uint64_t available_time_{0}; uint64_t base_time_{0}; // SmoothTree only, the lower start PTS time between all StreamIndex tags uint64_t m_liveDelay{0}; // Apply a delay in seconds from the live edge - bool has_timeshift_buffer_{false}; // Returns true when there is timeshift buffer for live content std::string m_supportedKeySystem; std::string location_; @@ -85,15 +78,12 @@ class ATTR_DLL_LOCAL AdaptiveTree /*! * \brief Configure the adaptive tree. * \param kodiProps The Kodi properties - * \param manifestUpdateParam Set to "full" to force enabling future manifest updates or set parameters - * that will be add to manifest request url, with an optional support - * of placeholder $START_NUMBER$ to allow set the segment start number - * to the parameter e.g. ?start_seq=$START_NUMBER$ become ?start_seq=10 + * \param manifestUpdParams Parameters to be add to manifest request url, depends on manifest implementation */ virtual void Configure(const UTILS::PROPERTIES::KodiProperties& kodiProps, CHOOSER::IRepresentationChooser* reprChooser, std::string_view supportedKeySystem, - std::string_view manifestUpdateParam); + std::string_view manifestUpdParams); /*! * \brief Open manifest data for parsing. @@ -168,10 +158,15 @@ class ATTR_DLL_LOCAL AdaptiveTree std::string BuildDownloadUrl(const std::string& url) const; + /*! + * \brief Check for live streaming content (timeshift buffer) + * \return True for live streaming content, otherwise false for VOD content + */ + bool IsLive() const { return m_isLive; } + bool HasManifestUpdates() const { - return ~m_updateInterval && m_updateInterval > 0 && has_timeshift_buffer_ && - !m_manifestUpdateParam.empty(); + return m_isLive && ~m_updateInterval && m_updateInterval > 0; } const std::chrono::time_point GetLastUpdated() const { return lastUpdated_; }; @@ -257,12 +252,15 @@ class ATTR_DLL_LOCAL AdaptiveTree void SortTree(); // Live segment update section + bool m_isLive{false}; virtual void StartUpdateThread(); virtual void RefreshLiveSegments() { lastUpdated_ = std::chrono::system_clock::now(); } std::atomic m_updateInterval{~0U}; TreeUpdateThread m_updThread; std::atomic> lastUpdated_{std::chrono::system_clock::now()}; + // Optionals URL parameters to add to the manifest update requests + std::string m_manifestUpdParams; std::string m_manifestParams; std::map m_manifestHeaders; CHOOSER::IRepresentationChooser* m_reprChooser{nullptr}; diff --git a/src/parser/DASHTree.cpp b/src/parser/DASHTree.cpp index ca036d6b6..b1c2581a8 100644 --- a/src/parser/DASHTree.cpp +++ b/src/parser/DASHTree.cpp @@ -206,14 +206,14 @@ void adaptive::CDashTree::ParseTagMPDAttribs(pugi::xml_node nodeMPD) double mediaPresDuration = XML::ParseDuration(XML::GetAttrib(nodeMPD, "mediaPresentationDuration")); - has_timeshift_buffer_ = XML::GetAttrib(nodeMPD, "type") == "dynamic"; + m_isLive = XML::GetAttrib(nodeMPD, "type") == "dynamic"; double timeShiftBufferDepth{0}; std::string timeShiftBufferDepthStr; if (XML::QueryAttrib(nodeMPD, "timeShiftBufferDepth", timeShiftBufferDepthStr)) { timeShiftBufferDepth = XML::ParseDuration(timeShiftBufferDepthStr); - has_timeshift_buffer_ = true; + m_isLive = true; } std::string availabilityStartTimeStr; @@ -511,9 +511,6 @@ void adaptive::CDashTree::ParseTagAdaptationSet(pugi::xml_node nodeAdp, PLAYLIST adpSet->SetStartPTS(startPts); - if (m_manifestUpdateParam.empty() && has_timeshift_buffer_) - m_manifestUpdateParam = "full"; - if (period->GetDuration() == 0 && segTemplate.GetTimescale() > 0) { // Calculate total duration of segments @@ -818,9 +815,6 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr, ParseTagSegmentTimeline(nodeSegTL, repr->SegmentTimeline(), segTemplate.GetTimescale(), totalTimeSecs, &segTemplate); - if (m_manifestUpdateParam.empty() && has_timeshift_buffer_) - m_manifestUpdateParam = "full"; - repr->nextPts_ = startPts; if (!repr->SegmentTimeline().IsEmpty()) @@ -1061,7 +1055,7 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr, segTl.range_end_ = repr->GetStartNumber(); segTl.startPTS_ = adpSet->GetStartPTS(); - if (has_timeshift_buffer_ && !segTemplate->HasVariableTime() && + if (m_isLive && !segTemplate->HasVariableTime() && segTemplate->GetDuration() > 0) { uint64_t sampleTime = period->GetStart() / 1000; @@ -1498,12 +1492,18 @@ void adaptive::CDashTree::RefreshLiveSegments() std::unique_ptr updateTree{std::move(Clone())}; - std::string manifestUrl = location_.empty() ? manifest_url_ : location_; - // Custom manifest update url parameters - std::string manifestParams; - if (m_manifestUpdateParam != "full") - manifestParams = m_manifestUpdateParam; + std::string manifestParams = m_manifestUpdParams; + + std::string manifestUrl; + if (location_.empty()) + { + manifestUrl = manifest_url_; + if (!manifestParams.empty()) + manifestUrl = URL::RemoveParameters(manifestUrl, false); + } + else + manifestUrl = location_; // YouTube needs segment start number as parameter bool urlHaveStartNumber = manifestParams.find("$START_NUMBER$") != std::string::npos; diff --git a/src/parser/HLSTree.cpp b/src/parser/HLSTree.cpp index 2ab840f28..a44357429 100644 --- a/src/parser/HLSTree.cpp +++ b/src/parser/HLSTree.cpp @@ -319,7 +319,7 @@ PLAYLIST::PrepareRepStatus adaptive::CHLSTree::prepareRepresentation(PLAYLIST::C if (STRING::CompareNoCase(tagValue, "VOD")) { m_refreshPlayList = false; - has_timeshift_buffer_ = false; + m_isLive = false; } } else if (tagName == "#EXT-X-TARGETDURATION") @@ -546,7 +546,7 @@ PLAYLIST::PrepareRepStatus adaptive::CHLSTree::prepareRepresentation(PLAYLIST::C else if (tagName == "#EXT-X-ENDLIST") { m_refreshPlayList = false; - has_timeshift_buffer_ = false; + m_isLive = false; } } @@ -593,7 +593,7 @@ PLAYLIST::PrepareRepStatus adaptive::CHLSTree::prepareRepresentation(PLAYLIST::C for (auto& p : m_periods) { totalTimeSecs += p->GetDuration() / p->GetTimescale(); - if (!has_timeshift_buffer_ && !m_refreshPlayList) + if (!m_isLive && !m_refreshPlayList) { auto& adpSet = p->GetAdaptationSets()[adpSetPos]; adpSet->GetRepresentations()[reprPos]->m_isDownloaded = true; @@ -603,7 +603,7 @@ PLAYLIST::PrepareRepStatus adaptive::CHLSTree::prepareRepresentation(PLAYLIST::C else { totalTimeSecs = rep->GetDuration() / rep->GetTimescale(); - if (!has_timeshift_buffer_ && !m_refreshPlayList) + if (!m_isLive && !m_refreshPlayList) { rep->m_isDownloaded = true; } @@ -1083,8 +1083,7 @@ bool adaptive::CHLSTree::ParseManifest(const std::string& data) m_extGroups.clear(); // Set Live as default - has_timeshift_buffer_ = true; - m_manifestUpdateParam = "full"; + m_isLive = true; m_periods.push_back(std::move(period)); diff --git a/src/parser/SmoothTree.cpp b/src/parser/SmoothTree.cpp index 91e401235..a05378511 100644 --- a/src/parser/SmoothTree.cpp +++ b/src/parser/SmoothTree.cpp @@ -77,7 +77,7 @@ bool adaptive::CSmoothTree::ParseManifest(const std::string& data) if (STRING::CompareNoCase(XML::GetAttrib(nodeSSM, "IsLive"), "true")) { - has_timeshift_buffer_ = true; + m_isLive = true; stream_start_ = UTILS::GetTimestamp(); available_time_ = stream_start_; } diff --git a/src/test/TestDASHTree.cpp b/src/test/TestDASHTree.cpp index 0769e6767..30f0e5362 100644 --- a/src/test/TestDASHTree.cpp +++ b/src/test/TestDASHTree.cpp @@ -13,7 +13,6 @@ #include - class DASHTreeTest : public ::testing::Test { protected: @@ -44,7 +43,7 @@ class DASHTreeTest : public ::testing::Test void OpenTestFile(std::string filePath, std::string url, std::map manifestHeaders, - std::string manifestUpdateParam) + std::string manifestUpdParams) { testHelper::testFile = filePath; @@ -61,7 +60,7 @@ class DASHTreeTest : public ::testing::Test m_reprChooser->SetDownloadSpeed(500000); tree->Configure(m_kodiProps, m_reprChooser, "urn:uuid:EDEF8BA9-79D6-4ACE-A3C8-27DCD51D21ED", - manifestUpdateParam); + manifestUpdParams); // Parse the manifest if (!tree->Open(resp.effectiveUrl, resp.headers, resp.data)) @@ -263,18 +262,6 @@ TEST_F(DASHTreeTest, CalculateCorrectSegmentNumbersFromSegmentTemplateWithOldPub EXPECT_EQ(segments.Get(30)->range_end_, 603302); } -TEST_F(DASHTreeTest, CalculateLiveWithPresentationDuration) -{ - OpenTestFile("mpd/segtimeline_live_pd.mpd"); - EXPECT_EQ(tree->has_timeshift_buffer_, true); -} - -TEST_F(DASHTreeTest, CalculateStaticWithPresentationDuration) -{ - OpenTestFile("mpd/segtpl_slash_baseurl_slash.mpd"); - EXPECT_EQ(tree->has_timeshift_buffer_, false); -} - TEST_F(DASHTreeTest, CalculateCorrectFpsScaleFromAdaptionSet) { OpenTestFile("mpd/fps_scale_adaptset.mpd"); @@ -306,7 +293,6 @@ TEST_F(DASHTreeTest, CalculateCorrectFpsScaleFromAdaptionSet) TEST_F(DASHTreeAdaptiveStreamTest, replacePlaceHolders) { OpenTestFile("mpd/placeholders.mpd", "https://foo.bar/placeholders.mpd"); - tree->has_timeshift_buffer_ = false; SetTestStream(NewStream(tree->m_periods[0]->GetAdaptationSets()[0].get())); testStream->start_stream(); @@ -352,45 +338,30 @@ TEST_F(DASHTreeAdaptiveStreamTest, replacePlaceHolders) EXPECT_EQ(testHelper::downloadList[4], "https://foo.bar/videosd-400x224/segment.m4s"); } -TEST_F(DASHTreeTest, updateParameterLiveSegmentTimeline) +TEST_F(DASHTreeTest, isLiveManifestOnLiveSegmentTimeline) { OpenTestFile("mpd/segtimeline_live_pd.mpd"); - EXPECT_EQ(tree->m_manifestUpdateParam, "full"); + EXPECT_EQ(tree->IsLive(), true); } -TEST_F(DASHTreeTest, updateParameterVODSegmentStartNumber) -{ - OpenTestFile("mpd/segtimeline_vod.mpd", "https://foo.bar/dash.mpd?foo=bar&baz=qux", {}, - "?start_seq=$START_NUMBER$"); - EXPECT_EQ(tree->m_manifestUpdateParam, "?start_seq=$START_NUMBER$"); - EXPECT_EQ(tree->manifest_url_, "https://foo.bar/dash.mpd?foo=bar&baz=qux"); -} - -TEST_F(DASHTreeTest, updateParameterVODSegmentStartNumberRedirect) -{ - testHelper::effectiveUrl = "https://foo.bar/mpd/stream.mpd?foo=bar&baz=qux&test=123"; - OpenTestFile("mpd/segtimeline_vod.mpd", "https://foo.bar/dash.mpd", {}, - "?start_seq=$START_NUMBER$"); - EXPECT_EQ(tree->m_manifestUpdateParam, "?start_seq=$START_NUMBER$"); - EXPECT_EQ(tree->manifest_url_, "https://foo.bar/mpd/stream.mpd?foo=bar&baz=qux&test=123"); -} - -TEST_F(DASHTreeTest, updateParameterVODSegmentTimeline) +TEST_F(DASHTreeTest, isLiveManifestOnVODSegmentTimeline) { OpenTestFile("mpd/segtimeline_vod.mpd"); - EXPECT_EQ(tree->m_manifestUpdateParam, ""); + EXPECT_EQ(tree->IsLive(), false); } -TEST_F(DASHTreeTest, updateParameterLiveSegmentTemplate) +TEST_F(DASHTreeTest, updateParameterLiveSegmentStartNumber) { - OpenTestFile("mpd/segtpl_pto.mpd"); - EXPECT_EQ(tree->m_manifestUpdateParam, ""); -} - -TEST_F(DASHTreeTest, updateParameterVODSegmentTemplate) -{ - OpenTestFile("mpd/segtpl_baseurl_noslashs.mpd"); - EXPECT_EQ(tree->m_manifestUpdateParam, ""); + OpenTestFile("mpd/segtimeline_live_pd.mpd", "https://foo.bar/dash.mpd?foo=bar&baz=qux", {}, + "?start_seq=$START_NUMBER$"); + // Clear testFile to not download the same file as manifest update or will cause mess, + // the manifest update operation is just to verify the manifest update url + testHelper::testFile.clear(); + + tree->StartManifestUpdate(); + std::string manifestUpdUrl; + tree->WaitManifestUpdate(manifestUpdUrl); + EXPECT_EQ(manifestUpdUrl, "https://foo.bar/dash.mpd?start_seq=487063"); } TEST_F(DASHTreeTest, CalculatePsshDefaultKid) diff --git a/src/test/TestHelper.cpp b/src/test/TestHelper.cpp index c55b8f222..194f6f5c1 100644 --- a/src/test/TestHelper.cpp +++ b/src/test/TestHelper.cpp @@ -13,6 +13,8 @@ std::string testHelper::testFile; std::string testHelper::effectiveUrl; std::vector testHelper::downloadList; +std::condition_variable DASHTestTree::s_cvDashManifestUpd; +std::mutex DASHTestTree::s_mutexDashManifestUpd; bool testHelper::LoadFile(std::string path, std::string& data) { @@ -54,6 +56,9 @@ bool testHelper::DownloadFile(std::string_view url, const std::vector& respHeaders, UTILS::CURL::HTTPResponse& resp) { + if (testHelper::testFile.empty()) + return false; + bool ret = LoadFile(testHelper::testFile, resp.data); if (!ret) @@ -157,11 +162,49 @@ void AESDecrypter::ivFromSequence(uint8_t* buffer, uint64_t sid){} bool AESDecrypter::RenewLicense(const std::string& pluginUrl){return false;} +void DASHTestTree::RefreshLiveSegments() +{ + if (m_isManifestUpdSingleRun) + m_updateInterval = ~0U; // Prevent the execution of new manifest updates after this + + CDashTree::RefreshLiveSegments(); + + m_isManifestUpdReady = true; + s_cvDashManifestUpd.notify_all(); +} + +void DASHTestTree::StartManifestUpdate() +{ + // Overriding interval to speed up execution + m_updateInterval = 1; + m_isManifestUpdSingleRun = true; + StartUpdateThread(); +} + +void DASHTestTree::WaitManifestUpdate(std::string& url) +{ + std::unique_lock lock(s_mutexDashManifestUpd); + if (s_cvDashManifestUpd.wait_for(lock, std::chrono::milliseconds(1000), + [&] { return m_isManifestUpdReady; }) == false) + { + LOG::LogF(LOGFATAL, "Too much time elapsed to wait for manifest update"); + } + else + { + url = m_manifestUpdUrl; + m_manifestUpdUrl.clear(); + } + m_isManifestUpdReady = false; + m_isManifestUpdSingleRun = false; +} + bool DASHTestTree::DownloadManifestUpd(std::string_view url, const std::map& reqHeaders, const std::vector& respHeaders, UTILS::CURL::HTTPResponse& resp) { + m_manifestUpdUrl = url.data(); + if (testHelper::DownloadFile(url, reqHeaders, respHeaders, resp)) { return true; diff --git a/src/test/TestHelper.h b/src/test/TestHelper.h index 1bb2339d9..0858692f0 100644 --- a/src/test/TestHelper.h +++ b/src/test/TestHelper.h @@ -18,6 +18,8 @@ #include "../utils/log.h" #include "../utils/PropertiesUtils.h" +#include +#include #include // \brief Current version of gtest dont support compare std::string_view values @@ -107,6 +109,20 @@ class DASHTestTree : public adaptive::CDashTree void SetLastUpdated(const std::chrono::system_clock::time_point tm) { lastUpdated_ = tm; } std::chrono::system_clock::time_point GetNowTimeChrono() { return m_mock_time_chrono; }; + virtual void RefreshLiveSegments() override; + + /*! + * \brief Start a manifest update, will be executed a single update, + * mandatory use WaitGetManifestUpdate method to wait the response data + */ + void StartManifestUpdate(); + + /*! + * \brief Wait the data response of a manifest update + * \param url[OUT] Provides the url used to the manifest request + */ + void WaitManifestUpdate(std::string& url); + private: bool DownloadManifestUpd(std::string_view url, const std::map& reqHeaders, @@ -117,6 +133,13 @@ class DASHTestTree : public adaptive::CDashTree uint64_t m_mockTime = 10000000L; std::chrono::system_clock::time_point m_mock_time_chrono = std::chrono::system_clock::now(); + + bool m_isManifestUpdSingleRun{false}; + std::string m_manifestUpdUrl; // Temporarily stores the url where to request the manifest update + bool m_isManifestUpdReady{false}; // Security check for the variable_condition + + static std::condition_variable s_cvDashManifestUpd; + static std::mutex s_mutexDashManifestUpd; }; class HLSTestTree : public adaptive::CHLSTree diff --git a/src/utils/PropertiesUtils.cpp b/src/utils/PropertiesUtils.cpp index de3c1f351..cda10e379 100644 --- a/src/utils/PropertiesUtils.cpp +++ b/src/utils/PropertiesUtils.cpp @@ -27,9 +27,10 @@ constexpr std::string_view PROP_LICENSE_FLAGS = "inputstream.adaptive.license_fl constexpr std::string_view PROP_SERVER_CERT = "inputstream.adaptive.server_certificate"; constexpr std::string_view PROP_MANIFEST_TYPE = "inputstream.adaptive.manifest_type"; //! @todo: deprecated, to be removed on next Kodi release -constexpr std::string_view PROP_MANIFEST_UPD_PARAM = "inputstream.adaptive.manifest_update_parameter"; +constexpr std::string_view PROP_MANIFEST_UPD_PARAM = "inputstream.adaptive.manifest_update_parameter"; //! @todo: deprecated, to be removed on next Kodi release constexpr std::string_view PROP_MANIFEST_PARAMS = "inputstream.adaptive.manifest_params"; constexpr std::string_view PROP_MANIFEST_HEADERS = "inputstream.adaptive.manifest_headers"; +constexpr std::string_view PROP_MANIFEST_UPD_PARAMS = "inputstream.adaptive.manifest_upd_params"; constexpr std::string_view PROP_STREAM_PARAMS = "inputstream.adaptive.stream_params"; constexpr std::string_view PROP_STREAM_HEADERS = "inputstream.adaptive.stream_headers"; @@ -101,9 +102,29 @@ KodiProperties UTILS::PROPERTIES::ParseKodiProperties( else LOG::LogF(LOGERROR, "Manifest type \"%s\" is not supported", prop.second.c_str()); } - else if (prop.first == PROP_MANIFEST_UPD_PARAM) + else if (prop.first == PROP_MANIFEST_UPD_PARAM) //! @todo: deprecated, to be removed on next Kodi release { - props.m_manifestUpdateParam = prop.second; + LOG::Log( + LOGWARNING, + "Warning \"inputstream.adaptive.manifest_update_parameter\" property is deprecated and" + " will be removed next Kodi version, use \"inputstream.adaptive.manifest_upd_params\"" + " instead.\nSee Wiki integration page for more details."); + if (prop.second == "full") + { + LOG::Log(LOGERROR, "The parameter \"full\" is no longer supported. For problems with live " + "streaming contents please open an Issue to the GitHub repository."); + } + else + props.m_manifestUpdateParam = prop.second; + } + else if (prop.first == PROP_MANIFEST_UPD_PARAMS) + { + // Should not happen that an add-on try to force the old "full" parameter value + // of PROP_MANIFEST_UPD_PARAM here but better verify it, in the future this can be removed + if (prop.second == "full") + LOG::Log(LOGERROR, "The parameter \"full\" is not supported."); + else + props.m_manifestUpdParams = prop.second; } else if (prop.first == PROP_MANIFEST_PARAMS) { diff --git a/src/utils/PropertiesUtils.h b/src/utils/PropertiesUtils.h index 9dbea0fc9..da4458ae1 100644 --- a/src/utils/PropertiesUtils.h +++ b/src/utils/PropertiesUtils.h @@ -42,9 +42,11 @@ struct KodiProperties bool m_isLicenseForceSecureDecoder{false}; std::string m_serverCertificate; ManifestType m_manifestType{ManifestType::UNKNOWN}; - // Can be used to force enable manifest updates, - // and optionally to set a specific url parameter - std::string m_manifestUpdateParam; + std::string m_manifestUpdateParam; // Deprecated + // HTTP parameters used to download manifest updates + // Dash manifest have the optional support of placeholder $START_NUMBER$ to allow set the segment + // start number to a parameter e.g. ?start_seq=$START_NUMBER$ become ?start_seq=10 + std::string m_manifestUpdParams; // HTTP parameters used to download manifests std::string m_manifestParams; // HTTP headers used to download manifests