Refactor MasterPlaylist::WriteMasterPlaylist

Change-Id: Ida7453761748087cd13aab640c400381696578e1
This commit is contained in:
KongQun Yang 2018-02-02 09:14:46 -08:00
parent b647c6731c
commit 04577b9638
6 changed files with 109 additions and 160 deletions

View File

@ -25,6 +25,14 @@ const char* kDefaultAudioGroupId = "default-audio-group";
const char* kDefaultSubtitleGroupId = "default-text-group"; const char* kDefaultSubtitleGroupId = "default-text-group";
const char* kUnexpectedGroupId = "unexpected-group"; const char* kUnexpectedGroupId = "unexpected-group";
void AppendVersionString(std::string* content) {
const std::string version = GetPackagerVersion();
if (version.empty())
return;
base::StringAppendF(content, "## Generated with %s version %s\n",
GetPackagerProjectUrl().c_str(), version.c_str());
}
struct Variant { struct Variant {
std::string audio_codec; std::string audio_codec;
const std::string* audio_group_id = nullptr; const std::string* audio_group_id = nullptr;
@ -271,6 +279,47 @@ void BuildMediaTags(
} }
} }
} }
void AppendPlaylists(const std::string& default_language,
const std::string& base_url,
const std::list<MediaPlaylist*>& playlists,
std::string* content) {
std::map<std::string, std::list<const MediaPlaylist*>> audio_playlist_groups;
std::map<std::string, std::list<const MediaPlaylist*>>
subtitle_playlist_groups;
std::list<const MediaPlaylist*> video_playlists;
std::list<const MediaPlaylist*> iframe_playlists;
for (const MediaPlaylist* playlist : playlists) {
switch (playlist->stream_type()) {
case MediaPlaylist::MediaPlaylistStreamType::kAudio:
audio_playlist_groups[GetGroupId(*playlist)].push_back(playlist);
break;
case MediaPlaylist::MediaPlaylistStreamType::kVideo:
video_playlists.push_back(playlist);
break;
case MediaPlaylist::MediaPlaylistStreamType::kSubtitle:
subtitle_playlist_groups[GetGroupId(*playlist)].push_back(playlist);
break;
default:
NOTIMPLEMENTED() << static_cast<int>(playlist->stream_type())
<< " not handled.";
}
}
BuildMediaTags(audio_playlist_groups, default_language, base_url, content);
BuildMediaTags(subtitle_playlist_groups, default_language, base_url, content);
std::list<Variant> variants =
BuildVariants(audio_playlist_groups, subtitle_playlist_groups);
for (const auto& playlist : video_playlists) {
for (const auto& variant : variants) {
BuildVideoTag(*playlist, variant.audio_bitrate, variant.audio_codec,
variant.audio_group_id, variant.text_group_id, base_url,
content);
}
}
}
} // namespace } // namespace
MasterPlaylist::MasterPlaylist(const std::string& file_name, MasterPlaylist::MasterPlaylist(const std::string& file_name,
@ -278,73 +327,13 @@ MasterPlaylist::MasterPlaylist(const std::string& file_name,
: file_name_(file_name), default_language_(default_language) {} : file_name_(file_name), default_language_(default_language) {}
MasterPlaylist::~MasterPlaylist() {} MasterPlaylist::~MasterPlaylist() {}
void MasterPlaylist::AddMediaPlaylist(MediaPlaylist* media_playlist) { bool MasterPlaylist::WriteMasterPlaylist(
DCHECK(media_playlist); const std::string& base_url,
switch (media_playlist->stream_type()) { const std::string& output_dir,
case MediaPlaylist::MediaPlaylistStreamType::kAudio: { const std::list<MediaPlaylist*>& playlists) {
std::string group_id = GetGroupId(*media_playlist); std::string content = "#EXTM3U\n";
audio_playlist_groups_[group_id].push_back(media_playlist); AppendVersionString(&content);
break; AppendPlaylists(default_language_, base_url, playlists, &content);
}
case MediaPlaylist::MediaPlaylistStreamType::kVideo: {
video_playlists_.push_back(media_playlist);
break;
}
case MediaPlaylist::MediaPlaylistStreamType::kSubtitle: {
std::string group_id = GetGroupId(*media_playlist);
subtitle_playlist_groups_[group_id].push_back(media_playlist);
break;
}
default: {
NOTIMPLEMENTED() << static_cast<int>(media_playlist->stream_type())
<< " not handled.";
break;
}
}
// Sometimes we need to iterate over all playlists, so keep a collection
// of all playlists to make iterating easier.
all_playlists_.push_back(media_playlist);
}
bool MasterPlaylist::WriteMasterPlaylist(const std::string& base_url,
const std::string& output_dir) {
// TODO(rkuroiwa): Handle audio only.
std::string audio_output;
std::string video_output;
std::string subtitle_output;
// Write out all the audio tags.
BuildMediaTags(audio_playlist_groups_, default_language_, base_url,
&audio_output);
// Write out all the text tags.
BuildMediaTags(subtitle_playlist_groups_, default_language_, base_url,
&subtitle_output);
std::list<Variant> variants =
BuildVariants(audio_playlist_groups_, subtitle_playlist_groups_);
// Write all the video tags out.
for (const auto& playlist : video_playlists_) {
for (const auto& variant : variants) {
BuildVideoTag(*playlist, variant.audio_bitrate, variant.audio_codec,
variant.audio_group_id, variant.text_group_id, base_url,
&video_output);
}
}
const std::string version = GetPackagerVersion();
std::string version_line;
if (!version.empty()) {
version_line =
base::StringPrintf("## Generated with %s version %s\n",
GetPackagerProjectUrl().c_str(), version.c_str());
}
std::string content = "";
base::StringAppendF(&content, "#EXTM3U\n%s%s%s%s", version_line.c_str(),
audio_output.c_str(), subtitle_output.c_str(),
video_output.c_str());
// Skip if the playlist is already written. // Skip if the playlist is already written.
if (content == written_playlist_) if (content == written_playlist_)

View File

@ -8,11 +8,8 @@
#define PACKAGER_HLS_BASE_MASTER_PLAYLIST_H_ #define PACKAGER_HLS_BASE_MASTER_PLAYLIST_H_
#include <list> #include <list>
#include <map>
#include <string> #include <string>
#include "packager/base/macros.h"
namespace shaka { namespace shaka {
namespace hls { namespace hls {
@ -29,11 +26,6 @@ class MasterPlaylist {
const std::string& default_language); const std::string& default_language);
virtual ~MasterPlaylist(); virtual ~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.
virtual void AddMediaPlaylist(MediaPlaylist* media_playlist);
/// Writes Master Playlist to output_dir + <name of playlist>. /// Writes Master Playlist to output_dir + <name of playlist>.
/// This assumes that @a base_url is used as the prefix for Media Playlists. /// 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 /// @param base_url is the prefix for the Media Playlist files. This should be
@ -44,23 +36,16 @@ class MasterPlaylist {
/// @return true if the playlist is updated successfully or there is no /// @return true if the playlist is updated successfully or there is no
/// difference since the last write, false otherwise. /// difference since the last write, false otherwise.
virtual bool WriteMasterPlaylist(const std::string& base_url, virtual bool WriteMasterPlaylist(const std::string& base_url,
const std::string& output_dir); const std::string& output_dir,
const std::list<MediaPlaylist*>& playlists);
private: private:
MasterPlaylist(const MasterPlaylist&) = delete;
MasterPlaylist& operator=(const MasterPlaylist&) = delete;
std::string written_playlist_; std::string written_playlist_;
const std::string file_name_; const std::string file_name_;
const std::string default_language_; const std::string default_language_;
std::list<MediaPlaylist*> all_playlists_;
std::list<const MediaPlaylist*> video_playlists_;
// The ID is the group name, and the value is the list of all media playlists
// in that group. Keep audio and subtitle separate as they are processed
// separately.
std::map<std::string, std::list<const MediaPlaylist*>> audio_playlist_groups_;
std::map<std::string, std::list<const MediaPlaylist*>>
subtitle_playlist_groups_;
DISALLOW_COPY_AND_ASSIGN(MasterPlaylist);
}; };
} // namespace hls } // namespace hls

View File

@ -117,21 +117,15 @@ class MasterPlaylistTest : public ::testing::Test {
std::string master_playlist_path_; std::string master_playlist_path_;
}; };
TEST_F(MasterPlaylistTest, AddMediaPlaylist) {
MockMediaPlaylist mock_playlist(kVodPlaylist, "playlist1.m3u8", "somename",
"somegroupid");
master_playlist_.AddMediaPlaylist(&mock_playlist);
}
TEST_F(MasterPlaylistTest, WriteMasterPlaylistOneVideo) { TEST_F(MasterPlaylistTest, WriteMasterPlaylistOneVideo) {
const uint64_t kBitRate = 435889; const uint64_t kBitRate = 435889;
std::unique_ptr<MockMediaPlaylist> mock_playlist = std::unique_ptr<MockMediaPlaylist> mock_playlist =
CreateVideoPlaylist("media1.m3u8", "avc1", kBitRate); CreateVideoPlaylist("media1.m3u8", "avc1", kBitRate);
master_playlist_.AddMediaPlaylist(mock_playlist.get());
const char kBaseUrl[] = "http://myplaylistdomain.com/"; const char kBaseUrl[] = "http://myplaylistdomain.com/";
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_)); EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_,
{mock_playlist.get()}));
std::string actual; std::string actual;
ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual)); ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual));
@ -159,27 +153,26 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudio) {
// First video, sd.m3u8. // First video, sd.m3u8.
std::unique_ptr<MockMediaPlaylist> sd_video_playlist = std::unique_ptr<MockMediaPlaylist> sd_video_playlist =
CreateVideoPlaylist("sd.m3u8", "sdvideocodec", kVideo1BitRate); CreateVideoPlaylist("sd.m3u8", "sdvideocodec", kVideo1BitRate);
master_playlist_.AddMediaPlaylist(sd_video_playlist.get());
// Second video, hd.m3u8. // Second video, hd.m3u8.
std::unique_ptr<MockMediaPlaylist> hd_video_playlist = std::unique_ptr<MockMediaPlaylist> hd_video_playlist =
CreateVideoPlaylist("hd.m3u8", "hdvideocodec", kVideo2BitRate); CreateVideoPlaylist("hd.m3u8", "hdvideocodec", kVideo2BitRate);
master_playlist_.AddMediaPlaylist(hd_video_playlist.get());
// First audio, english.m3u8. // First audio, english.m3u8.
std::unique_ptr<MockMediaPlaylist> english_playlist = std::unique_ptr<MockMediaPlaylist> english_playlist =
CreateAudioPlaylist("eng.m3u8", "english", "audiogroup", "audiocodec", CreateAudioPlaylist("eng.m3u8", "english", "audiogroup", "audiocodec",
"en", kAudio1Channels, kAudio1BitRate); "en", kAudio1Channels, kAudio1BitRate);
master_playlist_.AddMediaPlaylist(english_playlist.get());
// Second audio, spanish.m3u8. // Second audio, spanish.m3u8.
std::unique_ptr<MockMediaPlaylist> spanish_playlist = std::unique_ptr<MockMediaPlaylist> spanish_playlist =
CreateAudioPlaylist("spa.m3u8", "espanol", "audiogroup", "audiocodec", CreateAudioPlaylist("spa.m3u8", "espanol", "audiogroup", "audiocodec",
"es", kAudio2Channels, kAudio2BitRate); "es", kAudio2Channels, kAudio2BitRate);
master_playlist_.AddMediaPlaylist(spanish_playlist.get());
const char kBaseUrl[] = "http://playlists.org/"; const char kBaseUrl[] = "http://playlists.org/";
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_)); EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(
kBaseUrl, test_output_dir_,
{sd_video_playlist.get(), hd_video_playlist.get(), english_playlist.get(),
spanish_playlist.get()}));
std::string actual; std::string actual;
ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual)); ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual));
@ -216,22 +209,21 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) {
// First video, sd.m3u8. // First video, sd.m3u8.
std::unique_ptr<MockMediaPlaylist> video_playlist = std::unique_ptr<MockMediaPlaylist> video_playlist =
CreateVideoPlaylist("video.m3u8", "videocodec", kVideoBitRate); CreateVideoPlaylist("video.m3u8", "videocodec", kVideoBitRate);
master_playlist_.AddMediaPlaylist(video_playlist.get());
// First audio, eng_lo.m3u8. // First audio, eng_lo.m3u8.
std::unique_ptr<MockMediaPlaylist> eng_lo_playlist = CreateAudioPlaylist( std::unique_ptr<MockMediaPlaylist> eng_lo_playlist = CreateAudioPlaylist(
"eng_lo.m3u8", "english_lo", "audio_lo", "audiocodec_lo", "en", "eng_lo.m3u8", "english_lo", "audio_lo", "audiocodec_lo", "en",
kAudio1Channels, kAudio1BitRate); kAudio1Channels, kAudio1BitRate);
master_playlist_.AddMediaPlaylist(eng_lo_playlist.get());
// Second audio, eng_hi.m3u8. // Second audio, eng_hi.m3u8.
std::unique_ptr<MockMediaPlaylist> eng_hi_playlist = CreateAudioPlaylist( std::unique_ptr<MockMediaPlaylist> eng_hi_playlist = CreateAudioPlaylist(
"eng_hi.m3u8", "english_hi", "audio_hi", "audiocodec_hi", "en", "eng_hi.m3u8", "english_hi", "audio_hi", "audiocodec_hi", "en",
kAudio2Channels, kAudio2BitRate); kAudio2Channels, kAudio2BitRate);
master_playlist_.AddMediaPlaylist(eng_hi_playlist.get());
const char kBaseUrl[] = "http://anydomain.com/"; const char kBaseUrl[] = "http://anydomain.com/";
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_)); EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(
kBaseUrl, test_output_dir_,
{video_playlist.get(), eng_lo_playlist.get(), eng_hi_playlist.get()}));
std::string actual; std::string actual;
ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual)); ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual));
@ -260,19 +252,18 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistSameAudioGroupSameLanguage) {
// First video, video.m3u8. // First video, video.m3u8.
std::unique_ptr<MockMediaPlaylist> video_playlist = std::unique_ptr<MockMediaPlaylist> video_playlist =
CreateVideoPlaylist("video.m3u8", "videocodec", 300000); CreateVideoPlaylist("video.m3u8", "videocodec", 300000);
master_playlist_.AddMediaPlaylist(video_playlist.get());
// First audio, eng_lo.m3u8. // First audio, eng_lo.m3u8.
std::unique_ptr<MockMediaPlaylist> eng_lo_playlist = CreateAudioPlaylist( std::unique_ptr<MockMediaPlaylist> eng_lo_playlist = CreateAudioPlaylist(
"eng_lo.m3u8", "english", "audio", "audiocodec", "en", 1, 50000); "eng_lo.m3u8", "english", "audio", "audiocodec", "en", 1, 50000);
master_playlist_.AddMediaPlaylist(eng_lo_playlist.get());
std::unique_ptr<MockMediaPlaylist> eng_hi_playlist = CreateAudioPlaylist( std::unique_ptr<MockMediaPlaylist> eng_hi_playlist = CreateAudioPlaylist(
"eng_hi.m3u8", "english", "audio", "audiocodec", "en", 8, 100000); "eng_hi.m3u8", "english", "audio", "audiocodec", "en", 8, 100000);
master_playlist_.AddMediaPlaylist(eng_hi_playlist.get());
const char kBaseUrl[] = "http://anydomain.com/"; const char kBaseUrl[] = "http://anydomain.com/";
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_)); EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(
kBaseUrl, test_output_dir_,
{video_playlist.get(), eng_lo_playlist.get(), eng_hi_playlist.get()}));
std::string actual; std::string actual;
ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual)); ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual));
@ -297,25 +288,23 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideosAndTexts) {
// Video, sd.m3u8. // Video, sd.m3u8.
std::unique_ptr<MockMediaPlaylist> video1 = std::unique_ptr<MockMediaPlaylist> video1 =
CreateVideoPlaylist("sd.m3u8", "sdvideocodec", 300000); CreateVideoPlaylist("sd.m3u8", "sdvideocodec", 300000);
master_playlist_.AddMediaPlaylist(video1.get());
// Video, hd.m3u8. // Video, hd.m3u8.
std::unique_ptr<MockMediaPlaylist> video2 = std::unique_ptr<MockMediaPlaylist> video2 =
CreateVideoPlaylist("hd.m3u8", "sdvideocodec", 600000); CreateVideoPlaylist("hd.m3u8", "sdvideocodec", 600000);
master_playlist_.AddMediaPlaylist(video2.get());
// Text, eng.m3u8. // Text, eng.m3u8.
std::unique_ptr<MockMediaPlaylist> text_eng = std::unique_ptr<MockMediaPlaylist> text_eng =
MakeText("eng.m3u8", "english", "textgroup", "en"); MakeText("eng.m3u8", "english", "textgroup", "en");
master_playlist_.AddMediaPlaylist(text_eng.get());
// Text, fr.m3u8. // Text, fr.m3u8.
std::unique_ptr<MockMediaPlaylist> text_fr = std::unique_ptr<MockMediaPlaylist> text_fr =
MakeText("fr.m3u8", "french", "textgroup", "fr"); MakeText("fr.m3u8", "french", "textgroup", "fr");
master_playlist_.AddMediaPlaylist(text_fr.get());
const char kBaseUrl[] = "http://playlists.org/"; const char kBaseUrl[] = "http://playlists.org/";
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_)); EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(
kBaseUrl, test_output_dir_,
{video1.get(), video2.get(), text_eng.get(), text_fr.get()}));
std::string actual; std::string actual;
ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual)); ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual));
@ -343,20 +332,19 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndTextGroups) {
// Video, sd.m3u8. // Video, sd.m3u8.
std::unique_ptr<MockMediaPlaylist> video = std::unique_ptr<MockMediaPlaylist> video =
CreateVideoPlaylist("sd.m3u8", "sdvideocodec", 300000); CreateVideoPlaylist("sd.m3u8", "sdvideocodec", 300000);
master_playlist_.AddMediaPlaylist(video.get());
// Text, eng.m3u8. // Text, eng.m3u8.
std::unique_ptr<MockMediaPlaylist> text_eng = std::unique_ptr<MockMediaPlaylist> text_eng =
MakeText("eng.m3u8", "english", "en-text-group", "en"); MakeText("eng.m3u8", "english", "en-text-group", "en");
master_playlist_.AddMediaPlaylist(text_eng.get());
// Text, fr.m3u8. // Text, fr.m3u8.
std::unique_ptr<MockMediaPlaylist> text_fr = std::unique_ptr<MockMediaPlaylist> text_fr =
MakeText("fr.m3u8", "french", "fr-text-group", "fr"); MakeText("fr.m3u8", "french", "fr-text-group", "fr");
master_playlist_.AddMediaPlaylist(text_fr.get());
const char kBaseUrl[] = "http://playlists.org/"; const char kBaseUrl[] = "http://playlists.org/";
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_)); EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(
kBaseUrl, test_output_dir_,
{video.get(), text_eng.get(), text_fr.get()}));
std::string actual; std::string actual;
ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual)); ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual));
@ -385,20 +373,18 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVideoAndAudioAndText) {
// Video, sd.m3u8. // Video, sd.m3u8.
std::unique_ptr<MockMediaPlaylist> video = std::unique_ptr<MockMediaPlaylist> video =
CreateVideoPlaylist("sd.m3u8", "sdvideocodec", 300000); CreateVideoPlaylist("sd.m3u8", "sdvideocodec", 300000);
master_playlist_.AddMediaPlaylist(video.get());
// Audio, english.m3u8. // Audio, english.m3u8.
std::unique_ptr<MockMediaPlaylist> audio = CreateAudioPlaylist( std::unique_ptr<MockMediaPlaylist> audio = CreateAudioPlaylist(
"eng.m3u8", "english", "audiogroup", "audiocodec", "en", 2, 50000); "eng.m3u8", "english", "audiogroup", "audiocodec", "en", 2, 50000);
master_playlist_.AddMediaPlaylist(audio.get());
// Text, english.m3u8. // Text, english.m3u8.
std::unique_ptr<MockMediaPlaylist> text = std::unique_ptr<MockMediaPlaylist> text =
MakeText("eng.m3u8", "english", "textgroup", "en"); MakeText("eng.m3u8", "english", "textgroup", "en");
master_playlist_.AddMediaPlaylist(text.get());
const char kBaseUrl[] = "http://playlists.org/"; const char kBaseUrl[] = "http://playlists.org/";
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_)); EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(
kBaseUrl, test_output_dir_, {video.get(), audio.get(), text.get()}));
std::string actual; std::string actual;
ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual)); ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual));
@ -442,12 +428,14 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistVidesAudiosTextsDifferentGroups) {
}; };
// Add all the media playlists to the master playlist. // Add all the media playlists to the master playlist.
std::list<MediaPlaylist*> media_playlist_list;
for (const auto& media_playlist : media_playlists) { for (const auto& media_playlist : media_playlists) {
master_playlist_.AddMediaPlaylist(media_playlist.get()); media_playlist_list.push_back(media_playlist.get());
} }
const char kBaseUrl[] = "http://playlists.org/"; const char kBaseUrl[] = "http://playlists.org/";
EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_)); EXPECT_TRUE(master_playlist_.WriteMasterPlaylist(kBaseUrl, test_output_dir_,
media_playlist_list));
std::string actual; std::string actual;
ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual)); ASSERT_TRUE(File::ReadFileToString(master_playlist_path_.c_str(), &actual));

View File

@ -312,7 +312,7 @@ bool SimpleHlsNotifier::NotifyNewStream(const MediaInfo& media_info,
*stream_id = sequence_number_.GetNext(); *stream_id = sequence_number_.GetNext();
base::AutoLock auto_lock(lock_); base::AutoLock auto_lock(lock_);
master_playlist_->AddMediaPlaylist(media_playlist.get()); media_playlists_.push_back(media_playlist.get());
stream_map_[*stream_id].reset( stream_map_[*stream_id].reset(
new StreamEntry{std::move(media_playlist), encryption_method}); new StreamEntry{std::move(media_playlist), encryption_method});
return true; return true;
@ -348,14 +348,14 @@ bool SimpleHlsNotifier::NotifyNewSegment(uint32_t stream_id,
// Update the playlists when there is new segments in live mode. // Update the playlists when there is new segments in live mode.
if (playlist_type() == HlsPlaylistType::kLive || if (playlist_type() == HlsPlaylistType::kLive ||
playlist_type() == HlsPlaylistType::kEvent) { playlist_type() == HlsPlaylistType::kEvent) {
if (!master_playlist_->WriteMasterPlaylist(prefix_, output_dir_)) { if (!master_playlist_->WriteMasterPlaylist(prefix_, output_dir_,
media_playlists_)) {
LOG(ERROR) << "Failed to write master playlist."; LOG(ERROR) << "Failed to write master playlist.";
return false; return false;
} }
// Update all playlists if target duration is updated. // Update all playlists if target duration is updated.
if (target_duration_updated) { if (target_duration_updated) {
for (auto& streams : stream_map_) { for (MediaPlaylist* playlist : media_playlists_) {
MediaPlaylist* playlist = streams.second->media_playlist.get();
playlist->SetTargetDuration(target_duration_); playlist->SetTargetDuration(target_duration_);
if (!WriteMediaPlaylist(output_dir_, playlist)) if (!WriteMediaPlaylist(output_dir_, playlist))
return false; return false;
@ -461,12 +461,12 @@ bool SimpleHlsNotifier::NotifyEncryptionUpdate(
bool SimpleHlsNotifier::Flush() { bool SimpleHlsNotifier::Flush() {
base::AutoLock auto_lock(lock_); base::AutoLock auto_lock(lock_);
if (!master_playlist_->WriteMasterPlaylist(prefix_, output_dir_)) { if (!master_playlist_->WriteMasterPlaylist(prefix_, output_dir_,
media_playlists_)) {
LOG(ERROR) << "Failed to write master playlist."; LOG(ERROR) << "Failed to write master playlist.";
return false; return false;
} }
for (auto& streams : stream_map_) { for (MediaPlaylist* playlist : media_playlists_) {
MediaPlaylist* playlist = streams.second->media_playlist.get();
playlist->SetTargetDuration(target_duration_); playlist->SetTargetDuration(target_duration_);
if (!WriteMediaPlaylist(output_dir_, playlist)) if (!WriteMediaPlaylist(output_dir_, playlist))
return false; return false;

View File

@ -7,6 +7,7 @@
#ifndef PACKAGER_HLS_BASE_SIMPLE_HLS_NOTIFIER_H_ #ifndef PACKAGER_HLS_BASE_SIMPLE_HLS_NOTIFIER_H_
#define PACKAGER_HLS_BASE_SIMPLE_HLS_NOTIFIER_H_ #define PACKAGER_HLS_BASE_SIMPLE_HLS_NOTIFIER_H_
#include <list>
#include <map> #include <map>
#include <memory> #include <memory>
#include <string> #include <string>
@ -89,6 +90,7 @@ class SimpleHlsNotifier : public HlsNotifier {
// Maps to unique_ptr because StreamEntry also holds unique_ptr // Maps to unique_ptr because StreamEntry also holds unique_ptr
std::map<uint32_t, std::unique_ptr<StreamEntry>> stream_map_; std::map<uint32_t, std::unique_ptr<StreamEntry>> stream_map_;
std::list<MediaPlaylist*> media_playlists_;
base::AtomicSequenceNumber sequence_number_; base::AtomicSequenceNumber sequence_number_;

View File

@ -21,13 +21,14 @@
namespace shaka { namespace shaka {
namespace hls { namespace hls {
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Eq; using ::testing::Eq;
using ::testing::InSequence; using ::testing::InSequence;
using ::testing::Mock; using ::testing::Mock;
using ::testing::Property; using ::testing::Property;
using ::testing::Return; using ::testing::Return;
using ::testing::StrEq; using ::testing::StrEq;
using ::testing::_;
namespace { namespace {
const char kMasterPlaylistName[] = "master.m3u8"; const char kMasterPlaylistName[] = "master.m3u8";
@ -43,9 +44,10 @@ class MockMasterPlaylist : public MasterPlaylist {
MockMasterPlaylist() MockMasterPlaylist()
: MasterPlaylist(kMasterPlaylistName, kDefaultLanguage) {} : MasterPlaylist(kMasterPlaylistName, kDefaultLanguage) {}
MOCK_METHOD1(AddMediaPlaylist, void(MediaPlaylist* media_playlist)); MOCK_METHOD3(WriteMasterPlaylist,
MOCK_METHOD2(WriteMasterPlaylist, bool(const std::string& prefix,
bool(const std::string& prefix, const std::string& output_dir)); const std::string& output_dir,
const std::list<MediaPlaylist*>& playlists));
}; };
class MockMediaPlaylistFactory : public MediaPlaylistFactory { class MockMediaPlaylistFactory : public MediaPlaylistFactory {
@ -128,9 +130,6 @@ class SimpleHlsNotifierTest : public ::testing::Test {
std::unique_ptr<MockMediaPlaylistFactory> factory( std::unique_ptr<MockMediaPlaylistFactory> factory(
new MockMediaPlaylistFactory()); new MockMediaPlaylistFactory());
EXPECT_CALL(
*mock_master_playlist,
AddMediaPlaylist(static_cast<MediaPlaylist*>(mock_media_playlist)));
EXPECT_CALL(*mock_media_playlist, SetMediaInfo(_)).WillOnce(Return(true)); EXPECT_CALL(*mock_media_playlist, SetMediaInfo(_)).WillOnce(Return(true));
EXPECT_CALL(*factory, CreateMock(_, _, _, _, _)) EXPECT_CALL(*factory, CreateMock(_, _, _, _, _))
.WillOnce(Return(mock_media_playlist)); .WillOnce(Return(mock_media_playlist));
@ -167,7 +166,6 @@ TEST_F(SimpleHlsNotifierTest, RebaseSegmentUrl) {
// Pointer released by SimpleHlsNotifier. // Pointer released by SimpleHlsNotifier.
MockMediaPlaylist* mock_media_playlist = MockMediaPlaylist* mock_media_playlist =
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
EXPECT_CALL(*mock_master_playlist, AddMediaPlaylist(mock_media_playlist));
EXPECT_CALL(*mock_media_playlist, EXPECT_CALL(*mock_media_playlist,
SetMediaInfo(Property(&MediaInfo::init_segment_name, StrEq("")))) SetMediaInfo(Property(&MediaInfo::init_segment_name, StrEq(""))))
@ -207,7 +205,6 @@ TEST_F(SimpleHlsNotifierTest, RebaseInitSegmentUrl) {
// Pointer released by SimpleHlsNotifier. // Pointer released by SimpleHlsNotifier.
MockMediaPlaylist* mock_media_playlist = MockMediaPlaylist* mock_media_playlist =
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
EXPECT_CALL(*mock_master_playlist, AddMediaPlaylist(mock_media_playlist));
// Verify that the common prefix is stripped in init segment. // Verify that the common prefix is stripped in init segment.
EXPECT_CALL( EXPECT_CALL(
@ -243,7 +240,6 @@ TEST_F(SimpleHlsNotifierTest, RebaseSegmentUrlRelativeToPlaylist) {
// Pointer released by SimpleHlsNotifier. // Pointer released by SimpleHlsNotifier.
MockMediaPlaylist* mock_media_playlist = MockMediaPlaylist* mock_media_playlist =
new MockMediaPlaylist(kVodPlaylist, "video/playlist.m3u8", "", ""); new MockMediaPlaylist(kVodPlaylist, "video/playlist.m3u8", "", "");
EXPECT_CALL(*mock_master_playlist, AddMediaPlaylist(mock_media_playlist));
// Verify that the init segment URL is relative to playlist path. // Verify that the init segment URL is relative to playlist path.
EXPECT_CALL(*mock_media_playlist, EXPECT_CALL(*mock_media_playlist,
@ -292,7 +288,6 @@ TEST_F(SimpleHlsNotifierTest, RebaseAbsoluteSegmentPrefixAndOutputDirMatch) {
// Pointer released by SimpleHlsNotifier. // Pointer released by SimpleHlsNotifier.
MockMediaPlaylist* mock_media_playlist = MockMediaPlaylist* mock_media_playlist =
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
EXPECT_CALL(*mock_master_playlist, AddMediaPlaylist(mock_media_playlist));
EXPECT_CALL(*mock_media_playlist, SetMediaInfo(_)).WillOnce(Return(true)); EXPECT_CALL(*mock_media_playlist, SetMediaInfo(_)).WillOnce(Return(true));
@ -334,7 +329,6 @@ TEST_F(SimpleHlsNotifierTest,
// Pointer released by SimpleHlsNotifier. // Pointer released by SimpleHlsNotifier.
MockMediaPlaylist* mock_media_playlist = MockMediaPlaylist* mock_media_playlist =
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
EXPECT_CALL(*mock_master_playlist, AddMediaPlaylist(mock_media_playlist));
EXPECT_CALL(*mock_media_playlist, SetMediaInfo(_)).WillOnce(Return(true)); EXPECT_CALL(*mock_media_playlist, SetMediaInfo(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_media_playlist, EXPECT_CALL(*mock_media_playlist,
@ -363,7 +357,7 @@ TEST_F(SimpleHlsNotifierTest, Flush) {
std::unique_ptr<MockMasterPlaylist> mock_master_playlist( std::unique_ptr<MockMasterPlaylist> mock_master_playlist(
new MockMasterPlaylist()); new MockMasterPlaylist());
EXPECT_CALL(*mock_master_playlist, EXPECT_CALL(*mock_master_playlist,
WriteMasterPlaylist(StrEq(kTestPrefix), StrEq(kAnyOutputDir))) WriteMasterPlaylist(StrEq(kTestPrefix), StrEq(kAnyOutputDir), _))
.WillOnce(Return(true)); .WillOnce(Return(true));
InjectMasterPlaylist(std::move(mock_master_playlist), &notifier); InjectMasterPlaylist(std::move(mock_master_playlist), &notifier);
EXPECT_TRUE(notifier.Init()); EXPECT_TRUE(notifier.Init());
@ -379,7 +373,6 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewStream) {
// Pointer released by SimpleHlsNotifier. // Pointer released by SimpleHlsNotifier.
MockMediaPlaylist* mock_media_playlist = MockMediaPlaylist* mock_media_playlist =
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
EXPECT_CALL(*mock_master_playlist, AddMediaPlaylist(mock_media_playlist));
EXPECT_CALL(*mock_media_playlist, SetMediaInfo(_)).WillOnce(Return(true)); EXPECT_CALL(*mock_media_playlist, SetMediaInfo(_)).WillOnce(Return(true));
EXPECT_CALL(*factory, CreateMock(kVodPlaylist, Eq(kTestTimeShiftBufferDepth), EXPECT_CALL(*factory, CreateMock(kVodPlaylist, Eq(kTestTimeShiftBufferDepth),
@ -409,9 +402,6 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewSegment) {
MockMediaPlaylist* mock_media_playlist = MockMediaPlaylist* mock_media_playlist =
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", ""); new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
EXPECT_CALL(
*mock_master_playlist,
AddMediaPlaylist(static_cast<MediaPlaylist*>(mock_media_playlist)));
EXPECT_CALL(*mock_media_playlist, SetMediaInfo(_)).WillOnce(Return(true)); EXPECT_CALL(*mock_media_playlist, SetMediaInfo(_)).WillOnce(Return(true));
EXPECT_CALL(*factory, CreateMock(_, _, _, _, _)) EXPECT_CALL(*factory, CreateMock(_, _, _, _, _))
.WillOnce(Return(mock_media_playlist)); .WillOnce(Return(mock_media_playlist));
@ -446,7 +436,8 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewSegment) {
Mock::VerifyAndClearExpectations(mock_media_playlist); Mock::VerifyAndClearExpectations(mock_media_playlist);
EXPECT_CALL(*mock_master_playlist_ptr, EXPECT_CALL(*mock_master_playlist_ptr,
WriteMasterPlaylist(StrEq(kTestPrefix), StrEq(kAnyOutputDir))) WriteMasterPlaylist(StrEq(kTestPrefix), StrEq(kAnyOutputDir),
ElementsAre(mock_media_playlist)))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(*mock_media_playlist, SetTargetDuration(kTargetDuration)) EXPECT_CALL(*mock_media_playlist, SetTargetDuration(kTargetDuration))
.Times(1); .Times(1);
@ -908,9 +899,6 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegment) {
MockMediaPlaylist* mock_media_playlist = MockMediaPlaylist* mock_media_playlist =
new MockMediaPlaylist(expected_playlist_type_, "playlist.m3u8", "", ""); new MockMediaPlaylist(expected_playlist_type_, "playlist.m3u8", "", "");
EXPECT_CALL(
*mock_master_playlist,
AddMediaPlaylist(static_cast<MediaPlaylist*>(mock_media_playlist)));
EXPECT_CALL(*mock_media_playlist, SetMediaInfo(_)).WillOnce(Return(true)); EXPECT_CALL(*mock_media_playlist, SetMediaInfo(_)).WillOnce(Return(true));
EXPECT_CALL(*factory, CreateMock(expected_playlist_type_, _, _, _, _)) EXPECT_CALL(*factory, CreateMock(expected_playlist_type_, _, _, _, _))
.WillOnce(Return(mock_media_playlist)); .WillOnce(Return(mock_media_playlist));
@ -929,7 +917,7 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegment) {
.WillOnce(Return(kLongestSegmentDuration)); .WillOnce(Return(kLongestSegmentDuration));
EXPECT_CALL(*mock_master_playlist, EXPECT_CALL(*mock_master_playlist,
WriteMasterPlaylist(StrEq(kTestPrefix), StrEq(kAnyOutputDir))) WriteMasterPlaylist(StrEq(kTestPrefix), StrEq(kAnyOutputDir), _))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(*mock_media_playlist, SetTargetDuration(kTargetDuration)) EXPECT_CALL(*mock_media_playlist, SetTargetDuration(kTargetDuration))
.Times(1); .Times(1);
@ -975,15 +963,9 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegmentsWithMultipleStreams) {
EXPECT_CALL(*factory, CreateMock(_, _, StrEq("playlist1.m3u8"), _, _)) EXPECT_CALL(*factory, CreateMock(_, _, StrEq("playlist1.m3u8"), _, _))
.WillOnce(Return(mock_media_playlist1)); .WillOnce(Return(mock_media_playlist1));
EXPECT_CALL(*mock_media_playlist1, SetMediaInfo(_)).WillOnce(Return(true)); EXPECT_CALL(*mock_media_playlist1, SetMediaInfo(_)).WillOnce(Return(true));
EXPECT_CALL(
*mock_master_playlist,
AddMediaPlaylist(static_cast<MediaPlaylist*>(mock_media_playlist1)));
EXPECT_CALL(*factory, CreateMock(_, _, StrEq("playlist2.m3u8"), _, _)) EXPECT_CALL(*factory, CreateMock(_, _, StrEq("playlist2.m3u8"), _, _))
.WillOnce(Return(mock_media_playlist2)); .WillOnce(Return(mock_media_playlist2));
EXPECT_CALL(*mock_media_playlist2, SetMediaInfo(_)).WillOnce(Return(true)); EXPECT_CALL(*mock_media_playlist2, SetMediaInfo(_)).WillOnce(Return(true));
EXPECT_CALL(
*mock_master_playlist,
AddMediaPlaylist(static_cast<MediaPlaylist*>(mock_media_playlist2)));
hls_params_.playlist_type = GetParam(); hls_params_.playlist_type = GetParam();
SimpleHlsNotifier notifier(hls_params_); SimpleHlsNotifier notifier(hls_params_);
@ -1006,7 +988,10 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegmentsWithMultipleStreams) {
EXPECT_CALL(*mock_media_playlist1, GetLongestSegmentDuration()) EXPECT_CALL(*mock_media_playlist1, GetLongestSegmentDuration())
.WillOnce(Return(kLongestSegmentDuration)); .WillOnce(Return(kLongestSegmentDuration));
EXPECT_CALL(*mock_master_playlist_ptr, WriteMasterPlaylist(_, _)) EXPECT_CALL(
*mock_master_playlist_ptr,
WriteMasterPlaylist(
_, _, ElementsAre(mock_media_playlist1, mock_media_playlist2)))
.WillOnce(Return(true)); .WillOnce(Return(true));
// SetTargetDuration and update all playlists as target duration is updated. // SetTargetDuration and update all playlists as target duration is updated.
EXPECT_CALL(*mock_media_playlist1, SetTargetDuration(kTargetDuration)) EXPECT_CALL(*mock_media_playlist1, SetTargetDuration(kTargetDuration))
@ -1031,7 +1016,7 @@ TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegmentsWithMultipleStreams) {
EXPECT_CALL(*mock_media_playlist2, AddSegment(_, _, _, _, _)).Times(1); EXPECT_CALL(*mock_media_playlist2, AddSegment(_, _, _, _, _)).Times(1);
EXPECT_CALL(*mock_media_playlist2, GetLongestSegmentDuration()) EXPECT_CALL(*mock_media_playlist2, GetLongestSegmentDuration())
.WillOnce(Return(kLongestSegmentDuration)); .WillOnce(Return(kLongestSegmentDuration));
EXPECT_CALL(*mock_master_playlist_ptr, WriteMasterPlaylist(_, _)) EXPECT_CALL(*mock_master_playlist_ptr, WriteMasterPlaylist(_, _, _))
.WillOnce(Return(true)); .WillOnce(Return(true));
// Not updating other playlists as target duration does not change. // Not updating other playlists as target duration does not change.
EXPECT_CALL(*mock_media_playlist2, EXPECT_CALL(*mock_media_playlist2,