1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 6 package org.mozilla.gecko.media; 7 8 import java.util.ArrayList; 9 import java.util.UUID; 10 11 import org.mozilla.gecko.mozglue.JNIObject; 12 import org.mozilla.gecko.annotation.WrapForJNI; 13 14 import android.annotation.SuppressLint; 15 import android.media.MediaCrypto; 16 import android.media.MediaDrm; 17 import android.os.Build; 18 import android.util.Log; 19 20 public final class MediaDrmProxy { 21 private static final String LOGTAG = "GeckoMediaDrmProxy"; 22 private static final boolean DEBUG = false; 23 private static final UUID WIDEVINE_SCHEME_UUID = 24 new UUID(0xedef8ba979d64aceL, 0xa3c827dcd51d21edL); 25 26 private static final String WIDEVINE_KEY_SYSTEM = "com.widevine.alpha"; 27 @WrapForJNI 28 private static final String AAC = "audio/mp4a-latm"; 29 @WrapForJNI 30 private static final String AVC = "video/avc"; 31 @WrapForJNI 32 private static final String VORBIS = "audio/vorbis"; 33 @WrapForJNI 34 private static final String VP8 = "video/x-vnd.on2.vp8"; 35 @WrapForJNI 36 private static final String VP9 = "video/x-vnd.on2.vp9"; 37 @WrapForJNI 38 private static final String OPUS = "audio/opus"; 39 @WrapForJNI 40 private static final String FLAC = "audio/flac"; 41 42 public static final ArrayList<MediaDrmProxy> sProxyList = new ArrayList<MediaDrmProxy>(); 43 44 // A flag to avoid using the native object that has been destroyed. 45 private boolean mDestroyed; 46 private GeckoMediaDrm mImpl; 47 private String mDrmStubId; 48 isSystemSupported()49 private static boolean isSystemSupported() { 50 // Support versions >= Marshmallow 51 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { 52 if (DEBUG) Log.d(LOGTAG, "System Not supported !!, current SDK version is " + Build.VERSION.SDK_INT); 53 return false; 54 } 55 return true; 56 } 57 58 @SuppressLint("NewApi") 59 @WrapForJNI isSchemeSupported(final String keySystem)60 public static boolean isSchemeSupported(final String keySystem) { 61 if (!isSystemSupported()) { 62 return false; 63 } 64 if (keySystem.equals(WIDEVINE_KEY_SYSTEM)) { 65 return MediaDrm.isCryptoSchemeSupported(WIDEVINE_SCHEME_UUID) 66 && MediaCrypto.isCryptoSchemeSupported(WIDEVINE_SCHEME_UUID); 67 } 68 if (DEBUG) Log.d(LOGTAG, "isSchemeSupported key sytem = " + keySystem); 69 return false; 70 } 71 72 @SuppressLint("NewApi") 73 @WrapForJNI IsCryptoSchemeSupported(final String keySystem, final String container)74 public static boolean IsCryptoSchemeSupported(final String keySystem, 75 final String container) { 76 if (!isSystemSupported()) { 77 return false; 78 } 79 if (keySystem.equals(WIDEVINE_KEY_SYSTEM)) { 80 return MediaDrm.isCryptoSchemeSupported(WIDEVINE_SCHEME_UUID, container); 81 } 82 if (DEBUG) Log.d(LOGTAG, "cannot decrypt key sytem = " + keySystem + ", container = " + container); 83 return false; 84 } 85 86 // Interface for callback to native. 87 public interface Callbacks { onSessionCreated(int createSessionToken, int promiseId, byte[] sessionId, byte[] request)88 void onSessionCreated(int createSessionToken, 89 int promiseId, 90 byte[] sessionId, 91 byte[] request); 92 onSessionUpdated(int promiseId, byte[] sessionId)93 void onSessionUpdated(int promiseId, byte[] sessionId); 94 onSessionClosed(int promiseId, byte[] sessionId)95 void onSessionClosed(int promiseId, byte[] sessionId); 96 onSessionMessage(byte[] sessionId, int sessionMessageType, byte[] request)97 void onSessionMessage(byte[] sessionId, 98 int sessionMessageType, 99 byte[] request); 100 onSessionError(byte[] sessionId, String message)101 void onSessionError(byte[] sessionId, 102 String message); 103 104 // MediaDrm.KeyStatus is available in API level 23(M) 105 // https://developer.android.com/reference/android/media/MediaDrm.KeyStatus.html 106 // For compatibility between L and M above, we'll unwrap the KeyStatus structure 107 // and store the keyid and status into SessionKeyInfo and pass to native(MediaDrmCDMProxy). onSessionBatchedKeyChanged(byte[] sessionId, SessionKeyInfo[] keyInfos)108 void onSessionBatchedKeyChanged(byte[] sessionId, 109 SessionKeyInfo[] keyInfos); 110 onRejectPromise(int promiseId, String message)111 void onRejectPromise(int promiseId, 112 String message); 113 } // Callbacks 114 115 public static class NativeMediaDrmProxyCallbacks extends JNIObject implements Callbacks { 116 @WrapForJNI(calledFrom = "gecko") NativeMediaDrmProxyCallbacks()117 NativeMediaDrmProxyCallbacks() {} 118 119 @Override 120 @WrapForJNI(dispatchTo = "gecko") onSessionCreated(int createSessionToken, int promiseId, byte[] sessionId, byte[] request)121 public native void onSessionCreated(int createSessionToken, 122 int promiseId, 123 byte[] sessionId, 124 byte[] request); 125 126 @Override 127 @WrapForJNI(dispatchTo = "gecko") onSessionUpdated(int promiseId, byte[] sessionId)128 public native void onSessionUpdated(int promiseId, byte[] sessionId); 129 130 @Override 131 @WrapForJNI(dispatchTo = "gecko") onSessionClosed(int promiseId, byte[] sessionId)132 public native void onSessionClosed(int promiseId, byte[] sessionId); 133 134 @Override 135 @WrapForJNI(dispatchTo = "gecko") onSessionMessage(byte[] sessionId, int sessionMessageType, byte[] request)136 public native void onSessionMessage(byte[] sessionId, 137 int sessionMessageType, 138 byte[] request); 139 140 @Override 141 @WrapForJNI(dispatchTo = "gecko") onSessionError(byte[] sessionId, String message)142 public native void onSessionError(byte[] sessionId, 143 String message); 144 145 @Override 146 @WrapForJNI(dispatchTo = "gecko") onSessionBatchedKeyChanged(byte[] sessionId, SessionKeyInfo[] keyInfos)147 public native void onSessionBatchedKeyChanged(byte[] sessionId, 148 SessionKeyInfo[] keyInfos); 149 150 @Override 151 @WrapForJNI(dispatchTo = "gecko") onRejectPromise(int promiseId, String message)152 public native void onRejectPromise(int promiseId, 153 String message); 154 155 @Override // JNIObject disposeNative()156 protected void disposeNative() { 157 throw new UnsupportedOperationException(); 158 } 159 } // NativeMediaDrmProxyCallbacks 160 161 // A proxy to callback from LocalMediaDrmBridge to native instance. 162 public static class MediaDrmProxyCallbacks implements GeckoMediaDrm.Callbacks { 163 private final Callbacks mNativeCallbacks; 164 private final MediaDrmProxy mProxy; 165 MediaDrmProxyCallbacks(final MediaDrmProxy proxy, final Callbacks callbacks)166 public MediaDrmProxyCallbacks(final MediaDrmProxy proxy, final Callbacks callbacks) { 167 mNativeCallbacks = callbacks; 168 mProxy = proxy; 169 } 170 171 @Override onSessionCreated(final int createSessionToken, final int promiseId, final byte[] sessionId, final byte[] request)172 public void onSessionCreated(final int createSessionToken, 173 final int promiseId, 174 final byte[] sessionId, 175 final byte[] request) { 176 if (!mProxy.isDestroyed()) { 177 mNativeCallbacks.onSessionCreated(createSessionToken, 178 promiseId, 179 sessionId, 180 request); 181 } 182 } 183 184 @Override onSessionUpdated(final int promiseId, final byte[] sessionId)185 public void onSessionUpdated(final int promiseId, final byte[] sessionId) { 186 if (!mProxy.isDestroyed()) { 187 mNativeCallbacks.onSessionUpdated(promiseId, sessionId); 188 } 189 } 190 191 @Override onSessionClosed(final int promiseId, final byte[] sessionId)192 public void onSessionClosed(final int promiseId, final byte[] sessionId) { 193 if (!mProxy.isDestroyed()) { 194 mNativeCallbacks.onSessionClosed(promiseId, sessionId); 195 } 196 } 197 198 @Override onSessionMessage(final byte[] sessionId, final int sessionMessageType, final byte[] request)199 public void onSessionMessage(final byte[] sessionId, 200 final int sessionMessageType, 201 final byte[] request) { 202 if (!mProxy.isDestroyed()) { 203 mNativeCallbacks.onSessionMessage(sessionId, sessionMessageType, request); 204 } 205 } 206 207 @Override onSessionError(final byte[] sessionId, final String message)208 public void onSessionError(final byte[] sessionId, 209 final String message) { 210 if (!mProxy.isDestroyed()) { 211 mNativeCallbacks.onSessionError(sessionId, message); 212 } 213 } 214 215 @Override onSessionBatchedKeyChanged(final byte[] sessionId, final SessionKeyInfo[] keyInfos)216 public void onSessionBatchedKeyChanged(final byte[] sessionId, 217 final SessionKeyInfo[] keyInfos) { 218 if (!mProxy.isDestroyed()) { 219 mNativeCallbacks.onSessionBatchedKeyChanged(sessionId, keyInfos); 220 } 221 } 222 223 @Override onRejectPromise(final int promiseId, final String message)224 public void onRejectPromise(final int promiseId, 225 final String message) { 226 if (!mProxy.isDestroyed()) { 227 mNativeCallbacks.onRejectPromise(promiseId, message); 228 } 229 } 230 } // MediaDrmProxyCallbacks 231 isDestroyed()232 public boolean isDestroyed() { 233 return mDestroyed; 234 } 235 236 @WrapForJNI(calledFrom = "gecko") create(final String keySystem, final Callbacks nativeCallbacks)237 public static MediaDrmProxy create(final String keySystem, 238 final Callbacks nativeCallbacks) { 239 final MediaDrmProxy proxy = new MediaDrmProxy(keySystem, nativeCallbacks); 240 return proxy; 241 } 242 MediaDrmProxy(final String keySystem, final Callbacks nativeCallbacks)243 MediaDrmProxy(final String keySystem, final Callbacks nativeCallbacks) { 244 if (DEBUG) Log.d(LOGTAG, "Constructing MediaDrmProxy"); 245 try { 246 mDrmStubId = UUID.randomUUID().toString(); 247 final IMediaDrmBridge remoteBridge = 248 RemoteManager.getInstance().createRemoteMediaDrmBridge(keySystem, mDrmStubId); 249 mImpl = new RemoteMediaDrmBridge(remoteBridge); 250 mImpl.setCallbacks(new MediaDrmProxyCallbacks(this, nativeCallbacks)); 251 sProxyList.add(this); 252 } catch (final Exception e) { 253 Log.e(LOGTAG, "Constructing MediaDrmProxy ... error", e); 254 } 255 } 256 257 @WrapForJNI createSession(final int createSessionToken, final int promiseId, final String initDataType, final byte[] initData)258 private void createSession(final int createSessionToken, 259 final int promiseId, 260 final String initDataType, 261 final byte[] initData) { 262 if (DEBUG) Log.d(LOGTAG, "createSession, promiseId = " + promiseId); 263 mImpl.createSession(createSessionToken, 264 promiseId, 265 initDataType, 266 initData); 267 } 268 269 @WrapForJNI updateSession(final int promiseId, final String sessionId, final byte[] response)270 private void updateSession(final int promiseId, final String sessionId, final byte[] response) { 271 if (DEBUG) Log.d(LOGTAG, "updateSession, primiseId(" + promiseId + "sessionId(" + sessionId + ")"); 272 mImpl.updateSession(promiseId, sessionId, response); 273 } 274 275 @WrapForJNI closeSession(final int promiseId, final String sessionId)276 private void closeSession(final int promiseId, final String sessionId) { 277 if (DEBUG) Log.d(LOGTAG, "closeSession, primiseId(" + promiseId + "sessionId(" + sessionId + ")"); 278 mImpl.closeSession(promiseId, sessionId); 279 } 280 281 @WrapForJNI(calledFrom = "gecko") getStubId()282 private String getStubId() { 283 return mDrmStubId; 284 } 285 286 @WrapForJNI setServerCertificate(final byte[] cert)287 public boolean setServerCertificate(final byte[] cert) { 288 try { 289 mImpl.setServerCertificate(cert); 290 return true; 291 } catch (final RuntimeException e) { 292 return false; 293 } 294 } 295 296 // Get corresponding MediaCrypto object by a generated UUID for MediaCodec. 297 // Will be called on MediaFormatReader's TaskQueue. 298 @WrapForJNI getMediaCrypto(final String stubId)299 public static MediaCrypto getMediaCrypto(final String stubId) { 300 for (final MediaDrmProxy proxy : sProxyList) { 301 if (proxy.getStubId().equals(stubId)) { 302 return proxy.getMediaCryptoFromBridge(); 303 } 304 } 305 if (DEBUG) Log.d(LOGTAG, " NULL crytpo "); 306 return null; 307 } 308 309 @WrapForJNI // Called when natvie object is destroyed. destroy()310 private void destroy() { 311 if (DEBUG) Log.d(LOGTAG, "destroy!! Native object is destroyed."); 312 if (mDestroyed) { 313 return; 314 } 315 mDestroyed = true; 316 release(); 317 } 318 release()319 private void release() { 320 if (DEBUG) Log.d(LOGTAG, "release"); 321 sProxyList.remove(this); 322 mImpl.release(); 323 } 324 getMediaCryptoFromBridge()325 private MediaCrypto getMediaCryptoFromBridge() { 326 return mImpl != null ? mImpl.getMediaCrypto() : null; 327 } 328 } 329