HLS MasterPlaylist class
- Class to generate Master Playlist. - Takes multiple Media Playlists to generate all the playlists. Issue #85 Change-Id: I3e2be8763cc771d468245f64753e8a38b0746686
This commit is contained in:
parent
2ac57bf9b9
commit
f1e4f74a14
|
@ -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 <inttypes.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#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<uint32_t>(ceil(longest_segment_duration)));
|
||||
LOG_IF(WARNING, !set_target_duration)
|
||||
<< "Target duration was already set for " << file_path;
|
||||
}
|
||||
|
||||
scoped_ptr<media::File, media::FileCloser> 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<media::File, media::FileCloser> 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<std::string, std::list<const MediaPlaylist*>> audio_group_map;
|
||||
std::list<const MediaPlaylist*> 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<int>(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<const MediaPlaylist*>& 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<size_t>(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
|
|
@ -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 <list>
|
||||
#include <string>
|
||||
|
||||
#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 + <name of playlist>.
|
||||
/// 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 + <name of playlist>.
|
||||
/// 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<MediaPlaylist*> 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_
|
|
@ -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 <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#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
|
|
@ -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_;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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 <gmock/gmock.h>
|
||||
|
||||
#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_
|
|
@ -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',
|
||||
|
|
Loading…
Reference in New Issue