Skip to content

Commit bc2cf31

Browse files
authored
Merge pull request #1937 from CastagnaIT/ss_timeshift
[SmoothTree] Implemented timeshift
2 parents bae371a + c5ead7c commit bc2cf31

File tree

5 files changed

+236
-36
lines changed

5 files changed

+236
-36
lines changed

src/common/AdaptiveStream.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,10 +1197,6 @@ bool adaptive::AdaptiveStream::seek_time(double seek_seconds, bool preceeding, b
11971197
if (choosen_seg && current_rep_->Timeline().Get(choosen_seg)->startPTS_ > sec_in_ts)
11981198
--choosen_seg;
11991199

1200-
// Never seek into expired segments.....
1201-
if (choosen_seg < current_rep_->expired_segments_)
1202-
choosen_seg = current_rep_->expired_segments_;
1203-
12041200
if (!preceeding && sec_in_ts > current_rep_->Timeline().Get(choosen_seg)->startPTS_ &&
12051201
current_adp_->GetStreamType() == StreamType::VIDEO)
12061202
{

src/common/Representation.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,6 @@ class ATTR_DLL_LOCAL CRepresentation : public CCommonSegAttribs, public CCommonA
168168
return left->m_bandwidth < right->m_bandwidth;
169169
}
170170

171-
size_t expired_segments_{0};
172-
173171
std::optional<CSegment> current_segment_;
174172

175173
bool HasInitSegment() const { return m_initSegment.has_value(); }

src/parser/SmoothTree.cpp

Lines changed: 210 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
#include "SmoothTree.h"
1010

11+
#include "CompKodiProps.h"
12+
#include "SrvBroker.h"
1113
#include "decrypters/Helpers.h"
1214
#include "decrypters/HelperPr.h"
1315
#include "utils/StringUtils.h"
@@ -17,6 +19,8 @@
1719
#include "utils/log.h"
1820
#include "pugixml.hpp"
1921

22+
#include <numeric> // accumulate
23+
2024
using namespace adaptive;
2125
using namespace pugi;
2226
using namespace PLAYLIST;
@@ -52,8 +56,6 @@ bool adaptive::CSmoothTree::Open(const std::string& url,
5256

5357
m_currentPeriod = m_periods[0].get();
5458

55-
CreateSegmentTimeline();
56-
5759
return true;
5860
}
5961

@@ -84,14 +86,35 @@ bool adaptive::CSmoothTree::ParseManifest(const std::string& data)
8486
{
8587
m_isLive = true;
8688
available_time_ = stream_start_;
89+
m_updateInterval = 5000;
8790
}
8891

89-
if (!m_isLive)
90-
period->SetDuration(XML::GetAttribUint64(nodeSSM, "Duration"));
92+
m_mediaPresDuration = XML::GetAttribUint64(nodeSSM, "Duration") * 1000 / period->GetTimescale();
93+
94+
/*! @todo: future rework needed, we currently manage the TSB based on the segments/chunks
95+
*! provided by the manifest that should fall within the duration DVRWindowLength,
96+
*! but the DVRWindowLength can be also 0 or omitted that means infinite TSB,
97+
*! in this case, we should allow for a broader TSB,
98+
*! so segments should be collected until the maximum TSB is covered
99+
*! Currently each parser has its own segment management, more likely a new common
100+
*! interface should be considered to manage segements and TSB
101+
*/
102+
if (m_isLive)
103+
{
104+
m_dvrWindowLength =
105+
XML::GetAttribUint64(nodeSSM, "DVRWindowLength") * 1000 / period->GetTimescale();
91106

92-
period->SetTlDuration(XML::GetAttribUint64(nodeSSM, "Duration"));
107+
if (m_dvrWindowLength == 0) // Zero means infinite TSB
108+
{
109+
m_dvrWindowLength = 14400000; // Limit to default 4 hours
93110

94-
m_totalTime = period->GetDuration() * 1000 / period->GetTimescale();
111+
auto& manifestCfg = CSrvBroker::GetKodiProps().GetManifestConfig();
112+
if (manifestCfg.timeShiftBufferLimit.has_value())
113+
m_dvrWindowLength = *manifestCfg.timeShiftBufferLimit * 1000;
114+
}
115+
}
116+
else
117+
period->SetDuration(XML::GetAttribUint64(nodeSSM, "Duration"));
95118

96119
// Parse <Protection> tag
97120
DRM::PRHeaderParser protParser;
@@ -134,9 +157,10 @@ bool adaptive::CSmoothTree::ParseManifest(const std::string& data)
134157
}
135158

136159
// Parse <StreamIndex> tags
160+
std::set<uint64_t> ptsStartList;
137161
for (xml_node node : nodeSSM.children("StreamIndex"))
138162
{
139-
ParseTagStreamIndex(node, period.get(), drmInfos);
163+
ParseTagStreamIndex(node, period.get(), drmInfos, ptsStartList);
140164
}
141165

142166
if (period->GetAdaptationSets().empty())
@@ -145,14 +169,28 @@ bool adaptive::CSmoothTree::ParseManifest(const std::string& data)
145169
return false;
146170
}
147171

172+
if (ptsStartList.size() > 1)
173+
{
174+
m_ptsBase = *ptsStartList.begin(); // Select the lower PTS
175+
LOG::Log(
176+
LOGDEBUG,
177+
"StreamIndex tags use async PTS for chunk start, segments will be aligned to PTS: %llu",
178+
m_ptsBase);
179+
}
180+
148181
m_periods.push_back(std::move(period));
149182

183+
CreateSegmentTimeline();
184+
185+
UpdateTotalTime();
186+
150187
return true;
151188
}
152189

153190
void adaptive::CSmoothTree::ParseTagStreamIndex(pugi::xml_node nodeSI,
154191
PLAYLIST::CPeriod* period,
155-
const std::vector<DRM::DRMInfo>& drmInfos)
192+
const std::vector<DRM::DRMInfo>& drmInfos,
193+
std::set<uint64_t>& ptsStartList)
156194
{
157195
std::unique_ptr<CAdaptationSet> adpSet = CAdaptationSet::MakeUniquePtr(period);
158196

@@ -249,6 +287,7 @@ void adaptive::CSmoothTree::ParseTagStreamIndex(pugi::xml_node nodeSI,
249287
else
250288
{
251289
adpSet->SetStartPTS(t);
290+
ptsStartList.insert(t);
252291
}
253292
previousPts = t;
254293
hasDuration = true;
@@ -292,9 +331,6 @@ void adaptive::CSmoothTree::ParseTagStreamIndex(pugi::xml_node nodeSI,
292331
adpSet->AddCodecs(adpSet->GetRepresentations().front()->GetCodecs());
293332
}
294333

295-
if (m_ptsBase == NO_PTS_VALUE || adpSet->GetStartPTS() < m_ptsBase)
296-
m_ptsBase = adpSet->GetStartPTS();
297-
298334
period->AddAdaptationSet(adpSet);
299335
}
300336

@@ -409,26 +445,33 @@ void adaptive::CSmoothTree::CreateSegmentTimeline()
409445
{
410446
// Adjust PTS with the StreamIndex with lower PTS to sync streams during playback
411447
uint64_t nextStartPts = adpSet->GetStartPTS() - m_ptsBase;
412-
uint64_t index = 1;
413448

414449
for (uint32_t segDuration : adpSet->SegmentTimelineDuration())
415450
{
416451
CSegment seg;
417452
seg.startPTS_ = nextStartPts;
418453
seg.m_endPts = seg.startPTS_ + segDuration;
419454
seg.m_time = nextStartPts + m_ptsBase;
420-
seg.m_number = index;
421455

422456
repr->Timeline().Add(seg);
423457

424458
nextStartPts += segDuration;
425-
index++;
459+
}
460+
461+
// Update period duration
462+
if (adpSet->GetStreamType() == StreamType::VIDEO ||
463+
adpSet->GetStreamType() == StreamType::AUDIO)
464+
{
465+
const uint64_t tlDuration =
466+
repr->Timeline().GetDuration() * period->GetTimescale() / repr->GetTimescale();
467+
period->SetTlDuration(tlDuration);
426468
}
427469
}
428470
}
429471
}
430472
}
431473

474+
/*! @todo: commented for future removal
432475
bool adaptive::CSmoothTree::InsertLiveFragment(PLAYLIST::CAdaptationSet* adpSet,
433476
PLAYLIST::CRepresentation* repr,
434477
uint64_t fTimestamp,
@@ -438,10 +481,6 @@ bool adaptive::CSmoothTree::InsertLiveFragment(PLAYLIST::CAdaptationSet* adpSet,
438481
if (!m_isLive)
439482
return false;
440483
441-
//! @todo: expired_segments_ should be removed and need to be implemented DVRWindowLength
442-
//! then add a better way to delete old segments from the timeline based on timeshift window
443-
//! this also requires taking care of the Dash parser
444-
445484
const CSegment* lastSeg = repr->Timeline().GetBack();
446485
if (!lastSeg)
447486
return false;
@@ -457,8 +496,6 @@ bool adaptive::CSmoothTree::InsertLiveFragment(PLAYLIST::CAdaptationSet* adpSet,
457496
if (fStartPts <= lastSeg->startPTS_)
458497
return false;
459498
460-
repr->expired_segments_++;
461-
462499
CSegment segCopy = *lastSeg;
463500
const uint64_t duration =
464501
static_cast<uint64_t>(static_cast<double>(fDuration) / fTimescale * repr->GetTimescale());
@@ -478,3 +515,156 @@ bool adaptive::CSmoothTree::InsertLiveFragment(PLAYLIST::CAdaptationSet* adpSet,
478515
479516
return true;
480517
}
518+
*/
519+
520+
void adaptive::CSmoothTree::OnUpdateSegments()
521+
{
522+
lastUpdated_ = std::chrono::system_clock::now();
523+
524+
std::unique_ptr<CSmoothTree> updateTree{std::move(Clone())};
525+
526+
// Download and open the manifest update
527+
CURL::HTTPResponse resp;
528+
if (!DownloadManifestUpd(manifest_url_, m_manifestHeaders, {}, resp) ||
529+
!updateTree->Open(resp.effectiveUrl, resp.headers, resp.data))
530+
{
531+
return;
532+
}
533+
534+
// Update the local variables from manifest update
535+
auto& period = m_periods[0];
536+
auto& updPeriod = updateTree->m_periods[0];
537+
538+
if (updPeriod->GetAdaptationSets().size() != period->GetAdaptationSets().size())
539+
{
540+
LOG::LogF(LOGERROR, "Cannot update adaptation sets, the size dont match");
541+
return;
542+
}
543+
544+
// Update by index, a manifest with the same structure is expected
545+
const auto& updAdpSets = updPeriod->GetAdaptationSets();
546+
auto& adpSets = period->GetAdaptationSets();
547+
548+
for (size_t i = 0; i < updAdpSets.size(); ++i)
549+
{
550+
auto& updAdpSet = updAdpSets[i];
551+
auto& adpSet = adpSets[i];
552+
553+
if (updAdpSet->GetRepresentations().size() != adpSet->GetRepresentations().size())
554+
{
555+
LOG::LogF(LOGERROR, "Cannot update representations, the size dont match");
556+
break;
557+
}
558+
559+
// Update by index, a manifest with the same structure is expected
560+
const auto& updReprs = updAdpSet->GetRepresentations();
561+
auto& reprs = adpSet->GetRepresentations();
562+
563+
for (size_t i = 0; i < updReprs.size(); ++i)
564+
{
565+
auto& updRepr = updReprs[i];
566+
auto& repr = reprs[i];
567+
568+
if (repr->Timeline().IsEmpty())
569+
{
570+
LOG::LogF(LOGDEBUG, "SS update - No timeline (repr. id \"%s\")", repr->GetId().c_str());
571+
continue;
572+
}
573+
574+
if (!repr->current_segment_.has_value()) // Representation not used for playback yet
575+
{
576+
repr->Timeline().Swap(updRepr->Timeline());
577+
578+
LOG::LogF(LOGDEBUG, "SS update - Done (repr. id \"%s\")", updRepr->GetId().c_str());
579+
continue;
580+
}
581+
582+
if (repr->Timeline().GetSize() == updRepr->Timeline().GetSize() &&
583+
repr->Timeline().Get(0)->startPTS_ == updRepr->Timeline().Get(0)->startPTS_)
584+
{
585+
LOG::LogF(LOGDEBUG, "SS update - No new segments (repr. id \"%s\")", repr->GetId().c_str());
586+
continue;
587+
}
588+
589+
const CSegment* foundSeg{nullptr};
590+
const uint64_t segStartPTS = repr->current_segment_->startPTS_;
591+
592+
for (const CSegment& segment : updRepr->Timeline())
593+
{
594+
if (segment.startPTS_ == segStartPTS)
595+
{
596+
foundSeg = &segment;
597+
break;
598+
}
599+
else if (segment.startPTS_ > segStartPTS)
600+
{
601+
// Can fall here if video is paused and current segment is too old,
602+
// or the video provider provide updates that have misaligned PTS on segments,
603+
// so small PTS gaps that prevent to find the same segment
604+
foundSeg = &segment;
605+
LOG::LogF(LOGDEBUG,
606+
"SS update - Misaligned: current seg [PTS %llu] found [PTS %llu] "
607+
"(repr. id \"%s\")",
608+
segStartPTS, segment.startPTS_, repr->GetId().c_str());
609+
break;
610+
}
611+
}
612+
613+
if (!foundSeg)
614+
{
615+
LOG::LogF(LOGDEBUG, "SS update - No segment found (repr. id \"%s\")",
616+
repr->GetId().c_str());
617+
}
618+
else
619+
{
620+
repr->Timeline().Swap(updRepr->Timeline());
621+
repr->current_segment_ = *foundSeg;
622+
623+
// Update period duration
624+
if (adpSet->GetStreamType() == StreamType::VIDEO ||
625+
adpSet->GetStreamType() == StreamType::AUDIO)
626+
{
627+
const uint64_t tlDuration =
628+
updRepr->Timeline().GetDuration() * period->GetTimescale() / updRepr->GetTimescale();
629+
period->SetTlDuration(tlDuration);
630+
}
631+
632+
LOG::LogF(LOGDEBUG, "SS update - Done (repr. id \"%s\")", updRepr->GetId().c_str());
633+
}
634+
635+
if (repr->IsWaitForSegment() && repr->GetNextSegment())
636+
{
637+
repr->SetIsWaitForSegment(false);
638+
LOG::LogF(LOGDEBUG, "End WaitForSegment repr. id %s", repr->GetId().c_str());
639+
}
640+
}
641+
}
642+
643+
UpdateTotalTime();
644+
}
645+
646+
bool adaptive::CSmoothTree::DownloadManifestUpd(
647+
const std::string& url,
648+
const std::map<std::string, std::string>& reqHeaders,
649+
const std::vector<std::string>& respHeaders,
650+
UTILS::CURL::HTTPResponse& resp)
651+
{
652+
return CURL::DownloadFile(url, reqHeaders, respHeaders, resp);
653+
}
654+
655+
void adaptive::CSmoothTree::UpdateTotalTime()
656+
{
657+
uint64_t totalDurMs = m_mediaPresDuration;
658+
if (totalDurMs == 0)
659+
{
660+
totalDurMs =
661+
std::accumulate(m_periods.begin(), m_periods.end(), uint64_t{0},
662+
[](uint64_t sum, const std::unique_ptr<CPeriod>& period)
663+
{ return sum + period->GetTlDuration() * 1000 / period->GetTimescale(); });
664+
665+
if (m_dvrWindowLength != 0 && totalDurMs > m_dvrWindowLength)
666+
totalDurMs = m_dvrWindowLength;
667+
}
668+
669+
m_totalTime = totalDurMs;
670+
}

0 commit comments

Comments
 (0)