diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index 3376336711..610c61a2e5 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -404,6 +404,19 @@ class PackagerAppTest(unittest.TestCase): self._AssertStreamInfo(self.output[0], 'is_encrypted: true') self._AssertStreamInfo(self.output[1], 'is_encrypted: true') + @unittest.skipUnless(test_env.has_aes_flags, 'Requires AES credentials.') + def testWidevineEncryptionWithAesAndDashIfIopWithMultFiles(self): + flags = self._GetFlags(widevine_encryption=True, dash_if_iop=True) + flags += ['--aes_signing_key=' + test_env.options.aes_signing_key, + '--aes_signing_iv=' + test_env.options.aes_signing_iv] + self.packager.Package( + self._GetStreams(['audio', 'video'], + test_files=['bear-1280x720.mp4', 'bear-640x360.mp4', + 'bear-320x180.mp4']), flags) + with open(self.mpd_output, 'rb') as f: + print f.read() + # TODO(kqyang): Add some validations. + @unittest.skipUnless(test_env.has_aes_flags, 'Requires AES credentials.') def testKeyRotationWithAes(self): flags = self._GetFlags(widevine_encryption=True, key_rotation=True) diff --git a/packager/mpd/base/dash_iop_mpd_notifier.cc b/packager/mpd/base/dash_iop_mpd_notifier.cc index 2fe01a281f..62e4810772 100644 --- a/packager/mpd/base/dash_iop_mpd_notifier.cc +++ b/packager/mpd/base/dash_iop_mpd_notifier.cc @@ -15,8 +15,6 @@ namespace shaka { namespace { -const int kStartingGroupId = 1; - // The easiest way to check whether two protobufs are equal, is to compare the // serialized version. bool ProtectedContentEq( @@ -50,8 +48,7 @@ DashIopMpdNotifier::DashIopMpdNotifier( mpd_builder_(new MpdBuilder(dash_profile == kLiveProfile ? MpdBuilder::kDynamic : MpdBuilder::kStatic, - mpd_options)), - next_group_id_(kStartingGroupId) { + mpd_options)) { DCHECK(dash_profile == kLiveProfile || dash_profile == kOnDemandProfile); for (size_t i = 0; i < base_urls.size(); ++i) mpd_builder_->AddBaseUrl(base_urls[i]); @@ -91,7 +88,7 @@ bool DashIopMpdNotifier::NotifyNewContainer(const MediaInfo& media_info, representation_id_to_adaptation_set_[representation->id()] = adaptation_set; - SetGroupId(key, adaptation_set); + SetAdaptationSetSwitching(key, adaptation_set); *container_id = representation->id(); DCHECK(!ContainsKey(representation_map_, representation->id())); @@ -197,16 +194,18 @@ AdaptationSet* DashIopMpdNotifier::GetAdaptationSetForMediaInfo( } // Get all the UUIDs of the AdaptationSet. If another AdaptationSet has the -// same UUIDs then those should be groupable. -void DashIopMpdNotifier::SetGroupId(const std::string& key, - AdaptationSet* adaptation_set) { - if (adaptation_set->Group() >= 0) // @group already assigned. +// same UUIDs then those are switchable. +void DashIopMpdNotifier::SetAdaptationSetSwitching( + const std::string& key, + AdaptationSet* adaptation_set) { + // This adaptation set is already visited. + if (!adaptation_set->adaptation_set_switching_ids().empty()) return; ProtectedContentMap::const_iterator protected_content_it = protected_content_map_.find(adaptation_set->id()); - // Clear contents should be in one AdaptationSet, so no need to assign - // @group. + // Clear contents should be in one AdaptationSet and may not be switchable + // with encrypted contents. if (protected_content_it == protected_content_map_.end()) { DVLOG(1) << "No content protection set for AdaptationSet@id=" << adaptation_set->id(); @@ -237,21 +236,15 @@ void DashIopMpdNotifier::SetGroupId(const std::string& key, protected_content_map_[loop_adaptation_set_id]; if (static_cast(adaptation_set_uuids.size()) != loop_protected_content.content_protection_entry().size()) { - // Different number of UUIDs, cannot be grouped. + // Different number of UUIDs, may not be switchable. continue; } if (adaptation_set_uuids == GetUUIDs(loop_protected_content)) { AdaptationSet& uuid_match_adaptation_set = **adaptation_set_it; - // They match. These AdaptationSets can be in the same group. Break out. - if (uuid_match_adaptation_set.Group() >= 0) { - adaptation_set->SetGroup(uuid_match_adaptation_set.Group()); - } else { - const int group_id = next_group_id_++; - uuid_match_adaptation_set.SetGroup(group_id); - adaptation_set->SetGroup(group_id); - } - break; + // They match. These AdaptationSets are switchable. + uuid_match_adaptation_set.AddAdaptationSetSwitching(adaptation_set->id()); + adaptation_set->AddAdaptationSetSwitching(uuid_match_adaptation_set.id()); } } } diff --git a/packager/mpd/base/dash_iop_mpd_notifier.h b/packager/mpd/base/dash_iop_mpd_notifier.h index 381db88def..c454aa70f0 100644 --- a/packager/mpd/base/dash_iop_mpd_notifier.h +++ b/packager/mpd/base/dash_iop_mpd_notifier.h @@ -73,9 +73,10 @@ class DashIopMpdNotifier : public MpdNotifier { AdaptationSet* GetAdaptationSetForMediaInfo(const std::string& key, const MediaInfo& media_info); - // Sets a group id for |adaptation_set| if applicable. - // If a group ID is already assigned, then this returns immediately. - void SetGroupId(const std::string& key, AdaptationSet* adaptation_set); + // Sets adaptation set switching. If adaptation set switching is already + // set, then this returns immediately. + void SetAdaptationSetSwitching(const std::string& key, + AdaptationSet* adaptation_set); // Helper function to get a new AdaptationSet; registers the values // to the fields (maps) of the instance. @@ -104,9 +105,6 @@ class DashIopMpdNotifier : public MpdNotifier { std::unique_ptr mpd_builder_; base::Lock lock_; - // Next group ID to use for AdapationSets that can be grouped. - int next_group_id_; - // Maps Representation ID to AdaptationSet. This is for updating the PSSH. std::map representation_id_to_adaptation_set_; }; diff --git a/packager/mpd/base/dash_iop_mpd_notifier_unittest.cc b/packager/mpd/base/dash_iop_mpd_notifier_unittest.cc index bc55ab4a80..ce9693fa8e 100644 --- a/packager/mpd/base/dash_iop_mpd_notifier_unittest.cc +++ b/packager/mpd/base/dash_iop_mpd_notifier_unittest.cc @@ -18,9 +18,11 @@ namespace shaka { using ::testing::_; using ::testing::Eq; +using ::testing::ElementsAre; using ::testing::InSequence; using ::testing::Return; using ::testing::StrEq; +using ::testing::UnorderedElementsAre; namespace { @@ -37,7 +39,6 @@ const char kValidMediaInfo[] = "container_type: 1\n"; const uint32_t kDefaultAdaptationSetId = 0u; const uint32_t kDefaultRepresentationId = 1u; -const int kDefaultGroupId = -1; bool ElementEqual(const Element& lhs, const Element& rhs) { const bool all_equal_except_sublement_check = @@ -102,8 +103,6 @@ class DashIopMpdNotifierTest void SetUp() override { ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path_)); output_path_ = temp_file_path_.AsUTF8Unsafe(); - ON_CALL(*default_mock_adaptation_set_, Group()) - .WillByDefault(Return(kDefaultGroupId)); } void TearDown() override { @@ -218,7 +217,7 @@ TEST_P(DashIopMpdNotifierTest, NotifyNewTextContainer) { // Verify VOD NotifyNewContainer() operation works with different // MediaInfo::ProtectedContent. // Two AdaptationSets should be created. -// Different DRM so they won't be grouped. +// AdaptationSets with different DRM won't be switchable. TEST_P(DashIopMpdNotifierTest, NotifyNewContainersWithDifferentProtectedContent) { DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_, @@ -295,11 +294,6 @@ TEST_P(DashIopMpdNotifierTest, std::unique_ptr hd_adaptation_set( new MockAdaptationSet(kHdAdaptationSetId)); - ON_CALL(*sd_adaptation_set, Group()).WillByDefault(Return(kDefaultGroupId)); - ON_CALL(*hd_adaptation_set, Group()).WillByDefault(Return(kDefaultGroupId)); - EXPECT_CALL(*sd_adaptation_set, SetGroup(_)).Times(0); - EXPECT_CALL(*hd_adaptation_set, SetGroup(_)).Times(0); - const uint32_t kSdRepresentation = 4u; const uint32_t kHdRepresentation = 5u; std::unique_ptr sd_representation( @@ -336,6 +330,9 @@ TEST_P(DashIopMpdNotifierTest, ConvertToMediaInfo(kSdProtectedContent), &unused_container_id)); EXPECT_TRUE(notifier.NotifyNewContainer( ConvertToMediaInfo(kHdProtectedContent), &unused_container_id)); + + EXPECT_THAT(sd_adaptation_set->adaptation_set_switching_ids(), ElementsAre()); + EXPECT_THAT(hd_adaptation_set->adaptation_set_switching_ids(), ElementsAre()); } // Verify VOD NotifyNewContainer() operation works with same @@ -410,9 +407,6 @@ TEST_P(DashIopMpdNotifierTest, NotifyNewContainersWithSameProtectedContent) { std::unique_ptr hd_representation( new MockRepresentation(kHdRepresentation)); - // No reason to set @group if there is only one AdaptationSet. - EXPECT_CALL(*default_mock_adaptation_set_, SetGroup(_)).Times(0); - InSequence in_sequence; EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(_)) .WillOnce(Return(default_mock_adaptation_set_.get())); @@ -440,6 +434,10 @@ TEST_P(DashIopMpdNotifierTest, NotifyNewContainersWithSameProtectedContent) { ConvertToMediaInfo(kSdProtectedContent), &unused_container_id)); EXPECT_TRUE(notifier.NotifyNewContainer( ConvertToMediaInfo(kHdProtectedContent), &unused_container_id)); + + // No adaptation set switching if there is only one AdaptationSet. + EXPECT_THAT(default_mock_adaptation_set_->adaptation_set_switching_ids(), + ElementsAre()); } // AddContentProtection() should not work and should always return false. @@ -466,17 +464,16 @@ TEST_P(DashIopMpdNotifierTest, AddContentProtection) { } // Default Key IDs are different but if the content protection UUIDs match, then -// they can be in the same group. +// the AdaptationSet they belong to should be switchable. // This is a long test. // Basically this // 1. Add an SD protected content. This should make an AdaptationSet. // 2. Add an HD protected content. This should make another AdaptationSet that -// is different from the SD version. Both SD and HD should have the same -// group ID assigned. +// is different from the SD version. SD AdaptationSet and HD AdaptationSet +// should be switchable. // 3. Add a 4k protected content. This should also make a new AdaptationSet. -// The group ID should also match the SD and HD (but this takes a slightly -// different path). -TEST_P(DashIopMpdNotifierTest, SetGroup) { +// It should be switchable with SD/HD AdaptationSet. +TEST_P(DashIopMpdNotifierTest, SetAdaptationSetSwitching) { DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_, empty_base_urls_, output_path_); std::unique_ptr mock_mpd_builder( @@ -530,9 +527,6 @@ TEST_P(DashIopMpdNotifierTest, SetGroup) { std::unique_ptr hd_adaptation_set( new MockAdaptationSet(kHdAdaptationSetId)); - ON_CALL(*sd_adaptation_set, Group()).WillByDefault(Return(kDefaultGroupId)); - ON_CALL(*hd_adaptation_set, Group()).WillByDefault(Return(kDefaultGroupId)); - const uint32_t kSdRepresentation = 4u; const uint32_t kHdRepresentation = 5u; std::unique_ptr sd_representation( @@ -553,11 +547,6 @@ TEST_P(DashIopMpdNotifierTest, SetGroup) { EXPECT_CALL(*hd_adaptation_set, AddRepresentation(_)) .WillOnce(Return(hd_representation.get())); - // Both AdaptationSets' groups should be set to the same value. - const int kExpectedGroupId = 1; - EXPECT_CALL(*sd_adaptation_set, SetGroup(kExpectedGroupId)); - EXPECT_CALL(*hd_adaptation_set, SetGroup(kExpectedGroupId)); - // This is not very nice but we need it for settings expectations later. MockMpdBuilder* mock_mpd_builder_raw = mock_mpd_builder.get(); uint32_t unused_container_id; @@ -567,12 +556,13 @@ TEST_P(DashIopMpdNotifierTest, SetGroup) { EXPECT_TRUE(notifier.NotifyNewContainer( ConvertToMediaInfo(kHdProtectedContent), &unused_container_id)); - // Now that the group IDs are set Group() returns kExpectedGroupId. - ON_CALL(*sd_adaptation_set, Group()).WillByDefault(Return(kExpectedGroupId)); - ON_CALL(*hd_adaptation_set, Group()).WillByDefault(Return(kExpectedGroupId)); + EXPECT_THAT(sd_adaptation_set->adaptation_set_switching_ids(), + ElementsAre(kHdAdaptationSetId)); + EXPECT_THAT(hd_adaptation_set->adaptation_set_switching_ids(), + ElementsAre(kSdAdaptationSetId)); // Add another content that has the same protected content and make sure that - // it gets added to the existing group. + // adaptation set switching is set correctly. const char k4kProtectedContent[] = "video_info {\n" " codec: 'avc1'\n" @@ -596,8 +586,6 @@ TEST_P(DashIopMpdNotifierTest, SetGroup) { const uint32_t k4kAdaptationSetId = 4000u; std::unique_ptr fourk_adaptation_set( new MockAdaptationSet(k4kAdaptationSetId)); - ON_CALL(*fourk_adaptation_set, Group()) - .WillByDefault(Return(kDefaultGroupId)); const uint32_t k4kRepresentationId = 4001u; std::unique_ptr fourk_representation( @@ -609,16 +597,21 @@ TEST_P(DashIopMpdNotifierTest, SetGroup) { EXPECT_CALL(*fourk_adaptation_set, AddRepresentation(_)) .WillOnce(Return(fourk_representation.get())); - // Same group ID should be set. - EXPECT_CALL(*fourk_adaptation_set, SetGroup(kExpectedGroupId)); - EXPECT_TRUE(notifier.NotifyNewContainer( ConvertToMediaInfo(k4kProtectedContent), &unused_container_id)); + + EXPECT_THAT(sd_adaptation_set->adaptation_set_switching_ids(), + UnorderedElementsAre(kHdAdaptationSetId, k4kAdaptationSetId)); + EXPECT_THAT(hd_adaptation_set->adaptation_set_switching_ids(), + UnorderedElementsAre(kSdAdaptationSetId, k4kAdaptationSetId)); + EXPECT_THAT(fourk_adaptation_set->adaptation_set_switching_ids(), + ElementsAre(kSdAdaptationSetId, kHdAdaptationSetId)); } -// Even if the UUIDs match, video and audio AdaptationSets should not be grouped -// together. -TEST_P(DashIopMpdNotifierTest, DoNotSetGroupIfContentTypesDifferent) { +// Even if the UUIDs match, video and audio AdaptationSets should not be +// switchable. +TEST_P(DashIopMpdNotifierTest, + DoNotSetAdaptationSetSwitchingIfContentTypesDifferent) { DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_, empty_base_urls_, output_path_); std::unique_ptr mock_mpd_builder( @@ -671,15 +664,6 @@ TEST_P(DashIopMpdNotifierTest, DoNotSetGroupIfContentTypesDifferent) { std::unique_ptr audio_adaptation_set( new MockAdaptationSet(kAudioAdaptationSetId)); - ON_CALL(*video_adaptation_set, Group()) - .WillByDefault(Return(kDefaultGroupId)); - ON_CALL(*audio_adaptation_set, Group()) - .WillByDefault(Return(kDefaultGroupId)); - - // Both AdaptationSets' groups should NOT be set. - EXPECT_CALL(*video_adaptation_set, SetGroup(_)).Times(0); - EXPECT_CALL(*audio_adaptation_set, SetGroup(_)).Times(0); - const uint32_t kVideoRepresentation = 8u; const uint32_t kAudioRepresentation = 9u; std::unique_ptr video_representation( @@ -708,6 +692,11 @@ TEST_P(DashIopMpdNotifierTest, DoNotSetGroupIfContentTypesDifferent) { ConvertToMediaInfo(kVideoContent), &unused_container_id)); EXPECT_TRUE(notifier.NotifyNewContainer( ConvertToMediaInfo(kAudioContent), &unused_container_id)); + + EXPECT_THAT(video_adaptation_set->adaptation_set_switching_ids(), + ElementsAre()); + EXPECT_THAT(audio_adaptation_set->adaptation_set_switching_ids(), + ElementsAre()); } TEST_P(DashIopMpdNotifierTest, UpdateEncryption) { diff --git a/packager/mpd/base/mock_mpd_builder.h b/packager/mpd/base/mock_mpd_builder.h index 45c40c1176..97f93c4055 100644 --- a/packager/mpd/base/mock_mpd_builder.h +++ b/packager/mpd/base/mock_mpd_builder.h @@ -41,9 +41,6 @@ class MockAdaptationSet : public AdaptationSet { MOCK_METHOD1(AddRole, void(AdaptationSet::Role role)); MOCK_METHOD1(ForceSetSegmentAlignment, void(bool segment_alignment)); - MOCK_METHOD1(SetGroup, void(int group_number)); - MOCK_CONST_METHOD0(Group, int()); - private: // Only for constructing the super class. Not used for testing. base::AtomicSequenceNumber sequence_counter_; diff --git a/packager/mpd/base/mpd_builder.cc b/packager/mpd/base/mpd_builder.cc index 5bb3482e1f..6a5a42d884 100644 --- a/packager/mpd/base/mpd_builder.cc +++ b/packager/mpd/base/mpd_builder.cc @@ -40,8 +40,6 @@ using xml::AdaptationSetXmlNode; namespace { -const int kAdaptationSetGroupNotSet = -1; - AdaptationSet::Role MediaInfoTextTypeToRole( MediaInfo::TextInfo::TextType type) { switch (type) { @@ -682,7 +680,6 @@ AdaptationSet::AdaptationSet(uint32_t adaptation_set_id, lang_(lang), mpd_options_(mpd_options), mpd_type_(mpd_type), - group_(kAdaptationSetGroupNotSet), segments_aligned_(kSegmentAlignmentUnknown), force_set_segment_alignment_(false) { DCHECK(counter); @@ -808,13 +805,22 @@ xml::scoped_xml_ptr AdaptationSet::GetXml() { if (picture_aspect_ratio_.size() == 1) adaptation_set.SetStringAttribute("par", *picture_aspect_ratio_.begin()); - if (group_ >= 0) - adaptation_set.SetIntegerAttribute("group", group_); - if (!adaptation_set.AddContentProtectionElements( content_protection_elements_)) { return xml::scoped_xml_ptr(); } + + std::string switching_ids; + for (uint32_t id : adaptation_set_switching_ids_) { + if (!switching_ids.empty()) + switching_ids += ','; + switching_ids += base::UintToString(id); + } + if (!switching_ids.empty()) { + adaptation_set.AddSupplementalProperty( + "urn:mpeg:dash:adaptation-set-switching:2016", switching_ids); + } + for (AdaptationSet::Role role : roles_) adaptation_set.AddRoleElement("urn:mpeg:dash:role:2011", RoleToText(role)); @@ -840,12 +846,8 @@ void AdaptationSet::ForceSetSegmentAlignment(bool segment_alignment) { force_set_segment_alignment_ = true; } -void AdaptationSet::SetGroup(int group_number) { - group_ = group_number; -} - -int AdaptationSet::Group() const { - return group_; +void AdaptationSet::AddAdaptationSetSwitching(uint32_t adaptation_set_id) { + adaptation_set_switching_ids_.push_back(adaptation_set_id); } // Check segmentAlignment for Live here. Storing all start_time and duration diff --git a/packager/mpd/base/mpd_builder.h b/packager/mpd/base/mpd_builder.h index 93e142fac5..185bf0323f 100644 --- a/packager/mpd/base/mpd_builder.h +++ b/packager/mpd/base/mpd_builder.h @@ -232,15 +232,14 @@ class AdaptationSet { /// attribute. virtual void ForceSetSegmentAlignment(bool segment_alignment); - /// Sets the AdaptationSet@group attribute. - /// Passing a negative value to this method will unset the attribute. - /// Note that group=0 is a special group, as mentioned in the DASH MPD - /// specification. - /// @param group_number is the value of AdaptatoinSet@group. - virtual void SetGroup(int group_number); + /// Adds the id of the adaptation set this adaptation set can switch to. + /// @param adaptation_set_id is the id of the switchable adaptation set. + void AddAdaptationSetSwitching(uint32_t adaptation_set_id); - /// @return Returns the value for group. If not set, returns a negative value. - virtual int Group() const; + /// @return the ids of the adaptation sets this adaptation set can switch to. + const std::vector& adaptation_set_switching_ids() const { + return adaptation_set_switching_ids_; + } // Must be unique in the Period. uint32_t id() const { return id_; } @@ -348,10 +347,8 @@ class AdaptationSet { const MpdOptions& mpd_options_; const MpdBuilder::MpdType mpd_type_; - // The group attribute for the AdaptationSet. If the value is negative, - // no group number is specified. - // Note that group 0 is a special group number. - int group_; + // The ids of the adaptation sets this adaptation set can switch to. + std::vector adaptation_set_switching_ids_; // Video widths and heights of Representations. Note that this is a set; if // there is only 1 resolution, then @width & @height should be set, otherwise diff --git a/packager/mpd/base/mpd_builder_unittest.cc b/packager/mpd/base/mpd_builder_unittest.cc index cf2813b994..f27e8b9c9d 100644 --- a/packager/mpd/base/mpd_builder_unittest.cc +++ b/packager/mpd/base/mpd_builder_unittest.cc @@ -404,23 +404,39 @@ class TimeShiftBufferDepthTest : public SegmentTemplateTest { } }; -// Verify that AdaptationSet@group can be set and unset. -TEST_F(CommonMpdBuilderTest, SetAdaptationSetGroup) { - base::AtomicSequenceNumber sequence_counter; - auto adaptation_set = - CreateAdaptationSet(kAnyAdaptationSetId, "", MpdOptions(), - MpdBuilder::kStatic, &sequence_counter); - adaptation_set->SetGroup(1); +TEST_F(CommonMpdBuilderTest, AddAdaptationSetSwitching) { + MpdBuilder mpd_builder(MpdBuilder::kStatic, MpdOptions()); + AdaptationSet* adaptation_set = mpd_builder.AddAdaptationSet(""); + adaptation_set->AddAdaptationSetSwitching(1); + adaptation_set->AddAdaptationSetSwitching(2); + adaptation_set->AddAdaptationSetSwitching(8); - xml::scoped_xml_ptr xml_with_group(adaptation_set->GetXml()); - EXPECT_NO_FATAL_FAILURE( - ExpectAttributeEqString("group", "1", xml_with_group.get())); - - // Unset by passing a negative value. - adaptation_set->SetGroup(-1); - xml::scoped_xml_ptr xml_without_group(adaptation_set->GetXml()); - EXPECT_NO_FATAL_FAILURE( - ExpectAttributeNotSet("group", xml_without_group.get())); + xml::scoped_xml_ptr adaptation_set_xml(adaptation_set->GetXml()); + // The empty contentType is sort of a side effect of being able to generate an + // MPD without adding any Representations. + const char kExpectedOutput[] = + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""; + std::string mpd_output; + EXPECT_TRUE(mpd_builder.ToString(&mpd_output)); + ASSERT_TRUE(ValidateMpdSchema(mpd_output)); + EXPECT_TRUE(XmlEqual(kExpectedOutput, mpd_output)) + << "Expected " << kExpectedOutput << std::endl + << "Actual: " << mpd_output; } // Verify that Representation::Init() works with all "required" fields of diff --git a/packager/mpd/base/xml/xml_node.cc b/packager/mpd/base/xml/xml_node.cc index 25a77a6d87..49343a4594 100644 --- a/packager/mpd/base/xml/xml_node.cc +++ b/packager/mpd/base/xml/xml_node.cc @@ -166,6 +166,15 @@ bool RepresentationBaseXmlNode::AddContentProtectionElements( return true; } +void RepresentationBaseXmlNode::AddSupplementalProperty( + const std::string& scheme_id_uri, + const std::string& value) { + XmlNode supplemental_property("SupplementalProperty"); + supplemental_property.SetStringAttribute("schemeIdUri", scheme_id_uri); + supplemental_property.SetStringAttribute("value", value); + AddChild(supplemental_property.PassScopedPtr()); +} + bool RepresentationBaseXmlNode::AddContentProtectionElement( const ContentProtectionElement& content_protection_element) { XmlNode content_protection_node("ContentProtection"); diff --git a/packager/mpd/base/xml/xml_node.h b/packager/mpd/base/xml/xml_node.h index 6ed186487f..2d5c88ee1b 100644 --- a/packager/mpd/base/xml/xml_node.h +++ b/packager/mpd/base/xml/xml_node.h @@ -100,6 +100,11 @@ class RepresentationBaseXmlNode : public XmlNode { bool AddContentProtectionElements( const std::list& content_protection_elements); + /// @param scheme_id_uri is content of the schemeIdUri attribute. + /// @param value is the content of value attribute. + void AddSupplementalProperty(const std::string& scheme_id_uri, + const std::string& value); + protected: explicit RepresentationBaseXmlNode(const char* name);