mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
MCR: Ensure mediaCrypto
and codec
are atomically non-null
`mediaCrypto` is initialized before `codec` in `maybeInitCodecOrBypass`. Before this change, it was possible for `maybeInitCodecOrBypass` to complete with `mediaCrypto != null` and `codec == null`, in particular if it was run as part of clearing the player surface (since in that case, no video codec is initialized). This inconsistent state then causes issues during a later invocation of `maybeInitCodecOrBypass`, when `mediaCrypto` is still non-null, and `mediaCryptoRequiresSecureDecoder = true`, but the content has been changed to unencrypted with no associated DRM session. This results in a playback error, because a secure decoder is initialized but there's no DRM session available to work with it. This change ensures that when `maybeInitCodecOrBypass` completes, either both `mediaCrypto != null` and `codec != null` (i.e. codec initialization was completed) or `mediaCrypto == null` and `codec == null` (i.e. codec initialization was not completed). We also ensure that when nulling out `mediaCrypto` we also set `maybeInitCodecOrBypass = false`. A later change should be able to demote `maybeInitCodecOrBypass` from a field to a local in order to remove any risk of that part of state becoming out of sync. This resolves the issue, because during the second invocation of `maybeInitCodecOrBypass` an insecure decoder is now (correctly) initialized and the unencrypted content is successfully played. #minor-release PiperOrigin-RevId: 587713911
This commit is contained in:
parent
f32bdf86bc
commit
913f6da083
@ -59,6 +59,9 @@
|
|||||||
* Extend workaround for spurious ClearKey `https://default.url` license
|
* Extend workaround for spurious ClearKey `https://default.url` license
|
||||||
URL to API 33+ (previously the workaround only applied on API 33
|
URL to API 33+ (previously the workaround only applied on API 33
|
||||||
exactly) ([#837](https://github.com/androidx/media/pull/837)).
|
exactly) ([#837](https://github.com/androidx/media/pull/837)).
|
||||||
|
* Fix `ERROR_DRM_SESSION_NOT_OPENED` when switching from encrypted to
|
||||||
|
clear content without a surface attached to the player. The error was
|
||||||
|
due to incorrectly using a secure decoder to play the clear content.
|
||||||
* Effect:
|
* Effect:
|
||||||
* Muxers:
|
* Muxers:
|
||||||
* IMA extension:
|
* IMA extension:
|
||||||
|
@ -89,6 +89,7 @@ import java.nio.ByteOrder;
|
|||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
|
|
||||||
/** An abstract renderer that uses {@link MediaCodec} to decode samples for rendering. */
|
/** An abstract renderer that uses {@link MediaCodec} to decode samples for rendering. */
|
||||||
//
|
//
|
||||||
@ -545,50 +546,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setCodecDrmSession(sourceDrmSession);
|
setCodecDrmSession(sourceDrmSession);
|
||||||
|
if (codecDrmSession == null || initMediaCryptoIfDrmSessionReady()) {
|
||||||
String mimeType = checkNotNull(inputFormat).sampleMimeType;
|
|
||||||
@Nullable DrmSession codecDrmSession = this.codecDrmSession;
|
|
||||||
if (codecDrmSession != null) {
|
|
||||||
@Nullable CryptoConfig cryptoConfig = codecDrmSession.getCryptoConfig();
|
|
||||||
if (mediaCrypto == null) {
|
|
||||||
if (cryptoConfig == null) {
|
|
||||||
@Nullable DrmSessionException drmError = codecDrmSession.getError();
|
|
||||||
if (drmError != null) {
|
|
||||||
// Continue for now. We may be able to avoid failure if a new input format causes the
|
|
||||||
// session to be replaced without it having been used.
|
|
||||||
} else {
|
|
||||||
// The drm session isn't open yet.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (cryptoConfig instanceof FrameworkCryptoConfig) {
|
|
||||||
FrameworkCryptoConfig frameworkCryptoConfig = (FrameworkCryptoConfig) cryptoConfig;
|
|
||||||
try {
|
|
||||||
mediaCrypto =
|
|
||||||
new MediaCrypto(frameworkCryptoConfig.uuid, frameworkCryptoConfig.sessionId);
|
|
||||||
} catch (MediaCryptoException e) {
|
|
||||||
throw createRendererException(
|
|
||||||
e, inputFormat, PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR);
|
|
||||||
}
|
|
||||||
mediaCryptoRequiresSecureDecoder =
|
|
||||||
!frameworkCryptoConfig.forceAllowInsecureDecoderComponents
|
|
||||||
&& mediaCrypto.requiresSecureDecoderComponent(checkStateNotNull(mimeType));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (FrameworkCryptoConfig.WORKAROUND_DEVICE_NEEDS_KEYS_TO_CONFIGURE_CODEC
|
|
||||||
&& cryptoConfig instanceof FrameworkCryptoConfig) {
|
|
||||||
@DrmSession.State int drmSessionState = codecDrmSession.getState();
|
|
||||||
if (drmSessionState == DrmSession.STATE_ERROR) {
|
|
||||||
DrmSessionException drmSessionException =
|
|
||||||
Assertions.checkNotNull(codecDrmSession.getError());
|
|
||||||
throw createRendererException(
|
|
||||||
drmSessionException, inputFormat, drmSessionException.errorCode);
|
|
||||||
} else if (drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS) {
|
|
||||||
// Wait for keys.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
maybeInitCodecWithFallback(mediaCrypto, mediaCryptoRequiresSecureDecoder);
|
maybeInitCodecWithFallback(mediaCrypto, mediaCryptoRequiresSecureDecoder);
|
||||||
} catch (DecoderInitializationException e) {
|
} catch (DecoderInitializationException e) {
|
||||||
@ -596,6 +554,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
e, inputFormat, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED);
|
e, inputFormat, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (mediaCrypto != null && codec == null) {
|
||||||
|
// mediaCrypto was created, but a codec wasn't, so release the mediaCrypto before returning.
|
||||||
|
mediaCrypto.release();
|
||||||
|
mediaCrypto = null;
|
||||||
|
mediaCryptoRequiresSecureDecoder = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether buffers in the input format can be processed without a codec.
|
* Returns whether buffers in the input format can be processed without a codec.
|
||||||
@ -1035,6 +1000,57 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether {@link #codecDrmSession} is ready for playback, and if so initializes {@link
|
||||||
|
* #mediaCrypto} if needed.
|
||||||
|
*
|
||||||
|
* @return {@code true} if codec initialization should continue, or {@code false} if it should be
|
||||||
|
* aborted.
|
||||||
|
*/
|
||||||
|
@RequiresNonNull("this.codecDrmSession")
|
||||||
|
private boolean initMediaCryptoIfDrmSessionReady() throws ExoPlaybackException {
|
||||||
|
checkState(mediaCrypto == null);
|
||||||
|
DrmSession codecDrmSession = this.codecDrmSession;
|
||||||
|
String mimeType = checkNotNull(inputFormat).sampleMimeType;
|
||||||
|
@Nullable CryptoConfig cryptoConfig = codecDrmSession.getCryptoConfig();
|
||||||
|
if (FrameworkCryptoConfig.WORKAROUND_DEVICE_NEEDS_KEYS_TO_CONFIGURE_CODEC
|
||||||
|
&& cryptoConfig instanceof FrameworkCryptoConfig) {
|
||||||
|
@DrmSession.State int drmSessionState = codecDrmSession.getState();
|
||||||
|
if (drmSessionState == DrmSession.STATE_ERROR) {
|
||||||
|
DrmSessionException drmSessionException =
|
||||||
|
Assertions.checkNotNull(codecDrmSession.getError());
|
||||||
|
throw createRendererException(
|
||||||
|
drmSessionException, inputFormat, drmSessionException.errorCode);
|
||||||
|
} else if (drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS) {
|
||||||
|
// Wait for keys.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cryptoConfig == null) {
|
||||||
|
@Nullable DrmSessionException drmError = codecDrmSession.getError();
|
||||||
|
if (drmError != null) {
|
||||||
|
// Continue for now. We may be able to avoid failure if a new input format causes the
|
||||||
|
// session to be replaced without it having been used.
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// The drm session isn't open yet.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (cryptoConfig instanceof FrameworkCryptoConfig) {
|
||||||
|
FrameworkCryptoConfig frameworkCryptoConfig = (FrameworkCryptoConfig) cryptoConfig;
|
||||||
|
try {
|
||||||
|
mediaCrypto = new MediaCrypto(frameworkCryptoConfig.uuid, frameworkCryptoConfig.sessionId);
|
||||||
|
} catch (MediaCryptoException e) {
|
||||||
|
throw createRendererException(
|
||||||
|
e, inputFormat, PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR);
|
||||||
|
}
|
||||||
|
mediaCryptoRequiresSecureDecoder =
|
||||||
|
!frameworkCryptoConfig.forceAllowInsecureDecoderComponents
|
||||||
|
&& mediaCrypto.requiresSecureDecoderComponent(checkStateNotNull(mimeType));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void maybeInitCodecWithFallback(
|
private void maybeInitCodecWithFallback(
|
||||||
@Nullable MediaCrypto crypto, boolean mediaCryptoRequiresSecureDecoder)
|
@Nullable MediaCrypto crypto, boolean mediaCryptoRequiresSecureDecoder)
|
||||||
throws DecoderInitializationException {
|
throws DecoderInitializationException {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user