diff --git a/packager/hls/base/simple_hls_notifier.cc b/packager/hls/base/simple_hls_notifier.cc index afce9c0d6a..c9b36ecb2e 100644 --- a/packager/hls/base/simple_hls_notifier.cc +++ b/packager/hls/base/simple_hls_notifier.cc @@ -8,22 +8,28 @@ #include "packager/base/base64.h" #include "packager/base/files/file_path.h" +#include "packager/base/json/json_writer.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/fixed_key_source.h" +#include "packager/media/base/widevine_key_source.h" #include "packager/media/base/widevine_pssh_data.pb.h" namespace shaka { 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& system_id) { - return system_id.size() == arraysize(kSystemIdWidevine) && - std::equal(system_id.begin(), system_id.end(), kSystemIdWidevine); + return system_id.size() == arraysize(media::kWidevineSystemId) && + std::equal(system_id.begin(), system_id.end(), + media::kWidevineSystemId); +} + +bool IsCommonSystemId(const std::vector& system_id) { + return system_id.size() == arraysize(media::kCommonSystemId) && + std::equal(system_id.begin(), system_id.end(), media::kCommonSystemId); } // TODO(rkuroiwa): Dedup these with the functions in MpdBuilder. @@ -62,6 +68,48 @@ void MakePathsRelativeToOutputDirectory(const std::string& output_dir, media_info->segment_template(), directory_with_separator)); } } + +bool WidevinePsshToJson(const std::vector& pssh_data, + const std::vector& key_id, + std::string* pssh_json) { + media::WidevinePsshData pssh_proto; + if (!pssh_proto.ParseFromArray(pssh_data.data(), pssh_data.size())) { + LOG(ERROR) << "Failed to parse protection_system_specific_data."; + return false; + } + if (!pssh_proto.has_provider() || + (!pssh_proto.has_content_id() && pssh_proto.key_id_size() == 0)) { + LOG(ERROR) << "Missing fields to generate URI."; + return false; + } + + base::DictionaryValue pssh_dict; + pssh_dict.SetString("provider", pssh_proto.provider()); + if (pssh_proto.has_content_id()) { + std::string content_id_base64; + base::Base64Encode(base::StringPiece(pssh_proto.content_id().data(), + pssh_proto.content_id().size()), + &content_id_base64); + pssh_dict.SetString("content_id", content_id_base64); + } + base::ListValue* key_ids = new base::ListValue(); + key_ids->AppendString(base::HexEncode(key_id.data(), key_id.size())); + for (const std::string& id : pssh_proto.key_id()) { + if (key_id.size() == id.size() && + memcmp(key_id.data(), id.data(), id.size()) == 0) { + continue; + } + key_ids->AppendString(base::HexEncode(id.data(), id.size())); + } + pssh_dict.Set("key_ids", key_ids); + + if (!base::JSONWriter::Write(pssh_dict, pssh_json)) { + LOG(ERROR) << "Failed to write to JSON."; + return false; + } + return true; +} + } // namespace MediaPlaylistFactory::~MediaPlaylistFactory() {} @@ -149,7 +197,6 @@ bool SimpleHlsNotifier::NotifyNewSegment(uint32_t stream_id, return true; } -// TODO(rkuroiwa): Add static key support. for common system id. bool SimpleHlsNotifier::NotifyEncryptionUpdate( uint32_t stream_id, const std::vector& key_id, @@ -162,55 +209,37 @@ bool SimpleHlsNotifier::NotifyEncryptionUpdate( LOG(ERROR) << "Cannot find stream with ID: " << stream_id; return false; } - if (!IsWidevineSystemId(system_id)) { + + std::string key_format; + std::string key_uri_data; + if (IsWidevineSystemId(system_id)) { + key_format = "com.widevine"; + if (!WidevinePsshToJson(protection_system_specific_data, key_id, + &key_uri_data)) { + return false; + } + } else if (IsCommonSystemId(system_id)) { + key_format = "identity"; + // Use key_id as the key_uri. The player needs to have custom logic to + // convert it to the actual key url. + key_uri_data.assign(key_id.begin(), key_id.end()); + } else { 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(base::StringPiece(pssh_data.content_id().data(), - pssh_data.content_id().size()), - &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()); } + std::string key_uri_data_base64; + base::Base64Encode(key_uri_data, &key_uri_data_base64); media_playlist->AddEncryptionInfo( MediaPlaylist::EncryptionMethod::kSampleAes, - "data:text/plain;base64," + json_format_base64, iv_string, - "com.widevine", ""); + "data:text/plain;base64," + key_uri_data_base64, iv_string, key_format, + "" /* key_format_versions */); return true; } diff --git a/packager/hls/base/simple_hls_notifier_unittest.cc b/packager/hls/base/simple_hls_notifier_unittest.cc index 4978d522b0..fb9ed31f9f 100644 --- a/packager/hls/base/simple_hls_notifier_unittest.cc +++ b/packager/hls/base/simple_hls_notifier_unittest.cc @@ -10,6 +10,8 @@ #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/fixed_key_source.h" +#include "packager/media/base/widevine_key_source.h" #include "packager/media/base/widevine_pssh_data.pb.h" namespace shaka { @@ -72,7 +74,13 @@ class SimpleHlsNotifierTest : public ::testing::Test { : notifier_(HlsNotifier::HlsProfile::kOnDemandProfile, kTestPrefix, kAnyOutputDir, - kMasterPlaylistName) {} + kMasterPlaylistName), + widevine_system_id_( + media::kWidevineSystemId, + media::kWidevineSystemId + arraysize(media::kWidevineSystemId)), + common_system_id_( + media::kCommonSystemId, + media::kCommonSystemId + arraysize(media::kCommonSystemId)) {} void InjectMediaPlaylistFactory(scoped_ptr factory) { notifier_.media_playlist_factory_ = factory.Pass(); @@ -96,7 +104,32 @@ class SimpleHlsNotifierTest : public ::testing::Test { return notifier_.media_playlist_map_; } + uint32_t SetupStream(MockMediaPlaylist* mock_media_playlist) { + scoped_ptr mock_master_playlist( + new MockMasterPlaylist()); + scoped_ptr factory( + new MockMediaPlaylistFactory()); + + EXPECT_CALL( + *mock_master_playlist, + AddMediaPlaylist(static_cast(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; + uint32_t stream_id; + EXPECT_TRUE(notifier_.NotifyNewStream(media_info, "playlist.m3u8", "name", + "groupid", &stream_id)); + return stream_id; + } + SimpleHlsNotifier notifier_; + const std::vector widevine_system_id_; + const std::vector common_system_id_; }; TEST_F(SimpleHlsNotifierTest, Init) { @@ -288,35 +321,13 @@ TEST_F(SimpleHlsNotifierTest, NotifyNewSegmentWithoutStreamsRegistered) { EXPECT_FALSE(notifier_.NotifyNewSegment(1u, "anything", 0u, 0u, 0u)); } -TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdate) { - scoped_ptr mock_master_playlist(new MockMasterPlaylist()); - scoped_ptr factory(new MockMediaPlaylistFactory()); - +TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevine) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "", "", ""); + const uint32_t stream_id = SetupStream(mock_media_playlist); - EXPECT_CALL( - *mock_master_playlist, - AddMediaPlaylist(static_cast(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; - 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 system_id( - kSystemIdWidevine, kSystemIdWidevine + arraysize(kSystemIdWidevine)); - std::vector iv(16, 0x45); + const std::vector iv(16, 0x45); media::WidevinePsshData widevine_pssh_data; widevine_pssh_data.set_provider("someprovider"); @@ -333,9 +344,9 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdate) { const char kExpectedJson[] = "{" - "\"provider\":\"someprovider\"," "\"content_id\":\"Y29udGVudGlk\"," - "\"key_ids\":[\"11223344\",]}"; + "\"key_ids\":[\"11223344\"]," + "\"provider\":\"someprovider\"}"; std::string expected_json_base64; base::Base64Encode(kExpectedJson, &expected_json_base64); @@ -348,44 +359,86 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdate) { EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( stream_id, std::vector(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)), - system_id, iv, pssh_data)); + widevine_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 mock_master_playlist(new MockMasterPlaylist()); - scoped_ptr factory(new MockMediaPlaylistFactory()); - +// Verify that key_ids in pssh is optional. +TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWidevineNoKeyidsInPssh) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "", "", ""); + const uint32_t stream_id = SetupStream(mock_media_playlist); - EXPECT_CALL( - *mock_master_playlist, - AddMediaPlaylist(static_cast(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; - 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 system_id( - kSystemIdWidevine, kSystemIdWidevine + arraysize(kSystemIdWidevine)); - std::vector iv(16, 0x45); + const std::vector iv(16, 0x45); media::WidevinePsshData widevine_pssh_data; widevine_pssh_data.set_provider("someprovider"); widevine_pssh_data.set_content_id("contentid"); + std::string widevine_pssh_data_str = widevine_pssh_data.SerializeAsString(); + EXPECT_TRUE(!widevine_pssh_data_str.empty()); + std::vector pssh_data(widevine_pssh_data_str.begin(), + widevine_pssh_data_str.end()); + + const char kExpectedJson[] = + "{" + "\"content_id\":\"Y29udGVudGlk\"," + "\"key_ids\":[\"11223344\"]," + "\"provider\":\"someprovider\"}"; + 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"), _)); + const uint8_t kAnyKeyId[] = { + 0x11, 0x22, 0x33, 0x44, + }; + EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( + stream_id, + std::vector(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)), + widevine_system_id_, iv, pssh_data)); +} + +TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateFixedKey) { + // Pointer released by SimpleHlsNotifier. + MockMediaPlaylist* mock_media_playlist = + new MockMediaPlaylist(kVodPlaylist, "", "", ""); + const uint32_t stream_id = SetupStream(mock_media_playlist); + + const std::vector key_id(16, 0x23); + const std::vector iv(16, 0x45); + const std::vector dummy_pssh_data(10, 'p'); + + std::string expected_key_uri_base64; + base::Base64Encode(std::string(key_id.begin(), key_id.end()), + &expected_key_uri_base64); + + EXPECT_CALL( + *mock_media_playlist, + AddEncryptionInfo( + MediaPlaylist::EncryptionMethod::kSampleAes, + StrEq("data:text/plain;base64," + expected_key_uri_base64), + StrEq("0x45454545454545454545454545454545"), StrEq("identity"), _)); + EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( + stream_id, key_id, common_system_id_, iv, dummy_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. +// Also verify that content_id is optional. +TEST_F(SimpleHlsNotifierTest, WidevineMultipleKeyIdsNoContentIdInPssh) { + // Pointer released by SimpleHlsNotifier. + MockMediaPlaylist* mock_media_playlist = + new MockMediaPlaylist(kVodPlaylist, "", "", ""); + uint32_t stream_id = SetupStream(mock_media_playlist); + + std::vector iv(16, 0x45); + + media::WidevinePsshData widevine_pssh_data; + widevine_pssh_data.set_provider("someprovider"); const uint8_t kFirstKeyId[] = { 0x11, 0x11, 0x11, 0x11, }; @@ -403,9 +456,8 @@ TEST_F(SimpleHlsNotifierTest, MultipleKeyIdsInPssh) { const char kExpectedJson[] = "{" - "\"provider\":\"someprovider\"," - "\"content_id\":\"Y29udGVudGlk\"," - "\"key_ids\":[\"22222222\",\"11111111\",]}"; + "\"key_ids\":[\"22222222\",\"11111111\"]," + "\"provider\":\"someprovider\"}"; std::string expected_json_base64; base::Base64Encode(kExpectedJson, &expected_json_base64); @@ -421,37 +473,14 @@ TEST_F(SimpleHlsNotifierTest, MultipleKeyIdsInPssh) { // key_ids array in the JSON. std::vector(kSecondKeyId, kSecondKeyId + arraysize(kSecondKeyId)), - system_id, iv, pssh_data)); + widevine_system_id_, iv, pssh_data)); } -TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateEmptyIv) { - scoped_ptr mock_master_playlist(new MockMasterPlaylist()); - scoped_ptr factory(new MockMediaPlaylistFactory()); - +TEST_F(SimpleHlsNotifierTest, WidevineNotifyEncryptionUpdateEmptyIv) { // Pointer released by SimpleHlsNotifier. MockMediaPlaylist* mock_media_playlist = new MockMediaPlaylist(kVodPlaylist, "", "", ""); - - EXPECT_CALL( - *mock_master_playlist, - AddMediaPlaylist(static_cast(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; - 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 system_id( - kSystemIdWidevine, kSystemIdWidevine + arraysize(kSystemIdWidevine)); + const uint32_t stream_id = SetupStream(mock_media_playlist); media::WidevinePsshData widevine_pssh_data; widevine_pssh_data.set_provider("someprovider"); @@ -468,9 +497,9 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateEmptyIv) { const char kExpectedJson[] = "{" - "\"provider\":\"someprovider\"," "\"content_id\":\"Y29udGVudGlk\"," - "\"key_ids\":[\"11223344\",]}"; + "\"key_ids\":[\"11223344\"]," + "\"provider\":\"someprovider\"}"; std::string expected_json_base64; base::Base64Encode(kExpectedJson, &expected_json_base64); @@ -484,7 +513,7 @@ TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateEmptyIv) { EXPECT_TRUE(notifier_.NotifyEncryptionUpdate( stream_id, std::vector(kAnyKeyId, kAnyKeyId + arraysize(kAnyKeyId)), - system_id, empty_iv, pssh_data)); + widevine_system_id_, empty_iv, pssh_data)); } TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateWithoutStreamsRegistered) { diff --git a/packager/hls/hls.gyp b/packager/hls/hls.gyp index 8e134b8f64..741f774beb 100644 --- a/packager/hls/hls.gyp +++ b/packager/hls/hls.gyp @@ -23,7 +23,7 @@ ], 'dependencies': [ '../base/base.gyp:base', - '../media/base/media_base.gyp:widevine_pssh_data_proto', + '../media/base/media_base.gyp:media_base', '../media/file/file.gyp:file', '../mpd/mpd.gyp:media_info_proto', ], diff --git a/packager/media/base/widevine_key_source.cc b/packager/media/base/widevine_key_source.cc index bd0532bcc1..7a8bb1d5c8 100644 --- a/packager/media/base/widevine_key_source.cc +++ b/packager/media/base/widevine_key_source.cc @@ -43,10 +43,6 @@ const int kDefaultCryptoPeriodCount = 10; const int kGetKeyTimeoutInSeconds = 5 * 60; // 5 minutes. const int kKeyFetchTimeoutInSeconds = 60; // 1 minute. -const uint8_t kWidevineSystemId[] = {0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, - 0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc, - 0xd5, 0x1d, 0x21, 0xed}; - bool Base64StringToBytes(const std::string& base64_string, std::vector* bytes) { DCHECK(bytes); diff --git a/packager/media/base/widevine_key_source.h b/packager/media/base/widevine_key_source.h index caa34f9010..1a1e61b259 100644 --- a/packager/media/base/widevine_key_source.h +++ b/packager/media/base/widevine_key_source.h @@ -17,6 +17,11 @@ namespace shaka { namespace media { + +const uint8_t kWidevineSystemId[] = {0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, + 0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc, + 0xd5, 0x1d, 0x21, 0xed}; + class KeyFetcher; class RequestSigner; template class ProducerConsumerQueue;