mirror of
https://github.com/androidx/media.git
synced 2025-05-11 17:49:52 +08:00
Parameterize load error handling in HLS
Issue:#2844 Issue:#2981 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=204718939
This commit is contained in:
parent
e2059eabe1
commit
e247a08a36
@ -47,6 +47,8 @@
|
||||
* HLS:
|
||||
* Pass HTTP response headers to `HlsExtractorFactory.createExtractor`.
|
||||
* Add support for EXT-X-INDEPENDENT-SEGMENTS in the master playlist.
|
||||
* Support load error handling customization
|
||||
([#2981](https://github.com/google/ExoPlayer/issues/2981)).
|
||||
* DRM:
|
||||
* Allow DrmInitData to carry a license server URL
|
||||
([#3393](https://github.com/google/ExoPlayer/issues/3393)).
|
||||
|
@ -179,6 +179,11 @@ public final class Loader implements LoaderErrorThrower {
|
||||
this.type = type;
|
||||
this.retryDelayMillis = retryDelayMillis;
|
||||
}
|
||||
|
||||
/** Returns whether this is a retry action. */
|
||||
public boolean isRetry() {
|
||||
return type == ACTION_TYPE_RETRY || type == ACTION_TYPE_RETRY_AND_RESET_ERROR_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
private final ExecutorService downloadExecutorService;
|
||||
|
@ -397,17 +397,17 @@ import java.util.List;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the {@link HlsSampleStreamWrapper} encounters an error loading a chunk obtained
|
||||
* from this source.
|
||||
* Attempts to blacklist the track associated with the given chunk. Blacklisting will fail if the
|
||||
* track is the only non-blacklisted track in the selection.
|
||||
*
|
||||
* @param chunk The chunk whose load encountered the error.
|
||||
* @param cancelable Whether the load can be canceled.
|
||||
* @param error The error.
|
||||
* @return Whether the load should be canceled.
|
||||
* @param chunk The chunk whose load caused the blacklisting attempt.
|
||||
* @param blacklistDurationMs The number of milliseconds for which the track selection should be
|
||||
* blacklisted.
|
||||
* @return Whether the blacklisting succeeded.
|
||||
*/
|
||||
public boolean onChunkLoadError(Chunk chunk, boolean cancelable, IOException error) {
|
||||
return cancelable && ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection,
|
||||
trackSelection.indexOf(trackGroup.indexOf(chunk.trackFormat)), error);
|
||||
public boolean maybeBlacklistTrack(Chunk chunk, long blacklistDurationMs) {
|
||||
return trackSelection.blacklist(
|
||||
trackSelection.indexOf(trackGroup.indexOf(chunk.trackFormat)), blacklistDurationMs);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,6 +19,7 @@ import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.SeekParameters;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
|
||||
@ -26,12 +27,14 @@ import com.google.android.exoplayer2.source.SampleStream;
|
||||
import com.google.android.exoplayer2.source.SequenceableLoader;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.source.chunk.Chunk;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
@ -53,6 +56,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||
private final HlsPlaylistTracker playlistTracker;
|
||||
private final HlsDataSourceFactory dataSourceFactory;
|
||||
private final @Nullable TransferListener<? super DataSource> mediaTransferListener;
|
||||
private final LoadErrorHandlingPolicy<Chunk> chunkLoadErrorHandlingPolicy;
|
||||
private final int minLoadableRetryCount;
|
||||
private final EventDispatcher eventDispatcher;
|
||||
private final Allocator allocator;
|
||||
@ -69,11 +73,29 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||
private SequenceableLoader compositeSequenceableLoader;
|
||||
private boolean notifiedReadingStarted;
|
||||
|
||||
/**
|
||||
* Creates an HLS media period.
|
||||
*
|
||||
* @param extractorFactory An {@link HlsExtractorFactory} for {@link Extractor}s for the segments.
|
||||
* @param playlistTracker A tracker for HLS playlists.
|
||||
* @param dataSourceFactory An {@link HlsDataSourceFactory} for {@link DataSource}s for segments
|
||||
* and keys.
|
||||
* @param mediaTransferListener The transfer listener to inform of any media data transfers. May
|
||||
* be null if no listener is available.
|
||||
* @param chunkLoadErrorHandlingPolicy A {@link LoadErrorHandlingPolicy} for chunk loads.
|
||||
* @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs.
|
||||
* @param eventDispatcher A dispatcher to notify of events.
|
||||
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
|
||||
* @param compositeSequenceableLoaderFactory A factory to create composite {@link
|
||||
* SequenceableLoader}s for when this media source loads data from multiple streams.
|
||||
* @param allowChunklessPreparation Whether chunkless preparation is allowed.
|
||||
*/
|
||||
public HlsMediaPeriod(
|
||||
HlsExtractorFactory extractorFactory,
|
||||
HlsPlaylistTracker playlistTracker,
|
||||
HlsDataSourceFactory dataSourceFactory,
|
||||
@Nullable TransferListener<? super DataSource> mediaTransferListener,
|
||||
LoadErrorHandlingPolicy<Chunk> chunkLoadErrorHandlingPolicy,
|
||||
int minLoadableRetryCount,
|
||||
EventDispatcher eventDispatcher,
|
||||
Allocator allocator,
|
||||
@ -83,6 +105,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||
this.playlistTracker = playlistTracker;
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.mediaTransferListener = mediaTransferListener;
|
||||
this.chunkLoadErrorHandlingPolicy = chunkLoadErrorHandlingPolicy;
|
||||
this.minLoadableRetryCount = minLoadableRetryCount;
|
||||
this.eventDispatcher = eventDispatcher;
|
||||
this.allocator = allocator;
|
||||
@ -506,8 +529,16 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||
mediaTransferListener,
|
||||
timestampAdjusterProvider,
|
||||
muxedCaptionFormats);
|
||||
return new HlsSampleStreamWrapper(trackType, this, defaultChunkSource, allocator, positionUs,
|
||||
muxedAudioFormat, minLoadableRetryCount, eventDispatcher);
|
||||
return new HlsSampleStreamWrapper(
|
||||
trackType,
|
||||
/* callback= */ this,
|
||||
defaultChunkSource,
|
||||
allocator,
|
||||
positionUs,
|
||||
muxedAudioFormat,
|
||||
chunkLoadErrorHandlingPolicy,
|
||||
minLoadableRetryCount,
|
||||
eventDispatcher);
|
||||
}
|
||||
|
||||
private static Format deriveVideoFormat(Format variantFormat) {
|
||||
|
@ -32,6 +32,7 @@ import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispat
|
||||
import com.google.android.exoplayer2.source.SequenceableLoader;
|
||||
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
||||
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
|
||||
import com.google.android.exoplayer2.source.chunk.Chunk;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.DefaultHlsPlaylistTracker;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist;
|
||||
@ -39,6 +40,7 @@ import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||
import com.google.android.exoplayer2.upstream.ParsingLoadable;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
@ -62,6 +64,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
private @Nullable ParsingLoadable.Parser<HlsPlaylist> playlistParser;
|
||||
private @Nullable HlsPlaylistTracker playlistTracker;
|
||||
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
||||
private LoadErrorHandlingPolicy<Chunk> chunkLoadErrorHandlingPolicy;
|
||||
private int minLoadableRetryCount;
|
||||
private boolean allowChunklessPreparation;
|
||||
private boolean isCreateCalled;
|
||||
@ -87,6 +90,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
public Factory(HlsDataSourceFactory hlsDataSourceFactory) {
|
||||
this.hlsDataSourceFactory = Assertions.checkNotNull(hlsDataSourceFactory);
|
||||
extractorFactory = HlsExtractorFactory.DEFAULT;
|
||||
chunkLoadErrorHandlingPolicy = LoadErrorHandlingPolicy.getDefault();
|
||||
minLoadableRetryCount = DEFAULT_MIN_LOADABLE_RETRY_COUNT;
|
||||
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
|
||||
}
|
||||
@ -121,6 +125,21 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link LoadErrorHandlingPolicy} for chunk loads. The default value is {@link
|
||||
* LoadErrorHandlingPolicy#DEFAULT}.
|
||||
*
|
||||
* @param chunkLoadErrorHandlingPolicy A {@link LoadErrorHandlingPolicy} for chunk loads.
|
||||
* @return This factory, for convenience.
|
||||
* @throws IllegalStateException If one of the {@code create} methods has already been called.
|
||||
*/
|
||||
public Factory setChunkLoadErrorHandlingPolicy(
|
||||
LoadErrorHandlingPolicy<Chunk> chunkLoadErrorHandlingPolicy) {
|
||||
Assertions.checkState(!isCreateCalled);
|
||||
this.chunkLoadErrorHandlingPolicy = chunkLoadErrorHandlingPolicy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number of times to retry if a loading error occurs. The default value is
|
||||
* {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT}.
|
||||
@ -215,6 +234,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
playlistTracker =
|
||||
new DefaultHlsPlaylistTracker(
|
||||
hlsDataSourceFactory,
|
||||
LoadErrorHandlingPolicy.getDefault(),
|
||||
minLoadableRetryCount,
|
||||
playlistParser != null ? playlistParser : new HlsPlaylistParser());
|
||||
}
|
||||
@ -223,6 +243,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
hlsDataSourceFactory,
|
||||
extractorFactory,
|
||||
compositeSequenceableLoaderFactory,
|
||||
chunkLoadErrorHandlingPolicy,
|
||||
minLoadableRetryCount,
|
||||
playlistTracker,
|
||||
allowChunklessPreparation,
|
||||
@ -260,6 +281,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
private final Uri manifestUri;
|
||||
private final HlsDataSourceFactory dataSourceFactory;
|
||||
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
||||
private final LoadErrorHandlingPolicy<Chunk> chunkLoadErrorHandlingPolicy;
|
||||
private final int minLoadableRetryCount;
|
||||
private final boolean allowChunklessPreparation;
|
||||
private final HlsPlaylistTracker playlistTracker;
|
||||
@ -341,8 +363,13 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
dataSourceFactory,
|
||||
extractorFactory,
|
||||
new DefaultCompositeSequenceableLoaderFactory(),
|
||||
LoadErrorHandlingPolicy.getDefault(),
|
||||
minLoadableRetryCount,
|
||||
new DefaultHlsPlaylistTracker(dataSourceFactory, minLoadableRetryCount, playlistParser),
|
||||
new DefaultHlsPlaylistTracker(
|
||||
dataSourceFactory,
|
||||
LoadErrorHandlingPolicy.getDefault(),
|
||||
minLoadableRetryCount,
|
||||
playlistParser),
|
||||
/* allowChunklessPreparation= */ false,
|
||||
/* tag= */ null);
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
@ -355,6 +382,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
HlsDataSourceFactory dataSourceFactory,
|
||||
HlsExtractorFactory extractorFactory,
|
||||
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
|
||||
LoadErrorHandlingPolicy<Chunk> chunkLoadErrorHandlingPolicy,
|
||||
int minLoadableRetryCount,
|
||||
HlsPlaylistTracker playlistTracker,
|
||||
boolean allowChunklessPreparation,
|
||||
@ -363,6 +391,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.extractorFactory = extractorFactory;
|
||||
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
|
||||
this.chunkLoadErrorHandlingPolicy = chunkLoadErrorHandlingPolicy;
|
||||
this.minLoadableRetryCount = minLoadableRetryCount;
|
||||
this.playlistTracker = playlistTracker;
|
||||
this.allowChunklessPreparation = allowChunklessPreparation;
|
||||
@ -393,6 +422,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
playlistTracker,
|
||||
dataSourceFactory,
|
||||
mediaTransferListener,
|
||||
chunkLoadErrorHandlingPolicy,
|
||||
minLoadableRetryCount,
|
||||
eventDispatcher,
|
||||
allocator,
|
||||
|
@ -21,7 +21,6 @@ import android.util.Log;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.FormatHolder;
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||
@ -39,6 +38,7 @@ import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||
import com.google.android.exoplayer2.upstream.Loader;
|
||||
import com.google.android.exoplayer2.upstream.Loader.LoadErrorAction;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
@ -97,6 +97,7 @@ import java.util.List;
|
||||
private final HlsChunkSource chunkSource;
|
||||
private final Allocator allocator;
|
||||
private final Format muxedAudioFormat;
|
||||
private final LoadErrorHandlingPolicy<Chunk> chunkLoadErrorHandlingPolicy;
|
||||
private final int minLoadableRetryCount;
|
||||
private final Loader loader;
|
||||
private final EventDispatcher eventDispatcher;
|
||||
@ -149,18 +150,27 @@ import java.util.List;
|
||||
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
|
||||
* @param positionUs The position from which to start loading media.
|
||||
* @param muxedAudioFormat Optional muxed audio {@link Format} as defined by the master playlist.
|
||||
* @param chunkLoadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy} for chunk loads.
|
||||
* @param minLoadableRetryCount The minimum number of times that the source should retry a load
|
||||
* before propagating an error.
|
||||
* @param eventDispatcher A dispatcher to notify of events.
|
||||
*/
|
||||
public HlsSampleStreamWrapper(int trackType, Callback callback, HlsChunkSource chunkSource,
|
||||
Allocator allocator, long positionUs, Format muxedAudioFormat, int minLoadableRetryCount,
|
||||
public HlsSampleStreamWrapper(
|
||||
int trackType,
|
||||
Callback callback,
|
||||
HlsChunkSource chunkSource,
|
||||
Allocator allocator,
|
||||
long positionUs,
|
||||
Format muxedAudioFormat,
|
||||
LoadErrorHandlingPolicy<Chunk> chunkLoadErrorHandlingPolicy,
|
||||
int minLoadableRetryCount,
|
||||
EventDispatcher eventDispatcher) {
|
||||
this.trackType = trackType;
|
||||
this.callback = callback;
|
||||
this.chunkSource = chunkSource;
|
||||
this.allocator = allocator;
|
||||
this.muxedAudioFormat = muxedAudioFormat;
|
||||
this.chunkLoadErrorHandlingPolicy = chunkLoadErrorHandlingPolicy;
|
||||
this.minLoadableRetryCount = minLoadableRetryCount;
|
||||
this.eventDispatcher = eventDispatcher;
|
||||
loader = new Loader("Loader:HlsSampleStreamWrapper");
|
||||
@ -174,20 +184,8 @@ import java.util.List;
|
||||
mediaChunks = new ArrayList<>();
|
||||
readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks);
|
||||
hlsSampleStreams = new ArrayList<>();
|
||||
maybeFinishPrepareRunnable =
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
maybeFinishPrepare();
|
||||
}
|
||||
};
|
||||
onTracksEndedRunnable =
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onTracksEnded();
|
||||
}
|
||||
};
|
||||
maybeFinishPrepareRunnable = this::maybeFinishPrepare;
|
||||
onTracksEndedRunnable = this::onTracksEnded;
|
||||
handler = new Handler();
|
||||
lastSeekPositionUs = positionUs;
|
||||
pendingResetPositionUs = positionUs;
|
||||
@ -644,9 +642,19 @@ import java.util.List;
|
||||
int errorCount) {
|
||||
long bytesLoaded = loadable.bytesLoaded();
|
||||
boolean isMediaChunk = isMediaChunk(loadable);
|
||||
boolean cancelable = !isMediaChunk || bytesLoaded == 0;
|
||||
boolean canceled = false;
|
||||
if (chunkSource.onChunkLoadError(loadable, cancelable, error)) {
|
||||
boolean blacklistSucceeded = false;
|
||||
LoadErrorAction loadErrorAction;
|
||||
|
||||
if (!isMediaChunk || bytesLoaded == 0) {
|
||||
long blacklistDurationMs =
|
||||
chunkLoadErrorHandlingPolicy.getBlacklistDurationMsFor(
|
||||
loadable, loadDurationMs, error, errorCount);
|
||||
if (blacklistDurationMs != C.TIME_UNSET) {
|
||||
blacklistSucceeded = chunkSource.maybeBlacklistTrack(loadable, blacklistDurationMs);
|
||||
}
|
||||
}
|
||||
|
||||
if (blacklistSucceeded) {
|
||||
if (isMediaChunk) {
|
||||
HlsMediaChunk removed = mediaChunks.remove(mediaChunks.size() - 1);
|
||||
Assertions.checkState(removed == loadable);
|
||||
@ -654,8 +662,17 @@ import java.util.List;
|
||||
pendingResetPositionUs = lastSeekPositionUs;
|
||||
}
|
||||
}
|
||||
canceled = true;
|
||||
loadErrorAction = Loader.DONT_RETRY;
|
||||
} else /* did not blacklist */ {
|
||||
long retryDelayMs =
|
||||
chunkLoadErrorHandlingPolicy.getRetryDelayMsFor(
|
||||
loadable, loadDurationMs, error, errorCount);
|
||||
loadErrorAction =
|
||||
retryDelayMs != C.TIME_UNSET
|
||||
? Loader.createRetryAction(/* resetErrorCount= */ false, retryDelayMs)
|
||||
: Loader.DONT_RETRY_FATAL;
|
||||
}
|
||||
|
||||
eventDispatcher.loadError(
|
||||
loadable.dataSpec,
|
||||
loadable.getUri(),
|
||||
@ -670,17 +687,16 @@ import java.util.List;
|
||||
loadDurationMs,
|
||||
loadable.bytesLoaded(),
|
||||
error,
|
||||
canceled);
|
||||
if (canceled) {
|
||||
/* wasCanceled= */ !loadErrorAction.isRetry());
|
||||
|
||||
if (blacklistSucceeded) {
|
||||
if (!prepared) {
|
||||
continueLoading(lastSeekPositionUs);
|
||||
} else {
|
||||
callback.onContinueLoadingRequested(this);
|
||||
}
|
||||
return Loader.DONT_RETRY;
|
||||
} else {
|
||||
return error instanceof ParserException ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
|
||||
}
|
||||
return loadErrorAction;
|
||||
}
|
||||
|
||||
// Called by the consuming thread, but only when there is no loading thread.
|
||||
|
@ -26,6 +26,7 @@ import com.google.android.exoplayer2.source.hls.HlsDataSourceFactory;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
|
||||
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||
import com.google.android.exoplayer2.upstream.Loader;
|
||||
import com.google.android.exoplayer2.upstream.Loader.LoadErrorAction;
|
||||
import com.google.android.exoplayer2.upstream.ParsingLoadable;
|
||||
@ -48,6 +49,8 @@ public final class DefaultHlsPlaylistTracker
|
||||
|
||||
private final HlsDataSourceFactory dataSourceFactory;
|
||||
private final ParsingLoadable.Parser<HlsPlaylist> playlistParser;
|
||||
private final LoadErrorHandlingPolicy<ParsingLoadable<HlsPlaylist>>
|
||||
playlistLoadErrorHandlingPolicy;
|
||||
private final int minRetryCount;
|
||||
private final IdentityHashMap<HlsUrl, MediaPlaylistBundle> playlistBundles;
|
||||
private final List<PlaylistEventListener> listeners;
|
||||
@ -64,6 +67,7 @@ public final class DefaultHlsPlaylistTracker
|
||||
|
||||
/**
|
||||
* @param dataSourceFactory A factory for {@link DataSource} instances.
|
||||
* @param playlistLoadErrorHandlingPolicy The {@link LoadErrorHandlingPolicy} for playlist loads.
|
||||
* @param minRetryCount The minimum number of times loads must be retried before {@link
|
||||
* #maybeThrowPlaylistRefreshError(HlsUrl)} and {@link
|
||||
* #maybeThrowPrimaryPlaylistRefreshError()} propagate any loading errors.
|
||||
@ -71,11 +75,13 @@ public final class DefaultHlsPlaylistTracker
|
||||
*/
|
||||
public DefaultHlsPlaylistTracker(
|
||||
HlsDataSourceFactory dataSourceFactory,
|
||||
LoadErrorHandlingPolicy<ParsingLoadable<HlsPlaylist>> playlistLoadErrorHandlingPolicy,
|
||||
int minRetryCount,
|
||||
ParsingLoadable.Parser<HlsPlaylist> playlistParser) {
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.minRetryCount = minRetryCount;
|
||||
this.playlistParser = playlistParser;
|
||||
this.playlistLoadErrorHandlingPolicy = playlistLoadErrorHandlingPolicy;
|
||||
listeners = new ArrayList<>();
|
||||
playlistBundles = new IdentityHashMap<>();
|
||||
initialStartTimeUs = C.TIME_UNSET;
|
||||
@ -241,7 +247,10 @@ public final class DefaultHlsPlaylistTracker
|
||||
long loadDurationMs,
|
||||
IOException error,
|
||||
int errorCount) {
|
||||
boolean isFatal = error instanceof ParserException;
|
||||
long retryDelayMs =
|
||||
playlistLoadErrorHandlingPolicy.getRetryDelayMsFor(
|
||||
loadable, loadDurationMs, error, errorCount);
|
||||
boolean isFatal = retryDelayMs == C.TIME_UNSET;
|
||||
eventDispatcher.loadError(
|
||||
loadable.dataSpec,
|
||||
loadable.getUri(),
|
||||
@ -251,7 +260,9 @@ public final class DefaultHlsPlaylistTracker
|
||||
loadable.bytesLoaded(),
|
||||
error,
|
||||
isFatal);
|
||||
return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY;
|
||||
return isFatal
|
||||
? Loader.DONT_RETRY_FATAL
|
||||
: Loader.createRetryAction(/* resetErrorCount= */ false, retryDelayMs);
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
@ -501,7 +512,31 @@ public final class DefaultHlsPlaylistTracker
|
||||
long loadDurationMs,
|
||||
IOException error,
|
||||
int errorCount) {
|
||||
boolean isFatal = error instanceof ParserException;
|
||||
LoadErrorAction loadErrorAction;
|
||||
|
||||
long blacklistDurationMs =
|
||||
playlistLoadErrorHandlingPolicy.getBlacklistDurationMsFor(
|
||||
loadable, loadDurationMs, error, errorCount);
|
||||
boolean shouldBlacklist = blacklistDurationMs != C.TIME_UNSET;
|
||||
|
||||
boolean blacklistingFailed =
|
||||
notifyPlaylistError(playlistUrl, shouldBlacklist) || !shouldBlacklist;
|
||||
if (shouldBlacklist) {
|
||||
blacklistingFailed |= blacklistPlaylist();
|
||||
}
|
||||
|
||||
if (blacklistingFailed) {
|
||||
long retryDelay =
|
||||
playlistLoadErrorHandlingPolicy.getRetryDelayMsFor(
|
||||
loadable, loadDurationMs, error, errorCount);
|
||||
loadErrorAction =
|
||||
retryDelay != C.TIME_UNSET
|
||||
? Loader.createRetryAction(false, retryDelay)
|
||||
: Loader.DONT_RETRY_FATAL;
|
||||
} else {
|
||||
loadErrorAction = Loader.DONT_RETRY;
|
||||
}
|
||||
|
||||
eventDispatcher.loadError(
|
||||
loadable.dataSpec,
|
||||
loadable.getUri(),
|
||||
@ -510,17 +545,9 @@ public final class DefaultHlsPlaylistTracker
|
||||
loadDurationMs,
|
||||
loadable.bytesLoaded(),
|
||||
error,
|
||||
isFatal);
|
||||
boolean shouldBlacklist = ChunkedTrackBlacklistUtil.shouldBlacklist(error);
|
||||
boolean shouldRetryIfNotFatal =
|
||||
notifyPlaylistError(playlistUrl, shouldBlacklist) || !shouldBlacklist;
|
||||
if (isFatal) {
|
||||
return Loader.DONT_RETRY_FATAL;
|
||||
}
|
||||
if (shouldBlacklist) {
|
||||
shouldRetryIfNotFatal |= blacklistPlaylist();
|
||||
}
|
||||
return shouldRetryIfNotFatal ? Loader.RETRY : Loader.DONT_RETRY;
|
||||
/* wasCanceled= */ !loadErrorAction.isRetry());
|
||||
|
||||
return loadErrorAction;
|
||||
}
|
||||
|
||||
// Runnable implementation.
|
||||
@ -558,12 +585,14 @@ public final class DefaultHlsPlaylistTracker
|
||||
} else if (!playlistSnapshot.hasEndTag) {
|
||||
if (loadedPlaylist.mediaSequence + loadedPlaylist.segments.size()
|
||||
< playlistSnapshot.mediaSequence) {
|
||||
// TODO: Allow customization of playlist resets handling.
|
||||
// The media sequence jumped backwards. The server has probably reset.
|
||||
playlistError = new PlaylistResetException(playlistUrl.url);
|
||||
notifyPlaylistError(playlistUrl, false);
|
||||
} else if (currentTimeMs - lastSnapshotChangeMs
|
||||
> C.usToMs(playlistSnapshot.targetDurationUs)
|
||||
* PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT) {
|
||||
// TODO: Allow customization of stuck playlists handling.
|
||||
// The playlist seems to be stuck. Blacklist it.
|
||||
playlistError = new PlaylistStuckException(playlistUrl.url);
|
||||
notifyPlaylistError(playlistUrl, true);
|
||||
|
Loading…
x
Reference in New Issue
Block a user