diff --git a/CMakeLists.txt b/CMakeLists.txt index d0ef5bf2f..056331cbc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ set(ADP_SOURCES src/common/Representation.cpp src/common/ReprSelector.cpp src/common/Segment.cpp + src/common/SegmentBase.cpp src/common/SegmentList.cpp src/common/SegTemplate.cpp src/parser/CodecParser.cpp @@ -103,6 +104,7 @@ set(ADP_HEADERS src/common/Representation.h src/common/ReprSelector.h src/common/Segment.h + src/common/SegmentBase.h src/common/SegmentList.h src/common/SegTemplate.h src/parser/CodecParser.h diff --git a/src/Session.cpp b/src/Session.cpp index 70a203951..1b1f7c1e3 100644 --- a/src/Session.cpp +++ b/src/Session.cpp @@ -1004,7 +1004,7 @@ AP4_Movie* CSession::PrepareStream(CStream* stream, bool& needRefetch) } if (repr->GetContainerType() == ContainerType::MP4 && !repr->HasInitPrefixed() && - !repr->HasInitialization()) + !repr->HasInitSegment()) { //We'll create a Movie out of the things we got from manifest file //note: movie will be deleted in destructor of stream->input_file_ diff --git a/src/common/AdaptationSet.cpp b/src/common/AdaptationSet.cpp index e96b1be39..98a63033a 100644 --- a/src/common/AdaptationSet.cpp +++ b/src/common/AdaptationSet.cpp @@ -64,6 +64,7 @@ void PLAYLIST::CAdaptationSet::CopyHLSData(const CAdaptationSet* other) m_representations.push_back(std::move(rep)); } + m_baseUrl = other->m_baseUrl; m_streamType = other->m_streamType; m_duration = other->m_duration; m_startPts = other->m_startPts; diff --git a/src/common/AdaptiveStream.cpp b/src/common/AdaptiveStream.cpp index d488e3ff0..ded975131 100644 --- a/src/common/AdaptiveStream.cpp +++ b/src/common/AdaptiveStream.cpp @@ -237,80 +237,53 @@ bool AdaptiveStream::PrepareNextDownload(DownloadInfo& downloadInfo) segBuffer->buffer.clear(); downloadInfo.m_segmentBuffer = segBuffer; - return PrepareDownload(segBuffer->rep, segBuffer->segment, segBuffer->segment_number, - downloadInfo); + return PrepareDownload(segBuffer->rep, segBuffer->segment, downloadInfo); } bool AdaptiveStream::PrepareDownload(const PLAYLIST::CRepresentation* rep, const PLAYLIST::CSegment& seg, - const uint64_t segNum, DownloadInfo& downloadInfo) { - if (!rep) - return false; - std::string rangeHeader; std::string streamUrl; - if (!rep->HasSegmentBase()) + if (rep->HasSegmentTemplate()) { - if (!rep->HasSegmentTemplate()) + auto segTpl = rep->GetSegmentTemplate(); + + if (seg.IsInitialization()) // Templated initialization segment { - if (rep->HasSegmentsUrl()) - { - if (URL::IsUrlAbsolute(seg.url)) - streamUrl = seg.url; - else - streamUrl = URL::Join(rep->GetUrl(), seg.url); - } - else - streamUrl = rep->GetUrl(); - if (seg.range_begin_ != CSegment::NO_RANGE_VALUE) - { - uint64_t fileOffset = ~segNum ? m_segmentFileOffset : 0; - if (seg.range_end_ != CSegment::NO_RANGE_VALUE) - { - rangeHeader = StringUtils::Format("bytes=%llu-%llu", seg.range_begin_ + fileOffset, - seg.range_end_ + fileOffset); - } - else - { - rangeHeader = StringUtils::Format("bytes=%llu-", seg.range_begin_ + fileOffset); - } - } + streamUrl = segTpl->FormatUrl(segTpl->GetInitialization(), rep->GetId().data(), + rep->GetBandwidth(), rep->GetStartNumber(), 0); } - else if (~segNum) //templated segment + else // Templated media segment { - streamUrl = rep->GetSegmentTemplate()->GetMediaUrl(); - ReplacePlaceholder(streamUrl, "$Number", seg.range_end_); - ReplacePlaceholder(streamUrl, "$Time", seg.range_begin_); + streamUrl = segTpl->FormatUrl(segTpl->GetMedia(), rep->GetId().data(), rep->GetBandwidth(), + seg.m_number, seg.m_time); } - else //templated initialization segment - streamUrl = rep->GetUrl(); } else { - if (rep->HasSegmentTemplate() && ~segNum) + if (seg.url.empty()) + streamUrl = rep->GetBaseUrl(); + else + streamUrl = seg.url; + } + + if (URL::IsUrlRelative(streamUrl)) + streamUrl = URL::Join(rep->GetBaseUrl(), streamUrl); + + if (seg.range_begin_ != NO_VALUE) + { + uint64_t fileOffset = seg.IsInitialization() ? 0 : m_segmentFileOffset; + if (seg.range_end_ != NO_VALUE) { - streamUrl = rep->GetSegmentTemplate()->GetMediaUrl(); - ReplacePlaceholder(streamUrl, "$Number", rep->GetStartNumber()); - ReplacePlaceholder(streamUrl, "$Time", 0); + rangeHeader = StringUtils::Format("bytes=%llu-%llu", seg.range_begin_ + fileOffset, + seg.range_end_ + fileOffset); } else - streamUrl = rep->GetUrl(); - - if (seg.range_begin_ != CSegment::NO_RANGE_VALUE) { - uint64_t fileOffset = ~segNum ? m_segmentFileOffset : 0; - if (seg.range_end_ != CSegment::NO_RANGE_VALUE) - { - rangeHeader = StringUtils::Format("bytes=%llu-%llu", seg.range_begin_ + fileOffset, - seg.range_end_ + fileOffset); - } - else - { - rangeHeader = StringUtils::Format("bytes=%llu-", seg.range_begin_ + fileOffset); - } + rangeHeader = StringUtils::Format("bytes=%llu-", seg.range_begin_ + fileOffset); } } @@ -475,7 +448,7 @@ bool AdaptiveStream::parseIndexRange(PLAYLIST::CRepresentation* rep, const std:: if (rep->GetContainerType() == ContainerType::WEBM) { - if (!rep->m_segBaseIndexRangeMin) + if (rep->GetSegmentBase()->GetIndexRangeBegin() == 0) return false; WebmReader reader(&byteStream); @@ -510,7 +483,7 @@ bool AdaptiveStream::parseIndexRange(PLAYLIST::CRepresentation* rep, const std:: } else if (rep->GetContainerType() == ContainerType::MP4) { - if (rep->m_segBaseIndexRangeMin == 0) + if (rep->GetSegmentBase()->GetIndexRangeBegin() == 0) { AP4_File fileStream{byteStream, AP4_DefaultAtomFactory::Instance_, true}; AP4_Movie* movie{fileStream.GetMovie()}; @@ -521,11 +494,15 @@ bool AdaptiveStream::parseIndexRange(PLAYLIST::CRepresentation* rep, const std:: return false; } - rep->initialization_.range_begin_ = 0; + if (!rep->HasInitSegment()) + { + LOG::LogF(LOGERROR, "[AS-%u] Representation has no init segment", clsId); + return false; + } + rep->GetInitSegment()->range_begin_ = 0; AP4_Position pos; byteStream.Tell(pos); - rep->initialization_.range_end_ = pos - 1; - rep->SetHasInitialization(true); + rep->GetInitSegment()->range_end_ = pos - 1; } CSegment seg; @@ -565,7 +542,8 @@ bool AdaptiveStream::parseIndexRange(PLAYLIST::CRepresentation* rep, const std:: AP4_Position pos; byteStream.Tell(pos); - seg.range_end_ = pos + rep->m_segBaseIndexRangeMin + sidx->GetFirstOffset() - 1; + seg.range_end_ = + pos + rep->GetSegmentBase()->GetIndexRangeBegin() + sidx->GetFirstOffset() - 1; rep->SetTimescale(sidx->GetTimeScale()); rep->SetScaling(); @@ -645,10 +623,11 @@ bool AdaptiveStream::start_stream() thread_data_->signal_dl_.wait(lckdl); } + if (current_rep_->SegmentTimeline().IsEmpty() && current_rep_->HasSegmentBase()) { // ResolveSegmentbase assumes mutex_dl locked std::lock_guard lck(thread_data_->mutex_dl_); - if (!ResolveSegmentBase(current_rep_, true)) + if (!ResolveSegmentBase(current_rep_)) { state_ = STOPPED; return false; @@ -717,8 +696,7 @@ bool AdaptiveStream::start_stream() absolute_position_ = 0; // load the initialization segment - const CSegment* loadingSeg = current_rep_->get_initialization(); - if (loadingSeg) + if (current_rep_->HasInitSegment()) { StopWorker(PAUSED); WaitWorker(); @@ -728,10 +706,8 @@ bool AdaptiveStream::start_stream() segment_buffers_.rend() - available_segment_buffers_, segment_buffers_.rend()); ++available_segment_buffers_; - segment_buffers_[0]->segment.url.clear(); - segment_buffers_[0]->segment.Copy(loadingSeg); + segment_buffers_[0]->segment = *current_rep_->GetInitSegment(); segment_buffers_[0]->rep = current_rep_; - segment_buffers_[0]->segment_number = SEGMENT_NO_NUMBER; segment_buffers_[0]->buffer.clear(); segment_read_pos_ = 0; @@ -836,7 +812,7 @@ bool AdaptiveStream::ensureSegment() } if (valid_segment_buffers_) { - if (segment_buffers_[0]->segment_number != SEGMENT_NO_NUMBER) + if (!segment_buffers_[0]->segment.IsInitialization()) { nextSegment = current_rep_->get_segment(static_cast( segment_buffers_[0]->segment_number - current_rep_->GetStartNumber())); @@ -865,7 +841,7 @@ bool AdaptiveStream::ensureSegment() current_rep_->current_segment_ = nextSegment; ResetSegment(nextSegment); - if (observer_ && nextSegment != ¤t_rep_->initialization_ && + if (observer_ && !nextSegment->IsInitialization() && nextSegment->startPTS_ != NO_PTS_VALUE) { observer_->OnSegmentChanged(this); @@ -885,7 +861,7 @@ bool AdaptiveStream::ensureSegment() } } - if (segment_buffers_[0]->segment_number == SEGMENT_NO_NUMBER || + if (segment_buffers_[0]->segment.IsInitialization() || valid_segment_buffers_ == 0 || current_adp_->GetStreamType() != StreamType::VIDEO) { @@ -906,8 +882,9 @@ bool AdaptiveStream::ensureSegment() newRep = current_rep_; } - // Make sure, new representation has segments! - ResolveSegmentBase(newRep, false); // For DASH + // If the representation has been changed, segments may have to be generated (DASH) + if (newRep->SegmentTimeline().IsEmpty() && newRep->HasSegmentBase()) + ResolveSegmentBase(newRep); if (tree_.SecondsSinceRepUpdate(newRep) > 1) { @@ -926,7 +903,7 @@ bool AdaptiveStream::ensureSegment() if (futureSegment) { - segment_buffers_[updPos]->segment.Copy(futureSegment); + segment_buffers_[updPos]->segment = *futureSegment; segment_buffers_[updPos]->segment_number = newRep->GetStartNumber() + nextsegmentPos + updPos; segment_buffers_[updPos]->rep = newRep; @@ -1219,41 +1196,46 @@ bool AdaptiveStream::waitingForSegment(bool checkTime) const void AdaptiveStream::FixateInitialization(bool on) { - m_fixateInitialization = on && current_rep_->get_initialization() != nullptr; + m_fixateInitialization = on && current_rep_->HasInitSegment(); } -bool AdaptiveStream::ResolveSegmentBase(PLAYLIST::CRepresentation* rep, bool stopWorker) +bool AdaptiveStream::ResolveSegmentBase(PLAYLIST::CRepresentation* rep) { - /* If we have indexRangeExact SegmentBase, update SegmentList from SIDX */ - if (rep->HasSegmentBase()) - { - // We assume mutex_dl is locked so we can safely call prepare_download - CSegment seg; - unsigned int segNum = ~0U; - if (rep->m_segBaseIndexRangeMin > 0 || !rep->HasInitialization()) - { - seg.range_begin_ = rep->m_segBaseIndexRangeMin; - seg.range_end_ = rep->m_segBaseIndexRangeMax; - seg.pssh_set_ = 0; - segNum = 0; // It's no an initialization segment - } - else if (rep->HasInitialization()) - seg = *rep->get_initialization(); - else - return false; + // Get the byte ranges to download the index segment to generate media segments from SIDX atom - std::string sidxBuffer; - DownloadInfo downloadInfo; + auto& segBase = rep->GetSegmentBase(); + CSegment seg; - if (PrepareDownload(rep, seg, segNum, downloadInfo) && Download(downloadInfo, sidxBuffer) && - parseIndexRange(rep, sidxBuffer)) - { - rep->SetHasSegmentBase(false); - } - else - return false; + if (!rep->HasInitSegment() && segBase->GetIndexRangeBegin() == 0 && + segBase->GetIndexRangeEnd() > 0) + { + seg.SetIsInitialization(true); + seg.range_end_ = segBase->GetIndexRangeEnd(); + // Initialization segment will be set to representation by ParseIndexRange } - return true; + else if (segBase->GetIndexRangeBegin() > 0 || !rep->HasInitSegment()) + { + // It's no an initialization segment + seg.range_begin_ = segBase->GetIndexRangeBegin(); + seg.range_end_ = segBase->GetIndexRangeEnd(); + } + else if (rep->HasInitSegment()) + { + seg = *rep->GetInitSegment(); + } + else + return false; + + std::string sidxBuffer; + DownloadInfo downloadInfo; + // We assume mutex_dl is locked so we can safely call prepare_download + if (PrepareDownload(rep, seg, downloadInfo) && Download(downloadInfo, sidxBuffer) && + parseIndexRange(rep, sidxBuffer)) + { + return true; + } + + return false; } void AdaptiveStream::Stop() diff --git a/src/common/AdaptiveStream.h b/src/common/AdaptiveStream.h index 115a881d3..d332fc6f6 100644 --- a/src/common/AdaptiveStream.h +++ b/src/common/AdaptiveStream.h @@ -107,7 +107,7 @@ class AdaptiveStream; { std::string buffer; PLAYLIST::CSegment segment; - uint64_t segment_number{0}; // SEGMENT_NO_NUMBER is initialization segment! + uint64_t segment_number{0}; PLAYLIST::CRepresentation* rep{nullptr}; }; std::vector segment_buffers_; @@ -156,7 +156,6 @@ class AdaptiveStream; bool PrepareNextDownload(DownloadInfo& downloadInfo); bool PrepareDownload(const PLAYLIST::CRepresentation* rep, const PLAYLIST::CSegment& seg, - const uint64_t segNum, DownloadInfo& downloadInfo); void ResetSegment(const PLAYLIST::CSegment* segment); @@ -174,7 +173,7 @@ class AdaptiveStream; int SecondsSinceUpdate() const; static void ReplacePlaceholder(std::string& url, const std::string placeholder, uint64_t value); - bool ResolveSegmentBase(PLAYLIST::CRepresentation* rep, bool stopWorker); + bool ResolveSegmentBase(PLAYLIST::CRepresentation* rep); struct THREADDATA { diff --git a/src/common/AdaptiveTree.cpp b/src/common/AdaptiveTree.cpp index 3fb1048b4..ba912b9d2 100644 --- a/src/common/AdaptiveTree.cpp +++ b/src/common/AdaptiveTree.cpp @@ -91,9 +91,6 @@ namespace adaptive period->DecrasePSSHSetUsageCount(segment.pssh_set_); } - if (repr->HasInitialization() && repr->HasSegmentsUrl()) - repr->initialization_.url.clear(); - repr->SegmentTimeline().Clear(); repr->current_segment_ = nullptr; } diff --git a/src/common/AdaptiveUtils.cpp b/src/common/AdaptiveUtils.cpp index 6f6df24ae..25f8ce392 100644 --- a/src/common/AdaptiveUtils.cpp +++ b/src/common/AdaptiveUtils.cpp @@ -31,8 +31,8 @@ std::string_view PLAYLIST::StreamTypeToString(const StreamType streamType) bool PLAYLIST::ParseRangeRFC(std::string_view range, uint64_t& start, uint64_t& end) { //! @todo: must be reworked as https://httpwg.org/specs/rfc7233.html - uint64_t startVal; - uint64_t endVal; + uint64_t startVal{0}; + uint64_t endVal{0}; if (std::sscanf(range.data(), "%" SCNu64 "-%" SCNu64, &startVal, &endVal) > 0) { start = startVal; diff --git a/src/common/AdaptiveUtils.h b/src/common/AdaptiveUtils.h index 4db851027..04afa809b 100644 --- a/src/common/AdaptiveUtils.h +++ b/src/common/AdaptiveUtils.h @@ -35,6 +35,8 @@ constexpr size_t SEGMENT_NO_POS = std::numeric_limits::max(); constexpr uint64_t SEGMENT_NO_NUMBER = std::numeric_limits::max(); // Marker for undefined timestamp value constexpr uint64_t NO_PTS_VALUE = std::numeric_limits::max(); +// Marker for undefined value +constexpr uint64_t NO_VALUE = std::numeric_limits::max(); enum class EncryptionState { diff --git a/src/common/Representation.cpp b/src/common/Representation.cpp index 3baf1b4c2..c5035076d 100644 --- a/src/common/Representation.cpp +++ b/src/common/Representation.cpp @@ -52,10 +52,10 @@ bool PLAYLIST::CRepresentation::ContainsCodec(std::string_view codec, std::strin void PLAYLIST::CRepresentation::CopyHLSData(const CRepresentation* other) { - m_url = other->m_url; m_id = other->m_id; m_codecs = other->m_codecs; m_codecPrivateData = other->m_codecPrivateData; + m_baseUrl = other->m_baseUrl; m_sourceUrl = other->m_sourceUrl; m_bandwidth = other->m_bandwidth; m_sampleRate = other->m_sampleRate; @@ -71,10 +71,10 @@ void PLAYLIST::CRepresentation::CopyHLSData(const CRepresentation* other) timescale_ext_ = other->timescale_ext_; timescale_int_ = other->timescale_int_; - m_hasInitialization = other->m_hasInitialization; m_isIncludedStream = other->m_isIncludedStream; m_hasSegmentsUrl = other->m_hasSegmentsUrl; m_isEnabled = other->m_isEnabled; m_isWaitForSegment = other->m_isWaitForSegment; m_isDownloaded = other->m_isDownloaded; + m_initSegment = other->m_initSegment; } diff --git a/src/common/Representation.h b/src/common/Representation.h index 46e34ed83..feb18cb1d 100644 --- a/src/common/Representation.h +++ b/src/common/Representation.h @@ -12,6 +12,7 @@ #include "AdaptiveUtils.h" #include "CommonAttribs.h" #include "Segment.h" +#include "SegmentBase.h" #include "SegTemplate.h" #include "SegmentList.h" @@ -61,9 +62,6 @@ class ATTR_DLL_LOCAL CRepresentation : public CCommonSegAttribs, public CCommonA std::string_view GetId() const { return m_id; } void SetId(std::string_view id) { m_id = id; } - std::string GetUrl() const { return m_url; } - void SetUrl(std::string_view url) { m_url = url; } - std::string GetSourceUrl() const { return m_sourceUrl; } void SetSourceUrl(std::string_view sourceUrl) { m_sourceUrl = sourceUrl; } @@ -103,6 +101,10 @@ class ATTR_DLL_LOCAL CRepresentation : public CCommonSegAttribs, public CCommonA void SetSegmentTemplate(const CSegmentTemplate& segTemplate) { m_segmentTemplate = segTemplate; } bool HasSegmentTemplate() const { return m_segmentTemplate.has_value(); } + std::optional& GetSegmentBase() { return m_segmentBase; } + void SetSegmentBase(const CSegmentBase& segBase) { m_segmentBase = segBase; } + bool HasSegmentBase() const { return m_segmentBase.has_value(); } + uint32_t GetTimescale() const { return m_timescale; } void SetTimescale(uint32_t timescale) { m_timescale = timescale; } @@ -123,22 +125,9 @@ class ATTR_DLL_LOCAL CRepresentation : public CCommonSegAttribs, public CCommonA m_isSubtitleFileStream = isSubtitleFileStream; } - //! @todo: the use of HasInitialization/SetHasInitialization need to be improved maybe std::optional use - bool HasInitialization() const { return m_hasInitialization; } - void SetHasInitialization(bool hasInitialization) { m_hasInitialization = hasInitialization; } - bool HasInitPrefixed() const { return m_hasInitPrefixed; } void SetHasInitPrefixed(bool hasInitPrefixed) { m_hasInitPrefixed = hasInitPrefixed; } - bool HasSegmentBase() const { return m_hasSegmentBase; } - void SetHasSegmentBase(bool hasSegmentBase) { m_hasSegmentBase = hasSegmentBase; } - - bool HasSegBaseRangeExact() const { return m_hasSegBaseRangeExact; } - void SetHasSegBaseRangeExact(bool hasSegBaseRangeExact) - { - m_hasSegBaseRangeExact = hasSegBaseRangeExact; - } - bool HasSegmentsUrl() const { return m_hasSegmentsUrl; } void SetHasSegmentsUrl(bool hasSegmentsUrl) { m_hasSegmentsUrl = hasSegmentsUrl; } @@ -170,30 +159,22 @@ class ATTR_DLL_LOCAL CRepresentation : public CCommonSegAttribs, public CCommonA return left->m_bandwidth < right->m_bandwidth; } - //! @todo: make appropriate getters/setters and cleanups all following variables/methods - - uint64_t m_segBaseIndexRangeMin{0}; - uint64_t m_segBaseIndexRangeMax{0}; - uint16_t m_psshSetPos{PSSHSET_POS_DEFAULT}; // Index position of the PSSHSet const uint16_t GetPsshSetPos() const { return m_psshSetPos; } size_t expired_segments_{0}; CSegment* current_segment_{nullptr}; - //! @todo: controversial use of this variable, to investigate - //! if possible use std::optional to improve all related code - //! and segment methods below - CSegment initialization_; - const CSegment* get_initialization() const - { - return HasInitialization() ? &initialization_ : nullptr; - } + + bool HasInitSegment() const { return m_initSegment.has_value(); } + void SetInitSegment(CSegment initSegment) { m_initSegment = initSegment; } + std::optional& GetInitSegment() { return m_initSegment; } + CSegment* get_next_segment(const CSegment* seg) { - if (!seg || seg == &initialization_) + if (!seg || seg->IsInitialization()) return m_segmentTimeline.Get(0); size_t nextPos{m_segmentTimeline.GetPosition(seg) + 1}; @@ -278,7 +259,6 @@ class ATTR_DLL_LOCAL CRepresentation : public CCommonSegAttribs, public CCommonA protected: std::string m_id; - std::string m_url; std::string m_sourceUrl; std::string m_baseUrl; @@ -290,6 +270,8 @@ class ATTR_DLL_LOCAL CRepresentation : public CCommonSegAttribs, public CCommonA uint16_t m_hdcpVersion{0}; // 0 if not set std::optional m_segmentTemplate; + std::optional m_segmentBase; + std::optional m_initSegment; uint64_t m_startNumber{1}; @@ -299,11 +281,8 @@ class ATTR_DLL_LOCAL CRepresentation : public CCommonSegAttribs, public CCommonA uint32_t m_timescale{0}; bool m_isSubtitleFileStream{false}; - bool m_hasInitialization{false}; bool m_hasInitPrefixed{false}; - bool m_hasSegmentBase{false}; - bool m_hasSegBaseRangeExact{false}; bool m_hasSegmentsUrl{false}; bool m_isPrepared{false}; diff --git a/src/common/SegTemplate.cpp b/src/common/SegTemplate.cpp index cf3c16df4..1372d27b5 100644 --- a/src/common/SegTemplate.cpp +++ b/src/common/SegTemplate.cpp @@ -7,9 +7,13 @@ */ #include "SegTemplate.h" +#include "Segment.h" +#include "../utils/log.h" #include "kodi/tools/StringUtils.h" +#include // sprintf + using namespace PLAYLIST; using namespace kodi::tools; @@ -18,7 +22,7 @@ PLAYLIST::CSegmentTemplate::CSegmentTemplate(CSegmentTemplate* parent /* = nullp m_parentSegTemplate = parent; } -std::string_view PLAYLIST::CSegmentTemplate::GetInitialization() const +std::string PLAYLIST::CSegmentTemplate::GetInitialization() const { if (!m_initialization.empty()) return m_initialization; @@ -29,7 +33,7 @@ std::string_view PLAYLIST::CSegmentTemplate::GetInitialization() const return ""; // Default value } -std::string_view PLAYLIST::CSegmentTemplate::GetMedia() const +std::string PLAYLIST::CSegmentTemplate::GetMedia() const { if (!m_media.empty()) return m_media; @@ -40,17 +44,6 @@ std::string_view PLAYLIST::CSegmentTemplate::GetMedia() const return ""; // Default value } -std::string_view PLAYLIST::CSegmentTemplate::GetMediaUrl() const -{ - if (!m_mediaUrl.empty()) - return m_mediaUrl; - - if (m_parentSegTemplate) - return m_parentSegTemplate->GetMediaUrl(); - - return ""; // Default value -} - uint32_t PLAYLIST::CSegmentTemplate::GetTimescale() const { if (m_timescale.has_value()) @@ -88,3 +81,82 @@ bool PLAYLIST::CSegmentTemplate::HasVariableTime() const { return m_media.find("$Time") != std::string::npos; } + +CSegment PLAYLIST::CSegmentTemplate::MakeInitSegment() +{ + CSegment seg; + seg.SetIsInitialization(true); + seg.url = GetInitialization(); + return seg; +} + +std::string PLAYLIST::CSegmentTemplate::FormatUrl(const std::string url, + const std::string id, + const uint32_t bandwidth, + const uint64_t number, + const uint64_t time) +{ + std::vector chunks = StringUtils::Split(url, '$'); + + if (chunks.size() > 1) + { + for (size_t i = 0; i < chunks.size(); i++) + { + if (chunks.at(i) == "RepresentationID") + chunks.at(i) = id; + else if (chunks.at(i).find("Bandwidth") == 0) + FormatIdentifier(chunks.at(i), static_cast(bandwidth)); + else if (chunks.at(i).find("Number") == 0) + FormatIdentifier(chunks.at(i), number); + else if (chunks.at(i).find("Time") == 0) + FormatIdentifier(chunks.at(i), time); + } + + std::string replacedUrl = ""; + for (size_t i = 0; i < chunks.size(); i++) + { + replacedUrl += chunks.at(i); + } + return replacedUrl; + } + else + { + return url; + } +} + +void PLAYLIST::CSegmentTemplate::FormatIdentifier(std::string& identifier, const uint64_t value) +{ + size_t formatTagIndex = identifier.find("%0"); + std::string formatTag = "%01d"; // default format tag + + if (formatTagIndex != std::string::npos) + { + formatTag = identifier.substr(formatTagIndex); + switch (formatTag.back()) + { + case 'd': + case 'i': + case 'u': + case 'x': + case 'X': + case 'o': + break; // supported conversions as dash.js + default: + return; // leave as is + } + } + // sprintf expect the right length of data type + if (formatTag.size() > 2 && + (formatTag[formatTag.size() - 2] != 'l' && formatTag[formatTag.size() - 3] != 'l')) + { + formatTag.insert(formatTag.size() - 1, "ll"); + } + + char substitution[128]; + if (std::sprintf(substitution, formatTag.c_str(), value) > 0) + identifier = substitution; + else + LOG::LogF(LOGERROR, "Cannot convert value \"%llu\" with \"%s\" format tag", value, + formatTag.c_str()); +} diff --git a/src/common/SegTemplate.h b/src/common/SegTemplate.h index 339b7939f..7d0a29691 100644 --- a/src/common/SegTemplate.h +++ b/src/common/SegTemplate.h @@ -20,6 +20,9 @@ namespace PLAYLIST { +// Forward +class CSegment; + // SegmentTemplate class provide segment template data // of class itself or when not set of the parent class (if any). class ATTR_DLL_LOCAL CSegmentTemplate @@ -28,15 +31,13 @@ class ATTR_DLL_LOCAL CSegmentTemplate CSegmentTemplate(CSegmentTemplate* parent = nullptr); ~CSegmentTemplate() {} - std::string_view GetInitialization() const; + std::string GetInitialization() const; void SetInitialization(std::string_view init) { m_initialization = init; } - std::string_view GetMedia() const; - void SetMedia(std::string_view media) { m_media = media; } + bool HasInitialization() const { return !GetInitialization().empty(); } - // Same content of "GetMedia" method but with placeholder $RepresentationID$ and $Bandwidth$ filled - std::string_view GetMediaUrl() const; - void SetMediaUrl(std::string_view mediaUrl) { m_mediaUrl = mediaUrl; } + std::string GetMedia() const; + void SetMedia(std::string_view media) { m_media = media; } uint32_t GetTimescale() const; void SetTimescale(uint32_t timescale) { m_timescale = timescale; } @@ -48,11 +49,20 @@ class ATTR_DLL_LOCAL CSegmentTemplate void SetStartNumber(uint64_t startNumber) { m_startNumber = startNumber; } bool HasVariableTime() const; + + CSegment MakeInitSegment(); + + std::string FormatUrl(const std::string url, + const std::string id, + const uint32_t bandwidth, + const uint64_t number, + const uint64_t time); private: + void FormatIdentifier(std::string& identifier, const uint64_t value); + std::string m_initialization; std::string m_media; - std::string m_mediaUrl; std::optional m_timescale; std::optional m_duration; std::optional m_startNumber; diff --git a/src/common/Segment.cpp b/src/common/Segment.cpp index 0df938612..f6a0b77b8 100644 --- a/src/common/Segment.cpp +++ b/src/common/Segment.cpp @@ -7,14 +7,3 @@ */ #include "Segment.h" -#include "AdaptiveUtils.h" - -#include "kodi/tools/StringUtils.h" - -using namespace PLAYLIST; -using namespace kodi::tools; - -void PLAYLIST::CSegment::Copy(const CSegment* src) -{ - *this = *src; -} diff --git a/src/common/Segment.h b/src/common/Segment.h index 7618ec99e..d5aad7da4 100644 --- a/src/common/Segment.h +++ b/src/common/Segment.h @@ -28,21 +28,29 @@ class ATTR_DLL_LOCAL CSegment CSegment() {} ~CSegment(){} - static constexpr uint64_t NO_RANGE_VALUE = std::numeric_limits::max(); - //! @todo: create getters/setters - //! its possible add a way to determinate if range is set - //Either byterange start or timestamp or NO_RANGE_VALUE - uint64_t range_begin_ = NO_RANGE_VALUE; - //Either byterange end or sequence_id if range_begin is NO_RANGE_VALUE - uint64_t range_end_ = NO_RANGE_VALUE; + // Byte range start + uint64_t range_begin_ = NO_VALUE; + // Byte range end + uint64_t range_end_ = NO_VALUE; std::string url; uint64_t startPTS_ = NO_PTS_VALUE; uint64_t m_duration = 0; // If available gives the media duration of a segment (depends on type of stream e.g. HLS) uint16_t pssh_set_ = PSSHSET_POS_DEFAULT; - void Copy(const CSegment* src); + uint64_t m_time{0}; + uint64_t m_number{0}; + + /*! + * \brief Determines if it is an initialization segment. + * \return True if it is an initialization segment, otherwise false for media segment. + */ + bool IsInitialization() const { return m_isInitialization; } + void SetIsInitialization(bool isInitialization) { m_isInitialization = isInitialization; } + +private: + bool m_isInitialization{false}; }; -} // namespace adaptive +} // namespace PLAYLIST diff --git a/src/common/SegmentBase.cpp b/src/common/SegmentBase.cpp new file mode 100644 index 000000000..c4688c41f --- /dev/null +++ b/src/common/SegmentBase.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SegmentBase.h" + +#include "AdaptiveUtils.h" +#include "Segment.h" + +using namespace PLAYLIST; + +void PLAYLIST::CSegmentBase::SetIndexRange(std::string_view indexRange) +{ + if (!ParseRangeRFC(indexRange, m_indexRangeBegin, m_indexRangeEnd)) + LOG::LogF(LOGERROR, "Failed to parse \"indexrange\" attribute"); +} + +void PLAYLIST::CSegmentBase::SetInitRange(std::string_view range) +{ + if (!ParseRangeRFC(range, m_initRangeBegin, m_initRangeEnd)) + LOG::LogF(LOGERROR, "Failed to parse initialization \"range\" attribute"); +} + +CSegment PLAYLIST::CSegmentBase::MakeIndexSegment() +{ + CSegment seg; + seg.range_begin_ = m_indexRangeBegin; + seg.range_end_ = m_indexRangeEnd; + return seg; +} + +CSegment PLAYLIST::CSegmentBase::MakeInitSegment() +{ + CSegment seg; + seg.SetIsInitialization(true); + seg.startPTS_ = 0; + if (HasInitialization()) + { + seg.range_begin_ = m_initRangeBegin; + seg.range_end_ = m_initRangeEnd; + } + else + LOG::LogF(LOGWARNING, + "The \"range\" attribute is missing in the SegmentBase initialization tag"); + + return seg; +} diff --git a/src/common/SegmentBase.h b/src/common/SegmentBase.h new file mode 100644 index 000000000..67700cea5 --- /dev/null +++ b/src/common/SegmentBase.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "AdaptiveUtils.h" + +#ifdef INPUTSTREAM_TEST_BUILD +#include "../test/KodiStubs.h" +#else +#include +#endif + +#include + +namespace PLAYLIST +{ +// Forward +class CSegment; + +class ATTR_DLL_LOCAL CSegmentBase +{ +public: + CSegmentBase() = default; + ~CSegmentBase() = default; + + void SetIndexRange(std::string_view indexRange); + void SetInitRange(std::string_view range); + + void SetIndexRangeBegin(uint64_t value) { m_indexRangeBegin = value; } + void SetIndexRangeEnd(uint64_t value) { m_indexRangeEnd = value; } + + uint64_t GetIndexRangeBegin() { return m_indexRangeBegin; } + uint64_t GetIndexRangeEnd() { return m_indexRangeEnd; } + + void SetIsRangeExact(bool isRangeExact) { m_isRangeExact = isRangeExact; } + + void SetTimescale(uint32_t timescale) { m_timescale = timescale; } + uint32_t GetTimescale() { return m_timescale; } + + bool HasInitialization() { return m_initRangeBegin != NO_VALUE && m_initRangeEnd != NO_VALUE; } + + CSegment MakeIndexSegment(); + CSegment MakeInitSegment(); + +private: + uint64_t m_indexRangeBegin{0}; + uint64_t m_indexRangeEnd{0}; + + uint64_t m_initRangeBegin{NO_VALUE}; + uint64_t m_initRangeEnd{NO_VALUE}; + + uint32_t m_timescale{0}; + bool m_isRangeExact{false}; +}; + +} // namespace PLAYLIST diff --git a/src/common/SegmentList.cpp b/src/common/SegmentList.cpp index 22ecc60ef..8d8c68350 100644 --- a/src/common/SegmentList.cpp +++ b/src/common/SegmentList.cpp @@ -7,6 +7,7 @@ */ #include "SegmentList.h" +#include "Segment.h" using namespace PLAYLIST; @@ -37,3 +38,20 @@ uint64_t PLAYLIST::CSegmentList::GetPresTimeOffset() const return m_ptsOffset; return m_parentSegList->GetPresTimeOffset(); } + +void PLAYLIST::CSegmentList::SetInitRange(std::string_view range) +{ + if (!ParseRangeRFC(range, m_initRangeBegin, m_initRangeEnd)) + LOG::LogF(LOGERROR, "Failed to parse \"range\" attribute"); +} + +CSegment PLAYLIST::CSegmentList::MakeInitSegment() +{ + CSegment seg; + seg.SetIsInitialization(true); + seg.startPTS_ = 0; + seg.range_begin_ = m_initRangeBegin; + seg.range_end_ = m_initRangeEnd; + seg.url = m_initSourceUrl; + return seg; +} diff --git a/src/common/SegmentList.h b/src/common/SegmentList.h index 0cbbc5939..003e909a4 100644 --- a/src/common/SegmentList.h +++ b/src/common/SegmentList.h @@ -8,6 +8,8 @@ #pragma once +#include "AdaptiveUtils.h" + #ifdef INPUTSTREAM_TEST_BUILD #include "../test/KodiStubs.h" #else @@ -19,6 +21,8 @@ namespace PLAYLIST { +// Forward +class CSegment; class ATTR_DLL_LOCAL CSegmentList { @@ -38,11 +42,20 @@ class ATTR_DLL_LOCAL CSegmentList uint64_t GetPresTimeOffset() const; void SetPresTimeOffset(uint64_t ptsOffset) { m_ptsOffset = ptsOffset; } + void SetInitSourceUrl(std::string_view url) { m_initSourceUrl = url; } + + void SetInitRange(std::string_view range); + bool HasInitialization() { return m_initRangeBegin != NO_VALUE && m_initRangeEnd != NO_VALUE; } + CSegment MakeInitSegment(); + private: uint64_t m_startNumber{0}; uint64_t m_duration{0}; uint32_t m_timescale{0}; uint64_t m_ptsOffset{0}; + uint64_t m_initRangeBegin = NO_VALUE; + uint64_t m_initRangeEnd = NO_VALUE; + std::string m_initSourceUrl; CSegmentList* m_parentSegList{nullptr}; }; diff --git a/src/main.cpp b/src/main.cpp index 621aa89b3..eb3fd023e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -275,7 +275,7 @@ bool CInputStreamAdaptive::OpenStream(int streamid) if (rep->IsSubtitleFileStream()) { stream->SetReader(std::make_unique( - rep->GetUrl(), streamid, stream->m_info.GetCodecInternalName())); + rep->GetBaseUrl(), streamid, stream->m_info.GetCodecInternalName())); return stream->GetReader()->GetInformation(stream->m_info); } diff --git a/src/parser/DASHTree.cpp b/src/parser/DASHTree.cpp index 626ef4236..7595f7b04 100644 --- a/src/parser/DASHTree.cpp +++ b/src/parser/DASHTree.cpp @@ -744,51 +744,33 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr, xml_node nodeSegBase = nodeRepr.child("SegmentBase"); if (nodeSegBase) { - uint64_t rangeStart{0}; - uint64_t rangeEnd{0}; - - if (ParseRangeRFC(XML::GetAttrib(nodeSegBase, "indexRange"), rangeStart, rangeEnd)) - { - repr->m_segBaseIndexRangeMin = rangeStart; - repr->m_segBaseIndexRangeMax = rangeEnd; - } - else - LOG::LogF(LOGERROR, "Cannot get \"indexRange\" attribute from tag"); - - repr->SetHasSegmentBase(true); + CSegmentBase segBase; + std::string indexRange; + if (XML::QueryAttrib(nodeSegBase, "indexRange", indexRange)) + segBase.SetIndexRange(indexRange); if (XML::GetAttrib(nodeSegBase, "indexRangeExact") == "true") - repr->SetHasSegBaseRangeExact(true); + segBase.SetIsRangeExact(true); uint32_t timescale; if (XML::QueryAttrib(nodeSegBase, "timescale", timescale)) + { + segBase.SetTimescale(timescale); repr->SetTimescale(timescale); + } // Parse child tag xml_node nodeInit = nodeSegBase.child("Initialization"); if (nodeInit) { - CSegment seg; - uint64_t rangeStart{0}; - uint64_t rangeEnd{0}; - - seg.startPTS_ = 0; - std::string range; if (XML::QueryAttrib(nodeInit, "range", range)) - { - if (ParseRangeRFC(range, rangeStart, rangeEnd)) - { - seg.range_begin_ = rangeStart; - seg.range_end_ = rangeEnd; - } - else - LOG::LogF(LOGERROR, "Failed to parse \"range\" attribute in the tag"); - } + segBase.SetInitRange(range); - repr->initialization_ = seg; - repr->SetHasInitialization(true); + repr->SetInitSegment(segBase.MakeInitSegment()); } + + repr->SetSegmentBase(segBase); } // Parse tag @@ -801,8 +783,8 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr, ParseSegmentTemplate(nodeSegTpl, &segTemplate); - if (!segTemplate.GetInitialization().empty()) - repr->SetHasInitialization(true); + if (segTemplate.HasInitialization()) + repr->SetInitSegment(segTemplate.MakeInitSegment()); // Parse child xml_node nodeSegTL = nodeSegTpl.child("SegmentTimeline"); @@ -817,12 +799,6 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr, totalTimeSecs, &segTemplate); repr->nextPts_ = startPts; - - if (!repr->SegmentTimeline().IsEmpty()) - { - repr->SetHasInitialization(true); - repr->initialization_ = CSegment(); - } } repr->SetTimescale(segTemplate.GetTimescale()); repr->SetSegmentTemplate(segTemplate); @@ -837,8 +813,8 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr, repr->SetStartNumber(segTemplate.GetStartNumber()); - if (!repr->GetSegmentTemplate()->GetInitialization().empty()) - repr->SetHasInitialization(true); + if (segTemplate.HasInitialization()) + repr->SetInitSegment(segTemplate.MakeInitSegment()); } // Parse tag @@ -876,39 +852,19 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr, repr->GetStartNumber() * repr->GetDuration()); } - repr->SetSegmentList(segList); - // Parse child tag xml_node nodeInit = nodeSeglist.child("Initialization"); if (nodeInit) { - CSegment seg; - uint64_t rangeStart{0}; - uint64_t rangeEnd{0}; - - seg.startPTS_ = 0; - std::string range; if (XML::QueryAttrib(nodeInit, "range", range)) - { - if (ParseRangeRFC(range, rangeStart, rangeEnd)) - { - seg.range_begin_ = rangeStart; - seg.range_end_ = rangeEnd; - } - else - LOG::LogF(LOGERROR, "Failed to parse \"range\" attribute in the tag"); - } + segList.SetInitRange(range); std::string sourceURL; if (XML::QueryAttrib(nodeInit, "sourceURL", sourceURL)) - { - seg.url = sourceURL; - repr->SetHasSegmentsUrl(true); - } + segList.SetInitSourceUrl(sourceURL); - repr->initialization_ = seg; - repr->SetHasInitialization(true); + repr->SetInitSegment(segList.MakeInitSegment()); } if (segList.GetTimescale() > 0 && segList.GetDuration() > 0) @@ -941,11 +897,6 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr, seg.range_begin_ = rangeStart; seg.range_end_ = rangeEnd; } - else - { - if (isTimelineEmpty) - seg.range_end_ = repr->GetStartNumber(); - } uint32_t* tlDuration = adpSet->SegmentTimelineDuration().Get(index); uint64_t duration = tlDuration ? *tlDuration : segList.GetDuration(); @@ -979,6 +930,8 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr, if (segList.GetTimescale() > 0) repr->SetTimescale(segList.GetTimescale()); + + repr->SetSegmentList(segList); } // Parse tags @@ -1046,21 +999,21 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr, if (!segTemplate->GetInitialization().empty()) { CSegment seg; + seg.SetIsInitialization(true); seg.startPTS_ = 0; - repr->initialization_ = seg; - repr->SetHasInitialization(true); + repr->SetInitSegment(seg); } CSegment segTl; - segTl.range_begin_ = adpSet->GetStartPTS(); - segTl.range_end_ = repr->GetStartNumber(); + segTl.m_time = adpSet->GetStartPTS(); + segTl.m_number = repr->GetStartNumber(); segTl.startPTS_ = adpSet->GetStartPTS(); if (m_isLive && !segTemplate->HasVariableTime() && segTemplate->GetDuration() > 0) { uint64_t sampleTime = period->GetStart() / 1000; - segTl.range_end_ += + segTl.m_number += static_cast(static_cast(stream_start_ - available_time_ - reprTotalTimeSecs - sampleTime) * segTemplate->GetTimescale() / segTemplate->GetDuration() + @@ -1084,8 +1037,8 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr, uint32_t* tlDuration = adpSet->SegmentTimelineDuration().Get(pos); uint32_t duration = tlDuration ? *tlDuration : segTplDuration; - segTl.range_begin_ += duration; - segTl.range_end_ += 1; + segTl.m_time += duration; + segTl.m_number += 1; segTl.startPTS_ += duration; } @@ -1100,26 +1053,21 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr, else if (!repr->HasSegmentBase() && !repr->IsSubtitleFileStream()) { //Let us try to extract the fragments out of SIDX atom - repr->SetHasSegmentBase(true); - - repr->m_segBaseIndexRangeMin = 0; + CSegmentBase segBase; + + segBase.SetIndexRangeBegin(0); //! @todo: Explain the reason of these specific values static const uint64_t indexRangeMax = 1024 * 200; - repr->m_segBaseIndexRangeMax = indexRangeMax; - } - } + segBase.SetIndexRangeEnd(indexRangeMax); - if (repr->HasSegmentBase() && repr->m_segBaseIndexRangeMin == 0 && - repr->m_segBaseIndexRangeMax > 0 && !repr->HasInitialization()) - { - // AdaptiveStream::ParseIndexRange will fix the initialization max to its real value. - CSegment seg; - seg.range_end_ = repr->m_segBaseIndexRangeMax; - repr->initialization_ = seg; - repr->SetHasInitialization(true); + repr->SetSegmentBase(segBase); + } } - if (repr->HasInitialization() && !repr->SegmentTimeline().IsEmpty()) + //! @todo: "init prefixed" behaviour is not so clear and may be invalidated by ResolveSegmentBase (?) + //! we need to investigate if we can move this check where its used by CSession::PrepareStream + //! and remove HasInitPrefixed statement + if (repr->HasInitSegment() && !repr->SegmentTimeline().IsEmpty()) { // we assume that we have a MOOV atom included in each segment (max 100k = youtube) repr->SetHasInitPrefixed(true); @@ -1131,37 +1079,6 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr, if (period->GetDuration() == 0) period->SetDuration(repr->GetDuration()); - // Create the url - std::string url; - - if (repr->HasSegmentTemplate()) - { - if (repr->HasInitialization()) - { - url = ReplacePlaceHolders(repr->GetSegmentTemplate()->GetInitialization().data(), - repr->GetId(), repr->GetBandwidth()); - - if (URL::IsUrlRelative(url)) - url = URL::Join(repr->GetBaseUrl().data(), url); - - repr->GetSegmentTemplate()->SetInitialization(url); - } - else - url = repr->GetBaseUrl(); - - std::string mediaUrl = repr->GetSegmentTemplate()->GetMedia().data(); - mediaUrl = ReplacePlaceHolders(mediaUrl, repr->GetId(), repr->GetBandwidth()); - - if (URL::IsUrlRelative(mediaUrl)) - mediaUrl = URL::Join(repr->GetBaseUrl().data(), mediaUrl); - - repr->GetSegmentTemplate()->SetMediaUrl(mediaUrl); - } - else - url = repr->GetBaseUrl(); - - repr->SetUrl(url); - // YouTube fix if (repr->GetStartNumber() > m_firstStartNumber) m_firstStartNumber = repr->GetStartNumber(); @@ -1251,12 +1168,12 @@ uint64_t adaptive::CDashTree::ParseTagSegmentTimeline(xml_node nodeSegTL, else SCTimeline.GetData().reserve(EstimateSegmentsCount(duration, timescale, totalTimeSecs)); - seg.range_end_ = startNumber; + seg.m_number = startNumber; } else - seg.range_end_ = SCTimeline.GetData().back().range_end_ + 1; + seg.m_number = SCTimeline.GetData().back().m_number + 1; - seg.range_begin_ = nextPts; + seg.m_time = nextPts; seg.startPTS_ = nextPts; for (; repeat > 0; --repeat) @@ -1265,8 +1182,8 @@ uint64_t adaptive::CDashTree::ParseTagSegmentTimeline(xml_node nodeSegTL, startPts = seg.startPTS_; nextPts += duration; - seg.range_begin_ = nextPts; - seg.range_end_ += 1; + seg.m_time = nextPts; + seg.m_number += 1; seg.startPTS_ += duration; } } @@ -1705,28 +1622,28 @@ void adaptive::CDashTree::RefreshLiveSegments() if (repr->HasSegmentTimeline()) { - uint64_t search_pts = updRepr->SegmentTimeline().Get(0)->range_begin_; + uint64_t search_pts = updRepr->SegmentTimeline().Get(0)->m_time; uint64_t misaligned = 0; for (const auto& segment : repr->SegmentTimeline().GetData()) { if (misaligned) { - uint64_t ptsDiff = segment.range_begin_ - (&segment - 1)->range_begin_; + uint64_t ptsDiff = segment.m_time - (&segment - 1)->m_time; // our misalignment is small ( < 2%), let's decrement the start number if (misaligned < (ptsDiff * 2 / 100)) repr->SetStartNumber(repr->GetStartNumber() - 1); break; } - if (segment.range_begin_ == search_pts) + if (segment.m_time == search_pts) break; - else if (segment.range_begin_ > search_pts) + else if (segment.m_time > search_pts) { if (&repr->SegmentTimeline().GetData().front() == &segment) { repr->SetStartNumber(repr->GetStartNumber() - 1); break; } - misaligned = search_pts - (&segment - 1)->range_begin_; + misaligned = search_pts - (&segment - 1)->m_time; } else repr->SetStartNumber(repr->GetStartNumber() + 1); @@ -1735,10 +1652,10 @@ void adaptive::CDashTree::RefreshLiveSegments() else if (updRepr->SegmentTimeline().Get(0)->startPTS_ == repr->SegmentTimeline().Get(0)->startPTS_) { - uint64_t search_re = updRepr->SegmentTimeline().Get(0)->range_end_; + uint64_t search_re = updRepr->SegmentTimeline().Get(0)->m_number; for (const auto& segment : repr->SegmentTimeline().GetData()) { - if (segment.range_end_ >= search_re) + if (segment.m_number >= search_re) break; repr->SetStartNumber(repr->GetStartNumber() + 1); } diff --git a/src/parser/HLSTree.cpp b/src/parser/HLSTree.cpp index a44357429..588aa468d 100644 --- a/src/parser/HLSTree.cpp +++ b/src/parser/HLSTree.cpp @@ -217,10 +217,6 @@ PLAYLIST::PrepareRepStatus adaptive::CHLSTree::prepareRepresentation(PLAYLIST::C // Pssh set used between segments uint16_t psshSetPos = PSSHSET_POS_DEFAULT; - CSegment segInit; // Initialization segment - std::string segInitUrl; // Initialization segment URL - bool hasSegmentInit{false}; - uint32_t discontCount{0}; bool isExtM3Uformat{false}; @@ -282,33 +278,30 @@ PLAYLIST::PrepareRepStatus adaptive::CHLSTree::prepareRepresentation(PLAYLIST::C else if (tagName == "#EXT-X-MAP") { auto attribs = ParseTagAttributes(tagValue); + CSegment segInit; + + if (STRING::KeyExists(attribs, "BYTERANGE")) + { + if (ParseRangeValues(attribs["BYTERANGE"], segInit.range_end_, segInit.range_begin_)) + { + segInit.range_end_ = segInit.range_begin_ + segInit.range_end_ - 1; + } + } if (STRING::KeyExists(attribs, "URI")) { std::string uri = attribs["URI"]; if (URL::IsUrlRelative(uri)) - segInitUrl = URL::Join(baseUrl, uri); - else - segInitUrl = uri; + uri = URL::Join(baseUrl, uri); - segInit.url = segInitUrl; + segInit.SetIsInitialization(true); + segInit.url = uri; segInit.startPTS_ = NO_PTS_VALUE; segInit.pssh_set_ = PSSHSET_POS_DEFAULT; - rep->SetHasInitialization(true); + rep->SetInitSegment(segInit); rep->SetContainerType(ContainerType::MP4); - hasSegmentInit = true; - } - - if (STRING::KeyExists(attribs, "BYTERANGE")) - { - if (ParseRangeValues(attribs["BYTERANGE"], segInit.range_end_, segInit.range_begin_)) - { - segInit.range_end_ = segInit.range_begin_ + segInit.range_end_ - 1; - } } - else - segInit.range_begin_ = CSegment::NO_RANGE_VALUE; } else if (tagName == "#EXT-X-MEDIA-SEQUENCE") { @@ -346,7 +339,7 @@ PLAYLIST::PrepareRepStatus adaptive::CHLSTree::prepareRepresentation(PLAYLIST::C { ParseRangeValues(tagValue, newSegment->range_end_, newSegment->range_begin_); - if (newSegment->range_begin_ == CSegment::NO_RANGE_VALUE) + if (newSegment->range_begin_ == NO_VALUE) { if (newSegments.GetSize() > 0) newSegment->range_begin_ = newSegments.Get(newSegments.GetSize() - 1)->range_end_ + 1; @@ -414,7 +407,7 @@ PLAYLIST::PrepareRepStatus adaptive::CHLSTree::prepareRepresentation(PLAYLIST::C continue; } - if (!segmentHasByteRange || rep->GetUrl().empty()) + if (!segmentHasByteRange || rep->GetBaseUrl().empty()) { std::string url; if (URL::IsUrlRelative(line)) @@ -427,7 +420,7 @@ PLAYLIST::PrepareRepStatus adaptive::CHLSTree::prepareRepresentation(PLAYLIST::C newSegment->url = url; } else - rep->SetUrl(url); + rep->SetBaseUrl(url); } if (currentEncryptionType == EncryptionType::AES128) @@ -508,15 +501,11 @@ PLAYLIST::PrepareRepStatus adaptive::CHLSTree::prepareRepresentation(PLAYLIST::C rep->SegmentTimeline().Swap(newSegments); rep->SetStartNumber(newStartNumber); - if (hasSegmentInit) - { - std::swap(rep->initialization_, segInit); - // EXT-X-MAP init url must persist to next period until overrided by new tag - segInit.url = segInitUrl; - } if (m_periods.size() == ++discontCount) { auto newPeriod = CPeriod::MakeUniquePtr(); + // CopyHLSData will copy also the init segment in the representations + // that must persist to next period until overrided by new EXT-X-MAP tag newPeriod->CopyHLSData(m_currentPeriod); period = newPeriod.get(); m_periods.push_back(std::move(newPeriod)); @@ -537,11 +526,8 @@ PLAYLIST::PrepareRepStatus adaptive::CHLSTree::prepareRepresentation(PLAYLIST::C period->SetEncryptionState(EncryptionState::ENCRYPTED_SUPPORTED); } - if (hasSegmentInit && !segInitUrl.empty()) - { - rep->SetHasInitialization(true); + if (rep->HasInitSegment()) rep->SetContainerType(ContainerType::MP4); - } } else if (tagName == "#EXT-X-ENDLIST") { @@ -570,9 +556,6 @@ PLAYLIST::PrepareRepStatus adaptive::CHLSTree::prepareRepresentation(PLAYLIST::C rep->SegmentTimeline().Swap(newSegments); rep->SetStartNumber(newStartNumber); - if (hasSegmentInit) - std::swap(rep->initialization_, segInit); - uint64_t reprDuration{0}; if (rep->SegmentTimeline().Get(0)) reprDuration = currentSegStartPts - rep->SegmentTimeline().Get(0)->startPTS_; diff --git a/src/parser/SmoothTree.cpp b/src/parser/SmoothTree.cpp index a05378511..50a18494f 100644 --- a/src/parser/SmoothTree.cpp +++ b/src/parser/SmoothTree.cpp @@ -250,7 +250,7 @@ void adaptive::CSmoothTree::ParseTagQualityLevel(pugi::xml_node nodeQI, { std::unique_ptr repr = CRepresentation::MakeUniquePtr(adpSet); - repr->SetUrl(adpSet->GetBaseUrl()); + repr->SetBaseUrl(adpSet->GetBaseUrl()); repr->SetTimescale(timescale); repr->SetId(XML::GetAttrib(nodeQI, "Index")); @@ -321,13 +321,12 @@ void adaptive::CSmoothTree::ParseTagQualityLevel(pugi::xml_node nodeQI, CSegmentTemplate segTpl; - segTpl.SetMedia(repr->GetUrl()); - std::string mediaUrl = repr->GetUrl(); - + std::string mediaUrl = repr->GetBaseUrl(); + // Convert markers to DASH template identification tag STRING::ReplaceFirst(mediaUrl, "{start time}", "$Time$"); - STRING::ReplaceFirst(mediaUrl, "{bitrate}", std::to_string(repr->GetBandwidth())); + STRING::ReplaceFirst(mediaUrl, "{bitrate}", "$Bandwidth$"); - segTpl.SetMediaUrl(mediaUrl); + segTpl.SetMedia(mediaUrl); repr->SetSegmentTemplate(segTpl); @@ -341,8 +340,8 @@ void adaptive::CSmoothTree::ParseTagQualityLevel(pugi::xml_node nodeQI, { CSegment seg; seg.startPTS_ = nextStartPts; - seg.range_begin_ = nextStartPts + base_time_; - seg.range_end_ = index; + seg.m_time = nextStartPts + base_time_; + seg.m_number = index; repr->SegmentTimeline().GetData().emplace_back(seg); diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index b06d8424d..3e775e395 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -32,6 +32,7 @@ add_executable(${BINARY} ../common/Representation.cpp ../common/ReprSelector.cpp ../common/Segment.cpp + ../common/SegmentBase.cpp ../common/SegmentList.cpp ../common/SegTemplate.cpp ../oscompat.cpp diff --git a/src/test/TestDASHTree.cpp b/src/test/TestDASHTree.cpp index 264836519..0127a781e 100644 --- a/src/test/TestDASHTree.cpp +++ b/src/test/TestDASHTree.cpp @@ -96,10 +96,18 @@ class DASHTreeAdaptiveStreamTest : public DASHTreeTest testStream = newStream; } + TestAdaptiveStream* NewStream(PLAYLIST::CAdaptationSet* adp, bool playTimeshiftBuffer = true) + { + return NewStream(adp, nullptr, playTimeshiftBuffer); + } + TestAdaptiveStream* NewStream(PLAYLIST::CAdaptationSet* adp, - bool playTimeshiftBuffer = true) + PLAYLIST::CRepresentation* repr, bool playTimeshiftBuffer = true) { - auto initialRepr{tree->GetRepChooser()->GetRepresentation(adp)}; + PLAYLIST::CRepresentation* initialRepr = repr; + if (!repr) + initialRepr = tree->GetRepChooser()->GetRepresentation(adp); + UTILS::PROPERTIES::KodiProperties kodiProps; kodiProps.m_playTimeshiftBuffer = playTimeshiftBuffer; @@ -159,67 +167,86 @@ TEST_F(DASHTreeTest, CalculateBaseURLFromBaseURLTag) EXPECT_EQ(tree->m_currentPeriod->GetBaseUrl(), "https://foo.bar/mpd/"); } -TEST_F(DASHTreeTest, CalculateBaseURLWithNoSlashOutsidePeriod) +TEST_F(DASHTreeAdaptiveStreamTest, CalculateBaseURLWithNoSlashOutsidePeriod) { // BaseURL outside period with no trailing slash OpenTestFile("mpd/segtpl_baseurl_noslash_outside.mpd", "https://bit.ly/abcd"); - auto& segtpl = tree->m_periods[0]->GetAdaptationSets()[0]->GetRepresentations()[0]->GetSegmentTemplate(); - EXPECT_EQ(tree->m_currentPeriod->GetBaseUrl(), "https://foo.bar/mpd/"); - EXPECT_EQ(STR(segtpl->GetInitialization()), "https://foo.bar/mpd/V300/init.mp4"); - EXPECT_EQ(STR(segtpl->GetMediaUrl()), "https://foo.bar/mpd/V300/$Number$.m4s"); + + SetTestStream(NewStream(tree->m_periods[0]->GetAdaptationSets()[0].get())); + + testStream->start_stream(); + ReadSegments(testStream, 16, 5); + EXPECT_EQ(testHelper::downloadList[0], "https://foo.bar/mpd/V300/init.mp4"); + EXPECT_EQ(testHelper::downloadList[1], "https://foo.bar/mpd/V300/4999851.m4s"); } -TEST_F(DASHTreeTest, CalculateSegTplWithNoSlashes) +TEST_F(DASHTreeAdaptiveStreamTest, CalculateSegTplWithNoSlashes) { // BaseURL inside period with no trailing slash, uses segtpl, media/init doesn't start with slash OpenTestFile("mpd/segtpl_baseurl_noslashs.mpd", "https://foo.bar/initialpath/test.mpd"); - auto& segtpl = tree->m_periods[0]->GetAdaptationSets()[0]->GetRepresentations()[0]->GetSegmentTemplate(); + EXPECT_EQ(tree->m_currentPeriod->GetBaseUrl(), "https://foo.bar/guid.ism/dash/"); - EXPECT_EQ(STR(segtpl->GetInitialization()), "https://foo.bar/guid.ism/dash/media-video=66000.dash"); - EXPECT_EQ(STR(segtpl->GetMediaUrl()), "https://foo.bar/guid.ism/dash/media-video=66000-$Number$.m4s"); + SetTestStream(NewStream(tree->m_periods[0]->GetAdaptationSets()[0].get())); + + testStream->start_stream(); + ReadSegments(testStream, 16, 5); + EXPECT_EQ(testHelper::downloadList[0], "https://foo.bar/guid.ism/dash/media-video=66000.dash"); + EXPECT_EQ(testHelper::downloadList[1], "https://foo.bar/guid.ism/dash/media-video=66000-1.m4s"); } -TEST_F(DASHTreeTest, CalculateSegTplWithMediaInitSlash) +TEST_F(DASHTreeAdaptiveStreamTest, CalculateSegTplWithMediaInitSlash) { // BaseURL inside period with no trailing slash, uses segtpl, media/init starts with slash OpenTestFile("mpd/segtpl_slash_baseurl_noslash.mpd", "https://foo.bar/initialpath/test.mpd"); - auto& segtpl = tree->m_periods[0]->GetAdaptationSets()[0]->GetRepresentations()[0]->GetSegmentTemplate(); + EXPECT_EQ(tree->m_currentPeriod->GetBaseUrl(), "https://foo.bar/guid.ism/dash/"); - EXPECT_EQ(STR(segtpl->GetInitialization()), "https://foo.bar/media-video=66000.dash"); - EXPECT_EQ(STR(segtpl->GetMediaUrl()), "https://foo.bar/media-video=66000-$Number$.m4s"); + SetTestStream(NewStream(tree->m_periods[0]->GetAdaptationSets()[0].get())); + + testStream->start_stream(); + ReadSegments(testStream, 16, 5); + EXPECT_EQ(testHelper::downloadList[0], "https://foo.bar/media-video=66000.dash"); + EXPECT_EQ(testHelper::downloadList[1], "https://foo.bar/media-video=66000-1.m4s"); } -TEST_F(DASHTreeTest, CalculateSegTplWithBaseURLSlash) +TEST_F(DASHTreeAdaptiveStreamTest, CalculateSegTplWithBaseURLSlash) { // BaseURL inside period with trailing slash, uses segtpl, media/init doesn't start with slash OpenTestFile("mpd/segtpl_noslash_baseurl_slash.mpd", "https://foo.bar/initialpath/test.mpd"); - auto& segtpl = tree->m_periods[0]->GetAdaptationSets()[0]->GetRepresentations()[0]->GetSegmentTemplate(); + EXPECT_EQ(tree->m_currentPeriod->GetBaseUrl(), "https://foo.bar/guid.ism/dash/"); - EXPECT_EQ(STR(segtpl->GetInitialization()), "https://foo.bar/guid.ism/dash/media-video=66000.dash"); - EXPECT_EQ(STR(segtpl->GetMediaUrl()), "https://foo.bar/guid.ism/dash/media-video=66000-$Number$.m4s"); + SetTestStream(NewStream(tree->m_periods[0]->GetAdaptationSets()[0].get())); + + testStream->start_stream(); + ReadSegments(testStream, 16, 5); + EXPECT_EQ(testHelper::downloadList[0], "https://foo.bar/guid.ism/dash/media-video=66000.dash"); + EXPECT_EQ(testHelper::downloadList[1], "https://foo.bar/guid.ism/dash/media-video=66000-1.m4s"); } -TEST_F(DASHTreeTest, CalculateSegTplWithBaseURLAndMediaInitSlash) +TEST_F(DASHTreeAdaptiveStreamTest, CalculateSegTplWithBaseURLAndMediaInitSlash) { // BaseURL inside period with trailing slash, uses segtpl, media/init starts with slash OpenTestFile("mpd/segtpl_slash_baseurl_slash.mpd", "https://foo.bar/initialpath/test.mpd"); - auto& segtpl = tree->m_periods[0]->GetAdaptationSets()[0]->GetRepresentations()[0]->GetSegmentTemplate(); + EXPECT_EQ(tree->m_currentPeriod->GetBaseUrl(), "https://foo.bar/guid.ism/dash/"); + + SetTestStream(NewStream(tree->m_periods[0]->GetAdaptationSets()[0].get())); - EXPECT_EQ(STR(segtpl->GetInitialization()), "https://foo.bar/media-video=66000.dash"); - EXPECT_EQ(STR(segtpl->GetMediaUrl()), "https://foo.bar/media-video=66000-$Number$.m4s"); + testStream->start_stream(); + ReadSegments(testStream, 16, 5); + EXPECT_EQ(testHelper::downloadList[0], "https://foo.bar/media-video=66000.dash"); + EXPECT_EQ(testHelper::downloadList[1], "https://foo.bar/media-video=66000-1.m4s"); } TEST_F(DASHTreeTest, CalculateBaseURLInRepRangeBytes) { // Byteranged indexing OpenTestFile("mpd/segmentbase.mpd", "https://foo.bar/test.mpd"); - EXPECT_EQ(tree->m_periods[0]->GetAdaptationSets()[0]->GetRepresentations()[0]->GetUrl(), + EXPECT_EQ(tree->m_periods[0]->GetAdaptationSets()[0]->GetRepresentations()[0]->GetBaseUrl(), "https://foo.bar/video/23.98p/r0/vid10.mp4"); } @@ -232,8 +259,8 @@ TEST_F(DASHTreeTest, CalculateCorrectSegmentNumbersFromSegmentTimeline) tree->m_periods[0]->GetAdaptationSets()[0]->GetRepresentations()[0]->SegmentTimeline(); EXPECT_EQ(segments.GetSize(), 13); - EXPECT_EQ(segments.Get(0)->range_end_, 487050); - EXPECT_EQ(segments.Get(12)->range_end_, 487062); + EXPECT_EQ(segments.Get(0)->m_number, 487050); + EXPECT_EQ(segments.Get(12)->m_number, 487062); } TEST_F(DASHTreeTest, CalculateCorrectSegmentNumbersFromSegmentTemplateWithPTO) @@ -245,8 +272,8 @@ TEST_F(DASHTreeTest, CalculateCorrectSegmentNumbersFromSegmentTemplateWithPTO) auto& segments = tree->m_periods[0]->GetAdaptationSets()[0]->GetRepresentations()[0]->SegmentTimeline(); EXPECT_EQ(segments.GetSize(), 450); - EXPECT_EQ(segments.Get(0)->range_end_, 404305525); - EXPECT_EQ(segments.Get(449)->range_end_, 404305974); + EXPECT_EQ(segments.Get(0)->m_number, 404305525); + EXPECT_EQ(segments.Get(449)->m_number, 404305974); } TEST_F(DASHTreeTest, CalculateCorrectSegmentNumbersFromSegmentTemplateWithOldPublishTime) @@ -258,8 +285,8 @@ TEST_F(DASHTreeTest, CalculateCorrectSegmentNumbersFromSegmentTemplateWithOldPub auto& segments = tree->m_periods[0]->GetAdaptationSets()[0]->GetRepresentations()[0]->SegmentTimeline(); EXPECT_EQ(segments.GetSize(), 30); - EXPECT_EQ(segments.Get(0)->range_end_, 603272); - EXPECT_EQ(segments.Get(29)->range_end_, 603301); + EXPECT_EQ(segments.Get(0)->m_number, 603272); + EXPECT_EQ(segments.Get(29)->m_number, 603301); } TEST_F(DASHTreeTest, CalculateCorrectFpsScaleFromAdaptionSet) @@ -294,18 +321,17 @@ TEST_F(DASHTreeAdaptiveStreamTest, replacePlaceHolders) { OpenTestFile("mpd/placeholders.mpd", "https://foo.bar/placeholders.mpd"); SetTestStream(NewStream(tree->m_periods[0]->GetAdaptationSets()[0].get())); - + testStream->start_stream(); ReadSegments(testStream, 16, 5); EXPECT_EQ(testHelper::downloadList[0], "https://foo.bar/videosd-400x224/init.mp4"); EXPECT_EQ(testHelper::downloadList[4], "https://foo.bar/videosd-400x224/segment_487053.m4s"); - + SetTestStream(NewStream(tree->m_periods[0]->GetAdaptationSets()[1].get())); testStream->start_stream(); ReadSegments(testStream, 16, 5); EXPECT_EQ(testHelper::downloadList[1], "https://foo.bar/videosd-400x224/segment_00487050.m4s"); EXPECT_EQ(testHelper::downloadList[4], "https://foo.bar/videosd-400x224/segment_00487053.m4s"); - SetTestStream(NewStream(tree->m_periods[0]->GetAdaptationSets()[2].get())); testStream->start_stream(); @@ -450,61 +476,89 @@ TEST_F(DASHTreeTest, CalculateMultipleSegTpl) auto& adpSets = tree->m_periods[0]->GetAdaptationSets(); - EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[0]->GetSegmentTemplate()->GetInitialization()), "https://foo.bar/dash/3c1055cb-a842-4449-b393-7f31693b4a8f_1_448x252init.mp4"); - EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[0]->GetSegmentTemplate()->GetMediaUrl()), "https://foo.bar/dash/3c1055cb-a842-4449-b393-7f31693b4a8f_1_448x252_$Number%09d$.mp4"); + EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[0]->GetSegmentTemplate()->GetInitialization()), "3c1055cb-a842-4449-b393-7f31693b4a8f_1_448x252init.mp4"); + EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[0]->GetSegmentTemplate()->GetMedia()), "3c1055cb-a842-4449-b393-7f31693b4a8f_1_448x252_$Number%09d$.mp4"); EXPECT_EQ(adpSets[0]->GetRepresentations()[0]->GetSegmentTemplate()->GetTimescale(), 120000); - EXPECT_EQ(adpSets[0]->GetRepresentations()[0]->SegmentTimeline().Get(0)->range_end_, 3); + EXPECT_EQ(adpSets[0]->GetRepresentations()[0]->SegmentTimeline().Get(0)->m_number, 3); - EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[1]->GetSegmentTemplate()->GetInitialization()), "https://foo.bar/dash/3c1055cb-a842-4449-b393-7f31693b4a8f_2_1920x1080init.mp4"); - EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[1]->GetSegmentTemplate()->GetMediaUrl()), "https://foo.bar/dash/3c1055cb-a842-4449-b393-7f31693b4a8f_2_1920x1080_$Number%09d$.mp4"); + EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[1]->GetSegmentTemplate()->GetInitialization()), "3c1055cb-a842-4449-b393-7f31693b4a8f_2_1920x1080init.mp4"); + EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[1]->GetSegmentTemplate()->GetMedia()), "3c1055cb-a842-4449-b393-7f31693b4a8f_2_1920x1080_$Number%09d$.mp4"); EXPECT_EQ(adpSets[0]->GetRepresentations()[1]->GetSegmentTemplate()->GetTimescale(), 90000); - EXPECT_EQ(adpSets[0]->GetRepresentations()[1]->SegmentTimeline().Get(0)->range_end_, 5); + EXPECT_EQ(adpSets[0]->GetRepresentations()[1]->SegmentTimeline().Get(0)->m_number, 5); - EXPECT_EQ(STR(adpSets[1]->GetRepresentations()[0]->GetSegmentTemplate()->GetInitialization()), "https://foo.bar/dash/3c1055cb-a842-4449-b393-7f31693b4a8f_aac1init.mp4"); - EXPECT_EQ(STR(adpSets[1]->GetRepresentations()[0]->GetSegmentTemplate()->GetMediaUrl()), "https://foo.bar/dash/3c1055cb-a842-4449-b393-7f31693b4a8f_aac1_$Number%09d$.mp4"); + EXPECT_EQ(STR(adpSets[1]->GetRepresentations()[0]->GetSegmentTemplate()->GetInitialization()), "3c1055cb-a842-4449-b393-7f31693b4a8f_aac1init.mp4"); + EXPECT_EQ(STR(adpSets[1]->GetRepresentations()[0]->GetSegmentTemplate()->GetMedia()), "3c1055cb-a842-4449-b393-7f31693b4a8f_aac1_$Number%09d$.mp4"); EXPECT_EQ(adpSets[1]->GetRepresentations()[0]->GetSegmentTemplate()->GetTimescale(), 48000); - EXPECT_EQ(adpSets[1]->GetRepresentations()[0]->SegmentTimeline().Get(0)->range_end_, 1); + EXPECT_EQ(adpSets[1]->GetRepresentations()[0]->SegmentTimeline().Get(0)->m_number, 1); - EXPECT_EQ(STR(adpSets[2]->GetRepresentations()[0]->GetSegmentTemplate()->GetInitialization()), "https://foo.bar/dash/abc_aac1init.mp4"); - EXPECT_EQ(STR(adpSets[2]->GetRepresentations()[0]->GetSegmentTemplate()->GetMediaUrl()), "https://foo.bar/dash/abc2_$Number%09d$.mp4"); + EXPECT_EQ(STR(adpSets[2]->GetRepresentations()[0]->GetSegmentTemplate()->GetInitialization()), "abc_aac1init.mp4"); + EXPECT_EQ(STR(adpSets[2]->GetRepresentations()[0]->GetSegmentTemplate()->GetMedia()), "abc2_$Number%09d$.mp4"); EXPECT_EQ(adpSets[2]->GetRepresentations()[0]->GetSegmentTemplate()->GetTimescale(), 68000); - EXPECT_EQ(adpSets[2]->GetRepresentations()[0]->SegmentTimeline().Get(0)->range_end_, 5); + EXPECT_EQ(adpSets[2]->GetRepresentations()[0]->SegmentTimeline().Get(0)->m_number, 5); } -TEST_F(DASHTreeTest, CalculateRedirectSegTpl) +TEST_F(DASHTreeAdaptiveStreamTest, CalculateRedirectSegTpl) { testHelper::effectiveUrl = "https://foo.bar/mpd/stream.mpd"; OpenTestFile("mpd/segtpl.mpd", "https://bit.ly/abcd.mpd"); auto& adpSets = tree->m_periods[0]->GetAdaptationSets(); - EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[0]->GetSegmentTemplate()->GetInitialization()), "https://foo.bar/mpd/V300/init.mp4"); - EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[0]->GetSegmentTemplate()->GetMediaUrl()), "https://foo.bar/mpd/V300/$Number$.m4s"); + SetTestStream(NewStream(adpSets[0].get())); + testStream->start_stream(); + ReadSegments(testStream, 16, 5); - EXPECT_EQ(STR(adpSets[1]->GetRepresentations()[0]->GetSegmentTemplate()->GetInitialization()), "https://foo.bar/A48/init.mp4"); - EXPECT_EQ(STR(adpSets[1]->GetRepresentations()[0]->GetSegmentTemplate()->GetMediaUrl()), "https://foo.bar/A48/$Number$.m4s"); + EXPECT_EQ(testHelper::downloadList[0], "https://foo.bar/mpd/V300/init.mp4"); + EXPECT_EQ(testHelper::downloadList[1], "https://foo.bar/mpd/V300/4999851.m4s"); + + SetTestStream(NewStream(adpSets[1].get())); + testStream->start_stream(); + ReadSegments(testStream, 16, 5); + EXPECT_EQ(testHelper::downloadList[0], "https://foo.bar/A48/init.mp4"); + EXPECT_EQ(testHelper::downloadList[1], "https://foo.bar/A48/4999851.m4s"); } -TEST_F(DASHTreeTest, CalculateReprensentationBaseURL) +TEST_F(DASHTreeAdaptiveStreamTest, CalculateReprensentationBaseURL) { OpenTestFile("mpd/rep_base_url.mpd", "https://bit.ly/mpd/abcd.mpd"); auto& adpSets = tree->m_periods[0]->GetAdaptationSets(); + auto adpSet0 = adpSets[0].get(); + + SetTestStream(NewStream(adpSet0, adpSet0->GetRepresentations()[0].get())); + testStream->start_stream(); + ReadSegments(testStream, 16, 5); + EXPECT_EQ(testHelper::downloadList[0], "https://foo.bar/mpd/slices/A_init.mp4"); + EXPECT_EQ(testHelper::downloadList[1], "https://foo.bar/mpd/slices/A00000714.m4f"); + + SetTestStream(NewStream(adpSet0, adpSet0->GetRepresentations()[1].get())); + testStream->start_stream(); + ReadSegments(testStream, 16, 5); + EXPECT_EQ(testHelper::downloadList[0], "https://bit.ly/mpd/B_init.mp4"); + EXPECT_EQ(testHelper::downloadList[1], "https://bit.ly/mpd/B00000714.m4f"); + + auto adpSet1 = adpSets[1].get(); - EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[0]->GetSegmentTemplate()->GetInitialization()), "https://foo.bar/mpd/slices/A_init.mp4"); - EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[0]->GetSegmentTemplate()->GetMediaUrl()), "https://foo.bar/mpd/slices/A$Number%08d$.m4f"); - EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[1]->GetSegmentTemplate()->GetInitialization()), "https://bit.ly/mpd/B_init.mp4"); - EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[1]->GetSegmentTemplate()->GetMediaUrl()), "https://bit.ly/mpd/B$Number%08d$.m4f"); - - EXPECT_EQ(STR(adpSets[1]->GetRepresentations()[0]->GetSegmentTemplate()->GetInitialization()), "https://foo.bar/mpd/slices/A_init.mp4"); - EXPECT_EQ(STR(adpSets[1]->GetRepresentations()[0]->GetSegmentTemplate()->GetMediaUrl()), "https://foo.bar/mpd/slices/A$Number%08d$.m4f"); - EXPECT_EQ(STR(adpSets[1]->GetRepresentations()[1]->GetSegmentTemplate()->GetInitialization()), "https://foo.bar/mpd/slices2/B_init.mp4"); - EXPECT_EQ(STR(adpSets[1]->GetRepresentations()[1]->GetSegmentTemplate()->GetMediaUrl()), "https://foo.bar/mpd/slices2/B$Number%08d$.m4f"); - EXPECT_EQ(STR(adpSets[1]->GetRepresentations()[2]->GetSegmentTemplate()->GetInitialization()), "https://foo.bar/mpd/slices2/C_init.mp4"); - EXPECT_EQ(STR(adpSets[1]->GetRepresentations()[2]->GetSegmentTemplate()->GetMediaUrl()), "https://foo.bar/mpd/slices2/C$Number%08d$.m4f"); + SetTestStream(NewStream(adpSet1, adpSet1->GetRepresentations()[0].get())); + testStream->start_stream(); + ReadSegments(testStream, 16, 5); + EXPECT_EQ(testHelper::downloadList[0], "https://foo.bar/mpd/slices/A_init.mp4"); + EXPECT_EQ(testHelper::downloadList[1], "https://foo.bar/mpd/slices/A00000714.m4f"); + + SetTestStream(NewStream(adpSet1, adpSet1->GetRepresentations()[1].get())); + testStream->start_stream(); + ReadSegments(testStream, 16, 5); + EXPECT_EQ(testHelper::downloadList[0], "https://foo.bar/mpd/slices2/B_init.mp4"); + EXPECT_EQ(testHelper::downloadList[1], "https://foo.bar/mpd/slices2/B00000714.m4f"); + + SetTestStream(NewStream(adpSet1, adpSet1->GetRepresentations()[2].get())); + testStream->start_stream(); + ReadSegments(testStream, 16, 5); + EXPECT_EQ(testHelper::downloadList[0], "https://foo.bar/mpd/slices2/C_init.mp4"); + EXPECT_EQ(testHelper::downloadList[1], "https://foo.bar/mpd/slices2/C00000714.m4f"); } -TEST_F(DASHTreeTest, CalculateReprensentationBaseURLMultiple) +TEST_F(DASHTreeAdaptiveStreamTest, CalculateReprensentationBaseURLMultiple) { OpenTestFile( "mpd/rep_base_url_multiple.mpd", @@ -512,11 +566,18 @@ TEST_F(DASHTreeTest, CalculateReprensentationBaseURLMultiple) auto& adpSets = tree->m_periods[0]->GetAdaptationSets(); - EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[0]->GetSegmentTemplate()->GetInitialization()), "https://prod.foobar.com/video/assets/p/c30668ab1d7d10166938f06b9643a254.urlset/init-f1-v1-x3.mp4"); - EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[0]->GetSegmentTemplate()->GetMediaUrl()), "https://prod.foobar.com/video/assets/p/c30668ab1d7d10166938f06b9643a254.urlset/fragment-$Number$-f1-v1-x3.m4s"); + SetTestStream(NewStream(adpSets[0].get())); + testStream->start_stream(); + ReadSegments(testStream, 16, 5); + + EXPECT_EQ(testHelper::downloadList[0], "https://prod.foobar.com/video/assets/p/c30668ab1d7d10166938f06b9643a254.urlset/init-f1-v1-x3.mp4"); + EXPECT_EQ(testHelper::downloadList[1], "https://prod.foobar.com/video/assets/p/c30668ab1d7d10166938f06b9643a254.urlset/fragment-1-f1-v1-x3.m4s"); - EXPECT_EQ(STR(adpSets[1]->GetRepresentations()[0]->GetSegmentTemplate()->GetInitialization()), "https://prod.foobar.com/audio/assets/p/c30668ab1d7d10166938f06b9643a254.urlset/init-f1-a1-x3.mp4"); - EXPECT_EQ(STR(adpSets[1]->GetRepresentations()[0]->GetSegmentTemplate()->GetMediaUrl()), "https://prod.foobar.com/audio/assets/p/c30668ab1d7d10166938f06b9643a254.urlset/fragment-$Number$-f1-a1-x3.m4s"); + SetTestStream(NewStream(adpSets[1].get())); + testStream->start_stream(); + ReadSegments(testStream, 16, 5); + EXPECT_EQ(testHelper::downloadList[0], "https://prod.foobar.com/audio/assets/p/c30668ab1d7d10166938f06b9643a254.urlset/init-f1-a1-x3.mp4"); + EXPECT_EQ(testHelper::downloadList[1], "https://prod.foobar.com/audio/assets/p/c30668ab1d7d10166938f06b9643a254.urlset/fragment-1-f1-a1-x3.m4s"); } TEST_F(DASHTreeAdaptiveStreamTest, MisalignedSegmentTimeline) @@ -573,8 +634,6 @@ TEST_F(DASHTreeTest, SegmentTemplateStartNumber) auto& adpSets = tree->m_periods[0]->GetAdaptationSets(); - EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[0]->GetSegmentTemplate()->GetInitialization()), "https://vod.service.net/SGP1/highlightpost/1234567890/1/h264/288000/init_dash.m4v"); - EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[0]->GetSegmentTemplate()->GetMediaUrl()), "https://vod.service.net/SGP1/highlightpost/1234567890/1/h264/288000/seg_dash_$Number$.m4v"); EXPECT_EQ(adpSets[0]->GetRepresentations()[0]->GetSegmentTemplate()->GetStartNumber(), 0); EXPECT_EQ(adpSets[0]->GetRepresentations()[0]->GetSegmentTemplate()->GetTimescale(), 25000); EXPECT_EQ(adpSets[0]->GetRepresentations()[0]->GetSegmentTemplate()->GetDuration(), 48000); @@ -583,12 +642,12 @@ TEST_F(DASHTreeTest, SegmentTemplateStartNumber) auto& rep1Timeline = adpSets[0]->GetRepresentations()[0]->SegmentTimeline(); EXPECT_EQ(rep1Timeline.GetSize(), 144); - EXPECT_EQ(rep1Timeline.Get(0)->range_begin_, 0); - EXPECT_EQ(rep1Timeline.Get(0)->range_end_, 0); + EXPECT_EQ(rep1Timeline.Get(0)->m_time, 0); + EXPECT_EQ(rep1Timeline.Get(0)->m_number, 0); - EXPECT_EQ(rep1Timeline.Get(1)->range_begin_, 48000); - EXPECT_EQ(rep1Timeline.Get(1)->range_end_, 1); + EXPECT_EQ(rep1Timeline.Get(1)->m_time, 48000); + EXPECT_EQ(rep1Timeline.Get(1)->m_number, 1); - EXPECT_EQ(rep1Timeline.Get(143)->range_begin_, 6864000); - EXPECT_EQ(rep1Timeline.Get(143)->range_end_, 143); + EXPECT_EQ(rep1Timeline.Get(143)->m_time, 6864000); + EXPECT_EQ(rep1Timeline.Get(143)->m_number, 143); }