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:
Rintaro Kuroiwa 2016-03-25 01:40:15 -07:00
parent c6445b749c
commit a6646841b7
6 changed files with 721 additions and 6 deletions

View File

@ -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_

View File

@ -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,7 +39,7 @@ 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,
virtual bool WriteAllPlaylists(const std::string& base_url,
const std::string& output_dir);
/// Writes Master Playlist to output_dir + <name of playlist>.
@ -49,7 +50,7 @@ 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,
virtual bool WriteMasterPlaylist(const std::string& base_url,
const std::string& output_dir);
private:

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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',