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 d87d2aab51..a538d4f606 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 @@ -29,7 +29,6 @@ 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.DefaultBandwidthMeter; -import com.google.android.exoplayer.upstream.DefaultHttpDataSource; import com.google.android.exoplayer.upstream.DefaultUriDataSource; import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback; @@ -46,6 +45,9 @@ 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 final String userAgent; private final String url; private final TextView debugTextView; @@ -65,7 +67,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback playlistFetcher = - new ManifestFetcher(url, new DefaultHttpDataSource(userAgent, null), parser); + new ManifestFetcher(url, new DefaultUriDataSource(userAgent, null), parser); playlistFetcher.singleLoad(player.getMainHandler().getLooper(), this); } @@ -82,8 +84,8 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback variants; @@ -136,8 +124,6 @@ public class HlsChunkSource { private final String baseUri; private final int maxWidth; private final int maxHeight; - private final int targetBufferSize; - private final long targetBufferDurationUs; private final long minBufferDurationToSwitchUpUs; private final long maxBufferDurationToSwitchDownUs; @@ -157,7 +143,6 @@ public class HlsChunkSource { public HlsChunkSource(DataSource dataSource, String playlistUrl, HlsPlaylist playlist, BandwidthMeter bandwidthMeter, int[] variantIndices, int adaptiveMode) { this(dataSource, playlistUrl, playlist, bandwidthMeter, variantIndices, adaptiveMode, - DEFAULT_TARGET_BUFFER_SIZE, DEFAULT_TARGET_BUFFER_DURATION_MS, DEFAULT_MIN_BUFFER_TO_SWITCH_UP_MS, DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS); } @@ -171,10 +156,6 @@ public class HlsChunkSource { * @param adaptiveMode The mode for switching from one variant to another. One of * {@link #ADAPTIVE_MODE_NONE}, {@link #ADAPTIVE_MODE_ABRUPT} and * {@link #ADAPTIVE_MODE_SPLICE}. - * @param targetBufferSize The targeted buffer size in bytes. The buffer will not be filled more - * than one chunk beyond this amount of data. - * @param targetBufferDurationMs The targeted duration of media to buffer ahead of the current - * playback position. The buffer will not be filled more than one chunk beyond this position. * @param minBufferDurationToSwitchUpMs The minimum duration of media that needs to be buffered * for a switch to a higher quality variant to be considered. * @param maxBufferDurationToSwitchDownMs The maximum duration of media that needs to be buffered @@ -182,18 +163,14 @@ public class HlsChunkSource { */ public HlsChunkSource(DataSource dataSource, String playlistUrl, HlsPlaylist playlist, BandwidthMeter bandwidthMeter, int[] variantIndices, int adaptiveMode, - int targetBufferSize, long targetBufferDurationMs, long minBufferDurationToSwitchUpMs, - long maxBufferDurationToSwitchDownMs) { + long minBufferDurationToSwitchUpMs, long maxBufferDurationToSwitchDownMs) { this.dataSource = dataSource; this.bandwidthMeter = bandwidthMeter; this.adaptiveMode = adaptiveMode; - this.targetBufferSize = targetBufferSize; - targetBufferDurationUs = targetBufferDurationMs * 1000; minBufferDurationToSwitchUpUs = minBufferDurationToSwitchUpMs * 1000; maxBufferDurationToSwitchDownUs = maxBufferDurationToSwitchDownMs * 1000; baseUri = playlist.baseUri; playlistParser = new HlsPlaylistParser(); - bufferPool = new DefaultAllocator(256 * 1024); if (playlist.type == HlsPlaylist.TYPE_MEDIA) { variants = Collections.singletonList(new Variant(playlistUrl, 0, null, -1, -1)); @@ -256,13 +233,6 @@ public class HlsChunkSource { */ public Chunk getChunkOperation(TsChunk previousTsChunk, long seekPositionUs, long playbackPositionUs) { - if (previousTsChunk != null && (previousTsChunk.isLastChunk - || previousTsChunk.endTimeUs - playbackPositionUs >= targetBufferDurationUs) - || bufferPool.getTotalBytesAllocated() >= targetBufferSize) { - // We're either finished, or we have the target amount of data or time buffered. - return null; - } - int nextFormatIndex; boolean switchingVariant; boolean switchingVariantSpliced; @@ -364,8 +334,8 @@ public class HlsChunkSource { Extractor extractor = chunkUri.getLastPathSegment().endsWith(AAC_FILE_EXTENSION) ? new AdtsExtractor(startTimeUs) : new TsExtractor(startTimeUs); - extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, bufferPool, - extractor, switchingVariantSpliced); + extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor, + switchingVariantSpliced); } else { extractorWrapper = previousTsChunk.extractorWrapper; } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java index 6e7c9eb556..b189a12bd2 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java @@ -25,7 +25,7 @@ import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.extractor.ExtractorOutput; import com.google.android.exoplayer.extractor.SeekMap; import com.google.android.exoplayer.extractor.TrackOutput; -import com.google.android.exoplayer.upstream.DefaultAllocator; +import com.google.android.exoplayer.upstream.Allocator; import com.google.android.exoplayer.util.Assertions; import android.util.SparseArray; @@ -41,29 +41,38 @@ public final class HlsExtractorWrapper implements ExtractorOutput { public final Format format; public final long startTimeUs; - private final DefaultAllocator allocator; private final Extractor extractor; private final SparseArray sampleQueues; private final boolean shouldSpliceIn; + private Allocator allocator; + private volatile boolean tracksBuilt; // Accessed only by the consuming thread. private boolean prepared; private boolean spliceConfigured; - public HlsExtractorWrapper(int trigger, Format format, long startTimeUs, - DefaultAllocator allocator, Extractor extractor, boolean shouldSpliceIn) { + public HlsExtractorWrapper(int trigger, Format format, long startTimeUs, Extractor extractor, + boolean shouldSpliceIn) { this.trigger = trigger; this.format = format; this.startTimeUs = startTimeUs; - this.allocator = allocator; this.extractor = extractor; this.shouldSpliceIn = shouldSpliceIn; sampleQueues = new SparseArray(); extractor.init(this); } + /** + * Initializes the wrapper for use. + * + * @param allocator An allocator for obtaining allocations into which extracted data is written. + */ + public void init(Allocator allocator) { + this.allocator = allocator; + } + /** * Attempts to configure a splice from this extractor to the next. *

diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java index 92f00ece75..38239448be 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java @@ -25,6 +25,8 @@ import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener; import com.google.android.exoplayer.chunk.Chunk; 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.Loadable; import com.google.android.exoplayer.util.Assertions; @@ -50,12 +52,16 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { */ 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 final HlsChunkSource chunkSource; private final LinkedList extractors; + private final Allocator allocator; private final boolean frameAccurateSeeking; private final int minLoadableRetryCount; + private final int requestedBufferSize; + private final long requestedBufferDurationUs; private final int eventSourceId; private final Handler eventHandler; @@ -87,29 +93,35 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { private long currentLoadStartTimeMs; public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking, - int downstreamRendererCount) { - this(chunkSource, frameAccurateSeeking, downstreamRendererCount, null, null, 0); + int downstreamRendererCount, int requestedBufferSize, long requestedBufferDurationMs) { + this(chunkSource, frameAccurateSeeking, downstreamRendererCount, requestedBufferSize, + requestedBufferDurationMs, null, null, 0); } public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking, - int downstreamRendererCount, Handler eventHandler, EventListener eventListener, - int eventSourceId) { - this(chunkSource, frameAccurateSeeking, downstreamRendererCount, eventHandler, eventListener, - eventSourceId, DEFAULT_MIN_LOADABLE_RETRY_COUNT); + int downstreamRendererCount, int requestedBufferSize, long requestedBufferDurationMs, + Handler eventHandler, EventListener eventListener, int eventSourceId) { + this(chunkSource, frameAccurateSeeking, downstreamRendererCount, requestedBufferSize, + requestedBufferDurationMs, eventHandler, eventListener, eventSourceId, + DEFAULT_MIN_LOADABLE_RETRY_COUNT); } public HlsSampleSource(HlsChunkSource chunkSource, boolean frameAccurateSeeking, - int downstreamRendererCount, Handler eventHandler, EventListener eventListener, + int downstreamRendererCount, int requestedBufferSize, long requestedBufferDurationMs, + Handler eventHandler, EventListener eventListener, int eventSourceId, int minLoadableRetryCount) { this.chunkSource = chunkSource; 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 @@ -179,6 +191,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { loader.cancelLoading(); } else { clearState(); + allocator.trim(0); } } } @@ -342,6 +355,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { restartFrom(pendingResetPositionUs); } else { clearState(); + allocator.trim(0); } } @@ -451,6 +465,13 @@ 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. + return; + } + Chunk nextLoadable = chunkSource.getChunkOperation(previousTsLoadable, pendingResetPositionUs, downstreamPositionUs); if (nextLoadable == null) { @@ -464,8 +485,10 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { if (isPendingReset()) { pendingResetPositionUs = NO_RESET_PENDING; } - if (extractors.isEmpty() || extractors.getLast() != tsChunk.extractorWrapper) { - extractors.addLast(tsChunk.extractorWrapper); + HlsExtractorWrapper extractorWrapper = tsChunk.extractorWrapper; + if (extractors.isEmpty() || extractors.getLast() != extractorWrapper) { + extractorWrapper.init(allocator); + extractors.addLast(extractorWrapper); } notifyLoadStarted(tsChunk.dataSpec.length, tsChunk.type, tsChunk.trigger, tsChunk.format, tsChunk.startTimeUs, tsChunk.endTimeUs);