mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Make FormatHolder be entirely populated by SampleMetadataQueue
This should not introduce any functional changes. PiperOrigin-RevId: 277691550
This commit is contained in:
parent
fbea71b0fe
commit
2d530478ee
2 changed files with 152 additions and 202 deletions
|
|
@ -15,17 +15,19 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source;
|
package com.google.android.exoplayer2.source;
|
||||||
|
|
||||||
import androidx.annotation.IntDef;
|
import android.os.Looper;
|
||||||
|
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.TrackOutput.CryptoData;
|
import com.google.android.exoplayer2.extractor.TrackOutput.CryptoData;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.lang.annotation.Documented;
|
import java.io.IOException;
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A queue of metadata describing the contents of a media buffer.
|
* A queue of metadata describing the contents of a media buffer.
|
||||||
|
|
@ -43,29 +45,14 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Values returned by {@link #peekNext} ()}. */
|
|
||||||
@Documented
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@IntDef(
|
|
||||||
value = {
|
|
||||||
PEEK_RESULT_NOTHING,
|
|
||||||
PEEK_RESULT_FORMAT,
|
|
||||||
PEEK_RESULT_BUFFER_CLEAR,
|
|
||||||
PEEK_RESULT_BUFFER_ENCRYPTED
|
|
||||||
})
|
|
||||||
public @interface PeekResult {}
|
|
||||||
|
|
||||||
/** Nothing is available for reading. */
|
|
||||||
public static final int PEEK_RESULT_NOTHING = 0;
|
|
||||||
/** A format change is available for reading */
|
|
||||||
public static final int PEEK_RESULT_FORMAT = 1;
|
|
||||||
/** A clear buffer is available for reading. */
|
|
||||||
public static final int PEEK_RESULT_BUFFER_CLEAR = 2;
|
|
||||||
/** An encrypted buffer is available for reading. */
|
|
||||||
public static final int PEEK_RESULT_BUFFER_ENCRYPTED = 3;
|
|
||||||
|
|
||||||
private static final int SAMPLE_CAPACITY_INCREMENT = 1000;
|
private static final int SAMPLE_CAPACITY_INCREMENT = 1000;
|
||||||
|
|
||||||
|
private final DrmSessionManager<?> drmSessionManager;
|
||||||
|
private final boolean playClearSamplesWithoutKeys;
|
||||||
|
|
||||||
|
@Nullable private Format downstreamFormat;
|
||||||
|
@Nullable private DrmSession<?> currentDrmSession;
|
||||||
|
|
||||||
private int capacity;
|
private int capacity;
|
||||||
private int[] sourceIds;
|
private int[] sourceIds;
|
||||||
private long[] offsets;
|
private long[] offsets;
|
||||||
|
|
@ -89,7 +76,11 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
private Format upstreamCommittedFormat;
|
private Format upstreamCommittedFormat;
|
||||||
private int upstreamSourceId;
|
private int upstreamSourceId;
|
||||||
|
|
||||||
public SampleMetadataQueue() {
|
public SampleMetadataQueue(DrmSessionManager<?> drmSessionManager) {
|
||||||
|
this.drmSessionManager = drmSessionManager;
|
||||||
|
playClearSamplesWithoutKeys =
|
||||||
|
(drmSessionManager.getFlags() & DrmSessionManager.FLAG_PLAY_CLEAR_SAMPLES_WITHOUT_KEYS)
|
||||||
|
!= 0;
|
||||||
capacity = SAMPLE_CAPACITY_INCREMENT;
|
capacity = SAMPLE_CAPACITY_INCREMENT;
|
||||||
sourceIds = new int[capacity];
|
sourceIds = new int[capacity];
|
||||||
offsets = new long[capacity];
|
offsets = new long[capacity];
|
||||||
|
|
@ -104,6 +95,8 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
upstreamKeyframeRequired = true;
|
upstreamKeyframeRequired = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called by the consuming thread, but only when there is no loading thread.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears all sample metadata from the queue.
|
* Clears all sample metadata from the queue.
|
||||||
*
|
*
|
||||||
|
|
@ -163,8 +156,29 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
// Called by the consuming thread.
|
// Called by the consuming thread.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current absolute start index.
|
* 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 (currentDrmSession != null && currentDrmSession.getState() == DrmSession.STATE_ERROR) {
|
||||||
|
throw Assertions.checkNotNull(currentDrmSession.getError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Releases any owned {@link DrmSession} references. */
|
||||||
|
public void releaseDrmSessionReferences() {
|
||||||
|
if (currentDrmSession != null) {
|
||||||
|
currentDrmSession.releaseReference();
|
||||||
|
currentDrmSession = null;
|
||||||
|
// Clear downstream format to avoid violating the assumption that downstreamFormat.drmInitData
|
||||||
|
// != null implies currentSession != null
|
||||||
|
downstreamFormat = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the current absolute start index. */
|
||||||
public int getFirstIndex() {
|
public int getFirstIndex() {
|
||||||
return absoluteFirstIndex;
|
return absoluteFirstIndex;
|
||||||
}
|
}
|
||||||
|
|
@ -182,18 +196,11 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
*
|
*
|
||||||
* @return The source id.
|
* @return The source id.
|
||||||
*/
|
*/
|
||||||
public int peekSourceId() {
|
public synchronized int peekSourceId() {
|
||||||
int relativeReadIndex = getRelativeIndex(readPosition);
|
int relativeReadIndex = getRelativeIndex(readPosition);
|
||||||
return hasNextSample() ? sourceIds[relativeReadIndex] : upstreamSourceId;
|
return hasNextSample() ? sourceIds[relativeReadIndex] : upstreamSourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether a sample is available to be read.
|
|
||||||
*/
|
|
||||||
public synchronized boolean hasNextSample() {
|
|
||||||
return readPosition != length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the upstream {@link Format} in which samples are being queued.
|
* Returns the upstream {@link Format} in which samples are being queued.
|
||||||
*/
|
*/
|
||||||
|
|
@ -242,23 +249,39 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link PeekResult} depending on what a following call to {@link #read
|
* Returns whether there is data available for reading.
|
||||||
* read(formatHolder, decoderInputBuffer, formatRequired= false, allowOnlyClearBuffers= false,
|
*
|
||||||
* loadingFinished= false, decodeOnlyUntilUs= 0)} would result in.
|
* <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.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("ReferenceEquality")
|
public boolean isReady(boolean loadingFinished) {
|
||||||
@PeekResult
|
if (!hasNextSample()) {
|
||||||
public synchronized int peekNext(Format downstreamFormat) {
|
return loadingFinished
|
||||||
if (readPosition == length) {
|
|| isLastSampleQueued
|
||||||
return PEEK_RESULT_NOTHING;
|
|| (upstreamFormat != null && upstreamFormat != downstreamFormat);
|
||||||
}
|
}
|
||||||
int relativeReadIndex = getRelativeIndex(readPosition);
|
int relativeReadIndex = getRelativeIndex(readPosition);
|
||||||
if (formats[relativeReadIndex] != downstreamFormat) {
|
if (formats[relativeReadIndex] != downstreamFormat) {
|
||||||
return PEEK_RESULT_FORMAT;
|
// A format can be read.
|
||||||
|
return true;
|
||||||
|
} else if (Assertions.checkNotNull(downstreamFormat).drmInitData == null) {
|
||||||
|
// A sample from a clear section can be read.
|
||||||
|
return true;
|
||||||
|
} else if (drmSessionManager == DrmSessionManager.DUMMY
|
||||||
|
|| Assertions.checkNotNull(currentDrmSession).getState()
|
||||||
|
== DrmSession.STATE_OPENED_WITH_KEYS) {
|
||||||
|
// TODO: Remove DUMMY DrmSessionManager check once renderers are migrated [Internal ref:
|
||||||
|
// b/122519809].
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return (flags[relativeReadIndex] & C.BUFFER_FLAG_ENCRYPTED) != 0
|
// A clear sample in an encrypted section may be read if playClearSamplesWithoutKeys is true.
|
||||||
? PEEK_RESULT_BUFFER_ENCRYPTED
|
return (flags[relativeReadIndex] & C.BUFFER_FLAG_ENCRYPTED) == 0
|
||||||
: PEEK_RESULT_BUFFER_CLEAR;
|
&& playClearSamplesWithoutKeys;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,11 +301,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
* @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 downstreamFormat The current downstream {@link Format}. If the format of the next sample
|
|
||||||
* is different to the current downstream format then a format will be read.
|
|
||||||
* @param extrasHolder The holder into which extra sample information should be written.
|
* @param extrasHolder The holder into which extra sample information should be written.
|
||||||
* @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}.
|
||||||
|
|
@ -292,16 +311,14 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
FormatHolder formatHolder,
|
FormatHolder formatHolder,
|
||||||
DecoderInputBuffer buffer,
|
DecoderInputBuffer buffer,
|
||||||
boolean formatRequired,
|
boolean formatRequired,
|
||||||
boolean allowOnlyClearBuffers,
|
|
||||||
boolean loadingFinished,
|
boolean loadingFinished,
|
||||||
Format downstreamFormat,
|
|
||||||
SampleExtrasHolder extrasHolder) {
|
SampleExtrasHolder extrasHolder) {
|
||||||
if (!hasNextSample()) {
|
if (!hasNextSample()) {
|
||||||
if (loadingFinished || isLastSampleQueued) {
|
if (loadingFinished || isLastSampleQueued) {
|
||||||
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
|
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
|
||||||
return C.RESULT_BUFFER_READ;
|
return C.RESULT_BUFFER_READ;
|
||||||
} else if (upstreamFormat != null && (formatRequired || upstreamFormat != downstreamFormat)) {
|
} else if (upstreamFormat != null && (formatRequired || upstreamFormat != downstreamFormat)) {
|
||||||
formatHolder.format = upstreamFormat;
|
onFormatResult(Assertions.checkNotNull(upstreamFormat), formatHolder);
|
||||||
return C.RESULT_FORMAT_READ;
|
return C.RESULT_FORMAT_READ;
|
||||||
} else {
|
} else {
|
||||||
return C.RESULT_NOTHING_READ;
|
return C.RESULT_NOTHING_READ;
|
||||||
|
|
@ -310,11 +327,23 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
int relativeReadIndex = getRelativeIndex(readPosition);
|
int relativeReadIndex = getRelativeIndex(readPosition);
|
||||||
if (formatRequired || formats[relativeReadIndex] != downstreamFormat) {
|
if (formatRequired || formats[relativeReadIndex] != downstreamFormat) {
|
||||||
formatHolder.format = formats[relativeReadIndex];
|
onFormatResult(formats[relativeReadIndex], formatHolder);
|
||||||
return C.RESULT_FORMAT_READ;
|
return C.RESULT_FORMAT_READ;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allowOnlyClearBuffers && (flags[relativeReadIndex] & C.BUFFER_FLAG_ENCRYPTED) != 0) {
|
// It's likely that the media source creation has not yet been migrated and the renderer can
|
||||||
|
// acquire the session for the sample.
|
||||||
|
// TODO: Remove once renderers are migrated [Internal ref: b/122519809].
|
||||||
|
boolean skipDrmChecks = drmSessionManager == DrmSessionManager.DUMMY;
|
||||||
|
boolean isNextSampleEncrypted = (flags[relativeReadIndex] & C.BUFFER_FLAG_ENCRYPTED) != 0;
|
||||||
|
|
||||||
|
boolean mayReadSample =
|
||||||
|
skipDrmChecks
|
||||||
|
|| Util.castNonNull(downstreamFormat).drmInitData == null
|
||||||
|
|| (playClearSamplesWithoutKeys && !isNextSampleEncrypted)
|
||||||
|
|| Assertions.checkNotNull(currentDrmSession).getState()
|
||||||
|
== DrmSession.STATE_OPENED_WITH_KEYS;
|
||||||
|
if (!mayReadSample) {
|
||||||
return C.RESULT_NOTHING_READ;
|
return C.RESULT_NOTHING_READ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -557,9 +586,54 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
|
private boolean hasNextSample() {
|
||||||
|
return readPosition != length;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the sample in the specified range that's before or at the specified time. If
|
* Sets the downstream format, performs DRM resource management, and populates the {@code
|
||||||
* {@code keyframe} is {@code true} then the sample is additionally required to be a keyframe.
|
* outputFormatHolder}.
|
||||||
|
*
|
||||||
|
* @param newFormat The new downstream format.
|
||||||
|
* @param outputFormatHolder The output {@link FormatHolder}.
|
||||||
|
*/
|
||||||
|
private void onFormatResult(Format newFormat, FormatHolder outputFormatHolder) {
|
||||||
|
outputFormatHolder.format = newFormat;
|
||||||
|
boolean isFirstFormat = downstreamFormat == null;
|
||||||
|
DrmInitData oldDrmInitData = isFirstFormat ? null : downstreamFormat.drmInitData;
|
||||||
|
downstreamFormat = newFormat;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
DrmInitData newDrmInitData = newFormat.drmInitData;
|
||||||
|
outputFormatHolder.includesDrmSession = true;
|
||||||
|
outputFormatHolder.drmSession = currentDrmSession;
|
||||||
|
if (!isFirstFormat && Util.areEqual(oldDrmInitData, newDrmInitData)) {
|
||||||
|
// Nothing to do.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Ensure we acquire the new session before releasing the previous one in case the same session
|
||||||
|
// is being used for both DrmInitData.
|
||||||
|
DrmSession<?> previousSession = currentDrmSession;
|
||||||
|
Looper playbackLooper = Assertions.checkNotNull(Looper.myLooper());
|
||||||
|
currentDrmSession =
|
||||||
|
newDrmInitData != null
|
||||||
|
? drmSessionManager.acquireSession(playbackLooper, newDrmInitData)
|
||||||
|
: drmSessionManager.acquirePlaceholderSession(playbackLooper);
|
||||||
|
outputFormatHolder.drmSession = currentDrmSession;
|
||||||
|
|
||||||
|
if (previousSession != null) {
|
||||||
|
previousSession.releaseReference();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the sample in the specified range that's before or at the specified time. If {@code
|
||||||
|
* keyframe} is {@code true} then the sample is additionally required to be a keyframe.
|
||||||
*
|
*
|
||||||
* @param relativeStartIndex The relative index from which to start searching.
|
* @param relativeStartIndex The relative index from which to start searching.
|
||||||
* @param length The length of the range being searched.
|
* @param length The length of the range being searched.
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,11 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source;
|
package com.google.android.exoplayer2.source;
|
||||||
|
|
||||||
import android.os.Looper;
|
|
||||||
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.DrmSession;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||||
|
|
@ -29,9 +27,7 @@ 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.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
@ -58,23 +54,16 @@ public class SampleQueue implements TrackOutput {
|
||||||
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;
|
||||||
private AllocationNode readAllocationNode;
|
private AllocationNode readAllocationNode;
|
||||||
private AllocationNode writeAllocationNode;
|
private AllocationNode writeAllocationNode;
|
||||||
|
|
||||||
// Accessed only by the consuming thread.
|
|
||||||
private Format downstreamFormat;
|
|
||||||
@Nullable private DrmSession<?> currentSession;
|
|
||||||
|
|
||||||
// Accessed only by the loading thread (or the consuming thread when there is no loading thread).
|
// Accessed only by the loading thread (or the consuming thread when there is no loading thread).
|
||||||
private boolean pendingFormatAdjustment;
|
private boolean pendingFormatAdjustment;
|
||||||
private Format lastUnadjustedFormat;
|
private Format lastUnadjustedFormat;
|
||||||
|
|
@ -88,19 +77,14 @@ public class SampleQueue implements TrackOutput {
|
||||||
*
|
*
|
||||||
* @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}
|
* @param drmSessionManager The {@link DrmSessionManager} to obtain {@link DrmSession DrmSessions}
|
||||||
* from.
|
* from. The created instance does not take ownership of this {@link DrmSessionManager}.
|
||||||
*/
|
*/
|
||||||
public SampleQueue(Allocator allocator, DrmSessionManager<?> drmSessionManager) {
|
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(drmSessionManager);
|
||||||
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;
|
||||||
|
|
@ -116,7 +100,7 @@ public class SampleQueue implements TrackOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the output and releases any held DRM resources.
|
* Resets the output.
|
||||||
*
|
*
|
||||||
* @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)})
|
||||||
|
|
@ -197,10 +181,7 @@ public class SampleQueue implements TrackOutput {
|
||||||
* @throws IOException The underlying error.
|
* @throws IOException The underlying error.
|
||||||
*/
|
*/
|
||||||
public void maybeThrowError() throws IOException {
|
public void maybeThrowError() throws IOException {
|
||||||
// TODO: Avoid throwing if the DRM error is not preventing a read operation.
|
metadataQueue.maybeThrowError();
|
||||||
if (currentSession != null && currentSession.getState() == DrmSession.STATE_ERROR) {
|
|
||||||
throw Assertions.checkNotNull(currentSession.getError());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -291,16 +272,16 @@ public class SampleQueue implements TrackOutput {
|
||||||
discardDownstreamTo(metadataQueue.discardToRead());
|
discardDownstreamTo(metadataQueue.discardToRead());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link #discardToEnd()} and releases any held DRM resources. */
|
/** Calls {@link #discardToEnd()} and releases any owned {@link DrmSession} references. */
|
||||||
public void preRelease() {
|
public void preRelease() {
|
||||||
discardToEnd();
|
discardToEnd();
|
||||||
releaseDrmResources();
|
metadataQueue.releaseDrmSessionReferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link #reset()} and releases any held DRM resources. */
|
/** Calls {@link #reset()} and releases any owned {@link DrmSession} references. */
|
||||||
public void release() {
|
public void release() {
|
||||||
reset();
|
reset();
|
||||||
releaseDrmResources();
|
metadataQueue.releaseDrmSessionReferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -360,7 +341,7 @@ public class SampleQueue implements TrackOutput {
|
||||||
* {@link DrmSessionManager#FLAG_PLAY_CLEAR_SAMPLES_WITHOUT_KEYS}.
|
* {@link DrmSessionManager#FLAG_PLAY_CLEAR_SAMPLES_WITHOUT_KEYS}.
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param outputFormatHolder A {@link FormatHolder} to populate in the case of reading a format.
|
* @param formatHolder 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
|
||||||
|
|
@ -377,65 +358,22 @@ public class SampleQueue implements TrackOutput {
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("ReferenceEquality")
|
@SuppressWarnings("ReferenceEquality")
|
||||||
public int read(
|
public int read(
|
||||||
FormatHolder outputFormatHolder,
|
FormatHolder formatHolder,
|
||||||
DecoderInputBuffer buffer,
|
DecoderInputBuffer buffer,
|
||||||
boolean formatRequired,
|
boolean formatRequired,
|
||||||
boolean loadingFinished,
|
boolean loadingFinished,
|
||||||
long decodeOnlyUntilUs) {
|
long decodeOnlyUntilUs) {
|
||||||
|
int result =
|
||||||
boolean readFlagFormatRequired = false;
|
metadataQueue.read(formatHolder, buffer, formatRequired, loadingFinished, extrasHolder);
|
||||||
boolean readFlagAllowOnlyClearBuffers = false;
|
if (result == C.RESULT_BUFFER_READ && !buffer.isEndOfStream()) {
|
||||||
boolean onlyPropagateFormatChanges = false;
|
if (buffer.timeUs < decodeOnlyUntilUs) {
|
||||||
|
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
|
||||||
if (downstreamFormat == null || formatRequired) {
|
}
|
||||||
readFlagFormatRequired = true;
|
if (!buffer.isFlagsOnly()) {
|
||||||
} else if (drmSessionManager != DrmSessionManager.DUMMY
|
readToBuffer(buffer, extrasHolder);
|
||||||
&& 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
int result =
|
|
||||||
metadataQueue.read(
|
|
||||||
scratchFormatHolder,
|
|
||||||
buffer,
|
|
||||||
readFlagFormatRequired,
|
|
||||||
readFlagAllowOnlyClearBuffers,
|
|
||||||
loadingFinished,
|
|
||||||
downstreamFormat,
|
|
||||||
extrasHolder);
|
|
||||||
switch (result) {
|
|
||||||
case C.RESULT_FORMAT_READ:
|
|
||||||
if (onlyPropagateFormatChanges && downstreamFormat == scratchFormatHolder.format) {
|
|
||||||
return C.RESULT_NOTHING_READ;
|
|
||||||
}
|
|
||||||
onFormat(Assertions.checkNotNull(scratchFormatHolder.format), outputFormatHolder);
|
|
||||||
return C.RESULT_FORMAT_READ;
|
|
||||||
case C.RESULT_BUFFER_READ:
|
|
||||||
if (!buffer.isEndOfStream()) {
|
|
||||||
if (buffer.timeUs < decodeOnlyUntilUs) {
|
|
||||||
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
|
|
||||||
}
|
|
||||||
if (!buffer.isFlagsOnly()) {
|
|
||||||
readToBuffer(buffer, extrasHolder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return C.RESULT_BUFFER_READ;
|
|
||||||
case C.RESULT_NOTHING_READ:
|
|
||||||
return C.RESULT_NOTHING_READ;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -450,21 +388,7 @@ public class SampleQueue implements TrackOutput {
|
||||||
* queue is empty.
|
* queue is empty.
|
||||||
*/
|
*/
|
||||||
public boolean isReady(boolean loadingFinished) {
|
public boolean isReady(boolean loadingFinished) {
|
||||||
@SampleMetadataQueue.PeekResult int nextInQueue = metadataQueue.peekNext(downstreamFormat);
|
return metadataQueue.isReady(loadingFinished);
|
||||||
switch (nextInQueue) {
|
|
||||||
case SampleMetadataQueue.PEEK_RESULT_NOTHING:
|
|
||||||
return loadingFinished;
|
|
||||||
case SampleMetadataQueue.PEEK_RESULT_FORMAT:
|
|
||||||
return true;
|
|
||||||
case SampleMetadataQueue.PEEK_RESULT_BUFFER_CLEAR:
|
|
||||||
return currentSession == null || playClearSamplesWithoutKeys;
|
|
||||||
case SampleMetadataQueue.PEEK_RESULT_BUFFER_ENCRYPTED:
|
|
||||||
return drmSessionManager == DrmSessionManager.DUMMY
|
|
||||||
|| Assertions.checkNotNull(currentSession).getState()
|
|
||||||
== DrmSession.STATE_OPENED_WITH_KEYS;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -811,54 +735,6 @@ public class SampleQueue implements TrackOutput {
|
||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Releases any held DRM resources. */
|
|
||||||
private void releaseDrmResources() {
|
|
||||||
if (currentSession != null) {
|
|
||||||
currentSession.releaseReference();
|
|
||||||
currentSession = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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. */
|
/** A node in a linked list of {@link Allocation}s held by the output. */
|
||||||
private static final class AllocationNode {
|
private static final class AllocationNode {
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue