Support live and event HLS playlists
Closes #114 Change-Id: I6440383c3dc022fba5a9926839f79b9ed8a51f55
This commit is contained in:
parent
e2401f02ec
commit
b4aa34b803
|
@ -10,8 +10,12 @@ DEFINE_string(hls_master_playlist_output,
|
||||||
"",
|
"",
|
||||||
"Output path for the master playlist for HLS. This flag must be"
|
"Output path for the master playlist for HLS. This flag must be"
|
||||||
"used to output HLS.");
|
"used to output HLS.");
|
||||||
|
|
||||||
DEFINE_string(hls_base_url,
|
DEFINE_string(hls_base_url,
|
||||||
"",
|
"",
|
||||||
"The base URL for the Media Playlists and media files listed in "
|
"The base URL for the Media Playlists and media files listed in "
|
||||||
"the playlists. This is the prefix for the files.");
|
"the playlists. This is the prefix for the files.");
|
||||||
|
DEFINE_string(hls_playlist_type,
|
||||||
|
"VOD",
|
||||||
|
"VOD, EVENT, or LIVE. This defines the EXT-X-PLAYLIST-TYPE in "
|
||||||
|
"the HLS specification. For hls_playlist_type of LIVE, "
|
||||||
|
"EXT-X-PLAYLIST-TYPE tag is omitted.");
|
||||||
|
|
|
@ -11,5 +11,6 @@
|
||||||
|
|
||||||
DECLARE_string(hls_master_playlist_output);
|
DECLARE_string(hls_master_playlist_output);
|
||||||
DECLARE_string(hls_base_url);
|
DECLARE_string(hls_base_url);
|
||||||
|
DECLARE_string(hls_playlist_type);
|
||||||
|
|
||||||
#endif // PACKAGER_APP_HLS_FLAGS_H_
|
#endif // PACKAGER_APP_HLS_FLAGS_H_
|
||||||
|
|
|
@ -259,8 +259,19 @@ base::Optional<PackagingParams> GetPackagingParams() {
|
||||||
mpd_params.default_language = FLAGS_default_language;
|
mpd_params.default_language = FLAGS_default_language;
|
||||||
|
|
||||||
HlsParams& hls_params = packaging_params.hls_params;
|
HlsParams& hls_params = packaging_params.hls_params;
|
||||||
|
if (FLAGS_hls_playlist_type == "VOD") {
|
||||||
|
hls_params.playlist_type = HlsPlaylistType::kVod;
|
||||||
|
} else if (FLAGS_hls_playlist_type == "LIVE") {
|
||||||
|
hls_params.playlist_type = HlsPlaylistType::kLive;
|
||||||
|
} else if (FLAGS_hls_playlist_type == "EVENT") {
|
||||||
|
hls_params.playlist_type = HlsPlaylistType::kEvent;
|
||||||
|
} else {
|
||||||
|
LOG(ERROR) << "Unrecognized playlist type " << FLAGS_hls_playlist_type;
|
||||||
|
return base::nullopt;
|
||||||
|
}
|
||||||
hls_params.master_playlist_output = FLAGS_hls_master_playlist_output;
|
hls_params.master_playlist_output = FLAGS_hls_master_playlist_output;
|
||||||
hls_params.base_url = FLAGS_hls_base_url;
|
hls_params.base_url = FLAGS_hls_base_url;
|
||||||
|
hls_params.time_shift_buffer_depth = FLAGS_time_shift_buffer_depth;
|
||||||
|
|
||||||
TestParams& test_params = packaging_params.test_params;
|
TestParams& test_params = packaging_params.test_params;
|
||||||
test_params.dump_stream_info = FLAGS_dump_stream_info;
|
test_params.dump_stream_info = FLAGS_dump_stream_info;
|
||||||
|
|
|
@ -132,6 +132,8 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
dash_if_iop=True,
|
dash_if_iop=True,
|
||||||
output_media_info=False,
|
output_media_info=False,
|
||||||
output_hls=False,
|
output_hls=False,
|
||||||
|
hls_playlist_type=None,
|
||||||
|
time_shift_buffer_depth=0.0,
|
||||||
generate_static_mpd=False,
|
generate_static_mpd=False,
|
||||||
use_fake_clock=True):
|
use_fake_clock=True):
|
||||||
flags = []
|
flags = []
|
||||||
|
@ -177,6 +179,12 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
flags.append('--output_media_info')
|
flags.append('--output_media_info')
|
||||||
elif output_hls:
|
elif output_hls:
|
||||||
flags += ['--hls_master_playlist_output', self.hls_master_playlist_output]
|
flags += ['--hls_master_playlist_output', self.hls_master_playlist_output]
|
||||||
|
if hls_playlist_type:
|
||||||
|
flags += ['--hls_playlist_type', hls_playlist_type]
|
||||||
|
if time_shift_buffer_depth != 0.0:
|
||||||
|
flags += [
|
||||||
|
'--time_shift_buffer_depth={0}'.format(time_shift_buffer_depth)
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
flags += ['--mpd_output', self.mpd_output]
|
flags += ['--mpd_output', self.mpd_output]
|
||||||
|
|
||||||
|
@ -427,6 +435,58 @@ class PackagerFunctionalTest(PackagerAppTest):
|
||||||
self._DiffGold(
|
self._DiffGold(
|
||||||
os.path.join(self.tmp_dir, 'video.m3u8'), 'bear-640x360-v-golden.m3u8')
|
os.path.join(self.tmp_dir, 'video.m3u8'), 'bear-640x360-v-golden.m3u8')
|
||||||
|
|
||||||
|
def testPackageAvcTsLivePlaylist(self):
|
||||||
|
self.assertPackageSuccess(
|
||||||
|
self._GetStreams(
|
||||||
|
['audio', 'video'],
|
||||||
|
output_format='ts',
|
||||||
|
live=True,
|
||||||
|
test_files=['bear-640x360.ts']),
|
||||||
|
self._GetFlags(
|
||||||
|
output_hls=True,
|
||||||
|
hls_playlist_type='LIVE',
|
||||||
|
time_shift_buffer_depth=0.5))
|
||||||
|
self._DiffLiveGold(self.output[0],
|
||||||
|
'bear-640x360-a-golden',
|
||||||
|
output_format='ts')
|
||||||
|
self._DiffLiveGold(self.output[1],
|
||||||
|
'bear-640x360-v-golden',
|
||||||
|
output_format='ts')
|
||||||
|
self._DiffGold(self.hls_master_playlist_output,
|
||||||
|
'bear-640x360-av-master-golden.m3u8')
|
||||||
|
self._DiffGold(
|
||||||
|
os.path.join(self.tmp_dir, 'audio.m3u8'),
|
||||||
|
'bear-640x360-a-live-golden.m3u8')
|
||||||
|
self._DiffGold(
|
||||||
|
os.path.join(self.tmp_dir, 'video.m3u8'),
|
||||||
|
'bear-640x360-v-live-golden.m3u8')
|
||||||
|
|
||||||
|
def testPackageAvcTsEventPlaylist(self):
|
||||||
|
self.assertPackageSuccess(
|
||||||
|
self._GetStreams(
|
||||||
|
['audio', 'video'],
|
||||||
|
output_format='ts',
|
||||||
|
live=True,
|
||||||
|
test_files=['bear-640x360.ts']),
|
||||||
|
self._GetFlags(
|
||||||
|
output_hls=True,
|
||||||
|
hls_playlist_type='EVENT',
|
||||||
|
time_shift_buffer_depth=0.5))
|
||||||
|
self._DiffLiveGold(self.output[0],
|
||||||
|
'bear-640x360-a-golden',
|
||||||
|
output_format='ts')
|
||||||
|
self._DiffLiveGold(self.output[1],
|
||||||
|
'bear-640x360-v-golden',
|
||||||
|
output_format='ts')
|
||||||
|
self._DiffGold(self.hls_master_playlist_output,
|
||||||
|
'bear-640x360-av-master-golden.m3u8')
|
||||||
|
self._DiffGold(
|
||||||
|
os.path.join(self.tmp_dir, 'audio.m3u8'),
|
||||||
|
'bear-640x360-a-event-golden.m3u8')
|
||||||
|
self._DiffGold(
|
||||||
|
os.path.join(self.tmp_dir, 'video.m3u8'),
|
||||||
|
'bear-640x360-v-event-golden.m3u8')
|
||||||
|
|
||||||
def testPackageVp8Webm(self):
|
def testPackageVp8Webm(self):
|
||||||
self.assertPackageSuccess(
|
self.assertPackageSuccess(
|
||||||
self._GetStreams(['video'],
|
self._GetStreams(['video'],
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-VERSION:6
|
||||||
|
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
#EXT-X-TARGETDURATION:2
|
||||||
|
#EXT-X-PLAYLIST-TYPE:EVENT
|
||||||
|
#EXTINF:0.975,
|
||||||
|
output_audio-1.ts
|
||||||
|
#EXTINF:0.998,
|
||||||
|
output_audio-2.ts
|
||||||
|
#EXTINF:0.789,
|
||||||
|
output_audio-3.ts
|
|
@ -0,0 +1,9 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-VERSION:6
|
||||||
|
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
#EXT-X-TARGETDURATION:2
|
||||||
|
#EXT-X-MEDIA-SEQUENCE:1
|
||||||
|
#EXTINF:0.998,
|
||||||
|
output_audio-2.ts
|
||||||
|
#EXTINF:0.789,
|
||||||
|
output_audio-3.ts
|
|
@ -0,0 +1,11 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-VERSION:6
|
||||||
|
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
#EXT-X-TARGETDURATION:2
|
||||||
|
#EXT-X-PLAYLIST-TYPE:EVENT
|
||||||
|
#EXTINF:1.001,
|
||||||
|
output_video-1.ts
|
||||||
|
#EXTINF:1.001,
|
||||||
|
output_video-2.ts
|
||||||
|
#EXTINF:0.734,
|
||||||
|
output_video-3.ts
|
|
@ -0,0 +1,9 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-VERSION:6
|
||||||
|
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
#EXT-X-TARGETDURATION:2
|
||||||
|
#EXT-X-MEDIA-SEQUENCE:1
|
||||||
|
#EXTINF:1.001,
|
||||||
|
output_video-2.ts
|
||||||
|
#EXTINF:0.734,
|
||||||
|
output_video-3.ts
|
|
@ -15,14 +15,16 @@
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace hls {
|
namespace hls {
|
||||||
|
|
||||||
|
// TODO(kqyang): Combine with MediaPlaylistType in media_playlist.h.
|
||||||
|
enum class HlsProfile {
|
||||||
|
kOnDemandProfile,
|
||||||
|
kEventProfile,
|
||||||
|
kLiveProfile,
|
||||||
|
};
|
||||||
|
|
||||||
// TODO(rkuroiwa): Consider merging this with MpdNotifier.
|
// TODO(rkuroiwa): Consider merging this with MpdNotifier.
|
||||||
class HlsNotifier {
|
class HlsNotifier {
|
||||||
public:
|
public:
|
||||||
enum class HlsProfile {
|
|
||||||
kOnDemandProfile,
|
|
||||||
kLiveProfile,
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit HlsNotifier(HlsProfile profile) : profile_(profile) {}
|
explicit HlsNotifier(HlsProfile profile) : profile_(profile) {}
|
||||||
virtual ~HlsNotifier() {}
|
virtual ~HlsNotifier() {}
|
||||||
|
|
||||||
|
@ -76,7 +78,7 @@ class HlsNotifier {
|
||||||
/// @return true on success, false otherwise.
|
/// @return true on success, false otherwise.
|
||||||
virtual bool Flush() = 0;
|
virtual bool Flush() = 0;
|
||||||
|
|
||||||
protected:
|
/// @return the profile.
|
||||||
HlsProfile profile() const { return profile_; }
|
HlsProfile profile() const { return profile_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -8,9 +8,6 @@
|
||||||
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include <set>
|
|
||||||
|
|
||||||
#include "packager/base/files/file_path.h"
|
#include "packager/base/files/file_path.h"
|
||||||
#include "packager/base/strings/string_number_conversions.h"
|
#include "packager/base/strings/string_number_conversions.h"
|
||||||
#include "packager/base/strings/stringprintf.h"
|
#include "packager/base/strings/stringprintf.h"
|
||||||
|
@ -73,65 +70,8 @@ void MasterPlaylist::AddMediaPlaylist(MediaPlaylist* media_playlist) {
|
||||||
all_playlists_.push_back(media_playlist);
|
all_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 : all_playlists_) {
|
|
||||||
const double playlist_longest_segment =
|
|
||||||
playlist->GetLongestSegmentDuration();
|
|
||||||
if (longest_segment_duration < playlist_longest_segment)
|
|
||||||
longest_segment_duration = playlist_longest_segment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
base::FilePath output_path = base::FilePath::FromUTF8Unsafe(output_dir);
|
|
||||||
for (MediaPlaylist* playlist : all_playlists_) {
|
|
||||||
std::string file_path =
|
|
||||||
output_path
|
|
||||||
.Append(base::FilePath::FromUTF8Unsafe(playlist->file_name()))
|
|
||||||
.AsUTF8Unsafe();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_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,
|
bool MasterPlaylist::WriteMasterPlaylist(const std::string& base_url,
|
||||||
const std::string& output_dir) {
|
const std::string& output_dir) {
|
||||||
std::string file_path =
|
|
||||||
base::FilePath::FromUTF8Unsafe(output_dir)
|
|
||||||
.Append(base::FilePath::FromUTF8Unsafe(file_name_))
|
|
||||||
.AsUTF8Unsafe();
|
|
||||||
std::unique_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): Handle audio only.
|
// TODO(rkuroiwa): Handle audio only.
|
||||||
std::string audio_output;
|
std::string audio_output;
|
||||||
std::string video_output;
|
std::string video_output;
|
||||||
|
@ -213,6 +153,22 @@ bool MasterPlaylist::WriteMasterPlaylist(const std::string& base_url,
|
||||||
|
|
||||||
std::string content =
|
std::string content =
|
||||||
"#EXTM3U\n" + version_line + audio_output + video_output;
|
"#EXTM3U\n" + version_line + audio_output + video_output;
|
||||||
|
|
||||||
|
// Skip if the playlist is already written.
|
||||||
|
if (content == written_playlist_)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
std::string file_path =
|
||||||
|
base::FilePath::FromUTF8Unsafe(output_dir)
|
||||||
|
.Append(base::FilePath::FromUTF8Unsafe(file_name_))
|
||||||
|
.AsUTF8Unsafe();
|
||||||
|
std::unique_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;
|
||||||
|
}
|
||||||
|
|
||||||
int64_t bytes_written = file->Write(content.data(), content.size());
|
int64_t bytes_written = file->Write(content.data(), content.size());
|
||||||
if (bytes_written < 0) {
|
if (bytes_written < 0) {
|
||||||
LOG(ERROR) << "Error while writing master playlist " << file_path;
|
LOG(ERROR) << "Error while writing master playlist " << file_path;
|
||||||
|
@ -223,6 +179,7 @@ bool MasterPlaylist::WriteMasterPlaylist(const std::string& base_url,
|
||||||
<< content.size() << " " << file_path;
|
<< content.size() << " " << file_path;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
written_playlist_ = content;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,18 +31,6 @@ class MasterPlaylist {
|
||||||
/// @return true on success, false otherwise.
|
/// @return true on success, false otherwise.
|
||||||
virtual void AddMediaPlaylist(MediaPlaylist* media_playlist);
|
virtual 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.
|
|
||||||
virtual bool WriteAllPlaylists(const std::string& base_url,
|
|
||||||
const std::string& output_dir);
|
|
||||||
|
|
||||||
/// 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
|
||||||
|
@ -50,19 +38,19 @@ class MasterPlaylist {
|
||||||
/// @param output_dir is where the playlist files are written. This is not
|
/// @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
|
/// necessarily the same as base_url. It must be in a form that File
|
||||||
/// interface can open.
|
/// interface can open.
|
||||||
/// @return true on success, false otherwise.
|
/// @return true if the playlist is updated successfully or there is no
|
||||||
|
/// 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);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::string written_playlist_;
|
||||||
const std::string file_name_;
|
const std::string file_name_;
|
||||||
std::list<MediaPlaylist*> all_playlists_;
|
std::list<MediaPlaylist*> all_playlists_;
|
||||||
std::list<const MediaPlaylist*> video_playlists_;
|
std::list<const MediaPlaylist*> video_playlists_;
|
||||||
// The key is the audio group name.
|
// The key is the audio group name.
|
||||||
std::map<std::string, std::list<const MediaPlaylist*>> audio_playlist_groups_;
|
std::map<std::string, std::list<const MediaPlaylist*>> audio_playlist_groups_;
|
||||||
|
|
||||||
bool has_set_playlist_target_duration_ = false;
|
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(MasterPlaylist);
|
DISALLOW_COPY_AND_ASSIGN(MasterPlaylist);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ using ::testing::NotNull;
|
||||||
using ::testing::Return;
|
using ::testing::Return;
|
||||||
using ::testing::ReturnRef;
|
using ::testing::ReturnRef;
|
||||||
using ::testing::SetArgPointee;
|
using ::testing::SetArgPointee;
|
||||||
|
using ::testing::StrEq;
|
||||||
using ::testing::_;
|
using ::testing::_;
|
||||||
using base::FilePath;
|
using base::FilePath;
|
||||||
|
|
||||||
|
@ -58,8 +59,7 @@ class MasterPlaylistTest : public ::testing::Test {
|
||||||
ASSERT_TRUE(temp_dir_.IsValid());
|
ASSERT_TRUE(temp_dir_.IsValid());
|
||||||
*temp_dir_path = temp_dir_.path();
|
*temp_dir_path = temp_dir_.path();
|
||||||
// TODO(rkuroiwa): Use memory file sys once prefix is exposed.
|
// TODO(rkuroiwa): Use memory file sys once prefix is exposed.
|
||||||
*output_dir = media::kLocalFilePrefix + temp_dir_.path().AsUTF8Unsafe()
|
*output_dir = temp_dir_.path().AsUTF8Unsafe() + "/";
|
||||||
+ "/";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
base::ScopedTempDir temp_dir_;
|
base::ScopedTempDir temp_dir_;
|
||||||
|
@ -276,45 +276,5 @@ TEST_F(MasterPlaylistTest, WriteMasterPlaylistMultipleAudioGroups) {
|
||||||
ASSERT_EQ(expected, actual);
|
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(kVodPlaylist, "media1.m3u8", "somename",
|
|
||||||
"somegroupid");
|
|
||||||
mock_playlist.SetStreamTypeForTesting(
|
|
||||||
MediaPlaylist::MediaPlaylistStreamType::kPlayListVideo);
|
|
||||||
mock_playlist.SetCodecForTesting(codec);
|
|
||||||
ON_CALL(mock_playlist, Bitrate()).WillByDefault(Return(435889));
|
|
||||||
ON_CALL(mock_playlist, GetResolution(NotNull(), NotNull())).WillByDefault(
|
|
||||||
DoAll(SetArgPointee<0>(kWidth),
|
|
||||||
SetArgPointee<1>(kHeight),
|
|
||||||
Return(true)));
|
|
||||||
|
|
||||||
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(FilePath::FromUTF8Unsafe("media1.m3u8"))
|
|
||||||
.AsUTF8Unsafe())))
|
|
||||||
.WillOnce(Return(true));
|
|
||||||
|
|
||||||
const char kBaseUrl[] = "http://domain.com/";
|
|
||||||
EXPECT_TRUE(master_playlist_.WriteAllPlaylists(kBaseUrl, test_output_dir_));
|
|
||||||
FilePath master_playlist_path = test_output_dir_path_.Append(
|
|
||||||
FilePath::FromUTF8Unsafe(kDefaultMasterPlaylistName));
|
|
||||||
ASSERT_TRUE(base::PathExists(master_playlist_path))
|
|
||||||
<< "Cannot find master playlist at " << master_playlist_path.value();
|
|
||||||
}
|
|
||||||
} // namespace hls
|
} // namespace hls
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include "packager/base/strings/stringprintf.h"
|
#include "packager/base/strings/stringprintf.h"
|
||||||
#include "packager/media/base/language_utils.h"
|
#include "packager/media/base/language_utils.h"
|
||||||
#include "packager/media/file/file.h"
|
#include "packager/media/file/file.h"
|
||||||
|
#include "packager/media/file/file_closer.h"
|
||||||
#include "packager/version/version.h"
|
#include "packager/version/version.h"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
|
@ -32,10 +33,11 @@ uint32_t GetTimeScale(const MediaInfo& media_info) {
|
||||||
return 0u;
|
return 0u;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string CreatePlaylistHeader(
|
std::string CreatePlaylistHeader(const std::string& init_segment_name,
|
||||||
const std::string& init_segment_name,
|
uint32_t target_duration,
|
||||||
uint32_t target_duration,
|
MediaPlaylist::MediaPlaylistType type,
|
||||||
MediaPlaylist::MediaPlaylistType type) {
|
int sequence_number,
|
||||||
|
int discontinuity_sequence_number) {
|
||||||
const std::string version = GetPackagerVersion();
|
const std::string version = GetPackagerVersion();
|
||||||
std::string version_line;
|
std::string version_line;
|
||||||
if (!version.empty()) {
|
if (!version.empty()) {
|
||||||
|
@ -52,8 +54,25 @@ std::string CreatePlaylistHeader(
|
||||||
"#EXT-X-TARGETDURATION:%d\n",
|
"#EXT-X-TARGETDURATION:%d\n",
|
||||||
version_line.c_str(), target_duration);
|
version_line.c_str(), target_duration);
|
||||||
|
|
||||||
if (type == MediaPlaylist::MediaPlaylistType::kVod) {
|
switch (type) {
|
||||||
header += "#EXT-X-PLAYLIST-TYPE:VOD\n";
|
case MediaPlaylist::MediaPlaylistType::kVod:
|
||||||
|
header += "#EXT-X-PLAYLIST-TYPE:VOD\n";
|
||||||
|
break;
|
||||||
|
case MediaPlaylist::MediaPlaylistType::kEvent:
|
||||||
|
header += "#EXT-X-PLAYLIST-TYPE:EVENT\n";
|
||||||
|
break;
|
||||||
|
case MediaPlaylist::MediaPlaylistType::kLive:
|
||||||
|
if (sequence_number > 0) {
|
||||||
|
base::StringAppendF(&header, "#EXT-X-MEDIA-SEQUENCE:%d\n",
|
||||||
|
sequence_number);
|
||||||
|
}
|
||||||
|
if (discontinuity_sequence_number > 0) {
|
||||||
|
base::StringAppendF(&header, "#EXT-X-DISCONTINUITY-SEQUENCE:%d\n",
|
||||||
|
discontinuity_sequence_number);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
NOTREACHED() << "Unexpected MediaPlaylistType " << static_cast<int>(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put EXT-X-MAP at the end since the rest of the playlist is about the
|
// Put EXT-X-MAP at the end since the rest of the playlist is about the
|
||||||
|
@ -67,22 +86,29 @@ std::string CreatePlaylistHeader(
|
||||||
|
|
||||||
class SegmentInfoEntry : public HlsEntry {
|
class SegmentInfoEntry : public HlsEntry {
|
||||||
public:
|
public:
|
||||||
SegmentInfoEntry(const std::string& file_name, double duration);
|
SegmentInfoEntry(const std::string& file_name,
|
||||||
|
double start_time,
|
||||||
|
double duration);
|
||||||
~SegmentInfoEntry() override;
|
~SegmentInfoEntry() override;
|
||||||
|
|
||||||
std::string ToString() override;
|
std::string ToString() override;
|
||||||
|
double start_time() const { return start_time_; }
|
||||||
|
double duration() const { return duration_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const std::string file_name_;
|
const std::string file_name_;
|
||||||
|
const double start_time_;
|
||||||
const double duration_;
|
const double duration_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(SegmentInfoEntry);
|
DISALLOW_COPY_AND_ASSIGN(SegmentInfoEntry);
|
||||||
};
|
};
|
||||||
|
|
||||||
SegmentInfoEntry::SegmentInfoEntry(const std::string& file_name,
|
SegmentInfoEntry::SegmentInfoEntry(const std::string& file_name,
|
||||||
|
double start_time,
|
||||||
double duration)
|
double duration)
|
||||||
: HlsEntry(HlsEntry::EntryType::kExtInf),
|
: HlsEntry(HlsEntry::EntryType::kExtInf),
|
||||||
file_name_(file_name),
|
file_name_(file_name),
|
||||||
|
start_time_(start_time),
|
||||||
duration_(duration) {}
|
duration_(duration) {}
|
||||||
SegmentInfoEntry::~SegmentInfoEntry() {}
|
SegmentInfoEntry::~SegmentInfoEntry() {}
|
||||||
|
|
||||||
|
@ -160,19 +186,34 @@ std::string EncryptionInfoEntry::ToString() {
|
||||||
return ext_key + ",KEYFORMAT=\"" + key_format_ + "\"\n";
|
return ext_key + ",KEYFORMAT=\"" + key_format_ + "\"\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double LatestSegmentStartTime(
|
||||||
|
const std::list<std::unique_ptr<HlsEntry>>& entries) {
|
||||||
|
DCHECK(!entries.empty());
|
||||||
|
for (auto iter = entries.rbegin(); iter != entries.rend(); ++iter) {
|
||||||
|
if (iter->get()->type() == HlsEntry::EntryType::kExtInf) {
|
||||||
|
const SegmentInfoEntry* segment_info =
|
||||||
|
reinterpret_cast<SegmentInfoEntry*>(iter->get());
|
||||||
|
return segment_info->start_time();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
HlsEntry::HlsEntry(HlsEntry::EntryType type) : type_(type) {}
|
HlsEntry::HlsEntry(HlsEntry::EntryType type) : type_(type) {}
|
||||||
HlsEntry::~HlsEntry() {}
|
HlsEntry::~HlsEntry() {}
|
||||||
|
|
||||||
MediaPlaylist::MediaPlaylist(MediaPlaylistType type,
|
MediaPlaylist::MediaPlaylist(MediaPlaylistType type,
|
||||||
|
double time_shift_buffer_depth,
|
||||||
const std::string& file_name,
|
const std::string& file_name,
|
||||||
const std::string& name,
|
const std::string& name,
|
||||||
const std::string& group_id)
|
const std::string& group_id)
|
||||||
: file_name_(file_name), name_(name), group_id_(group_id), type_(type) {
|
: type_(type),
|
||||||
LOG_IF(WARNING, type != MediaPlaylistType::kVod)
|
time_shift_buffer_depth_(time_shift_buffer_depth),
|
||||||
<< "Non VOD Media Playlist is not supported.";
|
file_name_(file_name),
|
||||||
}
|
name_(name),
|
||||||
|
group_id_(group_id) {}
|
||||||
|
|
||||||
MediaPlaylist::~MediaPlaylist() {}
|
MediaPlaylist::~MediaPlaylist() {}
|
||||||
|
|
||||||
|
@ -209,16 +250,19 @@ bool MediaPlaylist::SetMediaInfo(const MediaInfo& media_info) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaPlaylist::AddSegment(const std::string& file_name,
|
void MediaPlaylist::AddSegment(const std::string& file_name,
|
||||||
|
uint64_t start_time,
|
||||||
uint64_t duration,
|
uint64_t duration,
|
||||||
uint64_t size) {
|
uint64_t size) {
|
||||||
if (time_scale_ == 0) {
|
if (time_scale_ == 0) {
|
||||||
LOG(WARNING) << "Timescale is not set and the duration for " << duration
|
LOG(WARNING) << "Timescale is not set and the duration for " << duration
|
||||||
<< " cannot be calculated. The output will be wrong.";
|
<< " cannot be calculated. The output will be wrong.";
|
||||||
|
|
||||||
entries_.emplace_back(new SegmentInfoEntry(file_name, 0.0));
|
entries_.emplace_back(new SegmentInfoEntry(file_name, 0.0, 0.0));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const double start_time_seconds =
|
||||||
|
static_cast<double>(start_time) / time_scale_;
|
||||||
const double segment_duration_seconds =
|
const double segment_duration_seconds =
|
||||||
static_cast<double>(duration) / time_scale_;
|
static_cast<double>(duration) / time_scale_;
|
||||||
if (segment_duration_seconds > longest_segment_duration_)
|
if (segment_duration_seconds > longest_segment_duration_)
|
||||||
|
@ -227,8 +271,9 @@ void MediaPlaylist::AddSegment(const std::string& file_name,
|
||||||
const int kBitsInByte = 8;
|
const int kBitsInByte = 8;
|
||||||
const uint64_t bitrate = kBitsInByte * size / segment_duration_seconds;
|
const uint64_t bitrate = kBitsInByte * size / segment_duration_seconds;
|
||||||
max_bitrate_ = std::max(max_bitrate_, bitrate);
|
max_bitrate_ = std::max(max_bitrate_, bitrate);
|
||||||
entries_.emplace_back(
|
entries_.emplace_back(new SegmentInfoEntry(file_name, start_time_seconds,
|
||||||
new SegmentInfoEntry(file_name, segment_duration_seconds));
|
segment_duration_seconds));
|
||||||
|
SlideWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(rkuroiwa): This works for single key format but won't work for multiple
|
// TODO(rkuroiwa): This works for single key format but won't work for multiple
|
||||||
|
@ -298,13 +343,14 @@ void MediaPlaylist::AddEncryptionInfo(MediaPlaylist::EncryptionMethod method,
|
||||||
method, url, key_id, iv, key_format, key_format_versions));
|
method, url, key_id, iv, key_format, key_format_versions));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MediaPlaylist::WriteToFile(media::File* file) {
|
bool MediaPlaylist::WriteToFile(const std::string& file_path) {
|
||||||
if (!target_duration_set_) {
|
if (!target_duration_set_) {
|
||||||
SetTargetDuration(ceil(GetLongestSegmentDuration()));
|
SetTargetDuration(ceil(GetLongestSegmentDuration()));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string header = CreatePlaylistHeader(media_info_.init_segment_name(),
|
std::string header = CreatePlaylistHeader(
|
||||||
target_duration_, type_);
|
media_info_.init_segment_name(), target_duration_, type_,
|
||||||
|
sequence_number_, discontinuity_sequence_number_);
|
||||||
|
|
||||||
std::string body;
|
std::string body;
|
||||||
if (!entries_.empty()) {
|
if (!entries_.empty()) {
|
||||||
|
@ -327,6 +373,12 @@ bool MediaPlaylist::WriteToFile(media::File* file) {
|
||||||
content += "#EXT-X-ENDLIST\n";
|
content += "#EXT-X-ENDLIST\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_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;
|
||||||
|
}
|
||||||
int64_t bytes_written = file->Write(content.data(), content.size());
|
int64_t bytes_written = file->Write(content.data(), content.size());
|
||||||
if (bytes_written < 0) {
|
if (bytes_written < 0) {
|
||||||
LOG(ERROR) << "Error while writing playlist to file.";
|
LOG(ERROR) << "Error while writing playlist to file.";
|
||||||
|
@ -355,15 +407,15 @@ double MediaPlaylist::GetLongestSegmentDuration() const {
|
||||||
return longest_segment_duration_;
|
return longest_segment_duration_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MediaPlaylist::SetTargetDuration(uint32_t target_duration) {
|
void MediaPlaylist::SetTargetDuration(uint32_t target_duration) {
|
||||||
if (target_duration_set_) {
|
if (target_duration_set_) {
|
||||||
LOG(WARNING) << "Cannot set target duration to " << target_duration
|
if (target_duration_ == target_duration)
|
||||||
<< ". Target duration already set to " << target_duration_;
|
return;
|
||||||
return false;
|
VLOG(1) << "Updating target duration from " << target_duration << " to "
|
||||||
|
<< target_duration_;
|
||||||
}
|
}
|
||||||
target_duration_ = target_duration;
|
target_duration_ = target_duration;
|
||||||
target_duration_set_ = true;
|
target_duration_set_ = true;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duplicated from MpdUtils because:
|
// Duplicated from MpdUtils because:
|
||||||
|
@ -392,5 +444,67 @@ bool MediaPlaylist::GetResolution(uint32_t* width, uint32_t* height) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MediaPlaylist::SlideWindow() {
|
||||||
|
DCHECK(!entries_.empty());
|
||||||
|
if (time_shift_buffer_depth_ <= 0.0 || type_ != MediaPlaylistType::kLive)
|
||||||
|
return;
|
||||||
|
DCHECK_GT(time_scale_, 0u);
|
||||||
|
|
||||||
|
// The start time of the latest segment is considered the current_play_time,
|
||||||
|
// and this should guarantee that the latest segment will stay in the list.
|
||||||
|
const double current_play_time = LatestSegmentStartTime(entries_);
|
||||||
|
if (current_play_time <= time_shift_buffer_depth_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const double timeshift_limit = current_play_time - time_shift_buffer_depth_;
|
||||||
|
|
||||||
|
// Temporary list to hold the EXT-X-KEYs. For example, this allows us to
|
||||||
|
// remove <3> without removing <1> and <2> below (<1> and <2> are moved to the
|
||||||
|
// temporary list and added back later).
|
||||||
|
// #EXT-X-KEY <1>
|
||||||
|
// #EXT-X-KEY <2>
|
||||||
|
// #EXTINF <3>
|
||||||
|
// #EXTINF <4>
|
||||||
|
std::list<std::unique_ptr<HlsEntry>> ext_x_keys;
|
||||||
|
// Consecutive key entries are either fully removed or not removed at all.
|
||||||
|
// Keep track of entry types so we know if it is consecutive key entries.
|
||||||
|
HlsEntry::EntryType prev_entry_type = HlsEntry::EntryType::kExtInf;
|
||||||
|
|
||||||
|
std::list<std::unique_ptr<HlsEntry>>::iterator last = entries_.begin();
|
||||||
|
size_t num_segments_removed = 0;
|
||||||
|
for (; last != entries_.end(); ++last) {
|
||||||
|
HlsEntry::EntryType entry_type = last->get()->type();
|
||||||
|
if (entry_type == HlsEntry::EntryType::kExtKey) {
|
||||||
|
if (prev_entry_type != HlsEntry::EntryType::kExtKey) {
|
||||||
|
if (!ext_x_keys.empty()) {
|
||||||
|
// Increase discontinuity sequence every time key changes. Note that
|
||||||
|
// it is inconsistent to how we insert EXT-X-DISCONTINUITY tag
|
||||||
|
// currently as we only insert the tag for the first EXT-X-KEY.
|
||||||
|
// TODO(kqyang): Find out if it is necessary to insert the
|
||||||
|
// EXT-X-DISCONTINUITY tag when key changes.
|
||||||
|
++discontinuity_sequence_number_;
|
||||||
|
ext_x_keys.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ext_x_keys.push_back(std::move(*last));
|
||||||
|
} else {
|
||||||
|
DCHECK_EQ(entry_type, HlsEntry::EntryType::kExtInf);
|
||||||
|
const SegmentInfoEntry* segment_info =
|
||||||
|
reinterpret_cast<SegmentInfoEntry*>(last->get());
|
||||||
|
const double last_segment_end_time =
|
||||||
|
segment_info->start_time() + segment_info->duration();
|
||||||
|
if (timeshift_limit < last_segment_end_time)
|
||||||
|
break;
|
||||||
|
++num_segments_removed;
|
||||||
|
}
|
||||||
|
prev_entry_type = entry_type;
|
||||||
|
}
|
||||||
|
entries_.erase(entries_.begin(), last);
|
||||||
|
// Add key entries back.
|
||||||
|
entries_.insert(entries_.begin(), std::make_move_iterator(ext_x_keys.begin()),
|
||||||
|
std::make_move_iterator(ext_x_keys.end()));
|
||||||
|
sequence_number_ += num_segments_removed;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace hls
|
} // namespace hls
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -62,6 +62,8 @@ class MediaPlaylist {
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @param type is the type of this media playlist.
|
/// @param type is the type of this media playlist.
|
||||||
|
/// @param time_shift_buffer_depth determines the duration of the time
|
||||||
|
/// shifting buffer, only for live HLS.
|
||||||
/// @param file_name is the file name of this media playlist.
|
/// @param file_name is the file name of this media playlist.
|
||||||
/// @param name is the name of this playlist. In other words this is the
|
/// @param name is the name of this playlist. In other words this is the
|
||||||
/// value of the NAME attribute for EXT-X-MEDIA. This is not
|
/// value of the NAME attribute for EXT-X-MEDIA. This is not
|
||||||
|
@ -69,6 +71,7 @@ class MediaPlaylist {
|
||||||
/// @param group_id is the group ID for this playlist. This is the value of
|
/// @param group_id is the group ID for this playlist. This is the value of
|
||||||
/// GROUP-ID attribute for EXT-X-MEDIA.
|
/// GROUP-ID attribute for EXT-X-MEDIA.
|
||||||
MediaPlaylist(MediaPlaylistType type,
|
MediaPlaylist(MediaPlaylistType type,
|
||||||
|
double time_shift_buffer_depth,
|
||||||
const std::string& file_name,
|
const std::string& file_name,
|
||||||
const std::string& name,
|
const std::string& name,
|
||||||
const std::string& group_id);
|
const std::string& group_id);
|
||||||
|
@ -94,9 +97,11 @@ class MediaPlaylist {
|
||||||
|
|
||||||
/// Segments must be added in order.
|
/// Segments must be added in order.
|
||||||
/// @param file_name is the file name of the segment.
|
/// @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 duration is in terms of the timescale of the media.
|
||||||
/// @param size is size in bytes.
|
/// @param size is size in bytes.
|
||||||
virtual void AddSegment(const std::string& file_name,
|
virtual void AddSegment(const std::string& file_name,
|
||||||
|
uint64_t start_time,
|
||||||
uint64_t duration,
|
uint64_t duration,
|
||||||
uint64_t size);
|
uint64_t size);
|
||||||
|
|
||||||
|
@ -122,16 +127,17 @@ class MediaPlaylist {
|
||||||
const std::string& key_format,
|
const std::string& key_format,
|
||||||
const std::string& key_format_versions);
|
const std::string& key_format_versions);
|
||||||
|
|
||||||
/// Write the playlist to |file|.
|
/// Write the playlist to |file_path|.
|
||||||
/// This does not close the file.
|
/// This does not close the file.
|
||||||
/// If target duration is not set expliticly, this will try to find the target
|
/// If target duration is not set expliticly, this will try to find the target
|
||||||
/// duration. Note that target duration cannot be changed. So calling this
|
/// duration. Note that target duration cannot be changed. So calling this
|
||||||
/// without explicitly setting the target duration and before adding any
|
/// without explicitly setting the target duration and before adding any
|
||||||
/// segments will end up setting the target duration to 0 and will always
|
/// segments will end up setting the target duration to 0 and will always
|
||||||
/// generate an invalid playlist.
|
/// generate an invalid playlist.
|
||||||
/// @param file is the output file.
|
/// @param file_path is the output file path accepted by the File
|
||||||
|
/// implementation.
|
||||||
/// @return true on success, false otherwise.
|
/// @return true on success, false otherwise.
|
||||||
virtual bool WriteToFile(media::File* file);
|
virtual bool WriteToFile(const std::string& file_path);
|
||||||
|
|
||||||
/// If bitrate is specified in MediaInfo then it will use that value.
|
/// If bitrate is specified in MediaInfo then it will use that value.
|
||||||
/// Otherwise, returns the max bitrate.
|
/// Otherwise, returns the max bitrate.
|
||||||
|
@ -146,11 +152,10 @@ class MediaPlaylist {
|
||||||
/// In other words this is the value for EXT-X-TARGETDURATION.
|
/// In other words this is the value for EXT-X-TARGETDURATION.
|
||||||
/// If this is not called before calling Write(), it will estimate the best
|
/// If this is not called before calling Write(), it will estimate the best
|
||||||
/// target duration.
|
/// target duration.
|
||||||
/// The spec does not allow changing EXT-X-TARGETDURATION, once Write() is
|
/// The spec does not allow changing EXT-X-TARGETDURATION. However, this class
|
||||||
/// called, this will fail.
|
/// has no control over the input source.
|
||||||
/// @param target_duration is the target duration for this playlist.
|
/// @param target_duration is the target duration for this playlist.
|
||||||
/// @return true if set, false otherwise.
|
virtual void SetTargetDuration(uint32_t target_duration);
|
||||||
virtual bool SetTargetDuration(uint32_t target_duration);
|
|
||||||
|
|
||||||
/// @return the language of the media, as an ISO language tag in its shortest
|
/// @return the language of the media, as an ISO language tag in its shortest
|
||||||
/// form. May be an empty string for video.
|
/// form. May be an empty string for video.
|
||||||
|
@ -161,15 +166,22 @@ class MediaPlaylist {
|
||||||
virtual bool GetResolution(uint32_t* width, uint32_t* height) const;
|
virtual bool GetResolution(uint32_t* width, uint32_t* height) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Remove elements from |entries_| for live profile. Increments
|
||||||
|
// |sequence_number_| by the number of segments removed.
|
||||||
|
void SlideWindow();
|
||||||
|
|
||||||
|
const MediaPlaylistType type_;
|
||||||
|
const double time_shift_buffer_depth_;
|
||||||
// Mainly for MasterPlaylist to use these values.
|
// Mainly for MasterPlaylist to use these values.
|
||||||
const std::string file_name_;
|
const std::string file_name_;
|
||||||
const std::string name_;
|
const std::string name_;
|
||||||
const std::string group_id_;
|
const std::string group_id_;
|
||||||
MediaInfo media_info_;
|
MediaInfo media_info_;
|
||||||
const MediaPlaylistType type_;
|
|
||||||
MediaPlaylistStreamType stream_type_ =
|
MediaPlaylistStreamType stream_type_ =
|
||||||
MediaPlaylistStreamType::kPlaylistUnknown;
|
MediaPlaylistStreamType::kPlaylistUnknown;
|
||||||
std::string codec_;
|
std::string codec_;
|
||||||
|
int sequence_number_ = 0;
|
||||||
|
int discontinuity_sequence_number_ = 0;
|
||||||
|
|
||||||
double longest_segment_duration_ = 0.0;
|
double longest_segment_duration_ = 0.0;
|
||||||
uint32_t time_scale_ = 0;
|
uint32_t time_scale_ = 0;
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
#include <gmock/gmock.h>
|
#include <gmock/gmock.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "packager/media/file/file.h"
|
|
||||||
#include "packager/hls/base/media_playlist.h"
|
#include "packager/hls/base/media_playlist.h"
|
||||||
|
#include "packager/media/file/file_test_util.h"
|
||||||
#include "packager/version/version.h"
|
#include "packager/version/version.h"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
|
@ -20,21 +20,9 @@ using ::testing::ReturnArg;
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const char kDefaultPlaylistFileName[] = "default_playlist.m3u8";
|
const char kDefaultPlaylistFileName[] = "default_playlist.m3u8";
|
||||||
|
const double kTimeShiftBufferDepth = 20;
|
||||||
class MockFile : public media::File {
|
const uint64_t kTimeScale = 90000;
|
||||||
public:
|
const uint64_t kMBytes = 1000000;
|
||||||
MockFile() : File(kDefaultPlaylistFileName) {}
|
|
||||||
MOCK_METHOD0(Close, bool());
|
|
||||||
MOCK_METHOD2(Read, int64_t(void* buffer, uint64_t length));
|
|
||||||
MOCK_METHOD2(Write,int64_t(const void* buffer, uint64_t length));
|
|
||||||
MOCK_METHOD0(Size, int64_t());
|
|
||||||
MOCK_METHOD0(Flush, bool());
|
|
||||||
MOCK_METHOD1(Seek, bool(uint64_t position));
|
|
||||||
MOCK_METHOD1(Tell, bool(uint64_t* position));
|
|
||||||
|
|
||||||
private:
|
|
||||||
MOCK_METHOD0(Open, bool());
|
|
||||||
};
|
|
||||||
|
|
||||||
MATCHER_P(MatchesString, expected_string, "") {
|
MATCHER_P(MatchesString, expected_string, "") {
|
||||||
const std::string arg_string(static_cast<const char*>(arg));
|
const std::string arg_string(static_cast<const char*>(arg));
|
||||||
|
@ -48,10 +36,14 @@ MATCHER_P(MatchesString, expected_string, "") {
|
||||||
class MediaPlaylistTest : public ::testing::Test {
|
class MediaPlaylistTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
MediaPlaylistTest()
|
MediaPlaylistTest()
|
||||||
|
: MediaPlaylistTest(MediaPlaylist::MediaPlaylistType::kVod) {}
|
||||||
|
|
||||||
|
MediaPlaylistTest(MediaPlaylist::MediaPlaylistType type)
|
||||||
: default_file_name_(kDefaultPlaylistFileName),
|
: default_file_name_(kDefaultPlaylistFileName),
|
||||||
default_name_("default_name"),
|
default_name_("default_name"),
|
||||||
default_group_id_("default_group_id"),
|
default_group_id_("default_group_id"),
|
||||||
media_playlist_(MediaPlaylist::MediaPlaylistType::kVod,
|
media_playlist_(type,
|
||||||
|
kTimeShiftBufferDepth,
|
||||||
default_file_name_,
|
default_file_name_,
|
||||||
default_name_,
|
default_name_,
|
||||||
default_group_id_) {}
|
default_group_id_) {}
|
||||||
|
@ -62,12 +54,14 @@ class MediaPlaylistTest : public ::testing::Test {
|
||||||
MediaInfo::VideoInfo* video_info =
|
MediaInfo::VideoInfo* video_info =
|
||||||
valid_video_media_info_.mutable_video_info();
|
valid_video_media_info_.mutable_video_info();
|
||||||
video_info->set_codec("avc1");
|
video_info->set_codec("avc1");
|
||||||
video_info->set_time_scale(90000);
|
video_info->set_time_scale(kTimeScale);
|
||||||
video_info->set_frame_duration(3000);
|
video_info->set_frame_duration(3000);
|
||||||
video_info->set_width(1280);
|
video_info->set_width(1280);
|
||||||
video_info->set_height(720);
|
video_info->set_height(720);
|
||||||
video_info->set_pixel_width(1);
|
video_info->set_pixel_width(1);
|
||||||
video_info->set_pixel_height(1);
|
video_info->set_pixel_height(1);
|
||||||
|
|
||||||
|
valid_video_media_info_.set_reference_time_scale(kTimeScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string default_file_name_;
|
const std::string default_file_name_;
|
||||||
|
@ -87,7 +81,7 @@ TEST_F(MediaPlaylistTest, NoTimeScale) {
|
||||||
// The current implementation only handles video and audio.
|
// The current implementation only handles video and audio.
|
||||||
TEST_F(MediaPlaylistTest, NoAudioOrVideo) {
|
TEST_F(MediaPlaylistTest, NoAudioOrVideo) {
|
||||||
MediaInfo media_info;
|
MediaInfo media_info;
|
||||||
media_info.set_reference_time_scale(90000);
|
media_info.set_reference_time_scale(kTimeScale);
|
||||||
MediaInfo::TextInfo* text_info = media_info.mutable_text_info();
|
MediaInfo::TextInfo* text_info = media_info.mutable_text_info();
|
||||||
text_info->set_format("vtt");
|
text_info->set_format("vtt");
|
||||||
EXPECT_FALSE(media_playlist_.SetMediaInfo(media_info));
|
EXPECT_FALSE(media_playlist_.SetMediaInfo(media_info));
|
||||||
|
@ -95,7 +89,7 @@ TEST_F(MediaPlaylistTest, NoAudioOrVideo) {
|
||||||
|
|
||||||
TEST_F(MediaPlaylistTest, SetMediaInfo) {
|
TEST_F(MediaPlaylistTest, SetMediaInfo) {
|
||||||
MediaInfo media_info;
|
MediaInfo media_info;
|
||||||
media_info.set_reference_time_scale(90000);
|
media_info.set_reference_time_scale(kTimeScale);
|
||||||
MediaInfo::VideoInfo* video_info = media_info.mutable_video_info();
|
MediaInfo::VideoInfo* video_info = media_info.mutable_video_info();
|
||||||
video_info->set_width(1280);
|
video_info->set_width(1280);
|
||||||
video_info->set_height(720);
|
video_info->set_height(720);
|
||||||
|
@ -105,7 +99,7 @@ TEST_F(MediaPlaylistTest, SetMediaInfo) {
|
||||||
// Verify that AddSegment works (not crash).
|
// Verify that AddSegment works (not crash).
|
||||||
TEST_F(MediaPlaylistTest, AddSegment) {
|
TEST_F(MediaPlaylistTest, AddSegment) {
|
||||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
||||||
media_playlist_.AddSegment("file1.ts", 900000, 1000000);
|
media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that AddEncryptionInfo works (not crash).
|
// Verify that AddEncryptionInfo works (not crash).
|
||||||
|
@ -118,7 +112,7 @@ TEST_F(MediaPlaylistTest, AddEncryptionInfo) {
|
||||||
|
|
||||||
TEST_F(MediaPlaylistTest, WriteToFile) {
|
TEST_F(MediaPlaylistTest, WriteToFile) {
|
||||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
||||||
const std::string kExpectedOutput =
|
const char kExpectedOutput[] =
|
||||||
"#EXTM3U\n"
|
"#EXTM3U\n"
|
||||||
"#EXT-X-VERSION:6\n"
|
"#EXT-X-VERSION:6\n"
|
||||||
"## Generated with https://github.com/google/shaka-packager version "
|
"## Generated with https://github.com/google/shaka-packager version "
|
||||||
|
@ -127,11 +121,9 @@ TEST_F(MediaPlaylistTest, WriteToFile) {
|
||||||
"#EXT-X-PLAYLIST-TYPE:VOD\n"
|
"#EXT-X-PLAYLIST-TYPE:VOD\n"
|
||||||
"#EXT-X-ENDLIST\n";
|
"#EXT-X-ENDLIST\n";
|
||||||
|
|
||||||
MockFile file;
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
EXPECT_CALL(file,
|
EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath));
|
||||||
Write(MatchesString(kExpectedOutput), kExpectedOutput.size()))
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
.WillOnce(ReturnArg<1>());
|
|
||||||
EXPECT_TRUE(media_playlist_.WriteToFile(&file));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If bitrate (bandwidth) is not set in the MediaInfo, use it.
|
// If bitrate (bandwidth) is not set in the MediaInfo, use it.
|
||||||
|
@ -145,65 +137,35 @@ TEST_F(MediaPlaylistTest, UseBitrateInMediaInfo) {
|
||||||
// segments.
|
// segments.
|
||||||
TEST_F(MediaPlaylistTest, GetBitrateFromSegments) {
|
TEST_F(MediaPlaylistTest, GetBitrateFromSegments) {
|
||||||
valid_video_media_info_.clear_bandwidth();
|
valid_video_media_info_.clear_bandwidth();
|
||||||
|
|
||||||
valid_video_media_info_.set_reference_time_scale(90000);
|
|
||||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
||||||
|
|
||||||
// 10 seconds, 1MB.
|
media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes);
|
||||||
media_playlist_.AddSegment("file1.ts", 900000, 1000000);
|
media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 20 * kTimeScale,
|
||||||
// 20 seconds, 5MB.
|
5 * kMBytes);
|
||||||
media_playlist_.AddSegment("file2.ts", 1800000, 5000000);
|
|
||||||
|
|
||||||
// Max bitrate is 2000Kb/s.
|
// Max bitrate is 2000Kb/s.
|
||||||
EXPECT_EQ(2000000u, media_playlist_.Bitrate());
|
EXPECT_EQ(2000000u, media_playlist_.Bitrate());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MediaPlaylistTest, GetLongestSegmentDuration) {
|
TEST_F(MediaPlaylistTest, GetLongestSegmentDuration) {
|
||||||
valid_video_media_info_.set_reference_time_scale(90000);
|
|
||||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
||||||
|
|
||||||
// 10 seconds.
|
media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes);
|
||||||
media_playlist_.AddSegment("file1.ts", 900000, 1000000);
|
media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale,
|
||||||
// 30 seconds.
|
5 * kMBytes);
|
||||||
media_playlist_.AddSegment("file2.ts", 2700000, 5000000);
|
media_playlist_.AddSegment("file3.ts", 40 * kTimeScale, 14 * kTimeScale,
|
||||||
// 14 seconds.
|
3 * kMBytes);
|
||||||
media_playlist_.AddSegment("file3.ts", 1260000, 3000000);
|
|
||||||
|
|
||||||
EXPECT_NEAR(30.0, media_playlist_.GetLongestSegmentDuration(), 0.01);
|
EXPECT_NEAR(30.0, media_playlist_.GetLongestSegmentDuration(), 0.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MediaPlaylistTest, SetTargetDuration) {
|
|
||||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
|
||||||
EXPECT_TRUE(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";
|
|
||||||
|
|
||||||
MockFile file;
|
|
||||||
EXPECT_CALL(file,
|
|
||||||
Write(MatchesString(kExpectedOutput), kExpectedOutput.size()))
|
|
||||||
.WillOnce(ReturnArg<1>());
|
|
||||||
EXPECT_TRUE(media_playlist_.WriteToFile(&file));
|
|
||||||
|
|
||||||
// Cannot set target duration more than once.
|
|
||||||
EXPECT_FALSE(media_playlist_.SetTargetDuration(20));
|
|
||||||
EXPECT_FALSE(media_playlist_.SetTargetDuration(10));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(MediaPlaylistTest, WriteToFileWithSegments) {
|
TEST_F(MediaPlaylistTest, WriteToFileWithSegments) {
|
||||||
valid_video_media_info_.set_reference_time_scale(90000);
|
|
||||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
||||||
|
|
||||||
// 10 seconds.
|
media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes);
|
||||||
media_playlist_.AddSegment("file1.ts", 900000, 1000000);
|
media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale,
|
||||||
// 30 seconds.
|
5 * kMBytes);
|
||||||
media_playlist_.AddSegment("file2.ts", 2700000, 5000000);
|
const char kExpectedOutput[] =
|
||||||
const std::string kExpectedOutput =
|
|
||||||
"#EXTM3U\n"
|
"#EXTM3U\n"
|
||||||
"#EXT-X-VERSION:6\n"
|
"#EXT-X-VERSION:6\n"
|
||||||
"## Generated with https://github.com/google/shaka-packager version "
|
"## Generated with https://github.com/google/shaka-packager version "
|
||||||
|
@ -216,25 +178,21 @@ TEST_F(MediaPlaylistTest, WriteToFileWithSegments) {
|
||||||
"file2.ts\n"
|
"file2.ts\n"
|
||||||
"#EXT-X-ENDLIST\n";
|
"#EXT-X-ENDLIST\n";
|
||||||
|
|
||||||
MockFile file;
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
EXPECT_CALL(file,
|
EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath));
|
||||||
Write(MatchesString(kExpectedOutput), kExpectedOutput.size()))
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
.WillOnce(ReturnArg<1>());
|
|
||||||
EXPECT_TRUE(media_playlist_.WriteToFile(&file));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MediaPlaylistTest, WriteToFileWithEncryptionInfo) {
|
TEST_F(MediaPlaylistTest, WriteToFileWithEncryptionInfo) {
|
||||||
valid_video_media_info_.set_reference_time_scale(90000);
|
|
||||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
||||||
|
|
||||||
media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes,
|
media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes,
|
||||||
"http://example.com", "", "0x12345678",
|
"http://example.com", "", "0x12345678",
|
||||||
"com.widevine", "1/2/4");
|
"com.widevine", "1/2/4");
|
||||||
// 10 seconds.
|
media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes);
|
||||||
media_playlist_.AddSegment("file1.ts", 900000, 1000000);
|
media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale,
|
||||||
// 30 seconds.
|
5 * kMBytes);
|
||||||
media_playlist_.AddSegment("file2.ts", 2700000, 5000000);
|
const char kExpectedOutput[] =
|
||||||
const std::string kExpectedOutput =
|
|
||||||
"#EXTM3U\n"
|
"#EXTM3U\n"
|
||||||
"#EXT-X-VERSION:6\n"
|
"#EXT-X-VERSION:6\n"
|
||||||
"## Generated with https://github.com/google/shaka-packager version "
|
"## Generated with https://github.com/google/shaka-packager version "
|
||||||
|
@ -250,25 +208,21 @@ TEST_F(MediaPlaylistTest, WriteToFileWithEncryptionInfo) {
|
||||||
"file2.ts\n"
|
"file2.ts\n"
|
||||||
"#EXT-X-ENDLIST\n";
|
"#EXT-X-ENDLIST\n";
|
||||||
|
|
||||||
MockFile file;
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
EXPECT_CALL(file,
|
EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath));
|
||||||
Write(MatchesString(kExpectedOutput), kExpectedOutput.size()))
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
.WillOnce(ReturnArg<1>());
|
|
||||||
EXPECT_TRUE(media_playlist_.WriteToFile(&file));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MediaPlaylistTest, WriteToFileWithEncryptionInfoEmptyIv) {
|
TEST_F(MediaPlaylistTest, WriteToFileWithEncryptionInfoEmptyIv) {
|
||||||
valid_video_media_info_.set_reference_time_scale(90000);
|
|
||||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
||||||
|
|
||||||
media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes,
|
media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes,
|
||||||
"http://example.com", "", "", "com.widevine",
|
"http://example.com", "", "", "com.widevine",
|
||||||
"");
|
"");
|
||||||
// 10 seconds.
|
media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes);
|
||||||
media_playlist_.AddSegment("file1.ts", 900000, 1000000);
|
media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale,
|
||||||
// 30 seconds.
|
5 * kMBytes);
|
||||||
media_playlist_.AddSegment("file2.ts", 2700000, 5000000);
|
const char kExpectedOutput[] =
|
||||||
const std::string kExpectedOutput =
|
|
||||||
"#EXTM3U\n"
|
"#EXTM3U\n"
|
||||||
"#EXT-X-VERSION:6\n"
|
"#EXT-X-VERSION:6\n"
|
||||||
"## Generated with https://github.com/google/shaka-packager version "
|
"## Generated with https://github.com/google/shaka-packager version "
|
||||||
|
@ -283,25 +237,23 @@ TEST_F(MediaPlaylistTest, WriteToFileWithEncryptionInfoEmptyIv) {
|
||||||
"file2.ts\n"
|
"file2.ts\n"
|
||||||
"#EXT-X-ENDLIST\n";
|
"#EXT-X-ENDLIST\n";
|
||||||
|
|
||||||
MockFile file;
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
EXPECT_CALL(file,
|
EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath));
|
||||||
Write(MatchesString(kExpectedOutput), kExpectedOutput.size()))
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
.WillOnce(ReturnArg<1>());
|
|
||||||
EXPECT_TRUE(media_playlist_.WriteToFile(&file));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that EXT-X-DISCONTINUITY is inserted before EXT-X-KEY.
|
// Verify that EXT-X-DISCONTINUITY is inserted before EXT-X-KEY.
|
||||||
TEST_F(MediaPlaylistTest, WriteToFileWithClearLead) {
|
TEST_F(MediaPlaylistTest, WriteToFileWithClearLead) {
|
||||||
valid_video_media_info_.set_reference_time_scale(90000);
|
|
||||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
||||||
|
|
||||||
media_playlist_.AddSegment("file1.ts", 900000, 1000000);
|
media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes);
|
||||||
|
|
||||||
media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes,
|
media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes,
|
||||||
"http://example.com", "", "0x12345678",
|
"http://example.com", "", "0x12345678",
|
||||||
"com.widevine", "1/2/4");
|
"com.widevine", "1/2/4");
|
||||||
media_playlist_.AddSegment("file2.ts", 2700000, 5000000);
|
media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale,
|
||||||
const std::string kExpectedOutput =
|
5 * kMBytes);
|
||||||
|
const char kExpectedOutput[] =
|
||||||
"#EXTM3U\n"
|
"#EXTM3U\n"
|
||||||
"#EXT-X-VERSION:6\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"
|
||||||
|
@ -317,25 +269,21 @@ TEST_F(MediaPlaylistTest, WriteToFileWithClearLead) {
|
||||||
"file2.ts\n"
|
"file2.ts\n"
|
||||||
"#EXT-X-ENDLIST\n";
|
"#EXT-X-ENDLIST\n";
|
||||||
|
|
||||||
MockFile file;
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
EXPECT_CALL(file,
|
EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath));
|
||||||
Write(MatchesString(kExpectedOutput), kExpectedOutput.size()))
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
.WillOnce(ReturnArg<1>());
|
|
||||||
EXPECT_TRUE(media_playlist_.WriteToFile(&file));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST_F(MediaPlaylistTest, RemoveOldestSegment) {
|
TEST_F(MediaPlaylistTest, RemoveOldestSegment) {
|
||||||
valid_video_media_info_.set_reference_time_scale(90000);
|
|
||||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
||||||
|
|
||||||
// 10 seconds.
|
media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes);
|
||||||
media_playlist_.AddSegment("file1.ts", 900000, 1000000);
|
media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale,
|
||||||
// 30 seconds.
|
5 * kMBytes);
|
||||||
media_playlist_.AddSegment("file2.ts", 2700000, 5000000);
|
|
||||||
media_playlist_.RemoveOldestSegment();
|
media_playlist_.RemoveOldestSegment();
|
||||||
|
|
||||||
const std::string kExpectedOutput =
|
const char kExpectedOutput[] =
|
||||||
"#EXTM3U\n"
|
"#EXTM3U\n"
|
||||||
"#EXT-X-VERSION:6\n"
|
"#EXT-X-VERSION:6\n"
|
||||||
"## Generated with https://github.com/google/shaka-packager version "
|
"## Generated with https://github.com/google/shaka-packager version "
|
||||||
|
@ -346,16 +294,14 @@ TEST_F(MediaPlaylistTest, RemoveOldestSegment) {
|
||||||
"file2.ts\n"
|
"file2.ts\n"
|
||||||
"#EXT-X-ENDLIST\n";
|
"#EXT-X-ENDLIST\n";
|
||||||
|
|
||||||
MockFile file;
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
EXPECT_CALL(file,
|
EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath));
|
||||||
Write(MatchesString(kExpectedOutput), kExpectedOutput.size()))
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
.WillOnce(ReturnArg<1>());
|
|
||||||
EXPECT_TRUE(media_playlist_.WriteToFile(&file));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MediaPlaylistTest, GetLanguage) {
|
TEST_F(MediaPlaylistTest, GetLanguage) {
|
||||||
MediaInfo media_info;
|
MediaInfo media_info;
|
||||||
media_info.set_reference_time_scale(90000);
|
media_info.set_reference_time_scale(kTimeScale);
|
||||||
|
|
||||||
// Check conversions from long to short form.
|
// Check conversions from long to short form.
|
||||||
media_info.mutable_audio_info()->set_language("eng");
|
media_info.mutable_audio_info()->set_language("eng");
|
||||||
|
@ -372,16 +318,14 @@ TEST_F(MediaPlaylistTest, GetLanguage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MediaPlaylistTest, InitSegment) {
|
TEST_F(MediaPlaylistTest, InitSegment) {
|
||||||
valid_video_media_info_.set_reference_time_scale(90000);
|
|
||||||
valid_video_media_info_.set_init_segment_name("init_segment.mp4");
|
valid_video_media_info_.set_init_segment_name("init_segment.mp4");
|
||||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
||||||
|
|
||||||
// 10 seconds.
|
media_playlist_.AddSegment("file1.mp4", 0, 10 * kTimeScale, kMBytes);
|
||||||
media_playlist_.AddSegment("file1.mp4", 900000, 1000000);
|
media_playlist_.AddSegment("file2.mp4", 10 * kTimeScale, 30 * kTimeScale,
|
||||||
// 30 seconds.
|
5 * kMBytes);
|
||||||
media_playlist_.AddSegment("file2.mp4", 2700000, 5000000);
|
|
||||||
|
|
||||||
const std::string kExpectedOutput =
|
const char kExpectedOutput[] =
|
||||||
"#EXTM3U\n"
|
"#EXTM3U\n"
|
||||||
"#EXT-X-VERSION:6\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"
|
||||||
|
@ -394,27 +338,23 @@ TEST_F(MediaPlaylistTest, InitSegment) {
|
||||||
"file2.mp4\n"
|
"file2.mp4\n"
|
||||||
"#EXT-X-ENDLIST\n";
|
"#EXT-X-ENDLIST\n";
|
||||||
|
|
||||||
MockFile file;
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
EXPECT_CALL(file,
|
EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath));
|
||||||
Write(MatchesString(kExpectedOutput), kExpectedOutput.size()))
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
.WillOnce(ReturnArg<1>());
|
|
||||||
EXPECT_TRUE(media_playlist_.WriteToFile(&file));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that kSampleAesCenc is handled correctly.
|
// Verify that kSampleAesCenc is handled correctly.
|
||||||
TEST_F(MediaPlaylistTest, SampleAesCenc) {
|
TEST_F(MediaPlaylistTest, SampleAesCenc) {
|
||||||
valid_video_media_info_.set_reference_time_scale(90000);
|
|
||||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
||||||
|
|
||||||
media_playlist_.AddEncryptionInfo(
|
media_playlist_.AddEncryptionInfo(
|
||||||
MediaPlaylist::EncryptionMethod::kSampleAesCenc, "http://example.com", "",
|
MediaPlaylist::EncryptionMethod::kSampleAesCenc, "http://example.com", "",
|
||||||
"0x12345678", "com.widevine", "1/2/4");
|
"0x12345678", "com.widevine", "1/2/4");
|
||||||
|
|
||||||
// 10 seconds.
|
media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes);
|
||||||
media_playlist_.AddSegment("file1.ts", 900000, 1000000);
|
media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale,
|
||||||
// 30 seconds.
|
5 * kMBytes);
|
||||||
media_playlist_.AddSegment("file2.ts", 2700000, 5000000);
|
const char kExpectedOutput[] =
|
||||||
const std::string kExpectedOutput =
|
|
||||||
"#EXTM3U\n"
|
"#EXTM3U\n"
|
||||||
"#EXT-X-VERSION:6\n"
|
"#EXT-X-VERSION:6\n"
|
||||||
"## Generated with https://github.com/google/shaka-packager version "
|
"## Generated with https://github.com/google/shaka-packager version "
|
||||||
|
@ -430,16 +370,13 @@ TEST_F(MediaPlaylistTest, SampleAesCenc) {
|
||||||
"file2.ts\n"
|
"file2.ts\n"
|
||||||
"#EXT-X-ENDLIST\n";
|
"#EXT-X-ENDLIST\n";
|
||||||
|
|
||||||
MockFile file;
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
EXPECT_CALL(file,
|
EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath));
|
||||||
Write(MatchesString(kExpectedOutput), kExpectedOutput.size()))
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
.WillOnce(ReturnArg<1>());
|
|
||||||
EXPECT_TRUE(media_playlist_.WriteToFile(&file));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that multiple encryption info can be set.
|
// Verify that multiple encryption info can be set.
|
||||||
TEST_F(MediaPlaylistTest, MultipleEncryptionInfo) {
|
TEST_F(MediaPlaylistTest, MultipleEncryptionInfo) {
|
||||||
valid_video_media_info_.set_reference_time_scale(90000);
|
|
||||||
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
ASSERT_TRUE(media_playlist_.SetMediaInfo(valid_video_media_info_));
|
||||||
|
|
||||||
media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes,
|
media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes,
|
||||||
|
@ -450,11 +387,10 @@ TEST_F(MediaPlaylistTest, MultipleEncryptionInfo) {
|
||||||
MediaPlaylist::EncryptionMethod::kSampleAes, "http://mydomain.com",
|
MediaPlaylist::EncryptionMethod::kSampleAes, "http://mydomain.com",
|
||||||
"0xfedc", "0x12345678", "com.widevine.someother", "1");
|
"0xfedc", "0x12345678", "com.widevine.someother", "1");
|
||||||
|
|
||||||
// 10 seconds.
|
media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes);
|
||||||
media_playlist_.AddSegment("file1.ts", 900000, 1000000);
|
media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 30 * kTimeScale,
|
||||||
// 30 seconds.
|
5 * kMBytes);
|
||||||
media_playlist_.AddSegment("file2.ts", 2700000, 5000000);
|
const char kExpectedOutput[] =
|
||||||
const std::string kExpectedOutput =
|
|
||||||
"#EXTM3U\n"
|
"#EXTM3U\n"
|
||||||
"#EXT-X-VERSION:6\n"
|
"#EXT-X-VERSION:6\n"
|
||||||
"## Generated with https://github.com/google/shaka-packager version "
|
"## Generated with https://github.com/google/shaka-packager version "
|
||||||
|
@ -474,11 +410,193 @@ TEST_F(MediaPlaylistTest, MultipleEncryptionInfo) {
|
||||||
"file2.ts\n"
|
"file2.ts\n"
|
||||||
"#EXT-X-ENDLIST\n";
|
"#EXT-X-ENDLIST\n";
|
||||||
|
|
||||||
MockFile file;
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
EXPECT_CALL(file,
|
EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath));
|
||||||
Write(MatchesString(kExpectedOutput), kExpectedOutput.size()))
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
.WillOnce(ReturnArg<1>());
|
}
|
||||||
EXPECT_TRUE(media_playlist_.WriteToFile(&file));
|
|
||||||
|
class LiveMediaPlaylistTest : public MediaPlaylistTest {
|
||||||
|
protected:
|
||||||
|
LiveMediaPlaylistTest()
|
||||||
|
: MediaPlaylistTest(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("file2.ts", 10 * kTimeScale, 20 * kTimeScale,
|
||||||
|
2 * kMBytes);
|
||||||
|
const char kExpectedOutput[] =
|
||||||
|
"#EXTM3U\n"
|
||||||
|
"#EXT-X-VERSION:6\n"
|
||||||
|
"## Generated with https://github.com/google/shaka-packager version "
|
||||||
|
"test\n"
|
||||||
|
"#EXT-X-TARGETDURATION:20\n"
|
||||||
|
"#EXTINF:10.000,\n"
|
||||||
|
"file1.ts\n"
|
||||||
|
"#EXTINF:20.000,\n"
|
||||||
|
"file2.ts\n";
|
||||||
|
|
||||||
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
|
EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath));
|
||||||
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
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("file2.ts", 10 * kTimeScale, 20 * kTimeScale,
|
||||||
|
2 * kMBytes);
|
||||||
|
media_playlist_.AddSegment("file3.ts", 30 * kTimeScale, 20 * kTimeScale,
|
||||||
|
2 * kMBytes);
|
||||||
|
const char kExpectedOutput[] =
|
||||||
|
"#EXTM3U\n"
|
||||||
|
"#EXT-X-VERSION:6\n"
|
||||||
|
"## Generated with https://github.com/google/shaka-packager version "
|
||||||
|
"test\n"
|
||||||
|
"#EXT-X-TARGETDURATION:20\n"
|
||||||
|
"#EXT-X-MEDIA-SEQUENCE:1\n"
|
||||||
|
"#EXTINF:20.000,\n"
|
||||||
|
"file2.ts\n"
|
||||||
|
"#EXTINF:20.000,\n"
|
||||||
|
"file3.ts\n";
|
||||||
|
|
||||||
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
|
EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath));
|
||||||
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LiveMediaPlaylistTest, TimeShiftedWithEncryptionInfo) {
|
||||||
|
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_.AddEncryptionInfo(
|
||||||
|
MediaPlaylist::EncryptionMethod::kSampleAes, "http://mydomain.com",
|
||||||
|
"0xfedc", "0x12345678", "com.widevine.someother", "1");
|
||||||
|
|
||||||
|
media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes);
|
||||||
|
media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 20 * kTimeScale,
|
||||||
|
2 * kMBytes);
|
||||||
|
media_playlist_.AddSegment("file3.ts", 30 * kTimeScale, 20 * kTimeScale,
|
||||||
|
2 * kMBytes);
|
||||||
|
const char kExpectedOutput[] =
|
||||||
|
"#EXTM3U\n"
|
||||||
|
"#EXT-X-VERSION:6\n"
|
||||||
|
"## Generated with https://github.com/google/shaka-packager version "
|
||||||
|
"test\n"
|
||||||
|
"#EXT-X-TARGETDURATION:20\n"
|
||||||
|
"#EXT-X-MEDIA-SEQUENCE:1\n"
|
||||||
|
"#EXT-X-KEY:METHOD=SAMPLE-AES,"
|
||||||
|
"URI=\"http://example.com\",IV=0x12345678,KEYFORMATVERSIONS=\"1/2/4\","
|
||||||
|
"KEYFORMAT=\"com.widevine\"\n"
|
||||||
|
"#EXT-X-KEY:METHOD=SAMPLE-AES,"
|
||||||
|
"URI=\"http://mydomain.com\",KEYID=0xfedc,IV=0x12345678,"
|
||||||
|
"KEYFORMATVERSIONS=\"1\","
|
||||||
|
"KEYFORMAT=\"com.widevine.someother\"\n"
|
||||||
|
"#EXTINF:20.000,\n"
|
||||||
|
"file2.ts\n"
|
||||||
|
"#EXTINF:20.000,\n"
|
||||||
|
"file3.ts\n";
|
||||||
|
|
||||||
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
|
EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath));
|
||||||
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LiveMediaPlaylistTest, TimeShiftedWithEncryptionInfoShifted) {
|
||||||
|
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_.AddEncryptionInfo(
|
||||||
|
MediaPlaylist::EncryptionMethod::kSampleAes, "http://mydomain.com",
|
||||||
|
"0xfedc", "0x12345678", "com.widevine.someother", "1");
|
||||||
|
|
||||||
|
media_playlist_.AddSegment("file1.ts", 0, 10 * kTimeScale, kMBytes);
|
||||||
|
|
||||||
|
media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes,
|
||||||
|
"http://example.com", "", "0x22345678",
|
||||||
|
"com.widevine", "1/2/4");
|
||||||
|
media_playlist_.AddEncryptionInfo(
|
||||||
|
MediaPlaylist::EncryptionMethod::kSampleAes, "http://mydomain.com",
|
||||||
|
"0xfedd", "0x22345678", "com.widevine.someother", "1");
|
||||||
|
|
||||||
|
media_playlist_.AddSegment("file2.ts", 10 * kTimeScale, 20 * kTimeScale,
|
||||||
|
2 * kMBytes);
|
||||||
|
|
||||||
|
media_playlist_.AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes,
|
||||||
|
"http://example.com", "", "0x32345678",
|
||||||
|
"com.widevine", "1/2/4");
|
||||||
|
media_playlist_.AddEncryptionInfo(
|
||||||
|
MediaPlaylist::EncryptionMethod::kSampleAes, "http://mydomain.com",
|
||||||
|
"0xfede", "0x32345678", "com.widevine.someother", "1");
|
||||||
|
|
||||||
|
media_playlist_.AddSegment("file3.ts", 30 * kTimeScale, 20 * kTimeScale,
|
||||||
|
2 * kMBytes);
|
||||||
|
const char kExpectedOutput[] =
|
||||||
|
"#EXTM3U\n"
|
||||||
|
"#EXT-X-VERSION:6\n"
|
||||||
|
"## Generated with https://github.com/google/shaka-packager version "
|
||||||
|
"test\n"
|
||||||
|
"#EXT-X-TARGETDURATION:20\n"
|
||||||
|
"#EXT-X-MEDIA-SEQUENCE:1\n"
|
||||||
|
"#EXT-X-DISCONTINUITY-SEQUENCE:1\n"
|
||||||
|
"#EXT-X-KEY:METHOD=SAMPLE-AES,"
|
||||||
|
"URI=\"http://example.com\",IV=0x22345678,KEYFORMATVERSIONS=\"1/2/4\","
|
||||||
|
"KEYFORMAT=\"com.widevine\"\n"
|
||||||
|
"#EXT-X-KEY:METHOD=SAMPLE-AES,"
|
||||||
|
"URI=\"http://mydomain.com\",KEYID=0xfedd,IV=0x22345678,"
|
||||||
|
"KEYFORMATVERSIONS=\"1\","
|
||||||
|
"KEYFORMAT=\"com.widevine.someother\"\n"
|
||||||
|
"#EXTINF:20.000,\n"
|
||||||
|
"file2.ts\n"
|
||||||
|
"#EXT-X-KEY:METHOD=SAMPLE-AES,"
|
||||||
|
"URI=\"http://example.com\",IV=0x32345678,KEYFORMATVERSIONS=\"1/2/4\","
|
||||||
|
"KEYFORMAT=\"com.widevine\"\n"
|
||||||
|
"#EXT-X-KEY:METHOD=SAMPLE-AES,"
|
||||||
|
"URI=\"http://mydomain.com\",KEYID=0xfede,IV=0x32345678,"
|
||||||
|
"KEYFORMATVERSIONS=\"1\","
|
||||||
|
"KEYFORMAT=\"com.widevine.someother\"\n"
|
||||||
|
"#EXTINF:20.000,\n"
|
||||||
|
"file3.ts\n";
|
||||||
|
|
||||||
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
|
EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath));
|
||||||
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventMediaPlaylistTest : public MediaPlaylistTest {
|
||||||
|
protected:
|
||||||
|
EventMediaPlaylistTest()
|
||||||
|
: MediaPlaylistTest(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("file2.ts", 10 * kTimeScale, 20 * kTimeScale,
|
||||||
|
2 * kMBytes);
|
||||||
|
const char kExpectedOutput[] =
|
||||||
|
"#EXTM3U\n"
|
||||||
|
"#EXT-X-VERSION:6\n"
|
||||||
|
"## Generated with https://github.com/google/shaka-packager version "
|
||||||
|
"test\n"
|
||||||
|
"#EXT-X-TARGETDURATION:20\n"
|
||||||
|
"#EXT-X-PLAYLIST-TYPE:EVENT\n"
|
||||||
|
"#EXTINF:10.000,\n"
|
||||||
|
"file1.ts\n"
|
||||||
|
"#EXTINF:20.000,\n"
|
||||||
|
"file2.ts\n";
|
||||||
|
|
||||||
|
const char kMemoryFilePath[] = "memory://media.m3u8";
|
||||||
|
EXPECT_TRUE(media_playlist_.WriteToFile(kMemoryFilePath));
|
||||||
|
ASSERT_FILE_STREQ(kMemoryFilePath, kExpectedOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace hls
|
} // namespace hls
|
||||||
|
|
|
@ -13,7 +13,7 @@ MockMediaPlaylist::MockMediaPlaylist(MediaPlaylistType type,
|
||||||
const std::string& file_name,
|
const std::string& file_name,
|
||||||
const std::string& name,
|
const std::string& name,
|
||||||
const std::string& group_id)
|
const std::string& group_id)
|
||||||
: MediaPlaylist(type, file_name, name, group_id) {}
|
: MediaPlaylist(type, 0, file_name, name, group_id) {}
|
||||||
MockMediaPlaylist::~MockMediaPlaylist() {}
|
MockMediaPlaylist::~MockMediaPlaylist() {}
|
||||||
|
|
||||||
} // namespace hls
|
} // namespace hls
|
||||||
|
|
|
@ -25,8 +25,9 @@ class MockMediaPlaylist : public MediaPlaylist {
|
||||||
~MockMediaPlaylist() override;
|
~MockMediaPlaylist() override;
|
||||||
|
|
||||||
MOCK_METHOD1(SetMediaInfo, bool(const MediaInfo& media_info));
|
MOCK_METHOD1(SetMediaInfo, bool(const MediaInfo& media_info));
|
||||||
MOCK_METHOD3(AddSegment,
|
MOCK_METHOD4(AddSegment,
|
||||||
void(const std::string& file_name,
|
void(const std::string& file_name,
|
||||||
|
uint64_t start_time,
|
||||||
uint64_t duration,
|
uint64_t duration,
|
||||||
uint64_t size));
|
uint64_t size));
|
||||||
MOCK_METHOD0(RemoveOldestSegment, void());
|
MOCK_METHOD0(RemoveOldestSegment, void());
|
||||||
|
@ -37,10 +38,10 @@ class MockMediaPlaylist : public MediaPlaylist {
|
||||||
const std::string& iv,
|
const std::string& iv,
|
||||||
const std::string& key_format,
|
const std::string& key_format,
|
||||||
const std::string& key_format_versions));
|
const std::string& key_format_versions));
|
||||||
MOCK_METHOD1(WriteToFile, bool(media::File* file));
|
MOCK_METHOD1(WriteToFile, bool(const std::string& file_path));
|
||||||
MOCK_CONST_METHOD0(Bitrate, uint64_t());
|
MOCK_CONST_METHOD0(Bitrate, uint64_t());
|
||||||
MOCK_CONST_METHOD0(GetLongestSegmentDuration, double());
|
MOCK_CONST_METHOD0(GetLongestSegmentDuration, double());
|
||||||
MOCK_METHOD1(SetTargetDuration, bool(uint32_t target_duration));
|
MOCK_METHOD1(SetTargetDuration, void(uint32_t target_duration));
|
||||||
MOCK_CONST_METHOD0(GetLanguage, std::string());
|
MOCK_CONST_METHOD0(GetLanguage, std::string());
|
||||||
MOCK_CONST_METHOD2(GetResolution, bool(uint32_t* width, uint32_t* height));
|
MOCK_CONST_METHOD2(GetResolution, bool(uint32_t* width, uint32_t* height));
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
#include "packager/hls/base/simple_hls_notifier.h"
|
#include "packager/hls/base/simple_hls_notifier.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
#include "packager/base/base64.h"
|
#include "packager/base/base64.h"
|
||||||
#include "packager/base/files/file_path.h"
|
#include "packager/base/files/file_path.h"
|
||||||
#include "packager/base/json/json_writer.h"
|
#include "packager/base/json/json_writer.h"
|
||||||
|
@ -191,24 +193,40 @@ bool HandleWidevineKeyFormats(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WriteMediaPlaylist(const std::string& output_dir,
|
||||||
|
MediaPlaylist* playlist) {
|
||||||
|
std::string file_path =
|
||||||
|
base::FilePath::FromUTF8Unsafe(output_dir)
|
||||||
|
.Append(base::FilePath::FromUTF8Unsafe(playlist->file_name()))
|
||||||
|
.AsUTF8Unsafe();
|
||||||
|
if (!playlist->WriteToFile(file_path)) {
|
||||||
|
LOG(ERROR) << "Failed to write playlist " << file_path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
MediaPlaylistFactory::~MediaPlaylistFactory() {}
|
MediaPlaylistFactory::~MediaPlaylistFactory() {}
|
||||||
|
|
||||||
std::unique_ptr<MediaPlaylist> MediaPlaylistFactory::Create(
|
std::unique_ptr<MediaPlaylist> MediaPlaylistFactory::Create(
|
||||||
MediaPlaylist::MediaPlaylistType type,
|
MediaPlaylist::MediaPlaylistType type,
|
||||||
|
double time_shift_buffer_depth,
|
||||||
const std::string& file_name,
|
const std::string& file_name,
|
||||||
const std::string& name,
|
const std::string& name,
|
||||||
const std::string& group_id) {
|
const std::string& group_id) {
|
||||||
return std::unique_ptr<MediaPlaylist>(
|
return std::unique_ptr<MediaPlaylist>(new MediaPlaylist(
|
||||||
new MediaPlaylist(type, file_name, name, group_id));
|
type, time_shift_buffer_depth, file_name, name, group_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
SimpleHlsNotifier::SimpleHlsNotifier(HlsProfile profile,
|
SimpleHlsNotifier::SimpleHlsNotifier(HlsProfile profile,
|
||||||
|
double time_shift_buffer_depth,
|
||||||
const std::string& prefix,
|
const std::string& prefix,
|
||||||
const std::string& output_dir,
|
const std::string& output_dir,
|
||||||
const std::string& master_playlist_name)
|
const std::string& master_playlist_name)
|
||||||
: HlsNotifier(profile),
|
: HlsNotifier(profile),
|
||||||
|
time_shift_buffer_depth_(time_shift_buffer_depth),
|
||||||
prefix_(prefix),
|
prefix_(prefix),
|
||||||
output_dir_(output_dir),
|
output_dir_(output_dir),
|
||||||
media_playlist_factory_(new MediaPlaylistFactory()),
|
media_playlist_factory_(new MediaPlaylistFactory()),
|
||||||
|
@ -235,6 +253,9 @@ bool SimpleHlsNotifier::NotifyNewStream(const MediaInfo& media_info,
|
||||||
case HlsProfile::kOnDemandProfile:
|
case HlsProfile::kOnDemandProfile:
|
||||||
type = MediaPlaylist::MediaPlaylistType::kVod;
|
type = MediaPlaylist::MediaPlaylistType::kVod;
|
||||||
break;
|
break;
|
||||||
|
case HlsProfile::kEventProfile:
|
||||||
|
type = MediaPlaylist::MediaPlaylistType::kEvent;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
NOTREACHED();
|
NOTREACHED();
|
||||||
return false;
|
return false;
|
||||||
|
@ -244,7 +265,8 @@ bool SimpleHlsNotifier::NotifyNewStream(const MediaInfo& media_info,
|
||||||
MakePathsRelativeToOutputDirectory(output_dir_, &adjusted_media_info);
|
MakePathsRelativeToOutputDirectory(output_dir_, &adjusted_media_info);
|
||||||
|
|
||||||
std::unique_ptr<MediaPlaylist> media_playlist =
|
std::unique_ptr<MediaPlaylist> media_playlist =
|
||||||
media_playlist_factory_->Create(type, playlist_name, name, group_id);
|
media_playlist_factory_->Create(type, time_shift_buffer_depth_,
|
||||||
|
playlist_name, name, group_id);
|
||||||
if (!media_playlist->SetMediaInfo(adjusted_media_info)) {
|
if (!media_playlist->SetMediaInfo(adjusted_media_info)) {
|
||||||
LOG(ERROR) << "Failed to set media info for playlist " << playlist_name;
|
LOG(ERROR) << "Failed to set media info for playlist " << playlist_name;
|
||||||
return false;
|
return false;
|
||||||
|
@ -288,7 +310,37 @@ bool SimpleHlsNotifier::NotifyNewSegment(uint32_t stream_id,
|
||||||
MakePathRelative(segment_name, output_dir_);
|
MakePathRelative(segment_name, output_dir_);
|
||||||
|
|
||||||
auto& media_playlist = stream_iterator->second->media_playlist;
|
auto& media_playlist = stream_iterator->second->media_playlist;
|
||||||
media_playlist->AddSegment(prefix_ + relative_segment_name, duration, size);
|
media_playlist->AddSegment(prefix_ + relative_segment_name, start_time,
|
||||||
|
duration, size);
|
||||||
|
|
||||||
|
// Update target duration.
|
||||||
|
uint32_t longest_segment_duration =
|
||||||
|
static_cast<uint32_t>(ceil(media_playlist->GetLongestSegmentDuration()));
|
||||||
|
bool target_duration_updated = false;
|
||||||
|
if (longest_segment_duration > target_duration_) {
|
||||||
|
target_duration_ = longest_segment_duration;
|
||||||
|
target_duration_updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the playlists when there is new segments in live mode.
|
||||||
|
if (profile() == HlsProfile::kLiveProfile ||
|
||||||
|
profile() == HlsProfile::kEventProfile) {
|
||||||
|
if (!master_playlist_->WriteMasterPlaylist(prefix_, output_dir_)) {
|
||||||
|
LOG(ERROR) << "Failed to write master playlist.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Update all playlists if target duration is updated.
|
||||||
|
if (target_duration_updated) {
|
||||||
|
for (auto& streams : stream_map_) {
|
||||||
|
MediaPlaylist* playlist = streams.second->media_playlist.get();
|
||||||
|
playlist->SetTargetDuration(target_duration_);
|
||||||
|
if (!WriteMediaPlaylist(output_dir_, playlist))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return WriteMediaPlaylist(output_dir_, media_playlist.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,7 +385,17 @@ bool SimpleHlsNotifier::NotifyEncryptionUpdate(
|
||||||
|
|
||||||
bool SimpleHlsNotifier::Flush() {
|
bool SimpleHlsNotifier::Flush() {
|
||||||
base::AutoLock auto_lock(lock_);
|
base::AutoLock auto_lock(lock_);
|
||||||
return master_playlist_->WriteAllPlaylists(prefix_, output_dir_);
|
if (!master_playlist_->WriteMasterPlaylist(prefix_, output_dir_)) {
|
||||||
|
LOG(ERROR) << "Failed to write master playlist.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (auto& streams : stream_map_) {
|
||||||
|
MediaPlaylist* playlist = streams.second->media_playlist.get();
|
||||||
|
playlist->SetTargetDuration(target_duration_);
|
||||||
|
if (!WriteMediaPlaylist(output_dir_, playlist))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace hls
|
} // namespace hls
|
||||||
|
|
|
@ -29,6 +29,7 @@ class MediaPlaylistFactory {
|
||||||
virtual ~MediaPlaylistFactory();
|
virtual ~MediaPlaylistFactory();
|
||||||
virtual std::unique_ptr<MediaPlaylist> Create(
|
virtual std::unique_ptr<MediaPlaylist> Create(
|
||||||
MediaPlaylist::MediaPlaylistType type,
|
MediaPlaylist::MediaPlaylistType type,
|
||||||
|
double time_shift_buffer_depth,
|
||||||
const std::string& file_name,
|
const std::string& file_name,
|
||||||
const std::string& name,
|
const std::string& name,
|
||||||
const std::string& group_id);
|
const std::string& group_id);
|
||||||
|
@ -40,12 +41,15 @@ class SimpleHlsNotifier : public HlsNotifier {
|
||||||
/// @a prefix is used as hte prefix for all the URIs for Media Playlist. This
|
/// @a prefix is used as hte prefix for all the URIs for Media Playlist. This
|
||||||
/// includes the segment URIs in the Media Playlists.
|
/// includes the segment URIs in the Media Playlists.
|
||||||
/// @param profile is the profile of the playlists.
|
/// @param profile is the profile of the playlists.
|
||||||
|
/// @param time_shift_buffer_depth determines the duration of the time
|
||||||
|
/// shifting buffer, only for live HLS.
|
||||||
/// @param prefix is the used as the prefix for MediaPlaylist URIs. May be
|
/// @param prefix is the used as the prefix for MediaPlaylist URIs. May be
|
||||||
/// empty for relative URI from the playlist.
|
/// empty for relative URI from the playlist.
|
||||||
/// @param output_dir is the output directory of the playlists. May be empty
|
/// @param output_dir is the output directory of the playlists. May be empty
|
||||||
/// to write to current directory.
|
/// to write to current directory.
|
||||||
/// @param master_playlist_name is the name of the master playlist.
|
/// @param master_playlist_name is the name of the master playlist.
|
||||||
SimpleHlsNotifier(HlsProfile profile,
|
SimpleHlsNotifier(HlsProfile profile,
|
||||||
|
double time_shift_buffer_depth,
|
||||||
const std::string& prefix,
|
const std::string& prefix,
|
||||||
const std::string& output_dir,
|
const std::string& output_dir,
|
||||||
const std::string& master_playlist_name);
|
const std::string& master_playlist_name);
|
||||||
|
@ -81,8 +85,10 @@ class SimpleHlsNotifier : public HlsNotifier {
|
||||||
MediaPlaylist::EncryptionMethod encryption_method;
|
MediaPlaylist::EncryptionMethod encryption_method;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const double time_shift_buffer_depth_ = 0;
|
||||||
const std::string prefix_;
|
const std::string prefix_;
|
||||||
const std::string output_dir_;
|
const std::string output_dir_;
|
||||||
|
uint32_t target_duration_ = 0;
|
||||||
|
|
||||||
std::unique_ptr<MediaPlaylistFactory> media_playlist_factory_;
|
std::unique_ptr<MediaPlaylistFactory> media_playlist_factory_;
|
||||||
std::unique_ptr<MasterPlaylist> master_playlist_;
|
std::unique_ptr<MasterPlaylist> master_playlist_;
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "packager/base/base64.h"
|
#include "packager/base/base64.h"
|
||||||
|
#include "packager/base/files/file_path.h"
|
||||||
#include "packager/hls/base/mock_media_playlist.h"
|
#include "packager/hls/base/mock_media_playlist.h"
|
||||||
#include "packager/hls/base/simple_hls_notifier.h"
|
#include "packager/hls/base/simple_hls_notifier.h"
|
||||||
#include "packager/media/base/fixed_key_source.h"
|
#include "packager/media/base/fixed_key_source.h"
|
||||||
|
@ -18,6 +19,9 @@
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace hls {
|
namespace hls {
|
||||||
|
|
||||||
|
using ::testing::Eq;
|
||||||
|
using ::testing::InSequence;
|
||||||
|
using ::testing::Mock;
|
||||||
using ::testing::Return;
|
using ::testing::Return;
|
||||||
using ::testing::StrEq;
|
using ::testing::StrEq;
|
||||||
using ::testing::_;
|
using ::testing::_;
|
||||||
|
@ -32,29 +36,30 @@ class MockMasterPlaylist : public MasterPlaylist {
|
||||||
MockMasterPlaylist() : MasterPlaylist(kMasterPlaylistName) {}
|
MockMasterPlaylist() : MasterPlaylist(kMasterPlaylistName) {}
|
||||||
|
|
||||||
MOCK_METHOD1(AddMediaPlaylist, void(MediaPlaylist* media_playlist));
|
MOCK_METHOD1(AddMediaPlaylist, void(MediaPlaylist* media_playlist));
|
||||||
MOCK_METHOD2(WriteAllPlaylists,
|
|
||||||
bool(const std::string& prefix, const std::string& output_dir));
|
|
||||||
MOCK_METHOD2(WriteMasterPlaylist,
|
MOCK_METHOD2(WriteMasterPlaylist,
|
||||||
bool(const std::string& prefix, const std::string& output_dir));
|
bool(const std::string& prefix, const std::string& output_dir));
|
||||||
};
|
};
|
||||||
|
|
||||||
class MockMediaPlaylistFactory : public MediaPlaylistFactory {
|
class MockMediaPlaylistFactory : public MediaPlaylistFactory {
|
||||||
public:
|
public:
|
||||||
MOCK_METHOD4(CreateMock,
|
MOCK_METHOD5(CreateMock,
|
||||||
MediaPlaylist*(MediaPlaylist::MediaPlaylistType type,
|
MediaPlaylist*(MediaPlaylist::MediaPlaylistType type,
|
||||||
|
double time_shift_buffer_depth,
|
||||||
const std::string& file_name,
|
const std::string& file_name,
|
||||||
const std::string& name,
|
const std::string& name,
|
||||||
const std::string& group_id));
|
const std::string& group_id));
|
||||||
|
|
||||||
std::unique_ptr<MediaPlaylist> Create(MediaPlaylist::MediaPlaylistType type,
|
std::unique_ptr<MediaPlaylist> Create(MediaPlaylist::MediaPlaylistType type,
|
||||||
|
double time_shift_buffer_depth,
|
||||||
const std::string& file_name,
|
const std::string& file_name,
|
||||||
const std::string& name,
|
const std::string& name,
|
||||||
const std::string& group_id) override {
|
const std::string& group_id) override {
|
||||||
return std::unique_ptr<MediaPlaylist>(
|
return std::unique_ptr<MediaPlaylist>(
|
||||||
CreateMock(type, file_name, name, group_id));
|
CreateMock(type, time_shift_buffer_depth, file_name, name, group_id));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const double kTestTimeShiftBufferDepth = 1800.0;
|
||||||
const char kTestPrefix[] = "http://testprefix.com/";
|
const char kTestPrefix[] = "http://testprefix.com/";
|
||||||
const char kAnyOutputDir[] = "anything/";
|
const char kAnyOutputDir[] = "anything/";
|
||||||
|
|
||||||
|
@ -75,7 +80,11 @@ const char kSampleAesProtectionScheme[] = "cbca";
|
||||||
class SimpleHlsNotifierTest : public ::testing::Test {
|
class SimpleHlsNotifierTest : public ::testing::Test {
|
||||||
protected:
|
protected:
|
||||||
SimpleHlsNotifierTest()
|
SimpleHlsNotifierTest()
|
||||||
: notifier_(HlsNotifier::HlsProfile::kOnDemandProfile,
|
: SimpleHlsNotifierTest(HlsProfile::kOnDemandProfile) {}
|
||||||
|
|
||||||
|
SimpleHlsNotifierTest(HlsProfile profile)
|
||||||
|
: notifier_(profile,
|
||||||
|
kTestTimeShiftBufferDepth,
|
||||||
kTestPrefix,
|
kTestPrefix,
|
||||||
kAnyOutputDir,
|
kAnyOutputDir,
|
||||||
kMasterPlaylistName),
|
kMasterPlaylistName),
|
||||||
|
@ -121,7 +130,7 @@ class SimpleHlsNotifierTest : public ::testing::Test {
|
||||||
*mock_master_playlist,
|
*mock_master_playlist,
|
||||||
AddMediaPlaylist(static_cast<MediaPlaylist*>(mock_media_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));
|
||||||
|
|
||||||
InjectMasterPlaylist(std::move(mock_master_playlist));
|
InjectMasterPlaylist(std::move(mock_master_playlist));
|
||||||
|
@ -153,7 +162,7 @@ TEST_F(SimpleHlsNotifierTest, RebaseSegmentTemplateRelative) {
|
||||||
|
|
||||||
// Pointer released by SimpleHlsNotifier.
|
// Pointer released by SimpleHlsNotifier.
|
||||||
MockMediaPlaylist* mock_media_playlist =
|
MockMediaPlaylist* mock_media_playlist =
|
||||||
new MockMediaPlaylist(kVodPlaylist, "", "", "");
|
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
|
||||||
EXPECT_CALL(*mock_master_playlist, AddMediaPlaylist(mock_media_playlist));
|
EXPECT_CALL(*mock_master_playlist, AddMediaPlaylist(mock_media_playlist));
|
||||||
|
|
||||||
EXPECT_CALL(
|
EXPECT_CALL(
|
||||||
|
@ -163,9 +172,10 @@ TEST_F(SimpleHlsNotifierTest, RebaseSegmentTemplateRelative) {
|
||||||
|
|
||||||
// Verify that the common prefix is stripped for AddSegment().
|
// Verify that the common prefix is stripped for AddSegment().
|
||||||
EXPECT_CALL(*mock_media_playlist,
|
EXPECT_CALL(*mock_media_playlist,
|
||||||
AddSegment("http://testprefix.com/path/to/media1.ts", _, _));
|
AddSegment("http://testprefix.com/path/to/media1.ts", _, _, _));
|
||||||
EXPECT_CALL(*factory, CreateMock(kVodPlaylist, StrEq("video_playlist.m3u8"),
|
EXPECT_CALL(*factory, CreateMock(kVodPlaylist, Eq(kTestTimeShiftBufferDepth),
|
||||||
StrEq("name"), StrEq("groupid")))
|
StrEq("video_playlist.m3u8"), StrEq("name"),
|
||||||
|
StrEq("groupid")))
|
||||||
.WillOnce(Return(mock_media_playlist));
|
.WillOnce(Return(mock_media_playlist));
|
||||||
|
|
||||||
InjectMasterPlaylist(std::move(mock_master_playlist));
|
InjectMasterPlaylist(std::move(mock_master_playlist));
|
||||||
|
@ -188,9 +198,9 @@ TEST_F(SimpleHlsNotifierTest,
|
||||||
RebaseAbsoluteSegmentTemplatePrefixAndOutputDirMatch) {
|
RebaseAbsoluteSegmentTemplatePrefixAndOutputDirMatch) {
|
||||||
const char kAbsoluteOutputDir[] = "/tmp/something/";
|
const char kAbsoluteOutputDir[] = "/tmp/something/";
|
||||||
// Require a separate instance to set kAbsoluteOutputDir.
|
// Require a separate instance to set kAbsoluteOutputDir.
|
||||||
SimpleHlsNotifier test_notifier(HlsNotifier::HlsProfile::kOnDemandProfile,
|
SimpleHlsNotifier test_notifier(HlsProfile::kOnDemandProfile,
|
||||||
kTestPrefix, kAbsoluteOutputDir,
|
kTestTimeShiftBufferDepth, kTestPrefix,
|
||||||
kMasterPlaylistName);
|
kAbsoluteOutputDir, kMasterPlaylistName);
|
||||||
|
|
||||||
std::unique_ptr<MockMasterPlaylist> mock_master_playlist(
|
std::unique_ptr<MockMasterPlaylist> mock_master_playlist(
|
||||||
new MockMasterPlaylist());
|
new MockMasterPlaylist());
|
||||||
|
@ -199,7 +209,7 @@ TEST_F(SimpleHlsNotifierTest,
|
||||||
|
|
||||||
// Pointer released by SimpleHlsNotifier.
|
// Pointer released by SimpleHlsNotifier.
|
||||||
MockMediaPlaylist* mock_media_playlist =
|
MockMediaPlaylist* mock_media_playlist =
|
||||||
new MockMediaPlaylist(kVodPlaylist, "", "", "");
|
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
|
||||||
EXPECT_CALL(*mock_master_playlist, AddMediaPlaylist(mock_media_playlist));
|
EXPECT_CALL(*mock_master_playlist, AddMediaPlaylist(mock_media_playlist));
|
||||||
|
|
||||||
EXPECT_CALL(*mock_media_playlist,
|
EXPECT_CALL(*mock_media_playlist,
|
||||||
|
@ -208,9 +218,10 @@ TEST_F(SimpleHlsNotifierTest,
|
||||||
|
|
||||||
// Verify that the output_dir is stripped and then kTestPrefix is prepended.
|
// Verify that the output_dir is stripped and then kTestPrefix is prepended.
|
||||||
EXPECT_CALL(*mock_media_playlist,
|
EXPECT_CALL(*mock_media_playlist,
|
||||||
AddSegment("http://testprefix.com/media1.ts", _, _));
|
AddSegment("http://testprefix.com/media1.ts", _, _, _));
|
||||||
EXPECT_CALL(*factory, CreateMock(kVodPlaylist, StrEq("video_playlist.m3u8"),
|
EXPECT_CALL(*factory, CreateMock(kVodPlaylist, Eq(kTestTimeShiftBufferDepth),
|
||||||
StrEq("name"), StrEq("groupid")))
|
StrEq("video_playlist.m3u8"), StrEq("name"),
|
||||||
|
StrEq("groupid")))
|
||||||
.WillOnce(Return(mock_media_playlist));
|
.WillOnce(Return(mock_media_playlist));
|
||||||
|
|
||||||
InjectMasterPlaylist(std::move(mock_master_playlist), &test_notifier);
|
InjectMasterPlaylist(std::move(mock_master_playlist), &test_notifier);
|
||||||
|
@ -232,9 +243,9 @@ TEST_F(SimpleHlsNotifierTest,
|
||||||
TEST_F(SimpleHlsNotifierTest,
|
TEST_F(SimpleHlsNotifierTest,
|
||||||
RebaseAbsoluteSegmentTemplateCompletelyDifferentDirectory) {
|
RebaseAbsoluteSegmentTemplateCompletelyDifferentDirectory) {
|
||||||
const char kAbsoluteOutputDir[] = "/tmp/something/";
|
const char kAbsoluteOutputDir[] = "/tmp/something/";
|
||||||
SimpleHlsNotifier test_notifier(HlsNotifier::HlsProfile::kOnDemandProfile,
|
SimpleHlsNotifier test_notifier(HlsProfile::kOnDemandProfile,
|
||||||
kTestPrefix, kAbsoluteOutputDir,
|
kTestTimeShiftBufferDepth, kTestPrefix,
|
||||||
kMasterPlaylistName);
|
kAbsoluteOutputDir, kMasterPlaylistName);
|
||||||
|
|
||||||
std::unique_ptr<MockMasterPlaylist> mock_master_playlist(
|
std::unique_ptr<MockMasterPlaylist> mock_master_playlist(
|
||||||
new MockMasterPlaylist());
|
new MockMasterPlaylist());
|
||||||
|
@ -243,18 +254,19 @@ TEST_F(SimpleHlsNotifierTest,
|
||||||
|
|
||||||
// Pointer released by SimpleHlsNotifier.
|
// Pointer released by SimpleHlsNotifier.
|
||||||
MockMediaPlaylist* mock_media_playlist =
|
MockMediaPlaylist* mock_media_playlist =
|
||||||
new MockMediaPlaylist(kVodPlaylist, "", "", "");
|
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
|
||||||
EXPECT_CALL(*mock_master_playlist, AddMediaPlaylist(mock_media_playlist));
|
EXPECT_CALL(*mock_master_playlist, AddMediaPlaylist(mock_media_playlist));
|
||||||
|
|
||||||
EXPECT_CALL(
|
EXPECT_CALL(
|
||||||
*mock_media_playlist,
|
*mock_media_playlist,
|
||||||
SetMediaInfo(SegmentTemplateEq("/var/somewhereelse/media$Number$.ts")))
|
SetMediaInfo(SegmentTemplateEq("/var/somewhereelse/media$Number$.ts")))
|
||||||
.WillOnce(Return(true));
|
.WillOnce(Return(true));
|
||||||
EXPECT_CALL(
|
EXPECT_CALL(*mock_media_playlist,
|
||||||
*mock_media_playlist,
|
AddSegment("http://testprefix.com//var/somewhereelse/media1.ts",
|
||||||
AddSegment("http://testprefix.com//var/somewhereelse/media1.ts", _, _));
|
_, _, _));
|
||||||
EXPECT_CALL(*factory, CreateMock(kVodPlaylist, StrEq("video_playlist.m3u8"),
|
EXPECT_CALL(*factory, CreateMock(kVodPlaylist, Eq(kTestTimeShiftBufferDepth),
|
||||||
StrEq("name"), StrEq("groupid")))
|
StrEq("video_playlist.m3u8"), StrEq("name"),
|
||||||
|
StrEq("groupid")))
|
||||||
.WillOnce(Return(mock_media_playlist));
|
.WillOnce(Return(mock_media_playlist));
|
||||||
|
|
||||||
InjectMasterPlaylist(std::move(mock_master_playlist), &test_notifier);
|
InjectMasterPlaylist(std::move(mock_master_playlist), &test_notifier);
|
||||||
|
@ -270,6 +282,17 @@ TEST_F(SimpleHlsNotifierTest,
|
||||||
kAnyStartTime, kAnyDuration, kAnySize));
|
kAnyStartTime, kAnyDuration, kAnySize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(SimpleHlsNotifierTest, Flush) {
|
||||||
|
std::unique_ptr<MockMasterPlaylist> 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());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(SimpleHlsNotifierTest, NotifyNewStream) {
|
TEST_F(SimpleHlsNotifierTest, NotifyNewStream) {
|
||||||
std::unique_ptr<MockMasterPlaylist> mock_master_playlist(
|
std::unique_ptr<MockMasterPlaylist> mock_master_playlist(
|
||||||
new MockMasterPlaylist());
|
new MockMasterPlaylist());
|
||||||
|
@ -278,12 +301,13 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewStream) {
|
||||||
|
|
||||||
// Pointer released by SimpleHlsNotifier.
|
// Pointer released by SimpleHlsNotifier.
|
||||||
MockMediaPlaylist* mock_media_playlist =
|
MockMediaPlaylist* mock_media_playlist =
|
||||||
new MockMediaPlaylist(kVodPlaylist, "", "", "");
|
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
|
||||||
EXPECT_CALL(*mock_master_playlist, AddMediaPlaylist(mock_media_playlist));
|
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, StrEq("video_playlist.m3u8"),
|
EXPECT_CALL(*factory, CreateMock(kVodPlaylist, Eq(kTestTimeShiftBufferDepth),
|
||||||
StrEq("name"), StrEq("groupid")))
|
StrEq("video_playlist.m3u8"), StrEq("name"),
|
||||||
|
StrEq("groupid")))
|
||||||
.WillOnce(Return(mock_media_playlist));
|
.WillOnce(Return(mock_media_playlist));
|
||||||
|
|
||||||
InjectMasterPlaylist(std::move(mock_master_playlist));
|
InjectMasterPlaylist(std::move(mock_master_playlist));
|
||||||
|
@ -304,13 +328,13 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewSegment) {
|
||||||
|
|
||||||
// Pointer released by SimpleHlsNotifier.
|
// Pointer released by SimpleHlsNotifier.
|
||||||
MockMediaPlaylist* mock_media_playlist =
|
MockMediaPlaylist* mock_media_playlist =
|
||||||
new MockMediaPlaylist(kVodPlaylist, "", "", "");
|
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
|
||||||
|
|
||||||
EXPECT_CALL(
|
EXPECT_CALL(
|
||||||
*mock_master_playlist,
|
*mock_master_playlist,
|
||||||
AddMediaPlaylist(static_cast<MediaPlaylist*>(mock_media_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));
|
||||||
|
|
||||||
const uint64_t kStartTime = 1328;
|
const uint64_t kStartTime = 1328;
|
||||||
|
@ -318,8 +342,15 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewSegment) {
|
||||||
const uint64_t kSize = 6595840;
|
const uint64_t kSize = 6595840;
|
||||||
const std::string segment_name = "segmentname";
|
const std::string segment_name = "segmentname";
|
||||||
EXPECT_CALL(*mock_media_playlist,
|
EXPECT_CALL(*mock_media_playlist,
|
||||||
AddSegment(StrEq(kTestPrefix + segment_name), kDuration, kSize));
|
AddSegment(StrEq(kTestPrefix + segment_name), kStartTime,
|
||||||
|
kDuration, kSize));
|
||||||
|
|
||||||
|
const double kLongestSegmentDuration = 11.3;
|
||||||
|
const uint32_t kTargetDuration = 12; // ceil(kLongestSegmentDuration).
|
||||||
|
EXPECT_CALL(*mock_media_playlist, GetLongestSegmentDuration())
|
||||||
|
.WillOnce(Return(kLongestSegmentDuration));
|
||||||
|
|
||||||
|
MockMasterPlaylist* mock_master_playlist_ptr = mock_master_playlist.get();
|
||||||
InjectMasterPlaylist(std::move(mock_master_playlist));
|
InjectMasterPlaylist(std::move(mock_master_playlist));
|
||||||
InjectMediaPlaylistFactory(std::move(factory));
|
InjectMediaPlaylistFactory(std::move(factory));
|
||||||
EXPECT_TRUE(notifier_.Init());
|
EXPECT_TRUE(notifier_.Init());
|
||||||
|
@ -330,6 +361,22 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewSegment) {
|
||||||
|
|
||||||
EXPECT_TRUE(notifier_.NotifyNewSegment(stream_id, segment_name, kStartTime,
|
EXPECT_TRUE(notifier_.NotifyNewSegment(stream_id, segment_name, kStartTime,
|
||||||
kDuration, kSize));
|
kDuration, kSize));
|
||||||
|
|
||||||
|
Mock::VerifyAndClearExpectations(mock_master_playlist_ptr);
|
||||||
|
Mock::VerifyAndClearExpectations(mock_media_playlist);
|
||||||
|
|
||||||
|
EXPECT_CALL(*mock_master_playlist_ptr,
|
||||||
|
WriteMasterPlaylist(StrEq(kTestPrefix), StrEq(kAnyOutputDir)))
|
||||||
|
.WillOnce(Return(true));
|
||||||
|
EXPECT_CALL(*mock_media_playlist, SetTargetDuration(kTargetDuration))
|
||||||
|
.Times(1);
|
||||||
|
EXPECT_CALL(*mock_media_playlist,
|
||||||
|
WriteToFile(StrEq(
|
||||||
|
base::FilePath::FromUTF8Unsafe(kAnyOutputDir)
|
||||||
|
.Append(base::FilePath::FromUTF8Unsafe("playlist.m3u8"))
|
||||||
|
.AsUTF8Unsafe())))
|
||||||
|
.WillOnce(Return(true));
|
||||||
|
EXPECT_TRUE(notifier_.Flush());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SimpleHlsNotifierTest, NotifyNewSegmentWithoutStreamsRegistered) {
|
TEST_F(SimpleHlsNotifierTest, NotifyNewSegmentWithoutStreamsRegistered) {
|
||||||
|
@ -340,7 +387,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewSegmentWithoutStreamsRegistered) {
|
||||||
TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevine) {
|
TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevine) {
|
||||||
// Pointer released by SimpleHlsNotifier.
|
// Pointer released by SimpleHlsNotifier.
|
||||||
MockMediaPlaylist* mock_media_playlist =
|
MockMediaPlaylist* mock_media_playlist =
|
||||||
new MockMediaPlaylist(kVodPlaylist, "", "", "");
|
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
|
||||||
const uint32_t stream_id =
|
const uint32_t stream_id =
|
||||||
SetupStream(kSampleAesProtectionScheme, mock_media_playlist);
|
SetupStream(kSampleAesProtectionScheme, mock_media_playlist);
|
||||||
|
|
||||||
|
@ -405,7 +452,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevine) {
|
||||||
TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevineNoKeyidsInPssh) {
|
TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevineNoKeyidsInPssh) {
|
||||||
// Pointer released by SimpleHlsNotifier.
|
// Pointer released by SimpleHlsNotifier.
|
||||||
MockMediaPlaylist* mock_media_playlist =
|
MockMediaPlaylist* mock_media_playlist =
|
||||||
new MockMediaPlaylist(kVodPlaylist, "", "", "");
|
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
|
||||||
const uint32_t stream_id =
|
const uint32_t stream_id =
|
||||||
SetupStream(kSampleAesProtectionScheme, mock_media_playlist);
|
SetupStream(kSampleAesProtectionScheme, mock_media_playlist);
|
||||||
|
|
||||||
|
@ -467,7 +514,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevineNoKeyidsInPssh) {
|
||||||
TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateFixedKey) {
|
TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateFixedKey) {
|
||||||
// Pointer released by SimpleHlsNotifier.
|
// Pointer released by SimpleHlsNotifier.
|
||||||
MockMediaPlaylist* mock_media_playlist =
|
MockMediaPlaylist* mock_media_playlist =
|
||||||
new MockMediaPlaylist(kVodPlaylist, "", "", "");
|
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
|
||||||
const uint32_t stream_id =
|
const uint32_t stream_id =
|
||||||
SetupStream(kSampleAesProtectionScheme, mock_media_playlist);
|
SetupStream(kSampleAesProtectionScheme, mock_media_playlist);
|
||||||
|
|
||||||
|
@ -496,7 +543,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateFixedKey) {
|
||||||
TEST_F(SimpleHlsNotifierTest, WidevineMultipleKeyIdsNoContentIdInPssh) {
|
TEST_F(SimpleHlsNotifierTest, WidevineMultipleKeyIdsNoContentIdInPssh) {
|
||||||
// Pointer released by SimpleHlsNotifier.
|
// Pointer released by SimpleHlsNotifier.
|
||||||
MockMediaPlaylist* mock_media_playlist =
|
MockMediaPlaylist* mock_media_playlist =
|
||||||
new MockMediaPlaylist(kVodPlaylist, "", "", "");
|
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
|
||||||
uint32_t stream_id =
|
uint32_t stream_id =
|
||||||
SetupStream(kSampleAesProtectionScheme, mock_media_playlist);
|
SetupStream(kSampleAesProtectionScheme, mock_media_playlist);
|
||||||
|
|
||||||
|
@ -578,7 +625,7 @@ TEST_F(SimpleHlsNotifierTest, WidevineMultipleKeyIdsNoContentIdInPssh) {
|
||||||
TEST_F(SimpleHlsNotifierTest, EncryptionScheme) {
|
TEST_F(SimpleHlsNotifierTest, EncryptionScheme) {
|
||||||
// Pointer released by SimpleHlsNotifier.
|
// Pointer released by SimpleHlsNotifier.
|
||||||
MockMediaPlaylist* mock_media_playlist =
|
MockMediaPlaylist* mock_media_playlist =
|
||||||
new MockMediaPlaylist(kVodPlaylist, "", "", "");
|
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
|
||||||
const uint32_t stream_id =
|
const uint32_t stream_id =
|
||||||
SetupStream(kCencProtectionScheme, mock_media_playlist);
|
SetupStream(kCencProtectionScheme, mock_media_playlist);
|
||||||
|
|
||||||
|
@ -605,7 +652,7 @@ TEST_F(SimpleHlsNotifierTest, EncryptionScheme) {
|
||||||
TEST_F(SimpleHlsNotifierTest, WidevineCencEncryptionScheme) {
|
TEST_F(SimpleHlsNotifierTest, WidevineCencEncryptionScheme) {
|
||||||
// Pointer released by SimpleHlsNotifier.
|
// Pointer released by SimpleHlsNotifier.
|
||||||
MockMediaPlaylist* mock_media_playlist =
|
MockMediaPlaylist* mock_media_playlist =
|
||||||
new MockMediaPlaylist(kVodPlaylist, "", "", "");
|
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
|
||||||
const uint32_t stream_id =
|
const uint32_t stream_id =
|
||||||
SetupStream(kCencProtectionScheme, mock_media_playlist);
|
SetupStream(kCencProtectionScheme, mock_media_playlist);
|
||||||
|
|
||||||
|
@ -654,7 +701,7 @@ TEST_F(SimpleHlsNotifierTest, WidevineCencEncryptionScheme) {
|
||||||
TEST_F(SimpleHlsNotifierTest, WidevineNotifyEncryptionUpdateEmptyIv) {
|
TEST_F(SimpleHlsNotifierTest, WidevineNotifyEncryptionUpdateEmptyIv) {
|
||||||
// Pointer released by SimpleHlsNotifier.
|
// Pointer released by SimpleHlsNotifier.
|
||||||
MockMediaPlaylist* mock_media_playlist =
|
MockMediaPlaylist* mock_media_playlist =
|
||||||
new MockMediaPlaylist(kVodPlaylist, "", "", "");
|
new MockMediaPlaylist(kVodPlaylist, "playlist.m3u8", "", "");
|
||||||
const uint32_t stream_id =
|
const uint32_t stream_id =
|
||||||
SetupStream(kSampleAesProtectionScheme, mock_media_playlist);
|
SetupStream(kSampleAesProtectionScheme, mock_media_playlist);
|
||||||
|
|
||||||
|
@ -727,19 +774,172 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWithoutStreamsRegistered) {
|
||||||
pssh_data));
|
pssh_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SimpleHlsNotifierTest, Flush) {
|
class LiveOrEventSimpleHlsNotifierTest
|
||||||
|
: public SimpleHlsNotifierTest,
|
||||||
|
public ::testing::WithParamInterface<HlsProfile> {
|
||||||
|
protected:
|
||||||
|
LiveOrEventSimpleHlsNotifierTest() : SimpleHlsNotifierTest(GetParam()) {
|
||||||
|
switch (GetParam()) {
|
||||||
|
case HlsProfile::kLiveProfile:
|
||||||
|
expected_playlist_type_ = MediaPlaylist::MediaPlaylistType::kLive;
|
||||||
|
break;
|
||||||
|
case HlsProfile::kOnDemandProfile:
|
||||||
|
expected_playlist_type_ = MediaPlaylist::MediaPlaylistType::kVod;
|
||||||
|
break;
|
||||||
|
case HlsProfile::kEventProfile:
|
||||||
|
expected_playlist_type_ = MediaPlaylist::MediaPlaylistType::kEvent;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaPlaylist::MediaPlaylistType expected_playlist_type_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegment) {
|
||||||
std::unique_ptr<MockMasterPlaylist> mock_master_playlist(
|
std::unique_ptr<MockMasterPlaylist> mock_master_playlist(
|
||||||
new MockMasterPlaylist());
|
new MockMasterPlaylist());
|
||||||
EXPECT_CALL(*mock_master_playlist,
|
|
||||||
WriteAllPlaylists(StrEq(kTestPrefix), StrEq(kAnyOutputDir)))
|
|
||||||
.WillOnce(Return(true));
|
|
||||||
std::unique_ptr<MockMediaPlaylistFactory> factory(
|
std::unique_ptr<MockMediaPlaylistFactory> factory(
|
||||||
new MockMediaPlaylistFactory());
|
new MockMediaPlaylistFactory());
|
||||||
|
|
||||||
|
// Pointer released by SimpleHlsNotifier.
|
||||||
|
MockMediaPlaylist* mock_media_playlist =
|
||||||
|
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(*factory, CreateMock(expected_playlist_type_, _, _, _, _))
|
||||||
|
.WillOnce(Return(mock_media_playlist));
|
||||||
|
|
||||||
|
const uint64_t kStartTime = 1328;
|
||||||
|
const uint64_t kDuration = 398407;
|
||||||
|
const uint64_t kSize = 6595840;
|
||||||
|
const std::string segment_name = "segmentname";
|
||||||
|
EXPECT_CALL(*mock_media_playlist,
|
||||||
|
AddSegment(StrEq(kTestPrefix + segment_name), kStartTime,
|
||||||
|
kDuration, kSize));
|
||||||
|
|
||||||
|
const double kLongestSegmentDuration = 11.3;
|
||||||
|
const uint32_t kTargetDuration = 12; // ceil(kLongestSegmentDuration).
|
||||||
|
EXPECT_CALL(*mock_media_playlist, GetLongestSegmentDuration())
|
||||||
|
.WillOnce(Return(kLongestSegmentDuration));
|
||||||
|
|
||||||
|
EXPECT_CALL(*mock_master_playlist,
|
||||||
|
WriteMasterPlaylist(StrEq(kTestPrefix), StrEq(kAnyOutputDir)))
|
||||||
|
.WillOnce(Return(true));
|
||||||
|
EXPECT_CALL(*mock_media_playlist, SetTargetDuration(kTargetDuration))
|
||||||
|
.Times(1);
|
||||||
|
EXPECT_CALL(*mock_media_playlist,
|
||||||
|
WriteToFile(StrEq(
|
||||||
|
base::FilePath::FromUTF8Unsafe(kAnyOutputDir)
|
||||||
|
.Append(base::FilePath::FromUTF8Unsafe("playlist.m3u8"))
|
||||||
|
.AsUTF8Unsafe())))
|
||||||
|
.WillOnce(Return(true));
|
||||||
|
|
||||||
InjectMasterPlaylist(std::move(mock_master_playlist));
|
InjectMasterPlaylist(std::move(mock_master_playlist));
|
||||||
|
InjectMediaPlaylistFactory(std::move(factory));
|
||||||
EXPECT_TRUE(notifier_.Init());
|
EXPECT_TRUE(notifier_.Init());
|
||||||
EXPECT_TRUE(notifier_.Flush());
|
MediaInfo media_info;
|
||||||
|
uint32_t 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_P(LiveOrEventSimpleHlsNotifierTest, NotifyNewSegmentsWithMultipleStreams) {
|
||||||
|
const uint64_t kStartTime = 1328;
|
||||||
|
const uint64_t kDuration = 398407;
|
||||||
|
const uint64_t kSize = 6595840;
|
||||||
|
|
||||||
|
InSequence in_sequence;
|
||||||
|
|
||||||
|
std::unique_ptr<MockMasterPlaylist> mock_master_playlist(
|
||||||
|
new MockMasterPlaylist());
|
||||||
|
std::unique_ptr<MockMediaPlaylistFactory> factory(
|
||||||
|
new MockMediaPlaylistFactory());
|
||||||
|
|
||||||
|
// Pointer released by SimpleHlsNotifier.
|
||||||
|
MockMediaPlaylist* mock_media_playlist1 =
|
||||||
|
new MockMediaPlaylist(expected_playlist_type_, "playlist1.m3u8", "", "");
|
||||||
|
MockMediaPlaylist* mock_media_playlist2 =
|
||||||
|
new MockMediaPlaylist(expected_playlist_type_, "playlist2.m3u8", "", "");
|
||||||
|
|
||||||
|
EXPECT_CALL(*factory, CreateMock(_, _, StrEq("playlist1.m3u8"), _, _))
|
||||||
|
.WillOnce(Return(mock_media_playlist1));
|
||||||
|
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"), _, _))
|
||||||
|
.WillOnce(Return(mock_media_playlist2));
|
||||||
|
EXPECT_CALL(*mock_media_playlist2, SetMediaInfo(_)).WillOnce(Return(true));
|
||||||
|
EXPECT_CALL(
|
||||||
|
*mock_master_playlist,
|
||||||
|
AddMediaPlaylist(static_cast<MediaPlaylist*>(mock_media_playlist2)));
|
||||||
|
|
||||||
|
MockMasterPlaylist* mock_master_playlist_ptr = mock_master_playlist.get();
|
||||||
|
InjectMasterPlaylist(std::move(mock_master_playlist));
|
||||||
|
InjectMediaPlaylistFactory(std::move(factory));
|
||||||
|
EXPECT_TRUE(notifier_.Init());
|
||||||
|
|
||||||
|
MediaInfo media_info;
|
||||||
|
uint32_t stream_id1;
|
||||||
|
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",
|
||||||
|
"groupid", &stream_id2));
|
||||||
|
|
||||||
|
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())
|
||||||
|
.WillOnce(Return(kLongestSegmentDuration));
|
||||||
|
|
||||||
|
EXPECT_CALL(*mock_master_playlist_ptr, WriteMasterPlaylist(_, _))
|
||||||
|
.WillOnce(Return(true));
|
||||||
|
// SetTargetDuration and update all playlists as target duration is updated.
|
||||||
|
EXPECT_CALL(*mock_media_playlist1, SetTargetDuration(kTargetDuration))
|
||||||
|
.Times(1);
|
||||||
|
EXPECT_CALL(*mock_media_playlist1,
|
||||||
|
WriteToFile(StrEq(
|
||||||
|
base::FilePath::FromUTF8Unsafe(kAnyOutputDir)
|
||||||
|
.Append(base::FilePath::FromUTF8Unsafe("playlist1.m3u8"))
|
||||||
|
.AsUTF8Unsafe())))
|
||||||
|
.WillOnce(Return(true));
|
||||||
|
EXPECT_CALL(*mock_media_playlist2, SetTargetDuration(kTargetDuration))
|
||||||
|
.Times(1);
|
||||||
|
EXPECT_CALL(*mock_media_playlist2,
|
||||||
|
WriteToFile(StrEq(
|
||||||
|
base::FilePath::FromUTF8Unsafe(kAnyOutputDir)
|
||||||
|
.Append(base::FilePath::FromUTF8Unsafe("playlist2.m3u8"))
|
||||||
|
.AsUTF8Unsafe())))
|
||||||
|
.WillOnce(Return(true));
|
||||||
|
EXPECT_TRUE(notifier_.NotifyNewSegment(stream_id1, "segment_name", kStartTime,
|
||||||
|
kDuration, kSize));
|
||||||
|
|
||||||
|
EXPECT_CALL(*mock_media_playlist2, AddSegment(_, _, _, _)).Times(1);
|
||||||
|
EXPECT_CALL(*mock_media_playlist2, GetLongestSegmentDuration())
|
||||||
|
.WillOnce(Return(kLongestSegmentDuration));
|
||||||
|
EXPECT_CALL(*mock_master_playlist_ptr, WriteMasterPlaylist(_, _))
|
||||||
|
.WillOnce(Return(true));
|
||||||
|
// Not updating other playlists as target duration does not change.
|
||||||
|
EXPECT_CALL(*mock_media_playlist2,
|
||||||
|
WriteToFile(StrEq(
|
||||||
|
base::FilePath::FromUTF8Unsafe(kAnyOutputDir)
|
||||||
|
.Append(base::FilePath::FromUTF8Unsafe("playlist2.m3u8"))
|
||||||
|
.AsUTF8Unsafe())))
|
||||||
|
.WillOnce(Return(true));
|
||||||
|
EXPECT_TRUE(notifier_.NotifyNewSegment(stream_id2, "segment_name", kStartTime,
|
||||||
|
kDuration, kSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
INSTANTIATE_TEST_CASE_P(PlaylistTypes,
|
||||||
|
LiveOrEventSimpleHlsNotifierTest,
|
||||||
|
::testing::Values(HlsProfile::kLiveProfile,
|
||||||
|
HlsProfile::kEventProfile));
|
||||||
} // namespace hls
|
} // namespace hls
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -121,6 +121,7 @@ void HlsNotifyMuxerListener::OnMediaEnd(bool has_init_range,
|
||||||
uint64_t index_range_end,
|
uint64_t index_range_end,
|
||||||
float duration_seconds,
|
float duration_seconds,
|
||||||
uint64_t file_size) {
|
uint64_t file_size) {
|
||||||
|
// TODO(kqyang): Should we just Flush here to avoid calling Flush explicitly?
|
||||||
// Don't flush the notifier here. Flushing here would write all the playlists
|
// Don't flush the notifier here. Flushing here would write all the playlists
|
||||||
// before all Media Playlists are read. Which could cause problems
|
// before all Media Playlists are read. Which could cause problems
|
||||||
// setting the correct EXT-X-TARGETDURATION.
|
// setting the correct EXT-X-TARGETDURATION.
|
||||||
|
|
|
@ -24,8 +24,7 @@ namespace {
|
||||||
|
|
||||||
class MockHlsNotifier : public hls::HlsNotifier {
|
class MockHlsNotifier : public hls::HlsNotifier {
|
||||||
public:
|
public:
|
||||||
MockHlsNotifier()
|
MockHlsNotifier() : HlsNotifier(hls::HlsProfile::kOnDemandProfile) {}
|
||||||
: HlsNotifier(hls::HlsNotifier::HlsProfile::kOnDemandProfile) {}
|
|
||||||
|
|
||||||
MOCK_METHOD0(Init, bool());
|
MOCK_METHOD0(Init, bool());
|
||||||
MOCK_METHOD5(NotifyNewStream,
|
MOCK_METHOD5(NotifyNewStream,
|
||||||
|
|
|
@ -17,18 +17,25 @@
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
#define ASSERT_FILE_EQ(file_name, array) \
|
#define ASSERT_FILE_EQ(file_name, array) \
|
||||||
do { \
|
do { \
|
||||||
std::string temp_data; \
|
std::string temp_data; \
|
||||||
ASSERT_TRUE(File::ReadFileToString((file_name), &temp_data)); \
|
ASSERT_TRUE(media::File::ReadFileToString((file_name), &temp_data)); \
|
||||||
const char* array_ptr = reinterpret_cast<const char*>(array); \
|
const char* array_ptr = reinterpret_cast<const char*>(array); \
|
||||||
ASSERT_EQ(std::string(array_ptr, arraysize(array)), temp_data); \
|
ASSERT_EQ(std::string(array_ptr, arraysize(array)), temp_data); \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
#define ASSERT_FILE_STREQ(file_name, str) \
|
||||||
|
do { \
|
||||||
|
std::string temp_data; \
|
||||||
|
ASSERT_TRUE(media::File::ReadFileToString((file_name), &temp_data)); \
|
||||||
|
ASSERT_EQ(str, temp_data); \
|
||||||
} while (false)
|
} while (false)
|
||||||
|
|
||||||
#define ASSERT_FILE_ENDS_WITH(file_name, array) \
|
#define ASSERT_FILE_ENDS_WITH(file_name, array) \
|
||||||
do { \
|
do { \
|
||||||
std::string temp_data; \
|
std::string temp_data; \
|
||||||
ASSERT_TRUE(File::ReadFileToString((file_name), &temp_data)); \
|
ASSERT_TRUE(media::File::ReadFileToString((file_name), &temp_data)); \
|
||||||
EXPECT_THAT(temp_data, \
|
EXPECT_THAT(temp_data, \
|
||||||
::testing::EndsWith(std::string( \
|
::testing::EndsWith(std::string( \
|
||||||
reinterpret_cast<const char*>(array), sizeof(array)))); \
|
reinterpret_cast<const char*>(array), sizeof(array)))); \
|
||||||
|
|
|
@ -529,8 +529,7 @@ class Representation {
|
||||||
uint64_t duration,
|
uint64_t duration,
|
||||||
uint64_t size) const;
|
uint64_t size) const;
|
||||||
|
|
||||||
// Remove elements from |segment_infos_| if
|
// Remove elements from |segment_infos_| for dynamic live profile. Increments
|
||||||
// mpd_options_.time_shift_buffer_depth is specified. Increments
|
|
||||||
// |start_number_| by the number of segments removed.
|
// |start_number_| by the number of segments removed.
|
||||||
void SlideWindow();
|
void SlideWindow();
|
||||||
|
|
||||||
|
|
|
@ -207,6 +207,20 @@ bool ValidateParams(const PackagingParams& packaging_params,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hls::HlsProfile GetHlsNotifierProfile(HlsPlaylistType playlist_type) {
|
||||||
|
switch (playlist_type) {
|
||||||
|
case HlsPlaylistType::kVod:
|
||||||
|
return hls::HlsProfile::kOnDemandProfile;
|
||||||
|
case HlsPlaylistType::kEvent:
|
||||||
|
return hls::HlsProfile::kEventProfile;
|
||||||
|
case HlsPlaylistType::kLive:
|
||||||
|
return hls::HlsProfile::kLiveProfile;
|
||||||
|
}
|
||||||
|
LOG(WARNING) << "Unrecognized playlist type ("
|
||||||
|
<< static_cast<int>(playlist_type) << "). Assuming VOD.";
|
||||||
|
return hls::HlsProfile::kOnDemandProfile;
|
||||||
|
}
|
||||||
|
|
||||||
class StreamDescriptorCompareFn {
|
class StreamDescriptorCompareFn {
|
||||||
public:
|
public:
|
||||||
bool operator()(const StreamDescriptor& a, const StreamDescriptor& b) {
|
bool operator()(const StreamDescriptor& a, const StreamDescriptor& b) {
|
||||||
|
@ -635,7 +649,8 @@ Status Packager::Initialize(
|
||||||
base::FilePath master_playlist_name = master_playlist_path.BaseName();
|
base::FilePath master_playlist_name = master_playlist_path.BaseName();
|
||||||
|
|
||||||
internal->hls_notifier.reset(new hls::SimpleHlsNotifier(
|
internal->hls_notifier.reset(new hls::SimpleHlsNotifier(
|
||||||
hls::HlsNotifier::HlsProfile::kOnDemandProfile, hls_params.base_url,
|
media::GetHlsNotifierProfile(hls_params.playlist_type),
|
||||||
|
hls_params.time_shift_buffer_depth, hls_params.base_url,
|
||||||
master_playlist_path.DirName().AsEndingWithSeparator().AsUTF8Unsafe(),
|
master_playlist_path.DirName().AsEndingWithSeparator().AsUTF8Unsafe(),
|
||||||
master_playlist_name.AsUTF8Unsafe()));
|
master_playlist_name.AsUTF8Unsafe()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,13 +118,26 @@ struct MpdParams {
|
||||||
bool generate_dash_if_iop_compliant_mpd = true;
|
bool generate_dash_if_iop_compliant_mpd = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Defines the EXT-X-PLAYLIST-TYPE in the HLS specification. For
|
||||||
|
/// HlsPlaylistType of kLive, EXT-X-PLAYLIST-TYPE tag is omitted.
|
||||||
|
enum class HlsPlaylistType {
|
||||||
|
kVod,
|
||||||
|
kEvent,
|
||||||
|
kLive,
|
||||||
|
};
|
||||||
|
|
||||||
/// HLS related parameters.
|
/// HLS related parameters.
|
||||||
struct HlsParams {
|
struct HlsParams {
|
||||||
|
/// HLS playlist type. See HLS specification for details.
|
||||||
|
HlsPlaylistType playlist_type = HlsPlaylistType::kVod;
|
||||||
/// HLS master playlist output path.
|
/// HLS master playlist output path.
|
||||||
std::string master_playlist_output;
|
std::string master_playlist_output;
|
||||||
/// The base URL for the Media Playlists and media files listed in the
|
/// The base URL for the Media Playlists and media files listed in the
|
||||||
/// playlists. This is the prefix for the files.
|
/// playlists. This is the prefix for the files.
|
||||||
std::string base_url;
|
std::string base_url;
|
||||||
|
/// Defines the live window, or the guaranteed duration of the time shifting
|
||||||
|
/// buffer for 'live' playlists.
|
||||||
|
double time_shift_buffer_depth = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Encryption / decryption key providers.
|
/// Encryption / decryption key providers.
|
||||||
|
|
Loading…
Reference in New Issue