From f1e4f74a144c15094aad18a63e35b5a1944ef426 Mon Sep 17 00:00:00 2001 From: Rintaro Kuroiwa Date: Fri, 25 Mar 2016 01:39:07 -0700 Subject: [PATCH] HLS MasterPlaylist class - Class to generate Master Playlist. - Takes multiple Media Playlists to generate all the playlists. Issue #85 Change-Id: I3e2be8763cc771d468245f64753e8a38b0746686 --- packager/hls/base/master_playlist.cc | 164 +++++++++++ packager/hls/base/master_playlist.h | 67 +++++ packager/hls/base/master_playlist_unittest.cc | 263 ++++++++++++++++++ packager/hls/base/media_playlist.cc | 2 +- packager/hls/base/media_playlist.h | 2 +- packager/hls/base/mock_media_playlist.cc | 19 ++ packager/hls/base/mock_media_playlist.h | 47 ++++ packager/hls/hls.gyp | 5 + 8 files changed, 567 insertions(+), 2 deletions(-) create mode 100644 packager/hls/base/master_playlist.cc create mode 100644 packager/hls/base/master_playlist.h create mode 100644 packager/hls/base/master_playlist_unittest.cc create mode 100644 packager/hls/base/mock_media_playlist.cc create mode 100644 packager/hls/base/mock_media_playlist.h diff --git a/packager/hls/base/master_playlist.cc b/packager/hls/base/master_playlist.cc new file mode 100644 index 0000000000..af4c4e9d8e --- /dev/null +++ b/packager/hls/base/master_playlist.cc @@ -0,0 +1,164 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/hls/base/master_playlist.h" + +#include + +#include +#include +#include +#include + +#include "packager/base/strings/string_number_conversions.h" +#include "packager/base/strings/stringprintf.h" +#include "packager/hls/base/media_playlist.h" +#include "packager/media/file/file.h" +#include "packager/media/file/file_closer.h" + +namespace edash_packager { +namespace hls { + +MasterPlaylist::MasterPlaylist(const std::string& file_name) + : file_name_(file_name) {} +MasterPlaylist::~MasterPlaylist() {} + +void MasterPlaylist::AddMediaPlaylist(MediaPlaylist* media_playlist) { + media_playlists_.push_back(media_playlist); +} + +bool MasterPlaylist::WriteAllPlaylists(const std::string& base_url, + const std::string& output_dir) { + if (!WriteMasterPlaylist(base_url, output_dir)) { + LOG(ERROR) << "Failed to write master playlist."; + return false; + } + + double longest_segment_duration = 0.0; + if (!has_set_playlist_target_duration_) { + for (const MediaPlaylist* playlist : media_playlists_) { + const double playlist_longest_segment = + playlist->GetLongestSegmentDuration(); + if (longest_segment_duration < playlist_longest_segment) + longest_segment_duration = playlist_longest_segment; + } + } + + for (MediaPlaylist* playlist : media_playlists_) { + std::string file_path = output_dir + playlist->file_name(); + if (!has_set_playlist_target_duration_) { + const bool set_target_duration = playlist->SetTargetDuration( + static_cast(ceil(longest_segment_duration))); + LOG_IF(WARNING, !set_target_duration) + << "Target duration was already set for " << file_path; + } + + scoped_ptr file( + media::File::Open(file_path.c_str(), "w")); + if (!file) { + LOG(ERROR) << "Failed to open file " << file_path; + return false; + } + if (!playlist->WriteToFile(file.get())) { + LOG(ERROR) << "Failed to write playlist " << file_path; + return false; + } + } + + has_set_playlist_target_duration_ = true; + return true; +} + +bool MasterPlaylist::WriteMasterPlaylist(const std::string& base_url, + const std::string& output_dir) { + std::string file_path = output_dir + file_name_; + scoped_ptr file( + media::File::Open(file_path.c_str(), "w")); + if (!file) { + LOG(ERROR) << "Failed to open file " << file_path; + return false; + } + + // TODO(rkuroiwa): This can be done in AddMediaPlaylist(), no need to create + // map and list on the fly. + std::map> audio_group_map; + std::list video_playlists; + for (const MediaPlaylist* media_playlist : media_playlists_) { + MediaPlaylist::MediaPlaylistType type = media_playlist->type(); + if (type == MediaPlaylist::MediaPlaylistType::kPlayListAudio) { + auto& audio_playlists = audio_group_map[media_playlist->group_id()]; + audio_playlists.push_back(media_playlist); + } else if (type == MediaPlaylist::MediaPlaylistType::kPlayListVideo) { + video_playlists.push_back(media_playlist); + } else { + NOTIMPLEMENTED() << static_cast(type) << " not handled."; + } + } + + // TODO(rkuroiwa): Handle audio only. + std::string audio_output; + std::string video_output; + for (auto& group_id_audio_playlists : audio_group_map) { + const std::string& group_id = group_id_audio_playlists.first; + const std::list& audio_playlists = + group_id_audio_playlists.second; + + uint64_t max_audio_bitrate = 0; + for (const MediaPlaylist* audio_playlist : audio_playlists) { + base::StringAppendF( + &audio_output, + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"%s\",NAME=\"%s\",URI=\"%s\"\n", + group_id.c_str(), audio_playlist->name().c_str(), + (base_url + audio_playlist->file_name()).c_str()); + const uint64_t audio_bitrate = audio_playlist->Bitrate(); + if (audio_bitrate > max_audio_bitrate) + max_audio_bitrate = audio_bitrate; + } + for (const MediaPlaylist* video_playlist : video_playlists) { + const std::string& video_codec = video_playlist->codec(); + const uint64_t video_bitrate = video_playlist->Bitrate(); + + // Assume all codecs are the same for same group ID. + const std::string& audio_codec = audio_playlists.front()->codec(); + base::StringAppendF( + &video_output, + "#EXT-X-STREAM-INF:AUDIO=\"%s\",CODEC=\"%s\",BANDWIDTH=%" PRIu64 "\n" + "%s\n", + group_id.c_str(), (video_codec + "," + audio_codec).c_str(), + video_bitrate + max_audio_bitrate, + (base_url + video_playlist->file_name()).c_str()); + } + } + + if (audio_group_map.empty()) { + for (const MediaPlaylist* video_playlist : video_playlists) { + const std::string& video_codec = video_playlist->codec(); + const uint64_t video_bitrate = video_playlist->Bitrate(); + base::StringAppendF(&video_output, + "#EXT-X-STREAM-INF:CODEC=\"%s\",BANDWIDTH=%" PRIu64 + "\n%s\n", + video_codec.c_str(), video_bitrate, + (base_url + video_playlist->file_name()).c_str()); + } + } + + std::string content = "#EXTM3U\n" + audio_output + video_output; + int64_t bytes_written = file->Write(content.data(), content.size()); + if (bytes_written < 0) { + LOG(ERROR) << "Error while writing master playlist " << file_path; + return false; + } + if (static_cast(bytes_written) != content.size()) { + LOG(ERROR) << "Written " << bytes_written << " but content size is " + << content.size() << " " << file_path; + return false; + } + + return true; +} + +} // namespace hls +} // namespace edash_packager diff --git a/packager/hls/base/master_playlist.h b/packager/hls/base/master_playlist.h new file mode 100644 index 0000000000..ba68dcf51f --- /dev/null +++ b/packager/hls/base/master_playlist.h @@ -0,0 +1,67 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef PACKAGER_HLS_BASE_MASTER_PLAYLIST_H_ +#define PACKAGER_HLS_BASE_MASTER_PLAYLIST_H_ + +#include +#include + +#include "packager/base/macros.h" + +namespace edash_packager { +namespace hls { + +class MediaPlaylist; + +/// Class to generate HLS Master Playlist. +class MasterPlaylist { + public: + /// @param file_name is the file name of the master playlist. + explicit MasterPlaylist(const std::string& file_name); + ~MasterPlaylist(); + + /// @param media_playlist is a MediaPlaylist that should get added to this + /// master playlist. Ownership does not transfer. + /// @return true on success, false otherwise. + void AddMediaPlaylist(MediaPlaylist* media_playlist); + + /// Write out Master Playlist and all the added MediaPlaylists to + /// base_url + . + /// This assumes that @a base_url is used as the prefix for Media Playlists. + /// @param base_url is the prefix for the playlist files. This should be in + /// URI form such that prefix_+file_name is a valid HLS URI. + /// @param output_dir is where the playlist files are written. This is not + /// necessarily the same as base_url. It must be in a form that File + /// interface can open. + /// @return true on success, false otherwise. + bool WriteAllPlaylists(const std::string& base_url, + const std::string& output_dir); + + /// Writes Master Playlist to output_dir + . + /// This assumes that @a base_url is used as the prefix for Media Playlists. + /// @param base_url is the prefix for the Media Playlist files. This should be + /// in URI form such that base_url+file_name is a valid HLS URI. + /// @param output_dir is where the playlist files are written. This is not + /// necessarily the same as base_url. It must be in a form that File + /// interface can open. + /// @return true on success, false otherwise. + bool WriteMasterPlaylist(const std::string& base_url, + const std::string& output_dir); + + private: + const std::string file_name_; + std::list media_playlists_; + + bool has_set_playlist_target_duration_ = false; + + DISALLOW_COPY_AND_ASSIGN(MasterPlaylist); +}; + +} // namespace hls +} // namespace edash_packager + +#endif // PACKAGER_HLS_BASE_MASTER_PLAYLIST_H_ diff --git a/packager/hls/base/master_playlist_unittest.cc b/packager/hls/base/master_playlist_unittest.cc new file mode 100644 index 0000000000..c8f591602a --- /dev/null +++ b/packager/hls/base/master_playlist_unittest.cc @@ -0,0 +1,263 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include +#include + +#include "packager/base/files/file_util.h" +#include "packager/base/files/scoped_temp_dir.h" +#include "packager/hls/base/master_playlist.h" +#include "packager/hls/base/media_playlist.h" +#include "packager/hls/base/mock_media_playlist.h" +#include "packager/media/file/file.h" + +namespace edash_packager { +namespace hls { + +using ::testing::AtLeast; +using ::testing::Return; +using ::testing::ReturnRef; +using ::testing::_; + +namespace { +const char kDefaultMasterPlaylistName[] = "playlist.m3u8"; +} // namespace + +class MasterPlaylistTest : public ::testing::Test { + protected: + MasterPlaylistTest() : master_playlist_(kDefaultMasterPlaylistName) {} + + void SetUp() override { + GetOutputDir(&test_output_dir_path_, &test_output_dir_); + } + + MasterPlaylist master_playlist_; + base::FilePath test_output_dir_path_; + std::string test_output_dir_; + + private: + // Creates a path to the output directory for writing out playlists. + // |temp_dir_path| is set to the temporary directory so that it can be opened + // using base::File* related API. + // |output_dir| is set to an equivalent value to |temp_dir_path| but formatted + // so that media::File interface can Open it. + void GetOutputDir(base::FilePath* temp_dir_path, std::string* output_dir) { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(temp_dir_.IsValid()); + *temp_dir_path = temp_dir_.path(); + // TODO(rkuroiwa): Use memory file sys once prefix is exposed. + *output_dir = media::kLocalFilePrefix + temp_dir_.path().value() + "/"; + } + + base::ScopedTempDir temp_dir_; +}; + +TEST_F(MasterPlaylistTest, AddMediaPlaylist) { + MockMediaPlaylist mock_playlist("playlist1.m3u8", "somename", "somegroupid"); + master_playlist_.AddMediaPlaylist(&mock_playlist); +} + +TEST_F(MasterPlaylistTest, WriteMasterPlaylistOneVideo) { + std::string codec = "avc1"; + MockMediaPlaylist mock_playlist("media1.m3u8", "somename", "somegroupid"); + mock_playlist.SetTypeForTesting( + MediaPlaylist::MediaPlaylistType::kPlayListVideo); + mock_playlist.SetCodecForTesting(codec); + EXPECT_CALL(mock_playlist, Bitrate()).WillOnce(Return(435889)); + master_playlist_.AddMediaPlaylist(&mock_playlist); + + const char kBaseUrl[] = "http://myplaylistdomain.com/"; + EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_)); + + base::FilePath master_playlist_path = + test_output_dir_path_.Append(kDefaultMasterPlaylistName); + ASSERT_TRUE(base::PathExists(master_playlist_path)) + << "Cannot find " << master_playlist_path.value(); + + std::string actual; + ASSERT_TRUE(base::ReadFileToString(master_playlist_path, &actual)); + + const std::string expected = + "#EXTM3U\n" + "#EXT-X-STREAM-INF:CODEC=\"avc1\",BANDWIDTH=435889\n" + "http://myplaylistdomain.com/media1.m3u8\n"; + + ASSERT_EQ(expected, actual); +} + +TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) { + // First video, sd.m3u8. + std::string sd_video_codec = "sdvideocodec"; + MockMediaPlaylist sd_video_playlist("sd.m3u8", "somename", "somegroupid"); + sd_video_playlist.SetTypeForTesting( + MediaPlaylist::MediaPlaylistType::kPlayListVideo); + sd_video_playlist.SetCodecForTesting(sd_video_codec); + EXPECT_CALL(sd_video_playlist, Bitrate()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(300000)); + master_playlist_.AddMediaPlaylist(&sd_video_playlist); + + // Second video, hd.m3u8. + std::string hd_video_codec = "hdvideocodec"; + MockMediaPlaylist hd_video_playlist("hd.m3u8", "somename", "somegroupid"); + hd_video_playlist.SetTypeForTesting( + MediaPlaylist::MediaPlaylistType::kPlayListVideo); + hd_video_playlist.SetCodecForTesting(hd_video_codec); + EXPECT_CALL(hd_video_playlist, Bitrate()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(700000)); + master_playlist_.AddMediaPlaylist(&hd_video_playlist); + + // First audio, english.m3u8. + // Note that audiocodecs should match for different audio tracks with same + // group ID. + std::string audio_codec = "audiocodec"; + MockMediaPlaylist english_playlist("eng.m3u8", "english", "audiogroup"); + english_playlist.SetTypeForTesting( + MediaPlaylist::MediaPlaylistType::kPlayListAudio); + english_playlist.SetCodecForTesting(audio_codec); + EXPECT_CALL(english_playlist, Bitrate()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(50000)); + master_playlist_.AddMediaPlaylist(&english_playlist); + + // Second audio, spanish.m3u8. + MockMediaPlaylist spanish_playlist("spa.m3u8", "espanol", "audiogroup"); + spanish_playlist.SetTypeForTesting( + MediaPlaylist::MediaPlaylistType::kPlayListAudio); + spanish_playlist.SetCodecForTesting(audio_codec); + EXPECT_CALL(spanish_playlist, Bitrate()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(60000)); + master_playlist_.AddMediaPlaylist(&spanish_playlist); + + const char kBaseUrl[] = "http://playlists.org/"; + EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_)); + + base::FilePath master_playlist_path = + test_output_dir_path_.Append(kDefaultMasterPlaylistName); + ASSERT_TRUE(base::PathExists(master_playlist_path)) + << "Cannot find " << master_playlist_path.value(); + + std::string actual; + ASSERT_TRUE(base::ReadFileToString(master_playlist_path, &actual)); + + const std::string expected = + "#EXTM3U\n" + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audiogroup\",NAME=\"english\"," + "URI=\"http://playlists.org/eng.m3u8\"\n" + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audiogroup\",NAME=\"espanol\"," + "URI=\"http://playlists.org/spa.m3u8\"\n" + "#EXT-X-STREAM-INF:AUDIO=\"audiogroup\"," + "CODEC=\"sdvideocodec,audiocodec\"," + "BANDWIDTH=360000\n" + "http://playlists.org/sd.m3u8\n" + "#EXT-X-STREAM-INF:AUDIO=\"audiogroup\"," + "CODEC=\"hdvideocodec,audiocodec\"," + "BANDWIDTH=760000\n" + "http://playlists.org/hd.m3u8\n"; + + ASSERT_EQ(expected, actual); +} + +TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) { + // First video, sd.m3u8. + std::string video_codec = "videocodec"; + MockMediaPlaylist video_playlist("video.m3u8", "somename", "somegroupid"); + video_playlist.SetTypeForTesting( + MediaPlaylist::MediaPlaylistType::kPlayListVideo); + video_playlist.SetCodecForTesting(video_codec); + EXPECT_CALL(video_playlist, Bitrate()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(300000)); + master_playlist_.AddMediaPlaylist(&video_playlist); + + // First audio, eng_lo.m3u8. + std::string audio_codec_lo = "audiocodec_lo"; + MockMediaPlaylist eng_lo_playlist("eng_lo.m3u8", "english_lo", "audio_lo"); + eng_lo_playlist.SetTypeForTesting( + MediaPlaylist::MediaPlaylistType::kPlayListAudio); + eng_lo_playlist.SetCodecForTesting(audio_codec_lo); + EXPECT_CALL(eng_lo_playlist, Bitrate()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(50000)); + master_playlist_.AddMediaPlaylist(&eng_lo_playlist); + + std::string audio_codec_hi = "audiocodec_hi"; + MockMediaPlaylist eng_hi_playlist("eng_hi.m3u8", "english_hi", "audio_hi"); + eng_hi_playlist.SetTypeForTesting( + MediaPlaylist::MediaPlaylistType::kPlayListAudio); + eng_hi_playlist.SetCodecForTesting(audio_codec_hi); + EXPECT_CALL(eng_hi_playlist, Bitrate()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(100000)); + master_playlist_.AddMediaPlaylist(&eng_hi_playlist); + + const char kBaseUrl[] = "http://anydomain.com/"; + EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_)); + + base::FilePath master_playlist_path = + test_output_dir_path_.Append(kDefaultMasterPlaylistName); + ASSERT_TRUE(base::PathExists(master_playlist_path)) + << "Cannot find " << master_playlist_path.value(); + + std::string actual; + ASSERT_TRUE(base::ReadFileToString(master_playlist_path, &actual)); + + const std::string expected = + "#EXTM3U\n" + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio_hi\",NAME=\"english_hi\"," + "URI=\"http://anydomain.com/eng_hi.m3u8\"\n" + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio_lo\",NAME=\"english_lo\"," + "URI=\"http://anydomain.com/eng_lo.m3u8\"\n" + "#EXT-X-STREAM-INF:AUDIO=\"audio_hi\"," + "CODEC=\"videocodec,audiocodec_hi\"," + "BANDWIDTH=400000\n" + "http://anydomain.com/video.m3u8\n" + "#EXT-X-STREAM-INF:AUDIO=\"audio_lo\"," + "CODEC=\"videocodec,audiocodec_lo\"," + "BANDWIDTH=350000\n" + "http://anydomain.com/video.m3u8\n"; + + ASSERT_EQ(expected, actual); +} + +MATCHER_P(FileNameMatches, expected_file_name, "") { + const std::string& actual_filename = arg->file_name(); + *result_listener << "which is " << actual_filename; + return expected_file_name == actual_filename; +} + +// This test basically is WriteMasterPlaylist() and also make sure that +// the target duration is set for MediaPlaylist and +// MediaPlaylist::WriteToFile() is called. +TEST_F(MasterPlaylistTest, WriteAllPlaylists) { + std::string codec = "avc1"; + MockMediaPlaylist mock_playlist("media1.m3u8", "somename", "somegroupid"); + mock_playlist.SetTypeForTesting( + MediaPlaylist::MediaPlaylistType::kPlayListVideo); + mock_playlist.SetCodecForTesting(codec); + ON_CALL(mock_playlist, Bitrate()).WillByDefault(Return(435889)); + + EXPECT_CALL(mock_playlist, GetLongestSegmentDuration()).WillOnce(Return(10)); + EXPECT_CALL(mock_playlist, SetTargetDuration(10)).WillOnce(Return(true)); + master_playlist_.AddMediaPlaylist(&mock_playlist); + + EXPECT_CALL(mock_playlist, + WriteToFile(FileNameMatches( + test_output_dir_path_.Append("media1.m3u8").value()))) + .WillOnce(Return(true)); + + const char kBaseUrl[] = "http://domain.com/"; + EXPECT_TRUE(master_playlist_.WriteAllPlaylists(kBaseUrl, test_output_dir_)); + base::FilePath master_playlist_path = + test_output_dir_path_.Append(kDefaultMasterPlaylistName); + ASSERT_TRUE(base::PathExists(master_playlist_path)) + << "Cannot find master playlist at " << master_playlist_path.value(); +} + +} // namespace hls +} // namespace edash_packager diff --git a/packager/hls/base/media_playlist.cc b/packager/hls/base/media_playlist.cc index 66cbd941aa..6347b2d370 100644 --- a/packager/hls/base/media_playlist.cc +++ b/packager/hls/base/media_playlist.cc @@ -301,7 +301,7 @@ uint64_t MediaPlaylist::Bitrate() const { return total_segments_size_ / total_duration_in_seconds_; } -double MediaPlaylist::GetLongestSegmentDuration() { +double MediaPlaylist::GetLongestSegmentDuration() const { return longest_segment_duration_; } diff --git a/packager/hls/base/media_playlist.h b/packager/hls/base/media_playlist.h index fd21f7cda0..aaa6e58ca0 100644 --- a/packager/hls/base/media_playlist.h +++ b/packager/hls/base/media_playlist.h @@ -133,7 +133,7 @@ class MediaPlaylist { /// @return the longest segment’s duration. This will return 0 if no /// segments have been added. - virtual double GetLongestSegmentDuration(); + virtual double GetLongestSegmentDuration() const; /// Set the target duration of this MediaPlaylist. /// In other words this is the value for EXT-X-TARGETDURATION. diff --git a/packager/hls/base/mock_media_playlist.cc b/packager/hls/base/mock_media_playlist.cc new file mode 100644 index 0000000000..d0a7faec9d --- /dev/null +++ b/packager/hls/base/mock_media_playlist.cc @@ -0,0 +1,19 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/hls/base/mock_media_playlist.h" + +namespace edash_packager { +namespace hls { + +MockMediaPlaylist::MockMediaPlaylist(const std::string& file_name, + const std::string& name, + const std::string& group_id) + : MediaPlaylist(file_name, name, group_id) {} +MockMediaPlaylist::~MockMediaPlaylist() {} + +} // namespace hls +} // namespace edash_packager diff --git a/packager/hls/base/mock_media_playlist.h b/packager/hls/base/mock_media_playlist.h new file mode 100644 index 0000000000..93b6b27018 --- /dev/null +++ b/packager/hls/base/mock_media_playlist.h @@ -0,0 +1,47 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef PACKAGER_HLS_BASE_MOCK_MEDIA_PLAYLIST_H_ +#define PACKAGER_HLS_BASE_MOCK_MEDIA_PLAYLIST_H_ + +#include + +#include "packager/hls/base/media_playlist.h" + +namespace edash_packager { +namespace hls { + +class MockMediaPlaylist : public MediaPlaylist { + public: + // The actual parameters to MediaPlaylist() (parent) constructor doesn't + // matter because the return value can be mocked. + MockMediaPlaylist(const std::string& file_name, + const std::string& name, + const std::string& group_id); + ~MockMediaPlaylist() override; + + MOCK_METHOD1(SetMediaInfo, bool(const MediaInfo& media_info)); + MOCK_METHOD3(AddSegment, + void(const std::string& file_name, + uint64_t duration, + uint64_t size)); + MOCK_METHOD0(RemoveOldestSegment, void()); + MOCK_METHOD5(AddEncryptionInfo, + void(EncryptionMethod method, + const std::string& url, + const std::string& iv, + const std::string& key_format, + const std::string& key_format_versions)); + MOCK_METHOD1(WriteToFile, bool(media::File* file)); + MOCK_CONST_METHOD0(Bitrate, uint64_t()); + MOCK_CONST_METHOD0(GetLongestSegmentDuration, double()); + MOCK_METHOD1(SetTargetDuration, bool(uint32_t target_duration)); +}; + +} // namespace hls +} // namespace edash_packager + +#endif // PACKAGER_HLS_BASE_MOCK_MEDIA_PLAYLIST_H_ diff --git a/packager/hls/hls.gyp b/packager/hls/hls.gyp index 58a423ca4f..bcf59dd7c9 100644 --- a/packager/hls/hls.gyp +++ b/packager/hls/hls.gyp @@ -13,6 +13,8 @@ 'target_name': 'hls_builder', 'type': '<(component)', 'sources': [ + 'base/master_playlist.cc', + 'base/master_playlist.h', 'base/media_playlist.cc', 'base/media_playlist.h', ], @@ -26,7 +28,10 @@ 'target_name': 'hls_unittest', 'type': '<(gtest_target_type)', 'sources': [ + 'base/master_playlist_unittest.cc', 'base/media_playlist_unittest.cc', + 'base/mock_media_playlist.cc', + 'base/mock_media_playlist.h', ], 'dependencies': [ '../base/base.gyp:base',