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"
1719#include " utils/log.h"
1820#include " pugixml.hpp"
1921
22+ #include < numeric> // accumulate
23+
2024using namespace adaptive ;
2125using namespace pugi ;
2226using 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
153190void 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
432475bool 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