mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
DrmSession cleanup
These changes are in part related to handling playback of mixed clear and encrypted content, where we might want to use a secure decoder throughout, but only have drm init data and only care about the state of the DrmSession during playback of encrypted parts. - requiresSecureDecoderComponent became unnecessary when we added ExoMediaCrypto, which provides a layer in which requiresSecureDecoderComponent can be overridden. - Relaxed requirements for obtaining the MediaCrypto. It's helpful to allow retrieval in the error state, since it can be used to instantiate a decoder and play clear samples. - Deferred throwing of errors in renderer implementations. As long as we can get a MediaCrypto, we should init the codec. We can also play clear samples without failing if playClearSamplesWithoutKeys is true, regardless of the errors state. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=160536365
This commit is contained in:
parent
beb0734107
commit
db177db6d9
9 changed files with 124 additions and 142 deletions
|
|
@ -30,6 +30,7 @@ import com.google.android.exoplayer2.FormatHolder;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||||
import com.google.android.exoplayer2.drm.DrmSession;
|
import com.google.android.exoplayer2.drm.DrmSession;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
@ -185,42 +186,21 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have a format.
|
// If we don't have a decoder yet, we need to instantiate one.
|
||||||
drmSession = pendingDrmSession;
|
maybeInitDecoder();
|
||||||
ExoMediaCrypto mediaCrypto = null;
|
|
||||||
if (drmSession != null) {
|
if (decoder != null) {
|
||||||
int drmSessionState = drmSession.getState();
|
try {
|
||||||
if (drmSessionState == DrmSession.STATE_ERROR) {
|
// Rendering loop.
|
||||||
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
TraceUtil.beginSection("drainAndFeed");
|
||||||
} else if (drmSessionState == DrmSession.STATE_OPENED
|
while (drainOutputBuffer(positionUs)) {}
|
||||||
|| drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) {
|
while (feedInputBuffer()) {}
|
||||||
mediaCrypto = drmSession.getMediaCrypto();
|
|
||||||
} else {
|
|
||||||
// The drm session isn't open yet.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (decoder == null) {
|
|
||||||
// If we don't have a decoder yet, we need to instantiate one.
|
|
||||||
long codecInitializingTimestamp = SystemClock.elapsedRealtime();
|
|
||||||
TraceUtil.beginSection("createVpxDecoder");
|
|
||||||
decoder = new VpxDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE, mediaCrypto);
|
|
||||||
decoder.setOutputMode(outputMode);
|
|
||||||
TraceUtil.endSection();
|
TraceUtil.endSection();
|
||||||
long codecInitializedTimestamp = SystemClock.elapsedRealtime();
|
} catch (VpxDecoderException e) {
|
||||||
eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp,
|
throw ExoPlaybackException.createForRenderer(e, getIndex());
|
||||||
codecInitializedTimestamp - codecInitializingTimestamp);
|
|
||||||
decoderCounters.decoderInitCount++;
|
|
||||||
}
|
}
|
||||||
TraceUtil.beginSection("drainAndFeed");
|
decoderCounters.ensureUpdated();
|
||||||
while (drainOutputBuffer(positionUs)) {}
|
|
||||||
while (feedInputBuffer()) {}
|
|
||||||
TraceUtil.endSection();
|
|
||||||
} catch (VpxDecoderException e) {
|
|
||||||
throw ExoPlaybackException.createForRenderer(e, getIndex());
|
|
||||||
}
|
}
|
||||||
decoderCounters.ensureUpdated();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean drainOutputBuffer(long positionUs) throws VpxDecoderException {
|
private boolean drainOutputBuffer(long positionUs) throws VpxDecoderException {
|
||||||
|
|
@ -399,15 +379,14 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException {
|
private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException {
|
||||||
if (drmSession == null) {
|
if (drmSession == null || (!bufferEncrypted && playClearSamplesWithoutKeys)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int drmSessionState = drmSession.getState();
|
@DrmSession.State int drmSessionState = drmSession.getState();
|
||||||
if (drmSessionState == DrmSession.STATE_ERROR) {
|
if (drmSessionState == DrmSession.STATE_ERROR) {
|
||||||
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
||||||
}
|
}
|
||||||
return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS
|
return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS;
|
||||||
&& (bufferEncrypted || !playClearSamplesWithoutKeys);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void flushDecoder() {
|
private void flushDecoder() {
|
||||||
|
|
@ -516,6 +495,40 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void maybeInitDecoder() throws ExoPlaybackException {
|
||||||
|
if (decoder != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
drmSession = pendingDrmSession;
|
||||||
|
ExoMediaCrypto mediaCrypto = null;
|
||||||
|
if (drmSession != null) {
|
||||||
|
mediaCrypto = drmSession.getMediaCrypto();
|
||||||
|
if (mediaCrypto == null) {
|
||||||
|
DrmSessionException drmError = drmSession.getError();
|
||||||
|
if (drmError != null) {
|
||||||
|
throw ExoPlaybackException.createForRenderer(drmError, getIndex());
|
||||||
|
}
|
||||||
|
// The drm session isn't open yet.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
long codecInitializingTimestamp = SystemClock.elapsedRealtime();
|
||||||
|
TraceUtil.beginSection("createVpxDecoder");
|
||||||
|
decoder = new VpxDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE, mediaCrypto);
|
||||||
|
decoder.setOutputMode(outputMode);
|
||||||
|
TraceUtil.endSection();
|
||||||
|
long codecInitializedTimestamp = SystemClock.elapsedRealtime();
|
||||||
|
eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp,
|
||||||
|
codecInitializedTimestamp - codecInitializingTimestamp);
|
||||||
|
decoderCounters.decoderInitCount++;
|
||||||
|
} catch (VpxDecoderException e) {
|
||||||
|
throw ExoPlaybackException.createForRenderer(e, getIndex());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void releaseDecoder() {
|
private void releaseDecoder() {
|
||||||
if (decoder != null) {
|
if (decoder != null) {
|
||||||
decoder.release();
|
decoder.release();
|
||||||
|
|
|
||||||
|
|
@ -1463,7 +1463,7 @@ public final class AudioTrack {
|
||||||
@TargetApi(21)
|
@TargetApi(21)
|
||||||
private int writeNonBlockingWithAvSyncV21(android.media.AudioTrack audioTrack,
|
private int writeNonBlockingWithAvSyncV21(android.media.AudioTrack audioTrack,
|
||||||
ByteBuffer buffer, int size, long presentationTimeUs) {
|
ByteBuffer buffer, int size, long presentationTimeUs) {
|
||||||
// TODO: Uncomment this when [Internal ref b/33627517] is clarified or fixed.
|
// TODO: Uncomment this when [Internal ref: b/33627517] is clarified or fixed.
|
||||||
// if (Util.SDK_INT >= 23) {
|
// if (Util.SDK_INT >= 23) {
|
||||||
// // The underlying platform AudioTrack writes AV sync headers directly.
|
// // The underlying platform AudioTrack writes AV sync headers directly.
|
||||||
// return audioTrack.write(buffer, size, WRITE_NON_BLOCKING, presentationTimeUs * 1000);
|
// return audioTrack.write(buffer, size, WRITE_NON_BLOCKING, presentationTimeUs * 1000);
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||||
import com.google.android.exoplayer2.decoder.SimpleDecoder;
|
import com.google.android.exoplayer2.decoder.SimpleDecoder;
|
||||||
import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
|
import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
|
||||||
import com.google.android.exoplayer2.drm.DrmSession;
|
import com.google.android.exoplayer2.drm.DrmSession;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
@ -376,15 +377,14 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException {
|
private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException {
|
||||||
if (drmSession == null) {
|
if (drmSession == null || (!bufferEncrypted && playClearSamplesWithoutKeys)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@DrmSession.State int drmSessionState = drmSession.getState();
|
@DrmSession.State int drmSessionState = drmSession.getState();
|
||||||
if (drmSessionState == DrmSession.STATE_ERROR) {
|
if (drmSessionState == DrmSession.STATE_ERROR) {
|
||||||
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
||||||
}
|
}
|
||||||
return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS
|
return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS;
|
||||||
&& (bufferEncrypted || !playClearSamplesWithoutKeys);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processEndOfStream() throws ExoPlaybackException {
|
private void processEndOfStream() throws ExoPlaybackException {
|
||||||
|
|
@ -514,13 +514,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
||||||
drmSession = pendingDrmSession;
|
drmSession = pendingDrmSession;
|
||||||
ExoMediaCrypto mediaCrypto = null;
|
ExoMediaCrypto mediaCrypto = null;
|
||||||
if (drmSession != null) {
|
if (drmSession != null) {
|
||||||
@DrmSession.State int drmSessionState = drmSession.getState();
|
mediaCrypto = drmSession.getMediaCrypto();
|
||||||
if (drmSessionState == DrmSession.STATE_ERROR) {
|
if (mediaCrypto == null) {
|
||||||
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
DrmSessionException drmError = drmSession.getError();
|
||||||
} else if (drmSessionState == DrmSession.STATE_OPENED
|
if (drmError != null) {
|
||||||
|| drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) {
|
throw ExoPlaybackException.createForRenderer(drmError, getIndex());
|
||||||
mediaCrypto = drmSession.getMediaCrypto();
|
}
|
||||||
} else {
|
|
||||||
// The drm session isn't open yet.
|
// The drm session isn't open yet.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -222,7 +222,6 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
|
||||||
this.eventHandler = eventHandler;
|
this.eventHandler = eventHandler;
|
||||||
this.eventListener = eventListener;
|
this.eventListener = eventListener;
|
||||||
mediaDrm.setOnEventListener(new MediaDrmEventListener());
|
mediaDrm.setOnEventListener(new MediaDrmEventListener());
|
||||||
state = STATE_CLOSED;
|
|
||||||
mode = MODE_PLAYBACK;
|
mode = MODE_PLAYBACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -358,7 +357,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
|
||||||
if (--openCount != 0) {
|
if (--openCount != 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state = STATE_CLOSED;
|
state = STATE_RELEASED;
|
||||||
provisioningInProgress = false;
|
provisioningInProgress = false;
|
||||||
mediaDrmHandler.removeCallbacksAndMessages(null);
|
mediaDrmHandler.removeCallbacksAndMessages(null);
|
||||||
postResponseHandler.removeCallbacksAndMessages(null);
|
postResponseHandler.removeCallbacksAndMessages(null);
|
||||||
|
|
@ -384,35 +383,19 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public final T getMediaCrypto() {
|
|
||||||
if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) {
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
return mediaCrypto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean requiresSecureDecoderComponent(String mimeType) {
|
|
||||||
if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) {
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
return mediaCrypto.requiresSecureDecoderComponent(mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final DrmSessionException getError() {
|
public final DrmSessionException getError() {
|
||||||
return state == STATE_ERROR ? lastException : null;
|
return state == STATE_ERROR ? lastException : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final T getMediaCrypto() {
|
||||||
|
return mediaCrypto;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String> queryKeyStatus() {
|
public Map<String, String> queryKeyStatus() {
|
||||||
// User may call this method rightfully even if state == STATE_ERROR. So only check if there is
|
return sessionId == null ? null : mediaDrm.queryKeyStatus(sessionId);
|
||||||
// a sessionId
|
|
||||||
if (sessionId == null) {
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
return mediaDrm.queryKeyStatus(sessionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -41,16 +41,16 @@ public interface DrmSession<T extends ExoMediaCrypto> {
|
||||||
* The state of the DRM session.
|
* The state of the DRM session.
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@IntDef({STATE_ERROR, STATE_CLOSED, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS})
|
@IntDef({STATE_RELEASED, STATE_ERROR, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS})
|
||||||
@interface State {}
|
@interface State {}
|
||||||
|
/**
|
||||||
|
* The session has been released.
|
||||||
|
*/
|
||||||
|
int STATE_RELEASED = 0;
|
||||||
/**
|
/**
|
||||||
* The session has encountered an error. {@link #getError()} can be used to retrieve the cause.
|
* The session has encountered an error. {@link #getError()} can be used to retrieve the cause.
|
||||||
*/
|
*/
|
||||||
int STATE_ERROR = 0;
|
int STATE_ERROR = 1;
|
||||||
/**
|
|
||||||
* The session is closed.
|
|
||||||
*/
|
|
||||||
int STATE_CLOSED = 1;
|
|
||||||
/**
|
/**
|
||||||
* The session is being opened.
|
* The session is being opened.
|
||||||
*/
|
*/
|
||||||
|
|
@ -65,66 +65,40 @@ public interface DrmSession<T extends ExoMediaCrypto> {
|
||||||
int STATE_OPENED_WITH_KEYS = 4;
|
int STATE_OPENED_WITH_KEYS = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current state of the session.
|
* Returns the current state of the session, which is one of {@link #STATE_ERROR},
|
||||||
*
|
* {@link #STATE_RELEASED}, {@link #STATE_OPENING}, {@link #STATE_OPENED} and
|
||||||
* @return One of {@link #STATE_ERROR}, {@link #STATE_CLOSED}, {@link #STATE_OPENING},
|
* {@link #STATE_OPENED_WITH_KEYS}.
|
||||||
* {@link #STATE_OPENED} and {@link #STATE_OPENED_WITH_KEYS}.
|
|
||||||
*/
|
*/
|
||||||
@State int getState();
|
@State int getState();
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@link ExoMediaCrypto} for the open session.
|
|
||||||
* <p>
|
|
||||||
* This method may be called when the session is in the following states:
|
|
||||||
* {@link #STATE_OPENED}, {@link #STATE_OPENED_WITH_KEYS}
|
|
||||||
*
|
|
||||||
* @return A {@link ExoMediaCrypto} for the open session.
|
|
||||||
* @throws IllegalStateException If called when a session isn't opened.
|
|
||||||
*/
|
|
||||||
T getMediaCrypto();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the session requires a secure decoder for the specified mime type.
|
|
||||||
* <p>
|
|
||||||
* Normally this method should return
|
|
||||||
* {@link ExoMediaCrypto#requiresSecureDecoderComponent(String)}, however in some cases
|
|
||||||
* implementations may wish to modify the return value (i.e. to force a secure decoder even when
|
|
||||||
* one is not required).
|
|
||||||
* <p>
|
|
||||||
* This method may be called when the session is in the following states:
|
|
||||||
* {@link #STATE_OPENED}, {@link #STATE_OPENED_WITH_KEYS}
|
|
||||||
*
|
|
||||||
* @return Whether the open session requires a secure decoder for the specified mime type.
|
|
||||||
* @throws IllegalStateException If called when a session isn't opened.
|
|
||||||
*/
|
|
||||||
boolean requiresSecureDecoderComponent(String mimeType);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the cause of the error state.
|
* Returns the cause of the error state.
|
||||||
* <p>
|
|
||||||
* This method may be called when the session is in any state.
|
|
||||||
*
|
|
||||||
* @return An exception if the state is {@link #STATE_ERROR}. Null otherwise.
|
|
||||||
*/
|
*/
|
||||||
DrmSessionException getError();
|
DrmSessionException getError();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an informative description of the key status for the session. The status is in the form
|
* Returns a {@link ExoMediaCrypto} for the open session, or null if called before the session has
|
||||||
* of {name, value} pairs.
|
* been opened or after it's been released.
|
||||||
*
|
*/
|
||||||
* <p>Since DRM license policies vary by vendor, the specific status field names are determined by
|
T getMediaCrypto();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map describing the key status for the session, or null if called before the session
|
||||||
|
* has been opened or after it's been released.
|
||||||
|
* <p>
|
||||||
|
* Since DRM license policies vary by vendor, the specific status field names are determined by
|
||||||
* each DRM vendor. Refer to your DRM provider documentation for definitions of the field names
|
* each DRM vendor. Refer to your DRM provider documentation for definitions of the field names
|
||||||
* for a particular DRM engine plugin.
|
* for a particular DRM engine plugin.
|
||||||
*
|
*
|
||||||
* @return A map of key status.
|
* @return A map describing the key status for the session, or null if called before the session
|
||||||
* @throws IllegalStateException If called when the session isn't opened.
|
* has been opened or after it's been released.
|
||||||
* @see MediaDrm#queryKeyStatus(byte[])
|
* @see MediaDrm#queryKeyStatus(byte[])
|
||||||
*/
|
*/
|
||||||
Map<String, String> queryKeyStatus();
|
Map<String, String> queryKeyStatus();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the key set id of the offline license loaded into this session, if there is one. Null
|
* Returns the key set id of the offline license loaded into this session, or null if there isn't
|
||||||
* otherwise.
|
* one.
|
||||||
*/
|
*/
|
||||||
byte[] getOfflineLicenseKeySetId();
|
byte[] getOfflineLicenseKeySetId();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,12 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||||
public final class FrameworkMediaCrypto implements ExoMediaCrypto {
|
public final class FrameworkMediaCrypto implements ExoMediaCrypto {
|
||||||
|
|
||||||
private final MediaCrypto mediaCrypto;
|
private final MediaCrypto mediaCrypto;
|
||||||
|
private final boolean forceAllowInsecureDecoderComponents;
|
||||||
|
|
||||||
/* package */ FrameworkMediaCrypto(MediaCrypto mediaCrypto) {
|
/* package */ FrameworkMediaCrypto(MediaCrypto mediaCrypto,
|
||||||
|
boolean forceAllowInsecureDecoderComponents) {
|
||||||
this.mediaCrypto = Assertions.checkNotNull(mediaCrypto);
|
this.mediaCrypto = Assertions.checkNotNull(mediaCrypto);
|
||||||
|
this.forceAllowInsecureDecoderComponents = forceAllowInsecureDecoderComponents;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MediaCrypto getWrappedMediaCrypto() {
|
public MediaCrypto getWrappedMediaCrypto() {
|
||||||
|
|
@ -37,7 +40,8 @@ public final class FrameworkMediaCrypto implements ExoMediaCrypto {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean requiresSecureDecoderComponent(String mimeType) {
|
public boolean requiresSecureDecoderComponent(String mimeType) {
|
||||||
return mediaCrypto.requiresSecureDecoderComponent(mimeType);
|
return !forceAllowInsecureDecoderComponents
|
||||||
|
&& mediaCrypto.requiresSecureDecoderComponent(mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,9 @@ import android.media.NotProvisionedException;
|
||||||
import android.media.ResourceBusyException;
|
import android.media.ResourceBusyException;
|
||||||
import android.media.UnsupportedSchemeException;
|
import android.media.UnsupportedSchemeException;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -163,7 +165,12 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
|
||||||
@Override
|
@Override
|
||||||
public FrameworkMediaCrypto createMediaCrypto(UUID uuid, byte[] initData)
|
public FrameworkMediaCrypto createMediaCrypto(UUID uuid, byte[] initData)
|
||||||
throws MediaCryptoException {
|
throws MediaCryptoException {
|
||||||
return new FrameworkMediaCrypto(new MediaCrypto(uuid, initData));
|
// Work around a bug prior to Lollipop where L1 Widevine forced into L3 mode would still
|
||||||
|
// indicate that it required secure video decoders [Internal ref: b/11428937].
|
||||||
|
boolean forceAllowInsecureDecoderComponents = Util.SDK_INT < 21
|
||||||
|
&& C.WIDEVINE_UUID.equals(uuid) && "L3".equals(getPropertyString("securityLevel"));
|
||||||
|
return new FrameworkMediaCrypto(new MediaCrypto(uuid, initData),
|
||||||
|
forceAllowInsecureDecoderComponents);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,14 +34,16 @@ public final class WidevineUtil {
|
||||||
/**
|
/**
|
||||||
* Returns license and playback durations remaining in seconds.
|
* Returns license and playback durations remaining in seconds.
|
||||||
*
|
*
|
||||||
* @return A {@link Pair} consisting of the remaining license and playback durations in seconds.
|
* @param drmSession The drm session to query.
|
||||||
* @throws IllegalStateException If called when a session isn't opened.
|
* @return A {@link Pair} consisting of the remaining license and playback durations in seconds,
|
||||||
* @param drmSession
|
* or null if called before the session has been opened or after it's been released.
|
||||||
*/
|
*/
|
||||||
public static Pair<Long, Long> getLicenseDurationRemainingSec(DrmSession<?> drmSession) {
|
public static Pair<Long, Long> getLicenseDurationRemainingSec(DrmSession<?> drmSession) {
|
||||||
Map<String, String> keyStatus = drmSession.queryKeyStatus();
|
Map<String, String> keyStatus = drmSession.queryKeyStatus();
|
||||||
return new Pair<>(
|
if (keyStatus == null) {
|
||||||
getDurationRemainingSec(keyStatus, PROPERTY_LICENSE_DURATION_REMAINING),
|
return null;
|
||||||
|
}
|
||||||
|
return new Pair<>(getDurationRemainingSec(keyStatus, PROPERTY_LICENSE_DURATION_REMAINING),
|
||||||
getDurationRemainingSec(keyStatus, PROPERTY_PLAYBACK_DURATION_REMAINING));
|
getDurationRemainingSec(keyStatus, PROPERTY_PLAYBACK_DURATION_REMAINING));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import com.google.android.exoplayer2.FormatHolder;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||||
import com.google.android.exoplayer2.drm.DrmSession;
|
import com.google.android.exoplayer2.drm.DrmSession;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
|
||||||
|
|
@ -298,20 +299,20 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
|
|
||||||
drmSession = pendingDrmSession;
|
drmSession = pendingDrmSession;
|
||||||
String mimeType = format.sampleMimeType;
|
String mimeType = format.sampleMimeType;
|
||||||
MediaCrypto mediaCrypto = null;
|
MediaCrypto wrappedMediaCrypto = null;
|
||||||
boolean drmSessionRequiresSecureDecoder = false;
|
boolean drmSessionRequiresSecureDecoder = false;
|
||||||
if (drmSession != null) {
|
if (drmSession != null) {
|
||||||
@DrmSession.State int drmSessionState = drmSession.getState();
|
FrameworkMediaCrypto mediaCrypto = drmSession.getMediaCrypto();
|
||||||
if (drmSessionState == DrmSession.STATE_ERROR) {
|
if (mediaCrypto == null) {
|
||||||
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
DrmSessionException drmError = drmSession.getError();
|
||||||
} else if (drmSessionState == DrmSession.STATE_OPENED
|
if (drmError != null) {
|
||||||
|| drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) {
|
throw ExoPlaybackException.createForRenderer(drmError, getIndex());
|
||||||
mediaCrypto = drmSession.getMediaCrypto().getWrappedMediaCrypto();
|
}
|
||||||
drmSessionRequiresSecureDecoder = drmSession.requiresSecureDecoderComponent(mimeType);
|
|
||||||
} else {
|
|
||||||
// The drm session isn't open yet.
|
// The drm session isn't open yet.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
wrappedMediaCrypto = mediaCrypto.getWrappedMediaCrypto();
|
||||||
|
drmSessionRequiresSecureDecoder = mediaCrypto.requiresSecureDecoderComponent(mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codecInfo == null) {
|
if (codecInfo == null) {
|
||||||
|
|
@ -358,7 +359,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
codec = MediaCodec.createByCodecName(codecName);
|
codec = MediaCodec.createByCodecName(codecName);
|
||||||
TraceUtil.endSection();
|
TraceUtil.endSection();
|
||||||
TraceUtil.beginSection("configureCodec");
|
TraceUtil.beginSection("configureCodec");
|
||||||
configureCodec(codecInfo, codec, format, mediaCrypto);
|
configureCodec(codecInfo, codec, format, wrappedMediaCrypto);
|
||||||
TraceUtil.endSection();
|
TraceUtil.endSection();
|
||||||
TraceUtil.beginSection("startCodec");
|
TraceUtil.beginSection("startCodec");
|
||||||
codec.start();
|
codec.start();
|
||||||
|
|
@ -736,15 +737,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException {
|
private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException {
|
||||||
if (drmSession == null) {
|
if (drmSession == null || (!bufferEncrypted && playClearSamplesWithoutKeys)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@DrmSession.State int drmSessionState = drmSession.getState();
|
@DrmSession.State int drmSessionState = drmSession.getState();
|
||||||
if (drmSessionState == DrmSession.STATE_ERROR) {
|
if (drmSessionState == DrmSession.STATE_ERROR) {
|
||||||
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
||||||
}
|
}
|
||||||
return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS
|
return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS;
|
||||||
&& (bufferEncrypted || !playClearSamplesWithoutKeys);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue