From 435d69c3a6452cd868a09fa7c48e762004358664 Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Thu, 11 Jan 2018 17:33:37 -0800 Subject: [PATCH] Insert EXT-X-PLACEMENT-OPPORTUNITY on CueEvent See https://support.google.com/dfp_premium/answer/7295798?hl=en Change-Id: I931427512cb695dc68d5c8c2ec214fe743161bcb --- packager/hls/base/media_playlist.cc | 46 +++++++++++++------ packager/hls/base/media_playlist.h | 5 ++ packager/hls/base/media_playlist_unittest.cc | 29 ++++++++++++ packager/hls/base/mock_media_playlist.h | 1 + packager/hls/base/simple_hls_notifier.cc | 14 ++++-- .../hls/base/simple_hls_notifier_unittest.cc | 15 ++++++ 6 files changed, 91 insertions(+), 19 deletions(-) diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index 13f0c2c361..e5d4644de6 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -121,13 +121,15 @@ class SegmentInfoEntry : public HlsEntry { uint64_t start_byte_offset, uint64_t segment_file_size, uint64_t previous_segment_end_offset); - ~SegmentInfoEntry() override; std::string ToString() override; double start_time() const { return start_time_; } double duration() const { return duration_; } private: + SegmentInfoEntry(const SegmentInfoEntry&) = delete; + SegmentInfoEntry& operator=(const SegmentInfoEntry&) = delete; + const std::string file_name_; const double start_time_; const double duration_; @@ -135,8 +137,6 @@ class SegmentInfoEntry : public HlsEntry { const uint64_t start_byte_offset_; const uint64_t segment_file_size_; const uint64_t previous_segment_end_offset_; - - DISALLOW_COPY_AND_ASSIGN(SegmentInfoEntry); }; SegmentInfoEntry::SegmentInfoEntry(const std::string& file_name, @@ -154,7 +154,6 @@ SegmentInfoEntry::SegmentInfoEntry(const std::string& file_name, start_byte_offset_(start_byte_offset), segment_file_size_(segment_file_size), previous_segment_end_offset_(previous_segment_end_offset) {} -SegmentInfoEntry::~SegmentInfoEntry() {} std::string SegmentInfoEntry::ToString() { std::string result = base::StringPrintf("#EXTINF:%.3f,\n", duration_); @@ -178,19 +177,18 @@ class EncryptionInfoEntry : public HlsEntry { const std::string& key_format, const std::string& key_format_versions); - ~EncryptionInfoEntry() override; - std::string ToString() override; private: + EncryptionInfoEntry(const EncryptionInfoEntry&) = delete; + EncryptionInfoEntry& operator=(const EncryptionInfoEntry&) = delete; + const MediaPlaylist::EncryptionMethod method_; const std::string url_; const std::string key_id_; const std::string iv_; const std::string key_format_; const std::string key_format_versions_; - - DISALLOW_COPY_AND_ASSIGN(EncryptionInfoEntry); }; EncryptionInfoEntry::EncryptionInfoEntry(MediaPlaylist::EncryptionMethod method, @@ -207,8 +205,6 @@ EncryptionInfoEntry::EncryptionInfoEntry(MediaPlaylist::EncryptionMethod method, key_format_(key_format), key_format_versions_(key_format_versions) {} -EncryptionInfoEntry::~EncryptionInfoEntry() {} - std::string EncryptionInfoEntry::ToString() { std::string method_attribute; if (method_ == MediaPlaylist::EncryptionMethod::kSampleAes) { @@ -242,23 +238,39 @@ class DiscontinuityEntry : public HlsEntry { public: DiscontinuityEntry(); - ~DiscontinuityEntry() override; - std::string ToString() override; private: - DISALLOW_COPY_AND_ASSIGN(DiscontinuityEntry); + DiscontinuityEntry(const DiscontinuityEntry&) = delete; + DiscontinuityEntry& operator=(const DiscontinuityEntry&) = delete; }; DiscontinuityEntry::DiscontinuityEntry() : HlsEntry(HlsEntry::EntryType::kExtDiscontinuity) {} -DiscontinuityEntry::~DiscontinuityEntry() {} - std::string DiscontinuityEntry::ToString() { return "#EXT-X-DISCONTINUITY\n"; } +class PlacementOpportunityEntry : public HlsEntry { + public: + PlacementOpportunityEntry(); + + std::string ToString() override; + + private: + PlacementOpportunityEntry(const PlacementOpportunityEntry&) = delete; + PlacementOpportunityEntry& operator=(const PlacementOpportunityEntry&) = + delete; +}; + +PlacementOpportunityEntry::PlacementOpportunityEntry() + : HlsEntry(HlsEntry::EntryType::kExtPlacementOpportunity) {} + +std::string PlacementOpportunityEntry::ToString() { + return "#EXT-X-PLACEMENT-OPPORTUNITY\n"; +} + double LatestSegmentStartTime( const std::list>& entries) { DCHECK(!entries.empty()); @@ -372,6 +384,10 @@ void MediaPlaylist::AddEncryptionInfo(MediaPlaylist::EncryptionMethod method, method, url, key_id, iv, key_format, key_format_versions)); } +void MediaPlaylist::AddPlacementOpportunity() { + entries_.emplace_back(new PlacementOpportunityEntry()); +} + bool MediaPlaylist::WriteToFile(const std::string& file_path) { if (!target_duration_set_) { SetTargetDuration(ceil(GetLongestSegmentDuration())); diff --git a/packager/hls/base/media_playlist.h b/packager/hls/base/media_playlist.h index ac882206c6..208b39c48f 100644 --- a/packager/hls/base/media_playlist.h +++ b/packager/hls/base/media_playlist.h @@ -27,6 +27,7 @@ class HlsEntry { kExtInf, kExtKey, kExtDiscontinuity, + kExtPlacementOpportunity, }; virtual ~HlsEntry(); @@ -121,6 +122,10 @@ class MediaPlaylist { const std::string& key_format, const std::string& key_format_versions); + /// Add #EXT-X-PLACEMENT-OPPORTUNITY for mid-roll ads. See + /// https://support.google.com/dfp_premium/answer/7295798?hl=en. + virtual void AddPlacementOpportunity(); + /// Write the playlist to |file_path|. /// This does not close the file. /// If target duration is not set expliticly, this will try to find the target diff --git a/packager/hls/base/media_playlist_unittest.cc b/packager/hls/base/media_playlist_unittest.cc index 67216920b6..0a8be8ee09 100644 --- a/packager/hls/base/media_playlist_unittest.cc +++ b/packager/hls/base/media_playlist_unittest.cc @@ -316,6 +316,35 @@ TEST_F(MediaPlaylistMultiSegmentTest, WriteToFileWithSegments) { ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); } +TEST_F(MediaPlaylistMultiSegmentTest, + WriteToFileWithSegmentsAndPlacementOpportunity) { + valid_video_media_info_.set_reference_time_scale(90000); + ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); + + media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kZeroByteOffset, + kMBytes); + media_playlist_.AddPlacementOpportunity(); + media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale, + kZeroByteOffset, 5 * kMBytes); + const char kExpectedOutput[] = + "#EXTM3U\n" + "#EXT-X-VERSION:6\n" + "## Generated with https://github.com/google/shaka-packager version " + "test\n" + "#EXT-X-TARGETDURATION:30\n" + "#EXT-X-PLAYLIST-TYPE:VOD\n" + "#EXTINF:10.000,\n" + "file1.ts\n" + "#EXT-X-PLACEMENT-OPPORTUNITY\n" + "#EXTINF:30.000,\n" + "file2.ts\n" + "#EXT-X-ENDLIST\n"; + + const char kMemoryFilePath[] = "memory://media.m3u8"; + EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath)); + ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); +} + TEST_F(MediaPlaylistMultiSegmentTest, WriteToFileWithEncryptionInfo) { valid_video_media_info_.set_reference_time_scale(90000); ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); diff --git a/packager/hls/base/mock_media_playlist.h b/packager/hls/base/mock_media_playlist.h index c0401bcc54..f10992d568 100644 --- a/packager/hls/base/mock_media_playlist.h +++ b/packager/hls/base/mock_media_playlist.h @@ -38,6 +38,7 @@ class MockMediaPlaylist : public MediaPlaylist { const std::string& iv, const std::string& key_format, const std::string& key_format_versions)); + MOCK_METHOD0(AddPlacementOpportunity, void()); MOCK_METHOD1(WriteToFile, bool(const std::string& file_path)); MOCK_CONST_METHOD0(Bitrate, uint64_t()); MOCK_CONST_METHOD0(GetLongestSegmentDuration, double()); diff --git a/packager/hls/base/simple_hls_notifier.cc b/packager/hls/base/simple_hls_notifier.cc index c59903a612..460d72ff7d 100644 --- a/packager/hls/base/simple_hls_notifier.cc +++ b/packager/hls/base/simple_hls_notifier.cc @@ -367,10 +367,16 @@ bool SimpleHlsNotifier::NotifyNewSegment(uint32_t stream_id, return true; } -bool SimpleHlsNotifier::NotifyCueEvent(uint32_t container_id, - uint64_t timestamp) { - NOTIMPLEMENTED(); - return false; +bool SimpleHlsNotifier::NotifyCueEvent(uint32_t stream_id, uint64_t timestamp) { + base::AutoLock auto_lock(lock_); + auto stream_iterator = stream_map_.find(stream_id); + if (stream_iterator == stream_map_.end()) { + LOG(ERROR) << "Cannot find stream with ID: " << stream_id; + return false; + } + auto& media_playlist = stream_iterator->second->media_playlist; + media_playlist->AddPlacementOpportunity(); + return true; } bool SimpleHlsNotifier::NotifyEncryptionUpdate( diff --git a/packager/hls/base/simple_hls_notifier_unittest.cc b/packager/hls/base/simple_hls_notifier_unittest.cc index e3e17029ac..cdf1c0117b 100644 --- a/packager/hls/base/simple_hls_notifier_unittest.cc +++ b/packager/hls/base/simple_hls_notifier_unittest.cc @@ -877,6 +877,21 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWithoutStreamsRegistered) { notifier.NotifyEncryptionUpdate(1238u, key_id, system_id, iv, pssh_data)); } +TEST_F(SimpleHlsNotifierTest, NotifyCueEvent) { + // Pointer released by SimpleHlsNotifier. + MockMediaPlaylist* mock_media_playlist = + new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); + SimpleHlsNotifier notifier(kVodPlaylist, kTestTimeShiftBufferDepth, + kTestPrefix, kIdentityKeyUri, kAnyOutputDir, + kMasterPlaylistName); + const uint32_t stream_id = + SetupStream(kCencProtectionScheme, mock_media_playlist, ¬ifier); + + EXPECT_CALL(*mock_media_playlist, AddPlacementOpportunity()); + const uint64_t kCueEventTimestamp = 12345; + EXPECT_TRUE(notifier.NotifyCueEvent(stream_id, kCueEventTimestamp)); +} + class LiveOrEventSimpleHlsNotifierTest : public SimpleHlsNotifierTest, public ::testing::WithParamInterface {