Make HlsSampleSource use LoadControl.

This will be needed for keeping WebVTT subtitle and separate
audio tracks is sync whilst loading.

Issue: #151
This commit is contained in:
Oliver Woodman 2015-05-28 17:14:52 +01:00
parent ea29c71d94
commit b806109cfd
2 changed files with 70 additions and 38 deletions

View File

@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer.demo.player; package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException; import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
@ -32,6 +34,7 @@ import com.google.android.exoplayer.metadata.Id3Parser;
import com.google.android.exoplayer.metadata.MetadataTrackRenderer; import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
import com.google.android.exoplayer.text.eia608.Eia608TrackRenderer; import com.google.android.exoplayer.text.eia608.Eia608TrackRenderer;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.DefaultUriDataSource; import com.google.android.exoplayer.upstream.DefaultUriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
@ -50,8 +53,8 @@ import java.util.Map;
*/ */
public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<HlsPlaylist> { public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<HlsPlaylist> {
private static final int REQUESTED_BUFFER_SIZE = 18 * 1024 * 1024; private static final int BUFFER_SEGMENT_SIZE = 256 * 1024;
private static final long REQUESTED_BUFFER_DURATION_MS = 40000; private static final int BUFFER_SEGMENTS = 64;
private final Context context; private final Context context;
private final String userAgent; private final String userAgent;
@ -89,6 +92,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
@Override @Override
public void onSingleManifest(HlsPlaylist manifest) { public void onSingleManifest(HlsPlaylist manifest) {
Handler mainHandler = player.getMainHandler(); Handler mainHandler = player.getMainHandler();
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
int[] variantIndices = null; int[] variantIndices = null;
@ -106,8 +110,8 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter, HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter,
variantIndices, HlsChunkSource.ADAPTIVE_MODE_SPLICE, audioCapabilities); variantIndices, HlsChunkSource.ADAPTIVE_MODE_SPLICE, audioCapabilities);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 3, REQUESTED_BUFFER_SIZE, HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl,
REQUESTED_BUFFER_DURATION_MS, mainHandler, player, DemoPlayer.TYPE_VIDEO); BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, 3, mainHandler, player, DemoPlayer.TYPE_VIDEO);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50); MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource); MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer.hls; package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.MediaFormatHolder; import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleHolder;
@ -25,8 +26,6 @@ import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener; import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener;
import com.google.android.exoplayer.chunk.Chunk; import com.google.android.exoplayer.chunk.Chunk;
import com.google.android.exoplayer.chunk.Format; import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.upstream.Loader.Loadable; import com.google.android.exoplayer.upstream.Loader.Loadable;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
@ -52,23 +51,22 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
*/ */
public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3;
private static final int BUFFER_FRAGMENT_LENGTH = 256 * 1024;
private static final int NO_RESET_PENDING = -1; private static final int NO_RESET_PENDING = -1;
private final HlsChunkSource chunkSource; private final HlsChunkSource chunkSource;
private final LinkedList<HlsExtractorWrapper> extractors; private final LinkedList<HlsExtractorWrapper> extractors;
private final Allocator allocator;
private final boolean frameAccurateSeeking; private final boolean frameAccurateSeeking;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final int requestedBufferSize; private final int bufferSizeContribution;
private final long requestedBufferDurationUs;
private final int eventSourceId; private final int eventSourceId;
private final LoadControl loadControl;
private final Handler eventHandler; private final Handler eventHandler;
private final EventListener eventListener; private final EventListener eventListener;
private int remainingReleaseCount; private int remainingReleaseCount;
private boolean prepared; private boolean prepared;
private boolean loadControlRegistered;
private int trackCount; private int trackCount;
private int enabledTrackCount; private int enabledTrackCount;
private boolean[] trackEnabledStates; private boolean[] trackEnabledStates;
@ -92,36 +90,35 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
private long currentLoadableExceptionTimestamp; private long currentLoadableExceptionTimestamp;
private long currentLoadStartTimeMs; private long currentLoadStartTimeMs;
public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking, public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl,
int downstreamRendererCount, int requestedBufferSize, long requestedBufferDurationMs) { int bufferSizeContribution, boolean frameAccurateSeeking, int downstreamRendererCount) {
this(chunkSource, frameAccurateSeeking, downstreamRendererCount, requestedBufferSize, this(chunkSource, loadControl, bufferSizeContribution, frameAccurateSeeking,
requestedBufferDurationMs, null, null, 0); downstreamRendererCount, null, null, 0);
} }
public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking, public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl,
int downstreamRendererCount, int requestedBufferSize, long requestedBufferDurationMs, int bufferSizeContribution, boolean frameAccurateSeeking, int downstreamRendererCount,
Handler eventHandler, EventListener eventListener, int eventSourceId) { Handler eventHandler, EventListener eventListener, int eventSourceId) {
this(chunkSource, frameAccurateSeeking, downstreamRendererCount, requestedBufferSize, this(chunkSource, loadControl, bufferSizeContribution, frameAccurateSeeking,
requestedBufferDurationMs, eventHandler, eventListener, eventSourceId, downstreamRendererCount, eventHandler, eventListener, eventSourceId,
DEFAULT_MIN_LOADABLE_RETRY_COUNT); DEFAULT_MIN_LOADABLE_RETRY_COUNT);
} }
public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking, public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl,
int downstreamRendererCount, int requestedBufferSize, long requestedBufferDurationMs, int bufferSizeContribution, boolean frameAccurateSeeking, int downstreamRendererCount,
Handler eventHandler, EventListener eventListener, Handler eventHandler, EventListener eventListener, int eventSourceId,
int eventSourceId, int minLoadableRetryCount) { int minLoadableRetryCount) {
this.chunkSource = chunkSource; this.chunkSource = chunkSource;
this.loadControl = loadControl;
this.bufferSizeContribution = bufferSizeContribution;
this.frameAccurateSeeking = frameAccurateSeeking; this.frameAccurateSeeking = frameAccurateSeeking;
this.remainingReleaseCount = downstreamRendererCount; this.remainingReleaseCount = downstreamRendererCount;
this.requestedBufferSize = requestedBufferSize;
this.requestedBufferDurationUs = requestedBufferDurationMs * 1000;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
this.eventListener = eventListener; this.eventListener = eventListener;
this.eventSourceId = eventSourceId; this.eventSourceId = eventSourceId;
this.pendingResetPositionUs = NO_RESET_PENDING; this.pendingResetPositionUs = NO_RESET_PENDING;
extractors = new LinkedList<>(); extractors = new LinkedList<>();
allocator = new DefaultAllocator(BUFFER_FRAGMENT_LENGTH);
} }
@Override @Override
@ -150,6 +147,10 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
if (loader == null) { if (loader == null) {
loader = new Loader("Loader:HLS"); loader = new Loader("Loader:HLS");
} }
if (!loadControlRegistered) {
loadControl.register(this, bufferSizeContribution);
loadControlRegistered = true;
}
if (!loader.isLoading()) { if (!loader.isLoading()) {
// We're going to have to start loading a chunk to get what we need for preparation. We should // We're going to have to start loading a chunk to get what we need for preparation. We should
// attempt to load the chunk at positionUs, so that we'll already be loading the correct chunk // attempt to load the chunk at positionUs, so that we'll already be loading the correct chunk
@ -182,6 +183,10 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
trackEnabledStates[track] = true; trackEnabledStates[track] = true;
downstreamMediaFormats[track] = null; downstreamMediaFormats[track] = null;
downstreamFormat = null; downstreamFormat = null;
if (!loadControlRegistered) {
loadControl.register(this, bufferSizeContribution);
loadControlRegistered = true;
}
if (enabledTrackCount == 1) { if (enabledTrackCount == 1) {
seekToUs(positionUs); seekToUs(positionUs);
} }
@ -194,12 +199,16 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
enabledTrackCount--; enabledTrackCount--;
trackEnabledStates[track] = false; trackEnabledStates[track] = false;
pendingDiscontinuities[track] = false; pendingDiscontinuities[track] = false;
if (loadControlRegistered) {
loadControl.unregister(this);
loadControlRegistered = false;
}
if (enabledTrackCount == 0) { if (enabledTrackCount == 0) {
if (loader.isLoading()) { if (loader.isLoading()) {
loader.cancelLoading(); loader.cancelLoading();
} else { } else {
clearState(); clearState();
allocator.trim(0); loadControl.trimAllocator();
} }
} }
} }
@ -357,7 +366,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
maybeStartLoading(); maybeStartLoading();
} else { } else {
clearState(); clearState();
allocator.trim(0); loadControl.trimAllocator();
} }
} }
@ -368,7 +377,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
restartFrom(pendingResetPositionUs); restartFrom(pendingResetPositionUs);
} else { } else {
clearState(); clearState();
allocator.trim(0); loadControl.trimAllocator();
} }
} }
@ -464,13 +473,23 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
} }
private void maybeStartLoading() { private void maybeStartLoading() {
if (currentLoadableExceptionFatal || loadingFinished || loader.isLoading()) { if (currentLoadableExceptionFatal) {
// We've failed, but we still need to update the control with our current state.
loadControl.update(this, downstreamPositionUs, -1, false, true);
return; return;
} }
long now = SystemClock.elapsedRealtime();
long nextLoadPositionUs = getNextLoadPositionUs();
boolean isBackedOff = currentLoadableException != null; boolean isBackedOff = currentLoadableException != null;
boolean loadingOrBackedOff = loader.isLoading() || isBackedOff;
// Update the control with our current state, and determine whether we're the next loader.
boolean nextLoader = loadControl.update(this, downstreamPositionUs, nextLoadPositionUs,
loadingOrBackedOff, false);
if (isBackedOff) { if (isBackedOff) {
long elapsedMillis = SystemClock.elapsedRealtime() - currentLoadableExceptionTimestamp; long elapsedMillis = now - currentLoadableExceptionTimestamp;
if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) { if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) {
currentLoadableException = null; currentLoadableException = null;
loader.startLoading(currentLoadable, this); loader.startLoading(currentLoadable, this);
@ -478,20 +497,17 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
return; return;
} }
if (previousTsLoadable != null if (loader.isLoading() || !nextLoader) {
&& (previousTsLoadable.endTimeUs - downstreamPositionUs >= requestedBufferDurationUs
|| allocator.getTotalBytesAllocated() >= requestedBufferSize)) {
// We already have the target amount of data or time buffered.
return; return;
} }
Chunk nextLoadable = chunkSource.getChunkOperation(previousTsLoadable, Chunk nextLoadable = chunkSource.getChunkOperation(previousTsLoadable, pendingResetPositionUs,
pendingResetPositionUs, downstreamPositionUs); downstreamPositionUs);
if (nextLoadable == null) { if (nextLoadable == null) {
return; return;
} }
currentLoadStartTimeMs = SystemClock.elapsedRealtime(); currentLoadStartTimeMs = now;
currentLoadable = nextLoadable; currentLoadable = nextLoadable;
if (isTsChunk(currentLoadable)) { if (isTsChunk(currentLoadable)) {
TsChunk tsChunk = (TsChunk) currentLoadable; TsChunk tsChunk = (TsChunk) currentLoadable;
@ -500,7 +516,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
} }
HlsExtractorWrapper extractorWrapper = tsChunk.extractorWrapper; HlsExtractorWrapper extractorWrapper = tsChunk.extractorWrapper;
if (extractors.isEmpty() || extractors.getLast() != extractorWrapper) { if (extractors.isEmpty() || extractors.getLast() != extractorWrapper) {
extractorWrapper.init(allocator); extractorWrapper.init(loadControl.getAllocator());
extractors.addLast(extractorWrapper); extractors.addLast(extractorWrapper);
} }
notifyLoadStarted(tsChunk.dataSpec.length, tsChunk.type, tsChunk.trigger, tsChunk.format, notifyLoadStarted(tsChunk.dataSpec.length, tsChunk.type, tsChunk.trigger, tsChunk.format,
@ -513,6 +529,18 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
loader.startLoading(currentLoadable, this); loader.startLoading(currentLoadable, this);
} }
/**
* Gets the next load time, assuming that the next load starts where the previous chunk ended (or
* from the pending reset time, if there is one).
*/
private long getNextLoadPositionUs() {
if (isPendingReset()) {
return pendingResetPositionUs;
} else {
return previousTsLoadable.isLastChunk ? -1 : previousTsLoadable.endTimeUs;
}
}
private boolean isTsChunk(Chunk chunk) { private boolean isTsChunk(Chunk chunk) {
return chunk instanceof TsChunk; return chunk instanceof TsChunk;
} }