diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java index db253c7757..c95bf59eb5 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer; import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException; +import com.google.android.exoplayer.drm.DrmSession; import com.google.android.exoplayer.drm.DrmSessionManager; import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.NalUnitUtil; @@ -155,6 +156,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { private Format format; private MediaCodec codec; + private DrmSession drmSession; private boolean codecIsAdaptive; private boolean codecNeedsDiscardToSpsWorkaround; private boolean codecNeedsFlushWorkaround; @@ -167,7 +169,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { private int inputIndex; private int outputIndex; private boolean shouldSkipOutputBuffer; - private boolean openedDrmSession; private boolean codecReconfigured; private int codecReconfigurationState; private int codecReinitializationState; @@ -266,20 +267,17 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { if (drmSessionManager == null) { throw ExoPlaybackException.createForRenderer( new IllegalStateException("Media requires a DrmSessionManager"), getIndex()); - } else if (drmSessionManager.getState() == DrmSessionManager.STATE_ERROR) { - throw ExoPlaybackException.createForRenderer(drmSessionManager.getError(), getIndex()); } - if (!openedDrmSession) { - drmSessionManager.open(Looper.myLooper(), format.drmInitData); - openedDrmSession = true; + if (drmSession == null) { + drmSession = drmSessionManager.acquireSession(Looper.myLooper(), format.drmInitData); } - int drmSessionState = drmSessionManager.getState(); - if (drmSessionState == DrmSessionManager.STATE_ERROR) { - throw ExoPlaybackException.createForRenderer(drmSessionManager.getError(), getIndex()); - } else if (drmSessionState == DrmSessionManager.STATE_OPENED - || drmSessionState == DrmSessionManager.STATE_OPENED_WITH_KEYS) { - mediaCrypto = drmSessionManager.getMediaCrypto(); - requiresSecureDecoder = drmSessionManager.requiresSecureDecoderComponent(mimeType); + int drmSessionState = drmSession.getState(); + if (drmSessionState == DrmSession.STATE_ERROR) { + throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); + } else if (drmSessionState == DrmSession.STATE_OPENED + || drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) { + mediaCrypto = drmSession.getMediaCrypto(); + requiresSecureDecoder = drmSession.requiresSecureDecoderComponent(mimeType); } else { // The drm session isn't open yet. return; @@ -349,9 +347,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { releaseCodec(); } finally { try { - if (openedDrmSession) { - drmSessionManager.close(); - openedDrmSession = false; + if (drmSession != null) { + drmSessionManager.releaseSession(drmSession); + drmSession = null; } } finally { super.onDisabled(); @@ -612,14 +610,14 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { } private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException { - if (!openedDrmSession) { + if (drmSession == null) { return false; } - int drmManagerState = drmSessionManager.getState(); - if (drmManagerState == DrmSessionManager.STATE_ERROR) { - throw ExoPlaybackException.createForRenderer(drmSessionManager.getError(), getIndex()); + int drmSessionState = drmSession.getState(); + if (drmSessionState == DrmSession.STATE_ERROR) { + throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); } - return drmManagerState != DrmSessionManager.STATE_OPENED_WITH_KEYS + return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS && (bufferEncrypted || !playClearSamplesWithoutKeys); } diff --git a/library/src/main/java/com/google/android/exoplayer/drm/DrmSession.java b/library/src/main/java/com/google/android/exoplayer/drm/DrmSession.java new file mode 100644 index 0000000000..d31f3b1f52 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/drm/DrmSession.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2014 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 com.google.android.exoplayer.drm; + +import android.annotation.TargetApi; +import android.media.MediaCrypto; + +/** + * A DRM session. + */ +@TargetApi(16) +public interface DrmSession { + + /** + * The session is in an error state. {@link #getError()} can be used to retrieve the cause. + */ + int STATE_ERROR = 0; + /** + * The session is closed. + */ + int STATE_CLOSED = 1; + /** + * The session is being opened. + */ + int STATE_OPENING = 2; + /** + * The session is open, but does not yet have the keys required for decryption. + */ + int STATE_OPENED = 3; + /** + * The session is open and has the keys required for decryption. + */ + int STATE_OPENED_WITH_KEYS = 3; + + /** + * Gets the current state of the session. + * + * @return One of {@link #STATE_ERROR}, {@link #STATE_CLOSED}, {@link #STATE_OPENING}, + * {@link #STATE_OPENED} and {@link #STATE_OPENED_WITH_KEYS}. + */ + int getState(); + + /** + * Gets a {@link MediaCrypto} for the open session. + *

+ * This method may be called when the manager is in the following states: + * {@link #STATE_OPENED}, {@link #STATE_OPENED_WITH_KEYS} + * + * @return A {@link MediaCrypto} for the open session. + * @throws IllegalStateException If called when a session isn't opened. + */ + MediaCrypto getMediaCrypto(); + + /** + * Whether the session requires a secure decoder for the specified mime type. + *

+ * Normally this method should return {@link MediaCrypto#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). + *

+ * This method may be called when the manager 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); + + /** + * Gets the cause of the error state. + *

+ * This method may be called when the manager is in any state. + * + * @return An exception if the state is {@link #STATE_ERROR}. Null otherwise. + */ + Exception getError(); + +} diff --git a/library/src/main/java/com/google/android/exoplayer/drm/DrmSessionManager.java b/library/src/main/java/com/google/android/exoplayer/drm/DrmSessionManager.java index 16fa52b730..940c347c2b 100644 --- a/library/src/main/java/com/google/android/exoplayer/drm/DrmSessionManager.java +++ b/library/src/main/java/com/google/android/exoplayer/drm/DrmSessionManager.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer.drm; import android.annotation.TargetApi; -import android.media.MediaCrypto; import android.os.Looper; /** @@ -26,81 +25,20 @@ import android.os.Looper; public interface DrmSessionManager { /** - * The error state. {@link #getError()} can be used to retrieve the cause. - */ - int STATE_ERROR = 0; - /** - * The session is closed. - */ - int STATE_CLOSED = 1; - /** - * The session is being opened (i.e. {@link #open(Looper, DrmInitData)} has been called, but the - * session is not yet open). - */ - int STATE_OPENING = 2; - /** - * The session is open, but does not yet have the keys required for decryption. - */ - int STATE_OPENED = 3; - /** - * The session is open and has the keys required for decryption. - */ - int STATE_OPENED_WITH_KEYS = 4; - - /** - * Opens the session, possibly asynchronously. + * Acquires a {@link DrmSession} for the specified {@link DrmInitData}. + *

+ * The {@link DrmSession} must be returned to {@link #releaseSession(DrmSession)} when it is no + * longer required * * @param playbackLooper The looper associated with the media playback thread. * @param drmInitData DRM initialization data. + * @return The DRM session. */ - void open(Looper playbackLooper, DrmInitData drmInitData); + DrmSession acquireSession(Looper playbackLooper, DrmInitData drmInitData); /** - * Closes the session. + * Releases a {@link DrmSession}. */ - void close(); - - /** - * Gets the current state of the session. - * - * @return One of {@link #STATE_ERROR}, {@link #STATE_CLOSED}, {@link #STATE_OPENING}, - * {@link #STATE_OPENED} and {@link #STATE_OPENED_WITH_KEYS}. - */ - int getState(); - - /** - * Gets a {@link MediaCrypto} for the open session. - *

- * This method may be called when the manager is in the following states: - * {@link #STATE_OPENED}, {@link #STATE_OPENED_WITH_KEYS} - * - * @return A {@link MediaCrypto} for the open session. - * @throws IllegalStateException If called when a session isn't opened. - */ - MediaCrypto getMediaCrypto(); - - /** - * Whether the session requires a secure decoder for the specified mime type. - *

- * Normally this method should return {@link MediaCrypto#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). - *

- * This method may be called when the manager 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); - - /** - * Gets the cause of the error state. - *

- * This method may be called when the manager is in any state. - * - * @return An exception if the state is {@link #STATE_ERROR}. Null otherwise. - */ - Exception getError(); + void releaseSession(DrmSession drmSession); } diff --git a/library/src/main/java/com/google/android/exoplayer/drm/StreamingDrmSessionManager.java b/library/src/main/java/com/google/android/exoplayer/drm/StreamingDrmSessionManager.java index 044e2a95f4..a670484d13 100644 --- a/library/src/main/java/com/google/android/exoplayer/drm/StreamingDrmSessionManager.java +++ b/library/src/main/java/com/google/android/exoplayer/drm/StreamingDrmSessionManager.java @@ -41,11 +41,10 @@ import java.util.HashMap; import java.util.UUID; /** - * A base class for {@link DrmSessionManager} implementations that support streaming playbacks - * using {@link MediaDrm}. + * A {@link DrmSessionManager} that supports streaming playbacks using {@link MediaDrm}. */ @TargetApi(18) -public class StreamingDrmSessionManager implements DrmSessionManager { +public class StreamingDrmSessionManager implements DrmSessionManager, DrmSession { /** * Interface definition for a callback to be notified of {@link StreamingDrmSessionManager} @@ -172,32 +171,6 @@ public class StreamingDrmSessionManager implements DrmSessionManager { state = STATE_CLOSED; } - @Override - public final int getState() { - return state; - } - - @Override - public final MediaCrypto 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 - public final Exception getError() { - return state == STATE_ERROR ? lastException : null; - } - /** * Provides access to {@link MediaDrm#getPropertyString(String)}. *

@@ -246,11 +219,13 @@ public class StreamingDrmSessionManager implements DrmSessionManager { mediaDrm.setPropertyByteArray(key, value); } + // DrmSessionManager implementation. + @Override - public void open(Looper playbackLooper, DrmInitData drmInitData) { + public DrmSession acquireSession(Looper playbackLooper, DrmInitData drmInitData) { Assertions.checkState(this.playbackLooper == null || this.playbackLooper == playbackLooper); if (++openCount != 1) { - return; + return this; } if (this.playbackLooper == null) { @@ -266,7 +241,7 @@ public class StreamingDrmSessionManager implements DrmSessionManager { schemeData = drmInitData.get(uuid); if (schemeData == null) { onError(new IllegalStateException("Media does not support uuid: " + uuid)); - return; + return this; } if (Util.SDK_INT < 21) { // Prior to L the Widevine CDM required data to be extracted from the PSSH atom. @@ -279,10 +254,11 @@ public class StreamingDrmSessionManager implements DrmSessionManager { } state = STATE_OPENING; openInternal(true); + return this; } @Override - public void close() { + public void releaseSession(DrmSession session) { if (--openCount != 0) { return; } @@ -303,6 +279,36 @@ public class StreamingDrmSessionManager implements DrmSessionManager { } } + // DrmSession implementation. + + @Override + public final int getState() { + return state; + } + + @Override + public final MediaCrypto 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 + public final Exception getError() { + return state == STATE_ERROR ? lastException : null; + } + + // Internal methods. + private void openInternal(boolean allowProvisioning) { try { sessionId = mediaDrm.openSession();