diff --git a/mpd/base/mpd_builder.cc b/mpd/base/mpd_builder.cc index dfd0430144..e308340875 100644 --- a/mpd/base/mpd_builder.cc +++ b/mpd/base/mpd_builder.cc @@ -323,7 +323,9 @@ bool Representation::AddNewSegment(uint64 start_time, uint64 duration) { } // TODO(rkuroiwa): We don't need to create a node every single time. Make an -// internal copy of this element. +// internal copy of this element. Then move most of the logic to +// RepresentationXmlNode so that all the work is done in it so that this class +// just becomes a thin layer. // Uses info in |media_info_| and |content_protection_elements_| to create a // "Representation" node. xml::ScopedXmlPtr::type Representation::GetXml() { @@ -337,6 +339,9 @@ xml::ScopedXmlPtr::type Representation::GetXml() { return xml::ScopedXmlPtr::type(); } + if (!representation.AddContentProtectionElementsFromMediaInfo(media_info_)) + return xml::ScopedXmlPtr::type(); + // Mandatory fields for Representation. representation.SetId(id_); representation.SetIntegerAttribute("bandwidth", media_info_.bandwidth()); diff --git a/mpd/base/mpd_builder.h b/mpd/base/mpd_builder.h index 4d297dd5e0..bbd8fe822a 100644 --- a/mpd/base/mpd_builder.h +++ b/mpd/base/mpd_builder.h @@ -117,6 +117,8 @@ class AdaptationSet { DISALLOW_COPY_AND_ASSIGN(AdaptationSet); }; +// In |media_info|, ContentProtectionXml::{schemeIdUri,value} takes precedence +// over schemeIdUri and value specified in ContentProtectionXml::attributes. class Representation { public: Representation(const MediaInfo& media_info, uint32 representation_id); diff --git a/mpd/base/xml/xml_node.cc b/mpd/base/xml/xml_node.cc index 3efe98bcf8..cfbc347198 100644 --- a/mpd/base/xml/xml_node.cc +++ b/mpd/base/xml/xml_node.cc @@ -6,6 +6,11 @@ #include "base/strings/string_number_conversions.h" #include "mpd/base/media_info.pb.h" +using dash_packager::xml::XmlNode; + +using dash_packager::MediaInfo; +typedef MediaInfo::ContentProtectionXml ContentProtectionXml; +typedef ContentProtectionXml::AttributeNameValuePair AttributeNameValuePair; namespace { @@ -14,6 +19,132 @@ std::string RangeToString(const dash_packager::Range& range) { base::Uint64ToString(range.end()); } +bool SetAttributes(const google::protobuf::RepeatedPtrField< + AttributeNameValuePair>& attributes, + XmlNode* xml_node) { + DCHECK(xml_node); + for (int i = 0; i < attributes.size(); ++i) { + const AttributeNameValuePair& attribute = attributes.Get(i); + const std::string& name = attribute.name(); + const std::string& value = attribute.value(); + + if (name.empty()) { + LOG(ERROR) << "For element " + << reinterpret_cast(xml_node->GetRawPtr()->name) + << ", no name specified for attribute with value: " << value; + return false; + } + + xml_node->SetStringAttribute(name.c_str(), value); + } + + return true; +} + +// This function is recursive. Note that elements.size() == 0 is a terminating +// condition. +bool AddSubelements(const google::protobuf::RepeatedPtrField< + ContentProtectionXml::Element>& elements, + XmlNode* xml_node) { + DCHECK(xml_node); + for (int i = 0; i < elements.size(); ++i) { + const ContentProtectionXml::Element& subelement = elements.Get(i); + const std::string& subelement_name = subelement.name(); + if (subelement_name.empty()) { + LOG(ERROR) << "Subelement name was not specified for node " + << reinterpret_cast(xml_node->GetRawPtr()->name); + return false; + } + + XmlNode subelement_xml_node(subelement_name.c_str()); + if (!SetAttributes(subelement.attributes(), &subelement_xml_node)) { + LOG(ERROR) << "Failed to set attributes for " << subelement_name; + return false; + } + + if (!AddSubelements(subelement.subelements(), &subelement_xml_node)) { + LOG(ERROR) << "Failed to add subelements to " << subelement_name; + return false; + } + + if (!xml_node->AddChild(subelement_xml_node.PassScopedPtr())) { + LOG(ERROR) << "Failed to add subelement " << subelement_name << " to " + << reinterpret_cast(xml_node->GetRawPtr()->name); + return false; + } + } + + return true; +} + +// Returns true if 'schemeIdUri' is set in |content_protection_xml| and sets +// |scheme_id_uri_output|. This function checks +// ContentProtectionXml::scheme_id_uri before searching thru attributes. +bool GetSchemeIdAttribute(const ContentProtectionXml& content_protection_xml, + std::string* scheme_id_uri_output) { + // Common case where 'schemeIdUri' is set directly. + if (content_protection_xml.has_scheme_id_uri()) { + scheme_id_uri_output->assign(content_protection_xml.scheme_id_uri()); + return true; + } + + // 'schemeIdUri' is one of the attributes. + for (int i = 0; i < content_protection_xml.attributes().size(); ++i) { + const AttributeNameValuePair& attribute = + content_protection_xml.attributes(i); + const std::string& name = attribute.name(); + const std::string& value = attribute.value(); + if (name == "schemeIdUri") { + if (value.empty()) + LOG(WARNING) << "schemeIdUri is specified with an empty string."; + + // 'schemeIdUri' is a mandatory field but MPD doesn't care what the actual + // value is, proceed. + scheme_id_uri_output->assign(value); + return true; + } + } + + return false; +} + +// Translates ContentProtectionXml to XmlNode. +// content_protection_xml.scheme_id_uri and content_protection_xml.value takes +// precedence over attributes in content_protection_xml.attributes. +bool TranslateToContentProtectionXmlNode( + const ContentProtectionXml& content_protection_xml, + XmlNode* xml_node_content_protection) { + std::string scheme_id_uri; + if (!GetSchemeIdAttribute(content_protection_xml, &scheme_id_uri)) { + LOG(ERROR) << "ContentProtection element requires schemeIdUri."; + return false; + } + + if (!SetAttributes(content_protection_xml.attributes(), + xml_node_content_protection)) { + LOG(ERROR) << "Failed to set attributes for ContentProtection."; + return false; + } + + if (!AddSubelements(content_protection_xml.subelements(), + xml_node_content_protection)) { + LOG(ERROR) << "Failed to add sublements to ContentProtection."; + return false; + } + + // Add 'schemeIdUri' and 'value' attributes after SetAttributes() to avoid + // being overridden by content_protection_xml.attributes(). + xml_node_content_protection->SetStringAttribute("schemeIdUri", scheme_id_uri); + + if (content_protection_xml.has_value()) { + // Note that |value| is an optional field. + xml_node_content_protection->SetStringAttribute( + "value", content_protection_xml.value()); + } + + return true; +} + } // namespace namespace dash_packager { @@ -104,6 +235,33 @@ bool RepresentationBaseXmlNode::AddContentProtectionElements( return true; } +bool RepresentationBaseXmlNode::AddContentProtectionElementsFromMediaInfo( + const MediaInfo& media_info) { + const bool has_content_protections = + media_info.content_protections().size() > 0; + + if (!has_content_protections) + return true; + + for (int i = 0; i < media_info.content_protections().size(); ++i) { + const ContentProtectionXml& content_protection_xml = + media_info.content_protections(i); + XmlNode content_protection_node("ContentProtection"); + if (!TranslateToContentProtectionXmlNode(content_protection_xml, + &content_protection_node)) { + LOG(ERROR) << "Failed to make ContentProtection element from MediaInfo."; + return false; + } + + if (!AddChild(content_protection_node.PassScopedPtr())) { + LOG(ERROR) << "Failed to add ContentProtection to Representation."; + return false; + } + } + + return true; +} + bool RepresentationBaseXmlNode::AddContentProtectionElement( const ContentProtectionElement& content_protection_element) { XmlNode content_protection_node("ContentProtection"); diff --git a/mpd/base/xml/xml_node.h b/mpd/base/xml/xml_node.h index a1b1c9a011..04b88ccb3e 100644 --- a/mpd/base/xml/xml_node.h +++ b/mpd/base/xml/xml_node.h @@ -68,6 +68,10 @@ class RepresentationBaseXmlNode : public XmlNode { bool AddContentProtectionElements( const std::list& content_protection_elements); + // Return true on success. If content_protections size is 0 in |media_info|, + // this will return true. Otherwise return false. + bool AddContentProtectionElementsFromMediaInfo(const MediaInfo& media_info); + protected: explicit RepresentationBaseXmlNode(const char* name);