Start cleaning up buffering.
- Always continue loading if we've got less than the minimum amount of media in the buffer. - Use LoadControl in ExtractorSampleSource. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=125679669
This commit is contained in:
parent
5055a4aa91
commit
a8883baa69
@ -58,8 +58,6 @@ public final class DefaultLoadControl implements LoadControl {
|
||||
|
||||
public static final int DEFAULT_LOW_WATERMARK_MS = 15000;
|
||||
public static final int DEFAULT_HIGH_WATERMARK_MS = 30000;
|
||||
public static final float DEFAULT_LOW_BUFFER_LOAD = 0.2f;
|
||||
public static final float DEFAULT_HIGH_BUFFER_LOAD = 0.8f;
|
||||
|
||||
private static final int ABOVE_HIGH_WATERMARK = 0;
|
||||
private static final int BETWEEN_WATERMARKS = 1;
|
||||
@ -73,12 +71,10 @@ public final class DefaultLoadControl implements LoadControl {
|
||||
|
||||
private final long lowWatermarkUs;
|
||||
private final long highWatermarkUs;
|
||||
private final float lowBufferLoad;
|
||||
private final float highBufferLoad;
|
||||
|
||||
private int targetBufferSize;
|
||||
private long maxLoadStartPositionUs;
|
||||
private int bufferState;
|
||||
private int targetBufferSize;
|
||||
private boolean targetBufferSizeReached;
|
||||
private boolean fillingBuffers;
|
||||
private boolean streamingPrioritySet;
|
||||
|
||||
@ -102,7 +98,7 @@ public final class DefaultLoadControl implements LoadControl {
|
||||
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);
|
||||
DEFAULT_HIGH_WATERMARK_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,15 +113,9 @@ public final class DefaultLoadControl implements LoadControl {
|
||||
* the filling state.
|
||||
* @param highWatermarkMs The minimum duration of media that can be buffered for the control to
|
||||
* transition from filling to draining.
|
||||
* @param lowBufferLoad The minimum fraction of the buffer that must be utilized for the control
|
||||
* to be in the draining state. If the utilization is lower, then the control will transition
|
||||
* to the filling state.
|
||||
* @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(DefaultAllocator allocator, Handler eventHandler,
|
||||
EventListener eventListener, int lowWatermarkMs, int highWatermarkMs, float lowBufferLoad,
|
||||
float highBufferLoad) {
|
||||
EventListener eventListener, int lowWatermarkMs, int highWatermarkMs) {
|
||||
this.allocator = allocator;
|
||||
this.eventHandler = eventHandler;
|
||||
this.eventListener = eventListener;
|
||||
@ -133,8 +123,6 @@ public final class DefaultLoadControl implements LoadControl {
|
||||
this.loaderStates = new HashMap<>();
|
||||
this.lowWatermarkUs = lowWatermarkMs * 1000L;
|
||||
this.highWatermarkUs = highWatermarkMs * 1000L;
|
||||
this.lowBufferLoad = lowBufferLoad;
|
||||
this.highBufferLoad = highBufferLoad;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -151,6 +139,7 @@ public final class DefaultLoadControl implements LoadControl {
|
||||
LoaderState state = loaderStates.remove(loader);
|
||||
targetBufferSize -= state.bufferSizeContribution;
|
||||
allocator.setTargetBufferSize(targetBufferSize);
|
||||
targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
|
||||
updateControlState();
|
||||
}
|
||||
|
||||
@ -174,11 +163,10 @@ public final class DefaultLoadControl implements LoadControl {
|
||||
}
|
||||
|
||||
// Update the buffer state.
|
||||
int currentBufferSize = allocator.getTotalBytesAllocated();
|
||||
int bufferState = getBufferState(currentBufferSize);
|
||||
boolean bufferStateChanged = this.bufferState != bufferState;
|
||||
boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
|
||||
boolean bufferStateChanged = this.targetBufferSizeReached != targetBufferSizeReached;
|
||||
if (bufferStateChanged) {
|
||||
this.bufferState = bufferState;
|
||||
this.targetBufferSizeReached = targetBufferSizeReached;
|
||||
}
|
||||
|
||||
// If either of the individual states have changed, update the shared control state.
|
||||
@ -186,8 +174,7 @@ public final class DefaultLoadControl implements LoadControl {
|
||||
updateControlState();
|
||||
}
|
||||
|
||||
return currentBufferSize < targetBufferSize && nextLoadPositionUs != C.UNSET_TIME_US
|
||||
&& nextLoadPositionUs <= maxLoadStartPositionUs;
|
||||
return nextLoadPositionUs != C.UNSET_TIME_US && nextLoadPositionUs <= maxLoadStartPositionUs;
|
||||
}
|
||||
|
||||
private int getLoaderBufferState(long playbackPositionUs, long nextLoadPositionUs) {
|
||||
@ -201,27 +188,20 @@ public final class DefaultLoadControl implements LoadControl {
|
||||
}
|
||||
}
|
||||
|
||||
private int getBufferState(int currentBufferSize) {
|
||||
float bufferLoad = (float) currentBufferSize / targetBufferSize;
|
||||
return bufferLoad > highBufferLoad ? ABOVE_HIGH_WATERMARK
|
||||
: bufferLoad < lowBufferLoad ? BELOW_LOW_WATERMARK
|
||||
: BETWEEN_WATERMARKS;
|
||||
}
|
||||
|
||||
private void updateControlState() {
|
||||
boolean loading = false;
|
||||
boolean haveNextLoadPosition = false;
|
||||
int highestState = bufferState;
|
||||
int worstLoaderState = ABOVE_HIGH_WATERMARK;
|
||||
for (int i = 0; i < loaders.size(); i++) {
|
||||
LoaderState loaderState = loaderStates.get(loaders.get(i));
|
||||
loading |= loaderState.loading;
|
||||
haveNextLoadPosition |= loaderState.nextLoadPositionUs != C.UNSET_TIME_US;
|
||||
highestState = Math.max(highestState, loaderState.bufferState);
|
||||
worstLoaderState = Math.max(worstLoaderState, loaderState.bufferState);
|
||||
}
|
||||
|
||||
fillingBuffers = !loaders.isEmpty() && (loading || haveNextLoadPosition)
|
||||
&& (highestState == BELOW_LOW_WATERMARK
|
||||
|| (highestState == BETWEEN_WATERMARKS && fillingBuffers));
|
||||
&& (worstLoaderState == BELOW_LOW_WATERMARK
|
||||
|| (worstLoaderState == BETWEEN_WATERMARKS && fillingBuffers && !targetBufferSizeReached));
|
||||
if (fillingBuffers && !streamingPrioritySet) {
|
||||
NetworkLock.instance.add(NetworkLock.STREAMING_PRIORITY);
|
||||
streamingPrioritySet = true;
|
||||
|
@ -17,14 +17,15 @@ package com.google.android.exoplayer.extractor;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer.DefaultLoadControl;
|
||||
import com.google.android.exoplayer.FormatHolder;
|
||||
import com.google.android.exoplayer.LoadControl;
|
||||
import com.google.android.exoplayer.ParserException;
|
||||
import com.google.android.exoplayer.SampleSource;
|
||||
import com.google.android.exoplayer.TrackGroup;
|
||||
import com.google.android.exoplayer.TrackGroupArray;
|
||||
import com.google.android.exoplayer.TrackSelection;
|
||||
import com.google.android.exoplayer.TrackStream;
|
||||
import com.google.android.exoplayer.upstream.Allocator;
|
||||
import com.google.android.exoplayer.upstream.BandwidthMeter;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DataSourceFactory;
|
||||
@ -36,6 +37,7 @@ import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.ConditionVariable;
|
||||
import android.os.Handler;
|
||||
|
||||
import java.io.EOFException;
|
||||
@ -125,7 +127,8 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
private final Handler eventHandler;
|
||||
private final EventListener eventListener;
|
||||
private final DataSource dataSource;
|
||||
private final Allocator allocator;
|
||||
private final ConditionVariable loadCondition;
|
||||
private final LoadControl loadControl;
|
||||
private final Loader loader;
|
||||
private final ExtractorHolder extractorHolder;
|
||||
|
||||
@ -186,7 +189,8 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
this.eventListener = eventListener;
|
||||
this.eventHandler = eventHandler;
|
||||
dataSource = dataSourceFactory.createDataSource(bandwidthMeter);
|
||||
allocator = new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE);
|
||||
loadCondition = new ConditionVariable();
|
||||
loadControl = new DefaultLoadControl(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
|
||||
loader = new Loader("Loader:ExtractorSampleSource");
|
||||
extractorHolder = new ExtractorHolder(extractors, this);
|
||||
pendingResetPositionUs = C.UNSET_TIME_US;
|
||||
@ -308,6 +312,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
return true;
|
||||
}
|
||||
if (seekMap != null && tracksBuilt && haveFormatsForAllTracks()) {
|
||||
loadCondition.close();
|
||||
int trackCount = sampleQueues.length;
|
||||
TrackGroup[] trackArray = new TrackGroup[trackCount];
|
||||
trackEnabledStates = new boolean[trackCount];
|
||||
@ -322,6 +327,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
// We're not prepared.
|
||||
maybeThrowError();
|
||||
if (!loader.isLoading()) {
|
||||
loadCondition.open();
|
||||
startLoading();
|
||||
}
|
||||
return false;
|
||||
@ -341,6 +347,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
public TrackStream[] selectTracks(List<TrackStream> oldStreams,
|
||||
List<TrackSelection> newSelections, long positionUs) {
|
||||
Assertions.checkState(prepared);
|
||||
boolean tracksWereEnabled = enabledTrackCount > 0;
|
||||
// Unselect old tracks.
|
||||
for (int i = 0; i < oldStreams.size(); i++) {
|
||||
int track = ((TrackStreamImpl) oldStreams.get(i)).track;
|
||||
@ -372,11 +379,21 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
}
|
||||
// Cancel or start requests as necessary.
|
||||
if (enabledTrackCount == 0) {
|
||||
if (tracksWereEnabled) {
|
||||
loadControl.unregister(this);
|
||||
loadCondition.close();
|
||||
}
|
||||
if (loader.isLoading()) {
|
||||
loader.cancelLoading();
|
||||
}
|
||||
} else if (seenFirstTrackSelection ? newStreams.length > 0 : positionUs != 0) {
|
||||
seekToUs(positionUs);
|
||||
} else {
|
||||
if (!tracksWereEnabled) {
|
||||
loadControl.register(this, C.DEFAULT_MUXED_BUFFER_SIZE);
|
||||
loadCondition.open();
|
||||
}
|
||||
if (seenFirstTrackSelection ? newStreams.length > 0 : positionUs != 0) {
|
||||
seekToUs(positionUs);
|
||||
}
|
||||
}
|
||||
seenFirstTrackSelection = true;
|
||||
return newStreams;
|
||||
@ -384,7 +401,11 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
|
||||
@Override
|
||||
public void continueBuffering(long playbackPositionUs) {
|
||||
// Do nothing.
|
||||
if (loadControl.update(this, playbackPositionUs, getBufferedPositionUs(), loader.isLoading())) {
|
||||
loadCondition.open();
|
||||
} else {
|
||||
loadCondition.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -434,6 +455,9 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
for (DefaultTrackOutput sampleQueue : sampleQueues) {
|
||||
sampleQueue.disable();
|
||||
}
|
||||
if (enabledTrackCount > 0) {
|
||||
loadControl.unregister(this);
|
||||
}
|
||||
loader.release();
|
||||
}
|
||||
|
||||
@ -498,7 +522,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
@Override
|
||||
public TrackOutput track(int id) {
|
||||
sampleQueues = Arrays.copyOf(sampleQueues, sampleQueues.length + 1);
|
||||
DefaultTrackOutput sampleQueue = new DefaultTrackOutput(allocator);
|
||||
DefaultTrackOutput sampleQueue = new DefaultTrackOutput(loadControl.getAllocator());
|
||||
sampleQueues[sampleQueues.length - 1] = sampleQueue;
|
||||
return sampleQueue;
|
||||
}
|
||||
@ -536,7 +560,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
|
||||
private void startLoading() {
|
||||
ExtractingLoadable loadable = new ExtractingLoadable(uri, dataSource, extractorHolder,
|
||||
allocator, C.DEFAULT_MUXED_BUFFER_SIZE);
|
||||
loadCondition);
|
||||
if (prepared) {
|
||||
Assertions.checkState(isPendingReset());
|
||||
if (durationUs != C.UNSET_TIME_US && pendingResetPositionUs >= durationUs) {
|
||||
@ -657,8 +681,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
private final Uri uri;
|
||||
private final DataSource dataSource;
|
||||
private final ExtractorHolder extractorHolder;
|
||||
private final Allocator allocator;
|
||||
private final int requestedBufferSize;
|
||||
private final ConditionVariable loadCondition;
|
||||
private final PositionHolder positionHolder;
|
||||
|
||||
private volatile boolean loadCanceled;
|
||||
@ -667,12 +690,11 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
private long length;
|
||||
|
||||
public ExtractingLoadable(Uri uri, DataSource dataSource, ExtractorHolder extractorHolder,
|
||||
Allocator allocator, int requestedBufferSize) {
|
||||
ConditionVariable loadCondition) {
|
||||
this.uri = Assertions.checkNotNull(uri);
|
||||
this.dataSource = Assertions.checkNotNull(dataSource);
|
||||
this.extractorHolder = Assertions.checkNotNull(extractorHolder);
|
||||
this.allocator = Assertions.checkNotNull(allocator);
|
||||
this.requestedBufferSize = requestedBufferSize;
|
||||
this.loadCondition = loadCondition;
|
||||
this.positionHolder = new PositionHolder();
|
||||
this.pendingExtractorSeek = true;
|
||||
this.length = C.LENGTH_UNBOUNDED;
|
||||
@ -711,11 +733,10 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
||||
pendingExtractorSeek = false;
|
||||
}
|
||||
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
|
||||
allocator.blockWhileTotalBytesAllocatedExceeds(requestedBufferSize);
|
||||
// TODO: Prevent the loader from loading too much data between evaluations of the
|
||||
// buffering condition.
|
||||
loadCondition.block();
|
||||
result = extractor.read(input, positionHolder);
|
||||
// TODO: Implement throttling to stop us from buffering data too often.
|
||||
// TODO: Block buffering between the point at which we have sufficient data for
|
||||
// preparation to complete and the first call to endTrackSelection.
|
||||
}
|
||||
} finally {
|
||||
if (result == Extractor.RESULT_SEEK) {
|
||||
|
@ -39,19 +39,10 @@ public interface Allocator {
|
||||
|
||||
/**
|
||||
* Hints to the {@link Allocator} that it should make a best effort to release any memory that it
|
||||
* has allocated that it no longer requires.
|
||||
* has allocated beyond the target buffer size.
|
||||
*/
|
||||
void trim();
|
||||
|
||||
/**
|
||||
* Blocks execution until the number of bytes allocated is not greater than the limit, or the
|
||||
* thread is interrupted.
|
||||
*
|
||||
* @param limit The limit in bytes.
|
||||
* @throws InterruptedException If the thread is interrupted.
|
||||
*/
|
||||
void blockWhileTotalBytesAllocatedExceeds(int limit) throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Returns the total number of bytes currently allocated.
|
||||
*/
|
||||
|
@ -47,7 +47,7 @@ public final class DefaultAllocator implements Allocator {
|
||||
/**
|
||||
* Constructs a pool with some {@link Allocation}s created up front.
|
||||
* <p>
|
||||
* Note: Initial {@link Allocation}s will never be discarded by {@link #trim(int)}.
|
||||
* Note: Initial {@link Allocation}s will never be discarded by {@link #trim()}.
|
||||
*
|
||||
* @param individualAllocationSize The length of each individual allocation.
|
||||
* @param initialAllocationCount The number of allocations to create up front.
|
||||
@ -70,7 +70,11 @@ public final class DefaultAllocator implements Allocator {
|
||||
}
|
||||
|
||||
public synchronized void setTargetBufferSize(int targetBufferSize) {
|
||||
boolean targetBufferSizeReduced = targetBufferSize < this.targetBufferSize;
|
||||
this.targetBufferSize = targetBufferSize;
|
||||
if (targetBufferSizeReduced) {
|
||||
trim();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -147,14 +151,6 @@ public final class DefaultAllocator implements Allocator {
|
||||
return allocatedCount * individualAllocationSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void blockWhileTotalBytesAllocatedExceeds(int limit)
|
||||
throws InterruptedException {
|
||||
while (getTotalBytesAllocated() > limit) {
|
||||
wait();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndividualAllocationLength() {
|
||||
return individualAllocationSize;
|
||||
|
Loading…
x
Reference in New Issue
Block a user