mirror of
https://github.com/androidx/media.git
synced 2025-05-03 21:57:46 +08:00
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:
parent
ea29c71d94
commit
b806109cfd
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user