package com.kaltura.playersdk.drm; import android.annotation.TargetApi; import android.media.MediaCrypto; import android.media.MediaCryptoException; import android.media.MediaDrm; import android.media.NotProvisionedException; import android.media.UnsupportedSchemeException; import android.os.Build; import android.support.annotation.NonNull; import android.util.Log; import com.google.android.exoplayer.drm.DrmInitData; import com.google.android.exoplayer.drm.DrmSessionManager; import com.google.android.exoplayer.drm.UnsupportedDrmException; import java.util.LinkedHashMap; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import static com.google.android.libraries.mediaframework.exoplayerextensions.ExoplayerUtil.WIDEVINE_UUID; import static com.kaltura.playersdk.helpers.KStringUtilities.toHexString; /** * Created by noamt on 04/05/2016. */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) class OfflineDrmSessionManager implements DrmSessionManager { private static final String TAG = "OfflineDrmSessionMgr"; private final MediaDrm mMediaDrm; private final OfflineKeySetStorage mStorage; private MediaCrypto mMediaCrypto; private MediaDrmSession mSession; private int mState = STATE_CLOSED; private Exception mLastError; private AtomicInteger mOpenCount = new AtomicInteger(0); OfflineDrmSessionManager(OfflineKeySetStorage storage) throws UnsupportedDrmException { mStorage = storage; try { mMediaDrm = new MediaDrm(WIDEVINE_UUID); OfflineDrmManager.printAllProperties(mMediaDrm); mMediaDrm.setOnEventListener(new MediaDrm.OnEventListener() { @Override public void onEvent(@NonNull MediaDrm md, byte[] sessionId, int event, int extra, byte[] data) { Log.d(TAG, "onEvent:" + toHexString(sessionId) + ":" + event + ":" + extra + ":" + toHexString(data)); } }); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { setOnKeyStatusChangeListener(); } } catch (UnsupportedSchemeException e) { throw new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME, e); } } @TargetApi(Build.VERSION_CODES.M) private void setOnKeyStatusChangeListener() { mMediaDrm.setOnKeyStatusChangeListener(new MediaDrm.OnKeyStatusChangeListener() { @Override public void onKeyStatusChange(@NonNull MediaDrm md, @NonNull byte[] sessionId, @NonNull List<MediaDrm.KeyStatus> keyInformation, boolean hasNewUsableKey) { Log.d(TAG, "onKeyStatusChange:" + toHexString(sessionId) + ":" + hasNewUsableKey); logKeyInformation(keyInformation); } }, null); } private void onError(Exception error) { mLastError = error; mState = STATE_ERROR; } @TargetApi(Build.VERSION_CODES.M) private void logKeyInformation(List<MediaDrm.KeyStatus> keyInformation) { LinkedHashMap<String, Integer> map = new LinkedHashMap<>(); for (MediaDrm.KeyStatus keyStatus : keyInformation) { map.put(toHexString(keyStatus.getKeyId()), keyStatus.getStatusCode()); } Log.d(TAG, "keyInformation:" + map); } @Override public void open(DrmInitData drmInitData) { if (mOpenCount.incrementAndGet() != 1) { return; } mState = STATE_OPENING; DrmInitData.SchemeInitData schemeInitData = OfflineDrmManager.getWidevineInitData(drmInitData); if (schemeInitData == null) { onError(new IllegalStateException("Widevine PSSH not found")); return; } byte[] initData = schemeInitData.data; try { mSession = OfflineDrmManager.openSessionWithKeys(mMediaDrm, mStorage, initData); mMediaCrypto = new MediaCrypto(WIDEVINE_UUID, mSession.getId()); mState = STATE_OPENED_WITH_KEYS; } catch (NotProvisionedException e) { throw new WidevineNotSupportedException(e); } catch (MediaCryptoException e) { Log.e(TAG, "Can't create MediaCrypto for offline Widevine playback", e); onError(e); } catch (RuntimeException e) { throw e; } catch (Exception e) { onError(e); } } @Override public void close() { if (mOpenCount.decrementAndGet() != 0) { return; } mMediaCrypto.release(); mMediaCrypto = null; mLastError = null; if (mSession != null) { mSession.close(); mSession = null; } mState = STATE_CLOSED; } @Override public int getState() { return mState; } @Override public MediaCrypto getMediaCrypto() { return mMediaCrypto; } @Override public boolean requiresSecureDecoderComponent(String mimeType) { return mMediaCrypto.requiresSecureDecoderComponent(mimeType); } @Override public Exception getError() { return mLastError; } }