2014-02-14 23:21:05 +00:00
|
|
|
// Copyright 2014 Google Inc. All rights reserved.
|
|
|
|
//
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file or at
|
|
|
|
// https://developers.google.com/open-source/licenses/bsd
|
|
|
|
|
2014-10-01 22:10:21 +00:00
|
|
|
#include "packager/mpd/base/mpd_builder.h"
|
2013-11-18 23:48:14 +00:00
|
|
|
|
2014-12-16 01:32:19 +00:00
|
|
|
#include "packager/base/files/file_path.h"
|
2014-10-01 22:10:21 +00:00
|
|
|
#include "packager/base/logging.h"
|
|
|
|
#include "packager/base/strings/string_number_conversions.h"
|
|
|
|
#include "packager/base/strings/stringprintf.h"
|
2015-03-23 19:55:58 +00:00
|
|
|
#include "packager/base/synchronization/lock.h"
|
2016-01-11 23:58:02 +00:00
|
|
|
#include "packager/base/time/default_clock.h"
|
2014-10-01 22:10:21 +00:00
|
|
|
#include "packager/base/time/time.h"
|
2017-12-13 01:26:37 +00:00
|
|
|
#include "packager/mpd/base/adaptation_set.h"
|
2014-10-01 22:10:21 +00:00
|
|
|
#include "packager/mpd/base/mpd_utils.h"
|
|
|
|
#include "packager/mpd/base/xml/xml_node.h"
|
2016-07-07 19:34:07 +00:00
|
|
|
#include "packager/version/version.h"
|
2013-11-18 23:48:14 +00:00
|
|
|
|
2016-05-20 21:19:33 +00:00
|
|
|
namespace shaka {
|
2013-11-18 23:48:14 +00:00
|
|
|
|
2014-12-16 01:32:19 +00:00
|
|
|
using base::FilePath;
|
2013-11-18 23:48:14 +00:00
|
|
|
using xml::XmlNode;
|
|
|
|
|
2014-01-10 23:53:20 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
void AddMpdNameSpaceInfo(XmlNode* mpd) {
|
|
|
|
DCHECK(mpd);
|
|
|
|
|
2016-01-04 22:21:01 +00:00
|
|
|
static const char kXmlNamespace[] = "urn:mpeg:dash:schema:mpd:2011";
|
2015-06-09 22:29:14 +00:00
|
|
|
static const char kXmlNamespaceXsi[] =
|
|
|
|
"http://www.w3.org/2001/XMLSchema-instance";
|
2014-01-10 23:53:20 +00:00
|
|
|
static const char kXmlNamespaceXlink[] = "http://www.w3.org/1999/xlink";
|
|
|
|
static const char kDashSchemaMpd2011[] =
|
2016-01-04 22:21:01 +00:00
|
|
|
"urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd";
|
2015-07-15 21:57:47 +00:00
|
|
|
static const char kCencNamespace[] = "urn:mpeg:cenc:2013";
|
|
|
|
|
|
|
|
mpd->SetStringAttribute("xmlns", kXmlNamespace);
|
|
|
|
mpd->SetStringAttribute("xmlns:xsi", kXmlNamespaceXsi);
|
|
|
|
mpd->SetStringAttribute("xmlns:xlink", kXmlNamespaceXlink);
|
2014-01-10 23:53:20 +00:00
|
|
|
mpd->SetStringAttribute("xsi:schemaLocation", kDashSchemaMpd2011);
|
2015-07-15 21:57:47 +00:00
|
|
|
mpd->SetStringAttribute("xmlns:cenc", kCencNamespace);
|
2014-01-10 23:53:20 +00:00
|
|
|
}
|
|
|
|
|
2014-02-04 02:01:45 +00:00
|
|
|
bool IsPeriodNode(xmlNodePtr node) {
|
|
|
|
DCHECK(node);
|
|
|
|
int kEqual = 0;
|
|
|
|
return xmlStrcmp(node->name, reinterpret_cast<const xmlChar*>("Period")) ==
|
|
|
|
kEqual;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the first <Period> element. This does not recurse down the tree,
|
|
|
|
// only checks direct children. Returns the pointer to Period element on
|
|
|
|
// success, otherwise returns false.
|
|
|
|
// As noted here, we must traverse.
|
|
|
|
// http://www.xmlsoft.org/tutorial/ar01s04.html
|
|
|
|
xmlNodePtr FindPeriodNode(XmlNode* xml_node) {
|
2015-06-09 22:29:14 +00:00
|
|
|
for (xmlNodePtr node = xml_node->GetRawPtr()->xmlChildrenNode; node != NULL;
|
2014-02-04 02:01:45 +00:00
|
|
|
node = node->next) {
|
|
|
|
if (IsPeriodNode(node))
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2014-05-22 02:16:17 +00:00
|
|
|
bool Positive(double d) {
|
|
|
|
return d > 0.0;
|
|
|
|
}
|
|
|
|
|
2015-09-10 18:53:53 +00:00
|
|
|
// Return current time in XML DateTime format. The value is in UTC, so the
|
|
|
|
// string ends with a 'Z'.
|
2016-01-11 23:58:02 +00:00
|
|
|
std::string XmlDateTimeNowWithOffset(
|
|
|
|
int32_t offset_seconds,
|
|
|
|
base::Clock* clock) {
|
|
|
|
base::Time time = clock->Now();
|
2014-06-26 01:33:09 +00:00
|
|
|
time += base::TimeDelta::FromSeconds(offset_seconds);
|
|
|
|
base::Time::Exploded time_exploded;
|
|
|
|
time.UTCExplode(&time_exploded);
|
2014-05-22 02:16:17 +00:00
|
|
|
|
2015-09-10 18:53:53 +00:00
|
|
|
return base::StringPrintf("%4d-%02d-%02dT%02d:%02d:%02dZ", time_exploded.year,
|
2015-06-09 22:29:14 +00:00
|
|
|
time_exploded.month, time_exploded.day_of_month,
|
|
|
|
time_exploded.hour, time_exploded.minute,
|
2014-06-26 01:33:09 +00:00
|
|
|
time_exploded.second);
|
2014-05-22 02:16:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SetIfPositive(const char* attr_name, double value, XmlNode* mpd) {
|
|
|
|
if (Positive(value)) {
|
|
|
|
mpd->SetStringAttribute(attr_name, SecondsToXmlDuration(value));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-08 17:52:48 +00:00
|
|
|
std::string MakePathRelative(const std::string& media_path,
|
|
|
|
const FilePath& parent_path) {
|
|
|
|
FilePath relative_path;
|
|
|
|
const FilePath child_path = FilePath::FromUTF8Unsafe(media_path);
|
|
|
|
const bool is_child =
|
|
|
|
parent_path.AppendRelativePath(child_path, &relative_path);
|
|
|
|
if (!is_child)
|
|
|
|
relative_path = child_path;
|
|
|
|
return relative_path.NormalizePathSeparatorsTo('/').AsUTF8Unsafe();
|
2014-12-16 01:32:19 +00:00
|
|
|
}
|
|
|
|
|
2015-03-23 19:55:58 +00:00
|
|
|
// Spooky static initialization/cleanup of libxml.
|
|
|
|
class LibXmlInitializer {
|
|
|
|
public:
|
|
|
|
LibXmlInitializer() : initialized_(false) {
|
|
|
|
base::AutoLock lock(lock_);
|
|
|
|
if (!initialized_) {
|
|
|
|
xmlInitParser();
|
|
|
|
initialized_ = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
~LibXmlInitializer() {
|
|
|
|
base::AutoLock lock(lock_);
|
|
|
|
if (initialized_) {
|
|
|
|
xmlCleanupParser();
|
|
|
|
initialized_ = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
base::Lock lock_;
|
|
|
|
bool initialized_;
|
|
|
|
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(LibXmlInitializer);
|
|
|
|
};
|
|
|
|
|
2014-01-10 23:53:20 +00:00
|
|
|
} // namespace
|
|
|
|
|
2016-12-21 23:28:56 +00:00
|
|
|
MpdBuilder::MpdBuilder(const MpdOptions& mpd_options)
|
|
|
|
: mpd_options_(mpd_options), clock_(new base::DefaultClock()) {}
|
2013-11-18 23:48:14 +00:00
|
|
|
|
2015-07-13 17:44:52 +00:00
|
|
|
MpdBuilder::~MpdBuilder() {}
|
2013-11-18 23:48:14 +00:00
|
|
|
|
|
|
|
void MpdBuilder::AddBaseUrl(const std::string& base_url) {
|
|
|
|
base_urls_.push_back(base_url);
|
|
|
|
}
|
|
|
|
|
2015-02-02 17:26:09 +00:00
|
|
|
AdaptationSet* MpdBuilder::AddAdaptationSet(const std::string& lang) {
|
2016-08-17 17:41:40 +00:00
|
|
|
std::unique_ptr<AdaptationSet> adaptation_set(
|
2015-06-09 22:29:14 +00:00
|
|
|
new AdaptationSet(adaptation_set_counter_.GetNext(), lang, mpd_options_,
|
2016-12-21 23:28:56 +00:00
|
|
|
&representation_counter_));
|
2013-11-18 23:48:14 +00:00
|
|
|
DCHECK(adaptation_set);
|
2016-09-28 21:42:52 +00:00
|
|
|
|
2017-07-27 18:49:50 +00:00
|
|
|
if (!lang.empty() && lang == mpd_options_.mpd_params.default_language) {
|
2016-09-28 21:42:52 +00:00
|
|
|
adaptation_set->AddRole(AdaptationSet::kRoleMain);
|
|
|
|
}
|
|
|
|
|
2016-08-30 23:01:19 +00:00
|
|
|
adaptation_sets_.push_back(std::move(adaptation_set));
|
|
|
|
return adaptation_sets_.back().get();
|
2013-11-18 23:48:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool MpdBuilder::ToString(std::string* output) {
|
2014-01-31 19:02:30 +00:00
|
|
|
DCHECK(output);
|
2015-03-23 19:55:58 +00:00
|
|
|
static LibXmlInitializer lib_xml_initializer;
|
|
|
|
|
2015-11-17 00:06:17 +00:00
|
|
|
xml::scoped_xml_ptr<xmlDoc> doc(GenerateMpd());
|
2013-11-18 23:48:14 +00:00
|
|
|
if (!doc.get())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
static const int kNiceFormat = 1;
|
|
|
|
int doc_str_size = 0;
|
2017-06-15 20:00:28 +00:00
|
|
|
xmlChar* doc_str = nullptr;
|
2015-06-09 22:29:14 +00:00
|
|
|
xmlDocDumpFormatMemoryEnc(doc.get(), &doc_str, &doc_str_size, "UTF-8",
|
|
|
|
kNiceFormat);
|
2017-06-15 20:00:28 +00:00
|
|
|
output->assign(doc_str, doc_str + doc_str_size);
|
2013-11-18 23:48:14 +00:00
|
|
|
xmlFree(doc_str);
|
|
|
|
|
2015-03-23 19:55:58 +00:00
|
|
|
// Cleanup, free the doc.
|
2013-11-18 23:48:14 +00:00
|
|
|
doc.reset();
|
2017-06-15 20:00:28 +00:00
|
|
|
return true;
|
2013-11-18 23:48:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
xmlDocPtr MpdBuilder::GenerateMpd() {
|
|
|
|
// Setup nodes.
|
|
|
|
static const char kXmlVersion[] = "1.0";
|
2015-11-17 00:06:17 +00:00
|
|
|
xml::scoped_xml_ptr<xmlDoc> doc(xmlNewDoc(BAD_CAST kXmlVersion));
|
2013-11-18 23:48:14 +00:00
|
|
|
XmlNode mpd("MPD");
|
|
|
|
|
|
|
|
// Iterate thru AdaptationSets and add them to one big Period element.
|
|
|
|
XmlNode period("Period");
|
2016-01-11 23:58:02 +00:00
|
|
|
|
|
|
|
// Always set id=0 for now. Since this class can only generate one Period
|
|
|
|
// at the moment, just use a constant.
|
|
|
|
// Required for 'dynamic' MPDs.
|
|
|
|
period.SetId(0);
|
2016-08-30 23:01:19 +00:00
|
|
|
for (const std::unique_ptr<AdaptationSet>& adaptation_set :
|
|
|
|
adaptation_sets_) {
|
|
|
|
xml::scoped_xml_ptr<xmlNode> child(adaptation_set->GetXml());
|
2016-08-17 17:41:40 +00:00
|
|
|
if (!child.get() || !period.AddChild(std::move(child)))
|
2013-11-18 23:48:14 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add baseurls to MPD.
|
|
|
|
std::list<std::string>::const_iterator base_urls_it = base_urls_.begin();
|
|
|
|
for (; base_urls_it != base_urls_.end(); ++base_urls_it) {
|
|
|
|
XmlNode base_url("BaseURL");
|
|
|
|
base_url.SetContent(*base_urls_it);
|
|
|
|
|
|
|
|
if (!mpd.AddChild(base_url.PassScopedPtr()))
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2016-12-21 23:28:56 +00:00
|
|
|
// TODO(kqyang): Should we set @start unconditionally to 0?
|
|
|
|
if (mpd_options_.mpd_type == MpdType::kDynamic) {
|
2014-05-22 02:16:17 +00:00
|
|
|
// This is the only Period and it is a regular period.
|
|
|
|
period.SetStringAttribute("start", "PT0S");
|
|
|
|
}
|
|
|
|
|
2013-11-18 23:48:14 +00:00
|
|
|
if (!mpd.AddChild(period.PassScopedPtr()))
|
|
|
|
return NULL;
|
|
|
|
|
2014-06-26 01:33:09 +00:00
|
|
|
AddMpdNameSpaceInfo(&mpd);
|
2016-12-21 23:28:56 +00:00
|
|
|
|
|
|
|
static const char kOnDemandProfile[] =
|
|
|
|
"urn:mpeg:dash:profile:isoff-on-demand:2011";
|
|
|
|
static const char kLiveProfile[] =
|
|
|
|
"urn:mpeg:dash:profile:isoff-live:2011";
|
|
|
|
switch (mpd_options_.dash_profile) {
|
|
|
|
case DashProfile::kOnDemand:
|
|
|
|
mpd.SetStringAttribute("profiles", kOnDemandProfile);
|
|
|
|
break;
|
|
|
|
case DashProfile::kLive:
|
|
|
|
mpd.SetStringAttribute("profiles", kLiveProfile);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
NOTREACHED() << "Unknown DASH profile: "
|
|
|
|
<< static_cast<int>(mpd_options_.dash_profile);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2014-06-26 01:33:09 +00:00
|
|
|
AddCommonMpdInfo(&mpd);
|
2016-12-21 23:28:56 +00:00
|
|
|
switch (mpd_options_.mpd_type) {
|
|
|
|
case MpdType::kStatic:
|
2013-11-18 23:48:14 +00:00
|
|
|
AddStaticMpdInfo(&mpd);
|
|
|
|
break;
|
2016-12-21 23:28:56 +00:00
|
|
|
case MpdType::kDynamic:
|
2014-05-22 02:16:17 +00:00
|
|
|
AddDynamicMpdInfo(&mpd);
|
2013-11-18 23:48:14 +00:00
|
|
|
break;
|
|
|
|
default:
|
2016-12-21 23:28:56 +00:00
|
|
|
NOTREACHED() << "Unknown MPD type: "
|
|
|
|
<< static_cast<int>(mpd_options_.mpd_type);
|
2013-11-18 23:48:14 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
DCHECK(doc);
|
2016-07-07 19:34:07 +00:00
|
|
|
const std::string version = GetPackagerVersion();
|
|
|
|
if (!version.empty()) {
|
|
|
|
std::string version_string =
|
|
|
|
base::StringPrintf("Generated with %s version %s",
|
|
|
|
GetPackagerProjectUrl().c_str(), version.c_str());
|
|
|
|
xml::scoped_xml_ptr<xmlNode> comment(
|
|
|
|
xmlNewDocComment(doc.get(), BAD_CAST version_string.c_str()));
|
|
|
|
xmlDocSetRootElement(doc.get(), comment.get());
|
|
|
|
xmlAddSibling(comment.release(), mpd.Release());
|
|
|
|
} else {
|
|
|
|
xmlDocSetRootElement(doc.get(), mpd.Release());
|
|
|
|
}
|
2013-11-18 23:48:14 +00:00
|
|
|
return doc.release();
|
|
|
|
}
|
|
|
|
|
2014-06-26 01:33:09 +00:00
|
|
|
void MpdBuilder::AddCommonMpdInfo(XmlNode* mpd_node) {
|
2017-07-27 18:49:50 +00:00
|
|
|
if (Positive(mpd_options_.mpd_params.min_buffer_time)) {
|
2014-06-26 01:33:09 +00:00
|
|
|
mpd_node->SetStringAttribute(
|
2017-07-27 18:49:50 +00:00
|
|
|
"minBufferTime",
|
|
|
|
SecondsToXmlDuration(mpd_options_.mpd_params.min_buffer_time));
|
2014-06-26 01:33:09 +00:00
|
|
|
} else {
|
|
|
|
LOG(ERROR) << "minBufferTime value not specified.";
|
|
|
|
// TODO(tinskip): Propagate error.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-18 23:48:14 +00:00
|
|
|
void MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) {
|
|
|
|
DCHECK(mpd_node);
|
2016-12-21 23:28:56 +00:00
|
|
|
DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
|
2013-11-18 23:48:14 +00:00
|
|
|
|
|
|
|
static const char kStaticMpdType[] = "static";
|
|
|
|
mpd_node->SetStringAttribute("type", kStaticMpdType);
|
|
|
|
mpd_node->SetStringAttribute(
|
|
|
|
"mediaPresentationDuration",
|
|
|
|
SecondsToXmlDuration(GetStaticMpdDuration(mpd_node)));
|
|
|
|
}
|
|
|
|
|
2014-05-22 02:16:17 +00:00
|
|
|
void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
|
|
|
|
DCHECK(mpd_node);
|
2016-12-21 23:28:56 +00:00
|
|
|
DCHECK_EQ(MpdType::kDynamic, mpd_options_.mpd_type);
|
2014-05-22 02:16:17 +00:00
|
|
|
|
|
|
|
static const char kDynamicMpdType[] = "dynamic";
|
|
|
|
mpd_node->SetStringAttribute("type", kDynamicMpdType);
|
2014-06-26 01:33:09 +00:00
|
|
|
|
2016-01-11 23:58:02 +00:00
|
|
|
// No offset from NOW.
|
|
|
|
mpd_node->SetStringAttribute("publishTime",
|
|
|
|
XmlDateTimeNowWithOffset(0, clock_.get()));
|
|
|
|
|
2014-06-26 01:33:09 +00:00
|
|
|
// 'availabilityStartTime' is required for dynamic profile. Calculate if
|
|
|
|
// not already calculated.
|
|
|
|
if (availability_start_time_.empty()) {
|
|
|
|
double earliest_presentation_time;
|
|
|
|
if (GetEarliestTimestamp(&earliest_presentation_time)) {
|
2017-05-12 21:46:24 +00:00
|
|
|
availability_start_time_ = XmlDateTimeNowWithOffset(
|
|
|
|
-std::ceil(earliest_presentation_time), clock_.get());
|
2014-06-26 01:33:09 +00:00
|
|
|
} else {
|
|
|
|
LOG(ERROR) << "Could not determine the earliest segment presentation "
|
|
|
|
"time for availabilityStartTime calculation.";
|
|
|
|
// TODO(tinskip). Propagate an error.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!availability_start_time_.empty())
|
2015-06-09 22:29:14 +00:00
|
|
|
mpd_node->SetStringAttribute("availabilityStartTime",
|
|
|
|
availability_start_time_);
|
2014-06-26 01:33:09 +00:00
|
|
|
|
2017-07-27 18:49:50 +00:00
|
|
|
if (Positive(mpd_options_.mpd_params.minimum_update_period)) {
|
2014-06-26 01:33:09 +00:00
|
|
|
mpd_node->SetStringAttribute(
|
|
|
|
"minimumUpdatePeriod",
|
2017-07-27 18:49:50 +00:00
|
|
|
SecondsToXmlDuration(mpd_options_.mpd_params.minimum_update_period));
|
2014-06-26 01:33:09 +00:00
|
|
|
} else {
|
|
|
|
LOG(WARNING) << "The profile is dynamic but no minimumUpdatePeriod "
|
2015-06-09 22:29:14 +00:00
|
|
|
"specified.";
|
2014-06-26 01:33:09 +00:00
|
|
|
}
|
|
|
|
|
2017-07-27 18:49:50 +00:00
|
|
|
SetIfPositive("timeShiftBufferDepth",
|
|
|
|
mpd_options_.mpd_params.time_shift_buffer_depth, mpd_node);
|
2015-06-09 22:29:14 +00:00
|
|
|
SetIfPositive("suggestedPresentationDelay",
|
2017-07-27 18:49:50 +00:00
|
|
|
mpd_options_.mpd_params.suggested_presentation_delay, mpd_node);
|
2014-05-22 02:16:17 +00:00
|
|
|
}
|
|
|
|
|
2014-01-03 00:59:16 +00:00
|
|
|
float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) {
|
2013-11-18 23:48:14 +00:00
|
|
|
DCHECK(mpd_node);
|
2016-12-21 23:28:56 +00:00
|
|
|
DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
|
2013-11-18 23:48:14 +00:00
|
|
|
|
2014-02-04 02:01:45 +00:00
|
|
|
xmlNodePtr period_node = FindPeriodNode(mpd_node);
|
|
|
|
DCHECK(period_node) << "Period element must be a child of mpd_node.";
|
|
|
|
DCHECK(IsPeriodNode(period_node));
|
2013-11-18 23:48:14 +00:00
|
|
|
|
2016-12-21 23:28:56 +00:00
|
|
|
// TODO(kqyang): Verify if this works for static + live profile.
|
2013-11-18 23:48:14 +00:00
|
|
|
// Attribute mediaPresentationDuration must be present for 'static' MPD. So
|
2014-01-03 00:59:16 +00:00
|
|
|
// setting "PT0S" is required even if none of the representaions have duration
|
|
|
|
// attribute.
|
|
|
|
float max_duration = 0.0f;
|
2013-11-18 23:48:14 +00:00
|
|
|
for (xmlNodePtr adaptation_set = xmlFirstElementChild(period_node);
|
2015-06-09 22:29:14 +00:00
|
|
|
adaptation_set; adaptation_set = xmlNextElementSibling(adaptation_set)) {
|
2013-11-18 23:48:14 +00:00
|
|
|
for (xmlNodePtr representation = xmlFirstElementChild(adaptation_set);
|
|
|
|
representation;
|
|
|
|
representation = xmlNextElementSibling(representation)) {
|
2014-01-03 00:59:16 +00:00
|
|
|
float duration = 0.0f;
|
2013-11-18 23:48:14 +00:00
|
|
|
if (GetDurationAttribute(representation, &duration)) {
|
|
|
|
max_duration = max_duration > duration ? max_duration : duration;
|
|
|
|
|
|
|
|
// 'duration' attribute is there only to help generate MPD, not
|
|
|
|
// necessary for MPD, remove the attribute.
|
|
|
|
xmlUnsetProp(representation, BAD_CAST "duration");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return max_duration;
|
|
|
|
}
|
|
|
|
|
2014-06-26 01:33:09 +00:00
|
|
|
bool MpdBuilder::GetEarliestTimestamp(double* timestamp_seconds) {
|
|
|
|
DCHECK(timestamp_seconds);
|
2014-05-22 02:16:17 +00:00
|
|
|
|
2014-06-26 01:33:09 +00:00
|
|
|
double earliest_timestamp(-1);
|
2016-08-30 23:01:19 +00:00
|
|
|
for (const std::unique_ptr<AdaptationSet>& adaptation_set :
|
|
|
|
adaptation_sets_) {
|
2014-06-26 01:33:09 +00:00
|
|
|
double timestamp;
|
2016-08-30 23:01:19 +00:00
|
|
|
if (adaptation_set->GetEarliestTimestamp(×tamp) &&
|
2014-06-26 01:33:09 +00:00
|
|
|
((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
|
|
|
|
earliest_timestamp = timestamp;
|
|
|
|
}
|
2014-05-22 02:16:17 +00:00
|
|
|
}
|
2014-06-26 01:33:09 +00:00
|
|
|
if (earliest_timestamp < 0)
|
|
|
|
return false;
|
2014-05-22 02:16:17 +00:00
|
|
|
|
2014-06-26 01:33:09 +00:00
|
|
|
*timestamp_seconds = earliest_timestamp;
|
|
|
|
return true;
|
2014-05-22 02:16:17 +00:00
|
|
|
}
|
|
|
|
|
2014-12-16 01:32:19 +00:00
|
|
|
void MpdBuilder::MakePathsRelativeToMpd(const std::string& mpd_path,
|
|
|
|
MediaInfo* media_info) {
|
|
|
|
DCHECK(media_info);
|
|
|
|
const std::string kFileProtocol("file://");
|
2015-06-09 22:29:14 +00:00
|
|
|
std::string mpd_file_path = (mpd_path.find(kFileProtocol) == 0)
|
|
|
|
? mpd_path.substr(kFileProtocol.size())
|
|
|
|
: mpd_path;
|
2014-12-16 01:32:19 +00:00
|
|
|
|
|
|
|
if (!mpd_file_path.empty()) {
|
2017-09-08 17:52:48 +00:00
|
|
|
const FilePath mpd_dir(FilePath::FromUTF8Unsafe(mpd_file_path)
|
|
|
|
.DirName()
|
|
|
|
.AsEndingWithSeparator());
|
2014-12-16 01:32:19 +00:00
|
|
|
if (!mpd_dir.empty()) {
|
|
|
|
if (media_info->has_media_file_name()) {
|
|
|
|
media_info->set_media_file_name(
|
|
|
|
MakePathRelative(media_info->media_file_name(), mpd_dir));
|
|
|
|
}
|
|
|
|
if (media_info->has_init_segment_name()) {
|
|
|
|
media_info->set_init_segment_name(
|
|
|
|
MakePathRelative(media_info->init_segment_name(), mpd_dir));
|
|
|
|
}
|
|
|
|
if (media_info->has_segment_template()) {
|
|
|
|
media_info->set_segment_template(
|
|
|
|
MakePathRelative(media_info->segment_template(), mpd_dir));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-20 21:19:33 +00:00
|
|
|
} // namespace shaka
|