From b806109cfd63f4aedf19f388c1091b81f61bead2 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Thu, 28 May 2015 17:14:52 +0100 Subject: [PATCH] Make HlsSampleSource use LoadControl. This will be needed for keeping WebVTT subtitle and separate audio tracks is sync whilst loading. Issue: #151 --- .../demo/player/HlsRendererBuilder.java | 12 ++- .../exoplayer/hls/HlsSampleSource.java | 96 ++++++++++++------- 2 files changed, 70 insertions(+), 38 deletions(-) diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java index 43cf49924e..39b8393059 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java @@ -15,6 +15,8 @@ */ 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.MediaCodecUtil.DecoderQueryException; 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.text.eia608.Eia608TrackRenderer; 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.DefaultUriDataSource; import com.google.android.exoplayer.util.ManifestFetcher; @@ -50,8 +53,8 @@ import java.util.Map; */ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback { - private static final int REQUESTED_BUFFER_SIZE = 18 * 1024 * 1024; - private static final long REQUESTED_BUFFER_DURATION_MS = 40000; + private static final int BUFFER_SEGMENT_SIZE = 256 * 1024; + private static final int BUFFER_SEGMENTS = 64; private final Context context; private final String userAgent; @@ -89,6 +92,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback extractors; - private final Allocator allocator; private final boolean frameAccurateSeeking; private final int minLoadableRetryCount; - private final int requestedBufferSize; - private final long requestedBufferDurationUs; + private final int bufferSizeContribution; private final int eventSourceId; + private final LoadControl loadControl; private final Handler eventHandler; private final EventListener eventListener; private int remainingReleaseCount; private boolean prepared; + private boolean loadControlRegistered; private int trackCount; private int enabledTrackCount; private boolean[] trackEnabledStates; @@ -92,36 +90,35 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { private long currentLoadableExceptionTimestamp; private long currentLoadStartTimeMs; - public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking, - int downstreamRendererCount, int requestedBufferSize, long requestedBufferDurationMs) { - this(chunkSource, frameAccurateSeeking, downstreamRendererCount, requestedBufferSize, - requestedBufferDurationMs, null, null, 0); + public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl, + int bufferSizeContribution, boolean frameAccurateSeeking, int downstreamRendererCount) { + this(chunkSource, loadControl, bufferSizeContribution, frameAccurateSeeking, + downstreamRendererCount, null, null, 0); } - public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking, - int downstreamRendererCount, int requestedBufferSize, long requestedBufferDurationMs, + public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl, + int bufferSizeContribution, boolean frameAccurateSeeking, int downstreamRendererCount, Handler eventHandler, EventListener eventListener, int eventSourceId) { - this(chunkSource, frameAccurateSeeking, downstreamRendererCount, requestedBufferSize, - requestedBufferDurationMs, eventHandler, eventListener, eventSourceId, + this(chunkSource, loadControl, bufferSizeContribution, frameAccurateSeeking, + downstreamRendererCount, eventHandler, eventListener, eventSourceId, DEFAULT_MIN_LOADABLE_RETRY_COUNT); } - public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking, - int downstreamRendererCount, int requestedBufferSize, long requestedBufferDurationMs, - Handler eventHandler, EventListener eventListener, - int eventSourceId, int minLoadableRetryCount) { + public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl, + int bufferSizeContribution, boolean frameAccurateSeeking, int downstreamRendererCount, + Handler eventHandler, EventListener eventListener, int eventSourceId, + int minLoadableRetryCount) { this.chunkSource = chunkSource; + this.loadControl = loadControl; + this.bufferSizeContribution = bufferSizeContribution; this.frameAccurateSeeking = frameAccurateSeeking; this.remainingReleaseCount = downstreamRendererCount; - this.requestedBufferSize = requestedBufferSize; - this.requestedBufferDurationUs = requestedBufferDurationMs * 1000; this.minLoadableRetryCount = minLoadableRetryCount; this.eventHandler = eventHandler; this.eventListener = eventListener; this.eventSourceId = eventSourceId; this.pendingResetPositionUs = NO_RESET_PENDING; extractors = new LinkedList<>(); - allocator = new DefaultAllocator(BUFFER_FRAGMENT_LENGTH); } @Override @@ -150,6 +147,10 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { if (loader == null) { loader = new Loader("Loader:HLS"); } + if (!loadControlRegistered) { + loadControl.register(this, bufferSizeContribution); + loadControlRegistered = true; + } if (!loader.isLoading()) { // 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 @@ -182,6 +183,10 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { trackEnabledStates[track] = true; downstreamMediaFormats[track] = null; downstreamFormat = null; + if (!loadControlRegistered) { + loadControl.register(this, bufferSizeContribution); + loadControlRegistered = true; + } if (enabledTrackCount == 1) { seekToUs(positionUs); } @@ -194,12 +199,16 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { enabledTrackCount--; trackEnabledStates[track] = false; pendingDiscontinuities[track] = false; + if (loadControlRegistered) { + loadControl.unregister(this); + loadControlRegistered = false; + } if (enabledTrackCount == 0) { if (loader.isLoading()) { loader.cancelLoading(); } else { clearState(); - allocator.trim(0); + loadControl.trimAllocator(); } } } @@ -357,7 +366,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { maybeStartLoading(); } else { clearState(); - allocator.trim(0); + loadControl.trimAllocator(); } } @@ -368,7 +377,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { restartFrom(pendingResetPositionUs); } else { clearState(); - allocator.trim(0); + loadControl.trimAllocator(); } } @@ -464,13 +473,23 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { } 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; } + long now = SystemClock.elapsedRealtime(); + long nextLoadPositionUs = getNextLoadPositionUs(); 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) { - long elapsedMillis = SystemClock.elapsedRealtime() - currentLoadableExceptionTimestamp; + long elapsedMillis = now - currentLoadableExceptionTimestamp; if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) { currentLoadableException = null; loader.startLoading(currentLoadable, this); @@ -478,20 +497,17 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { return; } - if (previousTsLoadable != null - && (previousTsLoadable.endTimeUs - downstreamPositionUs >= requestedBufferDurationUs - || allocator.getTotalBytesAllocated() >= requestedBufferSize)) { - // We already have the target amount of data or time buffered. + if (loader.isLoading() || !nextLoader) { return; } - Chunk nextLoadable = chunkSource.getChunkOperation(previousTsLoadable, - pendingResetPositionUs, downstreamPositionUs); + Chunk nextLoadable = chunkSource.getChunkOperation(previousTsLoadable, pendingResetPositionUs, + downstreamPositionUs); if (nextLoadable == null) { return; } - currentLoadStartTimeMs = SystemClock.elapsedRealtime(); + currentLoadStartTimeMs = now; currentLoadable = nextLoadable; if (isTsChunk(currentLoadable)) { TsChunk tsChunk = (TsChunk) currentLoadable; @@ -500,7 +516,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { } HlsExtractorWrapper extractorWrapper = tsChunk.extractorWrapper; if (extractors.isEmpty() || extractors.getLast() != extractorWrapper) { - extractorWrapper.init(allocator); + extractorWrapper.init(loadControl.getAllocator()); extractors.addLast(extractorWrapper); } 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); } + /** + * 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) { return chunk instanceof TsChunk; }