diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java | |
parent | Initial commit. (diff) | |
download | firefox-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.java | 691 |
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(); + } + } +} |