Skip to content

Commit a89a53c

Browse files
authored
Merge pull request #1834 from CastagnaIT/dash_utctiming
[DashTree] Implement UTCTiming for live streams
2 parents 6061b23 + 588aac9 commit a89a53c

File tree

16 files changed

+375
-62
lines changed

16 files changed

+375
-62
lines changed

plugin.video.isasamples/menu_data.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,14 @@
215215
'manifest_url': 'https://livesim2.dashif.org/livesim2/ato_10/testpic_2s/Manifest.mpd'
216216
}
217217
},
218+
'BBC LiveTestcard [SegmentTemplate - no timeline]': {
219+
SI_FEATURE: 'ADPV,SUBMP4',
220+
SI_CODECS: 'avc3,mp4a,ttml',
221+
SI_INFO: 'Multi BaseURL with priorities, font download on SupplementalProperty.',
222+
SI_CONFIG: {
223+
'manifest_url': 'https://rdmedia.bbc.co.uk/testcard/simulcast/manifests/avc-ctv-stereo-en-sfdt-woff.mpd'
224+
}
225+
},
218226
},
219227
'Dash VOD with DRM': {
220228
MI_CONFIG: {},
@@ -263,7 +271,8 @@
263271
},
264272
'Google angel one': {
265273
SI_ENCRYPT: 'DRMWV',
266-
SI_INFO: 'Multiple audio and subtitles tracks',
274+
SI_FEATURE: 'ADP,SUBEXT,CMP4,CWEBM',
275+
SI_INFO: 'Multiple audio/video and subtitles tracks. Use SegmentBase.',
267276
SI_CONFIG: {
268277
'manifest_url': 'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine/dash.mpd',
269278
'license_type': 'com.widevine.alpha',
@@ -362,14 +371,6 @@
362371
'drm': '{"org.w3.clearkey": {"license": {"keyids": {"eb676abbcb345e96bbcf616630f1a3da": "100b6c20940f779a4589152b57d2dacb"}}}}',
363372
}
364373
},
365-
'TEST WV': {
366-
SI_ENCRYPT: 'DRMWV,DRMPR',
367-
SI_INFO: 'Config set to Widevine',
368-
SI_CONFIG: {
369-
'manifest_url': 'https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p.mpd',
370-
'drm': '{"com.widevine.alpha":{"license": {"server_url": "https://drm-widevine-licensing.axtest.net/AcquireLicense","req_data": "eyJ0b2tlbiI6ICJwcmNiNzgyY2Y0NmI0MDM1YmU4Y2Y4ZGMyOTFmNmU0ODQ3MTkyZTViYjk1OGMwMDI5MjJmMTNlNmY1NThhZDM1NTk3ODViYWNmYjZjNjQxMjUyNTk2YTBlNTRlZWFhN2MwNWU4MGFjODI4MDJhMTNlZDk1NmVmMjY1MzIwZTA1NTQyMjA3NjA1MDkyNzk2YjY0NWIyYWMyMjAxMzVmM2MwNzQ2NzZlZjA0YmVjOTcyZTJmZmI3MWIyMGU5M2VjNWY4OGZhNDI3ZDA0ZDViYTU3OTU5ZGZiNGY1OWFkZDhiMGU1N2VmZDZlODBjYTk0ZGFmODZhMDM1OWYxNTY5NzE3MDViNTZmNWE4N2FlODQ4YjE1Y2I5NGY1YTRkNmU1ODA2NmYyYTcxNDk3OTg5ZTEwNWE4YjgzNjE2NzlhNTcyMjQwNmU3YjczMTk2N2JiNzNmZjAzMTBiZWJhNzU3NzA0ZTJlYWI3YmRlZmM2YmU1OGJhMzNjYWEyNzQ3ZDY1NWExNjIzYWRmMTUzMDNiOWI2NWJjZThmYzg0MGY1MzdiMzc5YmI4MzhlNWM5YjRmYzg1NWE3YWFlMzFmZjFlZjJkZTlhODE5N2RhM2YyNTQ1ODBhMDZkYmVlNTk1MDI3MWEzOTVlOGFiNWI5MGM4ZWYxNGY5ZGM2NGViODE0NzM3ZTczYmVkNzc2MTg5YmJmOTJlMzY5NjcyNTVlNzc1YmNhNWU1YmZmY2M1MTkzYjFmOTNmZDNiMGFmODc0NzE0MjE1MDA2MjY1OWNjZjViOWRkYzQ0NzM2MjQwNjlkZTA5ZjkyMjE4YjZiYTY1MGEyZjc4ZDA5MzNmODc1ODhmZjI0YWQwZTVmYWU4MjNlYjNjYzUyMjMwZTIyNWQ1YzliMDU5MzQ0MzkyNzJhNzYyMjE5NzAzY2Q2ZWQ0ZTI5OTRhN2Y4ZmZiMjJhNjI5MGJkYzdlYzQ1ZDU4MTAyNzk0NmU4M2EwMDg2ODI4MjY0NTdlY2YyMTRiNmQ1M2I3MTFmMTYyNzQ1ZDBmYjdjNzMyMTJiMzQxYTAzYjU4ZjQ5NGM0ODkzMTE1N2NmNDFkMzZhYWNiMTNhYmRiOTAwZjgzZGI4ZGJjMzNlMjQ5OWE3ZDhkZGE4YWFmZDg5ZGQ5NjI4Njg4YzFlOGQxYWE0ZTRhMjEyNDdkYmQ5MTE4MDE1ODExNzAwY2I0OTI5NDY2MjBkM2U0NzE0N2NjOTE5ZjNlYmVmY2E3NDk5YmFjNWYzMDM3NDU0NDk3NmI2ZTBkYWVhNThjNzllMmExMWE1YzNmMjdlOWM0ZjY2OWVjOWUzYTcxOWI3NjA4MDZiYzQ0NGE4NjYwZWU2N2U1MDhlMzY4OWEyMGJiNDNmYjdlZGUxNjY2NzFjZjQ0MWVlZGNkMDBhYWViYTFkNjIyYzJkNjJkOGY3YWI3YWZmZGFkNDNiMDkwM2I5NGNkY2NiNTUwMGEzN2E3MGIwNDJhMTg1Y2Q2Y2EwNTRjMWM4NTY0ZTM5YjMxMGVkYmI0ODE1YTRmMjc0NzljYzk3YzI4NjEwYTQyYjkwZjdkMGM5NGZkNDI0ZjkyOTBiZWU1YWIzMmI0ZmVmNTI4NTc5N2Q4ZWYwZDBjNTZlZTIwYjM0YWMyOGQxZjQyMjFlM2NlZjRmZDlmNjk4ZmFkNGU3MmVhMDNmMmJjM2RkNGYxMGEyMTBlZjQ1NDFiOTQ2ZTE4M2YxZDI0ZTZlY2JiOGFjMGZmYWQ0M2QxZTIxZGNlM2ExZWI2NmQ0NjFkMGExMzAyYWM1M2I3ZGM5NDcwMzhlNGY5ZWM1ZWNmNTQzMTMyODY2Nzg1NDI3NzNiYTZjYzUwNmQwODZhZDA1NDZiYWJlYmJjYmRjZWFiZGI2MWE4Mjk3YzU1NWQwZmNkZGYwZTY4ZjE4ZGU0YWZkMTRlMTA0ZjFhODYyMTBkOTc0OGQ4ZTE4YTI5ODBhZmQ1NTdjNzdkMTU1MjUyNThhNDI1MzAyYTgyNzJmMWZiOTFlNDJlNWM1NzUyZDZhMGRjMTU3NDI5ZmEzZGViZTYyNTM1ZmRmMDRkYjI5OGM1MzVlM2QyZmI1NDAwNzc3ZDcwZGIyNWY0OWJkNDg4ZDg0NmNhODY2NzNhNDc0YThhNzIwOThjNWQyNWM2YjM4N2IzNDI0NWMxZDViYjJiN2ZmYTc2MTNlZjFlZDM5Mjg2Zjg5MGFiZDUzNjM5NzcxN2Q0ZGEwZTVhOGVhZWQ2NzkyYjI3NjA0ZDlmYTFhZWY1ZjgwYjA2MGFkNTljZWJlOTIxYzZkYzQxM2Q3YTE5MDQ3MjhkNzlhMDE3OTUzYmQ1MmMwZmYxOWE0M2YxNWNkZjRlMThlMTNhYjE5ZmRlNGQ0NDEwN2U3ZjQ5ZjIwMjI5ZDE4ZTRhNTRlM2Q0ZGJjNzRiZWQyNWM4OWE2NWZiODQzMGZjZWVkZDIyNGQ3ZmFhNzlhNzMxNmY3YWFjNTdjNmVkNjA2NjM3NzY1NWVjOTVlMjA3NzAxYzViYzkzMDVhNzU0MGIzOGQwNjVlZDc2ZDliZjdkZTAyZDFhMGRkYWJjODczNDE5MmNmYTkyMTM5YjJlNDZjMjljODJmMzdkMTM5NTc2ZDAzMTQ2NDMxMmRhN2RlMzQ3NTM3N2NlNTE1ZmEyZGNjYjBlNDY2ZGYwMGI4MDYwMjJiYjYwMDZhZjk2MWUzOWEwODY1YjIzMTI1ZmQ1ZTIwNjUyYjY3OTI4MGI5YWY1M2U4N2RmMWJlZjIxNDcxY2IwMDk0YzJlYmI2OTc4M2M2M2Y1YTlkNTVkYTk5ZjA3ZTlkYWMxMTc2YjMwYjg4OGY0MzM3N2FmNjJmZDVlMjgyNzE3MzlmZDBjN2JlZjQ4NmM3YWFjNjg1MWNmZDBlOTAyYTg5OGNjMGM0OWQ0OWIxNDQ1ZjZhYjNkY2QxYmVlODY3OGM5NTU5OWQ2OTVlZTlmNzkzMmRmOWQ4OTIwMzc1NTUzNDk4MjFiMGNlNWZiZjc5Y2ZhNWNkNGRlNmY3ZTAxZTJlMjZhNGQ2Y2M3MGMzMThjOGUyMjk2NjA5YmE1N2VjNWVlNTA1YTMwNzc1Mzk2YWY1ZDE1ZDA5YjliYTM0MTE5NzBlMjZmMTAwMmM5MzYzMmQ2NGM1ZGYxMzUyZjA0MGQ4Mjc5YTY4ZGUxMDQ5Y2UxNzI3ZGZiYjcxMzI2Y2M1NGM1OWRlNjAxZTYyNzYyZmI1NTA0ZTQzNjMzMjQxN2JkZmZlNmZiZDQ1NTc3ZWRkNWIzMWZhZjcyMzA3MjFlOTg4NTEwYTA2ZDhjOWRjMTc2MTlmNzVkYTQ4MjU3MzU2NGY1NGRiYzM0MGVlYjk2YWVjNjgzOTMwYWZhODI2ZjA3Njc2NzkyMGM4OTQ1MDY4NWU3NjkyODI1NTk3OWUyMDE3ZmJiMGE3YWRjMTNhMjM4YWU2MzQ2NTgyN2Q5NzEzYThhNzY3MDRkNTA3ZmQyZDM5NmRlZDllM2RmODhhOTE0MTg4NWNmNzAxMzZhMmE5MDIyNWM4NGM3MGRiZDc4YzM1ZTliNjhjZGIyMTJkODhlNWUwMjBiNDliYmQ5ZjQ0NDlkMzc1NDY5YTk5YTJjNzJmMDU3ZGE1MTVmNDRkOGNlZmQyMzdjYzZlYTUxMWExMWE0NGE1Y2EyZWQ4ZjEzMmZiOTc5M2E1YjIwNjBmYzcxOGZjOTVhMmM1Y2IwNmJkNzZhNGM5YTg5NWFmZWRjOTQ1MTY0ZTFmYzRmNmQzNTdmZjVlNTU1MzJjMjBkZjYzOGQyNzI2YmVjZGRmOTJjNTc0NzVlYzRjNGIzMDI2OGZhMDk4MjVkMWNlOGIyYjhhMWZmZjkxYjVmYzFkMWFjOTAxMDE3OGU4MGE4YTBhMzJiMDVjZjRkM2RhNTM1YTU4ZDJlZTY1ZDNjZDhjMDgzZTlkZjc3ODdhNDEwMjMyMGMzMWFlYmJiOTNjZTY2ODBlZWM5NGFkODNiZTYwZmE3OWM0ZDRhYWZlYjkyYjJjZWY4YmU3NzUxMTUwOWJkNzEzOGZhY2YxMDlmOTJhZGUxNDM3MDg4NzQzMDRlZmQ4NzcxMTA1NzE1NzcyN2MxZmIyMjMyYjM5Y2Y4NzNkYzU5ODMyNzNmZWMwY2U0ZTg4MDkzZWI5MjgwM2Y2NDg2MzJhNDNjNTFhZTUwZjRkZTM0YmQ0OTY0YTY5NGEzN2IzYzRkNjUzOTA2MzgxYmM5NmNjZGQ1NjZiNzhhOGFhODZiNDA4ZTlkZDgxZmMzNGJkNzVlZWRmMjMzMTlhOGRmNjNmMTU4ODE1NmIwMDJiYjE2MTZhODNkNzU2NjJjZjE0MDc1MjI0OGQyYWVjMjhiN2E2NzBiZDYyM2E1MWI5MTEzZjBmN2I0YThiOTExOGQ2NzFkNmZlNjg2OTJhMmEzOTEzNWM1M2NkNzFjMTk2MDA5NjM0M2JmMjVlYWNkYjIwODJlZjU2YmNkMjAzZTkzOGNkYjg3MTZkZDBhZGM5ODBlZWI1MmMxNTAxODA2NGMxZmY2ZDMyZDMwZDliMjI4OTFiOWNjYTViNjRjNzFhNmI3ZmNlYjkwMTkzM2E3ODUwYyIsICJjaGFsbGVuZ2VfYmFzZTY0IjogIntDSEEtQjY0fSJ9"}}}'
371-
}
372-
},
373374
},
374375
},
375376
'Manifest HLS': {

src/CompKodiProps.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,22 @@ void ADP::KODI_PROPS::CCompKodiProps::ParseManifestConfig(const std::string& dat
411411
{
412412
m_manifestConfig.liveDelay = jDictVal.GetUint64();
413413
}
414+
else if (configName == "dash_utctiming" && jDictVal.IsObject())
415+
{
416+
for (auto& jPairUnwrap : jDictVal.GetObject()) // Iterate JSON dict
417+
{
418+
if (!jPairUnwrap.name.IsString() || !jPairUnwrap.value.IsString())
419+
{
420+
LOG::LogF(LOGERROR, "The manifest parameter \"dash_utctiming\" contains invalid values");
421+
break;
422+
}
423+
std::pair<std::string, std::string> utcTiming;
424+
utcTiming.first = jPairUnwrap.name.GetString();
425+
utcTiming.second = jPairUnwrap.value.GetString();
426+
m_manifestConfig.dashUTCTiming = utcTiming;
427+
break;
428+
}
429+
}
414430
else
415431
{
416432
LOG::LogF(LOGERROR, "Unsupported \"%s\" config or wrong data type on \"%s\" property",

src/CompKodiProps.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ struct ManifestConfig
7070
bool hlsFixDiscontSequence{false};
7171
// Custom delay from LIVE edge in seconds
7272
uint64_t liveDelay{0};
73+
// Allows to set a custom UTC Timing for DASH live streams (schemeIdUri, value)
74+
std::optional<std::pair<std::string, std::string>> dashUTCTiming;
7375
};
7476

7577
struct DrmCfg

src/codechandler/ttml/TTML.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,11 @@ void TTML2SRT::ParseTagBody(pugi::xml_node nodeTT)
215215
void TTML2SRT::ParseTagSpan(pugi::xml_node spanNode, std::string& subText)
216216
{
217217
StackStyle(XML::GetAttrib(spanNode, "style"));
218+
std::string_view beginTime = XML::GetAttrib(spanNode, "begin");
219+
std::string_view endTime = XML::GetAttrib(spanNode, "end");
220+
const bool hasTiming = !beginTime.empty() && !endTime.empty();
221+
std::string text;
222+
218223
// Parse additional style attributes of node and add them as another style stack
219224
StackStyle(ParseStyle(spanNode));
220225

@@ -224,23 +229,28 @@ void TTML2SRT::ParseTagSpan(pugi::xml_node spanNode, std::string& subText)
224229
if (spanTextNode.type() == pugi::node_pcdata)
225230
{
226231
// It's a text part
227-
AppendStyledText(spanTextNode.value(), subText);
232+
AppendStyledText(spanTextNode.value(), text);
228233
}
229234
else if (spanTextNode.type() == pugi::node_element)
230235
{
231236
if (STRING::Compare(spanTextNode.name(), "span"))
232237
{
233-
ParseTagSpan(spanTextNode, subText);
238+
ParseTagSpan(spanTextNode, text);
234239
}
235240
else if (STRING::Compare(spanTextNode.name(), "br"))
236241
{
237-
subText += "<br/>";
242+
text += "<br/>";
238243
}
239244
}
240245
}
241246

242247
UnstackStyle();
243248
UnstackStyle();
249+
250+
if (hasTiming)
251+
StackSubtitle("child_span", beginTime, endTime, text);
252+
else
253+
subText += text;
244254
}
245255

246256
void TTML2SRT::AppendStyledText(std::string_view textPart, std::string& subText)

src/parser/DASHTree.cpp

Lines changed: 162 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -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

103130
adaptive::CDashTree::CDashTree(const CDashTree& left) : AdaptiveTree(left)
104131
{
132+
m_clockOffset = left.m_clockOffset;
105133
}
106134

107135
void 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

Comments
 (0)