diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java index ca7a657a0f..47483a027d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.drm; import static com.google.android.exoplayer2.util.Assertions.checkState; +import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull; import static java.lang.Math.min; import android.annotation.SuppressLint; @@ -285,6 +286,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; return offlineLicenseKeySetId; } + @Override + public boolean requiresSecureDecoder(String mimeType) { + return mediaDrm.requiresSecureDecoder(checkStateNotNull(sessionId), mimeType); + } + @Override public void acquire(@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher) { checkState(referenceCount >= 0); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java index b993345613..7f07fd9c66 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DrmSession.java @@ -137,6 +137,15 @@ public interface DrmSession { @Nullable byte[] getOfflineLicenseKeySetId(); + /** + * Returns whether this session requires use of a secure decoder for the given MIME type. Assumes + * a license policy that requires the highest level of security supported by the session. + * + *

The session must be in {@link #getState() state} {@link #STATE_OPENED} or {@link + * #STATE_OPENED_WITH_KEYS}. + */ + boolean requiresSecureDecoder(String mimeType); + /** * Increments the reference count. When the caller no longer needs to use the instance, it must * call {@link #release(DrmSessionEventListener.EventDispatcher)} to decrement the reference diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DummyExoMediaDrm.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DummyExoMediaDrm.java index 9631b76491..b5c7692ab8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DummyExoMediaDrm.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DummyExoMediaDrm.java @@ -93,6 +93,12 @@ public final class DummyExoMediaDrm implements ExoMediaDrm { throw new IllegalStateException(); } + @Override + public boolean requiresSecureDecoder(byte[] sessionId, String mimeType) { + // Should not be invoked. No session should exist. + throw new IllegalStateException(); + } + @Override public void acquire() { // Do nothing. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/ErrorStateDrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/ErrorStateDrmSession.java index 068f1b3782..51cb019432 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/ErrorStateDrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/ErrorStateDrmSession.java @@ -69,6 +69,11 @@ public final class ErrorStateDrmSession implements DrmSession { return null; } + @Override + public boolean requiresSecureDecoder(String mimeType) { + return false; + } + @Override public void acquire(@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher) { // Do nothing. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/ExoMediaDrm.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/ExoMediaDrm.java index b20fc916c4..583fcf979a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/ExoMediaDrm.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/ExoMediaDrm.java @@ -460,6 +460,15 @@ public interface ExoMediaDrm { */ Map queryKeyStatus(byte[] sessionId); + /** + * Returns whether the given session requires use of a secure decoder for the given MIME type. + * Assumes a license policy that requires the highest level of security supported by the session. + * + * @param sessionId The ID of the session. + * @param mimeType The content MIME type to query. + */ + boolean requiresSecureDecoder(byte[] sessionId, String mimeType); + /** * Increments the reference count. When the caller no longer needs to use the instance, it must * call {@link #release()} to decrement the reference count. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java index 48255714bd..8c2b1c3a62 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/FrameworkMediaDrm.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.drm; import android.annotation.SuppressLint; import android.media.DeniedByServerException; +import android.media.MediaCrypto; import android.media.MediaCryptoException; import android.media.MediaDrm; import android.media.MediaDrmException; @@ -24,6 +25,7 @@ import android.media.NotProvisionedException; import android.media.UnsupportedSchemeException; import android.os.PersistableBundle; import android.text.TextUtils; +import androidx.annotation.DoNotInline; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.C; @@ -244,6 +246,26 @@ public final class FrameworkMediaDrm implements ExoMediaDrm { return mediaDrm.queryKeyStatus(sessionId); } + @Override + public boolean requiresSecureDecoder(byte[] sessionId, String mimeType) { + if (Util.SDK_INT >= 31) { + return Api31.requiresSecureDecoder(mediaDrm, mimeType); + } + + MediaCrypto mediaCrypto; + try { + mediaCrypto = new MediaCrypto(uuid, sessionId); + } catch (MediaCryptoException e) { + // This shouldn't happen, but if it does then assume that a secure decoder may be required. + return true; + } + try { + return mediaCrypto.requiresSecureDecoderComponent(mimeType); + } finally { + mediaCrypto.release(); + } + } + @Override public synchronized void acquire() { Assertions.checkState(referenceCount > 0); @@ -476,4 +498,12 @@ public final class FrameworkMediaDrm implements ExoMediaDrm { newData.put(xmlWithMockLaUrl.getBytes(Charsets.UTF_16LE)); return newData.array(); } + + @RequiresApi(31) + private static class Api31 { + @DoNotInline + public static boolean requiresSecureDecoder(MediaDrm mediaDrm, String mimeType) { + return mediaDrm.requiresSecureDecoder(mimeType); + } + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 9d689faa03..2100847c7b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -2099,7 +2099,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer { // the case is to occur, so we re-initialize in this case. return true; } - if (!codecInfo.secure && maybeRequiresSecureDecoder(newMediaCrypto, newFormat)) { + + boolean requiresSecureDecoder; + if (newMediaCrypto.forceAllowInsecureDecoderComponents) { + requiresSecureDecoder = false; + } else { + requiresSecureDecoder = newSession.requiresSecureDecoder(newFormat.sampleMimeType); + } + if (!codecInfo.secure && requiresSecureDecoder) { // Re-initialization is required because newSession might require switching to the secure // output path. return true; @@ -2108,32 +2115,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { return false; } - /** - * Returns whether a {@link DrmSession} may require a secure decoder for a given {@link Format}. - * - * @param sessionMediaCrypto The {@link DrmSession}'s {@link FrameworkMediaCrypto}. - * @param format The {@link Format}. - * @return Whether a secure decoder may be required. - */ - private boolean maybeRequiresSecureDecoder( - FrameworkMediaCrypto sessionMediaCrypto, Format format) { - if (sessionMediaCrypto.forceAllowInsecureDecoderComponents) { - return false; - } - MediaCrypto mediaCrypto; - try { - mediaCrypto = new MediaCrypto(sessionMediaCrypto.uuid, sessionMediaCrypto.sessionId); - } catch (MediaCryptoException e) { - // This shouldn't happen, but if it does then assume that a secure decoder may be required. - return true; - } - try { - return mediaCrypto.requiresSecureDecoderComponent(format.sampleMimeType); - } finally { - mediaCrypto.release(); - } - } - private void reinitializeCodec() throws ExoPlaybackException { releaseCodec(); maybeInitCodecOrBypass(); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java index e48307d7ee..0f8d3dc348 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExoMediaDrm.java @@ -272,6 +272,11 @@ public final class FakeExoMediaDrm implements ExoMediaDrm { : KEY_STATUS_UNAVAILABLE); } + @Override + public boolean requiresSecureDecoder(byte[] sessionId, String mimeType) { + return false; + } + @Override public void acquire() { Assertions.checkState(referenceCount > 0);