HlsNotifier and implementation
- HlsNotifier interface, similar to MpdNotifier. - SimpleHlsNotifier class can generate playlists. It can also handle encrypted media with Widevine encryption. Issue #85 Change-Id: I9305ddf42796370d3e5df360633c80997c909365
This commit is contained in:
parent
c6445b749c
commit
a6646841b7
|
@ -0,0 +1,88 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
#ifndef PACKAGER_HLS_BASE_HLS_NOTIFIER_H_
|
||||
#define PACKAGER_HLS_BASE_HLS_NOTIFIER_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "packager/mpd/base/media_info.pb.h"
|
||||
|
||||
namespace edash_packager {
|
||||
namespace hls {
|
||||
|
||||
// TODO(rkuroiwa): Consider merging this with MpdNotifier.
|
||||
class HlsNotifier {
|
||||
public:
|
||||
enum class HlsProfile {
|
||||
kOnDemandProfile,
|
||||
kLiveProfile,
|
||||
};
|
||||
|
||||
explicit HlsNotifier(HlsProfile profile) : profile_(profile) {}
|
||||
virtual ~HlsNotifier() {}
|
||||
|
||||
/// Intialize the notifier.
|
||||
/// @return true on sucess, false otherwise.
|
||||
virtual bool Init() = 0;
|
||||
|
||||
/// @param media_info specifies the stream.
|
||||
/// @param playlist_name is the name of the playlist that this stream should
|
||||
/// go.
|
||||
/// @param stream_name is the name of this stream.
|
||||
/// @param group_id is the group ID for this stream.
|
||||
/// @param stream_id is set to a value so that it can be used to call the
|
||||
/// other methods. If this returns false, the stream_id may be set to
|
||||
/// an invalid value.
|
||||
/// @return true on sucess, false otherwise.
|
||||
virtual bool NotifyNewStream(const MediaInfo& media_info,
|
||||
const std::string& playlist_name,
|
||||
const std::string& stream_name,
|
||||
const std::string& group_id,
|
||||
uint32_t* stream_id) = 0;
|
||||
|
||||
// |stream_id| is the value set by NotifyNewStream().
|
||||
// |segment_name| is the name of the new segment.
|
||||
// |start_time| is the start time of the segment in terms of timescale passed
|
||||
// in |media_info|.
|
||||
// |duration| is also in terms of timescale.
|
||||
// |size| is the size in bytes.
|
||||
virtual bool NotifyNewSegment(uint32_t stream_id,
|
||||
const std::string& segment_name,
|
||||
uint64_t start_time,
|
||||
uint64_t duration,
|
||||
uint64_t size) = 0;
|
||||
|
||||
/// @param stream_id is the value set by NotifyNewStream().
|
||||
/// @param key_id is the key ID for the stream.
|
||||
/// @param system_id is the DRM system ID in e.g. PSSH boxes. For example this
|
||||
/// can be used to determine the KEYFORMAT attribute for EXT-X-KEY.
|
||||
/// @param iv is the new initialization vector.
|
||||
/// @param protection_system_specific_data is the DRM specific data. The
|
||||
/// interpretation of this data is up to the implementation, possibly
|
||||
/// using @a system_id to determine how to interpret the data.
|
||||
virtual bool NotifyEncryptionUpdate(
|
||||
uint32_t stream_id,
|
||||
const std::vector<uint8_t>& key_id,
|
||||
const std::vector<uint8_t>& system_id,
|
||||
const std::vector<uint8_t>& iv,
|
||||
const std::vector<uint8_t>& protection_system_specific_data) = 0;
|
||||
|
||||
/// Process any current buffered states/resources.
|
||||
/// @return true on success, false otherwise.
|
||||
virtual bool Flush() = 0;
|
||||
|
||||
protected:
|
||||
HlsProfile profile() const { return profile_; }
|
||||
|
||||
private:
|
||||
HlsProfile profile_;
|
||||
};
|
||||
|
||||
} // namespace hls
|
||||
} // namespace edash_packager
|
||||
#endif // PACKAGER_HLS_BASE_HLS_NOTIFIER_H_
|
|
@ -18,16 +18,17 @@ namespace hls {
|
|||
class MediaPlaylist;
|
||||
|
||||
/// Class to generate HLS Master Playlist.
|
||||
/// Methods are virtual for mocking.
|
||||
class MasterPlaylist {
|
||||
public:
|
||||
/// @param file_name is the file name of the master playlist.
|
||||
explicit MasterPlaylist(const std::string& file_name);
|
||||
~MasterPlaylist();
|
||||
virtual ~MasterPlaylist();
|
||||
|
||||
/// @param media_playlist is a MediaPlaylist that should get added to this
|
||||
/// master playlist. Ownership does not transfer.
|
||||
/// @return true on success, false otherwise.
|
||||
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>.
|
||||
|
@ -38,8 +39,8 @@ class MasterPlaylist {
|
|||
/// necessarily the same as base_url. It must be in a form that File
|
||||
/// interface can open.
|
||||
/// @return true on success, false otherwise.
|
||||
bool WriteAllPlaylists(const std::string& base_url,
|
||||
const std::string& output_dir);
|
||||
virtual bool WriteAllPlaylists(const std::string& base_url,
|
||||
const std::string& output_dir);
|
||||
|
||||
/// Writes Master Playlist to output_dir + <name of playlist>.
|
||||
/// This assumes that @a base_url is used as the prefix for Media Playlists.
|
||||
|
@ -49,8 +50,8 @@ class MasterPlaylist {
|
|||
/// necessarily the same as base_url. It must be in a form that File
|
||||
/// interface can open.
|
||||
/// @return true on success, false otherwise.
|
||||
bool WriteMasterPlaylist(const std::string& base_url,
|
||||
const std::string& output_dir);
|
||||
virtual bool WriteMasterPlaylist(const std::string& base_url,
|
||||
const std::string& output_dir);
|
||||
|
||||
private:
|
||||
const std::string file_name_;
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
#include "packager/hls/base/simple_hls_notifier.h"
|
||||
|
||||
#include "packager/base/base64.h"
|
||||
#include "packager/base/logging.h"
|
||||
#include "packager/base/strings/string_number_conversions.h"
|
||||
#include "packager/base/strings/stringprintf.h"
|
||||
#include "packager/hls/base/media_playlist.h"
|
||||
#include "packager/media/base/widevine_pssh_data.pb.h"
|
||||
|
||||
namespace edash_packager {
|
||||
namespace hls {
|
||||
|
||||
namespace {
|
||||
const uint8_t kSystemIdWidevine[] = {0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6,
|
||||
0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc,
|
||||
0xd5, 0x1d, 0x21, 0xed};
|
||||
bool IsWidevineSystemId(const std::vector<uint8_t>& system_id) {
|
||||
return system_id.size() == arraysize(kSystemIdWidevine) &&
|
||||
std::equal(system_id.begin(), system_id.end(), kSystemIdWidevine);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
MediaPlaylistFactory::~MediaPlaylistFactory() {}
|
||||
|
||||
scoped_ptr<MediaPlaylist> MediaPlaylistFactory::Create(
|
||||
const std::string& file_name,
|
||||
const std::string& name,
|
||||
const std::string& group_id) {
|
||||
return scoped_ptr<MediaPlaylist>(
|
||||
new MediaPlaylist(file_name, name, group_id));
|
||||
}
|
||||
|
||||
SimpleHlsNotifier::SimpleHlsNotifier(const std::string& prefix,
|
||||
const std::string& output_dir,
|
||||
const std::string& master_playlist_name)
|
||||
: HlsNotifier(HlsNotifier::HlsProfile::kOnDemandProfile),
|
||||
prefix_(prefix),
|
||||
output_dir_(output_dir),
|
||||
media_playlist_factory_(new MediaPlaylistFactory()),
|
||||
master_playlist_(new MasterPlaylist(master_playlist_name)) {}
|
||||
|
||||
SimpleHlsNotifier::~SimpleHlsNotifier() {}
|
||||
|
||||
bool SimpleHlsNotifier::Init() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SimpleHlsNotifier::NotifyNewStream(const MediaInfo& media_info,
|
||||
const std::string& playlist_name,
|
||||
const std::string& name,
|
||||
const std::string& group_id,
|
||||
uint32_t* stream_id) {
|
||||
DCHECK(stream_id);
|
||||
if (!media_info.has_media_file_name()) {
|
||||
LOG(ERROR) << "MediaInfo.media_file_name is required to generate a Media "
|
||||
"Playlist name.";
|
||||
return false;
|
||||
}
|
||||
|
||||
*stream_id = sequence_number_.GetNext();
|
||||
|
||||
scoped_ptr<MediaPlaylist> media_playlist =
|
||||
media_playlist_factory_->Create(playlist_name, name, group_id);
|
||||
if (!media_playlist->SetMediaInfo(media_info)) {
|
||||
LOG(ERROR) << "Failed to set media info for playlist " << playlist_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
base::AutoLock auto_lock(lock_);
|
||||
master_playlist_->AddMediaPlaylist(media_playlist.get());
|
||||
media_playlist_map_.insert(std::make_pair(*stream_id, media_playlist.Pass()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SimpleHlsNotifier::NotifyNewSegment(uint32_t stream_id,
|
||||
const std::string& segment_name,
|
||||
uint64_t start_time,
|
||||
uint64_t duration,
|
||||
uint64_t size) {
|
||||
base::AutoLock auto_lock(lock_);
|
||||
auto result = media_playlist_map_.find(stream_id);
|
||||
if (result == media_playlist_map_.end()) {
|
||||
LOG(ERROR) << "Cannot find stream with ID: " << stream_id;
|
||||
return false;
|
||||
}
|
||||
auto& media_playlist = result->second;
|
||||
media_playlist->AddSegment(prefix_ + segment_name, duration, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO(rkuroiwa): Add static key support. for common system id.
|
||||
bool SimpleHlsNotifier::NotifyEncryptionUpdate(
|
||||
uint32_t stream_id,
|
||||
const std::vector<uint8_t>& key_id,
|
||||
const std::vector<uint8_t>& system_id,
|
||||
const std::vector<uint8_t>& iv,
|
||||
const std::vector<uint8_t>& protection_system_specific_data) {
|
||||
base::AutoLock auto_lock(lock_);
|
||||
auto result = media_playlist_map_.find(stream_id);
|
||||
if (result == media_playlist_map_.end()) {
|
||||
LOG(ERROR) << "Cannot find stream with ID: " << stream_id;
|
||||
return false;
|
||||
}
|
||||
if (!IsWidevineSystemId(system_id)) {
|
||||
LOG(ERROR) << "Unknown system ID: "
|
||||
<< base::HexEncode(system_id.data(), system_id.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
media::WidevinePsshData pssh_data;
|
||||
if (!pssh_data.ParseFromArray(protection_system_specific_data.data(),
|
||||
protection_system_specific_data.size())) {
|
||||
LOG(ERROR) << "Failed ot parse protection_system_specific_data.";
|
||||
return false;
|
||||
}
|
||||
if (!pssh_data.has_provider() || !pssh_data.has_content_id() ||
|
||||
pssh_data.key_id_size() == 0) {
|
||||
LOG(ERROR) << "Missing fields to generate URI.";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string content_id_base64;
|
||||
base::Base64Encode(pssh_data.content_id(), &content_id_base64);
|
||||
std::string json_format = base::StringPrintf(
|
||||
"{"
|
||||
"\"provider\":\"%s\","
|
||||
"\"content_id\":\"%s\","
|
||||
"\"key_ids\":[",
|
||||
pssh_data.provider().c_str(), content_id_base64.c_str());
|
||||
json_format += "\"" + base::HexEncode(key_id.data(), key_id.size()) + "\",";
|
||||
for (const std::string& id: pssh_data.key_id()) {
|
||||
if (key_id.size() == id.size() &&
|
||||
memcmp(key_id.data(), id.data(), id.size()) == 0) {
|
||||
continue;
|
||||
}
|
||||
json_format += "\"" + base::HexEncode(id.data(), id.size()) + "\",";
|
||||
}
|
||||
json_format += "]}";
|
||||
std::string json_format_base64;
|
||||
base::Base64Encode(json_format, &json_format_base64);
|
||||
|
||||
auto& media_playlist = result->second;
|
||||
std::string iv_string;
|
||||
if (!iv.empty()) {
|
||||
iv_string = "0x" + base::HexEncode(iv.data(), iv.size());
|
||||
}
|
||||
media_playlist->AddEncryptionInfo(
|
||||
MediaPlaylist::EncryptionMethod::kSampleAes,
|
||||
"data:text/plain;base64," + json_format_base64, iv_string,
|
||||
"com.widevine", "");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SimpleHlsNotifier::Flush() {
|
||||
base::AutoLock auto_lock(lock_);
|
||||
return master_playlist_->WriteAllPlaylists(prefix_, output_dir_);
|
||||
}
|
||||
|
||||
} // namespace hls
|
||||
} // namespace edash_packager
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
#ifndef PACKAGER_HLS_BASE_SIMPLE_HLS_NOTIFIER_H_
|
||||
#define PACKAGER_HLS_BASE_SIMPLE_HLS_NOTIFIER_H_
|
||||
|
||||
#include "packager/base/atomic_sequence_num.h"
|
||||
#include "packager/base/macros.h"
|
||||
#include "packager/base/memory/scoped_ptr.h"
|
||||
#include "packager/base/synchronization/lock.h"
|
||||
#include "packager/hls/base/hls_notifier.h"
|
||||
#include "packager/hls/base/master_playlist.h"
|
||||
|
||||
namespace edash_packager {
|
||||
namespace hls {
|
||||
|
||||
/// For testing.
|
||||
/// Creates MediaPlaylist. Mock this and return mock MediaPlaylist.
|
||||
class MediaPlaylistFactory {
|
||||
public:
|
||||
virtual ~MediaPlaylistFactory();
|
||||
virtual scoped_ptr<MediaPlaylist> Create(const std::string& file_name,
|
||||
const std::string& name,
|
||||
const std::string& group_id);
|
||||
};
|
||||
|
||||
/// This is thread safe.
|
||||
class SimpleHlsNotifier : public HlsNotifier {
|
||||
public:
|
||||
/// @a prefix is used as hte prefix for all the URIs for Media Playlist. This
|
||||
/// includes the segment URIs in the Media Playlists.
|
||||
/// @param prefix is the used as the prefix for MediaPlaylist URIs. May be
|
||||
/// empty for relative URI from the playlist.
|
||||
/// @param output_dir is the output directory of the playlists. May be empty
|
||||
/// to write to current directory.
|
||||
/// @param master_playlist_name is the name of the master playlist.
|
||||
SimpleHlsNotifier(const std::string& prefix,
|
||||
const std::string& output_dir,
|
||||
const std::string& master_playlist_name);
|
||||
~SimpleHlsNotifier() override;
|
||||
|
||||
/// @name HlsNotifier implemetation overrides.
|
||||
/// @{
|
||||
bool Init() override;
|
||||
bool NotifyNewStream(const MediaInfo& media_info,
|
||||
const std::string& playlist_name,
|
||||
const std::string& stream_name,
|
||||
const std::string& group_id,
|
||||
uint32_t* stream_id) override;
|
||||
bool NotifyNewSegment(uint32_t stream_id,
|
||||
const std::string& segment_name,
|
||||
uint64_t start_time,
|
||||
uint64_t duration,
|
||||
uint64_t size) override;
|
||||
bool NotifyEncryptionUpdate(
|
||||
uint32_t stream_id,
|
||||
const std::vector<uint8_t>& key_id,
|
||||
const std::vector<uint8_t>& system_id,
|
||||
const std::vector<uint8_t>& iv,
|
||||
const std::vector<uint8_t>& protection_system_specific_data) override;
|
||||
bool Flush() override;
|
||||
/// }@
|
||||
|
||||
private:
|
||||
friend class SimpleHlsNotifierTest;
|
||||
|
||||
const std::string prefix_;
|
||||
const std::string output_dir_;
|
||||
|
||||
scoped_ptr<MediaPlaylistFactory> media_playlist_factory_;
|
||||
scoped_ptr<MasterPlaylist> master_playlist_;
|
||||
std::map<uint32_t, scoped_ptr<MediaPlaylist>> media_playlist_map_;
|
||||
|
||||
base::AtomicSequenceNumber sequence_number_;
|
||||
|
||||
base::Lock lock_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SimpleHlsNotifier);
|
||||
};
|
||||
|
||||
} // namespace hls
|
||||
} // namespace edash_packager
|
||||
|
||||
#endif // PACKAGER_HLS_BASE_SIMPLE_HLS_NOTIFIER_H_
|
|
@ -0,0 +1,369 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "packager/base/base64.h"
|
||||
#include "packager/hls/base/mock_media_playlist.h"
|
||||
#include "packager/hls/base/simple_hls_notifier.h"
|
||||
#include "packager/media/base/widevine_pssh_data.pb.h"
|
||||
|
||||
namespace edash_packager {
|
||||
namespace hls {
|
||||
|
||||
using ::testing::Return;
|
||||
using ::testing::StrEq;
|
||||
using ::testing::_;
|
||||
|
||||
namespace {
|
||||
const char kMasterPlaylistName[] = "master.m3u8";
|
||||
|
||||
class MockMasterPlaylist : public MasterPlaylist {
|
||||
public:
|
||||
MockMasterPlaylist() : MasterPlaylist(kMasterPlaylistName) {}
|
||||
|
||||
MOCK_METHOD1(AddMediaPlaylist, void(MediaPlaylist* media_playlist));
|
||||
MOCK_METHOD2(WriteAllPlaylists,
|
||||
bool(const std::string& prefix, const std::string& output_dir));
|
||||
MOCK_METHOD2(WriteMasterPlaylist,
|
||||
bool(const std::string& prefix, const std::string& output_dir));
|
||||
};
|
||||
|
||||
class MockMediaPlaylistFactory : public MediaPlaylistFactory {
|
||||
public:
|
||||
MOCK_METHOD3(CreateMock,
|
||||
MediaPlaylist*(const std::string& file_name,
|
||||
const std::string& name,
|
||||
const std::string& group_id));
|
||||
|
||||
scoped_ptr<MediaPlaylist> Create(const std::string& file_name,
|
||||
const std::string& name,
|
||||
const std::string& group_id) override {
|
||||
return scoped_ptr<MediaPlaylist>(CreateMock(file_name, name, group_id));
|
||||
}
|
||||
};
|
||||
|
||||
const char kTestPrefix[] = "http://testprefix.com/";
|
||||
const char kAnyOutputDir[] = "anything/";
|
||||
|
||||
} // namespace
|
||||
|
||||
class SimpleHlsNotifierTest : public ::testing::Test {
|
||||
protected:
|
||||
SimpleHlsNotifierTest()
|
||||
: notifier_(kTestPrefix, kAnyOutputDir, kMasterPlaylistName) {}
|
||||
|
||||
void InjectMediaPlaylistFactory(scoped_ptr<MediaPlaylistFactory> factory) {
|
||||
notifier_.media_playlist_factory_ = factory.Pass();
|
||||
}
|
||||
|
||||
void InjectMasterPlaylist(scoped_ptr<MasterPlaylist> playlist) {
|
||||
notifier_.master_playlist_ = playlist.Pass();
|
||||
}
|
||||
|
||||
const std::map<uint32_t, scoped_ptr<MediaPlaylist>>& GetMediaPlaylistMap() {
|
||||
return notifier_.media_playlist_map_;
|
||||
}
|
||||
|
||||
SimpleHlsNotifier notifier_;
|
||||
};
|
||||
|
||||
TEST_F(SimpleHlsNotifierTest, Init) {
|
||||
EXPECT_TRUE(notifier_.Init());
|
||||
}
|
||||
|
||||
TEST_F(SimpleHlsNotifierTest, NotifyNewStream) {
|
||||
scoped_ptr<MockMasterPlaylist> mock_master_playlist(new MockMasterPlaylist());
|
||||
scoped_ptr<MockMediaPlaylistFactory> factory(new MockMediaPlaylistFactory());
|
||||
|
||||
// Pointer released by SimpleHlsNotifier.
|
||||
MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist("", "", "");
|
||||
EXPECT_CALL(*mock_master_playlist, AddMediaPlaylist(mock_media_playlist));
|
||||
|
||||
EXPECT_CALL(*mock_media_playlist, SetMediaInfo(_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(*factory, CreateMock(StrEq("video_playlist.m3u8"), StrEq("name"),
|
||||
StrEq("groupid")))
|
||||
.WillOnce(Return(mock_media_playlist));
|
||||
|
||||
InjectMasterPlaylist(mock_master_playlist.Pass());
|
||||
InjectMediaPlaylistFactory(factory.Pass());
|
||||
EXPECT_TRUE(notifier_.Init());
|
||||
MediaInfo media_info;
|
||||
media_info.set_media_file_name("media_file.mp4");
|
||||
media_info.mutable_video_info()->set_codec("videocodec");
|
||||
uint32_t stream_id;
|
||||
EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "video_playlist.m3u8",
|
||||
"name", "groupid", &stream_id));
|
||||
EXPECT_EQ(1u, GetMediaPlaylistMap().size());
|
||||
}
|
||||
|
||||
TEST_F(SimpleHlsNotifierTest, NotifyNewSegment) {
|
||||
scoped_ptr<MockMasterPlaylist> mock_master_playlist(new MockMasterPlaylist());
|
||||
scoped_ptr<MockMediaPlaylistFactory> factory(new MockMediaPlaylistFactory());
|
||||
|
||||
// Pointer released by SimpleHlsNotifier.
|
||||
MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist("", "", "");
|
||||
|
||||
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(_, _, _))
|
||||
.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), kDuration, kSize));
|
||||
|
||||
InjectMasterPlaylist(mock_master_playlist.Pass());
|
||||
InjectMediaPlaylistFactory(factory.Pass());
|
||||
EXPECT_TRUE(notifier_.Init());
|
||||
MediaInfo media_info;
|
||||
media_info.set_media_file_name("media_file.mp4");
|
||||
media_info.mutable_video_info()->set_codec("videocodec");
|
||||
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_F(SimpleHlsNotifierTest, NotifyNewSegmentWithoutStreamsRegistered) {
|
||||
EXPECT_TRUE(notifier_.Init());
|
||||
EXPECT_FALSE(notifier_.NotifyNewSegment(1u, "anything", 0u, 0u, 0u));
|
||||
}
|
||||
|
||||
TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdate) {
|
||||
scoped_ptr<MockMasterPlaylist> mock_master_playlist(new MockMasterPlaylist());
|
||||
scoped_ptr<MockMediaPlaylistFactory> factory(new MockMediaPlaylistFactory());
|
||||
|
||||
// Pointer released by SimpleHlsNotifier.
|
||||
MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist("", "", "");
|
||||
|
||||
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(_, _, _))
|
||||
.WillOnce(Return(mock_media_playlist));
|
||||
|
||||
InjectMasterPlaylist(mock_master_playlist.Pass());
|
||||
InjectMediaPlaylistFactory(factory.Pass());
|
||||
EXPECT_TRUE(notifier_.Init());
|
||||
MediaInfo media_info;
|
||||
media_info.set_media_file_name("media_file.mp4");
|
||||
media_info.mutable_video_info()->set_codec("videocodec");
|
||||
uint32_t stream_id;
|
||||
EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist.m3u8", "name",
|
||||
"groupid", &stream_id));
|
||||
|
||||
const uint8_t kSystemIdWidevine[] = {0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6,
|
||||
0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc,
|
||||
0xd5, 0x1d, 0x21, 0xed};
|
||||
std::vector<uint8_t> system_id(
|
||||
kSystemIdWidevine, kSystemIdWidevine + arraysize(kSystemIdWidevine));
|
||||
std::vector<uint8_t> iv(16, 0x45);
|
||||
|
||||
media::WidevinePsshData widevine_pssh_data;
|
||||
widevine_pssh_data.set_provider("someprovider");
|
||||
widevine_pssh_data.set_content_id("contentid");
|
||||
const uint8_t kAnyKeyId[] = {
|
||||
0x11, 0x22, 0x33, 0x44,
|
||||
};
|
||||
widevine_pssh_data.add_key_id()->assign(kAnyKeyId,
|
||||
kAnyKeyId + arraysize(kAnyKeyId));
|
||||
std::string widevine_pssh_data_str;
|
||||
ASSERT_TRUE(widevine_pssh_data.SerializeToString(&widevine_pssh_data_str));
|
||||
std::vector<uint8_t> pssh_data(widevine_pssh_data_str.begin(),
|
||||
widevine_pssh_data_str.end());
|
||||
|
||||
const char kExpectedJson[] =
|
||||
"{"
|
||||
"\"provider\":\"someprovider\","
|
||||
"\"content_id\":\"Y29udGVudGlk\","
|
||||
"\"key_ids\":[\"11223344\",]}";
|
||||
std::string expected_json_base64;
|
||||
base::Base64Encode(kExpectedJson, &expected_json_base64);
|
||||
|
||||
EXPECT_CALL(
|
||||
*mock_media_playlist,
|
||||
AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes,
|
||||
StrEq("data:text/plain;base64," + expected_json_base64),
|
||||
StrEq("0x45454545454545454545454545454545"),
|
||||
StrEq("com.widevine"), _));
|
||||
EXPECT_TRUE(notifier_.NotifyEncryptionUpdate(
|
||||
stream_id,
|
||||
std::vector<uint8_t>(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)),
|
||||
system_id, iv, pssh_data));
|
||||
}
|
||||
|
||||
// Verify that when there are multiple key IDs in PSSH, the key ID that is
|
||||
// passed to NotifyEncryptionUpdate() is the first key ID in the json format.
|
||||
TEST_F(SimpleHlsNotifierTest, MultipleKeyIdsInPssh) {
|
||||
scoped_ptr<MockMasterPlaylist> mock_master_playlist(new MockMasterPlaylist());
|
||||
scoped_ptr<MockMediaPlaylistFactory> factory(new MockMediaPlaylistFactory());
|
||||
|
||||
// Pointer released by SimpleHlsNotifier.
|
||||
MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist("", "", "");
|
||||
|
||||
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(_, _, _))
|
||||
.WillOnce(Return(mock_media_playlist));
|
||||
|
||||
InjectMasterPlaylist(mock_master_playlist.Pass());
|
||||
InjectMediaPlaylistFactory(factory.Pass());
|
||||
EXPECT_TRUE(notifier_.Init());
|
||||
MediaInfo media_info;
|
||||
media_info.set_media_file_name("media_file.mp4");
|
||||
media_info.mutable_video_info()->set_codec("videocodec");
|
||||
uint32_t stream_id;
|
||||
EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist.m3u8", "name",
|
||||
"groupid", &stream_id));
|
||||
|
||||
const uint8_t kSystemIdWidevine[] = {0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6,
|
||||
0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc,
|
||||
0xd5, 0x1d, 0x21, 0xed};
|
||||
std::vector<uint8_t> system_id(
|
||||
kSystemIdWidevine, kSystemIdWidevine + arraysize(kSystemIdWidevine));
|
||||
std::vector<uint8_t> iv(16, 0x45);
|
||||
|
||||
media::WidevinePsshData widevine_pssh_data;
|
||||
widevine_pssh_data.set_provider("someprovider");
|
||||
widevine_pssh_data.set_content_id("contentid");
|
||||
const uint8_t kFirstKeyId[] = {
|
||||
0x11, 0x11, 0x11, 0x11,
|
||||
};
|
||||
const uint8_t kSecondKeyId[] = {
|
||||
0x22, 0x22, 0x22, 0x22,
|
||||
};
|
||||
widevine_pssh_data.add_key_id()->assign(kFirstKeyId,
|
||||
kFirstKeyId + arraysize(kFirstKeyId));
|
||||
widevine_pssh_data.add_key_id()->assign(
|
||||
kSecondKeyId, kSecondKeyId + arraysize(kSecondKeyId));
|
||||
std::string widevine_pssh_data_str;
|
||||
ASSERT_TRUE(widevine_pssh_data.SerializeToString(&widevine_pssh_data_str));
|
||||
std::vector<uint8_t> pssh_data(widevine_pssh_data_str.begin(),
|
||||
widevine_pssh_data_str.end());
|
||||
|
||||
const char kExpectedJson[] =
|
||||
"{"
|
||||
"\"provider\":\"someprovider\","
|
||||
"\"content_id\":\"Y29udGVudGlk\","
|
||||
"\"key_ids\":[\"22222222\",\"11111111\",]}";
|
||||
std::string expected_json_base64;
|
||||
base::Base64Encode(kExpectedJson, &expected_json_base64);
|
||||
|
||||
EXPECT_CALL(
|
||||
*mock_media_playlist,
|
||||
AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes,
|
||||
StrEq("data:text/plain;base64," + expected_json_base64),
|
||||
StrEq("0x45454545454545454545454545454545"),
|
||||
StrEq("com.widevine"), _));
|
||||
EXPECT_TRUE(notifier_.NotifyEncryptionUpdate(
|
||||
stream_id,
|
||||
// Use the second key id here so that it will be thre first one in the
|
||||
// key_ids array in the JSON.
|
||||
std::vector<uint8_t>(kSecondKeyId,
|
||||
kSecondKeyId + arraysize(kSecondKeyId)),
|
||||
system_id, iv, pssh_data));
|
||||
}
|
||||
|
||||
TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateEmptyIv) {
|
||||
scoped_ptr<MockMasterPlaylist> mock_master_playlist(new MockMasterPlaylist());
|
||||
scoped_ptr<MockMediaPlaylistFactory> factory(new MockMediaPlaylistFactory());
|
||||
|
||||
// Pointer released by SimpleHlsNotifier.
|
||||
MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist("", "", "");
|
||||
|
||||
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(_, _, _))
|
||||
.WillOnce(Return(mock_media_playlist));
|
||||
|
||||
InjectMasterPlaylist(mock_master_playlist.Pass());
|
||||
InjectMediaPlaylistFactory(factory.Pass());
|
||||
EXPECT_TRUE(notifier_.Init());
|
||||
MediaInfo media_info;
|
||||
media_info.set_media_file_name("media_file.mp4");
|
||||
media_info.mutable_video_info()->set_codec("videocodec");
|
||||
uint32_t stream_id;
|
||||
EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist.m3u8", "name",
|
||||
"groupid", &stream_id));
|
||||
|
||||
const uint8_t kSystemIdWidevine[] = {0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6,
|
||||
0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc,
|
||||
0xd5, 0x1d, 0x21, 0xed};
|
||||
std::vector<uint8_t> system_id(
|
||||
kSystemIdWidevine, kSystemIdWidevine + arraysize(kSystemIdWidevine));
|
||||
|
||||
media::WidevinePsshData widevine_pssh_data;
|
||||
widevine_pssh_data.set_provider("someprovider");
|
||||
widevine_pssh_data.set_content_id("contentid");
|
||||
const uint8_t kAnyKeyId[] = {
|
||||
0x11, 0x22, 0x33, 0x44,
|
||||
};
|
||||
widevine_pssh_data.add_key_id()->assign(kAnyKeyId,
|
||||
kAnyKeyId + arraysize(kAnyKeyId));
|
||||
std::string widevine_pssh_data_str;
|
||||
ASSERT_TRUE(widevine_pssh_data.SerializeToString(&widevine_pssh_data_str));
|
||||
std::vector<uint8_t> pssh_data(widevine_pssh_data_str.begin(),
|
||||
widevine_pssh_data_str.end());
|
||||
|
||||
const char kExpectedJson[] =
|
||||
"{"
|
||||
"\"provider\":\"someprovider\","
|
||||
"\"content_id\":\"Y29udGVudGlk\","
|
||||
"\"key_ids\":[\"11223344\",]}";
|
||||
std::string expected_json_base64;
|
||||
base::Base64Encode(kExpectedJson, &expected_json_base64);
|
||||
|
||||
EXPECT_CALL(
|
||||
*mock_media_playlist,
|
||||
AddEncryptionInfo(MediaPlaylist::EncryptionMethod::kSampleAes,
|
||||
StrEq("data:text/plain;base64," + expected_json_base64),
|
||||
StrEq(""), StrEq("com.widevine"), _));
|
||||
|
||||
std::vector<uint8_t> empty_iv;
|
||||
EXPECT_TRUE(notifier_.NotifyEncryptionUpdate(
|
||||
stream_id,
|
||||
std::vector<uint8_t>(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)),
|
||||
system_id, empty_iv, pssh_data));
|
||||
}
|
||||
|
||||
TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWithoutStreamsRegistered) {
|
||||
std::vector<uint8_t> system_id;
|
||||
std::vector<uint8_t> iv;
|
||||
std::vector<uint8_t> pssh_data;
|
||||
std::vector<uint8_t> key_id;
|
||||
EXPECT_TRUE(notifier_.Init());
|
||||
EXPECT_FALSE(notifier_.NotifyEncryptionUpdate(1238u, key_id, system_id, iv,
|
||||
pssh_data));
|
||||
}
|
||||
|
||||
TEST_F(SimpleHlsNotifierTest, Flush) {
|
||||
scoped_ptr<MockMasterPlaylist> mock_master_playlist(new MockMasterPlaylist());
|
||||
EXPECT_CALL(*mock_master_playlist,
|
||||
WriteAllPlaylists(StrEq(kTestPrefix), StrEq(kAnyOutputDir)))
|
||||
.WillOnce(Return(true));
|
||||
scoped_ptr<MockMediaPlaylistFactory> factory(new MockMediaPlaylistFactory());
|
||||
|
||||
InjectMasterPlaylist(mock_master_playlist.Pass());
|
||||
EXPECT_TRUE(notifier_.Init());
|
||||
EXPECT_TRUE(notifier_.Flush());
|
||||
}
|
||||
|
||||
} // namespace hls
|
||||
} // namespace edash_packager
|
|
@ -13,10 +13,13 @@
|
|||
'target_name': 'hls_builder',
|
||||
'type': '<(component)',
|
||||
'sources': [
|
||||
'base/hls_notifier.h',
|
||||
'base/master_playlist.cc',
|
||||
'base/master_playlist.h',
|
||||
'base/media_playlist.cc',
|
||||
'base/media_playlist.h',
|
||||
'base/simple_hls_notifier.cc',
|
||||
'base/simple_hls_notifier.h',
|
||||
],
|
||||
'dependencies': [
|
||||
'../base/base.gyp:base',
|
||||
|
@ -32,6 +35,7 @@
|
|||
'base/media_playlist_unittest.cc',
|
||||
'base/mock_media_playlist.cc',
|
||||
'base/mock_media_playlist.h',
|
||||
'base/simple_hls_notifier_unittest.cc',
|
||||
],
|
||||
'dependencies': [
|
||||
'../base/base.gyp:base',
|
||||
|
|
Loading…
Reference in New Issue