diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DecryptableSampleQueueReader.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DecryptableSampleQueueReader.java
deleted file mode 100644
index b6f730cea3..0000000000
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/DecryptableSampleQueueReader.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2019 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.exoplayer2.source;
-
-import android.os.Looper;
-import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.FormatHolder;
-import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
-import com.google.android.exoplayer2.drm.DrmInitData;
-import com.google.android.exoplayer2.drm.DrmSession;
-import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.Util;
-import java.io.IOException;
-import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
-
-/**
- * Reads from a {@link SampleQueue} and attaches {@link DrmSession} references to the {@link Format
- * Formats} of encrypted regions.
- */
-public final class DecryptableSampleQueueReader {
-
- private final SampleQueue upstream;
- private final DrmSessionManager> sessionManager;
- private final FormatHolder formatHolder;
- private final boolean playClearSamplesWithoutKeys;
- private @MonotonicNonNull Format currentFormat;
- @Nullable private DrmSession> currentSession;
-
- /**
- * Creates a sample queue reader.
- *
- * @param upstream The {@link SampleQueue} from which the created reader will read samples.
- * @param sessionManager The {@link DrmSessionManager} that will provide {@link DrmSession
- * DrmSessions} for the encrypted regions.
- */
- public DecryptableSampleQueueReader(SampleQueue upstream, DrmSessionManager> sessionManager) {
- this.upstream = upstream;
- this.sessionManager = sessionManager;
- formatHolder = new FormatHolder();
- playClearSamplesWithoutKeys =
- (sessionManager.getFlags() & DrmSessionManager.FLAG_PLAY_CLEAR_SAMPLES_WITHOUT_KEYS) != 0;
- }
-
- /** Releases any resources acquired by this reader. */
- public void release() {
- if (currentSession != null) {
- currentSession.releaseReference();
- currentSession = null;
- }
- }
-
- /**
- * Throws an error that's preventing data from being read. Does nothing if no such error exists.
- *
- * @throws IOException The underlying error.
- */
- public void maybeThrowError() throws IOException {
- // TODO: Avoid throwing if the DRM error is not preventing a read operation.
- if (currentSession != null && currentSession.getState() == DrmSession.STATE_ERROR) {
- throw Assertions.checkNotNull(currentSession.getError());
- }
- }
-
- /**
- * Reads from the upstream {@link SampleQueue}, populating {@link FormatHolder#drmSession} if the
- * current {@link Format#drmInitData} is not null.
- *
- *
This reader guarantees that any read results are usable by clients. An encrypted sample will
- * only be returned along with a {@link FormatHolder#drmSession} that has available keys.
- *
- * @param outputFormatHolder A {@link FormatHolder} to populate in the case of reading a format.
- * {@link FormatHolder#drmSession} will be populated if the read format's {@link
- * Format#drmInitData} is not null.
- * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the
- * end of the stream. If the end of the stream has been reached, the {@link
- * C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. If a {@link
- * DecoderInputBuffer#isFlagsOnly() flags-only} buffer is passed, only the buffer flags may be
- * populated by this method and the read position of the queue will not change.
- * @param formatRequired Whether the caller requires that the format of the stream be read even if
- * it's not changing. A sample will never be read if set to true, however it is still possible
- * for the end of stream or nothing to be read.
- * @param loadingFinished True if an empty queue should be considered the end of the stream.
- * @param decodeOnlyUntilUs If a buffer is read, the {@link C#BUFFER_FLAG_DECODE_ONLY} flag will
- * be set if the buffer's timestamp is less than this value.
- * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or
- * {@link C#RESULT_BUFFER_READ}.
- */
- @SuppressWarnings("ReferenceEquality")
- public int read(
- FormatHolder outputFormatHolder,
- DecoderInputBuffer buffer,
- boolean formatRequired,
- boolean loadingFinished,
- long decodeOnlyUntilUs) {
-
- boolean readFlagFormatRequired = false;
- boolean readFlagAllowOnlyClearBuffers = false;
- boolean onlyPropagateFormatChanges = false;
-
- if (currentFormat == null || formatRequired) {
- readFlagFormatRequired = true;
- } else if (sessionManager != DrmSessionManager.DUMMY
- && currentFormat.drmInitData != null
- && Assertions.checkNotNull(currentSession).getState()
- != DrmSession.STATE_OPENED_WITH_KEYS) {
- if (playClearSamplesWithoutKeys) {
- // Content is encrypted and keys are not available, but clear samples are ok for reading.
- readFlagAllowOnlyClearBuffers = true;
- } else {
- // We must not read any samples, but we may still read a format or the end of stream.
- // However, because the formatRequired argument is false, we should not propagate a read
- // format unless it is different than the current format.
- onlyPropagateFormatChanges = true;
- readFlagFormatRequired = true;
- }
- }
-
- int result =
- upstream.read(
- formatHolder,
- buffer,
- readFlagFormatRequired,
- readFlagAllowOnlyClearBuffers,
- loadingFinished,
- decodeOnlyUntilUs);
- if (result == C.RESULT_FORMAT_READ) {
- if (onlyPropagateFormatChanges && currentFormat == formatHolder.format) {
- return C.RESULT_NOTHING_READ;
- }
- onFormat(Assertions.checkNotNull(formatHolder.format), outputFormatHolder);
- }
- return result;
- }
-
- /**
- * Updates the current format and manages any necessary DRM resources.
- *
- * @param format The format read from upstream.
- * @param outputFormatHolder The output {@link FormatHolder}.
- */
- private void onFormat(Format format, FormatHolder outputFormatHolder) {
- outputFormatHolder.format = format;
- boolean isFirstFormat = currentFormat == null;
- DrmInitData oldDrmInitData = currentFormat != null ? currentFormat.drmInitData : null;
- currentFormat = format;
- if (sessionManager == DrmSessionManager.DUMMY) {
- // Avoid attempting to acquire a session using the dummy DRM session manager. It's likely that
- // the media source creation has not yet been migrated and the renderer can acquire the
- // session for the read DRM init data.
- // TODO: Remove once renderers are migrated [Internal ref: b/122519809].
- return;
- }
- outputFormatHolder.includesDrmSession = true;
- outputFormatHolder.drmSession = currentSession;
- if (!isFirstFormat && Util.areEqual(oldDrmInitData, format.drmInitData)) {
- // Nothing to do.
- return;
- }
- // Ensure we acquire the new session before releasing the previous one in case the same session
- // can be used for both DrmInitData.
- DrmSession> previousSession = currentSession;
- DrmInitData drmInitData = currentFormat.drmInitData;
- Looper playbackLooper = Assertions.checkNotNull(Looper.myLooper());
- currentSession =
- drmInitData != null
- ? sessionManager.acquireSession(playbackLooper, drmInitData)
- : sessionManager.acquirePlaceholderSession(playbackLooper);
- outputFormatHolder.drmSession = currentSession;
-
- if (previousSession != null) {
- previousSession.releaseReference();
- }
- }
-
- /** Returns whether there is data available for reading. */
- public boolean isReady(boolean loadingFinished) {
- @SampleQueue.PeekResult int nextInQueue = upstream.peekNext();
- if (nextInQueue == SampleQueue.PEEK_RESULT_NOTHING) {
- return loadingFinished;
- } else if (nextInQueue == SampleQueue.PEEK_RESULT_FORMAT) {
- return true;
- } else if (nextInQueue == SampleQueue.PEEK_RESULT_BUFFER_CLEAR) {
- return currentSession == null || playClearSamplesWithoutKeys;
- } else if (nextInQueue == SampleQueue.PEEK_RESULT_BUFFER_ENCRYPTED) {
- return sessionManager == DrmSessionManager.DUMMY
- || Assertions.checkNotNull(currentSession).getState()
- == DrmSession.STATE_OPENED_WITH_KEYS;
- } else {
- throw new IllegalStateException();
- }
- }
-}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java
index 687472df75..30c46006b6 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java
@@ -116,7 +116,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Nullable private SeekMap seekMap;
@Nullable private IcyHeaders icyHeaders;
private SampleQueue[] sampleQueues;
- private DecryptableSampleQueueReader[] sampleQueueReaders;
private TrackId[] sampleQueueTrackIds;
private boolean sampleQueuesBuilt;
private boolean prepared;
@@ -193,7 +192,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
handler = new Handler();
sampleQueueTrackIds = new TrackId[0];
sampleQueues = new SampleQueue[0];
- sampleQueueReaders = new DecryptableSampleQueueReader[0];
pendingResetPositionUs = C.TIME_UNSET;
length = C.LENGTH_UNSET;
durationUs = C.TIME_UNSET;
@@ -206,10 +204,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
// Discard as much as we can synchronously. We only do this if we're prepared, since otherwise
// sampleQueues may still be being modified by the loading thread.
for (SampleQueue sampleQueue : sampleQueues) {
- sampleQueue.discardToEnd();
- }
- for (DecryptableSampleQueueReader reader : sampleQueueReaders) {
- reader.release();
+ sampleQueue.preRelease();
}
}
loader.release(/* callback= */ this);
@@ -222,10 +217,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Override
public void onLoaderReleased() {
for (SampleQueue sampleQueue : sampleQueues) {
- sampleQueue.reset();
- }
- for (DecryptableSampleQueueReader reader : sampleQueueReaders) {
- reader.release();
+ sampleQueue.release();
}
extractorHolder.release();
}
@@ -461,11 +453,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
// SampleStream methods.
/* package */ boolean isReady(int track) {
- return !suppressRead() && sampleQueueReaders[track].isReady(loadingFinished);
+ return !suppressRead() && sampleQueues[track].isReady(loadingFinished);
}
/* package */ void maybeThrowError(int sampleQueueIndex) throws IOException {
- sampleQueueReaders[sampleQueueIndex].maybeThrowError();
+ sampleQueues[sampleQueueIndex].maybeThrowError();
maybeThrowError();
}
@@ -483,7 +475,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
}
maybeNotifyDownstreamFormat(sampleQueueIndex);
int result =
- sampleQueueReaders[sampleQueueIndex].read(
+ sampleQueues[sampleQueueIndex].read(
formatHolder, buffer, formatRequired, loadingFinished, lastSeekPositionUs);
if (result == C.RESULT_NOTHING_READ) {
maybeStartDeferredRetry(sampleQueueIndex);
@@ -690,7 +682,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
return sampleQueues[i];
}
}
- SampleQueue trackOutput = new SampleQueue(allocator);
+ SampleQueue trackOutput = new SampleQueue(allocator, drmSessionManager);
trackOutput.setUpstreamFormatChangeListener(this);
@NullableType
TrackId[] sampleQueueTrackIds = Arrays.copyOf(this.sampleQueueTrackIds, trackCount + 1);
@@ -699,12 +691,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@NullableType SampleQueue[] sampleQueues = Arrays.copyOf(this.sampleQueues, trackCount + 1);
sampleQueues[trackCount] = trackOutput;
this.sampleQueues = Util.castNonNullTypeArray(sampleQueues);
- @NullableType
- DecryptableSampleQueueReader[] sampleQueueReaders =
- Arrays.copyOf(this.sampleQueueReaders, trackCount + 1);
- sampleQueueReaders[trackCount] =
- new DecryptableSampleQueueReader(this.sampleQueues[trackCount], drmSessionManager);
- this.sampleQueueReaders = Util.castNonNullTypeArray(sampleQueueReaders);
return trackOutput;
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java
index fa4a26aa3c..83dc13a6f9 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java
@@ -15,18 +15,24 @@
*/
package com.google.android.exoplayer2.source;
+import android.os.Looper;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import com.google.android.exoplayer2.drm.DrmInitData;
+import com.google.android.exoplayer2.drm.DrmSession;
+import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.source.SampleMetadataQueue.SampleExtrasHolder;
import com.google.android.exoplayer2.upstream.Allocation;
import com.google.android.exoplayer2.upstream.Allocator;
+import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableByteArray;
+import com.google.android.exoplayer2.util.Util;
import java.io.EOFException;
import java.io.IOException;
import java.lang.annotation.Documented;
@@ -61,26 +67,29 @@ public class SampleQueue implements TrackOutput {
PEEK_RESULT_BUFFER_CLEAR,
PEEK_RESULT_BUFFER_ENCRYPTED
})
- @interface PeekResult {}
+ /* package */ @interface PeekResult {}
/** Nothing is available for reading. */
- public static final int PEEK_RESULT_NOTHING = 0;
+ /* package */ static final int PEEK_RESULT_NOTHING = 0;
/** A format change is available for reading */
- public static final int PEEK_RESULT_FORMAT = 1;
+ /* package */ static final int PEEK_RESULT_FORMAT = 1;
/** A clear buffer is available for reading. */
- public static final int PEEK_RESULT_BUFFER_CLEAR = 2;
+ /* package */ static final int PEEK_RESULT_BUFFER_CLEAR = 2;
/** An encrypted buffer is available for reading. */
- public static final int PEEK_RESULT_BUFFER_ENCRYPTED = 3;
+ /* package */ static final int PEEK_RESULT_BUFFER_ENCRYPTED = 3;
public static final int ADVANCE_FAILED = -1;
private static final int INITIAL_SCRATCH_SIZE = 32;
private final Allocator allocator;
+ private final DrmSessionManager> drmSessionManager;
+ private final boolean playClearSamplesWithoutKeys;
private final int allocationLength;
private final SampleMetadataQueue metadataQueue;
private final SampleExtrasHolder extrasHolder;
private final ParsableByteArray scratch;
+ private final FormatHolder scratchFormatHolder;
// References into the linked list of allocations.
private AllocationNode firstAllocationNode;
@@ -89,6 +98,7 @@ public class SampleQueue implements TrackOutput {
// Accessed only by the consuming thread.
private Format downstreamFormat;
+ @Nullable private DrmSession> currentSession;
// Accessed only by the loading thread (or the consuming thread when there is no loading thread).
private boolean pendingFormatAdjustment;
@@ -99,14 +109,23 @@ public class SampleQueue implements TrackOutput {
private UpstreamFormatChangedListener upstreamFormatChangeListener;
/**
+ * Creates a sample queue.
+ *
* @param allocator An {@link Allocator} from which allocations for sample data can be obtained.
+ * @param drmSessionManager The {@link DrmSessionManager} to obtain {@link DrmSession DrmSessions}
+ * from.
*/
- public SampleQueue(Allocator allocator) {
+ public SampleQueue(Allocator allocator, DrmSessionManager> drmSessionManager) {
this.allocator = allocator;
+ this.drmSessionManager = drmSessionManager;
+ playClearSamplesWithoutKeys =
+ (drmSessionManager.getFlags() & DrmSessionManager.FLAG_PLAY_CLEAR_SAMPLES_WITHOUT_KEYS)
+ != 0;
allocationLength = allocator.getIndividualAllocationLength();
metadataQueue = new SampleMetadataQueue();
extrasHolder = new SampleExtrasHolder();
scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE);
+ scratchFormatHolder = new FormatHolder();
firstAllocationNode = new AllocationNode(0, allocationLength);
readAllocationNode = firstAllocationNode;
writeAllocationNode = firstAllocationNode;
@@ -122,7 +141,7 @@ public class SampleQueue implements TrackOutput {
}
/**
- * Resets the output.
+ * Resets the output and releases any held DRM resources.
*
* @param resetUpstreamFormat Whether the upstream format should be cleared. If set to false,
* samples queued after the reset (and before a subsequent call to {@link #format(Format)})
@@ -198,8 +217,18 @@ public class SampleQueue implements TrackOutput {
// Called by the consuming thread.
/**
- * Returns whether a sample is available to be read.
+ * Throws an error that's preventing data from being read. Does nothing if no such error exists.
+ *
+ * @throws IOException The underlying error.
*/
+ public void maybeThrowError() throws IOException {
+ // TODO: Avoid throwing if the DRM error is not preventing a read operation.
+ if (currentSession != null && currentSession.getState() == DrmSession.STATE_ERROR) {
+ throw Assertions.checkNotNull(currentSession.getError());
+ }
+ }
+
+ /** Returns whether a sample is available to be read. */
public boolean hasNextSample() {
return metadataQueue.hasNextSample();
}
@@ -292,6 +321,18 @@ public class SampleQueue implements TrackOutput {
discardDownstreamTo(metadataQueue.discardToRead());
}
+ /** Calls {@link #discardToEnd()} and releases any held DRM resources. */
+ public void preRelease() {
+ discardToEnd();
+ releaseDrmResources();
+ }
+
+ /** Calls {@link #reset()} and releases any held DRM resources. */
+ public void release() {
+ reset();
+ releaseDrmResources();
+ }
+
/**
* Discards to the end of the queue. The read position is also advanced.
*/
@@ -337,20 +378,19 @@ public class SampleQueue implements TrackOutput {
return metadataQueue.setReadPosition(sampleIndex);
}
- /**
- * Returns a {@link PeekResult} depending on what a following call to {@link #read
- * read(formatHolder, decoderInputBuffer, formatRequired= false, allowOnlyClearBuffers= false,
- * loadingFinished= false, decodeOnlyUntilUs= 0)} would result in.
- */
- @PeekResult
- public int peekNext() {
- return metadataQueue.peekNext(downstreamFormat);
- }
-
/**
* Attempts to read from the queue.
*
- * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format.
+ *
{@link Format Formats} read from the this method may be associated to a {@link DrmSession}
+ * through {@link FormatHolder#drmSession}, which is populated in two scenarios:
+ *
+ *
+ * - The sample has a {@link Format} with a non-null {@link Format#drmInitData}.
+ *
- The {@link DrmSessionManager} is configured to use secure decoders for clear samples. See
+ * {@link DrmSessionManager#FLAG_PLAY_CLEAR_SAMPLES_WITHOUT_KEYS}.
+ *
+ *
+ * @param outputFormatHolder A {@link FormatHolder} to populate in the case of reading a format.
* @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the
* end of the stream. If the end of the stream has been reached, the {@link
* C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. If a {@link
@@ -359,33 +399,57 @@ public class SampleQueue implements TrackOutput {
* @param formatRequired Whether the caller requires that the format of the stream be read even if
* it's not changing. A sample will never be read if set to true, however it is still possible
* for the end of stream or nothing to be read.
- * @param allowOnlyClearBuffers If set to true, this method will not return encrypted buffers,
- * returning {@link C#RESULT_NOTHING_READ} (without advancing the read position) instead.
* @param loadingFinished True if an empty queue should be considered the end of the stream.
* @param decodeOnlyUntilUs If a buffer is read, the {@link C#BUFFER_FLAG_DECODE_ONLY} flag will
* be set if the buffer's timestamp is less than this value.
* @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or
* {@link C#RESULT_BUFFER_READ}.
*/
+ @SuppressWarnings("ReferenceEquality")
public int read(
- FormatHolder formatHolder,
+ FormatHolder outputFormatHolder,
DecoderInputBuffer buffer,
boolean formatRequired,
- boolean allowOnlyClearBuffers,
boolean loadingFinished,
long decodeOnlyUntilUs) {
+
+ boolean readFlagFormatRequired = false;
+ boolean readFlagAllowOnlyClearBuffers = false;
+ boolean onlyPropagateFormatChanges = false;
+
+ if (downstreamFormat == null || formatRequired) {
+ readFlagFormatRequired = true;
+ } else if (drmSessionManager != DrmSessionManager.DUMMY
+ && downstreamFormat.drmInitData != null
+ && Assertions.checkNotNull(currentSession).getState()
+ != DrmSession.STATE_OPENED_WITH_KEYS) {
+ if (playClearSamplesWithoutKeys) {
+ // Content is encrypted and keys are not available, but clear samples are ok for reading.
+ readFlagAllowOnlyClearBuffers = true;
+ } else {
+ // We must not read any samples, but we may still read a format or the end of stream.
+ // However, because the formatRequired argument is false, we should not propagate a read
+ // format unless it is different than the current format.
+ onlyPropagateFormatChanges = true;
+ readFlagFormatRequired = true;
+ }
+ }
+
int result =
metadataQueue.read(
- formatHolder,
+ scratchFormatHolder,
buffer,
- formatRequired,
- allowOnlyClearBuffers,
+ readFlagFormatRequired,
+ readFlagAllowOnlyClearBuffers,
loadingFinished,
downstreamFormat,
extrasHolder);
switch (result) {
case C.RESULT_FORMAT_READ:
- downstreamFormat = formatHolder.format;
+ if (onlyPropagateFormatChanges && downstreamFormat == scratchFormatHolder.format) {
+ return C.RESULT_NOTHING_READ;
+ }
+ onFormat(Assertions.checkNotNull(scratchFormatHolder.format), outputFormatHolder);
return C.RESULT_FORMAT_READ;
case C.RESULT_BUFFER_READ:
if (!buffer.isEndOfStream()) {
@@ -404,6 +468,35 @@ public class SampleQueue implements TrackOutput {
}
}
+ /**
+ * Returns whether there is data available for reading.
+ *
+ * Note: If the stream has ended then a buffer with the end of stream flag can always be read
+ * from {@link #read}. Hence an ended stream is always ready.
+ *
+ * @param loadingFinished Whether no more samples will be written to the sample queue. When true,
+ * this method returns true if the sample queue is empty, because an empty sample queue means
+ * the end of stream has been reached. When false, this method returns false if the sample
+ * queue is empty.
+ */
+ public boolean isReady(boolean loadingFinished) {
+ @SampleQueue.PeekResult int nextInQueue = metadataQueue.peekNext(downstreamFormat);
+ switch (nextInQueue) {
+ case SampleQueue.PEEK_RESULT_NOTHING:
+ return loadingFinished;
+ case SampleQueue.PEEK_RESULT_FORMAT:
+ return true;
+ case SampleQueue.PEEK_RESULT_BUFFER_CLEAR:
+ return currentSession == null || playClearSamplesWithoutKeys;
+ case SampleQueue.PEEK_RESULT_BUFFER_ENCRYPTED:
+ return drmSessionManager == DrmSessionManager.DUMMY
+ || Assertions.checkNotNull(currentSession).getState()
+ == DrmSession.STATE_OPENED_WITH_KEYS;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
/**
* Reads data from the rolling buffer to populate a decoder input buffer.
*
@@ -748,9 +841,55 @@ public class SampleQueue implements TrackOutput {
return format;
}
+ /** Releases any held DRM resources. */
+ private void releaseDrmResources() {
+ if (currentSession != null) {
+ currentSession.releaseReference();
+ currentSession = null;
+ }
+ }
+
/**
- * A node in a linked list of {@link Allocation}s held by the output.
+ * Updates the current format and manages any necessary DRM resources.
+ *
+ * @param format The format read from upstream.
+ * @param outputFormatHolder The output {@link FormatHolder}.
*/
+ private void onFormat(Format format, FormatHolder outputFormatHolder) {
+ outputFormatHolder.format = format;
+ boolean isFirstFormat = downstreamFormat == null;
+ DrmInitData oldDrmInitData = isFirstFormat ? null : downstreamFormat.drmInitData;
+ downstreamFormat = format;
+ if (drmSessionManager == DrmSessionManager.DUMMY) {
+ // Avoid attempting to acquire a session using the dummy DRM session manager. It's likely that
+ // the media source creation has not yet been migrated and the renderer can acquire the
+ // session for the read DRM init data.
+ // TODO: Remove once renderers are migrated [Internal ref: b/122519809].
+ return;
+ }
+ outputFormatHolder.includesDrmSession = true;
+ outputFormatHolder.drmSession = currentSession;
+ if (!isFirstFormat && Util.areEqual(oldDrmInitData, format.drmInitData)) {
+ // Nothing to do.
+ return;
+ }
+ // Ensure we acquire the new session before releasing the previous one in case the same session
+ // can be used for both DrmInitData.
+ DrmSession> previousSession = currentSession;
+ DrmInitData drmInitData = downstreamFormat.drmInitData;
+ Looper playbackLooper = Assertions.checkNotNull(Looper.myLooper());
+ currentSession =
+ drmInitData != null
+ ? drmSessionManager.acquireSession(playbackLooper, drmInitData)
+ : drmSessionManager.acquirePlaceholderSession(playbackLooper);
+ outputFormatHolder.drmSession = currentSession;
+
+ if (previousSession != null) {
+ previousSession.releaseReference();
+ }
+ }
+
+ /** A node in a linked list of {@link Allocation}s held by the output. */
private static final class AllocationNode {
/**
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java
index 61e2868725..efdf927091 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java
@@ -23,7 +23,6 @@ import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionManager;
-import com.google.android.exoplayer2.source.DecryptableSampleQueueReader;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.SampleQueue;
import com.google.android.exoplayer2.source.SampleStream;
@@ -74,7 +73,6 @@ public class ChunkSampleStream implements SampleStream, S
private final ArrayList mediaChunks;
private final List readOnlyMediaChunks;
private final SampleQueue primarySampleQueue;
- private final DecryptableSampleQueueReader primarySampleQueueReader;
private final SampleQueue[] embeddedSampleQueues;
private final BaseMediaChunkOutput mediaChunkOutput;
@@ -132,14 +130,13 @@ public class ChunkSampleStream implements SampleStream, S
int[] trackTypes = new int[1 + embeddedTrackCount];
SampleQueue[] sampleQueues = new SampleQueue[1 + embeddedTrackCount];
- primarySampleQueue = new SampleQueue(allocator);
- primarySampleQueueReader =
- new DecryptableSampleQueueReader(primarySampleQueue, drmSessionManager);
+ primarySampleQueue = new SampleQueue(allocator, drmSessionManager);
trackTypes[0] = primaryTrackType;
sampleQueues[0] = primarySampleQueue;
for (int i = 0; i < embeddedTrackCount; i++) {
- SampleQueue sampleQueue = new SampleQueue(allocator);
+ SampleQueue sampleQueue =
+ new SampleQueue(allocator, DrmSessionManager.getDummyDrmSessionManager());
embeddedSampleQueues[i] = sampleQueue;
sampleQueues[i + 1] = sampleQueue;
trackTypes[i + 1] = embeddedTrackTypes[i];
@@ -337,19 +334,18 @@ public class ChunkSampleStream implements SampleStream, S
public void release(@Nullable ReleaseCallback callback) {
this.releaseCallback = callback;
// Discard as much as we can synchronously.
- primarySampleQueue.discardToEnd();
- primarySampleQueueReader.release();
+ primarySampleQueue.preRelease();
for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) {
- embeddedSampleQueue.discardToEnd();
+ embeddedSampleQueue.preRelease();
}
loader.release(this);
}
@Override
public void onLoaderReleased() {
- primarySampleQueue.reset();
+ primarySampleQueue.release();
for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) {
- embeddedSampleQueue.reset();
+ embeddedSampleQueue.release();
}
if (releaseCallback != null) {
releaseCallback.onSampleStreamReleased(this);
@@ -360,13 +356,13 @@ public class ChunkSampleStream implements SampleStream, S
@Override
public boolean isReady() {
- return !isPendingReset() && primarySampleQueueReader.isReady(loadingFinished);
+ return !isPendingReset() && primarySampleQueue.isReady(loadingFinished);
}
@Override
public void maybeThrowError() throws IOException {
loader.maybeThrowError();
- primarySampleQueueReader.maybeThrowError();
+ primarySampleQueue.maybeThrowError();
if (!loader.isLoading()) {
chunkSource.maybeThrowError();
}
@@ -380,7 +376,7 @@ public class ChunkSampleStream implements SampleStream, S
}
maybeNotifyPrimaryTrackFormatChanged();
- return primarySampleQueueReader.read(
+ return primarySampleQueue.read(
formatHolder, buffer, formatRequired, loadingFinished, decodeOnlyUntilPositionUs);
}
@@ -781,7 +777,6 @@ public class ChunkSampleStream implements SampleStream, S
formatHolder,
buffer,
formatRequired,
- /* allowOnlyClearBuffers= */ false,
loadingFinished,
decodeOnlyUntilPositionUs);
}
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java
index 6812e08ef7..b6b5b69e14 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java
@@ -29,16 +29,24 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import com.google.android.exoplayer2.drm.DrmInitData;
+import com.google.android.exoplayer2.drm.DrmSession;
+import com.google.android.exoplayer2.drm.DrmSessionManager;
+import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.util.ParsableByteArray;
+import java.io.IOException;
import java.util.Arrays;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
/** Test for {@link SampleQueue}. */
@RunWith(AndroidJUnit4.class)
@@ -50,6 +58,13 @@ public final class SampleQueueTest {
private static final Format FORMAT_2 = Format.createSampleFormat("2", "mimeType", 0);
private static final Format FORMAT_1_COPY = Format.createSampleFormat("1", "mimeType", 0);
private static final Format FORMAT_SPLICED = Format.createSampleFormat("spliced", "mimeType", 0);
+ private static final Format FORMAT_ENCRYPTED =
+ Format.createSampleFormat(
+ /* id= */ "encrypted",
+ "mimeType",
+ /* codecs= */ null,
+ /* bitrate= */ Format.NO_VALUE,
+ new DrmInitData());
private static final byte[] DATA = TestUtil.buildTestData(ALLOCATION_SIZE * 10);
/*
@@ -91,18 +106,45 @@ public final class SampleQueueTest {
private static final Format[] SAMPLE_FORMATS =
new Format[] {FORMAT_1, FORMAT_1, FORMAT_1, FORMAT_1, FORMAT_2, FORMAT_2, FORMAT_2, FORMAT_2};
private static final int DATA_SECOND_KEYFRAME_INDEX = 4;
+
+ private static final int[] ENCRYPTED_SAMPLES_FLAGS =
+ new int[] {
+ C.BUFFER_FLAG_KEY_FRAME, C.BUFFER_FLAG_ENCRYPTED, 0, C.BUFFER_FLAG_ENCRYPTED,
+ };
+ private static final long[] ENCRYPTED_SAMPLE_TIMESTAMPS = new long[] {0, 1000, 2000, 3000};
+ private static final Format[] ENCRYPTED_SAMPLES_FORMATS =
+ new Format[] {FORMAT_ENCRYPTED, FORMAT_ENCRYPTED, FORMAT_1, FORMAT_ENCRYPTED};
+ /** Encrypted samples require the encryption preamble. */
+ private static final int[] ENCRYPTED_SAMPLES_SIZES = new int[] {1, 3, 1, 3};
+
+ private static final int[] ENCRYPTED_SAMPLES_OFFSETS = new int[] {7, 4, 3, 0};
+ private static final byte[] ENCRYPTED_SAMPLES_DATA = new byte[8];
+
+ static {
+ Arrays.fill(ENCRYPTED_SAMPLES_DATA, (byte) 1);
+ }
+
private static final TrackOutput.CryptoData DUMMY_CRYPTO_DATA =
new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, new byte[16], 0, 0);
private Allocator allocator;
+ private DrmSessionManager mockDrmSessionManager;
+ private DrmSession mockDrmSession;
private SampleQueue sampleQueue;
private FormatHolder formatHolder;
private DecoderInputBuffer inputBuffer;
@Before
+ @SuppressWarnings("unchecked")
public void setUp() throws Exception {
allocator = new DefaultAllocator(false, ALLOCATION_SIZE);
- sampleQueue = new SampleQueue(allocator);
+ mockDrmSessionManager =
+ (DrmSessionManager) Mockito.mock(DrmSessionManager.class);
+ mockDrmSession = (DrmSession) Mockito.mock(DrmSession.class);
+ Mockito.when(
+ mockDrmSessionManager.acquireSession(ArgumentMatchers.any(), ArgumentMatchers.any()))
+ .thenReturn(mockDrmSession);
+ sampleQueue = new SampleQueue(allocator, mockDrmSessionManager);
formatHolder = new FormatHolder();
inputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
}
@@ -153,9 +195,9 @@ public final class SampleQueueTest {
sampleQueue.sampleMetadata(1000, C.BUFFER_FLAG_KEY_FRAME, ALLOCATION_SIZE, 0, null);
assertReadFormat(false, FORMAT_1);
- assertReadSample(0, true, DATA, 0, ALLOCATION_SIZE);
+ assertReadSample(0, true, /* isEncrypted= */ false, DATA, 0, ALLOCATION_SIZE);
// Assert the second sample is read without a format change.
- assertReadSample(1000, true, DATA, 0, ALLOCATION_SIZE);
+ assertReadSample(1000, true, /* isEncrypted= */ false, DATA, 0, ALLOCATION_SIZE);
// The same applies if the queue is empty when the formats are written.
sampleQueue.format(FORMAT_2);
@@ -164,7 +206,7 @@ public final class SampleQueueTest {
sampleQueue.sampleMetadata(2000, C.BUFFER_FLAG_KEY_FRAME, ALLOCATION_SIZE, 0, null);
// Assert the third sample is read without a format change.
- assertReadSample(2000, true, DATA, 0, ALLOCATION_SIZE);
+ assertReadSample(2000, true, /* isEncrypted= */ false, DATA, 0, ALLOCATION_SIZE);
}
@Test
@@ -187,7 +229,7 @@ public final class SampleQueueTest {
// If formatRequired, should read the format rather than the sample.
assertReadFormat(true, FORMAT_1);
// Otherwise should read the sample.
- assertReadSample(1000, true, DATA, 0, ALLOCATION_SIZE);
+ assertReadSample(1000, true, /* isEncrypted= */ false, DATA, 0, ALLOCATION_SIZE);
// Allocation should still be held.
assertAllocationCount(1);
sampleQueue.discardToRead();
@@ -204,7 +246,7 @@ public final class SampleQueueTest {
// If formatRequired, should read the format rather than the sample.
assertReadFormat(true, FORMAT_1);
// Read the sample.
- assertReadSample(2000, false, DATA, 0, ALLOCATION_SIZE - 1);
+ assertReadSample(2000, false, /* isEncrypted= */ false, DATA, 0, ALLOCATION_SIZE - 1);
// Allocation should still be held.
assertAllocationCount(1);
sampleQueue.discardToRead();
@@ -218,7 +260,7 @@ public final class SampleQueueTest {
// If formatRequired, should read the format rather than the sample.
assertReadFormat(true, FORMAT_1);
// Read the sample.
- assertReadSample(3000, false, DATA, ALLOCATION_SIZE - 1, 1);
+ assertReadSample(3000, false, /* isEncrypted= */ false, DATA, ALLOCATION_SIZE - 1, 1);
// Allocation should still be held.
assertAllocationCount(1);
sampleQueue.discardToRead();
@@ -265,6 +307,137 @@ public final class SampleQueueTest {
assertReadTestData();
}
+ @Test
+ public void testReadEncryptedSectionsWaitsForKeys() {
+ Mockito.when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
+ writeEncryptedTestData();
+
+ assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
+ assertReadNothing(/* formatRequired= */ false);
+ Mockito.when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
+ assertReadEncryptedSample(/* sampleIndex= */ 0);
+ }
+
+ @Test
+ public void testReadEncryptedSectionsPopulatesDrmSession() {
+ Mockito.when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
+ writeEncryptedTestData();
+
+ int result =
+ sampleQueue.read(
+ formatHolder,
+ inputBuffer,
+ /* formatRequired= */ false,
+ /* loadingFinished= */ false,
+ /* decodeOnlyUntilUs= */ 0);
+ assertThat(result).isEqualTo(RESULT_FORMAT_READ);
+ assertThat(formatHolder.drmSession).isSameInstanceAs(mockDrmSession);
+ assertReadEncryptedSample(/* sampleIndex= */ 0);
+ assertReadEncryptedSample(/* sampleIndex= */ 1);
+ formatHolder.clear();
+ assertThat(formatHolder.drmSession).isNull();
+ result =
+ sampleQueue.read(
+ formatHolder,
+ inputBuffer,
+ /* formatRequired= */ false,
+ /* loadingFinished= */ false,
+ /* decodeOnlyUntilUs= */ 0);
+ assertThat(result).isEqualTo(RESULT_FORMAT_READ);
+ assertThat(formatHolder.drmSession).isNull();
+ assertReadEncryptedSample(/* sampleIndex= */ 2);
+ result =
+ sampleQueue.read(
+ formatHolder,
+ inputBuffer,
+ /* formatRequired= */ false,
+ /* loadingFinished= */ false,
+ /* decodeOnlyUntilUs= */ 0);
+ assertThat(result).isEqualTo(RESULT_FORMAT_READ);
+ assertThat(formatHolder.drmSession).isSameInstanceAs(mockDrmSession);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testAllowPlaceholderSessionPopulatesDrmSession() {
+ Mockito.when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
+ DrmSession mockPlaceholderDrmSession =
+ (DrmSession) Mockito.mock(DrmSession.class);
+ Mockito.when(mockPlaceholderDrmSession.getState())
+ .thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
+ Mockito.when(mockDrmSessionManager.acquirePlaceholderSession(ArgumentMatchers.any()))
+ .thenReturn(mockPlaceholderDrmSession);
+ writeEncryptedTestData();
+
+ int result =
+ sampleQueue.read(
+ formatHolder,
+ inputBuffer,
+ /* formatRequired= */ false,
+ /* loadingFinished= */ false,
+ /* decodeOnlyUntilUs= */ 0);
+ assertThat(result).isEqualTo(RESULT_FORMAT_READ);
+ assertThat(formatHolder.drmSession).isSameInstanceAs(mockDrmSession);
+ assertReadEncryptedSample(/* sampleIndex= */ 0);
+ assertReadEncryptedSample(/* sampleIndex= */ 1);
+ formatHolder.clear();
+ assertThat(formatHolder.drmSession).isNull();
+ result =
+ sampleQueue.read(
+ formatHolder,
+ inputBuffer,
+ /* formatRequired= */ false,
+ /* loadingFinished= */ false,
+ /* decodeOnlyUntilUs= */ 0);
+ assertThat(result).isEqualTo(RESULT_FORMAT_READ);
+ assertThat(formatHolder.drmSession).isSameInstanceAs(mockPlaceholderDrmSession);
+ assertReadEncryptedSample(/* sampleIndex= */ 2);
+ result =
+ sampleQueue.read(
+ formatHolder,
+ inputBuffer,
+ /* formatRequired= */ false,
+ /* loadingFinished= */ false,
+ /* decodeOnlyUntilUs= */ 0);
+ assertThat(result).isEqualTo(RESULT_FORMAT_READ);
+ assertThat(formatHolder.drmSession).isSameInstanceAs(mockDrmSession);
+ }
+
+ @Test
+ public void testReadWithErrorSessionReadsNothingAndThrows() throws IOException {
+ Mockito.when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
+ writeEncryptedTestData();
+
+ assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
+ assertReadNothing(/* formatRequired= */ false);
+ sampleQueue.maybeThrowError();
+ Mockito.when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_ERROR);
+ Mockito.when(mockDrmSession.getError())
+ .thenReturn(new DrmSession.DrmSessionException(new Exception()));
+ assertReadNothing(/* formatRequired= */ false);
+ try {
+ sampleQueue.maybeThrowError();
+ Assert.fail();
+ } catch (IOException e) {
+ // Expected.
+ }
+ Mockito.when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
+ assertReadEncryptedSample(/* sampleIndex= */ 0);
+ }
+
+ @Test
+ public void testAllowPlayClearSamplesWithoutKeysReadsClearSamples() {
+ Mockito.when(mockDrmSessionManager.getFlags())
+ .thenReturn(DrmSessionManager.FLAG_PLAY_CLEAR_SAMPLES_WITHOUT_KEYS);
+ // We recreate the queue to ensure the mock DRM session manager flags are taken into account.
+ sampleQueue = new SampleQueue(allocator, mockDrmSessionManager);
+ Mockito.when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
+ writeEncryptedTestData();
+
+ assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
+ assertReadEncryptedSample(/* sampleIndex= */ 0);
+ }
+
@Test
public void testRewindAfterDiscard() {
writeTestData();
@@ -313,7 +486,7 @@ public final class SampleQueueTest {
sampleQueue.sampleMetadata(0, C.BUFFER_FLAG_KEY_FRAME, ALLOCATION_SIZE, 0, null);
// Once the metadata has been written, check the sample can be read as expected.
- assertReadSample(0, true, DATA, 0, ALLOCATION_SIZE);
+ assertReadSample(0, true, /* isEncrypted= */ false, DATA, 0, ALLOCATION_SIZE);
assertNoSamplesToRead(FORMAT_1);
assertAllocationCount(1);
sampleQueue.discardToRead();
@@ -541,50 +714,7 @@ public final class SampleQueueTest {
// Discarding everything from upstream without reading should unset the largest timestamp.
assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(MIN_VALUE);
}
-
- @Test
- public void testAllowOnlyClearBuffers() {
- int[] flags =
- new int[] {
- C.BUFFER_FLAG_KEY_FRAME,
- C.BUFFER_FLAG_ENCRYPTED,
- 0,
- 0,
- 0,
- C.BUFFER_FLAG_KEY_FRAME | C.BUFFER_FLAG_ENCRYPTED,
- 0,
- 0
- };
- int[] sampleSizes = new int[flags.length];
- Arrays.fill(sampleSizes, /* val= */ 1);
-
- // Two encryption preamble bytes per encrypted sample in the sample queue.
- byte[] sampleData = new byte[flags.length + 2 + 2];
- Arrays.fill(sampleData, /* val= */ (byte) 1);
-
- writeTestData(
- sampleData, sampleSizes, new int[flags.length], SAMPLE_TIMESTAMPS, SAMPLE_FORMATS, flags);
- assertReadFormat(/* formatRequired= */ false, FORMAT_1);
- assertResult(RESULT_BUFFER_READ, /* allowOnlyClearBuffers= */ true);
- assertResult(RESULT_NOTHING_READ, /* allowOnlyClearBuffers= */ true);
-
- assertResult(RESULT_BUFFER_READ, /* allowOnlyClearBuffers= */ false);
-
- assertResult(RESULT_BUFFER_READ, /* allowOnlyClearBuffers= */ true);
- assertResult(RESULT_BUFFER_READ, /* allowOnlyClearBuffers= */ true);
- assertResult(RESULT_FORMAT_READ, /* allowOnlyClearBuffers= */ true);
- assertResult(RESULT_BUFFER_READ, /* allowOnlyClearBuffers= */ true);
- assertResult(RESULT_NOTHING_READ, /* allowOnlyClearBuffers= */ true);
-
- assertResult(RESULT_BUFFER_READ, /* allowOnlyClearBuffers= */ false);
-
- assertResult(RESULT_BUFFER_READ, /* allowOnlyClearBuffers= */ true);
- assertResult(RESULT_BUFFER_READ, /* allowOnlyClearBuffers= */ true);
- assertResult(RESULT_NOTHING_READ, /* allowOnlyClearBuffers= */ true);
-
- assertResult(RESULT_NOTHING_READ, /* allowOnlyClearBuffers= */ false);
- }
-
+
@Test
public void testLargestQueuedTimestampWithRead() {
writeTestData();
@@ -612,7 +742,7 @@ public final class SampleQueueTest {
writeSample(DATA, spliceSampleTimeUs, FORMAT_SPLICED, C.BUFFER_FLAG_KEY_FRAME);
assertReadTestData(null, 0, 4);
assertReadFormat(false, FORMAT_SPLICED);
- assertReadSample(spliceSampleTimeUs, true, DATA, 0, DATA.length);
+ assertReadSample(spliceSampleTimeUs, true, /* isEncrypted= */ false, DATA, 0, DATA.length);
assertReadEndOfStream(false);
}
@@ -634,7 +764,7 @@ public final class SampleQueueTest {
spliceSampleTimeUs = SAMPLE_TIMESTAMPS[3] + 1;
writeSample(DATA, spliceSampleTimeUs, FORMAT_SPLICED, C.BUFFER_FLAG_KEY_FRAME);
assertReadFormat(false, FORMAT_SPLICED);
- assertReadSample(spliceSampleTimeUs, true, DATA, 0, DATA.length);
+ assertReadSample(spliceSampleTimeUs, true, /* isEncrypted= */ false, DATA, 0, DATA.length);
assertReadEndOfStream(false);
}
@@ -649,7 +779,8 @@ public final class SampleQueueTest {
writeSample(DATA, spliceSampleTimeUs, FORMAT_SPLICED, C.BUFFER_FLAG_KEY_FRAME);
assertReadTestData(null, 0, 4, sampleOffsetUs);
assertReadFormat(false, FORMAT_SPLICED.copyWithSubsampleOffsetUs(sampleOffsetUs));
- assertReadSample(spliceSampleTimeUs + sampleOffsetUs, true, DATA, 0, DATA.length);
+ assertReadSample(
+ spliceSampleTimeUs + sampleOffsetUs, true, /* isEncrypted= */ false, DATA, 0, DATA.length);
assertReadEndOfStream(false);
}
@@ -663,6 +794,16 @@ public final class SampleQueueTest {
DATA, SAMPLE_SIZES, SAMPLE_OFFSETS, SAMPLE_TIMESTAMPS, SAMPLE_FORMATS, SAMPLE_FLAGS);
}
+ private void writeEncryptedTestData() {
+ writeTestData(
+ ENCRYPTED_SAMPLES_DATA,
+ ENCRYPTED_SAMPLES_SIZES,
+ ENCRYPTED_SAMPLES_OFFSETS,
+ ENCRYPTED_SAMPLE_TIMESTAMPS,
+ ENCRYPTED_SAMPLES_FORMATS,
+ ENCRYPTED_SAMPLES_FLAGS);
+ }
+
/**
* Writes the specified test data to {@code sampleQueue}.
*/
@@ -755,6 +896,7 @@ public final class SampleQueueTest {
assertReadSample(
SAMPLE_TIMESTAMPS[i] + sampleOffsetUs,
(SAMPLE_FLAGS[i] & C.BUFFER_FLAG_KEY_FRAME) != 0,
+ /* isEncrypted= */ false,
DATA,
DATA.length - SAMPLE_OFFSETS[i] - SAMPLE_SIZES[i],
SAMPLE_SIZES[i]);
@@ -801,7 +943,6 @@ public final class SampleQueueTest {
formatHolder,
inputBuffer,
formatRequired,
- /* allowOnlyClearBuffers= */ false,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_NOTHING_READ);
@@ -825,7 +966,6 @@ public final class SampleQueueTest {
formatHolder,
inputBuffer,
formatRequired,
- /* allowOnlyClearBuffers= */ false,
/* loadingFinished= */ true,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_BUFFER_READ);
@@ -852,7 +992,6 @@ public final class SampleQueueTest {
formatHolder,
inputBuffer,
formatRequired,
- /* allowOnlyClearBuffers= */ false,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_FORMAT_READ);
@@ -863,25 +1002,44 @@ public final class SampleQueueTest {
assertInputBufferHasNoDefaultFlagsSet();
}
+ private void assertReadEncryptedSample(int sampleIndex) {
+ byte[] sampleData = new byte[ENCRYPTED_SAMPLES_SIZES[sampleIndex]];
+ Arrays.fill(sampleData, (byte) 1);
+ boolean isKeyFrame = (ENCRYPTED_SAMPLES_FLAGS[sampleIndex] & C.BUFFER_FLAG_KEY_FRAME) != 0;
+ boolean isEncrypted = (ENCRYPTED_SAMPLES_FLAGS[sampleIndex] & C.BUFFER_FLAG_ENCRYPTED) != 0;
+ assertReadSample(
+ ENCRYPTED_SAMPLE_TIMESTAMPS[sampleIndex],
+ isKeyFrame,
+ isEncrypted,
+ sampleData,
+ /* offset= */ 0,
+ ENCRYPTED_SAMPLES_SIZES[sampleIndex] - (isEncrypted ? 2 : 0));
+ }
+
/**
* Asserts {@link SampleQueue#read} returns {@link C#RESULT_BUFFER_READ} and that the buffer is
* filled with the specified sample data.
*
* @param timeUs The expected buffer timestamp.
* @param isKeyframe The expected keyframe flag.
+ * @param isEncrypted The expected encrypted flag.
* @param sampleData An array containing the expected sample data.
* @param offset The offset in {@code sampleData} of the expected sample data.
* @param length The length of the expected sample data.
*/
private void assertReadSample(
- long timeUs, boolean isKeyframe, byte[] sampleData, int offset, int length) {
+ long timeUs,
+ boolean isKeyframe,
+ boolean isEncrypted,
+ byte[] sampleData,
+ int offset,
+ int length) {
clearFormatHolderAndInputBuffer();
int result =
sampleQueue.read(
formatHolder,
inputBuffer,
/* formatRequired= */ false,
- /* allowOnlyClearBuffers= */ false,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_BUFFER_READ);
@@ -891,7 +1049,7 @@ public final class SampleQueueTest {
assertThat(inputBuffer.timeUs).isEqualTo(timeUs);
assertThat(inputBuffer.isKeyFrame()).isEqualTo(isKeyframe);
assertThat(inputBuffer.isDecodeOnly()).isFalse();
- assertThat(inputBuffer.isEncrypted()).isFalse();
+ assertThat(inputBuffer.isEncrypted()).isEqualTo(isEncrypted);
inputBuffer.flip();
assertThat(inputBuffer.data.limit()).isEqualTo(length);
byte[] readData = new byte[length];
@@ -905,7 +1063,6 @@ public final class SampleQueueTest {
sampleQueue.read(
formatHolder,
inputBuffer,
- /* formatRequired= */ false,
allowOnlyClearBuffers,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java
index af4bf3ad70..f7879a18cc 100644
--- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java
+++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java
@@ -24,6 +24,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.ParserException;
+import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.metadata.Metadata;
@@ -195,7 +196,8 @@ public final class PlayerEmsgHandler implements Handler.Callback {
/** Returns a {@link TrackOutput} that emsg messages could be written to. */
public PlayerTrackEmsgHandler newPlayerTrackEmsgHandler() {
- return new PlayerTrackEmsgHandler(new SampleQueue(allocator));
+ return new PlayerTrackEmsgHandler(
+ new SampleQueue(allocator, DrmSessionManager.getDummyDrmSessionManager()));
}
/** Release this emsg handler. It should not be reused after this call. */
@@ -376,7 +378,6 @@ public final class PlayerEmsgHandler implements Handler.Callback {
formatHolder,
buffer,
/* formatRequired= */ false,
- /* allowOnlyClearBuffers= */ false,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
if (result == C.RESULT_BUFFER_READ) {
diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java
index 41646096b1..e0d7437a21 100644
--- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java
+++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java
@@ -36,7 +36,6 @@ import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.emsg.EventMessage;
import com.google.android.exoplayer2.metadata.emsg.EventMessageDecoder;
import com.google.android.exoplayer2.metadata.id3.PrivFrame;
-import com.google.android.exoplayer2.source.DecryptableSampleQueueReader;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.SampleQueue;
import com.google.android.exoplayer2.source.SampleQueue.UpstreamFormatChangedListener;
@@ -129,7 +128,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private final Map overridingDrmInitData;
private SampleQueue[] sampleQueues;
- private DecryptableSampleQueueReader[] sampleQueueReaders;
private int[] sampleQueueTrackIds;
private Set sampleQueueMappingDoneByType;
private SparseIntArray sampleQueueIndicesByType;
@@ -209,7 +207,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
sampleQueueMappingDoneByType = new HashSet<>(MAPPABLE_TYPES.size());
sampleQueueIndicesByType = new SparseIntArray(MAPPABLE_TYPES.size());
sampleQueues = new SampleQueue[0];
- sampleQueueReaders = new DecryptableSampleQueueReader[0];
sampleQueueIsAudioVideoFlags = new boolean[0];
sampleQueuesEnabledStates = new boolean[0];
mediaChunks = new ArrayList<>();
@@ -490,10 +487,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
// Discard as much as we can synchronously. We only do this if we're prepared, since otherwise
// sampleQueues may still be being modified by the loading thread.
for (SampleQueue sampleQueue : sampleQueues) {
- sampleQueue.discardToEnd();
- }
- for (DecryptableSampleQueueReader reader : sampleQueueReaders) {
- reader.release();
+ sampleQueue.preRelease();
}
}
loader.release(this);
@@ -504,9 +498,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Override
public void onLoaderReleased() {
- resetSampleQueues();
- for (DecryptableSampleQueueReader reader : sampleQueueReaders) {
- reader.release();
+ for (SampleQueue sampleQueue : sampleQueues) {
+ sampleQueue.release();
}
}
@@ -521,12 +514,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
// SampleStream implementation.
public boolean isReady(int sampleQueueIndex) {
- return !isPendingReset() && sampleQueueReaders[sampleQueueIndex].isReady(loadingFinished);
+ return !isPendingReset() && sampleQueues[sampleQueueIndex].isReady(loadingFinished);
}
public void maybeThrowError(int sampleQueueIndex) throws IOException {
maybeThrowError();
- sampleQueueReaders[sampleQueueIndex].maybeThrowError();
+ sampleQueues[sampleQueueIndex].maybeThrowError();
}
public void maybeThrowError() throws IOException {
@@ -559,7 +552,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
}
int result =
- sampleQueueReaders[sampleQueueIndex].read(
+ sampleQueues[sampleQueueIndex].read(
formatHolder, buffer, requireFormat, loadingFinished, lastSeekPositionUs);
if (result == C.RESULT_FORMAT_READ) {
Format format = Assertions.checkNotNull(formatHolder.format);
@@ -917,17 +910,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private SampleQueue createSampleQueue(int id, int type) {
int trackCount = sampleQueues.length;
- SampleQueue trackOutput = new FormatAdjustingSampleQueue(allocator, overridingDrmInitData);
+ SampleQueue trackOutput =
+ new FormatAdjustingSampleQueue(allocator, drmSessionManager, overridingDrmInitData);
trackOutput.setSampleOffsetUs(sampleOffsetUs);
trackOutput.sourceId(chunkUid);
trackOutput.setUpstreamFormatChangeListener(this);
sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1);
sampleQueueTrackIds[trackCount] = id;
sampleQueues = Util.nullSafeArrayAppend(sampleQueues, trackOutput);
- sampleQueueReaders =
- Util.nullSafeArrayAppend(
- sampleQueueReaders,
- new DecryptableSampleQueueReader(sampleQueues[trackCount], drmSessionManager));
sampleQueueIsAudioVideoFlags = Arrays.copyOf(sampleQueueIsAudioVideoFlags, trackCount + 1);
sampleQueueIsAudioVideoFlags[trackCount] =
type == C.TRACK_TYPE_AUDIO || type == C.TRACK_TYPE_VIDEO;
@@ -1295,8 +1285,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private final Map overridingDrmInitData;
public FormatAdjustingSampleQueue(
- Allocator allocator, Map overridingDrmInitData) {
- super(allocator);
+ Allocator allocator,
+ DrmSessionManager> drmSessionManager,
+ Map overridingDrmInitData) {
+ super(allocator, drmSessionManager);
this.overridingDrmInitData = overridingDrmInitData;
}