summaryrefslogtreecommitdiffstats
path: root/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java')
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java691
1 files changed, 691 insertions, 0 deletions
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java
new file mode 100644
index 0000000000..683862b99a
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java
@@ -0,0 +1,691 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.drm;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.drm.ExoMediaDrm.OnEventListener;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.EventDispatcher;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Log;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/** A {@link DrmSessionManager} that supports playbacks using {@link ExoMediaDrm}. */
+@TargetApi(18)
+public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSessionManager<T> {
+
+ /**
+ * Builder for {@link DefaultDrmSessionManager} instances.
+ *
+ * <p>See {@link #Builder} for the list of default values.
+ */
+ public static final class Builder {
+
+ private final HashMap<String, String> keyRequestParameters;
+ private UUID uuid;
+ private ExoMediaDrm.Provider<ExoMediaCrypto> exoMediaDrmProvider;
+ private boolean multiSession;
+ private int[] useDrmSessionsForClearContentTrackTypes;
+ private boolean playClearSamplesWithoutKeys;
+ private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
+
+ /**
+ * Creates a builder with default values. The default values are:
+ *
+ * <ul>
+ * <li>{@link #setKeyRequestParameters keyRequestParameters}: An empty map.
+ * <li>{@link #setUuidAndExoMediaDrmProvider UUID}: {@link C#WIDEVINE_UUID}.
+ * <li>{@link #setUuidAndExoMediaDrmProvider ExoMediaDrm.Provider}: {@link
+ * FrameworkMediaDrm#DEFAULT_PROVIDER}.
+ * <li>{@link #setMultiSession multiSession}: {@code false}.
+ * <li>{@link #setUseDrmSessionsForClearContent useDrmSessionsForClearContent}: No tracks.
+ * <li>{@link #setPlayClearSamplesWithoutKeys playClearSamplesWithoutKeys}: {@code false}.
+ * <li>{@link #setLoadErrorHandlingPolicy LoadErrorHandlingPolicy}: {@link
+ * DefaultLoadErrorHandlingPolicy}.
+ * </ul>
+ */
+ @SuppressWarnings("unchecked")
+ public Builder() {
+ keyRequestParameters = new HashMap<>();
+ uuid = C.WIDEVINE_UUID;
+ exoMediaDrmProvider = (ExoMediaDrm.Provider) FrameworkMediaDrm.DEFAULT_PROVIDER;
+ loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
+ useDrmSessionsForClearContentTrackTypes = new int[0];
+ }
+
+ /**
+ * Sets the key request parameters to pass as the last argument to {@link
+ * ExoMediaDrm#getKeyRequest(byte[], List, int, HashMap)}.
+ *
+ * <p>Custom data for PlayReady should be set under {@link #PLAYREADY_CUSTOM_DATA_KEY}.
+ *
+ * @param keyRequestParameters A map with parameters.
+ * @return This builder.
+ */
+ public Builder setKeyRequestParameters(Map<String, String> keyRequestParameters) {
+ this.keyRequestParameters.clear();
+ this.keyRequestParameters.putAll(Assertions.checkNotNull(keyRequestParameters));
+ return this;
+ }
+
+ /**
+ * Sets the UUID of the DRM scheme and the {@link ExoMediaDrm.Provider} to use.
+ *
+ * @param uuid The UUID of the DRM scheme.
+ * @param exoMediaDrmProvider The {@link ExoMediaDrm.Provider}.
+ * @return This builder.
+ */
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public Builder setUuidAndExoMediaDrmProvider(
+ UUID uuid, ExoMediaDrm.Provider exoMediaDrmProvider) {
+ this.uuid = Assertions.checkNotNull(uuid);
+ this.exoMediaDrmProvider = Assertions.checkNotNull(exoMediaDrmProvider);
+ return this;
+ }
+
+ /**
+ * Sets whether this session manager is allowed to acquire multiple simultaneous sessions.
+ *
+ * <p>Users should pass false when a single key request will obtain all keys required to decrypt
+ * the associated content. {@code multiSession} is required when content uses key rotation.
+ *
+ * @param multiSession Whether this session manager is allowed to acquire multiple simultaneous
+ * sessions.
+ * @return This builder.
+ */
+ public Builder setMultiSession(boolean multiSession) {
+ this.multiSession = multiSession;
+ return this;
+ }
+
+ /**
+ * Sets whether this session manager should attach {@link DrmSession DrmSessions} to the clear
+ * sections of the media content.
+ *
+ * <p>Using {@link DrmSession DrmSessions} for clear content avoids the recreation of decoders
+ * when transitioning between clear and encrypted sections of content.
+ *
+ * @param useDrmSessionsForClearContentTrackTypes The track types ({@link C#TRACK_TYPE_AUDIO}
+ * and/or {@link C#TRACK_TYPE_VIDEO}) for which to use a {@link DrmSession} regardless of
+ * whether the content is clear or encrypted.
+ * @return This builder.
+ * @throws IllegalArgumentException If {@code useDrmSessionsForClearContentTrackTypes} contains
+ * track types other than {@link C#TRACK_TYPE_AUDIO} and {@link C#TRACK_TYPE_VIDEO}.
+ */
+ public Builder setUseDrmSessionsForClearContent(
+ int... useDrmSessionsForClearContentTrackTypes) {
+ for (int trackType : useDrmSessionsForClearContentTrackTypes) {
+ Assertions.checkArgument(
+ trackType == C.TRACK_TYPE_VIDEO || trackType == C.TRACK_TYPE_AUDIO);
+ }
+ this.useDrmSessionsForClearContentTrackTypes =
+ useDrmSessionsForClearContentTrackTypes.clone();
+ return this;
+ }
+
+ /**
+ * Sets whether clear samples within protected content should be played when keys for the
+ * encrypted part of the content have yet to be loaded.
+ *
+ * @param playClearSamplesWithoutKeys Whether clear samples within protected content should be
+ * played when keys for the encrypted part of the content have yet to be loaded.
+ * @return This builder.
+ */
+ public Builder setPlayClearSamplesWithoutKeys(boolean playClearSamplesWithoutKeys) {
+ this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
+ return this;
+ }
+
+ /**
+ * Sets the {@link LoadErrorHandlingPolicy} for key and provisioning requests.
+ *
+ * @param loadErrorHandlingPolicy A {@link LoadErrorHandlingPolicy}.
+ * @return This builder.
+ */
+ public Builder setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
+ this.loadErrorHandlingPolicy = Assertions.checkNotNull(loadErrorHandlingPolicy);
+ return this;
+ }
+
+ /** Builds a {@link DefaultDrmSessionManager} instance. */
+ public DefaultDrmSessionManager<ExoMediaCrypto> build(MediaDrmCallback mediaDrmCallback) {
+ return new DefaultDrmSessionManager<>(
+ uuid,
+ exoMediaDrmProvider,
+ mediaDrmCallback,
+ keyRequestParameters,
+ multiSession,
+ useDrmSessionsForClearContentTrackTypes,
+ playClearSamplesWithoutKeys,
+ loadErrorHandlingPolicy);
+ }
+ }
+
+ /**
+ * Signals that the {@link DrmInitData} passed to {@link #acquireSession} does not contain does
+ * not contain scheme data for the required UUID.
+ */
+ public static final class MissingSchemeDataException extends Exception {
+
+ private MissingSchemeDataException(UUID uuid) {
+ super("Media does not support uuid: " + uuid);
+ }
+ }
+
+ /**
+ * A key for specifying PlayReady custom data in the key request parameters passed to {@link
+ * Builder#setKeyRequestParameters(Map)}.
+ */
+ public static final String PLAYREADY_CUSTOM_DATA_KEY = "PRCustomData";
+
+ /**
+ * Determines the action to be done after a session acquired. One of {@link #MODE_PLAYBACK},
+ * {@link #MODE_QUERY}, {@link #MODE_DOWNLOAD} or {@link #MODE_RELEASE}.
+ */
+ @Documented
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({MODE_PLAYBACK, MODE_QUERY, MODE_DOWNLOAD, MODE_RELEASE})
+ public @interface Mode {}
+ /**
+ * Loads and refreshes (if necessary) a license for playback. Supports streaming and offline
+ * licenses.
+ */
+ public static final int MODE_PLAYBACK = 0;
+ /** Restores an offline license to allow its status to be queried. */
+ public static final int MODE_QUERY = 1;
+ /** Downloads an offline license or renews an existing one. */
+ public static final int MODE_DOWNLOAD = 2;
+ /** Releases an existing offline license. */
+ public static final int MODE_RELEASE = 3;
+ /** Number of times to retry for initial provisioning and key request for reporting error. */
+ public static final int INITIAL_DRM_REQUEST_RETRY_COUNT = 3;
+
+ private static final String TAG = "DefaultDrmSessionMgr";
+
+ private final UUID uuid;
+ private final ExoMediaDrm.Provider<T> exoMediaDrmProvider;
+ private final MediaDrmCallback callback;
+ private final HashMap<String, String> keyRequestParameters;
+ private final EventDispatcher<DefaultDrmSessionEventListener> eventDispatcher;
+ private final boolean multiSession;
+ private final int[] useDrmSessionsForClearContentTrackTypes;
+ private final boolean playClearSamplesWithoutKeys;
+ private final ProvisioningManagerImpl provisioningManagerImpl;
+ private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
+
+ private final List<DefaultDrmSession<T>> sessions;
+ private final List<DefaultDrmSession<T>> provisioningSessions;
+
+ private int prepareCallsCount;
+ @Nullable private ExoMediaDrm<T> exoMediaDrm;
+ @Nullable private DefaultDrmSession<T> placeholderDrmSession;
+ @Nullable private DefaultDrmSession<T> noMultiSessionDrmSession;
+ @Nullable private Looper playbackLooper;
+ private int mode;
+ @Nullable private byte[] offlineLicenseKeySetId;
+
+ /* package */ volatile @Nullable MediaDrmHandler mediaDrmHandler;
+
+ /**
+ * @param uuid The UUID of the drm scheme.
+ * @param exoMediaDrm An underlying {@link ExoMediaDrm} for use by the manager.
+ * @param callback Performs key and provisioning requests.
+ * @param keyRequestParameters An optional map of parameters to pass as the last argument to
+ * {@link ExoMediaDrm#getKeyRequest(byte[], List, int, HashMap)}. May be null.
+ * @deprecated Use {@link Builder} instead.
+ */
+ @SuppressWarnings("deprecation")
+ @Deprecated
+ public DefaultDrmSessionManager(
+ UUID uuid,
+ ExoMediaDrm<T> exoMediaDrm,
+ MediaDrmCallback callback,
+ @Nullable HashMap<String, String> keyRequestParameters) {
+ this(
+ uuid,
+ exoMediaDrm,
+ callback,
+ keyRequestParameters == null ? new HashMap<>() : keyRequestParameters,
+ /* multiSession= */ false,
+ INITIAL_DRM_REQUEST_RETRY_COUNT);
+ }
+
+ /**
+ * @param uuid The UUID of the drm scheme.
+ * @param exoMediaDrm An underlying {@link ExoMediaDrm} for use by the manager.
+ * @param callback Performs key and provisioning requests.
+ * @param keyRequestParameters An optional map of parameters to pass as the last argument to
+ * {@link ExoMediaDrm#getKeyRequest(byte[], List, int, HashMap)}. May be null.
+ * @param multiSession A boolean that specify whether multiple key session support is enabled.
+ * Default is false.
+ * @deprecated Use {@link Builder} instead.
+ */
+ @Deprecated
+ public DefaultDrmSessionManager(
+ UUID uuid,
+ ExoMediaDrm<T> exoMediaDrm,
+ MediaDrmCallback callback,
+ @Nullable HashMap<String, String> keyRequestParameters,
+ boolean multiSession) {
+ this(
+ uuid,
+ exoMediaDrm,
+ callback,
+ keyRequestParameters == null ? new HashMap<>() : keyRequestParameters,
+ multiSession,
+ INITIAL_DRM_REQUEST_RETRY_COUNT);
+ }
+
+ /**
+ * @param uuid The UUID of the drm scheme.
+ * @param exoMediaDrm An underlying {@link ExoMediaDrm} for use by the manager.
+ * @param callback Performs key and provisioning requests.
+ * @param keyRequestParameters An optional map of parameters to pass as the last argument to
+ * {@link ExoMediaDrm#getKeyRequest(byte[], List, int, HashMap)}. May be null.
+ * @param multiSession A boolean that specify whether multiple key session support is enabled.
+ * Default is false.
+ * @param initialDrmRequestRetryCount The number of times to retry for initial provisioning and
+ * key request before reporting error.
+ * @deprecated Use {@link Builder} instead.
+ */
+ @Deprecated
+ public DefaultDrmSessionManager(
+ UUID uuid,
+ ExoMediaDrm<T> exoMediaDrm,
+ MediaDrmCallback callback,
+ @Nullable HashMap<String, String> keyRequestParameters,
+ boolean multiSession,
+ int initialDrmRequestRetryCount) {
+ this(
+ uuid,
+ new ExoMediaDrm.AppManagedProvider<>(exoMediaDrm),
+ callback,
+ keyRequestParameters == null ? new HashMap<>() : keyRequestParameters,
+ multiSession,
+ /* useDrmSessionsForClearContentTrackTypes= */ new int[0],
+ /* playClearSamplesWithoutKeys= */ false,
+ new DefaultLoadErrorHandlingPolicy(initialDrmRequestRetryCount));
+ }
+
+ // the constructor does not initialize fields: offlineLicenseKeySetId
+ @SuppressWarnings("nullness:initialization.fields.uninitialized")
+ private DefaultDrmSessionManager(
+ UUID uuid,
+ ExoMediaDrm.Provider<T> exoMediaDrmProvider,
+ MediaDrmCallback callback,
+ HashMap<String, String> keyRequestParameters,
+ boolean multiSession,
+ int[] useDrmSessionsForClearContentTrackTypes,
+ boolean playClearSamplesWithoutKeys,
+ LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
+ Assertions.checkNotNull(uuid);
+ Assertions.checkArgument(!C.COMMON_PSSH_UUID.equals(uuid), "Use C.CLEARKEY_UUID instead");
+ this.uuid = uuid;
+ this.exoMediaDrmProvider = exoMediaDrmProvider;
+ this.callback = callback;
+ this.keyRequestParameters = keyRequestParameters;
+ this.eventDispatcher = new EventDispatcher<>();
+ this.multiSession = multiSession;
+ this.useDrmSessionsForClearContentTrackTypes = useDrmSessionsForClearContentTrackTypes;
+ this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
+ this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
+ provisioningManagerImpl = new ProvisioningManagerImpl();
+ mode = MODE_PLAYBACK;
+ sessions = new ArrayList<>();
+ provisioningSessions = new ArrayList<>();
+ }
+
+ /**
+ * Adds a {@link DefaultDrmSessionEventListener} to listen to drm session events.
+ *
+ * @param handler A handler to use when delivering events to {@code eventListener}.
+ * @param eventListener A listener of events.
+ */
+ public final void addListener(Handler handler, DefaultDrmSessionEventListener eventListener) {
+ eventDispatcher.addListener(handler, eventListener);
+ }
+
+ /**
+ * Removes a {@link DefaultDrmSessionEventListener} from the list of drm session event listeners.
+ *
+ * @param eventListener The listener to remove.
+ */
+ public final void removeListener(DefaultDrmSessionEventListener eventListener) {
+ eventDispatcher.removeListener(eventListener);
+ }
+
+ /**
+ * Sets the mode, which determines the role of sessions acquired from the instance. This must be
+ * called before {@link #acquireSession(Looper, DrmInitData)} or {@link
+ * #acquirePlaceholderSession} is called.
+ *
+ * <p>By default, the mode is {@link #MODE_PLAYBACK} and a streaming license is requested when
+ * required.
+ *
+ * <p>{@code mode} must be one of these:
+ *
+ * <ul>
+ * <li>{@link #MODE_PLAYBACK}: If {@code offlineLicenseKeySetId} is null, a streaming license is
+ * requested otherwise the offline license is restored.
+ * <li>{@link #MODE_QUERY}: {@code offlineLicenseKeySetId} can not be null. The offline license
+ * is restored.
+ * <li>{@link #MODE_DOWNLOAD}: If {@code offlineLicenseKeySetId} is null, an offline license is
+ * requested otherwise the offline license is renewed.
+ * <li>{@link #MODE_RELEASE}: {@code offlineLicenseKeySetId} can not be null. The offline
+ * license is released.
+ * </ul>
+ *
+ * @param mode The mode to be set.
+ * @param offlineLicenseKeySetId The key set id of the license to be used with the given mode.
+ */
+ public void setMode(@Mode int mode, @Nullable byte[] offlineLicenseKeySetId) {
+ Assertions.checkState(sessions.isEmpty());
+ if (mode == MODE_QUERY || mode == MODE_RELEASE) {
+ Assertions.checkNotNull(offlineLicenseKeySetId);
+ }
+ this.mode = mode;
+ this.offlineLicenseKeySetId = offlineLicenseKeySetId;
+ }
+
+ // DrmSessionManager implementation.
+
+ @Override
+ public final void prepare() {
+ if (prepareCallsCount++ == 0) {
+ Assertions.checkState(exoMediaDrm == null);
+ exoMediaDrm = exoMediaDrmProvider.acquireExoMediaDrm(uuid);
+ exoMediaDrm.setOnEventListener(new MediaDrmEventListener());
+ }
+ }
+
+ @Override
+ public final void release() {
+ if (--prepareCallsCount == 0) {
+ Assertions.checkNotNull(exoMediaDrm).release();
+ exoMediaDrm = null;
+ }
+ }
+
+ @Override
+ public boolean canAcquireSession(DrmInitData drmInitData) {
+ if (offlineLicenseKeySetId != null) {
+ // An offline license can be restored so a session can always be acquired.
+ return true;
+ }
+ List<SchemeData> schemeDatas = getSchemeDatas(drmInitData, uuid, true);
+ if (schemeDatas.isEmpty()) {
+ if (drmInitData.schemeDataCount == 1 && drmInitData.get(0).matches(C.COMMON_PSSH_UUID)) {
+ // Assume scheme specific data will be added before the session is opened.
+ Log.w(
+ TAG, "DrmInitData only contains common PSSH SchemeData. Assuming support for: " + uuid);
+ } else {
+ // No data for this manager's scheme.
+ return false;
+ }
+ }
+ String schemeType = drmInitData.schemeType;
+ if (schemeType == null || C.CENC_TYPE_cenc.equals(schemeType)) {
+ // If there is no scheme information, assume patternless AES-CTR.
+ return true;
+ } else if (C.CENC_TYPE_cbc1.equals(schemeType)
+ || C.CENC_TYPE_cbcs.equals(schemeType)
+ || C.CENC_TYPE_cens.equals(schemeType)) {
+ // API support for AES-CBC and pattern encryption was added in API 24. However, the
+ // implementation was not stable until API 25.
+ return Util.SDK_INT >= 25;
+ }
+ // Unknown schemes, assume one of them is supported.
+ return true;
+ }
+
+ @Override
+ @Nullable
+ public DrmSession<T> acquirePlaceholderSession(Looper playbackLooper, int trackType) {
+ assertExpectedPlaybackLooper(playbackLooper);
+ ExoMediaDrm<T> exoMediaDrm = Assertions.checkNotNull(this.exoMediaDrm);
+ boolean avoidPlaceholderDrmSessions =
+ FrameworkMediaCrypto.class.equals(exoMediaDrm.getExoMediaCryptoType())
+ && FrameworkMediaCrypto.WORKAROUND_DEVICE_NEEDS_KEYS_TO_CONFIGURE_CODEC;
+ // Avoid attaching a session to sparse formats.
+ if (avoidPlaceholderDrmSessions
+ || Util.linearSearch(useDrmSessionsForClearContentTrackTypes, trackType) == C.INDEX_UNSET
+ || exoMediaDrm.getExoMediaCryptoType() == null) {
+ return null;
+ }
+ maybeCreateMediaDrmHandler(playbackLooper);
+ if (placeholderDrmSession == null) {
+ DefaultDrmSession<T> placeholderDrmSession =
+ createNewDefaultSession(
+ /* schemeDatas= */ Collections.emptyList(), /* isPlaceholderSession= */ true);
+ sessions.add(placeholderDrmSession);
+ this.placeholderDrmSession = placeholderDrmSession;
+ }
+ placeholderDrmSession.acquire();
+ return placeholderDrmSession;
+ }
+
+ @Override
+ public DrmSession<T> acquireSession(Looper playbackLooper, DrmInitData drmInitData) {
+ assertExpectedPlaybackLooper(playbackLooper);
+ maybeCreateMediaDrmHandler(playbackLooper);
+
+ @Nullable List<SchemeData> schemeDatas = null;
+ if (offlineLicenseKeySetId == null) {
+ schemeDatas = getSchemeDatas(drmInitData, uuid, false);
+ if (schemeDatas.isEmpty()) {
+ final MissingSchemeDataException error = new MissingSchemeDataException(uuid);
+ eventDispatcher.dispatch(listener -> listener.onDrmSessionManagerError(error));
+ return new ErrorStateDrmSession<>(new DrmSessionException(error));
+ }
+ }
+
+ @Nullable DefaultDrmSession<T> session;
+ if (!multiSession) {
+ session = noMultiSessionDrmSession;
+ } else {
+ // Only use an existing session if it has matching init data.
+ session = null;
+ for (DefaultDrmSession<T> existingSession : sessions) {
+ if (Util.areEqual(existingSession.schemeDatas, schemeDatas)) {
+ session = existingSession;
+ break;
+ }
+ }
+ }
+
+ if (session == null) {
+ // Create a new session.
+ session = createNewDefaultSession(schemeDatas, /* isPlaceholderSession= */ false);
+ if (!multiSession) {
+ noMultiSessionDrmSession = session;
+ }
+ sessions.add(session);
+ }
+ session.acquire();
+ return session;
+ }
+
+ @Override
+ @Nullable
+ public Class<T> getExoMediaCryptoType(DrmInitData drmInitData) {
+ return canAcquireSession(drmInitData)
+ ? Assertions.checkNotNull(exoMediaDrm).getExoMediaCryptoType()
+ : null;
+ }
+
+ // Internal methods.
+
+ private void assertExpectedPlaybackLooper(Looper playbackLooper) {
+ Assertions.checkState(this.playbackLooper == null || this.playbackLooper == playbackLooper);
+ this.playbackLooper = playbackLooper;
+ }
+
+ private void maybeCreateMediaDrmHandler(Looper playbackLooper) {
+ if (mediaDrmHandler == null) {
+ mediaDrmHandler = new MediaDrmHandler(playbackLooper);
+ }
+ }
+
+ private DefaultDrmSession<T> createNewDefaultSession(
+ @Nullable List<SchemeData> schemeDatas, boolean isPlaceholderSession) {
+ Assertions.checkNotNull(exoMediaDrm);
+ // Placeholder sessions should always play clear samples without keys.
+ boolean playClearSamplesWithoutKeys = this.playClearSamplesWithoutKeys | isPlaceholderSession;
+ return new DefaultDrmSession<>(
+ uuid,
+ exoMediaDrm,
+ /* provisioningManager= */ provisioningManagerImpl,
+ /* releaseCallback= */ this::onSessionReleased,
+ schemeDatas,
+ mode,
+ playClearSamplesWithoutKeys,
+ isPlaceholderSession,
+ offlineLicenseKeySetId,
+ keyRequestParameters,
+ callback,
+ Assertions.checkNotNull(playbackLooper),
+ eventDispatcher,
+ loadErrorHandlingPolicy);
+ }
+
+ private void onSessionReleased(DefaultDrmSession<T> drmSession) {
+ sessions.remove(drmSession);
+ if (placeholderDrmSession == drmSession) {
+ placeholderDrmSession = null;
+ }
+ if (noMultiSessionDrmSession == drmSession) {
+ noMultiSessionDrmSession = null;
+ }
+ if (provisioningSessions.size() > 1 && provisioningSessions.get(0) == drmSession) {
+ // Other sessions were waiting for the released session to complete a provision operation.
+ // We need to have one of those sessions perform the provision operation instead.
+ provisioningSessions.get(1).provision();
+ }
+ provisioningSessions.remove(drmSession);
+ }
+
+ /**
+ * Extracts {@link SchemeData} instances suitable for the given DRM scheme {@link UUID}.
+ *
+ * @param drmInitData The {@link DrmInitData} from which to extract the {@link SchemeData}.
+ * @param uuid The UUID.
+ * @param allowMissingData Whether a {@link SchemeData} with null {@link SchemeData#data} may be
+ * returned.
+ * @return The extracted {@link SchemeData} instances, or an empty list if no suitable data is
+ * present.
+ */
+ private static List<SchemeData> getSchemeDatas(
+ DrmInitData drmInitData, UUID uuid, boolean allowMissingData) {
+ // Look for matching scheme data (matching the Common PSSH box for ClearKey).
+ List<SchemeData> matchingSchemeDatas = new ArrayList<>(drmInitData.schemeDataCount);
+ for (int i = 0; i < drmInitData.schemeDataCount; i++) {
+ SchemeData schemeData = drmInitData.get(i);
+ boolean uuidMatches =
+ schemeData.matches(uuid)
+ || (C.CLEARKEY_UUID.equals(uuid) && schemeData.matches(C.COMMON_PSSH_UUID));
+ if (uuidMatches && (schemeData.data != null || allowMissingData)) {
+ matchingSchemeDatas.add(schemeData);
+ }
+ }
+ return matchingSchemeDatas;
+ }
+
+ @SuppressLint("HandlerLeak")
+ private class MediaDrmHandler extends Handler {
+
+ public MediaDrmHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ byte[] sessionId = (byte[]) msg.obj;
+ if (sessionId == null) {
+ // The event is not associated with any particular session.
+ return;
+ }
+ for (DefaultDrmSession<T> session : sessions) {
+ if (session.hasSessionId(sessionId)) {
+ session.onMediaDrmEvent(msg.what);
+ return;
+ }
+ }
+ }
+ }
+
+ private class ProvisioningManagerImpl implements DefaultDrmSession.ProvisioningManager<T> {
+ @Override
+ public void provisionRequired(DefaultDrmSession<T> session) {
+ if (provisioningSessions.contains(session)) {
+ // The session has already requested provisioning.
+ return;
+ }
+ provisioningSessions.add(session);
+ if (provisioningSessions.size() == 1) {
+ // This is the first session requesting provisioning, so have it perform the operation.
+ session.provision();
+ }
+ }
+
+ @Override
+ public void onProvisionCompleted() {
+ for (DefaultDrmSession<T> session : provisioningSessions) {
+ session.onProvisionCompleted();
+ }
+ provisioningSessions.clear();
+ }
+
+ @Override
+ public void onProvisionError(Exception error) {
+ for (DefaultDrmSession<T> session : provisioningSessions) {
+ session.onProvisionError(error);
+ }
+ provisioningSessions.clear();
+ }
+ }
+
+ private class MediaDrmEventListener implements OnEventListener<T> {
+
+ @Override
+ public void onEvent(
+ ExoMediaDrm<? extends T> md,
+ @Nullable byte[] sessionId,
+ int event,
+ int extra,
+ @Nullable byte[] data) {
+ Assertions.checkNotNull(mediaDrmHandler).obtainMessage(event, sessionId).sendToTarget();
+ }
+ }
+}