mirror of
https://github.com/samsonjs/media.git
synced 2026-04-02 10:45:51 +00:00
Properly disable sample queues when not selected.
- The main goal of this change is to remove the need for discardSamplesForDisabledTracks() in continueBuffering in HlsTrackStreamWrapper and ExtractorSampleSource. As a result we'll also save one Allocation per disabled track. - Another benefit of this change is that Allocator.trim calls can no longer be dropped. This could previously happen on player release if the playback thread's Looper quit before delivery of onLoadCanceled which performed the trim in the case that a load needed to be canceled at the time of the release. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=123957434
This commit is contained in:
parent
14cb76a112
commit
8e717e2dee
8 changed files with 231 additions and 241 deletions
|
|
@ -16,6 +16,7 @@
|
|||
package com.google.android.exoplayer;
|
||||
|
||||
import com.google.android.exoplayer.upstream.Allocator;
|
||||
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
||||
import com.google.android.exoplayer.upstream.NetworkLock;
|
||||
|
||||
import android.os.Handler;
|
||||
|
|
@ -64,7 +65,7 @@ public final class DefaultLoadControl implements LoadControl {
|
|||
private static final int BETWEEN_WATERMARKS = 1;
|
||||
private static final int BELOW_LOW_WATERMARK = 2;
|
||||
|
||||
private final Allocator allocator;
|
||||
private final DefaultAllocator allocator;
|
||||
private final List<Object> loaders;
|
||||
private final HashMap<Object, LoaderState> loaderStates;
|
||||
private final Handler eventHandler;
|
||||
|
|
@ -84,21 +85,21 @@ public final class DefaultLoadControl implements LoadControl {
|
|||
/**
|
||||
* Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class.
|
||||
*
|
||||
* @param allocator The {@link Allocator} used by the loader.
|
||||
* @param allocator The {@link DefaultAllocator} used by the loader.
|
||||
*/
|
||||
public DefaultLoadControl(Allocator allocator) {
|
||||
public DefaultLoadControl(DefaultAllocator allocator) {
|
||||
this(allocator, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class.
|
||||
*
|
||||
* @param allocator The {@link Allocator} used by the loader.
|
||||
* @param allocator The {@link DefaultAllocator} used by the loader.
|
||||
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
|
||||
* null if delivery of events is not required.
|
||||
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
||||
*/
|
||||
public DefaultLoadControl(Allocator allocator, Handler eventHandler,
|
||||
public DefaultLoadControl(DefaultAllocator allocator, Handler eventHandler,
|
||||
EventListener eventListener) {
|
||||
this(allocator, eventHandler, eventListener, DEFAULT_LOW_WATERMARK_MS,
|
||||
DEFAULT_HIGH_WATERMARK_MS, DEFAULT_LOW_BUFFER_LOAD, DEFAULT_HIGH_BUFFER_LOAD);
|
||||
|
|
@ -107,7 +108,7 @@ public final class DefaultLoadControl implements LoadControl {
|
|||
/**
|
||||
* Constructs a new instance.
|
||||
*
|
||||
* @param allocator The {@link Allocator} used by the loader.
|
||||
* @param allocator The {@link DefaultAllocator} used by the loader.
|
||||
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
|
||||
* null if delivery of events is not required.
|
||||
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
||||
|
|
@ -122,8 +123,9 @@ public final class DefaultLoadControl implements LoadControl {
|
|||
* @param highBufferLoad The minimum fraction of the buffer that must be utilized for the control
|
||||
* to transition from the loading state to the draining state.
|
||||
*/
|
||||
public DefaultLoadControl(Allocator allocator, Handler eventHandler, EventListener eventListener,
|
||||
int lowWatermarkMs, int highWatermarkMs, float lowBufferLoad, float highBufferLoad) {
|
||||
public DefaultLoadControl(DefaultAllocator allocator, Handler eventHandler,
|
||||
EventListener eventListener, int lowWatermarkMs, int highWatermarkMs, float lowBufferLoad,
|
||||
float highBufferLoad) {
|
||||
this.allocator = allocator;
|
||||
this.eventHandler = eventHandler;
|
||||
this.eventListener = eventListener;
|
||||
|
|
@ -140,6 +142,7 @@ public final class DefaultLoadControl implements LoadControl {
|
|||
loaders.add(loader);
|
||||
loaderStates.put(loader, new LoaderState(bufferSizeContribution));
|
||||
targetBufferSize += bufferSizeContribution;
|
||||
allocator.setTargetBufferSize(targetBufferSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -147,14 +150,10 @@ public final class DefaultLoadControl implements LoadControl {
|
|||
loaders.remove(loader);
|
||||
LoaderState state = loaderStates.remove(loader);
|
||||
targetBufferSize -= state.bufferSizeContribution;
|
||||
allocator.setTargetBufferSize(targetBufferSize);
|
||||
updateControlState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trimAllocator() {
|
||||
allocator.trim(targetBufferSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Allocator getAllocator() {
|
||||
return allocator;
|
||||
|
|
|
|||
|
|
@ -47,15 +47,6 @@ public interface LoadControl {
|
|||
*/
|
||||
Allocator getAllocator();
|
||||
|
||||
/**
|
||||
* Hints to the control that it should consider trimming any unused memory being held in order
|
||||
* to satisfy allocation requests.
|
||||
* <p>
|
||||
* This method is typically invoked by a recently unregistered loader, once it has released all
|
||||
* of its allocations back to the {@link Allocator}.
|
||||
*/
|
||||
void trimAllocator();
|
||||
|
||||
/**
|
||||
* Invoked by a loader to update the control with its current state.
|
||||
* <p>
|
||||
|
|
|
|||
|
|
@ -170,16 +170,11 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
|
|||
* This method should be called when the stream is no longer required.
|
||||
*/
|
||||
public void release() {
|
||||
chunkSource.release();
|
||||
loadControl.unregister(this);
|
||||
if (loader.isLoading()) {
|
||||
loader.cancelLoading();
|
||||
} else {
|
||||
clearState();
|
||||
loadControl.trimAllocator();
|
||||
}
|
||||
loader.release();
|
||||
released = true;
|
||||
chunkSource.release();
|
||||
sampleQueue.disable();
|
||||
loadControl.unregister(this);
|
||||
loader.release();
|
||||
}
|
||||
|
||||
// TrackStream implementation.
|
||||
|
|
@ -242,9 +237,6 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
|
|||
eventDispatcher.loadCanceled(loadable.bytesLoaded());
|
||||
if (!released) {
|
||||
restartFrom(pendingResetPositionUs);
|
||||
} else {
|
||||
clearState();
|
||||
loadControl.trimAllocator();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -277,19 +269,15 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
|
|||
private void restartFrom(long positionUs) {
|
||||
pendingResetPositionUs = positionUs;
|
||||
loadingFinished = false;
|
||||
mediaChunks.clear();
|
||||
if (loader.isLoading()) {
|
||||
loader.cancelLoading();
|
||||
} else {
|
||||
clearState();
|
||||
sampleQueue.reset(true);
|
||||
maybeStartLoading();
|
||||
}
|
||||
}
|
||||
|
||||
private void clearState() {
|
||||
sampleQueue.clear();
|
||||
mediaChunks.clear();
|
||||
}
|
||||
|
||||
private void maybeStartLoading() {
|
||||
long now = SystemClock.elapsedRealtime();
|
||||
if (now - lastPreferredQueueSizeEvaluationTimeMs > 5000) {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import java.io.EOFException;
|
|||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* A {@link TrackOutput} that buffers extracted samples in a queue and allows for consumption from
|
||||
|
|
@ -39,6 +40,10 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||
|
||||
private static final int INITIAL_SCRATCH_SIZE = 32;
|
||||
|
||||
private static final int STATE_ENABLED = 0;
|
||||
private static final int STATE_ENABLED_WRITING = 1;
|
||||
private static final int STATE_DISABLED = 2;
|
||||
|
||||
private final Allocator allocator;
|
||||
private final int allocationLength;
|
||||
|
||||
|
|
@ -46,6 +51,7 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||
private final LinkedBlockingDeque<Allocation> dataQueue;
|
||||
private final BufferExtrasHolder extrasHolder;
|
||||
private final ParsableByteArray scratch;
|
||||
private final AtomicInteger state;
|
||||
|
||||
// Accessed only by the consuming thread.
|
||||
private long totalBytesDropped;
|
||||
|
|
@ -69,6 +75,7 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||
dataQueue = new LinkedBlockingDeque<>();
|
||||
extrasHolder = new BufferExtrasHolder();
|
||||
scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE);
|
||||
state = new AtomicInteger();
|
||||
lastAllocationOffset = allocationLength;
|
||||
needKeyframe = true;
|
||||
}
|
||||
|
|
@ -76,26 +83,17 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||
// Called by the consuming thread, but only when there is no loading thread.
|
||||
|
||||
/**
|
||||
* Clears the buffer, returning all allocations to the allocator.
|
||||
* Resets the output.
|
||||
*
|
||||
* @param enable True if the output should be enabled. False if it should be disabled.
|
||||
*/
|
||||
public void clear() {
|
||||
infoQueue.clear();
|
||||
while (!dataQueue.isEmpty()) {
|
||||
allocator.release(dataQueue.remove());
|
||||
public void reset(boolean enable) {
|
||||
int previousState = state.getAndSet(enable ? STATE_ENABLED : STATE_DISABLED);
|
||||
clearSampleData();
|
||||
infoQueue.resetLargestParsedTimestamps();
|
||||
if (previousState == STATE_DISABLED) {
|
||||
downstreamFormat = null;
|
||||
}
|
||||
totalBytesDropped = 0;
|
||||
totalBytesWritten = 0;
|
||||
lastAllocation = null;
|
||||
lastAllocationOffset = allocationLength;
|
||||
needKeyframe = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that {@link #readData(FormatHolder, DecoderInputBuffer, boolean, long)} should
|
||||
* provide the sample format before any samples, even if it has already been provided.
|
||||
*/
|
||||
public void needDownstreamFormat() {
|
||||
downstreamFormat = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -151,6 +149,15 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||
|
||||
// Called by the consuming thread.
|
||||
|
||||
/**
|
||||
* Disables buffering of sample data and metadata.
|
||||
*/
|
||||
public void disable() {
|
||||
if (state.getAndSet(STATE_DISABLED) == STATE_ENABLED) {
|
||||
clearSampleData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the buffer is empty.
|
||||
*/
|
||||
|
|
@ -173,30 +180,19 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the largest sample timestamp that has been queued since the last {@link #clear()}.
|
||||
* Returns the largest sample timestamp that has been queued since the last {@link #reset}.
|
||||
* <p>
|
||||
* Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not
|
||||
* considered as having been queued. Samples that were dequeued from the front of the queue are
|
||||
* considered as having been queued.
|
||||
*
|
||||
* @return The largest sample timestamp that has been queued, or {@link Long#MIN_VALUE} if no
|
||||
* samples have been queued.
|
||||
* @return The largest sample timestamp that has been queued, or {@link Long#MIN_VALUE} if no
|
||||
* samples have been queued.
|
||||
*/
|
||||
public long getLargestQueuedTimestampUs() {
|
||||
return infoQueue.getLargestQueuedTimestampUs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips all currently buffered samples.
|
||||
*/
|
||||
public void skipAllSamples() {
|
||||
long nextOffset = infoQueue.skipAllSamples();
|
||||
if (nextOffset == -1) {
|
||||
return;
|
||||
}
|
||||
dropDownstreamTo(nextOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to skip to the keyframe before the specified time, if it's present in the buffer.
|
||||
*
|
||||
|
|
@ -417,22 +413,40 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||
@Override
|
||||
public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
|
||||
throws IOException, InterruptedException {
|
||||
length = prepareForAppend(length);
|
||||
int bytesAppended = input.read(lastAllocation.data,
|
||||
lastAllocation.translateOffset(lastAllocationOffset), length);
|
||||
if (bytesAppended == C.RESULT_END_OF_INPUT) {
|
||||
if (allowEndOfInput) {
|
||||
return C.RESULT_END_OF_INPUT;
|
||||
if (!startWriteOperation()) {
|
||||
int bytesSkipped = input.skip(length);
|
||||
if (bytesSkipped == C.RESULT_END_OF_INPUT) {
|
||||
if (allowEndOfInput) {
|
||||
return C.RESULT_END_OF_INPUT;
|
||||
}
|
||||
throw new EOFException();
|
||||
}
|
||||
throw new EOFException();
|
||||
return bytesSkipped;
|
||||
}
|
||||
try {
|
||||
length = prepareForAppend(length);
|
||||
int bytesAppended = input.read(lastAllocation.data,
|
||||
lastAllocation.translateOffset(lastAllocationOffset), length);
|
||||
if (bytesAppended == C.RESULT_END_OF_INPUT) {
|
||||
if (allowEndOfInput) {
|
||||
return C.RESULT_END_OF_INPUT;
|
||||
}
|
||||
throw new EOFException();
|
||||
}
|
||||
lastAllocationOffset += bytesAppended;
|
||||
totalBytesWritten += bytesAppended;
|
||||
return bytesAppended;
|
||||
} finally {
|
||||
endWriteOperation();
|
||||
}
|
||||
lastAllocationOffset += bytesAppended;
|
||||
totalBytesWritten += bytesAppended;
|
||||
return bytesAppended;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sampleData(ParsableByteArray buffer, int length) {
|
||||
if (!startWriteOperation()) {
|
||||
buffer.skipBytes(length);
|
||||
return;
|
||||
}
|
||||
while (length > 0) {
|
||||
int thisAppendLength = prepareForAppend(length);
|
||||
buffer.readBytes(lastAllocation.data, lastAllocation.translateOffset(lastAllocationOffset),
|
||||
|
|
@ -441,29 +455,63 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||
totalBytesWritten += thisAppendLength;
|
||||
length -= thisAppendLength;
|
||||
}
|
||||
endWriteOperation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
|
||||
if (pendingSplice) {
|
||||
if ((flags & C.BUFFER_FLAG_KEY_FRAME) == 0 || !infoQueue.attemptSplice(timeUs)) {
|
||||
return;
|
||||
}
|
||||
// TODO - We should be able to actually remove the data from the rolling buffer after a splice
|
||||
// succeeds, but doing so is a little bit tricky; it requires moving data written after the
|
||||
// last committed sample.
|
||||
pendingSplice = false;
|
||||
if (!startWriteOperation()) {
|
||||
infoQueue.commitSampleTimestamp(timeUs);
|
||||
return;
|
||||
}
|
||||
if (needKeyframe) {
|
||||
if ((flags & C.BUFFER_FLAG_KEY_FRAME) == 0) {
|
||||
// TODO - As above, although this case is probably less worthwhile.
|
||||
return;
|
||||
try {
|
||||
if (pendingSplice) {
|
||||
if ((flags & C.BUFFER_FLAG_KEY_FRAME) == 0 || !infoQueue.attemptSplice(timeUs)) {
|
||||
return;
|
||||
}
|
||||
// TODO - We should be able to actually remove the data from the rolling buffer after a
|
||||
// splice succeeds, but doing so is a little bit tricky; it requires moving data written
|
||||
// after the last committed sample.
|
||||
pendingSplice = false;
|
||||
}
|
||||
needKeyframe = false;
|
||||
if (needKeyframe) {
|
||||
if ((flags & C.BUFFER_FLAG_KEY_FRAME) == 0) {
|
||||
// TODO - As above, although this case is probably less worthwhile.
|
||||
return;
|
||||
}
|
||||
needKeyframe = false;
|
||||
}
|
||||
timeUs += sampleOffsetUs;
|
||||
long absoluteOffset = totalBytesWritten - size - offset;
|
||||
infoQueue.commitSample(timeUs, flags, absoluteOffset, size, encryptionKey);
|
||||
} finally {
|
||||
endWriteOperation();
|
||||
}
|
||||
timeUs += sampleOffsetUs;
|
||||
long absoluteOffset = totalBytesWritten - size - offset;
|
||||
infoQueue.commitSample(timeUs, flags, absoluteOffset, size, encryptionKey);
|
||||
}
|
||||
|
||||
// Private methods.
|
||||
|
||||
private boolean startWriteOperation() {
|
||||
return state.compareAndSet(STATE_ENABLED, STATE_ENABLED_WRITING);
|
||||
}
|
||||
|
||||
private void endWriteOperation() {
|
||||
if (!state.compareAndSet(STATE_ENABLED_WRITING, STATE_ENABLED)) {
|
||||
clearSampleData();
|
||||
}
|
||||
}
|
||||
|
||||
private void clearSampleData() {
|
||||
infoQueue.clearSampleData();
|
||||
while (!dataQueue.isEmpty()) {
|
||||
allocator.release(dataQueue.remove());
|
||||
}
|
||||
allocator.trim();
|
||||
totalBytesDropped = 0;
|
||||
totalBytesWritten = 0;
|
||||
lastAllocation = null;
|
||||
lastAllocationOffset = allocationLength;
|
||||
needKeyframe = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -533,16 +581,16 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||
largestQueuedTimestampUs = Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
// Called by the consuming thread, but only when there is no loading thread.
|
||||
|
||||
/**
|
||||
* Clears the queue.
|
||||
*/
|
||||
public void clear() {
|
||||
public void clearSampleData() {
|
||||
absoluteReadIndex = 0;
|
||||
relativeReadIndex = 0;
|
||||
relativeWriteIndex = 0;
|
||||
queueSize = 0;
|
||||
}
|
||||
|
||||
// Called by the consuming thread, but only when there is no loading thread.
|
||||
|
||||
public void resetLargestParsedTimestamps() {
|
||||
largestDequeuedTimestampUs = Long.MIN_VALUE;
|
||||
largestQueuedTimestampUs = Long.MIN_VALUE;
|
||||
}
|
||||
|
|
@ -612,7 +660,7 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the largest sample timestamp that has been queued since the last {@link #clear()}.
|
||||
* Returns the largest sample timestamp that has been queued since the last {@link #reset}.
|
||||
* <p>
|
||||
* Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not
|
||||
* considered as having been queued. Samples that were dequeued from the front of the queue are
|
||||
|
|
@ -676,25 +724,6 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||
return TrackStream.BUFFER_READ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips all currently buffered samples.
|
||||
*
|
||||
* @return The absolute position of the first byte in the rolling buffer that may still be
|
||||
* required after skipping the samples, or -1 if no samples were skipped.
|
||||
*/
|
||||
public synchronized long skipAllSamples() {
|
||||
if (queueSize == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
relativeReadIndex = (relativeReadIndex + queueSize) % capacity;
|
||||
absoluteReadIndex += queueSize;
|
||||
queueSize = 0;
|
||||
|
||||
int lastReadIndex = (relativeReadIndex == 0 ? capacity : relativeReadIndex) - 1;
|
||||
return sizes[lastReadIndex] + offsets[lastReadIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to locate the keyframe before the specified time, if it's present in the buffer.
|
||||
*
|
||||
|
|
@ -751,13 +780,13 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||
|
||||
public synchronized void commitSample(long timeUs, int sampleFlags, long offset, int size,
|
||||
byte[] encryptionKey) {
|
||||
commitSampleTimestamp(timeUs);
|
||||
timesUs[relativeWriteIndex] = timeUs;
|
||||
offsets[relativeWriteIndex] = offset;
|
||||
sizes[relativeWriteIndex] = size;
|
||||
flags[relativeWriteIndex] = sampleFlags;
|
||||
encryptionKeys[relativeWriteIndex] = encryptionKey;
|
||||
formats[relativeWriteIndex] = upstreamFormat;
|
||||
largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timeUs);
|
||||
// Increment the write index.
|
||||
queueSize++;
|
||||
if (queueSize == capacity) {
|
||||
|
|
@ -802,6 +831,10 @@ public final class DefaultTrackOutput implements TrackOutput {
|
|||
}
|
||||
}
|
||||
|
||||
public synchronized void commitSampleTimestamp(long timeUs) {
|
||||
largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timeUs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to discard samples from the tail of the queue to allow samples starting from the
|
||||
* specified timestamp to be spliced in.
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ import java.util.List;
|
|||
* from {@link Extractor#sniff(ExtractorInput)} will be used.
|
||||
*/
|
||||
public final class ExtractorSampleSource implements SampleSource, ExtractorOutput,
|
||||
Loader.Callback<Loadable> {
|
||||
Loader.Callback<ExtractorSampleSource.ExtractingLoadable> {
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be notified of {@link ExtractorSampleSource} events.
|
||||
|
|
@ -136,11 +136,9 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||
private boolean[] trackEnabledStates;
|
||||
private long length;
|
||||
|
||||
private long downstreamPositionUs;
|
||||
private long lastSeekPositionUs;
|
||||
private long pendingResetPositionUs;
|
||||
|
||||
private ExtractingLoadable loadable;
|
||||
private int extractedSamplesCountAtStartOfLoad;
|
||||
private boolean loadingFinished;
|
||||
|
||||
|
|
@ -391,6 +389,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||
Assertions.checkState(trackEnabledStates[track]);
|
||||
enabledTrackCount--;
|
||||
trackEnabledStates[track] = false;
|
||||
sampleQueues[track].disable();
|
||||
}
|
||||
// Select new tracks.
|
||||
TrackStream[] newStreams = new TrackStream[newSelections.size()];
|
||||
|
|
@ -402,20 +401,24 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||
Assertions.checkState(!trackEnabledStates[track]);
|
||||
enabledTrackCount++;
|
||||
trackEnabledStates[track] = true;
|
||||
sampleQueues[track].needDownstreamFormat();
|
||||
newStreams[i] = new TrackStreamImpl(track);
|
||||
}
|
||||
// At the time of the first track selection all queues will be enabled, so we need to disable
|
||||
// any that are no longer required.
|
||||
if (!seenFirstTrackSelection) {
|
||||
for (int i = 0; i < sampleQueues.length; i++) {
|
||||
if (!trackEnabledStates[i]) {
|
||||
sampleQueues[i].disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cancel or start requests as necessary.
|
||||
if (enabledTrackCount == 0) {
|
||||
downstreamPositionUs = Long.MIN_VALUE;
|
||||
if (loader.isLoading()) {
|
||||
loader.cancelLoading();
|
||||
} else {
|
||||
clearState();
|
||||
allocator.trim(0);
|
||||
}
|
||||
} else if (seenFirstTrackSelection ? newStreams.length > 0 : positionUs != 0) {
|
||||
seekToInternal(positionUs);
|
||||
seekToUs(positionUs);
|
||||
}
|
||||
seenFirstTrackSelection = true;
|
||||
return newStreams;
|
||||
|
|
@ -423,8 +426,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||
|
||||
@Override
|
||||
public void continueBuffering(long playbackPositionUs) {
|
||||
downstreamPositionUs = playbackPositionUs;
|
||||
discardSamplesForDisabledTracks();
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -448,18 +450,35 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||
largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs,
|
||||
sampleQueue.getLargestQueuedTimestampUs());
|
||||
}
|
||||
return largestQueuedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs
|
||||
return largestQueuedTimestampUs == Long.MIN_VALUE ? lastSeekPositionUs
|
||||
: largestQueuedTimestampUs;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekToUs(long positionUs) {
|
||||
seekToInternal(positionUs);
|
||||
// Treat all seeks into non-seekable media as being to t=0.
|
||||
positionUs = seekMap.isSeekable() ? positionUs : 0;
|
||||
lastSeekPositionUs = positionUs;
|
||||
notifyReset = true;
|
||||
// If we're not pending a reset, see if we can seek within the sample queues.
|
||||
boolean seekInsideBuffer = !isPendingReset();
|
||||
for (int i = 0; seekInsideBuffer && i < sampleQueues.length; i++) {
|
||||
if (trackEnabledStates[i]) {
|
||||
seekInsideBuffer = sampleQueues[i].skipToKeyframeBefore(positionUs);
|
||||
}
|
||||
}
|
||||
// If we failed to seek within the sample queues, we need to restart.
|
||||
if (!seekInsideBuffer) {
|
||||
restartFrom(positionUs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
for (DefaultTrackOutput sampleQueue : sampleQueues) {
|
||||
sampleQueue.disable();
|
||||
}
|
||||
enabledTrackCount = 0;
|
||||
loader.release();
|
||||
}
|
||||
|
|
@ -485,32 +504,29 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||
// Loader.Callback implementation.
|
||||
|
||||
@Override
|
||||
public void onLoadCompleted(Loadable loadable, long elapsedMs) {
|
||||
copyLengthFromLoader();
|
||||
public void onLoadCompleted(ExtractingLoadable loadable, long elapsedMs) {
|
||||
copyLengthFromLoader(loadable);
|
||||
loadingFinished = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCanceled(Loadable loadable, long elapsedMs) {
|
||||
copyLengthFromLoader();
|
||||
public void onLoadCanceled(ExtractingLoadable loadable, long elapsedMs) {
|
||||
copyLengthFromLoader(loadable);
|
||||
if (enabledTrackCount > 0) {
|
||||
restartFrom(pendingResetPositionUs);
|
||||
} else {
|
||||
clearState();
|
||||
allocator.trim(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onLoadError(Loadable loadable, long elapsedMs, IOException e) {
|
||||
copyLengthFromLoader();
|
||||
public int onLoadError(ExtractingLoadable loadable, long elapsedMs, IOException e) {
|
||||
copyLengthFromLoader(loadable);
|
||||
notifyLoadError(e);
|
||||
if (isLoadableExceptionFatal(e)) {
|
||||
return Loader.DONT_RETRY_FATAL;
|
||||
}
|
||||
int extractedSamplesCount = getExtractedSamplesCount();
|
||||
boolean madeProgress = extractedSamplesCount > extractedSamplesCountAtStartOfLoad;
|
||||
configureRetry(); // May clear the sample queues.
|
||||
configureRetry(loadable); // May reset the sample queues.
|
||||
extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount();
|
||||
return madeProgress ? Loader.RETRY_RESET_ERROR_COUNT : Loader.RETRY;
|
||||
}
|
||||
|
|
@ -537,45 +553,28 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||
|
||||
// Internal methods.
|
||||
|
||||
private void copyLengthFromLoader() {
|
||||
private void copyLengthFromLoader(ExtractingLoadable loadable) {
|
||||
if (length == C.LENGTH_UNBOUNDED) {
|
||||
length = loadable.length;
|
||||
}
|
||||
}
|
||||
|
||||
private void seekToInternal(long positionUs) {
|
||||
// Treat all seeks into non-seekable media as being to t=0.
|
||||
positionUs = seekMap.isSeekable() ? positionUs : 0;
|
||||
lastSeekPositionUs = positionUs;
|
||||
downstreamPositionUs = positionUs;
|
||||
notifyReset = true;
|
||||
// If we're not pending a reset, see if we can seek within the sample queues.
|
||||
boolean seekInsideBuffer = !isPendingReset();
|
||||
for (int i = 0; seekInsideBuffer && i < sampleQueues.length; i++) {
|
||||
if (trackEnabledStates[i]) {
|
||||
seekInsideBuffer = sampleQueues[i].skipToKeyframeBefore(positionUs);
|
||||
}
|
||||
}
|
||||
// If we failed to seek within the sample queues, we need to restart.
|
||||
if (!seekInsideBuffer) {
|
||||
restartFrom(positionUs);
|
||||
}
|
||||
}
|
||||
|
||||
private void restartFrom(long positionUs) {
|
||||
pendingResetPositionUs = positionUs;
|
||||
loadingFinished = false;
|
||||
if (loader.isLoading()) {
|
||||
loader.cancelLoading();
|
||||
} else {
|
||||
clearState();
|
||||
for (int i = 0; i < sampleQueues.length; i++) {
|
||||
sampleQueues[i].reset(trackEnabledStates[i]);
|
||||
}
|
||||
startLoading();
|
||||
}
|
||||
}
|
||||
|
||||
private void startLoading() {
|
||||
loadable = new ExtractingLoadable(uri, dataSource, extractorHolder, allocator,
|
||||
requestedBufferSize);
|
||||
ExtractingLoadable loadable = new ExtractingLoadable(uri, dataSource, extractorHolder,
|
||||
allocator, requestedBufferSize);
|
||||
if (prepared) {
|
||||
Assertions.checkState(isPendingReset());
|
||||
if (durationUs != C.UNSET_TIME_US && pendingResetPositionUs >= durationUs) {
|
||||
|
|
@ -599,8 +598,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||
loader.startLoading(loadable, this, minRetryCount);
|
||||
}
|
||||
|
||||
private void configureRetry() {
|
||||
Assertions.checkState(loadable != null);
|
||||
private void configureRetry(ExtractingLoadable loadable) {
|
||||
if (length != C.LENGTH_UNBOUNDED
|
||||
|| (seekMap != null && seekMap.getDurationUs() != C.UNSET_TIME_US)) {
|
||||
// We're playing an on-demand stream. Resume the current loadable, which will
|
||||
|
|
@ -612,7 +610,9 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||
// previous load finished, so it's necessary to load from the start whenever commencing
|
||||
// a new load.
|
||||
notifyReset = prepared;
|
||||
clearSampleQueues();
|
||||
for (int i = 0; i < sampleQueues.length; i++) {
|
||||
sampleQueues[i].reset(trackEnabledStates[i]);
|
||||
}
|
||||
loadable.setLoadPosition(0);
|
||||
}
|
||||
}
|
||||
|
|
@ -634,25 +634,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||
return true;
|
||||
}
|
||||
|
||||
private void discardSamplesForDisabledTracks() {
|
||||
for (int i = 0; i < trackEnabledStates.length; i++) {
|
||||
if (!trackEnabledStates[i]) {
|
||||
sampleQueues[i].skipAllSamples();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clearState() {
|
||||
clearSampleQueues();
|
||||
loadable = null;
|
||||
}
|
||||
|
||||
private void clearSampleQueues() {
|
||||
for (DefaultTrackOutput sampleQueue : sampleQueues) {
|
||||
sampleQueue.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPendingReset() {
|
||||
return pendingResetPositionUs != C.UNSET_TIME_US;
|
||||
}
|
||||
|
|
@ -700,7 +681,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||
/**
|
||||
* Loads the media stream and extracts sample data from it.
|
||||
*/
|
||||
private static class ExtractingLoadable implements Loadable {
|
||||
/* package */ static final class ExtractingLoadable implements Loadable {
|
||||
|
||||
private final Uri uri;
|
||||
private final DataSource dataSource;
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ import java.util.List;
|
|||
for (int i = 0; i < oldStreams.size(); i++) {
|
||||
int group = ((TrackStreamImpl) oldStreams.get(i)).group;
|
||||
setTrackGroupEnabledState(group, false);
|
||||
sampleQueues.valueAt(group).disable();
|
||||
}
|
||||
// Select new tracks.
|
||||
TrackStream[] newStreams = new TrackStream[newSelections.size()];
|
||||
|
|
@ -185,25 +186,32 @@ import java.util.List;
|
|||
int group = selection.group;
|
||||
int[] tracks = selection.getTracks();
|
||||
setTrackGroupEnabledState(group, true);
|
||||
sampleQueues.valueAt(group).needDownstreamFormat();
|
||||
if (group == primaryTrackGroupIndex) {
|
||||
chunkSource.selectTracks(tracks, isFirstTrackSelection);
|
||||
}
|
||||
newStreams[i] = new TrackStreamImpl(group);
|
||||
}
|
||||
// Cancel or start requests as necessary.
|
||||
// At the time of the first track selection all queues will be enabled, so we need to disable
|
||||
// any that are no longer required.
|
||||
if (isFirstTrackSelection) {
|
||||
int sampleQueueCount = sampleQueues.size();
|
||||
for (int i = 0; i < sampleQueueCount; i++) {
|
||||
if (!groupEnabledStates[i]) {
|
||||
sampleQueues.valueAt(i).disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cancel requests if necessary.
|
||||
if (enabledTrackCount == 0) {
|
||||
chunkSource.reset();
|
||||
downstreamPositionUs = Long.MIN_VALUE;
|
||||
downstreamFormat = null;
|
||||
mediaChunks.clear();
|
||||
if (tracksWereEnabled) {
|
||||
loadControl.unregister(this);
|
||||
}
|
||||
if (loader.isLoading()) {
|
||||
loader.cancelLoading();
|
||||
} else {
|
||||
clearState();
|
||||
loadControl.trimAllocator();
|
||||
}
|
||||
} else if (!tracksWereEnabled) {
|
||||
loadControl.register(this, bufferSizeContribution);
|
||||
|
|
@ -211,9 +219,26 @@ import java.util.List;
|
|||
return newStreams;
|
||||
}
|
||||
|
||||
public void restartFrom(long positionUs) {
|
||||
lastSeekPositionUs = positionUs;
|
||||
downstreamPositionUs = positionUs;
|
||||
pendingResetPositionUs = positionUs;
|
||||
loadingFinished = false;
|
||||
mediaChunks.clear();
|
||||
if (loader.isLoading()) {
|
||||
loader.cancelLoading();
|
||||
} else {
|
||||
int sampleQueueCount = sampleQueues.size();
|
||||
for (int i = 0; i < sampleQueueCount; i++) {
|
||||
sampleQueues.valueAt(i).reset(groupEnabledStates[i]);
|
||||
}
|
||||
maybeStartLoading();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO[REFACTOR]: Find a way to get rid of this.
|
||||
public void continueBuffering(long playbackPositionUs) {
|
||||
downstreamPositionUs = playbackPositionUs;
|
||||
discardSamplesForDisabledTracks();
|
||||
if (!loader.isLoading()) {
|
||||
maybeStartLoading();
|
||||
}
|
||||
|
|
@ -245,23 +270,14 @@ import java.util.List;
|
|||
}
|
||||
}
|
||||
|
||||
public void restartFrom(long positionUs) {
|
||||
lastSeekPositionUs = positionUs;
|
||||
downstreamPositionUs = positionUs;
|
||||
pendingResetPositionUs = positionUs;
|
||||
loadingFinished = false;
|
||||
if (loader.isLoading()) {
|
||||
loader.cancelLoading();
|
||||
} else {
|
||||
clearState();
|
||||
maybeStartLoading();
|
||||
}
|
||||
}
|
||||
|
||||
public void release() {
|
||||
int sampleQueueCount = sampleQueues.size();
|
||||
for (int i = 0; i < sampleQueueCount; i++) {
|
||||
sampleQueues.valueAt(i).disable();
|
||||
}
|
||||
if (enabledTrackCount > 0) {
|
||||
loadControl.unregister(this);
|
||||
enabledTrackCount = 0;
|
||||
loadControl.unregister(this);
|
||||
}
|
||||
loader.release();
|
||||
}
|
||||
|
|
@ -319,9 +335,6 @@ import java.util.List;
|
|||
eventDispatcher.loadCanceled(loadable.bytesLoaded());
|
||||
if (enabledTrackCount > 0) {
|
||||
restartFrom(pendingResetPositionUs);
|
||||
} else {
|
||||
clearState();
|
||||
loadControl.trimAllocator();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -501,24 +514,6 @@ import java.util.List;
|
|||
containerFormat.width, containerFormat.height, 0, containerFormat.language);
|
||||
}
|
||||
|
||||
private void discardSamplesForDisabledTracks() {
|
||||
if (!prepared) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < groupEnabledStates.length; i++) {
|
||||
if (!groupEnabledStates[i]) {
|
||||
sampleQueues.valueAt(i).skipAllSamples();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clearState() {
|
||||
for (int i = 0; i < sampleQueues.size(); i++) {
|
||||
sampleQueues.valueAt(i).clear();
|
||||
}
|
||||
mediaChunks.clear();
|
||||
}
|
||||
|
||||
private void maybeStartLoading() {
|
||||
boolean shouldStartLoading = !prepared || (enabledTrackCount > 0
|
||||
&& loadControl.update(this, downstreamPositionUs, getNextLoadPositionUs(), false));
|
||||
|
|
|
|||
|
|
@ -39,11 +39,9 @@ public interface Allocator {
|
|||
|
||||
/**
|
||||
* Hints to the {@link Allocator} that it should make a best effort to release any memory that it
|
||||
* has allocated, beyond the specified target number of bytes.
|
||||
*
|
||||
* @param targetSize The target size in bytes.
|
||||
* has allocated that it no longer requires.
|
||||
*/
|
||||
void trim(int targetSize);
|
||||
void trim();
|
||||
|
||||
/**
|
||||
* Blocks execution until the number of bytes allocated is not greater than the limit, or the
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ public final class DefaultAllocator implements Allocator {
|
|||
private final int individualAllocationSize;
|
||||
private final byte[] initialAllocationBlock;
|
||||
|
||||
private int targetBufferSize;
|
||||
private int allocatedCount;
|
||||
private int availableCount;
|
||||
private Allocation[] availableAllocations;
|
||||
|
|
@ -68,6 +69,10 @@ public final class DefaultAllocator implements Allocator {
|
|||
}
|
||||
}
|
||||
|
||||
public synchronized void setTargetBufferSize(int targetBufferSize) {
|
||||
this.targetBufferSize = targetBufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Allocation allocate() {
|
||||
allocatedCount++;
|
||||
|
|
@ -96,8 +101,8 @@ public final class DefaultAllocator implements Allocator {
|
|||
}
|
||||
|
||||
@Override
|
||||
public synchronized void trim(int targetSize) {
|
||||
int targetAllocationCount = Util.ceilDivide(targetSize, individualAllocationSize);
|
||||
public synchronized void trim() {
|
||||
int targetAllocationCount = Util.ceilDivide(targetBufferSize, individualAllocationSize);
|
||||
int targetAvailableCount = Math.max(0, targetAllocationCount - allocatedCount);
|
||||
if (targetAvailableCount >= availableCount) {
|
||||
// We're already at or below the target.
|
||||
|
|
|
|||
Loading…
Reference in a new issue