diff --git a/AUTHORS b/AUTHORS index cc37e22214..b8945ee42d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -17,6 +17,7 @@ Anders Hasselqvist Chun-da Chen Google Inc. <*@google.com> Leandro Moreira +More Screens Ltd. <*@morescreens.net> Philo Inc. <*@philo.com> Richard Eklycke Sergio Ammirata diff --git a/CONTRIBUTORS b/CONTRIBUTORS index aa494eeaf0..71fadc886c 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -25,6 +25,7 @@ Anders Hasselqvist Bei Li Chun-da Chen +David Cavar Gabe Kopley Haoming Chen Jacob Trimble diff --git a/packager/hls/base/simple_hls_notifier.cc b/packager/hls/base/simple_hls_notifier.cc index eb6cefa158..e1c4674c57 100644 --- a/packager/hls/base/simple_hls_notifier.cc +++ b/packager/hls/base/simple_hls_notifier.cc @@ -27,6 +27,7 @@ namespace hls { namespace { const char kUriBase64Prefix[] = "data:text/plain;base64,"; +const char kUriFairplayPrefix[] = "skd://"; const char kWidevineDashIfIopUUID[] = "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"; @@ -41,6 +42,22 @@ bool IsCommonSystemId(const std::vector& system_id) { std::equal(system_id.begin(), system_id.end(), media::kCommonSystemId); } +bool IsFairplaySystemId(const std::vector& system_id) { + return system_id.size() == arraysize(media::kFairplaySystemId) && + std::equal(system_id.begin(), system_id.end(), media::kFairplaySystemId); +} + +std::string Base64EncodeData(const std::string& prefix, + const std::string& data) { + std::string data_base64; + base::Base64Encode(data, &data_base64); + return prefix + data_base64; +} + +std::string VectorToString(const std::vector& v) { + return std::string(v.begin(), v.end()); +} + // TODO(rkuroiwa): Dedup these with the functions in MpdBuilder. std::string MakePathRelative(const std::string& original_path, const std::string& output_dir) { @@ -157,11 +174,10 @@ void NotifyEncryptionToMediaPlaylist( if (!key_id.empty()) { key_id_string = "0x" + base::HexEncode(key_id.data(), key_id.size()); } - std::string key_uri_data_base64; - base::Base64Encode(uri, &key_uri_data_base64); + media_playlist->AddEncryptionInfo( encryption_method, - kUriBase64Prefix + key_uri_data_base64, key_id_string, iv_string, + uri, key_id_string, iv_string, key_format, key_format_version); } @@ -179,8 +195,9 @@ bool HandleWidevineKeyFormats( &key_uri_data)) { return false; } - // This format does not have a key id field. - NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data, + std::string key_uri_data_base64 = + Base64EncodeData(kUriBase64Prefix, key_uri_data); + NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64, std::vector(), iv, "com.widevine", "1", media_playlist); } @@ -188,8 +205,11 @@ bool HandleWidevineKeyFormats( std::string pssh_as_string( reinterpret_cast(protection_system_specific_data.data()), protection_system_specific_data.size()); - NotifyEncryptionToMediaPlaylist(encryption_method, pssh_as_string, key_id, iv, - kWidevineDashIfIopUUID, "1", media_playlist); + std::string key_uri_data_base64 = + Base64EncodeData(kUriBase64Prefix, pssh_as_string); + NotifyEncryptionToMediaPlaylist(encryption_method, key_uri_data_base64, + key_id, iv, kWidevineDashIfIopUUID, "1", + media_playlist); return true; } @@ -356,13 +376,31 @@ bool SimpleHlsNotifier::NotifyEncryptionUpdate( if (IsCommonSystemId(system_id)) { // Use key_id as the key_uri. The player needs to have custom logic to // convert it to the actual key url. - std::string key_uri_data; - key_uri_data.assign(key_id.begin(), key_id.end()); + std::string key_uri_data = VectorToString(key_id); + std::string key_uri_data_base64 = + Base64EncodeData(kUriBase64Prefix, key_uri_data); NotifyEncryptionToMediaPlaylist(encryption_method, - key_uri_data, std::vector(), iv, - "identity", "", media_playlist.get()); + key_uri_data_base64, std::vector(), + iv, "identity", "", media_playlist.get()); return true; } + + if (IsFairplaySystemId(system_id)) { + // Use key_id as the key_uri. The player needs to have custom logic to + // convert it to the actual key url. + std::string key_uri_data = VectorToString(key_id); + std::string key_uri_data_base64 = + Base64EncodeData(kUriFairplayPrefix, key_uri_data); + + // Fairplay defines IV to be carried with the key, not the playlist. + NotifyEncryptionToMediaPlaylist(encryption_method, + key_uri_data_base64, std::vector(), + std::vector(), + "com.apple.streamingkeydelivery", "1", + media_playlist.get()); + return true; + } + LOG(ERROR) << "Unknown system ID: " << base::HexEncode(system_id.data(), system_id.size()); return false; diff --git a/packager/hls/base/simple_hls_notifier_unittest.cc b/packager/hls/base/simple_hls_notifier_unittest.cc index 2a22c1e398..9e880a4c64 100644 --- a/packager/hls/base/simple_hls_notifier_unittest.cc +++ b/packager/hls/base/simple_hls_notifier_unittest.cc @@ -31,6 +31,7 @@ using ::testing::_; namespace { const char kMasterPlaylistName[] = "master.m3u8"; const HlsPlaylistType kVodPlaylist = HlsPlaylistType::kVod; +const HlsPlaylistType kLivePlaylist = HlsPlaylistType::kLive; class MockMasterPlaylist : public MasterPlaylist { public: @@ -94,7 +95,10 @@ class SimpleHlsNotifierTest : public ::testing::Test { media::kWidevineSystemId + arraysize(media::kWidevineSystemId)), common_system_id_( media::kCommonSystemId, - media::kCommonSystemId + arraysize(media::kCommonSystemId)) {} + media::kCommonSystemId + arraysize(media::kCommonSystemId)), + fairplay_system_id_( + media::kFairplaySystemId, + media::kFairplaySystemId + arraysize(media::kFairplaySystemId)) {} void InjectMediaPlaylistFactory(std::unique_ptr factory, SimpleHlsNotifier* notifier) { @@ -139,6 +143,7 @@ class SimpleHlsNotifierTest : public ::testing::Test { const std::vector widevine_system_id_; const std::vector common_system_id_; + const std::vector fairplay_system_id_; }; TEST_F(SimpleHlsNotifierTest, Init) { @@ -645,6 +650,35 @@ TEST_F(SimpleHlsNotifierTest, EncryptionScheme) { stream_id, key_id, common_system_id_, iv, dummy_pssh_data)); } +// Verify that the Fairplay systemID is correctly handled when constructing +// encryption info. +TEST_F(SimpleHlsNotifierTest, NotifyEncryptionUpdateFairplay) { + // Pointer released by SimpleHlsNotifier. + MockMediaPlaylist* mock_media_playlist = + new MockMediaPlaylist(kLivePlaylist, "playlist.m3u8", "", ""); + SimpleHlsNotifier notifier(kLivePlaylist, kTestTimeShiftBufferDepth, + kTestPrefix, kAnyOutputDir, kMasterPlaylistName); + const uint32_t stream_id = + SetupStream(kSampleAesProtectionScheme, mock_media_playlist, ¬ifier); + const std::vector key_id(16, 0x12); + 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("skd://" + expected_key_uri_base64), + StrEq(""), + StrEq(""), + StrEq("com.apple.streamingkeydelivery"), StrEq("1"))); + EXPECT_TRUE(notifier.NotifyEncryptionUpdate( + stream_id, key_id, fairplay_system_id_, std::vector(), + dummy_pssh_data)); +} + // If using 'cenc' with Widevine, don't output the json form. TEST_F(SimpleHlsNotifierTest, WidevineCencEncryptionScheme) { // Pointer released by SimpleHlsNotifier. diff --git a/packager/media/base/fixed_key_source.h b/packager/media/base/fixed_key_source.h index 944cccfb37..f7b1c28148 100644 --- a/packager/media/base/fixed_key_source.h +++ b/packager/media/base/fixed_key_source.h @@ -23,6 +23,12 @@ const uint8_t kCommonSystemId[] = {0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b}; +// Unofficial fairplay system id extracted from +// https://forums.developer.apple.com/thread/6185. +const uint8_t kFairplaySystemId[] = {0x29, 0x70, 0x1F, 0xE4, 0x3C, 0xC7, + 0x4A, 0x34, 0x8C, 0x5B, 0xAE, 0x90, + 0xC7, 0x43, 0x9A, 0x47}; + /// A key source that uses fixed keys for encryption. class FixedKeySource : public KeySource { public: