Bug 1306219-Part3 Add logic to determine if Widevine keysystem is supported on Fennec. draft
authorJames Cheng <jacheng@mozilla.com>
Tue, 04 Oct 2016 11:45:34 +0800
changeset 429578 8bbb0fa9d9fd0425bc9f544aec2d21c22a85cd15
parent 429577 4ebebc9346d09f7038a2269ee899a1d02696dc28
child 535001 789a82525c78d079ac57d1bada801918c656dce4
push id33604
push userbmo:jacheng@mozilla.com
push dateWed, 26 Oct 2016 04:56:25 +0000
bugs1306219
milestone52.0a1
Bug 1306219-Part3 Add logic to determine if Widevine keysystem is supported on Fennec. MozReview-Commit-ID: 7lquhpJPYfW
dom/media/eme/MediaKeySystemAccess.cpp
mobile/android/app/mobile.js
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -32,17 +32,19 @@
 #include "gmp-audio-decode.h"
 #include "gmp-video-decode.h"
 #include "DecoderDoctorDiagnostics.h"
 #include "WebMDecoder.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "nsUnicharUtils.h"
 #include "mozilla/dom/MediaSource.h"
-
+#ifdef MOZ_WIDGET_ANDROID
+#include "FennecJNIWrappers.h"
+#endif
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess,
                                       mParent)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccess)
@@ -292,30 +294,40 @@ MediaKeySystemAccess::GetKeySystemStatus
         aOutMessage = NS_LITERAL_CSTRING("Minimum Windows version (Vista) not met for Adobe EME");
         return MediaKeySystemStatus::Cdm_not_supported;
       }
 #endif
       return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion, aOutMessage, aOutCdmVersion);
     }
   }
 
-  if (Preferences::GetBool("media.gmp-widevinecdm.visible", false)) {
-    if (IsWidevineKeySystem(aKeySystem)) {
+  if (IsWidevineKeySystem(aKeySystem)) {
+    if (Preferences::GetBool("media.gmp-widevinecdm.visible", false)) {
 #ifdef XP_WIN
       // Win Vista and later only.
       if (!IsVistaOrLater()) {
         aOutMessage = NS_LITERAL_CSTRING("Minimum Windows version (Vista) not met for Widevine EME");
         return MediaKeySystemStatus::Cdm_not_supported;
       }
 #endif
       if (!Preferences::GetBool("media.gmp-widevinecdm.enabled", false)) {
         aOutMessage = NS_LITERAL_CSTRING("Widevine EME disabled");
         return MediaKeySystemStatus::Cdm_disabled;
       }
       return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion, aOutMessage, aOutCdmVersion);
+#ifdef MOZ_WIDGET_ANDROID
+    } else if (Preferences::GetBool("media.mediadrm-widevinecdm.visible", false)) {
+        nsCString keySystem = NS_ConvertUTF16toUTF8(aKeySystem);
+        bool supported = mozilla::java::MediaDrmProxy::IsSchemeSupported(keySystem);
+        if (!supported) {
+          aOutMessage = NS_LITERAL_CSTRING("Widevine CDM is not available");
+          return MediaKeySystemStatus::Cdm_not_installed;
+        }
+        return MediaKeySystemStatus::Available;
+#endif
     }
   }
 
   return MediaKeySystemStatus::Cdm_not_supported;
 }
 
 typedef nsCString EMECodecString;
 
@@ -410,26 +422,43 @@ struct KeySystemConfig
   KeySystemFeatureSupport mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
   nsTArray<MediaKeySessionType> mSessionTypes;
   nsTArray<nsString> mVideoRobustness;
   nsTArray<nsString> mAudioRobustness;
   KeySystemContainerSupport mMP4;
   KeySystemContainerSupport mWebM;
 };
 
-StaticAutoPtr<nsTArray<KeySystemConfig>> sKeySystemConfigs;
+bool
+HavePluginForKeySystem(const nsCString& aKeySystem)
+{
+  bool havePlugin = false;
+  nsCOMPtr<mozIGeckoMediaPluginService> mps =
+    do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+  if (mps) {
+    havePlugin = HaveGMPFor(mps,
+                            aKeySystem,
+                            NS_LITERAL_CSTRING(GMP_API_DECRYPTOR));
+  }
+#ifdef MOZ_WIDGET_ANDROID
+  // Check if we can use MediaDrm for this keysystem.
+  if (!havePlugin) {
+     havePlugin = mozilla::java::MediaDrmProxy::IsSchemeSupported(aKeySystem);
+  }
+#endif
+  return havePlugin;
+}
 
-static const nsTArray<KeySystemConfig>&
+static nsTArray<KeySystemConfig>
 GetSupportedKeySystems()
 {
-  if (!sKeySystemConfigs) {
-    sKeySystemConfigs = new nsTArray<KeySystemConfig>();
-    ClearOnShutdown(&sKeySystemConfigs);
+  nsTArray<KeySystemConfig> keySystemConfigs;
 
-    {
+  {
+    if (HavePluginForKeySystem(kEMEKeySystemClearkey)) {
       KeySystemConfig clearkey;
       clearkey.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemClearkey);
       clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
       clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("keyids"));
       clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("webm"));
       clearkey.mPersistentState = KeySystemFeatureSupport::Requestable;
       clearkey.mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
       clearkey.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
@@ -449,114 +478,149 @@ GetSupportedKeySystems()
 #else
       clearkey.mMP4.SetCanDecrypt(EME_CODEC_AAC);
       clearkey.mMP4.SetCanDecrypt(EME_CODEC_H264);
 #endif
       clearkey.mWebM.SetCanDecrypt(EME_CODEC_VORBIS);
       clearkey.mWebM.SetCanDecrypt(EME_CODEC_OPUS);
       clearkey.mWebM.SetCanDecrypt(EME_CODEC_VP8);
       clearkey.mWebM.SetCanDecrypt(EME_CODEC_VP9);
-      sKeySystemConfigs->AppendElement(Move(clearkey));
+      keySystemConfigs.AppendElement(Move(clearkey));
     }
-    {
+  }
+  {
+    if (HavePluginForKeySystem(kEMEKeySystemWidevine)) {
       KeySystemConfig widevine;
       widevine.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemWidevine);
       widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
       widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("keyids"));
       widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("webm"));
       widevine.mPersistentState = KeySystemFeatureSupport::Requestable;
       widevine.mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
       widevine.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
+#ifdef MOZ_WIDGET_ANDROID
+      widevine.mSessionTypes.AppendElement(MediaKeySessionType::Persistent_license);
+#endif
       widevine.mAudioRobustness.AppendElement(NS_LITERAL_STRING("SW_SECURE_CRYPTO"));
       widevine.mVideoRobustness.AppendElement(NS_LITERAL_STRING("SW_SECURE_DECODE"));
 #if defined(XP_WIN)
       // Widevine CDM doesn't include an AAC decoder. So if WMF can't
       // decode AAC, and a codec wasn't specified, be conservative
       // and reject the MediaKeys request, since our policy is to prevent
       //  the Adobe GMP's unencrypted AAC decoding path being used to
       // decode content decrypted by the Widevine CDM.
       if (WMFDecoderModule::HasAAC()) {
         widevine.mMP4.SetCanDecrypt(EME_CODEC_AAC);
       }
-#else
+#elif !defined(MOZ_WIDGET_ANDROID)
       widevine.mMP4.SetCanDecrypt(EME_CODEC_AAC);
 #endif
+
+#if defined(MOZ_WIDGET_ANDROID)
+      using namespace mozilla::java;
+      // MediaDrm.isCryptoSchemeSupported only allows passing
+      // "video/mp4" or "video/webm" for mimetype string.
+      // See https://developer.android.com/reference/android/media/MediaDrm.html#isCryptoSchemeSupported(java.util.UUID, java.lang.String)
+      // for more detail.
+      typedef struct {
+        const nsCString& mMimeType;
+        const nsCString& mEMECodecType;
+        const char16_t* mCodecType;
+        KeySystemContainerSupport* mSupportType;
+      } DataForValidation;
+
+      DataForValidation validationList[] = {
+        { nsCString("video/mp4"), EME_CODEC_H264, MediaDrmProxy::AVC, &widevine.mMP4 },
+        { nsCString("audio/mp4"), EME_CODEC_AAC, MediaDrmProxy::AAC, &widevine.mMP4 },
+        { nsCString("video/webm"), EME_CODEC_VP8, MediaDrmProxy::VP8, &widevine.mWebM },
+        { nsCString("video/webm"), EME_CODEC_VP9, MediaDrmProxy::VP9, &widevine.mWebM},
+        { nsCString("audio/webm"), EME_CODEC_VORBIS, MediaDrmProxy::VORBIS, &widevine.mWebM},
+        { nsCString("audio/webm"), EME_CODEC_OPUS, MediaDrmProxy::OPUS, &widevine.mWebM},
+      };
+
+      for (const auto& data: validationList) {
+        if (MediaDrmProxy::IsCryptoSchemeSupported(kEMEKeySystemWidevine,
+                                                   data.mMimeType)) {
+          if (MediaDrmProxy::CanDecode(data.mCodecType)) {
+            data.mSupportType->SetCanDecryptAndDecode(data.mEMECodecType);
+          } else {
+            data.mSupportType->SetCanDecrypt(data.mEMECodecType);
+          }
+        }
+      }
+#else
       widevine.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
       widevine.mWebM.SetCanDecrypt(EME_CODEC_VORBIS);
       widevine.mWebM.SetCanDecrypt(EME_CODEC_OPUS);
       widevine.mWebM.SetCanDecryptAndDecode(EME_CODEC_VP8);
       widevine.mWebM.SetCanDecryptAndDecode(EME_CODEC_VP9);
-      sKeySystemConfigs->AppendElement(Move(widevine));
+#endif
+      keySystemConfigs.AppendElement(Move(widevine));
     }
-    {
+  }
+  {
+    if (HavePluginForKeySystem(kEMEKeySystemPrimetime)) {
       KeySystemConfig primetime;
       primetime.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemPrimetime);
       primetime.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
       primetime.mPersistentState = KeySystemFeatureSupport::Required;
       primetime.mDistinctiveIdentifier = KeySystemFeatureSupport::Required;
       primetime.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
       primetime.mMP4.SetCanDecryptAndDecode(EME_CODEC_AAC);
       primetime.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
-      sKeySystemConfigs->AppendElement(Move(primetime));
+      keySystemConfigs.AppendElement(Move(primetime));
     }
   }
-  return *sKeySystemConfigs;
+
+  return keySystemConfigs;
 }
 
-static const KeySystemConfig*
-GetKeySystemConfig(const nsAString& aKeySystem)
+static bool
+GetKeySystemConfig(const nsAString& aKeySystem, KeySystemConfig& aOutKeySystemConfig)
 {
-  for (const KeySystemConfig& config : GetSupportedKeySystems()) {
+  for (auto&& config : GetSupportedKeySystems()) {
     if (config.mKeySystem.Equals(aKeySystem)) {
-      return &config;
+      aOutKeySystemConfig = mozilla::Move(config);
+      return true;
     }
   }
-  return nullptr;
+  // No matching key system found.
+  return false;
 }
 
 /* static */
 bool
 MediaKeySystemAccess::KeySystemSupportsInitDataType(const nsAString& aKeySystem,
                                                     const nsAString& aInitDataType)
 {
-  const KeySystemConfig* implementation = GetKeySystemConfig(aKeySystem);
-  return implementation &&
-         implementation->mInitDataTypes.Contains(aInitDataType);
+  KeySystemConfig implementation;
+  return GetKeySystemConfig(aKeySystem, implementation) &&
+         implementation.mInitDataTypes.Contains(aInitDataType);
 }
 
 enum CodecType
 {
   Audio,
   Video,
   Invalid
 };
 
 static bool
-CanDecryptAndDecode(mozIGeckoMediaPluginService* aGMPService,
-                    const nsString& aKeySystem,
+CanDecryptAndDecode(const nsString& aKeySystem,
                     const nsString& aContentType,
                     CodecType aCodecType,
                     const KeySystemContainerSupport& aContainerSupport,
                     const nsTArray<EMECodecString>& aCodecs,
                     DecoderDoctorDiagnostics* aDiagnostics)
 {
   MOZ_ASSERT(aCodecType != Invalid);
-  MOZ_ASSERT(HaveGMPFor(aGMPService,
-                        NS_ConvertUTF16toUTF8(aKeySystem),
-                        NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
   for (const EMECodecString& codec : aCodecs) {
     MOZ_ASSERT(!codec.IsEmpty());
 
-    nsCString api = (aCodecType == Audio) ? NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER)
-                                          : NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER);
-    if (aContainerSupport.DecryptsAndDecodes(codec) &&
-        HaveGMPFor(aGMPService,
-                   NS_ConvertUTF16toUTF8(aKeySystem),
-                   api,
-                   codec)) {
+    if (aContainerSupport.DecryptsAndDecodes(codec)) {
       // GMP can decrypt-and-decode this codec.
       continue;
     }
 
     if (aContainerSupport.Decrypts(codec) &&
         NS_SUCCEEDED(MediaSource::IsTypeSupported(aContentType, aDiagnostics))) {
       // GMP can decrypt and is allowed to return compressed samples to
       // Gecko to decode, and Gecko has a decoder.
@@ -680,17 +744,16 @@ IsParameterUnrecognized(const nsAString&
     }
   }
   return false;
 }
 
 // 3.1.2.3 Get Supported Capabilities for Audio/Video Type
 static Sequence<MediaKeySystemMediaCapability>
 GetSupportedCapabilities(const CodecType aCodecType,
-                         mozIGeckoMediaPluginService* aGMPService,
                          const nsTArray<MediaKeySystemMediaCapability>& aRequestedCapabilities,
                          const MediaKeySystemConfiguration& aPartialConfig,
                          const KeySystemConfig& aKeySystem,
                          DecoderDoctorDiagnostics* aDiagnostics)
 {
   // Let local accumulated configuration be a local copy of partial configuration.
   // (Note: It's not necessary for us to maintain a local copy, as we don't need
   // to test whether capabilites from previous calls to this algorithm work with
@@ -866,18 +929,17 @@ GetSupportedCapabilities(const CodecType
       // Note: specified robustness requirements are satisfied.
     }
 
     // If the user agent and implementation definitely support playback of
     // encrypted media data for the combination of container, media types,
     // robustness and local accumulated configuration in combination with
     // restrictions...
     const auto& containerSupport = isMP4 ? aKeySystem.mMP4 : aKeySystem.mWebM;
-    if (!CanDecryptAndDecode(aGMPService,
-                             aKeySystem.mKeySystem,
+    if (!CanDecryptAndDecode(aKeySystem.mKeySystem,
                              contentType,
                              majorType,
                              containerSupport,
                              codecs,
                              aDiagnostics)) {
         EME_LOG("MediaKeySystemConfiguration (label='%s') "
                 "MediaKeySystemMediaCapability('%s','%s') unsupported; "
                 "codec unsupported by CDM requested.",
@@ -969,18 +1031,17 @@ UnboxSessionTypes(const Optional<Sequenc
     // Note: fallible. Results in an empty array.
     sessionTypes.AppendElement(NS_ConvertUTF8toUTF16(nsDependentCString(temporary)), mozilla::fallible);
   }
   return sessionTypes;
 }
 
 // 3.1.2.2 Get Supported Configuration and Consent
 static bool
-GetSupportedConfig(mozIGeckoMediaPluginService* aGMPService,
-                   const KeySystemConfig& aKeySystem,
+GetSupportedConfig(const KeySystemConfig& aKeySystem,
                    const MediaKeySystemConfiguration& aCandidate,
                    MediaKeySystemConfiguration& aOutConfig,
                    DecoderDoctorDiagnostics* aDiagnostics)
 {
   // Let accumulated configuration be a new MediaKeySystemConfiguration dictionary.
   MediaKeySystemConfiguration config;
   // Set the label member of accumulated configuration to equal the label member of
   // candidate configuration.
@@ -1089,17 +1150,16 @@ GetSupportedConfig(mozIGeckoMediaPluginS
   // If the videoCapabilities member in candidate configuration is non-empty:
   if (!aCandidate.mVideoCapabilities.IsEmpty()) {
     // Let video capabilities be the result of executing the Get Supported
     // Capabilities for Audio/Video Type algorithm on Video, candidate
     // configuration's videoCapabilities member, accumulated configuration,
     // and restrictions.
     Sequence<MediaKeySystemMediaCapability> caps =
       GetSupportedCapabilities(Video,
-                               aGMPService,
                                aCandidate.mVideoCapabilities,
                                config,
                                aKeySystem,
                                aDiagnostics);
     // If video capabilities is null, return NotSupported.
     if (caps.IsEmpty()) {
       EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
               "no supported video capabilities.",
@@ -1115,17 +1175,16 @@ GetSupportedConfig(mozIGeckoMediaPluginS
 
   // If the audioCapabilities member in candidate configuration is non-empty:
   if (!aCandidate.mAudioCapabilities.IsEmpty()) {
     // Let audio capabilities be the result of executing the Get Supported Capabilities
     // for Audio/Video Type algorithm on Audio, candidate configuration's audioCapabilities
     // member, accumulated configuration, and restrictions.
     Sequence<MediaKeySystemMediaCapability> caps =
       GetSupportedCapabilities(Audio,
-                               aGMPService,
                                aCandidate.mAudioCapabilities,
                                config,
                                aKeySystem,
                                aDiagnostics);
     // If audio capabilities is null, return NotSupported.
     if (caps.IsEmpty()) {
       EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
               "no supported audio capabilities.",
@@ -1198,31 +1257,22 @@ GetSupportedConfig(mozIGeckoMediaPluginS
 
 /* static */
 bool
 MediaKeySystemAccess::GetSupportedConfig(const nsAString& aKeySystem,
                                          const Sequence<MediaKeySystemConfiguration>& aConfigs,
                                          MediaKeySystemConfiguration& aOutConfig,
                                          DecoderDoctorDiagnostics* aDiagnostics)
 {
-  nsCOMPtr<mozIGeckoMediaPluginService> mps =
-    do_GetService("@mozilla.org/gecko-media-plugin-service;1");
-  if (NS_WARN_IF(!mps)) {
-    return false;
-  }
-  const KeySystemConfig* implementation = nullptr;
-  if (!HaveGMPFor(mps,
-                  NS_ConvertUTF16toUTF8(aKeySystem),
-                  NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)) ||
-      !(implementation = GetKeySystemConfig(aKeySystem))) {
+  KeySystemConfig implementation;
+  if (!GetKeySystemConfig(aKeySystem, implementation)) {
     return false;
   }
   for (const MediaKeySystemConfiguration& candidate : aConfigs) {
-    if (mozilla::dom::GetSupportedConfig(mps,
-                                         *implementation,
+    if (mozilla::dom::GetSupportedConfig(implementation,
                                          candidate,
                                          aOutConfig,
                                          aDiagnostics)) {
       return true;
     }
   }
 
   return false;
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -619,16 +619,18 @@ pref("media.video-queue.default-size", 3
 pref("media.android-media-codec.enabled", true);
 pref("media.android-media-codec.preferred", true);
 // Run decoder in seperate process.
 pref("media.android-remote-codec.enabled", false);
 
 // Enable MSE
 pref("media.mediasource.enabled", true);
 
+pref("media.mediadrm-widevinecdm.visible", true);
+
 // optimize images memory usage
 pref("image.downscale-during-decode.enabled", true);
 
 pref("browser.safebrowsing.downloads.enabled", false);
 
 pref("browser.safebrowsing.id", @MOZ_APP_UA_NAME@);
 
 // True if this is the first time we are showing about:firstrun