mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Make SampleQueue populate FormatHolder.drmSession
Also add unit tests for SampleQueue read for samples with DRM requirements. PiperOrigin-RevId: 277037826
This commit is contained in:
parent
e025371429
commit
b68a698806
7 changed files with 417 additions and 355 deletions
|
|
@ -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.
|
|
||||||
*
|
|
||||||
* <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;
|
||||||
@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;
|
||||||
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;
|
||||||
// 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;
|
||||||
@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;
|
||||||
// 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;
|
||||||
}
|
}
|
||||||
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;
|
||||||
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;
|
||||||
@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 @@
|
||||||
*/
|
*/
|
||||||
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 {
|
||||||
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 {
|
||||||
|
|
||||||
// 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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 {
|
||||||
// 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 {
|
||||||
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.
|
||||||
*/
|
*/
|
||||||
|
|
@ -337,20 +378,19 @@ public class SampleQueue implements TrackOutput {
|
||||||
return metadataQueue.setReadPosition(sampleIndex);
|
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.
|
* 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 {
|
||||||
* @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()) {
|
||||||
|
|
@ -404,6 +468,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.
|
||||||
*
|
*
|
||||||
|
|
@ -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;
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
|
|
||||||
@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
|
||||||
}
|
}
|
||||||
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
|
||||||
formatHolder,
|
formatHolder,
|
||||||
buffer,
|
buffer,
|
||||||
formatRequired,
|
formatRequired,
|
||||||
/* allowOnlyClearBuffers= */ false,
|
|
||||||
loadingFinished,
|
loadingFinished,
|
||||||
decodeOnlyUntilPositionUs);
|
decodeOnlyUntilPositionUs);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
// 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 {
|
||||||
// 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 {
|
||||||
// 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();
|
||||||
|
|
@ -265,6 +307,137 @@ public final class SampleQueueTest {
|
||||||
assertReadTestData();
|
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<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
|
@Test
|
||||||
public void testRewindAfterDiscard() {
|
public void testRewindAfterDiscard() {
|
||||||
writeTestData();
|
writeTestData();
|
||||||
|
|
@ -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 {
|
||||||
// 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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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;
|
||||||
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 {
|
||||||
|
|
||||||
/** 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 {
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
// 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;
|
||||||
|
|
||||||
@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;
|
||||||
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue