From d096f4b485009e05c5a6def7ac0144ade3d2b9ce Mon Sep 17 00:00:00 2001 From: Rintaro Kuroiwa Date: Mon, 1 May 2017 13:38:58 -0700 Subject: [PATCH] HLS byte range - Use byte range format if segment_template is not set in MediaInfo. - Change HlsNotifier::NotifyNewSegment() to take start byte offset. - Change HlsNotifyMuxerListener to cache the segment information if MuxerOptions::segment_template is set. Change-Id: Ida1b8b9198acc5c43e3a5d9a641c78c81bc9a6ac --- packager/hls/base/hls_notifier.h | 15 +- packager/hls/base/media_playlist.cc | 106 ++++-- packager/hls/base/media_playlist.h | 11 +- packager/hls/base/media_playlist_unittest.cc | 248 ++++++++++--- packager/hls/base/mock_media_playlist.h | 3 +- packager/hls/base/simple_hls_notifier.cc | 3 +- packager/hls/base/simple_hls_notifier.h | 1 + .../hls/base/simple_hls_notifier_unittest.cc | 350 +++++++++--------- .../media/event/hls_notify_muxer_listener.cc | 66 +++- .../media/event/hls_notify_muxer_listener.h | 7 + .../hls_notify_muxer_listener_unittest.cc | 115 +++++- .../media/event/muxer_listener_internal.cc | 3 +- 12 files changed, 670 insertions(+), 258 deletions(-) diff --git a/packager/hls/base/hls_notifier.h b/packager/hls/base/hls_notifier.h index 26dfac5480..74ee64d149 100644 --- a/packager/hls/base/hls_notifier.h +++ b/packager/hls/base/hls_notifier.h @@ -47,16 +47,19 @@ class HlsNotifier { const std::string& group_id, uint32_t* stream_id) = 0; - // |stream_id| is the value set by NotifyNewStream(). - // |segment_name| is the name of the new segment. - // |start_time| is the start time of the segment in terms of timescale passed - // in |media_info|. - // |duration| is also in terms of timescale. - // |size| is the size in bytes. + /// @param stream_id is the value set by NotifyNewStream(). + /// @param segment_name is the name of the new segment. + /// @param start_time is the start time of the segment in timescale units + /// passed in @a media_info. + /// @param duration is also in terms of timescale. + /// @param start_byte_offset is the offset of where the subsegment starts. + /// This should be 0 if the whole segment is a subsegment. + /// @param size is the size in bytes. virtual bool NotifyNewSegment(uint32_t stream_id, const std::string& segment_name, uint64_t start_time, uint64_t duration, + uint64_t start_byte_offset, uint64_t size) = 0; /// @param stream_id is the value set by NotifyNewStream(). diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index 135731873d..0f431acf98 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -6,11 +6,14 @@ #include "packager/hls/base/media_playlist.h" +#include + #include #include #include #include "packager/base/logging.h" +#include "packager/base/strings/string_number_conversions.h" #include "packager/base/strings/stringprintf.h" #include "packager/media/base/language_utils.h" #include "packager/media/file/file.h" @@ -32,11 +35,36 @@ uint32_t GetTimeScale(const MediaInfo& media_info) { return 0u; } -std::string CreatePlaylistHeader(const std::string& init_segment_name, - uint32_t target_duration, - MediaPlaylist::MediaPlaylistType type, - int media_sequence_number, - int discontinuity_sequence_number) { +std::string CreateExtXMap(const MediaInfo& media_info) { + std::string ext_x_map; + if (media_info.has_init_segment_name()) { + base::StringAppendF(&ext_x_map, "#EXT-X-MAP:URI=\"%s\"", + media_info.init_segment_name().data()); + } else if (media_info.has_media_file_name() && media_info.has_init_range()) { + // It only makes sense for single segment media to have EXT-X-MAP if + // there is init_range. + base::StringAppendF(&ext_x_map, "#EXT-X-MAP:URI=\"%s\"", + media_info.media_file_name().data()); + } else { + return ""; + } + if (media_info.has_init_range()) { + const uint64_t begin = media_info.init_range().begin(); + const uint64_t end = media_info.init_range().end(); + const uint64_t length = end - begin + 1; + base::StringAppendF(&ext_x_map, ",BYTERANGE=%" PRIu64 "@%" PRIu64, length, + begin); + } + ext_x_map += "\n"; + return ext_x_map; +} + +std::string CreatePlaylistHeader( + const MediaInfo& media_info, + uint32_t target_duration, + MediaPlaylist::MediaPlaylistType type, + int media_sequence_number, + int discontinuity_sequence_number) { const std::string version = GetPackagerVersion(); std::string version_line; if (!version.empty()) { @@ -76,18 +104,24 @@ std::string CreatePlaylistHeader(const std::string& init_segment_name, // Put EXT-X-MAP at the end since the rest of the playlist is about the // segment and key info. - if (!init_segment_name.empty()) { - header += "#EXT-X-MAP:URI=\"" + init_segment_name + "\"\n"; - } - + header += CreateExtXMap(media_info); return header; } class SegmentInfoEntry : public HlsEntry { public: + // If |use_byte_range| true then this will append EXT-X-BYTERANGE + // after EXTINF. + // It uses |previous_segment_end_offset| to determine if it has to also + // specify the start byte offset in the tag. + // |duration| is duration in seconds. SegmentInfoEntry(const std::string& file_name, double start_time, - double duration); + double duration, + bool use_byte_range, + uint64_t start_byte_offset, + uint64_t segment_file_size, + uint64_t previous_segment_end_offset); ~SegmentInfoEntry() override; std::string ToString() override; @@ -98,22 +132,42 @@ class SegmentInfoEntry : public HlsEntry { const std::string file_name_; const double start_time_; const double duration_; + const bool use_byte_range_; + 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, double start_time, - double duration) + double duration, + bool use_byte_range, + uint64_t start_byte_offset, + uint64_t segment_file_size, + uint64_t previous_segment_end_offset) : HlsEntry(HlsEntry::EntryType::kExtInf), file_name_(file_name), start_time_(start_time), - duration_(duration) {} + duration_(duration), + use_byte_range_(use_byte_range), + 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() { - return base::StringPrintf("#EXTINF:%.3f,\n%s\n", duration_, - file_name_.c_str()); + std::string result = base::StringPrintf("#EXTINF:%.3f,\n", duration_); + if (use_byte_range_) { + result += "#EXT-X-BYTERANGE:" + base::Uint64ToString(segment_file_size_); + if (previous_segment_end_offset_ + 1 != start_byte_offset_) { + result += "@" + base::Uint64ToString(start_byte_offset_); + } + result += "\n"; + } + result += file_name_ + "\n"; + return result; } class EncryptionInfoEntry : public HlsEntry { @@ -224,12 +278,12 @@ double LatestSegmentStartTime( HlsEntry::HlsEntry(HlsEntry::EntryType type) : type_(type) {} HlsEntry::~HlsEntry() {} -MediaPlaylist::MediaPlaylist(MediaPlaylistType type, +MediaPlaylist::MediaPlaylist(MediaPlaylistType playlist_type, double time_shift_buffer_depth, const std::string& file_name, const std::string& name, const std::string& group_id) - : type_(type), + : playlist_type_(playlist_type), time_shift_buffer_depth_(time_shift_buffer_depth), file_name_(file_name), name_(name), @@ -272,12 +326,15 @@ bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) { void MediaPlaylist::AddSegment(const std::string& file_name, uint64_t start_time, uint64_t duration, + uint64_t start_byte_offset, uint64_t size) { if (time_scale_ == 0) { LOG(WARNING) << "Timescale is not set and the duration for " << duration << " cannot be calculated. The output will be wrong."; - entries_.emplace_back(new SegmentInfoEntry(file_name, 0.0, 0.0)); + entries_.emplace_back(new SegmentInfoEntry( + file_name, 0.0, 0.0, !media_info_.has_segment_template(), + start_byte_offset, size, previous_segment_end_offset_)); return; } @@ -291,8 +348,11 @@ void MediaPlaylist::AddSegment(const std::string& file_name, const int kBitsInByte = 8; const uint64_t bitrate = kBitsInByte * size / segment_duration_seconds; max_bitrate_ = std::max(max_bitrate_, bitrate); - entries_.emplace_back(new SegmentInfoEntry(file_name, start_time_seconds, - segment_duration_seconds)); + entries_.emplace_back(new SegmentInfoEntry( + file_name, start_time_seconds, segment_duration_seconds, + !media_info_.has_segment_template(), start_byte_offset, size, + previous_segment_end_offset_)); + previous_segment_end_offset_ = start_byte_offset + size - 1; SlideWindow(); } @@ -319,7 +379,7 @@ bool MediaPlaylist::WriteToFile(const std::string& file_path) { } std::string header = CreatePlaylistHeader( - media_info_.init_segment_name(), target_duration_, type_, + media_info_, target_duration_, playlist_type_, media_sequence_number_, discontinuity_sequence_number_); std::string body; @@ -328,7 +388,7 @@ bool MediaPlaylist::WriteToFile(const std::string& file_path) { std::string content = header + body; - if (type_ == MediaPlaylistType::kVod) { + if (playlist_type_ == MediaPlaylistType::kVod) { content += "#EXT-X-ENDLIST\n"; } @@ -388,8 +448,10 @@ bool MediaPlaylist::GetResolution(uint32_t* width, uint32_t* height) const { void MediaPlaylist::SlideWindow() { DCHECK(!entries_.empty()); - if (time_shift_buffer_depth_ <= 0.0 || type_ != MediaPlaylistType::kLive) + if (time_shift_buffer_depth_ <= 0.0 || + playlist_type_ != MediaPlaylistType::kLive) { return; + } DCHECK_GT(time_scale_, 0u); // The start time of the latest segment is considered the current_play_time, diff --git a/packager/hls/base/media_playlist.h b/packager/hls/base/media_playlist.h index c10637fe0d..7342d38544 100644 --- a/packager/hls/base/media_playlist.h +++ b/packager/hls/base/media_playlist.h @@ -71,7 +71,7 @@ class MediaPlaylist { /// necessarily the same as @a file_name. /// @param group_id is the group ID for this playlist. This is the value of /// GROUP-ID attribute for EXT-X-MEDIA. - MediaPlaylist(MediaPlaylistType type, + MediaPlaylist(MediaPlaylistType playlist_type, double time_shift_buffer_depth, const std::string& file_name, const std::string& name, @@ -100,10 +100,13 @@ class MediaPlaylist { /// @param file_name is the file name of the segment. /// @param start_time is in terms of the timescale of the media. /// @param duration is in terms of the timescale of the media. + /// @param start_byte_offset is the offset of where the subsegment starts. + /// This must be 0 if the whole segment is a subsegment. /// @param size is size in bytes. virtual void AddSegment(const std::string& file_name, uint64_t start_time, uint64_t duration, + uint64_t start_byte_offset, uint64_t size); /// All segments added after calling this method must be decryptable with @@ -167,7 +170,7 @@ class MediaPlaylist { // |sequence_number_| by the number of segments removed. void SlideWindow(); - const MediaPlaylistType type_; + const MediaPlaylistType playlist_type_; const double time_shift_buffer_depth_; // Mainly for MasterPlaylist to use these values. const std::string file_name_; @@ -186,6 +189,10 @@ class MediaPlaylist { uint64_t max_bitrate_ = 0; + // Cache the previous calls AddSegment() end offset. This is used to construct + // SegmentInfoEntry. + uint64_t previous_segment_end_offset_ = 0; + // See SetTargetDuration() comments. bool target_duration_set_ = false; uint32_t target_duration_ = 0; diff --git a/packager/hls/base/media_playlist_unittest.cc b/packager/hls/base/media_playlist_unittest.cc index 11ede8390a..a28ab6decc 100644 --- a/packager/hls/base/media_playlist_unittest.cc +++ b/packager/hls/base/media_playlist_unittest.cc @@ -23,6 +23,7 @@ const char kDefaultPlaylistFileName[] = "default_playlist.m3u8"; const double kTimeShiftBufferDepth = 20; const uint64_t kTimeScale = 90000; const uint64_t kMBytes = 1000000; +const uint64_t kZeroByteOffset = 0; MATCHER_P(MatchesString, expected_string, "") { const std::string arg_string(static_cast(arg)); @@ -72,14 +73,35 @@ class MediaPlaylistTest : public ::testing::Test { MediaInfo valid_video_media_info_; }; +class MediaPlaylistMultiSegmentTest : public MediaPlaylistTest { + protected: + MediaPlaylistMultiSegmentTest() : MediaPlaylistTest() {} + // This constructor is for Live and Event playlist tests. + MediaPlaylistMultiSegmentTest(MediaPlaylist::MediaPlaylistType type) + : MediaPlaylistTest(type) {} + + void SetUp() override { + MediaPlaylistTest::SetUp(); + // This is just set to be consistent with the multisegment format and used + // as a switch in MediaPlaylist. + // The template string doesn't really matter. + valid_video_media_info_.set_segment_template("file$Number$.ts"); + } +}; + +class MediaPlaylistSingleSegmentTest : public MediaPlaylistTest { + protected: + MediaPlaylistSingleSegmentTest() : MediaPlaylistTest() {} +}; + // Verify that SetMediaInfo() fails if timescale is not present. -TEST_F(MediaPlaylistTest, NoTimeScale) { +TEST_F(MediaPlaylistMultiSegmentTest, NoTimeScale) { MediaInfo media_info; EXPECT_FALSE(media_playlist_.SetMediaInfo(media_info)); } // The current implementation only handles video and audio. -TEST_F(MediaPlaylistTest, NoAudioOrVideo) { +TEST_F(MediaPlaylistMultiSegmentTest, NoAudioOrVideo) { MediaInfo media_info; media_info.set_reference_time_scale(kTimeScale); MediaInfo::TextInfo* text_info = media_info.mutable_text_info(); @@ -87,7 +109,7 @@ TEST_F(MediaPlaylistTest, NoAudioOrVideo) { EXPECT_FALSE(media_playlist_.SetMediaInfo(media_info)); } -TEST_F(MediaPlaylistTest, SetMediaInfo) { +TEST_F(MediaPlaylistMultiSegmentTest, SetMediaInfo) { MediaInfo media_info; media_info.set_reference_time_scale(kTimeScale); MediaInfo::VideoInfo* video_info = media_info.mutable_video_info(); @@ -97,20 +119,93 @@ TEST_F(MediaPlaylistTest, SetMediaInfo) { } // Verify that AddSegment works (not crash). -TEST_F(MediaPlaylistTest, AddSegment) { +TEST_F(MediaPlaylistMultiSegmentTest, AddSegment) { ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); - media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes); + media_playlist_.AddSegment("file1.ts", 900000, 0, kZeroByteOffset, 1000000); +} + +TEST_F(MediaPlaylistSingleSegmentTest, InitRange) { + const std::string kExpectedOutput = + "#EXTM3U\n" + "#EXT-X-VERSION:6\n" + "## Generated with https://github.com/google/shaka-packager version " + "test\n" + "#EXT-X-TARGETDURATION:0\n" + "#EXT-X-PLAYLIST-TYPE:VOD\n" + "#EXT-X-MAP:URI=\"file.mp4\",BYTERANGE=501@0\n" + "#EXT-X-ENDLIST\n"; + valid_video_media_info_.set_media_file_name("file.mp4"); + valid_video_media_info_.mutable_init_range()->set_begin(0); + valid_video_media_info_.mutable_init_range()->set_end(500); + + ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); + const char kMemoryFilePath[] = "memory://media.m3u8"; + EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath)); + ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); +} + +TEST_F(MediaPlaylistSingleSegmentTest, InitRangeWithOffset) { + const std::string kExpectedOutput = + "#EXTM3U\n" + "#EXT-X-VERSION:6\n" + "## Generated with https://github.com/google/shaka-packager version " + "test\n" + "#EXT-X-TARGETDURATION:0\n" + "#EXT-X-PLAYLIST-TYPE:VOD\n" + "#EXT-X-MAP:URI=\"file.mp4\",BYTERANGE=485@16\n" + "#EXT-X-ENDLIST\n"; + valid_video_media_info_.set_media_file_name("file.mp4"); + valid_video_media_info_.mutable_init_range()->set_begin(16); + valid_video_media_info_.mutable_init_range()->set_end(500); + + ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); + const char kMemoryFilePath[] = "memory://media.m3u8"; + EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath)); + ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); +} + +// Closest to the normal use case where there is an init range and then +// subsegment ranges. There is index range between the subsegment and init range. +TEST_F(MediaPlaylistSingleSegmentTest, AddSegmentByteRange) { + const std::string kExpectedOutput = + "#EXTM3U\n" + "#EXT-X-VERSION:6\n" + "## Generated with https://github.com/google/shaka-packager version " + "test\n" + "#EXT-X-TARGETDURATION:10\n" + "#EXT-X-PLAYLIST-TYPE:VOD\n" + "#EXT-X-MAP:URI=\"file.mp4\",BYTERANGE=501@0\n" + "#EXTINF:10.000,\n" + "#EXT-X-BYTERANGE:1000000@1000\n" + "file.mp4\n" + "#EXTINF:10.000,\n" + "#EXT-X-BYTERANGE:2000000\n" + "file.mp4\n" + "#EXT-X-ENDLIST\n"; + valid_video_media_info_.set_media_file_name("file.mp4"); + valid_video_media_info_.mutable_init_range()->set_begin(0); + valid_video_media_info_.mutable_init_range()->set_end(500); + + ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); + media_playlist_.AddSegment("file.mp4", 0, 10 * kTimeScale, 1000, + 1 * kMBytes); + media_playlist_.AddSegment("file.mp4", 10 * kTimeScale, 10 * kTimeScale, + 1001000, 2 * kMBytes); + + const char kMemoryFilePath[] = "memory://media.m3u8"; + EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath)); + ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); } // Verify that AddEncryptionInfo works (not crash). -TEST_F(MediaPlaylistTest, AddEncryptionInfo) { +TEST_F(MediaPlaylistMultiSegmentTest, AddEncryptionInfo) { ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes, "http://example.com", "", "0xabcedf", "", ""); } -TEST_F(MediaPlaylistTest, WriteToFile) { +TEST_F(MediaPlaylistMultiSegmentTest, WriteToFile) { ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); const char kExpectedOutput[] = "#EXTM3U\n" @@ -127,7 +222,7 @@ TEST_F(MediaPlaylistTest, WriteToFile) { } // If bitrate (bandwidth) is not set in the MediaInfo, use it. -TEST_F(MediaPlaylistTest, UseBitrateInMediaInfo) { +TEST_F(MediaPlaylistMultiSegmentTest, UseBitrateInMediaInfo) { valid_video_media_info_.set_bandwidth(8191); ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); EXPECT_EQ(8191u, media_playlist_.Bitrate()); @@ -135,36 +230,57 @@ TEST_F(MediaPlaylistTest, UseBitrateInMediaInfo) { // If bitrate (bandwidth) is not set in the MediaInfo, then calculate from the // segments. -TEST_F(MediaPlaylistTest, GetBitrateFromSegments) { +TEST_F(MediaPlaylistMultiSegmentTest, GetBitrateFromSegments) { valid_video_media_info_.clear_bandwidth(); ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); - media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes); + media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kZeroByteOffset, + kMBytes); media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 20 * kTimeScale, - 5 * kMBytes); + kZeroByteOffset, 5 * kMBytes); // Max bitrate is 2000Kb/s. EXPECT_EQ(2000000u, media_playlist_.Bitrate()); } -TEST_F(MediaPlaylistTest, GetLongestSegmentDuration) { +TEST_F(MediaPlaylistMultiSegmentTest, GetLongestSegmentDuration) { ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); - media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes); + media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kZeroByteOffset, + kMBytes); media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale, - 5 * kMBytes); + kZeroByteOffset, 5 * kMBytes); media_playlist_.AddSegment("file3.ts", 40 * kTimeScale, 14 * kTimeScale, - 3 * kMBytes); + kZeroByteOffset, 3 * kMBytes); EXPECT_NEAR(30.0, media_playlist_.GetLongestSegmentDuration(), 0.01); } -TEST_F(MediaPlaylistTest, WriteToFileWithSegments) { +TEST_F(MediaPlaylistMultiSegmentTest, SetTargetDuration) { + ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); + media_playlist_.SetTargetDuration(20); + const std::string kExpectedOutput = + "#EXTM3U\n" + "#EXT-X-VERSION:6\n" + "## Generated with https://github.com/google/shaka-packager version " + "test\n" + "#EXT-X-TARGETDURATION:20\n" + "#EXT-X-PLAYLIST-TYPE:VOD\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, WriteToFileWithSegments) { + 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, kMBytes); + media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kZeroByteOffset, + kMBytes); media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale, - 5 * kMBytes); + kZeroByteOffset, 5 * kMBytes); const char kExpectedOutput[] = "#EXTM3U\n" "#EXT-X-VERSION:6\n" @@ -183,15 +299,17 @@ TEST_F(MediaPlaylistTest, WriteToFileWithSegments) { ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); } -TEST_F(MediaPlaylistTest, WriteToFileWithEncryptionInfo) { +TEST_F(MediaPlaylistMultiSegmentTest, WriteToFileWithEncryptionInfo) { + valid_video_media_info_.set_reference_time_scale(90000); ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes, "http://example.com", "", "0x12345678", "com.widevine", "1/2/4"); - media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes); + media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kZeroByteOffset, + kMBytes); media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale, - 5 * kMBytes); + kZeroByteOffset, 5 * kMBytes); const char kExpectedOutput[] = "#EXTM3U\n" "#EXT-X-VERSION:6\n" @@ -213,15 +331,17 @@ TEST_F(MediaPlaylistTest, WriteToFileWithEncryptionInfo) { ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); } -TEST_F(MediaPlaylistTest, WriteToFileWithEncryptionInfoEmptyIv) { +TEST_F(MediaPlaylistMultiSegmentTest, WriteToFileWithEncryptionInfoEmptyIv) { + valid_video_media_info_.set_reference_time_scale(90000); ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes, "http://example.com", "", "", "com.widevine", ""); - media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes); + media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kZeroByteOffset, + kMBytes); media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale, - 5 * kMBytes); + kZeroByteOffset, 5 * kMBytes); const char kExpectedOutput[] = "#EXTM3U\n" "#EXT-X-VERSION:6\n" @@ -243,20 +363,23 @@ TEST_F(MediaPlaylistTest, WriteToFileWithEncryptionInfoEmptyIv) { } // Verify that EXT-X-DISCONTINUITY is inserted before EXT-X-KEY. -TEST_F(MediaPlaylistTest, WriteToFileWithClearLead) { +TEST_F(MediaPlaylistMultiSegmentTest, WriteToFileWithClearLead) { + 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, kMBytes); + media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kZeroByteOffset, + kMBytes); media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes, "http://example.com", "", "0x12345678", "com.widevine", "1/2/4"); media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale, - 5 * kMBytes); + kZeroByteOffset, 5 * kMBytes); const char kExpectedOutput[] = "#EXTM3U\n" "#EXT-X-VERSION:6\n" - "## Generated with https://github.com/google/shaka-packager version test\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" @@ -274,7 +397,7 @@ TEST_F(MediaPlaylistTest, WriteToFileWithClearLead) { ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); } -TEST_F(MediaPlaylistTest, GetLanguage) { +TEST_F(MediaPlaylistMultiSegmentTest, GetLanguage) { MediaInfo media_info; media_info.set_reference_time_scale(kTimeScale); @@ -292,13 +415,15 @@ TEST_F(MediaPlaylistTest, GetLanguage) { EXPECT_EQ("apa", media_playlist_.GetLanguage()); // no short form exists } -TEST_F(MediaPlaylistTest, InitSegment) { +TEST_F(MediaPlaylistMultiSegmentTest, InitSegment) { + valid_video_media_info_.set_reference_time_scale(90000); valid_video_media_info_.set_init_segment_name("init_segment.mp4"); ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); - media_playlist_.AddSegment("file1.mp4", 0, 10 * kTimeScale, kMBytes); + media_playlist_.AddSegment("file1.mp4", 0, 10 * kTimeScale, kZeroByteOffset, + kMBytes); media_playlist_.AddSegment("file2.mp4", 10 * kTimeScale, 30 * kTimeScale, - 5 * kMBytes); + kZeroByteOffset, 5 * kMBytes); const char kExpectedOutput[] = "#EXTM3U\n" @@ -319,16 +444,18 @@ TEST_F(MediaPlaylistTest, InitSegment) { } // Verify that kSampleAesCenc is handled correctly. -TEST_F(MediaPlaylistTest, SampleAesCenc) { +TEST_F(MediaPlaylistMultiSegmentTest, SampleAesCenc) { + valid_video_media_info_.set_reference_time_scale(90000); ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); media_playlist_.AddEncryptionInfo( MediaPlaylist::EncryptionMethod::kSampleAesCenc, "http://example.com", "", "0x12345678", "com.widevine", "1/2/4"); - media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes); + media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kZeroByteOffset, + kMBytes); media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale, - 5 * kMBytes); + kZeroByteOffset, 5 * kMBytes); const char kExpectedOutput[] = "#EXTM3U\n" "#EXT-X-VERSION:6\n" @@ -350,8 +477,7 @@ TEST_F(MediaPlaylistTest, SampleAesCenc) { ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); } -// Verify that multiple encryption info can be set. -TEST_F(MediaPlaylistTest, MultipleEncryptionInfo) { +TEST_F(MediaPlaylistMultiSegmentTest, MultipleEncryptionInfo) { ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes, @@ -362,9 +488,10 @@ TEST_F(MediaPlaylistTest, MultipleEncryptionInfo) { MediaPlaylist::EncryptionMethod::kSampleAes, "http://mydomain.com", "0xfedc", "0x12345678", "com.widevine.someother", "1"); - media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes); + media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kZeroByteOffset, + kMBytes); media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale, - 5 * kMBytes); + kZeroByteOffset, 5 * kMBytes); const char kExpectedOutput[] = "#EXTM3U\n" "#EXT-X-VERSION:6\n" @@ -390,18 +517,20 @@ TEST_F(MediaPlaylistTest, MultipleEncryptionInfo) { ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); } -class LiveMediaPlaylistTest : public MediaPlaylistTest { +class LiveMediaPlaylistTest : public MediaPlaylistMultiSegmentTest { protected: LiveMediaPlaylistTest() - : MediaPlaylistTest(MediaPlaylist::MediaPlaylistType::kLive) {} + : MediaPlaylistMultiSegmentTest(MediaPlaylist::MediaPlaylistType::kLive) { + } }; TEST_F(LiveMediaPlaylistTest, Basic) { ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); - media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes); + media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kZeroByteOffset, + kMBytes); media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 20 * kTimeScale, - 2 * kMBytes); + kZeroByteOffset, 2 * kMBytes); const char kExpectedOutput[] = "#EXTM3U\n" "#EXT-X-VERSION:6\n" @@ -421,11 +550,12 @@ TEST_F(LiveMediaPlaylistTest, Basic) { TEST_F(LiveMediaPlaylistTest, TimeShifted) { ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); - media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes); + media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kZeroByteOffset, + kMBytes); media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 20 * kTimeScale, - 2 * kMBytes); + kZeroByteOffset, 2 * kMBytes); media_playlist_.AddSegment("file3.ts", 30 * kTimeScale, 20 * kTimeScale, - 2 * kMBytes); + kZeroByteOffset, 2 * kMBytes); const char kExpectedOutput[] = "#EXTM3U\n" "#EXT-X-VERSION:6\n" @@ -453,11 +583,12 @@ TEST_F(LiveMediaPlaylistTest, TimeShiftedWithEncryptionInfo) { MediaPlaylist::EncryptionMethod::kSampleAes, "http://mydomain.com", "0xfedc", "0x12345678", "com.widevine.someother", "1"); - media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes); + media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kZeroByteOffset, + kMBytes); media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 20 * kTimeScale, - 2 * kMBytes); + kZeroByteOffset, 2 * kMBytes); media_playlist_.AddSegment("file3.ts", 30 * kTimeScale, 20 * kTimeScale, - 2 * kMBytes); + kZeroByteOffset, 2 * kMBytes); const char kExpectedOutput[] = "#EXTM3U\n" "#EXT-X-VERSION:6\n" @@ -485,7 +616,8 @@ TEST_F(LiveMediaPlaylistTest, TimeShiftedWithEncryptionInfo) { TEST_F(LiveMediaPlaylistTest, TimeShiftedWithEncryptionInfoShifted) { ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); - media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes); + media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kZeroByteOffset, + kMBytes); media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes, "http://example.com", "", "0x12345678", @@ -495,7 +627,7 @@ TEST_F(LiveMediaPlaylistTest, TimeShiftedWithEncryptionInfoShifted) { "0xfedc", "0x12345678", "com.widevine.someother", "1"); media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 20 * kTimeScale, - 2 * kMBytes); + kZeroByteOffset, 2 * kMBytes); media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes, "http://example.com", "", "0x22345678", @@ -505,7 +637,7 @@ TEST_F(LiveMediaPlaylistTest, TimeShiftedWithEncryptionInfoShifted) { "0xfedd", "0x22345678", "com.widevine.someother", "1"); media_playlist_.AddSegment("file3.ts", 30 * kTimeScale, 20 * kTimeScale, - 2 * kMBytes); + kZeroByteOffset, 2 * kMBytes); media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes, "http://example.com", "", "0x32345678", @@ -515,7 +647,7 @@ TEST_F(LiveMediaPlaylistTest, TimeShiftedWithEncryptionInfoShifted) { "0xfede", "0x32345678", "com.widevine.someother", "1"); media_playlist_.AddSegment("file4.ts", 50 * kTimeScale, 20 * kTimeScale, - 2 * kMBytes); + kZeroByteOffset, 2 * kMBytes); const char kExpectedOutput[] = "#EXTM3U\n" "#EXT-X-VERSION:6\n" @@ -548,18 +680,20 @@ TEST_F(LiveMediaPlaylistTest, TimeShiftedWithEncryptionInfoShifted) { ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput); } -class EventMediaPlaylistTest : public MediaPlaylistTest { +class EventMediaPlaylistTest : public MediaPlaylistMultiSegmentTest { protected: EventMediaPlaylistTest() - : MediaPlaylistTest(MediaPlaylist::MediaPlaylistType::kEvent) {} + : MediaPlaylistMultiSegmentTest( + MediaPlaylist::MediaPlaylistType::kEvent) {} }; TEST_F(EventMediaPlaylistTest, Basic) { ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_)); - media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes); + media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kZeroByteOffset, + kMBytes); media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 20 * kTimeScale, - 2 * kMBytes); + kZeroByteOffset, 2 * kMBytes); const char kExpectedOutput[] = "#EXTM3U\n" "#EXT-X-VERSION:6\n" diff --git a/packager/hls/base/mock_media_playlist.h b/packager/hls/base/mock_media_playlist.h index 5e00706406..43e1c706f3 100644 --- a/packager/hls/base/mock_media_playlist.h +++ b/packager/hls/base/mock_media_playlist.h @@ -25,10 +25,11 @@ class MockMediaPlaylist : public MediaPlaylist { ~MockMediaPlaylist() override; MOCK_METHOD1(SetMediaInfo, bool(const MediaInfo& media_info)); - MOCK_METHOD4(AddSegment, + MOCK_METHOD5(AddSegment, void(const std::string& file_name, uint64_t start_time, uint64_t duration, + uint64_t start_byte_offset, uint64_t size)); MOCK_METHOD0(RemoveOldestSegment, void()); MOCK_METHOD6(AddEncryptionInfo, diff --git a/packager/hls/base/simple_hls_notifier.cc b/packager/hls/base/simple_hls_notifier.cc index 4d61e72472..fd085a73b3 100644 --- a/packager/hls/base/simple_hls_notifier.cc +++ b/packager/hls/base/simple_hls_notifier.cc @@ -299,6 +299,7 @@ bool SimpleHlsNotifier::NotifyNewSegment(uint32_t stream_id, const std::string& segment_name, uint64_t start_time, uint64_t duration, + uint64_t start_byte_offset, uint64_t size) { base::AutoLock auto_lock(lock_); auto stream_iterator = stream_map_.find(stream_id); @@ -311,7 +312,7 @@ bool SimpleHlsNotifier::NotifyNewSegment(uint32_t stream_id, auto& media_playlist = stream_iterator->second->media_playlist; media_playlist->AddSegment(prefix_ + relative_segment_name, start_time, - duration, size); + duration, start_byte_offset, size); // Update target duration. uint32_t longest_segment_duration = diff --git a/packager/hls/base/simple_hls_notifier.h b/packager/hls/base/simple_hls_notifier.h index 714481ea06..937782bbc2 100644 --- a/packager/hls/base/simple_hls_notifier.h +++ b/packager/hls/base/simple_hls_notifier.h @@ -67,6 +67,7 @@ class SimpleHlsNotifier : public HlsNotifier { const std::string& segment_name, uint64_t start_time, uint64_t duration, + uint64_t start_byte_offset, uint64_t size) override; bool NotifyEncryptionUpdate( uint32_t stream_id, diff --git a/packager/hls/base/simple_hls_notifier_unittest.cc b/packager/hls/base/simple_hls_notifier_unittest.cc index 4f3e943e72..d5d16296c9 100644 --- a/packager/hls/base/simple_hls_notifier_unittest.cc +++ b/packager/hls/base/simple_hls_notifier_unittest.cc @@ -7,6 +7,8 @@ #include #include +#include + #include "packager/base/base64.h" #include "packager/base/files/file_path.h" #include "packager/hls/base/mock_media_playlist.h" @@ -77,47 +79,42 @@ const char kSampleAesProtectionScheme[] = "cbca"; } // namespace +// : notifier_(profile, +// kTestTimeShiftBufferDepth, +// kTestPrefix, +// kAnyOutputDir, +// kMasterPlaylistName), + class SimpleHlsNotifierTest : public ::testing::Test { protected: SimpleHlsNotifierTest() : SimpleHlsNotifierTest(HlsProfile::kOnDemandProfile) {} SimpleHlsNotifierTest(HlsProfile profile) - : notifier_(profile, - kTestTimeShiftBufferDepth, - kTestPrefix, - kAnyOutputDir, - kMasterPlaylistName), - widevine_system_id_( + : widevine_system_id_( media::kWidevineSystemId, media::kWidevineSystemId + arraysize(media::kWidevineSystemId)), common_system_id_( media::kCommonSystemId, media::kCommonSystemId + arraysize(media::kCommonSystemId)) {} - void InjectMediaPlaylistFactory( - std::unique_ptr factory) { - notifier_.media_playlist_factory_ = std::move(factory); - } - void InjectMediaPlaylistFactory(std::unique_ptr factory, SimpleHlsNotifier* notifier) { notifier->media_playlist_factory_ = std::move(factory); } - void InjectMasterPlaylist(std::unique_ptr playlist) { - notifier_.master_playlist_ = std::move(playlist); - } - void InjectMasterPlaylist(std::unique_ptr playlist, SimpleHlsNotifier* notifier) { notifier->master_playlist_ = std::move(playlist); } - size_t NumRegisteredMediaPlaylists() { return notifier_.stream_map_.size(); } + size_t NumRegisteredMediaPlaylists(const SimpleHlsNotifier& notifier) { + return notifier.stream_map_.size(); + } uint32_t SetupStream(const std::string& protection_scheme, - MockMediaPlaylist* mock_media_playlist) { + MockMediaPlaylist* mock_media_playlist, + SimpleHlsNotifier* notifier) { MediaInfo media_info; media_info.mutable_protected_content()->set_protection_scheme( protection_scheme); @@ -133,22 +130,24 @@ class SimpleHlsNotifierTest : public ::testing::Test { EXPECT_CALL(*factory, CreateMock(_, _, _, _, _)) .WillOnce(Return(mock_media_playlist)); - InjectMasterPlaylist(std::move(mock_master_playlist)); - InjectMediaPlaylistFactory(std::move(factory)); - EXPECT_TRUE(notifier_.Init()); + InjectMasterPlaylist(std::move(mock_master_playlist), notifier); + InjectMediaPlaylistFactory(std::move(factory), notifier); + EXPECT_TRUE(notifier->Init()); uint32_t stream_id; - EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist.m3u8", "name", + EXPECT_TRUE(notifier->NotifyNewStream(media_info, "playlist.m3u8", "name", "groupid", &stream_id)); return stream_id; } - SimpleHlsNotifier notifier_; const std::vector widevine_system_id_; const std::vector common_system_id_; }; TEST_F(SimpleHlsNotifierTest, Init) { - EXPECT_TRUE(notifier_.Init()); + SimpleHlsNotifier notifier(HlsProfile::kOnDemandProfile, + kTestTimeShiftBufferDepth, kTestPrefix, + kAnyOutputDir, kMasterPlaylistName); + EXPECT_TRUE(notifier.Init()); } // Verify that relative paths can be handled. @@ -165,31 +164,36 @@ TEST_F(SimpleHlsNotifierTest, RebaseSegmentTemplateRelative) { new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); EXPECT_CALL(*mock_master_playlist, AddMediaPlaylist(mock_media_playlist)); - EXPECT_CALL( - *mock_media_playlist, - SetMediaInfo(SegmentTemplateEq("path/to/media$Number$.ts"))) + EXPECT_CALL(*mock_media_playlist, + SetMediaInfo(SegmentTemplateEq("path/to/media$Number$.ts"))) .WillOnce(Return(true)); // Verify that the common prefix is stripped for AddSegment(). - EXPECT_CALL(*mock_media_playlist, - AddSegment("http://testprefix.com/path/to/media1.ts", _, _, _)); + EXPECT_CALL( + *mock_media_playlist, + AddSegment("http://testprefix.com/path/to/media1.ts", _, _, _, _)); EXPECT_CALL(*factory, CreateMock(kVodPlaylist, Eq(kTestTimeShiftBufferDepth), StrEq("video_playlist.m3u8"), StrEq("name"), StrEq("groupid"))) .WillOnce(Return(mock_media_playlist)); - InjectMasterPlaylist(std::move(mock_master_playlist)); - InjectMediaPlaylistFactory(std::move(factory)); - EXPECT_TRUE(notifier_.Init()); + SimpleHlsNotifier notifier(HlsProfile::kOnDemandProfile, + kTestTimeShiftBufferDepth, kTestPrefix, + kAnyOutputDir, kMasterPlaylistName); + + InjectMasterPlaylist(std::move(mock_master_playlist), ¬ifier); + InjectMediaPlaylistFactory(std::move(factory), ¬ifier); + + EXPECT_TRUE(notifier.Init()); MediaInfo media_info; media_info.set_segment_template("anything/path/to/media$Number$.ts"); uint32_t stream_id; - EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "video_playlist.m3u8", - "name", "groupid", &stream_id)); + EXPECT_TRUE(notifier.NotifyNewStream(media_info, "video_playlist.m3u8", + "name", "groupid", &stream_id)); - EXPECT_TRUE( - notifier_.NotifyNewSegment(stream_id, "anything/path/to/media1.ts", - kAnyStartTime, kAnyDuration, kAnySize)); + EXPECT_TRUE(notifier.NotifyNewSegment(stream_id, "anything/path/to/media1.ts", + kAnyStartTime, kAnyDuration, 0, + kAnySize)); } // Verify that when segment template's prefix and output dir match, then the @@ -197,7 +201,6 @@ TEST_F(SimpleHlsNotifierTest, RebaseSegmentTemplateRelative) { TEST_F(SimpleHlsNotifierTest, RebaseAbsoluteSegmentTemplatePrefixAndOutputDirMatch) { const char kAbsoluteOutputDir[] = "/tmp/something/"; - // Require a separate instance to set kAbsoluteOutputDir. SimpleHlsNotifier test_notifier(HlsProfile::kOnDemandProfile, kTestTimeShiftBufferDepth, kTestPrefix, kAbsoluteOutputDir, kMasterPlaylistName); @@ -218,7 +221,7 @@ TEST_F(SimpleHlsNotifierTest, // Verify that the output_dir is stripped and then kTestPrefix is prepended. EXPECT_CALL(*mock_media_playlist, - AddSegment("http://testprefix.com/media1.ts", _, _, _)); + AddSegment("http://testprefix.com/media1.ts", _, _, _, _)); EXPECT_CALL(*factory, CreateMock(kVodPlaylist, Eq(kTestTimeShiftBufferDepth), StrEq("video_playlist.m3u8"), StrEq("name"), StrEq("groupid"))) @@ -235,7 +238,7 @@ TEST_F(SimpleHlsNotifierTest, EXPECT_TRUE( test_notifier.NotifyNewSegment(stream_id, "/tmp/something/media1.ts", - kAnyStartTime, kAnyDuration, kAnySize)); + kAnyStartTime, kAnyDuration, 0, kAnySize)); } // If the paths don't match at all and they are both absolute and completely @@ -263,7 +266,7 @@ TEST_F(SimpleHlsNotifierTest, .WillOnce(Return(true)); EXPECT_CALL(*mock_media_playlist, AddSegment("http://testprefix.com//var/somewhereelse/media1.ts", - _, _, _)); + _, _, _, _)); EXPECT_CALL(*factory, CreateMock(kVodPlaylist, Eq(kTestTimeShiftBufferDepth), StrEq("video_playlist.m3u8"), StrEq("name"), StrEq("groupid"))) @@ -279,18 +282,21 @@ TEST_F(SimpleHlsNotifierTest, "name", "groupid", &stream_id)); EXPECT_TRUE( test_notifier.NotifyNewSegment(stream_id, "/var/somewhereelse/media1.ts", - kAnyStartTime, kAnyDuration, kAnySize)); + kAnyStartTime, kAnyDuration, 0, kAnySize)); } TEST_F(SimpleHlsNotifierTest, Flush) { + SimpleHlsNotifier notifier(HlsProfile::kOnDemandProfile, + kTestTimeShiftBufferDepth, kTestPrefix, + kAnyOutputDir, kMasterPlaylistName); std::unique_ptr mock_master_playlist( new MockMasterPlaylist()); EXPECT_CALL(*mock_master_playlist, WriteMasterPlaylist(StrEq(kTestPrefix), StrEq(kAnyOutputDir))) .WillOnce(Return(true)); - InjectMasterPlaylist(std::move(mock_master_playlist)); - EXPECT_TRUE(notifier_.Init()); - EXPECT_TRUE(notifier_.Flush()); + InjectMasterPlaylist(std::move(mock_master_playlist), ¬ifier); + EXPECT_TRUE(notifier.Init()); + EXPECT_TRUE(notifier.Flush()); } TEST_F(SimpleHlsNotifierTest, NotifyNewStream) { @@ -310,14 +316,18 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewStream) { StrEq("groupid"))) .WillOnce(Return(mock_media_playlist)); - InjectMasterPlaylist(std::move(mock_master_playlist)); - InjectMediaPlaylistFactory(std::move(factory)); - EXPECT_TRUE(notifier_.Init()); + SimpleHlsNotifier notifier(HlsProfile::kOnDemandProfile, + kTestTimeShiftBufferDepth, kTestPrefix, + kAnyOutputDir, kMasterPlaylistName); + + InjectMasterPlaylist(std::move(mock_master_playlist), ¬ifier); + InjectMediaPlaylistFactory(std::move(factory), ¬ifier); + EXPECT_TRUE(notifier.Init()); MediaInfo media_info; uint32_t stream_id; - EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "video_playlist.m3u8", - "name", "groupid", &stream_id)); - EXPECT_EQ(1u, NumRegisteredMediaPlaylists()); + EXPECT_TRUE(notifier.NotifyNewStream(media_info, "video_playlist.m3u8", + "name", "groupid", &stream_id)); + EXPECT_EQ(1u, NumRegisteredMediaPlaylists(notifier)); } TEST_F(SimpleHlsNotifierTest, NotifyNewSegment) { @@ -343,24 +353,27 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewSegment) { const std::string segment_name = "segmentname"; EXPECT_CALL(*mock_media_playlist, AddSegment(StrEq(kTestPrefix + segment_name), kStartTime, - kDuration, kSize)); + kDuration, 203, kSize)); const double kLongestSegmentDuration = 11.3; const uint32_t kTargetDuration = 12; // ceil(kLongestSegmentDuration). EXPECT_CALL(*mock_media_playlist, GetLongestSegmentDuration()) .WillOnce(Return(kLongestSegmentDuration)); + SimpleHlsNotifier notifier(HlsProfile::kOnDemandProfile, + kTestTimeShiftBufferDepth, kTestPrefix, + kAnyOutputDir, kMasterPlaylistName); MockMasterPlaylist* mock_master_playlist_ptr = mock_master_playlist.get(); - InjectMasterPlaylist(std::move(mock_master_playlist)); - InjectMediaPlaylistFactory(std::move(factory)); - EXPECT_TRUE(notifier_.Init()); + InjectMasterPlaylist(std::move(mock_master_playlist), ¬ifier); + InjectMediaPlaylistFactory(std::move(factory), ¬ifier); + EXPECT_TRUE(notifier.Init()); MediaInfo media_info; uint32_t stream_id; - EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist.m3u8", "name", - "groupid", &stream_id)); + EXPECT_TRUE(notifier.NotifyNewStream(media_info, "playlist.m3u8", "name", + "groupid", &stream_id)); - EXPECT_TRUE(notifier_.NotifyNewSegment(stream_id, segment_name, kStartTime, - kDuration, kSize)); + EXPECT_TRUE(notifier.NotifyNewSegment(stream_id, segment_name, kStartTime, + kDuration, 203, kSize)); Mock::VerifyAndClearExpectations(mock_master_playlist_ptr); Mock::VerifyAndClearExpectations(mock_media_playlist); @@ -376,20 +389,26 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewSegment) { .Append(base::FilePath::FromUTF8Unsafe("playlist.m3u8")) .AsUTF8Unsafe()))) .WillOnce(Return(true)); - EXPECT_TRUE(notifier_.Flush()); + EXPECT_TRUE(notifier.Flush()); } TEST_F(SimpleHlsNotifierTest, NotifyNewSegmentWithoutStreamsRegistered) { - EXPECT_TRUE(notifier_.Init()); - EXPECT_FALSE(notifier_.NotifyNewSegment(1u, "anything", 0u, 0u, 0u)); + SimpleHlsNotifier notifier(HlsProfile::kOnDemandProfile, + kTestTimeShiftBufferDepth, kTestPrefix, + kAnyOutputDir, kMasterPlaylistName); + EXPECT_TRUE(notifier.Init()); + EXPECT_FALSE(notifier.NotifyNewSegment(1u, "anything", 0u, 0u, 0u, 0u)); } TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevine) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); + SimpleHlsNotifier notifier(HlsProfile::kOnDemandProfile, + kTestTimeShiftBufferDepth, kTestPrefix, + kAnyOutputDir, kMasterPlaylistName); const uint32_t stream_id = - SetupStream(kSampleAesProtectionScheme, mock_media_playlist); + SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier); const std::vector iv(16, 0x45); @@ -397,10 +416,8 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevine) { widevine_pssh_data.set_provider("someprovider"); widevine_pssh_data.set_content_id("contentid"); const uint8_t kAnyKeyId[] = { - 0x11, 0x22, 0x33, 0x44, - 0x11, 0x22, 0x33, 0x44, - 0x11, 0x22, 0x33, 0x44, - 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, 0x11, 0x22, 0x33, 0x44, }; std::vector any_key_id(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)); widevine_pssh_data.add_key_id()->assign(kAnyKeyId, @@ -430,21 +447,18 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevine) { base::Base64Encode(std::string(pssh_box.begin(), pssh_box.end()), &expected_pssh_base64); - EXPECT_CALL( - *mock_media_playlist, - AddEncryptionInfo(_, - StrEq("data:text/plain;base64," + expected_json_base64), - StrEq(""), - StrEq("0x45454545454545454545454545454545"), - StrEq("com.widevine"), _)); EXPECT_CALL(*mock_media_playlist, AddEncryptionInfo( - _, - StrEq("data:text/plain;base64," + expected_pssh_base64), + _, StrEq("data:text/plain;base64," + expected_json_base64), + StrEq(""), StrEq("0x45454545454545454545454545454545"), + StrEq("com.widevine"), _)); + EXPECT_CALL(*mock_media_playlist, + AddEncryptionInfo( + _, StrEq("data:text/plain;base64," + expected_pssh_base64), StrEq("0x11223344112233441122334411223344"), StrEq("0x45454545454545454545454545454545"), StrEq("urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"), _)); - EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( + EXPECT_TRUE(notifier.NotifyEncryptionUpdate( stream_id, any_key_id, widevine_system_id_, iv, pssh_box)); } @@ -453,8 +467,11 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevineNoKeyidsInPssh) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); + SimpleHlsNotifier notifier(HlsProfile::kOnDemandProfile, + kTestTimeShiftBufferDepth, kTestPrefix, + kAnyOutputDir, kMasterPlaylistName); const uint32_t stream_id = - SetupStream(kSampleAesProtectionScheme, mock_media_playlist); + SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier); const std::vector iv(16, 0x45); @@ -484,28 +501,22 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevineNoKeyidsInPssh) { base::Base64Encode(std::string(pssh_box.begin(), pssh_box.end()), &expected_pssh_base64); - EXPECT_CALL( - *mock_media_playlist, - AddEncryptionInfo(_, - StrEq("data:text/plain;base64," + expected_json_base64), - StrEq(""), - StrEq("0x45454545454545454545454545454545"), - StrEq("com.widevine"), _)); - EXPECT_CALL( - *mock_media_playlist, - AddEncryptionInfo( - _, - StrEq("data:text/plain;base64," + expected_pssh_base64), - StrEq("0x11223344112233441122334411223344"), - StrEq("0x45454545454545454545454545454545"), - StrEq("urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"), _)); + EXPECT_CALL(*mock_media_playlist, + AddEncryptionInfo( + _, StrEq("data:text/plain;base64," + expected_json_base64), + StrEq(""), StrEq("0x45454545454545454545454545454545"), + StrEq("com.widevine"), _)); + EXPECT_CALL(*mock_media_playlist, + AddEncryptionInfo( + _, StrEq("data:text/plain;base64," + expected_pssh_base64), + StrEq("0x11223344112233441122334411223344"), + StrEq("0x45454545454545454545454545454545"), + StrEq("urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"), _)); const uint8_t kAnyKeyId[] = { - 0x11, 0x22, 0x33, 0x44, - 0x11, 0x22, 0x33, 0x44, - 0x11, 0x22, 0x33, 0x44, - 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, 0x11, 0x22, 0x33, 0x44, }; - EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( + EXPECT_TRUE(notifier.NotifyEncryptionUpdate( stream_id, std::vector(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)), widevine_system_id_, iv, pssh_box)); @@ -515,8 +526,11 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateFixedKey) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); + SimpleHlsNotifier notifier(HlsProfile::kOnDemandProfile, + kTestTimeShiftBufferDepth, kTestPrefix, + kAnyOutputDir, kMasterPlaylistName); const uint32_t stream_id = - SetupStream(kSampleAesProtectionScheme, mock_media_playlist); + SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier); const std::vector key_id(16, 0x23); const std::vector iv(16, 0x45); @@ -526,14 +540,12 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateFixedKey) { base::Base64Encode(std::string(key_id.begin(), key_id.end()), &expected_key_uri_base64); - EXPECT_CALL( - *mock_media_playlist, - AddEncryptionInfo( - _, - StrEq("data:text/plain;base64," + expected_key_uri_base64), - StrEq(""), - StrEq("0x45454545454545454545454545454545"), StrEq("identity"), _)); - EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( + EXPECT_CALL(*mock_media_playlist, + AddEncryptionInfo( + _, StrEq("data:text/plain;base64," + expected_key_uri_base64), + StrEq(""), StrEq("0x45454545454545454545454545454545"), + StrEq("identity"), _)); + EXPECT_TRUE(notifier.NotifyEncryptionUpdate( stream_id, key_id, common_system_id_, iv, dummy_pssh_data)); } @@ -544,24 +556,23 @@ TEST_F(SimpleHlsNotifierTest, WidevineMultipleKeyIdsNoContentIdInPssh) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); + SimpleHlsNotifier notifier(HlsProfile::kOnDemandProfile, + kTestTimeShiftBufferDepth, kTestPrefix, + kAnyOutputDir, kMasterPlaylistName); uint32_t stream_id = - SetupStream(kSampleAesProtectionScheme, mock_media_playlist); + SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier); std::vector iv(16, 0x45); media::WidevinePsshData widevine_pssh_data; widevine_pssh_data.set_provider("someprovider"); const uint8_t kFirstKeyId[] = { - 0x11, 0x11, 0x11, 0x11, - 0x11, 0x11, 0x11, 0x11, - 0x11, 0x11, 0x11, 0x11, - 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, }; const uint8_t kSecondKeyId[] = { - 0x22, 0x22, 0x22, 0x22, - 0x22, 0x22, 0x22, 0x22, - 0x22, 0x22, 0x22, 0x22, - 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, }; std::vector first_keyid(kFirstKeyId, kFirstKeyId + arraysize(kFirstKeyId)); @@ -597,23 +608,20 @@ TEST_F(SimpleHlsNotifierTest, WidevineMultipleKeyIdsNoContentIdInPssh) { base::Base64Encode(std::string(pssh_box.begin(), pssh_box.end()), &expected_pssh_base64); - EXPECT_CALL( - *mock_media_playlist, - AddEncryptionInfo(_, - StrEq("data:text/plain;base64," + expected_json_base64), - StrEq(""), - StrEq("0x45454545454545454545454545454545"), - StrEq("com.widevine"), _)); + EXPECT_CALL(*mock_media_playlist, + AddEncryptionInfo( + _, StrEq("data:text/plain;base64," + expected_json_base64), + StrEq(""), StrEq("0x45454545454545454545454545454545"), + StrEq("com.widevine"), _)); EXPECT_CALL(*mock_media_playlist, AddEncryptionInfo( - _, - StrEq("data:text/plain;base64," + expected_pssh_base64), + _, StrEq("data:text/plain;base64," + expected_pssh_base64), StrEq("0x22222222222222222222222222222222"), StrEq("0x45454545454545454545454545454545"), StrEq("urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"), _)); - EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( + EXPECT_TRUE(notifier.NotifyEncryptionUpdate( stream_id, // Use the second key id here so that it will be thre first one in the // key_ids array in the JSON. @@ -626,8 +634,11 @@ TEST_F(SimpleHlsNotifierTest, EncryptionScheme) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); + SimpleHlsNotifier notifier(HlsProfile::kOnDemandProfile, + kTestTimeShiftBufferDepth, kTestPrefix, + kAnyOutputDir, kMasterPlaylistName); const uint32_t stream_id = - SetupStream(kCencProtectionScheme, mock_media_playlist); + SetupStream(kCencProtectionScheme, mock_media_playlist, ¬ifier); const std::vector key_id(16, 0x23); const std::vector iv(16, 0x45); @@ -641,10 +652,9 @@ TEST_F(SimpleHlsNotifierTest, EncryptionScheme) { *mock_media_playlist, AddEncryptionInfo( MediaPlaylist::EncryptionMethod::kSampleAesCenc, - StrEq("data:text/plain;base64," + expected_key_uri_base64), - StrEq(""), + StrEq("data:text/plain;base64," + expected_key_uri_base64), StrEq(""), StrEq("0x45454545454545454545454545454545"), StrEq("identity"), _)); - EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( + EXPECT_TRUE(notifier.NotifyEncryptionUpdate( stream_id, key_id, common_system_id_, iv, dummy_pssh_data)); } @@ -653,8 +663,11 @@ TEST_F(SimpleHlsNotifierTest, WidevineCencEncryptionScheme) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); + SimpleHlsNotifier notifier(HlsProfile::kOnDemandProfile, + kTestTimeShiftBufferDepth, kTestPrefix, + kAnyOutputDir, kMasterPlaylistName); const uint32_t stream_id = - SetupStream(kCencProtectionScheme, mock_media_playlist); + SetupStream(kCencProtectionScheme, mock_media_playlist, ¬ifier); const std::vector iv(16, 0x45); @@ -662,10 +675,8 @@ TEST_F(SimpleHlsNotifierTest, WidevineCencEncryptionScheme) { widevine_pssh_data.set_provider("someprovider"); widevine_pssh_data.set_content_id("contentid"); const uint8_t kAnyKeyId[] = { - 0x11, 0x22, 0x33, 0x44, - 0x11, 0x22, 0x33, 0x44, - 0x11, 0x22, 0x33, 0x44, - 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, 0x11, 0x22, 0x33, 0x44, }; std::vector any_key_id(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)); widevine_pssh_data.add_key_id()->assign(kAnyKeyId, @@ -689,12 +700,11 @@ TEST_F(SimpleHlsNotifierTest, WidevineCencEncryptionScheme) { EXPECT_CALL(*mock_media_playlist, AddEncryptionInfo( - _, - StrEq("data:text/plain;base64," + expected_pssh_base64), + _, StrEq("data:text/plain;base64," + expected_pssh_base64), StrEq("0x11223344112233441122334411223344"), StrEq("0x45454545454545454545454545454545"), StrEq("urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"), _)); - EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( + EXPECT_TRUE(notifier.NotifyEncryptionUpdate( stream_id, any_key_id, widevine_system_id_, iv, pssh_box)); } @@ -702,17 +712,18 @@ TEST_F(SimpleHlsNotifierTest, WidevineNotifyEncryptionUpdateEmptyIv) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); + SimpleHlsNotifier notifier(HlsProfile::kOnDemandProfile, + kTestTimeShiftBufferDepth, kTestPrefix, + kAnyOutputDir, kMasterPlaylistName); const uint32_t stream_id = - SetupStream(kSampleAesProtectionScheme, mock_media_playlist); + SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier); media::WidevinePsshData widevine_pssh_data; widevine_pssh_data.set_provider("someprovider"); widevine_pssh_data.set_content_id("contentid"); const uint8_t kAnyKeyId[] = { - 0x11, 0x22, 0x33, 0x44, - 0x11, 0x22, 0x33, 0x44, - 0x11, 0x22, 0x33, 0x44, - 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, 0x11, 0x22, 0x33, 0x44, + 0x11, 0x22, 0x33, 0x44, 0x11, 0x22, 0x33, 0x44, }; std::vector any_key_id(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)); widevine_pssh_data.add_key_id()->assign(kAnyKeyId, @@ -758,7 +769,7 @@ TEST_F(SimpleHlsNotifierTest, WidevineNotifyEncryptionUpdateEmptyIv) { LOG(INFO) << base_64_encoded_pssh; std::vector empty_iv; - EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( + EXPECT_TRUE(notifier.NotifyEncryptionUpdate( stream_id, std::vector(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)), widevine_system_id_, empty_iv, pssh_info.CreateBox())); @@ -769,9 +780,12 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWithoutStreamsRegistered) { std::vector iv; std::vector pssh_data; std::vector key_id; - EXPECT_TRUE(notifier_.Init()); - EXPECT_FALSE(notifier_.NotifyEncryptionUpdate(1238u, key_id, system_id, iv, - pssh_data)); + SimpleHlsNotifier notifier(HlsProfile::kOnDemandProfile, + kTestTimeShiftBufferDepth, kTestPrefix, + kAnyOutputDir, kMasterPlaylistName); + EXPECT_TRUE(notifier.Init()); + EXPECT_FALSE( + notifier.NotifyEncryptionUpdate(1238u, key_id, system_id, iv, pssh_data)); } class LiveOrEventSimpleHlsNotifierTest @@ -818,7 +832,7 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegment) { const std::string segment_name = "segmentname"; EXPECT_CALL(*mock_media_playlist, AddSegment(StrEq(kTestPrefix + segment_name), kStartTime, - kDuration, kSize)); + kDuration, _, kSize)); const double kLongestSegmentDuration = 11.3; const uint32_t kTargetDuration = 12; // ceil(kLongestSegmentDuration). @@ -837,16 +851,19 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegment) { .AsUTF8Unsafe()))) .WillOnce(Return(true)); - InjectMasterPlaylist(std::move(mock_master_playlist)); - InjectMediaPlaylistFactory(std::move(factory)); - EXPECT_TRUE(notifier_.Init()); + SimpleHlsNotifier notifier(GetParam(), + kTestTimeShiftBufferDepth, kTestPrefix, + kAnyOutputDir, kMasterPlaylistName); + InjectMasterPlaylist(std::move(mock_master_playlist), ¬ifier); + InjectMediaPlaylistFactory(std::move(factory), ¬ifier); + EXPECT_TRUE(notifier.Init()); MediaInfo media_info; uint32_t stream_id; - EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist.m3u8", "name", - "groupid", &stream_id)); + EXPECT_TRUE(notifier.NotifyNewStream(media_info, "playlist.m3u8", "name", + "groupid", &stream_id)); - EXPECT_TRUE(notifier_.NotifyNewSegment(stream_id, segment_name, kStartTime, - kDuration, kSize)); + EXPECT_TRUE(notifier.NotifyNewSegment(stream_id, segment_name, kStartTime, + kDuration, 0, kSize)); } TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegmentsWithMultipleStreams) { @@ -880,20 +897,23 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegmentsWithMultipleStreams) { *mock_master_playlist, AddMediaPlaylist(static_cast(mock_media_playlist2))); + SimpleHlsNotifier notifier(GetParam(), + kTestTimeShiftBufferDepth, kTestPrefix, + kAnyOutputDir, kMasterPlaylistName); MockMasterPlaylist* mock_master_playlist_ptr = mock_master_playlist.get(); - InjectMasterPlaylist(std::move(mock_master_playlist)); - InjectMediaPlaylistFactory(std::move(factory)); - EXPECT_TRUE(notifier_.Init()); + InjectMasterPlaylist(std::move(mock_master_playlist), ¬ifier); + InjectMediaPlaylistFactory(std::move(factory), ¬ifier); + EXPECT_TRUE(notifier.Init()); MediaInfo media_info; uint32_t stream_id1; - EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist1.m3u8", "name", + EXPECT_TRUE(notifier.NotifyNewStream(media_info, "playlist1.m3u8", "name", "groupid", &stream_id1)); uint32_t stream_id2; - EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist2.m3u8", "name", + EXPECT_TRUE(notifier.NotifyNewStream(media_info, "playlist2.m3u8", "name", "groupid", &stream_id2)); - EXPECT_CALL(*mock_media_playlist1, AddSegment(_, _, _, _)).Times(1); + EXPECT_CALL(*mock_media_playlist1, AddSegment(_, _, _, _, _)).Times(1); const double kLongestSegmentDuration = 11.3; const uint32_t kTargetDuration = 12; // ceil(kLongestSegmentDuration). EXPECT_CALL(*mock_media_playlist1, GetLongestSegmentDuration()) @@ -918,10 +938,10 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegmentsWithMultipleStreams) { .Append(base::FilePath::FromUTF8Unsafe("playlist2.m3u8")) .AsUTF8Unsafe()))) .WillOnce(Return(true)); - EXPECT_TRUE(notifier_.NotifyNewSegment(stream_id1, "segment_name", kStartTime, - kDuration, kSize)); + EXPECT_TRUE(notifier.NotifyNewSegment(stream_id1, "segment_name", kStartTime, + kDuration, 0, kSize)); - EXPECT_CALL(*mock_media_playlist2, AddSegment(_, _, _, _)).Times(1); + EXPECT_CALL(*mock_media_playlist2, AddSegment(_, _, _, _, _)).Times(1); EXPECT_CALL(*mock_media_playlist2, GetLongestSegmentDuration()) .WillOnce(Return(kLongestSegmentDuration)); EXPECT_CALL(*mock_master_playlist_ptr, WriteMasterPlaylist(_, _)) @@ -933,8 +953,8 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegmentsWithMultipleStreams) { .Append(base::FilePath::FromUTF8Unsafe("playlist2.m3u8")) .AsUTF8Unsafe()))) .WillOnce(Return(true)); - EXPECT_TRUE(notifier_.NotifyNewSegment(stream_id2, "segment_name", kStartTime, - kDuration, kSize)); + EXPECT_TRUE(notifier.NotifyNewSegment(stream_id2, "segment_name", kStartTime, + kDuration, 0, kSize)); } INSTANTIATE_TEST_CASE_P(PlaylistTypes, diff --git a/packager/media/event/hls_notify_muxer_listener.cc b/packager/media/event/hls_notify_muxer_listener.cc index 649cbb26ab..49c1a237a4 100644 --- a/packager/media/event/hls_notify_muxer_listener.cc +++ b/packager/media/event/hls_notify_muxer_listener.cc @@ -94,6 +94,11 @@ void HlsNotifyMuxerListener::OnMediaStart(const MuxerOptions& muxer_options, next_key_system_infos_, &media_info); } + media_info_ = media_info; + if (!media_info_.has_segment_template()) { + return; + } + const bool result = hls_notifier_->NotifyNewStream( media_info, playlist_name_, ext_x_media_name_, ext_x_media_group_id_, &stream_id_); @@ -117,14 +122,73 @@ void HlsNotifyMuxerListener::OnMediaEnd(const MediaRanges& media_ranges, // Don't flush the notifier here. Flushing here would write all the playlists // before all Media Playlists are read. Which could cause problems // setting the correct EXT-X-TARGETDURATION. + if (media_info_.has_segment_template()) { + return; + } + if (media_ranges.init_range) { + shaka::Range* init_range = media_info_.mutable_init_range(); + init_range->set_begin(media_ranges.init_range.value().start); + init_range->set_end(media_ranges.init_range.value().end); + } + if (media_ranges.index_range) { + shaka::Range* index_range = media_info_.mutable_index_range(); + index_range->set_begin(media_ranges.index_range.value().start); + index_range->set_end(media_ranges.index_range.value().end); + } + + // TODO(rkuroiwa): Make this a method. This is the same as OnMediaStart(). + const bool result = hls_notifier_->NotifyNewStream( + media_info_, playlist_name_, ext_x_media_name_, ext_x_media_group_id_, + &stream_id_); + if (!result) { + LOG(WARNING) << "Failed to notify new stream for VOD."; + return; + } + + // TODO(rkuroiwa); Keep track of which (sub)segments are encrypted so that the + // notification is sent right before the enecrypted (sub)segments. + media_started_ = true; + if (must_notify_encryption_start_) { + OnEncryptionStart(); + } + + if (!media_ranges.subsegment_ranges.empty()) { + const std::vector& subsegment_ranges = + media_ranges.subsegment_ranges; + size_t num_subsegments = subsegment_ranges.size(); + if (segment_infos_.size() != num_subsegments) { + LOG(WARNING) << "Number of subsegment ranges (" << num_subsegments + << ") does not match the number of subsegments notified to " + "OnNewSegment() (" + << segment_infos_.size() << ")."; + num_subsegments = std::min(segment_infos_.size(), num_subsegments); + } + for (size_t i = 0; i < num_subsegments; ++i) { + const Range& range = subsegment_ranges[i]; + const SegmentInfo& subsegment_info = segment_infos_[i]; + hls_notifier_->NotifyNewSegment( + stream_id_, media_info_.media_file_name(), subsegment_info.start_time, + subsegment_info.duration, range.start, range.end + 1 - range.start); + } + } } void HlsNotifyMuxerListener::OnNewSegment(const std::string& file_name, uint64_t start_time, uint64_t duration, uint64_t segment_file_size) { + if (!media_info_.has_segment_template()) { + SegmentInfo info; + info.duration = duration; + info.start_time = start_time; + segment_infos_.push_back(info); + return; + } + // For multisegment, it always starts from the beginning of the file. + const size_t kStartingByteOffset = 0u; const bool result = hls_notifier_->NotifyNewSegment( - stream_id_, file_name, start_time, duration, segment_file_size); + stream_id_, file_name, start_time, duration, kStartingByteOffset, + segment_file_size); LOG_IF(WARNING, !result) << "Failed to add new segment."; } diff --git a/packager/media/event/hls_notify_muxer_listener.h b/packager/media/event/hls_notify_muxer_listener.h index ac3a960dcb..edd7b780e4 100644 --- a/packager/media/event/hls_notify_muxer_listener.h +++ b/packager/media/event/hls_notify_muxer_listener.h @@ -11,6 +11,8 @@ #include "packager/base/macros.h" #include "packager/media/event/muxer_listener.h" +#include "packager/mpd/base/media_info.pb.h" +#include "packager/mpd/base/segment_info.h" namespace shaka { @@ -75,6 +77,11 @@ class HlsNotifyMuxerListener : public MuxerListener { std::vector next_key_system_infos_; FourCC protection_scheme_ = FOURCC_NULL; + // MediaInfo passed to Notifier::OnNewStream(). Mainly for single segment + // playlists. + MediaInfo media_info_; + std::vector segment_infos_; + DISALLOW_COPY_AND_ASSIGN(HlsNotifyMuxerListener); }; diff --git a/packager/media/event/hls_notify_muxer_listener_unittest.cc b/packager/media/event/hls_notify_muxer_listener_unittest.cc index 1d2a7eebfa..5b025f877d 100644 --- a/packager/media/event/hls_notify_muxer_listener_unittest.cc +++ b/packager/media/event/hls_notify_muxer_listener_unittest.cc @@ -33,11 +33,12 @@ class MockHlsNotifier : public hls::HlsNotifier { const std::string& name, const std::string& group_id, uint32_t* stream_id)); - MOCK_METHOD5(NotifyNewSegment, + MOCK_METHOD6(NotifyNewSegment, bool(uint32_t stream_id, const std::string& segment_name, uint64_t start_time, uint64_t duration, + uint64_t start_byte_offset, uint64_t size)); MOCK_METHOD5( NotifyEncryptionUpdate, @@ -125,6 +126,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnMediaStart) { .WillOnce(Return(true)); MuxerOptions muxer_options; + muxer_options.segment_template = "$Number$.mp4"; listener_.OnMediaStart(muxer_options, *video_stream_info, 90000, MuxerListener::kContainerMpeg2ts); } @@ -156,6 +158,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnEncryptionStart) { std::shared_ptr video_stream_info = CreateVideoStreamInfo(video_params); MuxerOptions muxer_options; + muxer_options.segment_template = "$Number$.mp4"; EXPECT_CALL(mock_notifier_, NotifyEncryptionUpdate(_, _, _, _, _)).Times(0); listener_.OnMediaStart(muxer_options, *video_stream_info, 90000, @@ -196,6 +199,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnEncryptionStartBeforeMediaStart) { std::shared_ptr video_stream_info = CreateVideoStreamInfo(video_params); MuxerOptions muxer_options; + muxer_options.segment_template = "$Number$.mp4"; // It doesn't really matter when this is called, could be called right away in // OnEncryptionStart() if that is possible. Just matters that it is called by @@ -235,6 +239,7 @@ TEST_F(HlsNotifyMuxerListenerTest, NoEncryptionUpdateIfNotifyNewStreamFails) { std::shared_ptr video_stream_info = CreateVideoStreamInfo(video_params); MuxerOptions muxer_options; + muxer_options.segment_template = "$Number$.mp4"; EXPECT_CALL(mock_notifier_, NotifyEncryptionUpdate(_, _, _, _, _)).Times(0); listener_.OnMediaStart(muxer_options, *video_stream_info, 90000, @@ -250,6 +255,7 @@ TEST_F(HlsNotifyMuxerListenerTest, OnEncryptionInfoReady) { std::shared_ptr video_stream_info = CreateVideoStreamInfo(video_params); MuxerOptions muxer_options; + muxer_options.segment_template = "$Number$.mp4"; listener_.OnMediaStart(muxer_options, *video_stream_info, 90000, MuxerListener::kContainerMpeg2ts); @@ -317,15 +323,120 @@ TEST_F(HlsNotifyMuxerListenerTest, OnMediaEnd) { } TEST_F(HlsNotifyMuxerListenerTest, OnNewSegment) { + ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) + .WillByDefault(Return(true)); + VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); + std::shared_ptr video_stream_info = + CreateVideoStreamInfo(video_params); + MuxerOptions muxer_options; + muxer_options.segment_template = "$Number$.mp4"; + listener_.OnMediaStart(muxer_options, *video_stream_info, 90000, + MuxerListener::kContainerMpeg2ts); + const uint64_t kStartTime = 19283; const uint64_t kDuration = 98028; const uint64_t kFileSize = 756739; EXPECT_CALL(mock_notifier_, NotifyNewSegment(_, StrEq("new_segment_name10.ts"), kStartTime, - kDuration, kFileSize)); + kDuration, _, kFileSize)); listener_.OnNewSegment("new_segment_name10.ts", kStartTime, kDuration, kFileSize); } +// Verify that the notifier is called for every segment in OnMediaEnd if +// segment_template is not set. +TEST_F(HlsNotifyMuxerListenerTest, NoSegmentTemplateOnMediaEnd) { + ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) + .WillByDefault(Return(true)); + VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); + std::shared_ptr video_stream_info = + CreateVideoStreamInfo(video_params); + MuxerOptions muxer_options; + muxer_options.output_file_name = "filename.mp4"; + listener_.OnMediaStart(muxer_options, *video_stream_info, 90000, + MuxerListener::kContainerMpeg2ts); + + const uint64_t kSegmentStartOffset = 10000; + const uint64_t kStartTime = 19283; + const uint64_t kDuration = 98028; + const uint64_t kFileSize = 756739; + + listener_.OnNewSegment("filename.mp4", kStartTime, kDuration, + kFileSize); + MuxerListener::MediaRanges ranges; + Range init_range; + init_range.start = 0; + init_range.end = 100; + Range index_range; + index_range.start = 101; + index_range.end = 200; + // Only one segment range for this test. + std::vector segment_ranges; + Range segment_range; + segment_range.start = kSegmentStartOffset; + segment_range.end = kSegmentStartOffset + kFileSize - 1; + segment_ranges.push_back(segment_range); + ranges.init_range = init_range; + ranges.index_range = index_range; + ranges.subsegment_ranges = segment_ranges; + + EXPECT_CALL(mock_notifier_, + NotifyNewSegment(_, StrEq("filename.mp4"), kStartTime, + kDuration, kSegmentStartOffset, kFileSize)); + listener_.OnMediaEnd(ranges, 200000, 98234328); +} + +// Verify that when there is a mismatch in the number of calls to +// NotifyNewSegment and the number of segment ranges, it uses the min of the +// two. +TEST_F(HlsNotifyMuxerListenerTest, + NoSegmentTemplateOnMediaEndSubsegmentSizeMismatch) { + ON_CALL(mock_notifier_, NotifyNewStream(_, _, _, _, _)) + .WillByDefault(Return(true)); + VideoStreamInfoParameters video_params = GetDefaultVideoStreamInfoParams(); + std::shared_ptr video_stream_info = + CreateVideoStreamInfo(video_params); + MuxerOptions muxer_options; + muxer_options.output_file_name = "filename.mp4"; + listener_.OnMediaStart(muxer_options, *video_stream_info, 90000, + MuxerListener::kContainerMpeg2ts); + + const uint64_t kSegmentStartOffset = 10000; + const uint64_t kStartTime = 19283; + const uint64_t kDuration = 98028; + const uint64_t kFileSize = 756739; + + listener_.OnNewSegment("filename.mp4", kStartTime, kDuration, + kFileSize); + MuxerListener::MediaRanges ranges; + Range init_range; + init_range.start = 0; + init_range.end = 100; + Range index_range; + index_range.start = 101; + index_range.end = 200; + // Only one segment range for this test. + std::vector segment_ranges; + + Range segment_range1; + segment_range1.start = kSegmentStartOffset; + segment_range1.end = kSegmentStartOffset + kFileSize - 1; + segment_ranges.push_back(segment_range1); + + Range segment_range2; + segment_range2.start = segment_range1.end + 1; + segment_range2.end = segment_range2.start + 109823; + segment_ranges.push_back(segment_range2); + + ranges.init_range = init_range; + ranges.index_range = index_range; + ranges.subsegment_ranges = segment_ranges; + + EXPECT_CALL(mock_notifier_, + NotifyNewSegment(_, StrEq("filename.mp4"), kStartTime, + kDuration, kSegmentStartOffset, kFileSize)); + listener_.OnMediaEnd(ranges, 200000, 98234328); +} + } // namespace media } // namespace shaka diff --git a/packager/media/event/muxer_listener_internal.cc b/packager/media/event/muxer_listener_internal.cc index af8b569780..4b98b26bad 100644 --- a/packager/media/event/muxer_listener_internal.cc +++ b/packager/media/event/muxer_listener_internal.cc @@ -165,7 +165,8 @@ void SetMediaInfoMuxerOptions(const MuxerOptions& muxer_options, if (muxer_options.segment_template.empty()) { media_info->set_media_file_name(muxer_options.output_file_name); } else { - media_info->set_init_segment_name(muxer_options.output_file_name); + if (!muxer_options.output_file_name.empty()) + media_info->set_init_segment_name(muxer_options.output_file_name); media_info->set_segment_template(muxer_options.segment_template); } }