2022-08-26 15:44:59 +00:00
|
|
|
// Copyright 2014 Google LLC. All rights reserved.
|
2014-02-14 23:21:05 +00:00
|
|
|
//
|
|
|
|
// 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
|
|
|
|
2023-08-05 03:45:21 +00:00
|
|
|
#include <absl/strings/numbers.h>
|
|
|
|
#include <absl/strings/str_format.h>
|
|
|
|
#include <absl/synchronization/mutex.h>
|
|
|
|
#include <glog/logging.h>
|
2018-01-24 23:26:37 +00:00
|
|
|
#include <algorithm>
|
2023-08-05 03:45:21 +00:00
|
|
|
#include <chrono>
|
|
|
|
#include <filesystem>
|
|
|
|
#include <optional>
|
2018-01-24 23:26:37 +00:00
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
#include "packager/media/base/rcheck.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"
|
2017-12-14 01:00:11 +00:00
|
|
|
#include "packager/mpd/base/period.h"
|
2018-01-05 02:31:27 +00:00
|
|
|
#include "packager/mpd/base/representation.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
|
|
|
|
|
|
|
using xml::XmlNode;
|
|
|
|
|
2014-01-10 23:53:20 +00:00
|
|
|
namespace {
|
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
bool AddMpdNameSpaceInfo(XmlNode* mpd) {
|
2014-01-10 23:53:20 +00:00
|
|
|
DCHECK(mpd);
|
|
|
|
|
2018-09-18 19:05:11 +00:00
|
|
|
const std::set<std::string> namespaces = mpd->ExtractReferencedNamespaces();
|
|
|
|
|
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 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
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
RCHECK(mpd->SetStringAttribute("xmlns", kXmlNamespace));
|
|
|
|
RCHECK(mpd->SetStringAttribute("xmlns:xsi", kXmlNamespaceXsi));
|
|
|
|
RCHECK(mpd->SetStringAttribute("xsi:schemaLocation", kDashSchemaMpd2011));
|
2018-09-18 19:05:11 +00:00
|
|
|
|
|
|
|
static const char kCencNamespace[] = "urn:mpeg:cenc:2013";
|
2018-09-18 00:27:02 +00:00
|
|
|
static const char kMarlinNamespace[] =
|
|
|
|
"urn:marlin:mas:1-0:services:schemas:mpd";
|
2018-09-18 19:05:11 +00:00
|
|
|
static const char kXmlNamespaceXlink[] = "http://www.w3.org/1999/xlink";
|
2020-04-17 17:20:03 +00:00
|
|
|
static const char kMsprNamespace[] = "urn:microsoft:playready";
|
2018-09-18 19:05:11 +00:00
|
|
|
|
|
|
|
const std::map<std::string, std::string> uris = {
|
|
|
|
{"cenc", kCencNamespace},
|
2018-09-18 00:27:02 +00:00
|
|
|
{"mas", kMarlinNamespace},
|
2018-09-18 19:05:11 +00:00
|
|
|
{"xlink", kXmlNamespaceXlink},
|
2020-04-17 17:20:03 +00:00
|
|
|
{"mspr", kMsprNamespace},
|
2018-09-18 19:05:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
for (const std::string& namespace_name : namespaces) {
|
|
|
|
auto iter = uris.find(namespace_name);
|
|
|
|
CHECK(iter != uris.end()) << " unexpected namespace " << namespace_name;
|
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
RCHECK(mpd->SetStringAttribute(
|
2023-08-05 03:45:21 +00:00
|
|
|
absl::StrFormat("xmlns:%s", namespace_name.c_str()).c_str(),
|
2020-11-10 00:32:58 +00:00
|
|
|
iter->second));
|
2018-09-18 19:05:11 +00:00
|
|
|
}
|
2020-11-10 00:32:58 +00:00
|
|
|
return true;
|
2014-01-10 23:53:20 +00:00
|
|
|
}
|
|
|
|
|
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'.
|
2023-08-05 03:45:21 +00:00
|
|
|
std::string XmlDateTimeNowWithOffset(int32_t offset_seconds, Clock* clock) {
|
|
|
|
auto time_t = std::chrono::system_clock::to_time_t(
|
|
|
|
clock->now() + std::chrono::seconds(offset_seconds));
|
|
|
|
std::tm* tm = std::gmtime(&time_t);
|
|
|
|
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << std::put_time(tm, "%Y-%m-%dT%H:%M:%SZ");
|
|
|
|
return ss.str();
|
2014-05-22 02:16:17 +00:00
|
|
|
}
|
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
bool SetIfPositive(const char* attr_name, double value, XmlNode* mpd) {
|
|
|
|
return !Positive(value) ||
|
|
|
|
mpd->SetStringAttribute(attr_name, SecondsToXmlDuration(value));
|
2014-05-22 02:16:17 +00:00
|
|
|
}
|
|
|
|
|
2023-08-05 03:45:21 +00:00
|
|
|
std::string MakePathRelative(const std::filesystem::path& media_path,
|
|
|
|
const std::filesystem::path& parent_path) {
|
|
|
|
auto relative_path = std::filesystem::relative(media_path, parent_path);
|
|
|
|
if (relative_path.empty() || *relative_path.begin() == "..") {
|
|
|
|
// Not related.
|
|
|
|
relative_path = media_path;
|
|
|
|
}
|
|
|
|
|
|
|
|
return relative_path.lexically_normal().generic_string();
|
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) {
|
2023-08-05 03:45:21 +00:00
|
|
|
absl::MutexLock lock(&lock_);
|
2015-03-23 19:55:58 +00:00
|
|
|
if (!initialized_) {
|
|
|
|
xmlInitParser();
|
|
|
|
initialized_ = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
~LibXmlInitializer() {
|
2023-08-05 03:45:21 +00:00
|
|
|
absl::MutexLock lock(&lock_);
|
2015-03-23 19:55:58 +00:00
|
|
|
if (initialized_) {
|
|
|
|
xmlCleanupParser();
|
|
|
|
initialized_ = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2023-08-05 03:45:21 +00:00
|
|
|
absl::Mutex lock_;
|
2015-03-23 19:55:58 +00:00
|
|
|
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)
|
2023-08-05 03:45:21 +00:00
|
|
|
: mpd_options_(mpd_options), clock_(new Clock{}) {}
|
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);
|
|
|
|
}
|
|
|
|
|
2018-01-03 00:10:54 +00:00
|
|
|
Period* MpdBuilder::GetOrCreatePeriod(double start_time_in_seconds) {
|
|
|
|
for (auto& period : periods_) {
|
|
|
|
const double kPeriodTimeDriftThresholdInSeconds = 1.0;
|
|
|
|
const bool match =
|
|
|
|
std::fabs(period->start_time_in_seconds() - start_time_in_seconds) <
|
|
|
|
kPeriodTimeDriftThresholdInSeconds;
|
|
|
|
if (match)
|
|
|
|
return period.get();
|
|
|
|
}
|
2018-07-23 22:24:20 +00:00
|
|
|
periods_.emplace_back(new Period(period_counter_++, start_time_in_seconds,
|
|
|
|
mpd_options_, &representation_counter_));
|
2017-12-14 01:00:11 +00:00
|
|
|
return periods_.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;
|
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
auto mpd = GenerateMpd();
|
|
|
|
if (!mpd)
|
2013-11-18 23:48:14 +00:00
|
|
|
return false;
|
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
std::string version = GetPackagerVersion();
|
|
|
|
if (!version.empty()) {
|
2023-08-05 03:45:21 +00:00
|
|
|
version = absl::StrFormat("Generated with %s version %s",
|
|
|
|
GetPackagerProjectUrl().c_str(), version.c_str());
|
2020-11-10 00:32:58 +00:00
|
|
|
}
|
|
|
|
*output = mpd->ToString(version);
|
2017-06-15 20:00:28 +00:00
|
|
|
return true;
|
2013-11-18 23:48:14 +00:00
|
|
|
}
|
|
|
|
|
2023-08-05 03:45:21 +00:00
|
|
|
std::optional<xml::XmlNode> MpdBuilder::GenerateMpd() {
|
2013-11-18 23:48:14 +00:00
|
|
|
XmlNode mpd("MPD");
|
|
|
|
|
|
|
|
// Add baseurls to MPD.
|
2017-12-14 01:00:11 +00:00
|
|
|
for (const std::string& base_url : base_urls_) {
|
|
|
|
XmlNode xml_base_url("BaseURL");
|
|
|
|
xml_base_url.SetContent(base_url);
|
2013-11-18 23:48:14 +00:00
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
if (!mpd.AddChild(std::move(xml_base_url)))
|
2023-08-05 03:45:21 +00:00
|
|
|
return std::nullopt;
|
2013-11-18 23:48:14 +00:00
|
|
|
}
|
|
|
|
|
2018-01-29 18:37:50 +00:00
|
|
|
bool output_period_duration = false;
|
|
|
|
if (mpd_options_.mpd_type == MpdType::kStatic) {
|
|
|
|
UpdatePeriodDurationAndPresentationTimestamp();
|
|
|
|
// Only output period duration if there are more than one period. In the
|
|
|
|
// case of only one period, Period@duration is redundant as it is identical
|
|
|
|
// to Mpd Duration so the convention is not to output Period@duration.
|
|
|
|
output_period_duration = periods_.size() > 1;
|
2018-01-24 23:26:37 +00:00
|
|
|
}
|
|
|
|
|
2017-12-14 01:00:11 +00:00
|
|
|
for (const auto& period : periods_) {
|
2020-11-10 00:32:58 +00:00
|
|
|
auto period_node = period->GetXml(output_period_duration);
|
|
|
|
if (!period_node || !mpd.AddChild(std::move(*period_node)))
|
2023-08-05 03:45:21 +00:00
|
|
|
return std::nullopt;
|
2014-05-22 02:16:17 +00:00
|
|
|
}
|
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
if (!AddMpdNameSpaceInfo(&mpd))
|
2023-08-05 03:45:21 +00:00
|
|
|
return std::nullopt;
|
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:
|
2020-11-10 00:32:58 +00:00
|
|
|
if (!mpd.SetStringAttribute("profiles", kOnDemandProfile))
|
2023-08-05 03:45:21 +00:00
|
|
|
return std::nullopt;
|
2016-12-21 23:28:56 +00:00
|
|
|
break;
|
|
|
|
case DashProfile::kLive:
|
2020-11-10 00:32:58 +00:00
|
|
|
if (!mpd.SetStringAttribute("profiles", kLiveProfile))
|
2023-08-05 03:45:21 +00:00
|
|
|
return std::nullopt;
|
2016-12-21 23:28:56 +00:00
|
|
|
break;
|
|
|
|
default:
|
2023-08-05 03:45:21 +00:00
|
|
|
NOTIMPLEMENTED() << "Unknown DASH profile: "
|
|
|
|
<< static_cast<int>(mpd_options_.dash_profile);
|
2016-12-21 23:28:56 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
if (!AddCommonMpdInfo(&mpd))
|
2023-08-05 03:45:21 +00:00
|
|
|
return std::nullopt;
|
2016-12-21 23:28:56 +00:00
|
|
|
switch (mpd_options_.mpd_type) {
|
|
|
|
case MpdType::kStatic:
|
2020-11-10 00:32:58 +00:00
|
|
|
if (!AddStaticMpdInfo(&mpd))
|
2023-08-05 03:45:21 +00:00
|
|
|
return std::nullopt;
|
2013-11-18 23:48:14 +00:00
|
|
|
break;
|
2016-12-21 23:28:56 +00:00
|
|
|
case MpdType::kDynamic:
|
2020-11-10 00:32:58 +00:00
|
|
|
if (!AddDynamicMpdInfo(&mpd))
|
2023-08-05 03:45:21 +00:00
|
|
|
return std::nullopt;
|
2018-03-17 01:37:53 +00:00
|
|
|
// Must be after Period element.
|
2020-11-10 00:32:58 +00:00
|
|
|
if (!AddUtcTiming(&mpd))
|
2023-08-05 03:45:21 +00:00
|
|
|
return std::nullopt;
|
2013-11-18 23:48:14 +00:00
|
|
|
break;
|
|
|
|
default:
|
2023-08-05 03:45:21 +00:00
|
|
|
NOTIMPLEMENTED() << "Unknown MPD type: "
|
|
|
|
<< static_cast<int>(mpd_options_.mpd_type);
|
2013-11-18 23:48:14 +00:00
|
|
|
break;
|
|
|
|
}
|
2020-11-10 00:32:58 +00:00
|
|
|
return mpd;
|
2013-11-18 23:48:14 +00:00
|
|
|
}
|
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
bool MpdBuilder::AddCommonMpdInfo(XmlNode* mpd_node) {
|
2017-07-27 18:49:50 +00:00
|
|
|
if (Positive(mpd_options_.mpd_params.min_buffer_time)) {
|
2020-11-10 00:32:58 +00:00
|
|
|
RCHECK(mpd_node->SetStringAttribute(
|
2017-07-27 18:49:50 +00:00
|
|
|
"minBufferTime",
|
2020-11-10 00:32:58 +00:00
|
|
|
SecondsToXmlDuration(mpd_options_.mpd_params.min_buffer_time)));
|
2014-06-26 01:33:09 +00:00
|
|
|
} else {
|
|
|
|
LOG(ERROR) << "minBufferTime value not specified.";
|
2020-11-10 00:32:58 +00:00
|
|
|
return false;
|
2014-06-26 01:33:09 +00:00
|
|
|
}
|
2020-11-10 00:32:58 +00:00
|
|
|
return true;
|
2014-06-26 01:33:09 +00:00
|
|
|
}
|
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
bool MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) {
|
2013-11-18 23:48:14 +00:00
|
|
|
DCHECK(mpd_node);
|
2023-08-05 03:45:21 +00:00
|
|
|
DCHECK_EQ(static_cast<int>(MpdType::kStatic),
|
|
|
|
static_cast<int>(mpd_options_.mpd_type));
|
2013-11-18 23:48:14 +00:00
|
|
|
|
|
|
|
static const char kStaticMpdType[] = "static";
|
2020-11-10 00:32:58 +00:00
|
|
|
return mpd_node->SetStringAttribute("type", kStaticMpdType) &&
|
|
|
|
mpd_node->SetStringAttribute(
|
|
|
|
"mediaPresentationDuration",
|
|
|
|
SecondsToXmlDuration(GetStaticMpdDuration()));
|
2013-11-18 23:48:14 +00:00
|
|
|
}
|
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
bool MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
|
2014-05-22 02:16:17 +00:00
|
|
|
DCHECK(mpd_node);
|
2023-08-05 03:45:21 +00:00
|
|
|
DCHECK_EQ(static_cast<int>(MpdType::kDynamic),
|
|
|
|
static_cast<int>(mpd_options_.mpd_type));
|
2014-05-22 02:16:17 +00:00
|
|
|
|
|
|
|
static const char kDynamicMpdType[] = "dynamic";
|
2020-11-10 00:32:58 +00:00
|
|
|
RCHECK(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.
|
2020-11-10 00:32:58 +00:00
|
|
|
RCHECK(mpd_node->SetStringAttribute(
|
|
|
|
"publishTime", XmlDateTimeNowWithOffset(0, clock_.get())));
|
2016-01-11 23:58:02 +00:00
|
|
|
|
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.
|
|
|
|
}
|
|
|
|
}
|
2020-11-10 00:32:58 +00:00
|
|
|
if (!availability_start_time_.empty()) {
|
|
|
|
RCHECK(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)) {
|
2020-11-10 00:32:58 +00:00
|
|
|
RCHECK(mpd_node->SetStringAttribute(
|
2014-06-26 01:33:09 +00:00
|
|
|
"minimumUpdatePeriod",
|
2020-11-10 00:32:58 +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
|
|
|
}
|
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
return SetIfPositive("timeShiftBufferDepth",
|
|
|
|
mpd_options_.mpd_params.time_shift_buffer_depth,
|
|
|
|
mpd_node) &&
|
|
|
|
SetIfPositive("suggestedPresentationDelay",
|
|
|
|
mpd_options_.mpd_params.suggested_presentation_delay,
|
|
|
|
mpd_node);
|
2014-05-22 02:16:17 +00:00
|
|
|
}
|
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
bool MpdBuilder::AddUtcTiming(XmlNode* mpd_node) {
|
2018-03-17 01:37:53 +00:00
|
|
|
DCHECK(mpd_node);
|
2023-08-05 03:45:21 +00:00
|
|
|
DCHECK_EQ(static_cast<int>(MpdType::kDynamic),
|
|
|
|
static_cast<int>(mpd_options_.mpd_type));
|
2018-03-17 01:37:53 +00:00
|
|
|
|
|
|
|
for (const MpdParams::UtcTiming& utc_timing :
|
|
|
|
mpd_options_.mpd_params.utc_timings) {
|
|
|
|
XmlNode utc_timing_node("UTCTiming");
|
2020-11-10 00:32:58 +00:00
|
|
|
RCHECK(utc_timing_node.SetStringAttribute("schemeIdUri",
|
|
|
|
utc_timing.scheme_id_uri));
|
|
|
|
RCHECK(utc_timing_node.SetStringAttribute("value", utc_timing.value));
|
|
|
|
RCHECK(mpd_node->AddChild(std::move(utc_timing_node)));
|
2018-03-17 01:37:53 +00:00
|
|
|
}
|
2020-11-10 00:32:58 +00:00
|
|
|
return true;
|
2018-03-17 01:37:53 +00:00
|
|
|
}
|
|
|
|
|
2018-01-24 23:26:37 +00:00
|
|
|
float MpdBuilder::GetStaticMpdDuration() {
|
2023-08-05 03:45:21 +00:00
|
|
|
DCHECK_EQ(static_cast<int>(MpdType::kStatic),
|
|
|
|
static_cast<int>(mpd_options_.mpd_type));
|
2013-11-18 23:48:14 +00:00
|
|
|
|
2018-01-29 18:37:50 +00:00
|
|
|
float total_duration = 0.0f;
|
|
|
|
for (const auto& period : periods_) {
|
|
|
|
total_duration += period->duration_seconds();
|
2013-11-18 23:48:14 +00:00
|
|
|
}
|
2018-01-29 18:37:50 +00:00
|
|
|
return total_duration;
|
2013-11-18 23:48:14 +00:00
|
|
|
}
|
|
|
|
|
2014-06-26 01:33:09 +00:00
|
|
|
bool MpdBuilder::GetEarliestTimestamp(double* timestamp_seconds) {
|
|
|
|
DCHECK(timestamp_seconds);
|
2017-12-14 01:00:11 +00:00
|
|
|
DCHECK(!periods_.empty());
|
2023-07-12 21:51:00 +00:00
|
|
|
if (periods_.empty())
|
|
|
|
return false;
|
2018-01-05 02:31:27 +00:00
|
|
|
double timestamp = 0;
|
|
|
|
double earliest_timestamp = -1;
|
2018-01-29 18:37:50 +00:00
|
|
|
// TODO(kqyang): This is used to set availabilityStartTime. We may consider
|
|
|
|
// set presentationTimeOffset in the Representations then we can set
|
|
|
|
// availabilityStartTime to the time when MPD is first generated.
|
2018-01-05 02:31:27 +00:00
|
|
|
// The first period should have the earliest timestamp.
|
|
|
|
for (const auto* adaptation_set : periods_.front()->GetAdaptationSets()) {
|
|
|
|
for (const auto* representation : adaptation_set->GetRepresentations()) {
|
2018-01-29 18:37:50 +00:00
|
|
|
if (representation->GetStartAndEndTimestamps(×tamp, nullptr) &&
|
2018-01-05 02:31:27 +00:00
|
|
|
(earliest_timestamp < 0 || timestamp < earliest_timestamp)) {
|
|
|
|
earliest_timestamp = timestamp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (earliest_timestamp < 0)
|
|
|
|
return false;
|
|
|
|
*timestamp_seconds = earliest_timestamp;
|
|
|
|
return true;
|
2014-05-22 02:16:17 +00:00
|
|
|
}
|
|
|
|
|
2018-01-29 18:37:50 +00:00
|
|
|
void MpdBuilder::UpdatePeriodDurationAndPresentationTimestamp() {
|
2023-08-05 03:45:21 +00:00
|
|
|
DCHECK_EQ(static_cast<int>(MpdType::kStatic),
|
|
|
|
static_cast<int>(mpd_options_.mpd_type));
|
2018-01-29 18:37:50 +00:00
|
|
|
|
|
|
|
for (const auto& period : periods_) {
|
|
|
|
std::list<Representation*> video_representations;
|
|
|
|
std::list<Representation*> non_video_representations;
|
|
|
|
for (const auto& adaptation_set : period->GetAdaptationSets()) {
|
|
|
|
const auto& representations = adaptation_set->GetRepresentations();
|
|
|
|
if (adaptation_set->IsVideo()) {
|
|
|
|
video_representations.insert(video_representations.end(),
|
|
|
|
representations.begin(),
|
|
|
|
representations.end());
|
|
|
|
} else {
|
|
|
|
non_video_representations.insert(non_video_representations.end(),
|
|
|
|
representations.begin(),
|
|
|
|
representations.end());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-05 03:45:21 +00:00
|
|
|
std::optional<double> earliest_start_time;
|
|
|
|
std::optional<double> latest_end_time;
|
2018-01-29 18:37:50 +00:00
|
|
|
// The timestamps are based on Video Representations if exist.
|
|
|
|
const auto& representations = video_representations.size() > 0
|
|
|
|
? video_representations
|
|
|
|
: non_video_representations;
|
|
|
|
for (const auto& representation : representations) {
|
|
|
|
double start_time = 0;
|
|
|
|
double end_time = 0;
|
|
|
|
if (representation->GetStartAndEndTimestamps(&start_time, &end_time)) {
|
|
|
|
earliest_start_time =
|
|
|
|
std::min(earliest_start_time.value_or(start_time), start_time);
|
|
|
|
latest_end_time =
|
|
|
|
std::max(latest_end_time.value_or(end_time), end_time);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!earliest_start_time)
|
|
|
|
return;
|
|
|
|
|
|
|
|
period->set_duration_seconds(*latest_end_time - *earliest_start_time);
|
|
|
|
|
|
|
|
double presentation_time_offset = *earliest_start_time;
|
|
|
|
for (const auto& adaptation_set : period->GetAdaptationSets()) {
|
|
|
|
for (const auto& representation : adaptation_set->GetRepresentations()) {
|
|
|
|
representation->SetPresentationTimeOffset(presentation_time_offset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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://");
|
2023-08-05 03:45:21 +00:00
|
|
|
std::filesystem::path 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()) {
|
2023-08-05 03:45:21 +00:00
|
|
|
const std::filesystem::path mpd_dir(mpd_file_path.parent_path());
|
2014-12-16 01:32:19 +00:00
|
|
|
if (!mpd_dir.empty()) {
|
|
|
|
if (media_info->has_media_file_name()) {
|
2018-04-17 17:42:45 +00:00
|
|
|
media_info->set_media_file_url(
|
2014-12-16 01:32:19 +00:00
|
|
|
MakePathRelative(media_info->media_file_name(), mpd_dir));
|
|
|
|
}
|
|
|
|
if (media_info->has_init_segment_name()) {
|
2018-04-17 17:42:45 +00:00
|
|
|
media_info->set_init_segment_url(
|
2014-12-16 01:32:19 +00:00
|
|
|
MakePathRelative(media_info->init_segment_name(), mpd_dir));
|
|
|
|
}
|
|
|
|
if (media_info->has_segment_template()) {
|
2018-04-17 17:42:45 +00:00
|
|
|
media_info->set_segment_template_url(
|
2014-12-16 01:32:19 +00:00
|
|
|
MakePathRelative(media_info->segment_template(), mpd_dir));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-20 21:19:33 +00:00
|
|
|
} // namespace shaka
|