KeySystemConfig.cpp (14523B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "KeySystemConfig.h" 8 9 #include "EMEUtils.h" 10 #include "GMPUtils.h" 11 #include "KeySystemNames.h" 12 #include "mozilla/StaticPrefs_media.h" 13 #include "mozilla/dom/Promise.h" 14 #include "nsPrintfCString.h" 15 16 #ifdef XP_WIN 17 # include "PDMFactory.h" 18 # include "WMFDecoderModule.h" 19 #endif 20 #ifdef MOZ_WIDGET_ANDROID 21 # include "AndroidDecoderModule.h" 22 # include "mozilla/java/MediaDrmProxyWrappers.h" 23 # include "nsMimeTypes.h" 24 #endif 25 26 #ifdef MOZ_WMF_CDM 27 # include "mediafoundation/WMFCDMImpl.h" 28 #endif 29 30 namespace mozilla { 31 32 /* static */ 33 bool KeySystemConfig::Supports(const nsAString& aKeySystem) { 34 #ifdef MOZ_WIDGET_ANDROID 35 // Check if we can use MediaDrm for this keysystem. 36 if (mozilla::java::MediaDrmProxy::IsSchemeSupported( 37 NS_ConvertUTF16toUTF8(aKeySystem))) { 38 return true; 39 } 40 // Check if we can use our bundled Clearkey plugin. 41 if (IsClearkeyKeySystem(aKeySystem)) { 42 return HaveGMPFor(nsCString(CHROMIUM_CDM_API), 43 {NS_ConvertUTF16toUTF8(aKeySystem)}); 44 } 45 #else 46 # ifdef MOZ_WMF_CDM 47 // Test only, pretend we have already installed CDMs. 48 if (StaticPrefs::media_eme_wmf_use_mock_cdm_for_external_cdms()) { 49 return true; 50 } 51 # endif 52 // Check if Widevine L3 or Clearkey has been downloaded via GMP downloader. 53 if (IsWidevineKeySystem(aKeySystem) || IsClearkeyKeySystem(aKeySystem)) { 54 return HaveGMPFor(nsCString(CHROMIUM_CDM_API), 55 {NS_ConvertUTF16toUTF8(aKeySystem)}); 56 } 57 #endif 58 59 #if MOZ_WMF_CDM 60 // Check if Widevine L1 has been downloaded via GMP downloader. 61 if (IsWidevineExperimentKeySystemAndSupported(aKeySystem)) { 62 return HaveGMPFor(nsCString(kWidevineExperimentAPIName), 63 {nsCString(kWidevineExperimentKeySystemName)}); 64 } 65 66 // PlayReady and WMF-based ClearKey are always installed, we don't need to 67 // download them. 68 if (IsPlayReadyKeySystemAndSupported(aKeySystem) || 69 IsWMFClearKeySystemAndSupported(aKeySystem)) { 70 return true; 71 } 72 #endif 73 74 return false; 75 } 76 77 /* static */ void KeySystemConfig::CreateClearKeyKeySystemConfigs( 78 const KeySystemConfigRequest& aRequest, 79 nsTArray<KeySystemConfig>& aOutConfigs) { 80 KeySystemConfig* config = aOutConfigs.AppendElement(); 81 config->mKeySystem = aRequest.mKeySystem; 82 config->mInitDataTypes.AppendElement(u"cenc"_ns); 83 config->mInitDataTypes.AppendElement(u"keyids"_ns); 84 config->mInitDataTypes.AppendElement(u"webm"_ns); 85 config->mPersistentState = Requirement::Optional; 86 config->mDistinctiveIdentifier = Requirement::NotAllowed; 87 config->mSessionTypes.AppendElement(SessionType::Temporary); 88 if (StaticPrefs::media_clearkey_persistent_license_enabled()) { 89 config->mSessionTypes.AppendElement(SessionType::PersistentLicense); 90 } 91 #if defined(XP_WIN) 92 // Clearkey CDM uses WMF's H.264 decoder on Windows. 93 auto pdmFactory = MakeRefPtr<PDMFactory>(); 94 if (!pdmFactory->SupportsMimeType("video/avc"_ns).isEmpty()) { 95 config->mMP4.SetCanDecryptAndDecode(EME_CODEC_H264); 96 } else { 97 config->mMP4.SetCanDecrypt(EME_CODEC_H264); 98 } 99 #else 100 config->mMP4.SetCanDecrypt(EME_CODEC_H264); 101 #endif 102 config->mMP4.SetCanDecrypt(EME_CODEC_AAC); 103 config->mMP4.SetCanDecrypt(EME_CODEC_FLAC); 104 config->mMP4.SetCanDecrypt(EME_CODEC_OPUS); 105 config->mMP4.SetCanDecrypt(EME_CODEC_VP9); 106 #ifdef MOZ_AV1 107 config->mMP4.SetCanDecrypt(EME_CODEC_AV1); 108 #endif 109 config->mWebM.SetCanDecrypt(EME_CODEC_VORBIS); 110 config->mWebM.SetCanDecrypt(EME_CODEC_OPUS); 111 config->mWebM.SetCanDecrypt(EME_CODEC_VP8); 112 config->mWebM.SetCanDecrypt(EME_CODEC_VP9); 113 #ifdef MOZ_AV1 114 config->mWebM.SetCanDecrypt(EME_CODEC_AV1); 115 #endif 116 117 if (StaticPrefs::media_clearkey_test_key_systems_enabled()) { 118 // Add testing key systems. These offer the same capabilities as the 119 // base clearkey system, so just clone clearkey and change the name. 120 KeySystemConfig clearkeyWithProtectionQuery{*config}; 121 clearkeyWithProtectionQuery.mKeySystem.AssignLiteral( 122 kClearKeyWithProtectionQueryKeySystemName); 123 aOutConfigs.AppendElement(std::move(clearkeyWithProtectionQuery)); 124 } 125 } 126 127 /* static */ void KeySystemConfig::CreateWivineL3KeySystemConfigs( 128 const KeySystemConfigRequest& aRequest, 129 nsTArray<KeySystemConfig>& aOutConfigs) { 130 KeySystemConfig* config = aOutConfigs.AppendElement(); 131 config->mKeySystem = aRequest.mKeySystem; 132 config->mInitDataTypes.AppendElement(u"cenc"_ns); 133 config->mInitDataTypes.AppendElement(u"keyids"_ns); 134 config->mInitDataTypes.AppendElement(u"webm"_ns); 135 config->mPersistentState = Requirement::Optional; 136 config->mDistinctiveIdentifier = Requirement::NotAllowed; 137 config->mSessionTypes.AppendElement(SessionType::Temporary); 138 #ifdef MOZ_WIDGET_ANDROID 139 config->mSessionTypes.AppendElement(SessionType::PersistentLicense); 140 #endif 141 config->mAudioRobustness.AppendElement(u"SW_SECURE_CRYPTO"_ns); 142 config->mVideoRobustness.AppendElement(u"SW_SECURE_CRYPTO"_ns); 143 config->mVideoRobustness.AppendElement(u"SW_SECURE_DECODE"_ns); 144 145 #if defined(MOZ_WIDGET_ANDROID) 146 // MediaDrm.isCryptoSchemeSupported only allows passing 147 // "video/mp4" or "video/webm" for mimetype string. 148 // See 149 // https://developer.android.com/reference/android/media/MediaDrm.html#isCryptoSchemeSupported(java.util.UUID, 150 // java.lang.String) for more detail. 151 typedef struct { 152 const nsCString& mMimeType; 153 const nsCString& mEMECodecType; 154 const char16_t* mCodecType; 155 KeySystemConfig::ContainerSupport* mSupportType; 156 } DataForValidation; 157 158 DataForValidation validationList[] = { 159 {nsCString(VIDEO_MP4), EME_CODEC_H264, java::MediaDrmProxy::AVC, 160 &config->mMP4}, 161 {nsCString(VIDEO_MP4), EME_CODEC_VP9, java::MediaDrmProxy::AVC, 162 &config->mMP4}, 163 # ifdef MOZ_AV1 164 {nsCString(VIDEO_MP4), EME_CODEC_AV1, java::MediaDrmProxy::AV1, 165 &config->mMP4}, 166 # endif 167 {nsCString(AUDIO_MP4), EME_CODEC_AAC, java::MediaDrmProxy::AAC, 168 &config->mMP4}, 169 {nsCString(AUDIO_MP4), EME_CODEC_FLAC, java::MediaDrmProxy::FLAC, 170 &config->mMP4}, 171 {nsCString(AUDIO_MP4), EME_CODEC_OPUS, java::MediaDrmProxy::OPUS, 172 &config->mMP4}, 173 {nsCString(VIDEO_WEBM), EME_CODEC_VP8, java::MediaDrmProxy::VP8, 174 &config->mWebM}, 175 {nsCString(VIDEO_WEBM), EME_CODEC_VP9, java::MediaDrmProxy::VP9, 176 &config->mWebM}, 177 # ifdef MOZ_AV1 178 {nsCString(VIDEO_WEBM), EME_CODEC_AV1, java::MediaDrmProxy::AV1, 179 &config->mWebM}, 180 # endif 181 {nsCString(AUDIO_WEBM), EME_CODEC_VORBIS, java::MediaDrmProxy::VORBIS, 182 &config->mWebM}, 183 {nsCString(AUDIO_WEBM), EME_CODEC_OPUS, java::MediaDrmProxy::OPUS, 184 &config->mWebM}, 185 }; 186 187 for (const auto& data : validationList) { 188 if (java::MediaDrmProxy::IsCryptoSchemeSupported(kWidevineKeySystemName, 189 data.mMimeType)) { 190 if (!AndroidDecoderModule::SupportsMimeType(data.mMimeType).isEmpty()) { 191 data.mSupportType->SetCanDecryptAndDecode(data.mEMECodecType); 192 } else { 193 data.mSupportType->SetCanDecrypt(data.mEMECodecType); 194 } 195 } 196 } 197 #else 198 # if defined(XP_WIN) 199 // Widevine CDM doesn't include an AAC decoder. So if WMF can't 200 // decode AAC, and a codec wasn't specified, be conservative 201 // and reject the MediaKeys request, since we assume Widevine 202 // will be used with AAC. 203 auto pdmFactory = MakeRefPtr<PDMFactory>(); 204 if (!pdmFactory->SupportsMimeType("audio/mp4a-latm"_ns).isEmpty()) { 205 config->mMP4.SetCanDecrypt(EME_CODEC_AAC); 206 } 207 # else 208 config->mMP4.SetCanDecrypt(EME_CODEC_AAC); 209 # endif 210 config->mMP4.SetCanDecrypt(EME_CODEC_FLAC); 211 config->mMP4.SetCanDecrypt(EME_CODEC_OPUS); 212 config->mMP4.SetCanDecryptAndDecode(EME_CODEC_H264); 213 config->mMP4.SetCanDecryptAndDecode(EME_CODEC_VP9); 214 # ifdef MOZ_AV1 215 config->mMP4.SetCanDecryptAndDecode(EME_CODEC_AV1); 216 # endif 217 config->mWebM.SetCanDecrypt(EME_CODEC_VORBIS); 218 config->mWebM.SetCanDecrypt(EME_CODEC_OPUS); 219 config->mWebM.SetCanDecryptAndDecode(EME_CODEC_VP8); 220 config->mWebM.SetCanDecryptAndDecode(EME_CODEC_VP9); 221 # ifdef MOZ_AV1 222 config->mWebM.SetCanDecryptAndDecode(EME_CODEC_AV1); 223 # endif 224 #endif 225 } 226 227 /* static */ 228 RefPtr<KeySystemConfig::SupportedConfigsPromise> 229 KeySystemConfig::CreateKeySystemConfigs( 230 const nsTArray<KeySystemConfigRequest>& aRequests) { 231 // Create available configs for all supported key systems in the request, but 232 // some of them might not be created immediately. 233 234 nsTArray<KeySystemConfig> outConfigs; 235 nsTArray<KeySystemConfigRequest> asyncRequests; 236 237 for (const auto& request : aRequests) { 238 const nsAString& keySystem = request.mKeySystem; 239 if (!Supports(keySystem)) { 240 continue; 241 } 242 243 if (IsClearkeyKeySystem(keySystem)) { 244 CreateClearKeyKeySystemConfigs(request, outConfigs); 245 } else if (IsWidevineKeySystem(keySystem)) { 246 CreateWivineL3KeySystemConfigs(request, outConfigs); 247 } 248 #ifdef MOZ_WMF_CDM 249 else if (IsPlayReadyKeySystemAndSupported(keySystem) || 250 IsWidevineExperimentKeySystemAndSupported(keySystem)) { 251 asyncRequests.AppendElement(request); 252 } 253 #endif 254 } 255 256 #ifdef MOZ_WMF_CDM 257 if (!asyncRequests.IsEmpty()) { 258 RefPtr<SupportedConfigsPromise::Private> promise = 259 new SupportedConfigsPromise::Private(__func__); 260 RefPtr<WMFCDMCapabilites> cdm = new WMFCDMCapabilites(); 261 cdm->GetCapabilities(asyncRequests) 262 ->Then(GetMainThreadSerialEventTarget(), __func__, 263 [syncConfigs = std::move(outConfigs), 264 promise](SupportedConfigsPromise::ResolveOrRejectValue&& 265 aResult) mutable { 266 // Return the capabilities we already know 267 if (aResult.IsReject()) { 268 promise->Resolve(std::move(syncConfigs), __func__); 269 return; 270 } 271 // Merge sync results with async results 272 auto& asyncConfigs = aResult.ResolveValue(); 273 asyncConfigs.AppendElements(std::move(syncConfigs)); 274 promise->Resolve(std::move(asyncConfigs), __func__); 275 }); 276 return promise; 277 } 278 #endif 279 return SupportedConfigsPromise::CreateAndResolve(std::move(outConfigs), 280 __func__); 281 } 282 283 /* static */ 284 void KeySystemConfig::GetGMPKeySystemConfigs(dom::Promise* aPromise) { 285 MOZ_ASSERT(aPromise); 286 287 // Generate config requests 288 const nsTArray<nsString> keySystemNames{ 289 NS_ConvertUTF8toUTF16(kClearKeyKeySystemName), 290 NS_ConvertUTF8toUTF16(kWidevineKeySystemName), 291 }; 292 nsTArray<KeySystemConfigRequest> requests; 293 for (const auto& keySystem : keySystemNames) { 294 #ifdef MOZ_WMF_CDM 295 if (IsWMFClearKeySystemAndSupported(keySystem)) { 296 // Using wmf clearkey, not gmp clearkey. 297 continue; 298 } 299 #endif 300 requests.AppendElement(KeySystemConfigRequest{ 301 keySystem, DecryptionInfo::Software, false /* IsPrivateBrowsing */}); 302 } 303 304 // Get supported configs 305 KeySystemConfig::CreateKeySystemConfigs(requests)->Then( 306 GetMainThreadSerialEventTarget(), __func__, 307 [promise = RefPtr<dom::Promise>{aPromise}]( 308 const SupportedConfigsPromise::ResolveOrRejectValue& aResult) { 309 if (aResult.IsResolve()) { 310 // Generate CDMInformation from configs 311 FallibleTArray<dom::CDMInformation> cdmInfo; 312 for (const auto& config : aResult.ResolveValue()) { 313 auto* info = cdmInfo.AppendElement(fallible); 314 if (!info) { 315 promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY); 316 return; 317 } 318 info->mKeySystemName = config.mKeySystem; 319 info->mCapabilities = config.GetDebugInfo(); 320 info->mClearlead = DoesKeySystemSupportClearLead(config.mKeySystem); 321 // GMP-based CDM doesn't support hardware decryption. 322 info->mIsHardwareDecryption = false; 323 } 324 promise->MaybeResolve(cdmInfo); 325 } else { 326 promise->MaybeReject(NS_ERROR_DOM_MEDIA_CDM_ERR); 327 } 328 }); 329 } 330 331 nsString KeySystemConfig::GetDebugInfo() const { 332 nsString debugInfo; 333 debugInfo.AppendLiteral(" key-system="); 334 debugInfo.Append(mKeySystem); 335 debugInfo.AppendLiteral(" init-data-type=["); 336 for (size_t idx = 0; idx < mInitDataTypes.Length(); idx++) { 337 debugInfo.Append(mInitDataTypes[idx]); 338 if (idx + 1 < mInitDataTypes.Length()) { 339 debugInfo.AppendLiteral(","); 340 } 341 } 342 debugInfo.AppendLiteral("]"); 343 debugInfo.AppendPrintf(" persistent=%s", EnumValueToString(mPersistentState)); 344 debugInfo.AppendPrintf(" distinctive=%s", 345 EnumValueToString(mDistinctiveIdentifier)); 346 debugInfo.AppendLiteral(" sessionType=["); 347 for (size_t idx = 0; idx < mSessionTypes.Length(); idx++) { 348 debugInfo.AppendASCII(EnumValueToString(mSessionTypes[idx])); 349 if (idx + 1 < mSessionTypes.Length()) { 350 debugInfo.AppendLiteral(","); 351 } 352 } 353 debugInfo.AppendLiteral("]"); 354 debugInfo.AppendLiteral(" video-robustness="); 355 for (size_t idx = 0; idx < mVideoRobustness.Length(); idx++) { 356 debugInfo.Append(mVideoRobustness[idx]); 357 if (idx + 1 < mVideoRobustness.Length()) { 358 debugInfo.AppendLiteral(","); 359 } 360 } 361 debugInfo.AppendLiteral(" audio-robustness="); 362 for (size_t idx = 0; idx < mAudioRobustness.Length(); idx++) { 363 debugInfo.Append(mAudioRobustness[idx]); 364 if (idx + 1 < mAudioRobustness.Length()) { 365 debugInfo.AppendLiteral(","); 366 } 367 } 368 debugInfo.AppendLiteral(" MP4={"); 369 debugInfo.Append(NS_ConvertUTF8toUTF16(mMP4.GetDebugInfo())); 370 debugInfo.AppendLiteral("}"); 371 debugInfo.AppendLiteral(" WEBM={"); 372 debugInfo.Append(NS_ConvertUTF8toUTF16(mWebM.GetDebugInfo())); 373 debugInfo.AppendLiteral("}"); 374 return debugInfo; 375 } 376 377 KeySystemConfig::SessionType ConvertToKeySystemConfigSessionType( 378 dom::MediaKeySessionType aType) { 379 switch (aType) { 380 case dom::MediaKeySessionType::Temporary: 381 return KeySystemConfig::SessionType::Temporary; 382 case dom::MediaKeySessionType::Persistent_license: 383 return KeySystemConfig::SessionType::PersistentLicense; 384 default: 385 MOZ_ASSERT_UNREACHABLE("Invalid session type"); 386 return KeySystemConfig::SessionType::Temporary; 387 } 388 } 389 390 } // namespace mozilla