Commit b68a6988 by aquilescanta Committed by Oliver Woodman

Make SampleQueue populate FormatHolder.drmSession

Also add unit tests for SampleQueue read for samples with
DRM requirements.

PiperOrigin-RevId: 277037826
parent e0253714
/*
* 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.
*
* <p>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();
}
}
}
...@@ -116,7 +116,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -116,7 +116,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Nullable private SeekMap seekMap; @Nullable private SeekMap seekMap;
@Nullable private IcyHeaders icyHeaders; @Nullable private IcyHeaders icyHeaders;
private SampleQueue[] sampleQueues; private SampleQueue[] sampleQueues;
private DecryptableSampleQueueReader[] sampleQueueReaders;
private TrackId[] sampleQueueTrackIds; private TrackId[] sampleQueueTrackIds;
private boolean sampleQueuesBuilt; private boolean sampleQueuesBuilt;
private boolean prepared; private boolean prepared;
...@@ -193,7 +192,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -193,7 +192,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
handler = new Handler(); handler = new Handler();
sampleQueueTrackIds = new TrackId[0]; sampleQueueTrackIds = new TrackId[0];
sampleQueues = new SampleQueue[0]; sampleQueues = new SampleQueue[0];
sampleQueueReaders = new DecryptableSampleQueueReader[0];
pendingResetPositionUs = C.TIME_UNSET; pendingResetPositionUs = C.TIME_UNSET;
length = C.LENGTH_UNSET; length = C.LENGTH_UNSET;
durationUs = C.TIME_UNSET; durationUs = C.TIME_UNSET;
...@@ -206,10 +204,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -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 // 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. // sampleQueues may still be being modified by the loading thread.
for (SampleQueue sampleQueue : sampleQueues) { for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.discardToEnd(); sampleQueue.preRelease();
}
for (DecryptableSampleQueueReader reader : sampleQueueReaders) {
reader.release();
} }
} }
loader.release(/* callback= */ this); loader.release(/* callback= */ this);
...@@ -222,10 +217,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -222,10 +217,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Override @Override
public void onLoaderReleased() { public void onLoaderReleased() {
for (SampleQueue sampleQueue : sampleQueues) { for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.reset(); sampleQueue.release();
}
for (DecryptableSampleQueueReader reader : sampleQueueReaders) {
reader.release();
} }
extractorHolder.release(); extractorHolder.release();
} }
...@@ -461,11 +453,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -461,11 +453,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
// SampleStream methods. // SampleStream methods.
/* package */ boolean isReady(int track) { /* package */ boolean isReady(int track) {
return !suppressRead() && sampleQueueReaders[track].isReady(loadingFinished); return !suppressRead() && sampleQueues[track].isReady(loadingFinished);
} }
/* package */ void maybeThrowError(int sampleQueueIndex) throws IOException { /* package */ void maybeThrowError(int sampleQueueIndex) throws IOException {
sampleQueueReaders[sampleQueueIndex].maybeThrowError(); sampleQueues[sampleQueueIndex].maybeThrowError();
maybeThrowError(); maybeThrowError();
} }
...@@ -483,7 +475,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -483,7 +475,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} }
maybeNotifyDownstreamFormat(sampleQueueIndex); maybeNotifyDownstreamFormat(sampleQueueIndex);
int result = int result =
sampleQueueReaders[sampleQueueIndex].read( sampleQueues[sampleQueueIndex].read(
formatHolder, buffer, formatRequired, loadingFinished, lastSeekPositionUs); formatHolder, buffer, formatRequired, loadingFinished, lastSeekPositionUs);
if (result == C.RESULT_NOTHING_READ) { if (result == C.RESULT_NOTHING_READ) {
maybeStartDeferredRetry(sampleQueueIndex); maybeStartDeferredRetry(sampleQueueIndex);
...@@ -690,7 +682,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -690,7 +682,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
return sampleQueues[i]; return sampleQueues[i];
} }
} }
SampleQueue trackOutput = new SampleQueue(allocator); SampleQueue trackOutput = new SampleQueue(allocator, drmSessionManager);
trackOutput.setUpstreamFormatChangeListener(this); trackOutput.setUpstreamFormatChangeListener(this);
@NullableType @NullableType
TrackId[] sampleQueueTrackIds = Arrays.copyOf(this.sampleQueueTrackIds, trackCount + 1); TrackId[] sampleQueueTrackIds = Arrays.copyOf(this.sampleQueueTrackIds, trackCount + 1);
...@@ -699,12 +691,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -699,12 +691,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@NullableType SampleQueue[] sampleQueues = Arrays.copyOf(this.sampleQueues, trackCount + 1); @NullableType SampleQueue[] sampleQueues = Arrays.copyOf(this.sampleQueues, trackCount + 1);
sampleQueues[trackCount] = trackOutput; sampleQueues[trackCount] = trackOutput;
this.sampleQueues = Util.castNonNullTypeArray(sampleQueues); 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; return trackOutput;
} }
......
...@@ -15,18 +15,24 @@ ...@@ -15,18 +15,24 @@
*/ */
package com.google.android.exoplayer2.source; package com.google.android.exoplayer2.source;
import android.os.Looper;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; 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.ExtractorInput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.source.SampleMetadataQueue.SampleExtrasHolder; import com.google.android.exoplayer2.source.SampleMetadataQueue.SampleExtrasHolder;
import com.google.android.exoplayer2.upstream.Allocation; import com.google.android.exoplayer2.upstream.Allocation;
import com.google.android.exoplayer2.upstream.Allocator; 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.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
...@@ -61,26 +67,29 @@ public class SampleQueue implements TrackOutput { ...@@ -61,26 +67,29 @@ public class SampleQueue implements TrackOutput {
PEEK_RESULT_BUFFER_CLEAR, PEEK_RESULT_BUFFER_CLEAR,
PEEK_RESULT_BUFFER_ENCRYPTED PEEK_RESULT_BUFFER_ENCRYPTED
}) })
@interface PeekResult {} /* package */ @interface PeekResult {}
/** Nothing is available for reading. */ /** 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 */ /** 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. */ /** 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. */ /** 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; public static final int ADVANCE_FAILED = -1;
private static final int INITIAL_SCRATCH_SIZE = 32; private static final int INITIAL_SCRATCH_SIZE = 32;
private final Allocator allocator; private final Allocator allocator;
private final DrmSessionManager<?> drmSessionManager;
private final boolean playClearSamplesWithoutKeys;
private final int allocationLength; private final int allocationLength;
private final SampleMetadataQueue metadataQueue; private final SampleMetadataQueue metadataQueue;
private final SampleExtrasHolder extrasHolder; private final SampleExtrasHolder extrasHolder;
private final ParsableByteArray scratch; private final ParsableByteArray scratch;
private final FormatHolder scratchFormatHolder;
// References into the linked list of allocations. // References into the linked list of allocations.
private AllocationNode firstAllocationNode; private AllocationNode firstAllocationNode;
...@@ -89,6 +98,7 @@ public class SampleQueue implements TrackOutput { ...@@ -89,6 +98,7 @@ public class SampleQueue implements TrackOutput {
// Accessed only by the consuming thread. // Accessed only by the consuming thread.
private Format downstreamFormat; private Format downstreamFormat;
@Nullable private DrmSession<?> currentSession;
// Accessed only by the loading thread (or the consuming thread when there is no loading thread). // Accessed only by the loading thread (or the consuming thread when there is no loading thread).
private boolean pendingFormatAdjustment; private boolean pendingFormatAdjustment;
...@@ -99,14 +109,23 @@ public class SampleQueue implements TrackOutput { ...@@ -99,14 +109,23 @@ public class SampleQueue implements TrackOutput {
private UpstreamFormatChangedListener upstreamFormatChangeListener; private UpstreamFormatChangedListener upstreamFormatChangeListener;
/** /**
* Creates a sample queue.
*
* @param allocator An {@link Allocator} from which allocations for sample data can be obtained. * @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.allocator = allocator;
this.drmSessionManager = drmSessionManager;
playClearSamplesWithoutKeys =
(drmSessionManager.getFlags() & DrmSessionManager.FLAG_PLAY_CLEAR_SAMPLES_WITHOUT_KEYS)
!= 0;
allocationLength = allocator.getIndividualAllocationLength(); allocationLength = allocator.getIndividualAllocationLength();
metadataQueue = new SampleMetadataQueue(); metadataQueue = new SampleMetadataQueue();
extrasHolder = new SampleExtrasHolder(); extrasHolder = new SampleExtrasHolder();
scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE); scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE);
scratchFormatHolder = new FormatHolder();
firstAllocationNode = new AllocationNode(0, allocationLength); firstAllocationNode = new AllocationNode(0, allocationLength);
readAllocationNode = firstAllocationNode; readAllocationNode = firstAllocationNode;
writeAllocationNode = firstAllocationNode; writeAllocationNode = firstAllocationNode;
...@@ -122,7 +141,7 @@ public class SampleQueue implements TrackOutput { ...@@ -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, * @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)}) * samples queued after the reset (and before a subsequent call to {@link #format(Format)})
...@@ -198,8 +217,18 @@ public class SampleQueue implements TrackOutput { ...@@ -198,8 +217,18 @@ public class SampleQueue implements TrackOutput {
// Called by the consuming thread. // 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() { public boolean hasNextSample() {
return metadataQueue.hasNextSample(); return metadataQueue.hasNextSample();
} }
...@@ -292,6 +321,18 @@ public class SampleQueue implements TrackOutput { ...@@ -292,6 +321,18 @@ public class SampleQueue implements TrackOutput {
discardDownstreamTo(metadataQueue.discardToRead()); 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. * Discards to the end of the queue. The read position is also advanced.
*/ */
...@@ -338,19 +379,18 @@ public class SampleQueue implements TrackOutput { ...@@ -338,19 +379,18 @@ public class SampleQueue implements TrackOutput {
} }
/** /**
* 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. * Attempts to read from the queue.
* *
* @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. * <p>{@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:
*
* <ul>
* <li>The sample has a {@link Format} with a non-null {@link Format#drmInitData}.
* <li>The {@link DrmSessionManager} is configured to use secure decoders for clear samples. See
* {@link DrmSessionManager#FLAG_PLAY_CLEAR_SAMPLES_WITHOUT_KEYS}.
* </ul>
*
* @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 * @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 * 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 * 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 { ...@@ -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 * @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 * 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. * 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 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 * @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. * 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 * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or
* {@link C#RESULT_BUFFER_READ}. * {@link C#RESULT_BUFFER_READ}.
*/ */
@SuppressWarnings("ReferenceEquality")
public int read( public int read(
FormatHolder formatHolder, FormatHolder outputFormatHolder,
DecoderInputBuffer buffer, DecoderInputBuffer buffer,
boolean formatRequired, boolean formatRequired,
boolean allowOnlyClearBuffers,
boolean loadingFinished, boolean loadingFinished,
long decodeOnlyUntilUs) { 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 = int result =
metadataQueue.read( metadataQueue.read(
formatHolder, scratchFormatHolder,
buffer, buffer,
formatRequired, readFlagFormatRequired,
allowOnlyClearBuffers, readFlagAllowOnlyClearBuffers,
loadingFinished, loadingFinished,
downstreamFormat, downstreamFormat,
extrasHolder); extrasHolder);
switch (result) { switch (result) {
case C.RESULT_FORMAT_READ: 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; return C.RESULT_FORMAT_READ;
case C.RESULT_BUFFER_READ: case C.RESULT_BUFFER_READ:
if (!buffer.isEndOfStream()) { if (!buffer.isEndOfStream()) {
...@@ -405,6 +469,35 @@ public class SampleQueue implements TrackOutput { ...@@ -405,6 +469,35 @@ public class SampleQueue implements TrackOutput {
} }
/** /**
* Returns whether there is data available for reading.
*
* <p>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. * Reads data from the rolling buffer to populate a decoder input buffer.
* *
* @param buffer The buffer to populate. * @param buffer The buffer to populate.
...@@ -748,9 +841,55 @@ public class SampleQueue implements TrackOutput { ...@@ -748,9 +841,55 @@ public class SampleQueue implements TrackOutput {
return format; 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 { private static final class AllocationNode {
/** /**
......
...@@ -23,7 +23,6 @@ import com.google.android.exoplayer2.SeekParameters; ...@@ -23,7 +23,6 @@ import com.google.android.exoplayer2.SeekParameters;
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.DrmSessionManager; 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.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.SampleQueue; import com.google.android.exoplayer2.source.SampleQueue;
import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.SampleStream;
...@@ -74,7 +73,6 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -74,7 +73,6 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
private final ArrayList<BaseMediaChunk> mediaChunks; private final ArrayList<BaseMediaChunk> mediaChunks;
private final List<BaseMediaChunk> readOnlyMediaChunks; private final List<BaseMediaChunk> readOnlyMediaChunks;
private final SampleQueue primarySampleQueue; private final SampleQueue primarySampleQueue;
private final DecryptableSampleQueueReader primarySampleQueueReader;
private final SampleQueue[] embeddedSampleQueues; private final SampleQueue[] embeddedSampleQueues;
private final BaseMediaChunkOutput mediaChunkOutput; private final BaseMediaChunkOutput mediaChunkOutput;
...@@ -132,14 +130,13 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -132,14 +130,13 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
int[] trackTypes = new int[1 + embeddedTrackCount]; int[] trackTypes = new int[1 + embeddedTrackCount];
SampleQueue[] sampleQueues = new SampleQueue[1 + embeddedTrackCount]; SampleQueue[] sampleQueues = new SampleQueue[1 + embeddedTrackCount];
primarySampleQueue = new SampleQueue(allocator); primarySampleQueue = new SampleQueue(allocator, drmSessionManager);
primarySampleQueueReader =
new DecryptableSampleQueueReader(primarySampleQueue, drmSessionManager);
trackTypes[0] = primaryTrackType; trackTypes[0] = primaryTrackType;
sampleQueues[0] = primarySampleQueue; sampleQueues[0] = primarySampleQueue;
for (int i = 0; i < embeddedTrackCount; i++) { for (int i = 0; i < embeddedTrackCount; i++) {
SampleQueue sampleQueue = new SampleQueue(allocator); SampleQueue sampleQueue =
new SampleQueue(allocator, DrmSessionManager.getDummyDrmSessionManager());
embeddedSampleQueues[i] = sampleQueue; embeddedSampleQueues[i] = sampleQueue;
sampleQueues[i + 1] = sampleQueue; sampleQueues[i + 1] = sampleQueue;
trackTypes[i + 1] = embeddedTrackTypes[i]; trackTypes[i + 1] = embeddedTrackTypes[i];
...@@ -337,19 +334,18 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -337,19 +334,18 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
public void release(@Nullable ReleaseCallback<T> callback) { public void release(@Nullable ReleaseCallback<T> callback) {
this.releaseCallback = callback; this.releaseCallback = callback;
// Discard as much as we can synchronously. // Discard as much as we can synchronously.
primarySampleQueue.discardToEnd(); primarySampleQueue.preRelease();
primarySampleQueueReader.release();
for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) { for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) {
embeddedSampleQueue.discardToEnd(); embeddedSampleQueue.preRelease();
} }
loader.release(this); loader.release(this);
} }
@Override @Override
public void onLoaderReleased() { public void onLoaderReleased() {
primarySampleQueue.reset(); primarySampleQueue.release();
for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) { for (SampleQueue embeddedSampleQueue : embeddedSampleQueues) {
embeddedSampleQueue.reset(); embeddedSampleQueue.release();
} }
if (releaseCallback != null) { if (releaseCallback != null) {
releaseCallback.onSampleStreamReleased(this); releaseCallback.onSampleStreamReleased(this);
...@@ -360,13 +356,13 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -360,13 +356,13 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
@Override @Override
public boolean isReady() { public boolean isReady() {
return !isPendingReset() && primarySampleQueueReader.isReady(loadingFinished); return !isPendingReset() && primarySampleQueue.isReady(loadingFinished);
} }
@Override @Override
public void maybeThrowError() throws IOException { public void maybeThrowError() throws IOException {
loader.maybeThrowError(); loader.maybeThrowError();
primarySampleQueueReader.maybeThrowError(); primarySampleQueue.maybeThrowError();
if (!loader.isLoading()) { if (!loader.isLoading()) {
chunkSource.maybeThrowError(); chunkSource.maybeThrowError();
} }
...@@ -380,7 +376,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -380,7 +376,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
} }
maybeNotifyPrimaryTrackFormatChanged(); maybeNotifyPrimaryTrackFormatChanged();
return primarySampleQueueReader.read( return primarySampleQueue.read(
formatHolder, buffer, formatRequired, loadingFinished, decodeOnlyUntilPositionUs); formatHolder, buffer, formatRequired, loadingFinished, decodeOnlyUntilPositionUs);
} }
...@@ -781,7 +777,6 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -781,7 +777,6 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
formatHolder, formatHolder,
buffer, buffer,
formatRequired, formatRequired,
/* allowOnlyClearBuffers= */ false,
loadingFinished, loadingFinished,
decodeOnlyUntilPositionUs); decodeOnlyUntilPositionUs);
} }
......
...@@ -29,16 +29,24 @@ import com.google.android.exoplayer2.C; ...@@ -29,16 +29,24 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; 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.extractor.TrackOutput;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import org.junit.After; import org.junit.After;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
/** Test for {@link SampleQueue}. */ /** Test for {@link SampleQueue}. */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
...@@ -50,6 +58,13 @@ public final class SampleQueueTest { ...@@ -50,6 +58,13 @@ public final class SampleQueueTest {
private static final Format FORMAT_2 = Format.createSampleFormat("2", "mimeType", 0); 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_1_COPY = Format.createSampleFormat("1", "mimeType", 0);
private static final Format FORMAT_SPLICED = Format.createSampleFormat("spliced", "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); private static final byte[] DATA = TestUtil.buildTestData(ALLOCATION_SIZE * 10);
/* /*
...@@ -91,18 +106,45 @@ public final class SampleQueueTest { ...@@ -91,18 +106,45 @@ public final class SampleQueueTest {
private static final Format[] SAMPLE_FORMATS = private static final Format[] SAMPLE_FORMATS =
new Format[] {FORMAT_1, FORMAT_1, FORMAT_1, FORMAT_1, FORMAT_2, FORMAT_2, FORMAT_2, FORMAT_2}; 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 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 = private static final TrackOutput.CryptoData DUMMY_CRYPTO_DATA =
new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, new byte[16], 0, 0); new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, new byte[16], 0, 0);
private Allocator allocator; private Allocator allocator;
private DrmSessionManager<ExoMediaCrypto> mockDrmSessionManager;
private DrmSession<ExoMediaCrypto> mockDrmSession;
private SampleQueue sampleQueue; private SampleQueue sampleQueue;
private FormatHolder formatHolder; private FormatHolder formatHolder;
private DecoderInputBuffer inputBuffer; private DecoderInputBuffer inputBuffer;
@Before @Before
@SuppressWarnings("unchecked")
public void setUp() throws Exception { public void setUp() throws Exception {
allocator = new DefaultAllocator(false, ALLOCATION_SIZE); allocator = new DefaultAllocator(false, ALLOCATION_SIZE);
sampleQueue = new SampleQueue(allocator); mockDrmSessionManager =
(DrmSessionManager<ExoMediaCrypto>) Mockito.mock(DrmSessionManager.class);
mockDrmSession = (DrmSession<ExoMediaCrypto>) Mockito.mock(DrmSession.class);
Mockito.when(
mockDrmSessionManager.acquireSession(ArgumentMatchers.any(), ArgumentMatchers.any()))
.thenReturn(mockDrmSession);
sampleQueue = new SampleQueue(allocator, mockDrmSessionManager);
formatHolder = new FormatHolder(); formatHolder = new FormatHolder();
inputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); inputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
} }
...@@ -153,9 +195,9 @@ public final class SampleQueueTest { ...@@ -153,9 +195,9 @@ public final class SampleQueueTest {
sampleQueue.sampleMetadata(1000, C.BUFFER_FLAG_KEY_FRAME, ALLOCATION_SIZE, 0, null); sampleQueue.sampleMetadata(1000, C.BUFFER_FLAG_KEY_FRAME, ALLOCATION_SIZE, 0, null);
assertReadFormat(false, FORMAT_1); 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. // 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. // The same applies if the queue is empty when the formats are written.
sampleQueue.format(FORMAT_2); sampleQueue.format(FORMAT_2);
...@@ -164,7 +206,7 @@ public final class SampleQueueTest { ...@@ -164,7 +206,7 @@ public final class SampleQueueTest {
sampleQueue.sampleMetadata(2000, C.BUFFER_FLAG_KEY_FRAME, ALLOCATION_SIZE, 0, null); sampleQueue.sampleMetadata(2000, C.BUFFER_FLAG_KEY_FRAME, ALLOCATION_SIZE, 0, null);
// Assert the third sample is read without a format change. // 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 @Test
...@@ -187,7 +229,7 @@ public final class SampleQueueTest { ...@@ -187,7 +229,7 @@ public final class SampleQueueTest {
// If formatRequired, should read the format rather than the sample. // If formatRequired, should read the format rather than the sample.
assertReadFormat(true, FORMAT_1); assertReadFormat(true, FORMAT_1);
// Otherwise should read the sample. // 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. // Allocation should still be held.
assertAllocationCount(1); assertAllocationCount(1);
sampleQueue.discardToRead(); sampleQueue.discardToRead();
...@@ -204,7 +246,7 @@ public final class SampleQueueTest { ...@@ -204,7 +246,7 @@ public final class SampleQueueTest {
// If formatRequired, should read the format rather than the sample. // If formatRequired, should read the format rather than the sample.
assertReadFormat(true, FORMAT_1); assertReadFormat(true, FORMAT_1);
// Read the sample. // 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. // Allocation should still be held.
assertAllocationCount(1); assertAllocationCount(1);
sampleQueue.discardToRead(); sampleQueue.discardToRead();
...@@ -218,7 +260,7 @@ public final class SampleQueueTest { ...@@ -218,7 +260,7 @@ public final class SampleQueueTest {
// If formatRequired, should read the format rather than the sample. // If formatRequired, should read the format rather than the sample.
assertReadFormat(true, FORMAT_1); assertReadFormat(true, FORMAT_1);
// Read the sample. // 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. // Allocation should still be held.
assertAllocationCount(1); assertAllocationCount(1);
sampleQueue.discardToRead(); sampleQueue.discardToRead();
...@@ -266,6 +308,137 @@ public final class SampleQueueTest { ...@@ -266,6 +308,137 @@ public final class SampleQueueTest {
} }
@Test @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<ExoMediaCrypto> mockPlaceholderDrmSession =
(DrmSession<ExoMediaCrypto>) 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() { public void testRewindAfterDiscard() {
writeTestData(); writeTestData();
assertReadTestData(); assertReadTestData();
...@@ -313,7 +486,7 @@ public final class SampleQueueTest { ...@@ -313,7 +486,7 @@ public final class SampleQueueTest {
sampleQueue.sampleMetadata(0, C.BUFFER_FLAG_KEY_FRAME, ALLOCATION_SIZE, 0, null); 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. // 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); assertNoSamplesToRead(FORMAT_1);
assertAllocationCount(1); assertAllocationCount(1);
sampleQueue.discardToRead(); sampleQueue.discardToRead();
...@@ -541,50 +714,7 @@ public final class SampleQueueTest { ...@@ -541,50 +714,7 @@ public final class SampleQueueTest {
// Discarding everything from upstream without reading should unset the largest timestamp. // Discarding everything from upstream without reading should unset the largest timestamp.
assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(MIN_VALUE); 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 @Test
public void testLargestQueuedTimestampWithRead() { public void testLargestQueuedTimestampWithRead() {
writeTestData(); writeTestData();
...@@ -612,7 +742,7 @@ public final class SampleQueueTest { ...@@ -612,7 +742,7 @@ public final class SampleQueueTest {
writeSample(DATA, spliceSampleTimeUs, FORMAT_SPLICED, C.BUFFER_FLAG_KEY_FRAME); writeSample(DATA, spliceSampleTimeUs, FORMAT_SPLICED, C.BUFFER_FLAG_KEY_FRAME);
assertReadTestData(null, 0, 4); assertReadTestData(null, 0, 4);
assertReadFormat(false, FORMAT_SPLICED); assertReadFormat(false, FORMAT_SPLICED);
assertReadSample(spliceSampleTimeUs, true, DATA, 0, DATA.length); assertReadSample(spliceSampleTimeUs, true, /* isEncrypted= */ false, DATA, 0, DATA.length);
assertReadEndOfStream(false); assertReadEndOfStream(false);
} }
...@@ -634,7 +764,7 @@ public final class SampleQueueTest { ...@@ -634,7 +764,7 @@ public final class SampleQueueTest {
spliceSampleTimeUs = SAMPLE_TIMESTAMPS[3] + 1; spliceSampleTimeUs = SAMPLE_TIMESTAMPS[3] + 1;
writeSample(DATA, spliceSampleTimeUs, FORMAT_SPLICED, C.BUFFER_FLAG_KEY_FRAME); writeSample(DATA, spliceSampleTimeUs, FORMAT_SPLICED, C.BUFFER_FLAG_KEY_FRAME);
assertReadFormat(false, FORMAT_SPLICED); assertReadFormat(false, FORMAT_SPLICED);
assertReadSample(spliceSampleTimeUs, true, DATA, 0, DATA.length); assertReadSample(spliceSampleTimeUs, true, /* isEncrypted= */ false, DATA, 0, DATA.length);
assertReadEndOfStream(false); assertReadEndOfStream(false);
} }
...@@ -649,7 +779,8 @@ public final class SampleQueueTest { ...@@ -649,7 +779,8 @@ public final class SampleQueueTest {
writeSample(DATA, spliceSampleTimeUs, FORMAT_SPLICED, C.BUFFER_FLAG_KEY_FRAME); writeSample(DATA, spliceSampleTimeUs, FORMAT_SPLICED, C.BUFFER_FLAG_KEY_FRAME);
assertReadTestData(null, 0, 4, sampleOffsetUs); assertReadTestData(null, 0, 4, sampleOffsetUs);
assertReadFormat(false, FORMAT_SPLICED.copyWithSubsampleOffsetUs(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); assertReadEndOfStream(false);
} }
...@@ -663,6 +794,16 @@ public final class SampleQueueTest { ...@@ -663,6 +794,16 @@ public final class SampleQueueTest {
DATA, SAMPLE_SIZES, SAMPLE_OFFSETS, SAMPLE_TIMESTAMPS, SAMPLE_FORMATS, SAMPLE_FLAGS); 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}. * Writes the specified test data to {@code sampleQueue}.
*/ */
...@@ -755,6 +896,7 @@ public final class SampleQueueTest { ...@@ -755,6 +896,7 @@ public final class SampleQueueTest {
assertReadSample( assertReadSample(
SAMPLE_TIMESTAMPS[i] + sampleOffsetUs, SAMPLE_TIMESTAMPS[i] + sampleOffsetUs,
(SAMPLE_FLAGS[i] & C.BUFFER_FLAG_KEY_FRAME) != 0, (SAMPLE_FLAGS[i] & C.BUFFER_FLAG_KEY_FRAME) != 0,
/* isEncrypted= */ false,
DATA, DATA,
DATA.length - SAMPLE_OFFSETS[i] - SAMPLE_SIZES[i], DATA.length - SAMPLE_OFFSETS[i] - SAMPLE_SIZES[i],
SAMPLE_SIZES[i]); SAMPLE_SIZES[i]);
...@@ -801,7 +943,6 @@ public final class SampleQueueTest { ...@@ -801,7 +943,6 @@ public final class SampleQueueTest {
formatHolder, formatHolder,
inputBuffer, inputBuffer,
formatRequired, formatRequired,
/* allowOnlyClearBuffers= */ false,
/* loadingFinished= */ false, /* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0); /* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_NOTHING_READ); assertThat(result).isEqualTo(RESULT_NOTHING_READ);
...@@ -825,7 +966,6 @@ public final class SampleQueueTest { ...@@ -825,7 +966,6 @@ public final class SampleQueueTest {
formatHolder, formatHolder,
inputBuffer, inputBuffer,
formatRequired, formatRequired,
/* allowOnlyClearBuffers= */ false,
/* loadingFinished= */ true, /* loadingFinished= */ true,
/* decodeOnlyUntilUs= */ 0); /* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_BUFFER_READ); assertThat(result).isEqualTo(RESULT_BUFFER_READ);
...@@ -852,7 +992,6 @@ public final class SampleQueueTest { ...@@ -852,7 +992,6 @@ public final class SampleQueueTest {
formatHolder, formatHolder,
inputBuffer, inputBuffer,
formatRequired, formatRequired,
/* allowOnlyClearBuffers= */ false,
/* loadingFinished= */ false, /* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0); /* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_FORMAT_READ); assertThat(result).isEqualTo(RESULT_FORMAT_READ);
...@@ -863,25 +1002,44 @@ public final class SampleQueueTest { ...@@ -863,25 +1002,44 @@ public final class SampleQueueTest {
assertInputBufferHasNoDefaultFlagsSet(); 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 * Asserts {@link SampleQueue#read} returns {@link C#RESULT_BUFFER_READ} and that the buffer is
* filled with the specified sample data. * filled with the specified sample data.
* *
* @param timeUs The expected buffer timestamp. * @param timeUs The expected buffer timestamp.
* @param isKeyframe The expected keyframe flag. * @param isKeyframe The expected keyframe flag.
* @param isEncrypted The expected encrypted flag.
* @param sampleData An array containing the expected sample data. * @param sampleData An array containing the expected sample data.
* @param offset The offset in {@code sampleData} of 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. * @param length The length of the expected sample data.
*/ */
private void assertReadSample( 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(); clearFormatHolderAndInputBuffer();
int result = int result =
sampleQueue.read( sampleQueue.read(
formatHolder, formatHolder,
inputBuffer, inputBuffer,
/* formatRequired= */ false, /* formatRequired= */ false,
/* allowOnlyClearBuffers= */ false,
/* loadingFinished= */ false, /* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0); /* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_BUFFER_READ); assertThat(result).isEqualTo(RESULT_BUFFER_READ);
...@@ -891,7 +1049,7 @@ public final class SampleQueueTest { ...@@ -891,7 +1049,7 @@ public final class SampleQueueTest {
assertThat(inputBuffer.timeUs).isEqualTo(timeUs); assertThat(inputBuffer.timeUs).isEqualTo(timeUs);
assertThat(inputBuffer.isKeyFrame()).isEqualTo(isKeyframe); assertThat(inputBuffer.isKeyFrame()).isEqualTo(isKeyframe);
assertThat(inputBuffer.isDecodeOnly()).isFalse(); assertThat(inputBuffer.isDecodeOnly()).isFalse();
assertThat(inputBuffer.isEncrypted()).isFalse(); assertThat(inputBuffer.isEncrypted()).isEqualTo(isEncrypted);
inputBuffer.flip(); inputBuffer.flip();
assertThat(inputBuffer.data.limit()).isEqualTo(length); assertThat(inputBuffer.data.limit()).isEqualTo(length);
byte[] readData = new byte[length]; byte[] readData = new byte[length];
...@@ -905,7 +1063,6 @@ public final class SampleQueueTest { ...@@ -905,7 +1063,6 @@ public final class SampleQueueTest {
sampleQueue.read( sampleQueue.read(
formatHolder, formatHolder,
inputBuffer, inputBuffer,
/* formatRequired= */ false,
allowOnlyClearBuffers, allowOnlyClearBuffers,
/* loadingFinished= */ false, /* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0); /* decodeOnlyUntilUs= */ 0);
......
...@@ -24,6 +24,7 @@ import com.google.android.exoplayer2.C; ...@@ -24,6 +24,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.ParserException; 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.ExtractorInput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
...@@ -195,7 +196,8 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -195,7 +196,8 @@ public final class PlayerEmsgHandler implements Handler.Callback {
/** Returns a {@link TrackOutput} that emsg messages could be written to. */ /** Returns a {@link TrackOutput} that emsg messages could be written to. */
public PlayerTrackEmsgHandler newPlayerTrackEmsgHandler() { 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. */ /** Release this emsg handler. It should not be reused after this call. */
...@@ -376,7 +378,6 @@ public final class PlayerEmsgHandler implements Handler.Callback { ...@@ -376,7 +378,6 @@ public final class PlayerEmsgHandler implements Handler.Callback {
formatHolder, formatHolder,
buffer, buffer,
/* formatRequired= */ false, /* formatRequired= */ false,
/* allowOnlyClearBuffers= */ false,
/* loadingFinished= */ false, /* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0); /* decodeOnlyUntilUs= */ 0);
if (result == C.RESULT_BUFFER_READ) { if (result == C.RESULT_BUFFER_READ) {
......
...@@ -36,7 +36,6 @@ import com.google.android.exoplayer2.metadata.Metadata; ...@@ -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.EventMessage;
import com.google.android.exoplayer2.metadata.emsg.EventMessageDecoder; import com.google.android.exoplayer2.metadata.emsg.EventMessageDecoder;
import com.google.android.exoplayer2.metadata.id3.PrivFrame; 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.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.SampleQueue; import com.google.android.exoplayer2.source.SampleQueue;
import com.google.android.exoplayer2.source.SampleQueue.UpstreamFormatChangedListener; import com.google.android.exoplayer2.source.SampleQueue.UpstreamFormatChangedListener;
...@@ -129,7 +128,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -129,7 +128,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private final Map<String, DrmInitData> overridingDrmInitData; private final Map<String, DrmInitData> overridingDrmInitData;
private SampleQueue[] sampleQueues; private SampleQueue[] sampleQueues;
private DecryptableSampleQueueReader[] sampleQueueReaders;
private int[] sampleQueueTrackIds; private int[] sampleQueueTrackIds;
private Set<Integer> sampleQueueMappingDoneByType; private Set<Integer> sampleQueueMappingDoneByType;
private SparseIntArray sampleQueueIndicesByType; private SparseIntArray sampleQueueIndicesByType;
...@@ -209,7 +207,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -209,7 +207,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
sampleQueueMappingDoneByType = new HashSet<>(MAPPABLE_TYPES.size()); sampleQueueMappingDoneByType = new HashSet<>(MAPPABLE_TYPES.size());
sampleQueueIndicesByType = new SparseIntArray(MAPPABLE_TYPES.size()); sampleQueueIndicesByType = new SparseIntArray(MAPPABLE_TYPES.size());
sampleQueues = new SampleQueue[0]; sampleQueues = new SampleQueue[0];
sampleQueueReaders = new DecryptableSampleQueueReader[0];
sampleQueueIsAudioVideoFlags = new boolean[0]; sampleQueueIsAudioVideoFlags = new boolean[0];
sampleQueuesEnabledStates = new boolean[0]; sampleQueuesEnabledStates = new boolean[0];
mediaChunks = new ArrayList<>(); mediaChunks = new ArrayList<>();
...@@ -490,10 +487,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -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 // 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. // sampleQueues may still be being modified by the loading thread.
for (SampleQueue sampleQueue : sampleQueues) { for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.discardToEnd(); sampleQueue.preRelease();
}
for (DecryptableSampleQueueReader reader : sampleQueueReaders) {
reader.release();
} }
} }
loader.release(this); loader.release(this);
...@@ -504,9 +498,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -504,9 +498,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Override @Override
public void onLoaderReleased() { public void onLoaderReleased() {
resetSampleQueues(); for (SampleQueue sampleQueue : sampleQueues) {
for (DecryptableSampleQueueReader reader : sampleQueueReaders) { sampleQueue.release();
reader.release();
} }
} }
...@@ -521,12 +514,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -521,12 +514,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
// SampleStream implementation. // SampleStream implementation.
public boolean isReady(int sampleQueueIndex) { public boolean isReady(int sampleQueueIndex) {
return !isPendingReset() && sampleQueueReaders[sampleQueueIndex].isReady(loadingFinished); return !isPendingReset() && sampleQueues[sampleQueueIndex].isReady(loadingFinished);
} }
public void maybeThrowError(int sampleQueueIndex) throws IOException { public void maybeThrowError(int sampleQueueIndex) throws IOException {
maybeThrowError(); maybeThrowError();
sampleQueueReaders[sampleQueueIndex].maybeThrowError(); sampleQueues[sampleQueueIndex].maybeThrowError();
} }
public void maybeThrowError() throws IOException { public void maybeThrowError() throws IOException {
...@@ -559,7 +552,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -559,7 +552,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
int result = int result =
sampleQueueReaders[sampleQueueIndex].read( sampleQueues[sampleQueueIndex].read(
formatHolder, buffer, requireFormat, loadingFinished, lastSeekPositionUs); formatHolder, buffer, requireFormat, loadingFinished, lastSeekPositionUs);
if (result == C.RESULT_FORMAT_READ) { if (result == C.RESULT_FORMAT_READ) {
Format format = Assertions.checkNotNull(formatHolder.format); Format format = Assertions.checkNotNull(formatHolder.format);
...@@ -917,17 +910,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -917,17 +910,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private SampleQueue createSampleQueue(int id, int type) { private SampleQueue createSampleQueue(int id, int type) {
int trackCount = sampleQueues.length; int trackCount = sampleQueues.length;
SampleQueue trackOutput = new FormatAdjustingSampleQueue(allocator, overridingDrmInitData); SampleQueue trackOutput =
new FormatAdjustingSampleQueue(allocator, drmSessionManager, overridingDrmInitData);
trackOutput.setSampleOffsetUs(sampleOffsetUs); trackOutput.setSampleOffsetUs(sampleOffsetUs);
trackOutput.sourceId(chunkUid); trackOutput.sourceId(chunkUid);
trackOutput.setUpstreamFormatChangeListener(this); trackOutput.setUpstreamFormatChangeListener(this);
sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1); sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1);
sampleQueueTrackIds[trackCount] = id; sampleQueueTrackIds[trackCount] = id;
sampleQueues = Util.nullSafeArrayAppend(sampleQueues, trackOutput); sampleQueues = Util.nullSafeArrayAppend(sampleQueues, trackOutput);
sampleQueueReaders =
Util.nullSafeArrayAppend(
sampleQueueReaders,
new DecryptableSampleQueueReader(sampleQueues[trackCount], drmSessionManager));
sampleQueueIsAudioVideoFlags = Arrays.copyOf(sampleQueueIsAudioVideoFlags, trackCount + 1); sampleQueueIsAudioVideoFlags = Arrays.copyOf(sampleQueueIsAudioVideoFlags, trackCount + 1);
sampleQueueIsAudioVideoFlags[trackCount] = sampleQueueIsAudioVideoFlags[trackCount] =
type == C.TRACK_TYPE_AUDIO || type == C.TRACK_TYPE_VIDEO; type == C.TRACK_TYPE_AUDIO || type == C.TRACK_TYPE_VIDEO;
...@@ -1295,8 +1285,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -1295,8 +1285,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private final Map<String, DrmInitData> overridingDrmInitData; private final Map<String, DrmInitData> overridingDrmInitData;
public FormatAdjustingSampleQueue( public FormatAdjustingSampleQueue(
Allocator allocator, Map<String, DrmInitData> overridingDrmInitData) { Allocator allocator,
super(allocator); DrmSessionManager<?> drmSessionManager,
Map<String, DrmInitData> overridingDrmInitData) {
super(allocator, drmSessionManager);
this.overridingDrmInitData = overridingDrmInitData; this.overridingDrmInitData = overridingDrmInitData;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment