Soften MediaCodecRenderer
's assumptions about using framework DRM
#minor-release PiperOrigin-RevId: 536724725
This commit is contained in:
parent
208eefc0fd
commit
0ddc024c69
@ -309,7 +309,20 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
@Nullable private Format outputFormat;
|
@Nullable private Format outputFormat;
|
||||||
@Nullable private DrmSession codecDrmSession;
|
@Nullable private DrmSession codecDrmSession;
|
||||||
@Nullable private DrmSession sourceDrmSession;
|
@Nullable private DrmSession sourceDrmSession;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A framework {@link MediaCrypto} for use with {@link MediaCodec#queueSecureInputBuffer(int, int,
|
||||||
|
* MediaCodec.CryptoInfo, long, int)} to play encrypted content.
|
||||||
|
*
|
||||||
|
* <p>Non-null if framework decryption is being used (i.e. {@link DrmSession#getCryptoConfig()
|
||||||
|
* codecDrmSession.getCryptoConfig()} returns an instance of {@link FrameworkCryptoConfig}).
|
||||||
|
*
|
||||||
|
* <p>Can be null if the content is not encrypted (in which case {@link #codecDrmSession} and
|
||||||
|
* {@link #sourceDrmSession} will also be null), or if decryption is happening without framework
|
||||||
|
* support ({@link #codecDrmSession} and {@link #sourceDrmSession} will be non-null).
|
||||||
|
*/
|
||||||
@Nullable private MediaCrypto mediaCrypto;
|
@Nullable private MediaCrypto mediaCrypto;
|
||||||
|
|
||||||
private boolean mediaCryptoRequiresSecureDecoder;
|
private boolean mediaCryptoRequiresSecureDecoder;
|
||||||
private long renderTimeLimitMs;
|
private long renderTimeLimitMs;
|
||||||
private float currentPlaybackSpeed;
|
private float currentPlaybackSpeed;
|
||||||
@ -500,10 +513,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
|
|
||||||
String mimeType = inputFormat.sampleMimeType;
|
String mimeType = inputFormat.sampleMimeType;
|
||||||
if (codecDrmSession != null) {
|
if (codecDrmSession != null) {
|
||||||
|
@Nullable CryptoConfig cryptoConfig = codecDrmSession.getCryptoConfig();
|
||||||
if (mediaCrypto == null) {
|
if (mediaCrypto == null) {
|
||||||
@Nullable
|
if (cryptoConfig == null) {
|
||||||
FrameworkCryptoConfig sessionCryptoConfig = getFrameworkCryptoConfig(codecDrmSession);
|
|
||||||
if (sessionCryptoConfig == null) {
|
|
||||||
@Nullable DrmSessionException drmError = codecDrmSession.getError();
|
@Nullable DrmSessionException drmError = codecDrmSession.getError();
|
||||||
if (drmError != null) {
|
if (drmError != null) {
|
||||||
// Continue for now. We may be able to avoid failure if a new input format causes the
|
// Continue for now. We may be able to avoid failure if a new input format causes the
|
||||||
@ -512,19 +524,22 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
// The drm session isn't open yet.
|
// The drm session isn't open yet.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else if (cryptoConfig instanceof FrameworkCryptoConfig) {
|
||||||
|
FrameworkCryptoConfig frameworkCryptoConfig = (FrameworkCryptoConfig) cryptoConfig;
|
||||||
try {
|
try {
|
||||||
mediaCrypto = new MediaCrypto(sessionCryptoConfig.uuid, sessionCryptoConfig.sessionId);
|
mediaCrypto =
|
||||||
|
new MediaCrypto(frameworkCryptoConfig.uuid, frameworkCryptoConfig.sessionId);
|
||||||
} catch (MediaCryptoException e) {
|
} catch (MediaCryptoException e) {
|
||||||
throw createRendererException(
|
throw createRendererException(
|
||||||
e, inputFormat, PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR);
|
e, inputFormat, PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR);
|
||||||
}
|
}
|
||||||
mediaCryptoRequiresSecureDecoder =
|
mediaCryptoRequiresSecureDecoder =
|
||||||
!sessionCryptoConfig.forceAllowInsecureDecoderComponents
|
!frameworkCryptoConfig.forceAllowInsecureDecoderComponents
|
||||||
&& mediaCrypto.requiresSecureDecoderComponent(mimeType);
|
&& mediaCrypto.requiresSecureDecoderComponent(mimeType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (FrameworkCryptoConfig.WORKAROUND_DEVICE_NEEDS_KEYS_TO_CONFIGURE_CODEC) {
|
if (FrameworkCryptoConfig.WORKAROUND_DEVICE_NEEDS_KEYS_TO_CONFIGURE_CODEC
|
||||||
|
&& cryptoConfig instanceof FrameworkCryptoConfig) {
|
||||||
@DrmSession.State int drmSessionState = codecDrmSession.getState();
|
@DrmSession.State int drmSessionState = codecDrmSession.getState();
|
||||||
if (drmSessionState == DrmSession.STATE_ERROR) {
|
if (drmSessionState == DrmSession.STATE_ERROR) {
|
||||||
DrmSessionException drmSessionException =
|
DrmSessionException drmSessionException =
|
||||||
@ -985,7 +1000,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void maybeInitCodecWithFallback(
|
private void maybeInitCodecWithFallback(
|
||||||
MediaCrypto crypto, boolean mediaCryptoRequiresSecureDecoder)
|
@Nullable MediaCrypto crypto, boolean mediaCryptoRequiresSecureDecoder)
|
||||||
throws DecoderInitializationException {
|
throws DecoderInitializationException {
|
||||||
if (availableCodecInfos == null) {
|
if (availableCodecInfos == null) {
|
||||||
try {
|
try {
|
||||||
@ -1101,7 +1116,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
bypassEnabled = true;
|
bypassEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initCodec(MediaCodecInfo codecInfo, MediaCrypto crypto) throws Exception {
|
private void initCodec(MediaCodecInfo codecInfo, @Nullable MediaCrypto crypto) throws Exception {
|
||||||
long codecInitializingTimestamp;
|
long codecInitializingTimestamp;
|
||||||
long codecInitializedTimestamp;
|
long codecInitializedTimestamp;
|
||||||
String codecName = codecInfo.name;
|
String codecName = codecInfo.name;
|
||||||
@ -2116,6 +2131,36 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable CryptoConfig newCryptoConfig = newSession.getCryptoConfig();
|
||||||
|
if (newCryptoConfig == null) {
|
||||||
|
// We'd only expect this to happen if the CDM from which newSession is obtained needs
|
||||||
|
// provisioning. This is unlikely to happen (it probably requires a switch from one DRM scheme
|
||||||
|
// to another, where the new CDM hasn't been used before and needs provisioning). It would be
|
||||||
|
// possible to handle this case without codec re-initialization, but it would require the
|
||||||
|
// re-use code path to be able to wait for provisioning to finish before calling
|
||||||
|
// MediaCrypto.setMediaDrmSession. The extra complexity is not warranted given how unlikely
|
||||||
|
// the case is to occur, so we re-initialize in this case.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable CryptoConfig oldCryptoConfig = oldSession.getCryptoConfig();
|
||||||
|
if (oldCryptoConfig == null || !newCryptoConfig.getClass().equals(oldCryptoConfig.getClass())) {
|
||||||
|
// Switching between different CryptoConfig implementations suggests we're switching between
|
||||||
|
// different forms of decryption (e.g. in-app vs framework-provided), which requires codec
|
||||||
|
// re-initialization.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(newCryptoConfig instanceof FrameworkCryptoConfig)) {
|
||||||
|
// Assume that non-framework CryptoConfig implementations indicate the codec can be re-used
|
||||||
|
// (since it suggests that decryption is happening in-app before the data is passed to
|
||||||
|
// MediaCodec, and therefore a change in DRM keys has no affect on the (decrypted) data seen
|
||||||
|
// by MediaCodec).
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameworkCryptoConfig newFrameworkCryptoConfig = (FrameworkCryptoConfig) newCryptoConfig;
|
||||||
|
|
||||||
// Note: Both oldSession and newSession are non-null, and they are different sessions.
|
// Note: Both oldSession and newSession are non-null, and they are different sessions.
|
||||||
|
|
||||||
if (!newSession.getSchemeUuid().equals(oldSession.getSchemeUuid())) {
|
if (!newSession.getSchemeUuid().equals(oldSession.getSchemeUuid())) {
|
||||||
@ -2135,20 +2180,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
// TODO: Add an API check once [Internal ref: b/128835874] is fixed.
|
// TODO: Add an API check once [Internal ref: b/128835874] is fixed.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@Nullable FrameworkCryptoConfig newCryptoConfig = getFrameworkCryptoConfig(newSession);
|
|
||||||
if (newCryptoConfig == null) {
|
|
||||||
// We'd only expect this to happen if the CDM from which newSession is obtained needs
|
|
||||||
// provisioning. This is unlikely to happen (it probably requires a switch from one DRM scheme
|
|
||||||
// to another, where the new CDM hasn't been used before and needs provisioning). It would be
|
|
||||||
// possible to handle this case without codec re-initialization, but it would require the
|
|
||||||
// re-use code path to be able to wait for provisioning to finish before calling
|
|
||||||
// MediaCrypto.setMediaDrmSession. The extra complexity is not warranted given how unlikely
|
|
||||||
// the case is to occur, so we re-initialize in this case.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean requiresSecureDecoder;
|
boolean requiresSecureDecoder;
|
||||||
if (newCryptoConfig.forceAllowInsecureDecoderComponents) {
|
if (newFrameworkCryptoConfig.forceAllowInsecureDecoderComponents) {
|
||||||
requiresSecureDecoder = false;
|
requiresSecureDecoder = false;
|
||||||
} else {
|
} else {
|
||||||
requiresSecureDecoder = newSession.requiresSecureDecoder(newFormat.sampleMimeType);
|
requiresSecureDecoder = newSession.requiresSecureDecoder(newFormat.sampleMimeType);
|
||||||
@ -2182,32 +2216,20 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
|
|
||||||
@RequiresApi(23)
|
@RequiresApi(23)
|
||||||
private void updateDrmSessionV23() throws ExoPlaybackException {
|
private void updateDrmSessionV23() throws ExoPlaybackException {
|
||||||
|
CryptoConfig cryptoConfig = sourceDrmSession.getCryptoConfig();
|
||||||
|
if (cryptoConfig instanceof FrameworkCryptoConfig) {
|
||||||
try {
|
try {
|
||||||
mediaCrypto.setMediaDrmSession(getFrameworkCryptoConfig(sourceDrmSession).sessionId);
|
mediaCrypto.setMediaDrmSession(((FrameworkCryptoConfig) cryptoConfig).sessionId);
|
||||||
} catch (MediaCryptoException e) {
|
} catch (MediaCryptoException e) {
|
||||||
throw createRendererException(e, inputFormat, PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR);
|
throw createRendererException(
|
||||||
|
e, inputFormat, PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setCodecDrmSession(sourceDrmSession);
|
setCodecDrmSession(sourceDrmSession);
|
||||||
codecDrainState = DRAIN_STATE_NONE;
|
codecDrainState = DRAIN_STATE_NONE;
|
||||||
codecDrainAction = DRAIN_ACTION_NONE;
|
codecDrainAction = DRAIN_ACTION_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private FrameworkCryptoConfig getFrameworkCryptoConfig(DrmSession drmSession)
|
|
||||||
throws ExoPlaybackException {
|
|
||||||
@Nullable CryptoConfig cryptoConfig = drmSession.getCryptoConfig();
|
|
||||||
if (cryptoConfig != null && !(cryptoConfig instanceof FrameworkCryptoConfig)) {
|
|
||||||
// This should not happen if the track went through a supportsFormatDrm() check, during track
|
|
||||||
// selection.
|
|
||||||
throw createRendererException(
|
|
||||||
new IllegalArgumentException(
|
|
||||||
"Expecting FrameworkCryptoConfig but found: " + cryptoConfig),
|
|
||||||
inputFormat,
|
|
||||||
PlaybackException.ERROR_CODE_DRM_SCHEME_UNSUPPORTED);
|
|
||||||
}
|
|
||||||
return (FrameworkCryptoConfig) cryptoConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes any pending batch of buffers without using a decoder, and drains a new batch of
|
* Processes any pending batch of buffers without using a decoder, and drains a new batch of
|
||||||
* buffers from the source.
|
* buffers from the source.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user