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