@@ -97,11 +97,39 @@ std::string DetectCodecFromMimeType(std::string_view mimeType)
9797 return " " ;
9898}
9999
100+ // \brief Make an HTTP request to get UTC Timing and return the string
101+ std::string RequestUTCTiming (const std::string& url, CURL::RequestType reqType)
102+ {
103+ CURL::CUrl curl (url, reqType);
104+ const int statusCode = curl.Open ();
105+ if (statusCode == -1 || statusCode >= 400 )
106+ {
107+ LOG::LogF (LOGERROR, " Unable to retrieve UTC timing from url: %s" , url.c_str ());
108+ return {};
109+ }
110+
111+ std::string date;
112+ if (reqType == CURL::RequestType::HEAD)
113+ {
114+ date = curl.GetResponseHeader (" date" );
115+ }
116+ else
117+ {
118+ if (curl.Read (date) != CURL::ReadStatus::IS_EOF)
119+ {
120+ LOG::LogF (LOGERROR, " Unable to retrieve UTC timing data from url: (url: %s)" , url.c_str ());
121+ return {};
122+ }
123+ }
124+ return date;
125+ }
126+
100127} // unnamed namespace
101128
102129
103130adaptive::CDashTree::CDashTree (const CDashTree& left) : AdaptiveTree(left)
104131{
132+ m_clockOffset = left.m_clockOffset ;
105133}
106134
107135void adaptive::CDashTree::Configure (CHOOSER::IRepresentationChooser* reprChooser,
@@ -190,9 +218,26 @@ bool adaptive::CDashTree::ParseManifest(const std::string& data)
190218 }
191219
192220 // Parse <MPD> <UTCTiming> tags
193- // ! @todo: needed implementation
194- if (nodeMPD.child (" UTCTiming" ))
195- LOG::LogF (LOGWARNING, " The <UTCTiming> tag element is not supported so playback problems may occur." );
221+ if (!m_clockOffset.has_value ())
222+ m_clockOffset = ResolveUTCTiming (nodeMPD);
223+
224+ // If TSB is not set but availabilityStartTime, use the last one as TSB
225+ // since all segments from availabilityStartTime are available
226+ if (m_timeShiftBufferDepth == 0 && available_time_ > 0 )
227+ {
228+ const uint64_t now = stream_start_ + *m_clockOffset;
229+ m_timeShiftBufferDepth = now - available_time_;
230+ }
231+
232+ // TSB can be very large, limit it to avoid excessive memory consumption
233+ uint64_t tsbLimitMs = 14400000 ; // Default 4 hours
234+
235+ auto & manifestCfg = CSrvBroker::GetKodiProps ().GetManifestConfig ();
236+ if (manifestCfg.timeShiftBufferLimit .has_value ())
237+ tsbLimitMs = *manifestCfg.timeShiftBufferLimit * 1000 ;
238+
239+ if (m_timeShiftBufferDepth > tsbLimitMs)
240+ m_timeShiftBufferDepth = tsbLimitMs;
196241
197242 // Parse <MPD> <BaseURL> tag (just first, multi BaseURL not supported yet)
198243 std::string mpdUrl = base_url_;
@@ -312,21 +357,6 @@ void adaptive::CDashTree::ParseTagMPDAttribs(pugi::xml_node nodeMPD)
312357 available_time_ =
313358 static_cast <uint64_t >(XML::ParseDate (availabilityStartTimeStr.c_str ()) * 1000 );
314359
315- // If TSB is not set but availabilityStartTime, use the last one as TSB
316- // since all segments from availabilityStartTime are available
317- if (m_timeShiftBufferDepth == 0 && available_time_ > 0 )
318- m_timeShiftBufferDepth = stream_start_ - available_time_;
319-
320- // TSB can be very large, limit it to avoid excessive memory consumption
321- uint64_t tsbLimitMs = 14400000 ; // Default 4 hours
322-
323- auto & manifestCfg = CSrvBroker::GetKodiProps ().GetManifestConfig ();
324- if (manifestCfg.timeShiftBufferLimit .has_value ())
325- tsbLimitMs = *manifestCfg.timeShiftBufferLimit * 1000 ;
326-
327- if (m_timeShiftBufferDepth > tsbLimitMs)
328- m_timeShiftBufferDepth = tsbLimitMs;
329-
330360 std::string suggestedPresentationDelayStr;
331361 if (XML::QueryAttrib (nodeMPD, " suggestedPresentationDelay" , suggestedPresentationDelayStr))
332362 m_liveDelay = static_cast <uint64_t >(XML::ParseDuration (suggestedPresentationDelayStr));
@@ -1078,7 +1108,8 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr,
10781108 periodDurMs = m_mediaPresDuration;
10791109
10801110 // Generate segments from TSB
1081- uint64_t tsbStart = (stream_start_ - available_time_) - m_timeShiftBufferDepth;
1111+ const uint64_t tsbNow = stream_start_ + *m_clockOffset;
1112+ uint64_t tsbStart = (tsbNow - available_time_) - m_timeShiftBufferDepth;
10821113 uint64_t tsbEnd = tsbStart + m_timeShiftBufferDepth;
10831114 if (m_timeShiftBufferDepth > 0 && tsbEnd > periodStartMs)
10841115 {
@@ -1095,16 +1126,8 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr,
10951126
10961127 segmentsCount = std::max<size_t >(durationMs / segDurMs, 1 );
10971128
1098- if (available_time_ == 0 )
1099- {
1100- time = tsbStart * segTemplate->GetTimescale () / 1000 ;
1101- segNumber = tsbStart / segDurMs;
1102- }
1103- else
1104- {
1105- time += tsbStart * segTemplate->GetTimescale () / 1000 ;
1106- segNumber += tsbStart / segDurMs;
1107- }
1129+ segNumber += (tsbStart - periodStartMs) / segDurMs;
1130+ time += (segNumber - segTemplate->GetStartNumber ()) * segTemplate->GetDuration ();
11081131 }
11091132 else if (periodDurMs > 0 )
11101133 {
@@ -1119,11 +1142,18 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr,
11191142 else if (repr->HasSegmentEndNr ())
11201143 segNumberEnd = repr->GetSegmentEndNr ();
11211144
1145+ // Current wall clock, in timescale
1146+ const uint64_t nowScaled = (UTILS::GetTimestampMs () + *m_clockOffset) * segTimescale / 1000 ;
1147+
11221148 for (size_t i = 0 ; i < segmentsCount; ++i)
11231149 {
11241150 if (segNumber > segNumberEnd)
11251151 break ;
11261152
1153+ // Make sure to not add segments of the future
1154+ if (time > nowScaled)
1155+ break ;
1156+
11271157 CSegment seg;
11281158 seg.startPTS_ = time;
11291159 seg.m_endPts = seg.startPTS_ + segDuration;
@@ -1466,6 +1496,88 @@ uint32_t adaptive::CDashTree::ParseAudioChannelConfig(pugi::xml_node node)
14661496 return channels;
14671497}
14681498
1499+ int64_t adaptive::CDashTree::ResolveUTCTiming (pugi::xml_node node)
1500+ {
1501+ std::vector<std::pair<std::string, std::string>> utcTimings;
1502+
1503+ // Custom UTC timing
1504+ auto & manifestCfg = CSrvBroker::GetKodiProps ().GetManifestConfig ();
1505+ if (manifestCfg.dashUTCTiming .has_value ())
1506+ utcTimings.emplace_back (*manifestCfg.dashUTCTiming );
1507+
1508+ // Parse <UTCTiming> child tags
1509+ for (xml_node nodeUTC : node.children (" UTCTiming" ))
1510+ {
1511+ utcTimings.emplace_back (XML::GetAttrib (nodeUTC, " schemeIdUri" ), XML::GetAttrib (nodeUTC, " value" ));
1512+ }
1513+
1514+ std::optional<int64_t > tsMs;
1515+
1516+ // Elements are in order of preference so the first have the highest priority
1517+ for (auto & [scheme, value] : utcTimings)
1518+ {
1519+ if (scheme == " urn:mpeg:dash:utc:http-head:2012" ||
1520+ scheme == " urn:mpeg:dash:utc:http-head:2014" )
1521+ {
1522+ const uint64_t ts =
1523+ UTILS::ConvertDate2822ToTs (RequestUTCTiming (value, CURL::RequestType::HEAD));
1524+ if (ts == 0 )
1525+ {
1526+ LOG::LogF (LOGERROR, " UTCTiming conversion failed from scheme \" %s\" " , scheme.c_str ());
1527+ continue ;
1528+ }
1529+ tsMs = static_cast <int64_t >(ts * 1000 );
1530+ break ;
1531+ }
1532+ else if (scheme == " urn:mpeg:dash:utc:http-xsdate:2014" ||
1533+ scheme == " urn:mpeg:dash:utc:http-iso:2014" ||
1534+ scheme == " urn:mpeg:dash:utc:http-xsdate:2012" ||
1535+ scheme == " urn:mpeg:dash:utc:http-iso:2012" )
1536+ {
1537+ const double ts = XML::ParseDate (RequestUTCTiming (value, CURL::RequestType::AUTO).c_str (), 0 );
1538+ if (ts == 0 )
1539+ {
1540+ LOG::LogF (LOGERROR, " UTCTiming conversion failed from scheme \" %s\" " , scheme.c_str ());
1541+ continue ;
1542+ }
1543+ tsMs = static_cast <int64_t >(ts * 1000 );
1544+ break ;
1545+ }
1546+ else if (scheme == " urn:mpeg:dash:utc:direct:2014" ||
1547+ scheme == " urn:mpeg:dash:utc:direct:2012" )
1548+ {
1549+ const double ts = XML::ParseDate (value.c_str (), 0 );
1550+ if (ts == 0 )
1551+ {
1552+ LOG::LogF (LOGERROR, " A problem occurred in the UTCTiming scheme \" %s\" parsing" , scheme.c_str ());
1553+ continue ;
1554+ }
1555+ tsMs = static_cast <int64_t >(ts * 1000 );
1556+ break ;
1557+ }
1558+ else if (scheme == " urn:mpeg:dash:utc:http-ntp:2014" ||
1559+ scheme == " urn:mpeg:dash:utc:ntp:2014" ||
1560+ scheme == " urn:mpeg:dash:utc:sntp:2014" )
1561+ {
1562+ LOG::Log (LOGDEBUG, " NTP UTCTiming scheme \" %s\" not supported" , scheme.c_str ());
1563+ }
1564+ else
1565+ LOG::Log (LOGDEBUG, " UTCTiming scheme \" %s\" not supported" , scheme.c_str ());
1566+ }
1567+
1568+ int64_t offset{0 };
1569+
1570+ if (tsMs.has_value ())
1571+ {
1572+ offset = *tsMs - GetTimestamp ();
1573+ LOG::Log (LOGDEBUG, " UTCTiming clock offset %lli ms" , offset);
1574+ }
1575+ else if (!utcTimings.empty ())
1576+ LOG::Log (LOGWARNING, " Unable to get UTCTiming, playback problems may occur" );
1577+
1578+ return offset;
1579+ }
1580+
14691581// ! @todo: MergeAdpSets its a kind of workaround
14701582// ! its missing a middle interface where store "streams" (or media tracks) data in a form
14711583// ! that is detached from "tree" interface, this would avoid the force
@@ -1774,9 +1886,6 @@ bool adaptive::CDashTree::InsertLiveSegment(PLAYLIST::CPeriod* period,
17741886 return false;
17751887 */
17761888
1777- // ! @todo: expired_segments_ should be reworked, see also other parsers
1778- repr->expired_segments_ ++;
1779-
17801889 const CSegment* segment = repr->Timeline ().Get (pos);
17811890
17821891 if (!segment)
@@ -1786,11 +1895,28 @@ bool adaptive::CDashTree::InsertLiveSegment(PLAYLIST::CPeriod* period,
17861895 return false ;
17871896 }
17881897
1898+ // Current wall clock, in timescale
1899+ const uint64_t nowScaled =
1900+ (UTILS::GetTimestampMs () + *m_clockOffset) * repr->GetSegmentTemplate ()->GetTimescale () / 1000 ;
1901+
1902+ const uint64_t segEndPts = segment->m_endPts + repr->GetSegmentTemplate ()->GetDuration ();
1903+
1904+ if (segEndPts >= nowScaled)
1905+ {
1906+ // Attempt to add segments beyond the current time are not available segments, if it happens
1907+ // IsWaitForSegment will be set and playback becomes buffering until the next manifest update
1908+ LOG::LogF (LOGDEBUG, " Insert live segment skipped! Attempt to add future segments (rep id: %s)" ,
1909+ repr->GetId ().c_str ());
1910+ return false ;
1911+ }
1912+
1913+ // ! @todo: expired_segments_ should be reworked, see also other parsers
1914+ repr->expired_segments_ ++;
1915+
17891916 CSegment segCopy = *segment;
1790- uint64_t dur = segCopy.m_endPts - segCopy.startPTS_ ;
1791- segCopy.startPTS_ = segCopy.m_endPts ;
1792- segCopy.m_endPts = segCopy.startPTS_ + dur;
1793- segCopy.m_time = segCopy.m_endPts ;
1917+ segCopy.startPTS_ = segment->m_endPts ;
1918+ segCopy.m_endPts = segEndPts;
1919+ segCopy.m_time = segCopy.startPTS_ ;
17941920 segCopy.m_number ++;
17951921
17961922 LOG::LogF (LOGDEBUG, " Insert live segment to adptation set \" %s\" (Start PTS: %llu, number: %llu)" ,
0 commit comments