mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Allow HlsPlaylistTracker to change the primaryHlsUrl
When the primary url is blacklisted (due to a 404, for example) or the selected variant is different from primary url, allow the tracker to change the url. Issue:#87 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=141291435
This commit is contained in:
parent
8765b1981c
commit
1cbc0fc678
8 changed files with 220 additions and 140 deletions
|
|
@ -51,9 +51,9 @@ public final class ChunkedTrackBlacklistUtil {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blacklists {@code trackSelectionIndex} in {@code trackSelection} for
|
* Blacklists {@code trackSelectionIndex} in {@code trackSelection} for
|
||||||
* {@code blacklistDurationMs} if {@code e} is an {@link InvalidResponseCodeException} with
|
* {@code blacklistDurationMs} if calling {@link #shouldBlacklist(Exception)} for {@code e}
|
||||||
* {@link InvalidResponseCodeException#responseCode} equal to 404 or 410. Else does nothing. Note
|
* returns true. Else does nothing. Note that blacklisting will fail if the track is the only
|
||||||
* that blacklisting will fail if the track is the only non-blacklisted track in the selection.
|
* non-blacklisted track in the selection.
|
||||||
*
|
*
|
||||||
* @param trackSelection The track selection.
|
* @param trackSelection The track selection.
|
||||||
* @param trackSelectionIndex The index in the selection to consider blacklisting.
|
* @param trackSelectionIndex The index in the selection to consider blacklisting.
|
||||||
|
|
@ -63,24 +63,33 @@ public final class ChunkedTrackBlacklistUtil {
|
||||||
*/
|
*/
|
||||||
public static boolean maybeBlacklistTrack(TrackSelection trackSelection, int trackSelectionIndex,
|
public static boolean maybeBlacklistTrack(TrackSelection trackSelection, int trackSelectionIndex,
|
||||||
Exception e, long blacklistDurationMs) {
|
Exception e, long blacklistDurationMs) {
|
||||||
if (trackSelection.length() == 1) {
|
if (shouldBlacklist(e)) {
|
||||||
// Blacklisting won't ever work if there's only one track in the selection.
|
boolean blacklisted = trackSelection.blacklist(trackSelectionIndex, blacklistDurationMs);
|
||||||
return false;
|
int responseCode = ((InvalidResponseCodeException) e).responseCode;
|
||||||
}
|
if (blacklisted) {
|
||||||
if (e instanceof InvalidResponseCodeException) {
|
Log.w(TAG, "Blacklisted: duration=" + blacklistDurationMs + ", responseCode="
|
||||||
InvalidResponseCodeException responseCodeException = (InvalidResponseCodeException) e;
|
+ responseCode + ", format=" + trackSelection.getFormat(trackSelectionIndex));
|
||||||
int responseCode = responseCodeException.responseCode;
|
} else {
|
||||||
if (responseCode == 404 || responseCode == 410) {
|
Log.w(TAG, "Blacklisting failed (cannot blacklist last enabled track): responseCode="
|
||||||
boolean blacklisted = trackSelection.blacklist(trackSelectionIndex, blacklistDurationMs);
|
+ responseCode + ", format=" + trackSelection.getFormat(trackSelectionIndex));
|
||||||
if (blacklisted) {
|
|
||||||
Log.w(TAG, "Blacklisted: duration=" + blacklistDurationMs + ", responseCode="
|
|
||||||
+ responseCode + ", format=" + trackSelection.getFormat(trackSelectionIndex));
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "Blacklisting failed (cannot blacklist last enabled track): responseCode="
|
|
||||||
+ responseCode + ", format=" + trackSelection.getFormat(trackSelectionIndex));
|
|
||||||
}
|
|
||||||
return blacklisted;
|
|
||||||
}
|
}
|
||||||
|
return blacklisted;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a loading error is an {@link InvalidResponseCodeException} with
|
||||||
|
* {@link InvalidResponseCodeException#responseCode} equal to 404 or 410.
|
||||||
|
*
|
||||||
|
* @param e The loading error.
|
||||||
|
* @return Wheter the loading error is an {@link InvalidResponseCodeException} with
|
||||||
|
* {@link InvalidResponseCodeException#responseCode} equal to 404 or 410.
|
||||||
|
*/
|
||||||
|
public static boolean shouldBlacklist(Exception e) {
|
||||||
|
if (e instanceof InvalidResponseCodeException) {
|
||||||
|
int responseCode = ((InvalidResponseCodeException) e).responseCode;
|
||||||
|
return responseCode == 404 || responseCode == 410;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -278,9 +278,10 @@ import java.util.Locale;
|
||||||
DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength,
|
DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength,
|
||||||
null);
|
null);
|
||||||
out.chunk = new HlsMediaChunk(dataSource, dataSpec, initDataSpec, variants[newVariantIndex],
|
out.chunk = new HlsMediaChunk(dataSource, dataSpec, initDataSpec, variants[newVariantIndex],
|
||||||
trackSelection.getSelectionReason(), trackSelection.getSelectionData(), startTimeUs,
|
trackSelection.getSelectionReason(), trackSelection.getSelectionData(),
|
||||||
startTimeUs + segment.durationUs, chunkMediaSequence, segment.discontinuitySequenceNumber,
|
startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence,
|
||||||
isTimestampMaster, timestampAdjuster, previous, encryptionKey, encryptionIv);
|
segment.discontinuitySequenceNumber, isTimestampMaster, timestampAdjuster, previous,
|
||||||
|
encryptionKey, encryptionIv);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -317,19 +318,19 @@ import java.util.Locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when an error is encountered while loading a playlist.
|
* Called when a playlist is blacklisted.
|
||||||
*
|
*
|
||||||
* @param url The url that references the playlist whose load encountered the error.
|
* @param url The url that references the blacklisted playlist.
|
||||||
* @param error The error.
|
* @param blacklistMs The amount of milliseconds for which the playlist was blacklisted.
|
||||||
*/
|
*/
|
||||||
public void onPlaylistLoadError(HlsUrl url, IOException error) {
|
public void onPlaylistBlacklisted(HlsUrl url, long blacklistMs) {
|
||||||
int trackGroupIndex = trackGroup.indexOf(url.format);
|
int trackGroupIndex = trackGroup.indexOf(url.format);
|
||||||
if (trackGroupIndex == C.INDEX_UNSET) {
|
if (trackGroupIndex != C.INDEX_UNSET) {
|
||||||
// The url is not handled by this chunk source.
|
int trackSelectionIndex = trackSelection.indexOf(trackGroupIndex);
|
||||||
return;
|
if (trackSelectionIndex != C.INDEX_UNSET) {
|
||||||
|
trackSelection.blacklist(trackSelectionIndex, blacklistMs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection,
|
|
||||||
trackSelection.indexOf(trackGroupIndex), error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private methods.
|
// Private methods.
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ 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.Loader;
|
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -42,7 +41,7 @@ import java.util.List;
|
||||||
* A {@link MediaPeriod} that loads an HLS stream.
|
* A {@link MediaPeriod} that loads an HLS stream.
|
||||||
*/
|
*/
|
||||||
public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper.Callback,
|
public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper.Callback,
|
||||||
HlsPlaylistTracker.PlaylistRefreshCallback {
|
HlsPlaylistTracker.PlaylistEventListener {
|
||||||
|
|
||||||
private final HlsPlaylistTracker playlistTracker;
|
private final HlsPlaylistTracker playlistTracker;
|
||||||
private final DataSource.Factory dataSourceFactory;
|
private final DataSource.Factory dataSourceFactory;
|
||||||
|
|
@ -52,7 +51,6 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||||
private final IdentityHashMap<SampleStream, Integer> streamWrapperIndices;
|
private final IdentityHashMap<SampleStream, Integer> streamWrapperIndices;
|
||||||
private final TimestampAdjusterProvider timestampAdjusterProvider;
|
private final TimestampAdjusterProvider timestampAdjusterProvider;
|
||||||
private final Handler continueLoadingHandler;
|
private final Handler continueLoadingHandler;
|
||||||
private final Loader manifestFetcher;
|
|
||||||
private final long preparePositionUs;
|
private final long preparePositionUs;
|
||||||
|
|
||||||
private Callback callback;
|
private Callback callback;
|
||||||
|
|
@ -74,13 +72,12 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||||
streamWrapperIndices = new IdentityHashMap<>();
|
streamWrapperIndices = new IdentityHashMap<>();
|
||||||
timestampAdjusterProvider = new TimestampAdjusterProvider();
|
timestampAdjusterProvider = new TimestampAdjusterProvider();
|
||||||
continueLoadingHandler = new Handler();
|
continueLoadingHandler = new Handler();
|
||||||
manifestFetcher = new Loader("Loader:ManifestFetcher");
|
|
||||||
preparePositionUs = positionUs;
|
preparePositionUs = positionUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void release() {
|
public void release() {
|
||||||
|
playlistTracker.removeListener(this);
|
||||||
continueLoadingHandler.removeCallbacksAndMessages(null);
|
continueLoadingHandler.removeCallbacksAndMessages(null);
|
||||||
manifestFetcher.release();
|
|
||||||
if (sampleStreamWrappers != null) {
|
if (sampleStreamWrappers != null) {
|
||||||
for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
|
for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
|
||||||
sampleStreamWrapper.release();
|
sampleStreamWrapper.release();
|
||||||
|
|
@ -90,15 +87,14 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepare(Callback callback) {
|
public void prepare(Callback callback) {
|
||||||
|
playlistTracker.addListener(this);
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
buildAndPrepareSampleStreamWrappers();
|
buildAndPrepareSampleStreamWrappers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void maybeThrowPrepareError() throws IOException {
|
public void maybeThrowPrepareError() throws IOException {
|
||||||
if (sampleStreamWrappers == null) {
|
if (sampleStreamWrappers != null) {
|
||||||
manifestFetcher.maybeThrowError();
|
|
||||||
} else {
|
|
||||||
for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
|
for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
|
||||||
sampleStreamWrapper.maybeThrowPrepareError();
|
sampleStreamWrapper.maybeThrowPrepareError();
|
||||||
}
|
}
|
||||||
|
|
@ -255,7 +251,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaylistRefreshRequired(HlsUrl url) {
|
public void onPlaylistRefreshRequired(HlsUrl url) {
|
||||||
playlistTracker.refreshPlaylist(url, this);
|
playlistTracker.refreshPlaylist(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -271,22 +267,15 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaylistChanged() {
|
public void onPlaylistChanged() {
|
||||||
if (trackGroups != null) {
|
continuePreparingOrLoading();
|
||||||
callback.onContinueLoadingRequested(this);
|
|
||||||
} else {
|
|
||||||
// Some of the wrappers were waiting for their media playlist to prepare.
|
|
||||||
for (HlsSampleStreamWrapper wrapper : sampleStreamWrappers) {
|
|
||||||
wrapper.continuePreparing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaylistLoadError(HlsUrl url, IOException error) {
|
public void onPlaylistBlacklisted(HlsUrl url, long blacklistMs) {
|
||||||
for (HlsSampleStreamWrapper sampleStreamWrapper : enabledSampleStreamWrappers) {
|
for (HlsSampleStreamWrapper streamWrapper : sampleStreamWrappers) {
|
||||||
sampleStreamWrapper.onPlaylistLoadError(url, error);
|
streamWrapper.onPlaylistBlacklisted(url, blacklistMs);
|
||||||
}
|
}
|
||||||
callback.onContinueLoadingRequested(this);
|
continuePreparingOrLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
@ -363,6 +352,17 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||||
eventDispatcher);
|
eventDispatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void continuePreparingOrLoading() {
|
||||||
|
if (trackGroups != null) {
|
||||||
|
callback.onContinueLoadingRequested(this);
|
||||||
|
} else {
|
||||||
|
// Some of the wrappers were waiting for their media playlist to prepare.
|
||||||
|
for (HlsSampleStreamWrapper wrapper : sampleStreamWrappers) {
|
||||||
|
wrapper.continuePreparing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean variantHasExplicitCodecWithPrefix(HlsUrl variant, String prefix) {
|
private static boolean variantHasExplicitCodecWithPrefix(HlsUrl variant, String prefix) {
|
||||||
String codecs = variant.format.codecs;
|
String codecs = variant.format.codecs;
|
||||||
if (TextUtils.isEmpty(codecs)) {
|
if (TextUtils.isEmpty(codecs)) {
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ public final class HlsMediaSource implements MediaSource,
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void maybeThrowSourceInfoRefreshError() throws IOException {
|
public void maybeThrowSourceInfoRefreshError() throws IOException {
|
||||||
playlistTracker.maybeThrowPrimaryPlaylistRefreshError();
|
playlistTracker.maybeThrowPlaylistRefreshError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -279,8 +279,8 @@ import java.util.LinkedList;
|
||||||
chunkSource.setIsTimestampMaster(isTimestampMaster);
|
chunkSource.setIsTimestampMaster(isTimestampMaster);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onPlaylistLoadError(HlsUrl url, IOException error) {
|
public void onPlaylistBlacklisted(HlsUrl url, long blacklistMs) {
|
||||||
chunkSource.onPlaylistLoadError(url, error);
|
chunkSource.onPlaylistBlacklisted(url, blacklistMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SampleStream implementation.
|
// SampleStream implementation.
|
||||||
|
|
|
||||||
|
|
@ -68,19 +68,21 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||||
public final long startTimeUs;
|
public final long startTimeUs;
|
||||||
public final int mediaSequence;
|
public final int mediaSequence;
|
||||||
public final int version;
|
public final int version;
|
||||||
public final Segment initializationSegment;
|
public final long targetDurationUs;
|
||||||
public final List<Segment> segments;
|
|
||||||
public final boolean hasEndTag;
|
public final boolean hasEndTag;
|
||||||
public final boolean hasProgramDateTime;
|
public final boolean hasProgramDateTime;
|
||||||
|
public final Segment initializationSegment;
|
||||||
|
public final List<Segment> segments;
|
||||||
public final long durationUs;
|
public final long durationUs;
|
||||||
|
|
||||||
public HlsMediaPlaylist(String baseUri, long startTimeUs, int mediaSequence, int version,
|
public HlsMediaPlaylist(String baseUri, long startTimeUs, int mediaSequence,
|
||||||
boolean hasEndTag, boolean hasProgramDateTime, Segment initializationSegment,
|
int version, long targetDurationUs, boolean hasEndTag, boolean hasProgramDateTime,
|
||||||
List<Segment> segments) {
|
Segment initializationSegment, List<Segment> segments) {
|
||||||
super(baseUri, HlsPlaylist.TYPE_MEDIA);
|
super(baseUri, HlsPlaylist.TYPE_MEDIA);
|
||||||
this.startTimeUs = startTimeUs;
|
this.startTimeUs = startTimeUs;
|
||||||
this.mediaSequence = mediaSequence;
|
this.mediaSequence = mediaSequence;
|
||||||
this.version = version;
|
this.version = version;
|
||||||
|
this.targetDurationUs = targetDurationUs;
|
||||||
this.hasEndTag = hasEndTag;
|
this.hasEndTag = hasEndTag;
|
||||||
this.hasProgramDateTime = hasProgramDateTime;
|
this.hasProgramDateTime = hasProgramDateTime;
|
||||||
this.initializationSegment = initializationSegment;
|
this.initializationSegment = initializationSegment;
|
||||||
|
|
@ -105,8 +107,8 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||||
}
|
}
|
||||||
|
|
||||||
public HlsMediaPlaylist copyWithStartTimeUs(long startTimeUs) {
|
public HlsMediaPlaylist copyWithStartTimeUs(long startTimeUs) {
|
||||||
return new HlsMediaPlaylist(baseUri, startTimeUs, mediaSequence, version, hasEndTag,
|
return new HlsMediaPlaylist(baseUri, startTimeUs, mediaSequence, version, targetDurationUs,
|
||||||
hasProgramDateTime, initializationSegment, segments);
|
hasEndTag, hasProgramDateTime, initializationSegment, segments);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||||
private static final Pattern REGEX_BANDWIDTH = Pattern.compile("BANDWIDTH=(\\d+)\\b");
|
private static final Pattern REGEX_BANDWIDTH = Pattern.compile("BANDWIDTH=(\\d+)\\b");
|
||||||
private static final Pattern REGEX_CODECS = Pattern.compile("CODECS=\"(.+?)\"");
|
private static final Pattern REGEX_CODECS = Pattern.compile("CODECS=\"(.+?)\"");
|
||||||
private static final Pattern REGEX_RESOLUTION = Pattern.compile("RESOLUTION=(\\d+x\\d+)");
|
private static final Pattern REGEX_RESOLUTION = Pattern.compile("RESOLUTION=(\\d+x\\d+)");
|
||||||
|
private static final Pattern REGEX_TARGET_DURATION = Pattern.compile(TAG_TARGET_DURATION
|
||||||
|
+ ":(\\d+)\\b");
|
||||||
private static final Pattern REGEX_VERSION = Pattern.compile(TAG_VERSION + ":(\\d+)\\b");
|
private static final Pattern REGEX_VERSION = Pattern.compile(TAG_VERSION + ":(\\d+)\\b");
|
||||||
private static final Pattern REGEX_MEDIA_SEQUENCE = Pattern.compile(TAG_MEDIA_SEQUENCE
|
private static final Pattern REGEX_MEDIA_SEQUENCE = Pattern.compile(TAG_MEDIA_SEQUENCE
|
||||||
+ ":(\\d+)\\b");
|
+ ":(\\d+)\\b");
|
||||||
|
|
@ -207,6 +209,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||||
throws IOException {
|
throws IOException {
|
||||||
int mediaSequence = 0;
|
int mediaSequence = 0;
|
||||||
int version = 1; // Default version == 1.
|
int version = 1; // Default version == 1.
|
||||||
|
long targetDurationUs = C.TIME_UNSET;
|
||||||
boolean hasEndTag = false;
|
boolean hasEndTag = false;
|
||||||
Segment initializationSegment = null;
|
Segment initializationSegment = null;
|
||||||
List<Segment> segments = new ArrayList<>();
|
List<Segment> segments = new ArrayList<>();
|
||||||
|
|
@ -239,6 +242,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||||
initializationSegment = new Segment(uri, segmentByteRangeOffset, segmentByteRangeLength);
|
initializationSegment = new Segment(uri, segmentByteRangeOffset, segmentByteRangeLength);
|
||||||
segmentByteRangeOffset = 0;
|
segmentByteRangeOffset = 0;
|
||||||
segmentByteRangeLength = C.LENGTH_UNSET;
|
segmentByteRangeLength = C.LENGTH_UNSET;
|
||||||
|
} else if (line.startsWith(TAG_TARGET_DURATION)) {
|
||||||
|
targetDurationUs = parseIntAttr(line, REGEX_TARGET_DURATION) * C.MICROS_PER_SECOND;
|
||||||
} else if (line.startsWith(TAG_MEDIA_SEQUENCE)) {
|
} else if (line.startsWith(TAG_MEDIA_SEQUENCE)) {
|
||||||
mediaSequence = parseIntAttr(line, REGEX_MEDIA_SEQUENCE);
|
mediaSequence = parseIntAttr(line, REGEX_MEDIA_SEQUENCE);
|
||||||
segmentMediaSequence = mediaSequence;
|
segmentMediaSequence = mediaSequence;
|
||||||
|
|
@ -300,8 +305,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||||
hasEndTag = true;
|
hasEndTag = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new HlsMediaPlaylist(baseUri, playlistStartTimeUs, mediaSequence, version, hasEndTag,
|
return new HlsMediaPlaylist(baseUri, playlistStartTimeUs, mediaSequence, version,
|
||||||
playlistStartTimeUs != 0, initializationSegment, segments);
|
targetDurationUs, hasEndTag, playlistStartTimeUs != 0, initializationSegment, segments);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String parseStringAttr(String line, Pattern pattern) throws ParserException {
|
private static String parseStringAttr(String line, Pattern pattern) throws ParserException {
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,11 @@ package com.google.android.exoplayer2.source.hls.playlist;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.SystemClock;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
||||||
|
import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil;
|
||||||
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;
|
||||||
|
|
@ -52,35 +54,37 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the playlist changes.
|
* Called on playlist loading events.
|
||||||
*/
|
*/
|
||||||
public interface PlaylistRefreshCallback {
|
public interface PlaylistEventListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the target playlist changes.
|
* Called a playlist changes.
|
||||||
*/
|
*/
|
||||||
void onPlaylistChanged();
|
void onPlaylistChanged();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called if an error is encountered while loading the target playlist.
|
* Called if an error is encountered while loading a playlist.
|
||||||
*
|
*
|
||||||
* @param url The loaded url that caused the error.
|
* @param url The loaded url that caused the error.
|
||||||
* @param error The loading error.
|
* @param blacklistDurationMs The number of milliseconds for which the playlist has been
|
||||||
|
* blacklisted.
|
||||||
*/
|
*/
|
||||||
void onPlaylistLoadError(HlsUrl url, IOException error);
|
void onPlaylistBlacklisted(HlsUrl url, long blacklistDurationMs);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines the minimum amount of time by which a media playlist segment's start time has to
|
* The minimum number of milliseconds by which a media playlist segment's start time has to drift
|
||||||
* drift from the actual start time of the chunk it refers to for it to be adjusted.
|
* from the actual start time of the chunk it refers to for it to be adjusted.
|
||||||
*/
|
*/
|
||||||
private static final long TIMESTAMP_ADJUSTMENT_THRESHOLD_US = 500000;
|
private static final long TIMESTAMP_ADJUSTMENT_THRESHOLD_US = 500000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Period for refreshing playlists.
|
* The minimum number of milliseconds that a url is kept as primary url, if no
|
||||||
|
* {@link #getPlaylistSnapshot} call is made for that url.
|
||||||
*/
|
*/
|
||||||
private static final long PLAYLIST_REFRESH_PERIOD_MS = 5000;
|
private static final long PRIMARY_URL_KEEPALIVE_MS = 15000;
|
||||||
|
|
||||||
private final Uri initialPlaylistUri;
|
private final Uri initialPlaylistUri;
|
||||||
private final DataSource.Factory dataSourceFactory;
|
private final DataSource.Factory dataSourceFactory;
|
||||||
|
|
@ -89,11 +93,13 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
private final IdentityHashMap<HlsUrl, MediaPlaylistBundle> playlistBundles;
|
private final IdentityHashMap<HlsUrl, MediaPlaylistBundle> playlistBundles;
|
||||||
private final Handler playlistRefreshHandler;
|
private final Handler playlistRefreshHandler;
|
||||||
private final PrimaryPlaylistListener primaryPlaylistListener;
|
private final PrimaryPlaylistListener primaryPlaylistListener;
|
||||||
|
private final List<PlaylistEventListener> listeners;
|
||||||
private final Loader initialPlaylistLoader;
|
private final Loader initialPlaylistLoader;
|
||||||
private final EventDispatcher eventDispatcher;
|
private final EventDispatcher eventDispatcher;
|
||||||
|
|
||||||
private HlsMasterPlaylist masterPlaylist;
|
private HlsMasterPlaylist masterPlaylist;
|
||||||
private HlsUrl primaryHlsUrl;
|
private HlsUrl primaryHlsUrl;
|
||||||
|
private HlsMediaPlaylist primaryUrlSnapshot;
|
||||||
private boolean isLive;
|
private boolean isLive;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -113,12 +119,31 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
this.eventDispatcher = eventDispatcher;
|
this.eventDispatcher = eventDispatcher;
|
||||||
this.minRetryCount = minRetryCount;
|
this.minRetryCount = minRetryCount;
|
||||||
this.primaryPlaylistListener = primaryPlaylistListener;
|
this.primaryPlaylistListener = primaryPlaylistListener;
|
||||||
|
listeners = new ArrayList<>();
|
||||||
initialPlaylistLoader = new Loader("HlsPlaylistTracker:MasterPlaylist");
|
initialPlaylistLoader = new Loader("HlsPlaylistTracker:MasterPlaylist");
|
||||||
playlistParser = new HlsPlaylistParser();
|
playlistParser = new HlsPlaylistParser();
|
||||||
playlistBundles = new IdentityHashMap<>();
|
playlistBundles = new IdentityHashMap<>();
|
||||||
playlistRefreshHandler = new Handler();
|
playlistRefreshHandler = new Handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a listener to receive events from the playlist tracker.
|
||||||
|
*
|
||||||
|
* @param listener The listener.
|
||||||
|
*/
|
||||||
|
public void addListener(PlaylistEventListener listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters a listener.
|
||||||
|
*
|
||||||
|
* @param listener The listener to unregister.
|
||||||
|
*/
|
||||||
|
public void removeListener(PlaylistEventListener listener) {
|
||||||
|
listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts tracking all the playlists related to the provided Uri.
|
* Starts tracking all the playlists related to the provided Uri.
|
||||||
*/
|
*/
|
||||||
|
|
@ -147,7 +172,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
* be null if no snapshot has been loaded yet.
|
* be null if no snapshot has been loaded yet.
|
||||||
*/
|
*/
|
||||||
public HlsMediaPlaylist getPlaylistSnapshot(HlsUrl url) {
|
public HlsMediaPlaylist getPlaylistSnapshot(HlsUrl url) {
|
||||||
return playlistBundles.get(url).latestPlaylistSnapshot;
|
return playlistBundles.get(url).getPlaylistSnapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -163,12 +188,12 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the tracker is having trouble refreshing the primary playlist, this method throws the
|
* If the tracker is having trouble refreshing the primary playlist or loading an irreplaceable
|
||||||
* underlying error. Otherwise, does nothing.
|
* playlist, this method throws the underlying error. Otherwise, does nothing.
|
||||||
*
|
*
|
||||||
* @throws IOException The underlying error.
|
* @throws IOException The underlying error.
|
||||||
*/
|
*/
|
||||||
public void maybeThrowPrimaryPlaylistRefreshError() throws IOException {
|
public void maybeThrowPlaylistRefreshError() throws IOException {
|
||||||
initialPlaylistLoader.maybeThrowError();
|
initialPlaylistLoader.maybeThrowError();
|
||||||
if (primaryHlsUrl != null) {
|
if (primaryHlsUrl != null) {
|
||||||
playlistBundles.get(primaryHlsUrl).mediaPlaylistLoader.maybeThrowError();
|
playlistBundles.get(primaryHlsUrl).mediaPlaylistLoader.maybeThrowError();
|
||||||
|
|
@ -176,16 +201,12 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers a playlist refresh and sets the callback to be called once the playlist referenced by
|
* Triggers a playlist refresh and whitelists it.
|
||||||
* the provided {@link HlsUrl} changes.
|
|
||||||
*
|
*
|
||||||
* @param key The {@link HlsUrl} of the playlist to be refreshed.
|
* @param url The {@link HlsUrl} of the playlist to be refreshed.
|
||||||
* @param callback The callback.
|
|
||||||
*/
|
*/
|
||||||
public void refreshPlaylist(HlsUrl key, PlaylistRefreshCallback callback) {
|
public void refreshPlaylist(HlsUrl url) {
|
||||||
MediaPlaylistBundle bundle = playlistBundles.get(key);
|
playlistBundles.get(url).loadPlaylist();
|
||||||
bundle.setCallback(callback);
|
|
||||||
bundle.loadPlaylist();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -206,6 +227,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
*/
|
*/
|
||||||
public void onChunkLoaded(HlsUrl hlsUrl, int chunkMediaSequence, long adjustedStartTimeUs) {
|
public void onChunkLoaded(HlsUrl hlsUrl, int chunkMediaSequence, long adjustedStartTimeUs) {
|
||||||
playlistBundles.get(hlsUrl).adjustTimestampsOfPlaylist(chunkMediaSequence, adjustedStartTimeUs);
|
playlistBundles.get(hlsUrl).adjustTimestampsOfPlaylist(chunkMediaSequence, adjustedStartTimeUs);
|
||||||
|
maybeSetPrimaryUrl(hlsUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loader.Callback implementation.
|
// Loader.Callback implementation.
|
||||||
|
|
@ -257,11 +279,41 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
|
private boolean maybeSelectNewPrimaryUrl() {
|
||||||
|
List<HlsUrl> variants = masterPlaylist.variants;
|
||||||
|
int variantsSize = variants.size();
|
||||||
|
long currentTimeMs = SystemClock.elapsedRealtime();
|
||||||
|
for (int i = 0; i < variantsSize; i++) {
|
||||||
|
MediaPlaylistBundle bundle = playlistBundles.get(variants.get(i));
|
||||||
|
if (currentTimeMs > bundle.blacklistUntilMs) {
|
||||||
|
primaryHlsUrl = bundle.playlistUrl;
|
||||||
|
bundle.loadPlaylist();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeSetPrimaryUrl(HlsUrl url) {
|
||||||
|
if (!masterPlaylist.variants.contains(url)) {
|
||||||
|
// Only allow variant urls to be chosen as primary.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MediaPlaylistBundle currentPrimaryBundle = playlistBundles.get(primaryHlsUrl);
|
||||||
|
long primarySnapshotAccessAgeMs =
|
||||||
|
currentPrimaryBundle.lastSnapshotAccessTimeMs - SystemClock.elapsedRealtime();
|
||||||
|
if (primarySnapshotAccessAgeMs > PRIMARY_URL_KEEPALIVE_MS) {
|
||||||
|
primaryHlsUrl = url;
|
||||||
|
playlistBundles.get(primaryHlsUrl).loadPlaylist();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void createBundles(List<HlsUrl> urls) {
|
private void createBundles(List<HlsUrl> urls) {
|
||||||
int listSize = urls.size();
|
int listSize = urls.size();
|
||||||
|
long currentTimeMs = SystemClock.elapsedRealtime();
|
||||||
for (int i = 0; i < listSize; i++) {
|
for (int i = 0; i < listSize; i++) {
|
||||||
HlsUrl url = urls.get(i);
|
HlsUrl url = urls.get(i);
|
||||||
MediaPlaylistBundle bundle = new MediaPlaylistBundle(url);
|
MediaPlaylistBundle bundle = new MediaPlaylistBundle(url, currentTimeMs);
|
||||||
playlistBundles.put(urls.get(i), bundle);
|
playlistBundles.put(urls.get(i), bundle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -271,20 +323,30 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
*
|
*
|
||||||
* @param url The url of the playlist.
|
* @param url The url of the playlist.
|
||||||
* @param newSnapshot The new snapshot.
|
* @param newSnapshot The new snapshot.
|
||||||
* @param isFirstSnapshot Whether this is the first snapshot for the given playlist.
|
|
||||||
* @return True if a refresh should be scheduled.
|
* @return True if a refresh should be scheduled.
|
||||||
*/
|
*/
|
||||||
private boolean onPlaylistUpdated(HlsUrl url, HlsMediaPlaylist newSnapshot,
|
private boolean onPlaylistUpdated(HlsUrl url, HlsMediaPlaylist newSnapshot) {
|
||||||
boolean isFirstSnapshot) {
|
|
||||||
if (url == primaryHlsUrl) {
|
if (url == primaryHlsUrl) {
|
||||||
if (isFirstSnapshot) {
|
if (primaryUrlSnapshot == null) {
|
||||||
|
// This is the first primary url snapshot.
|
||||||
isLive = !newSnapshot.hasEndTag;
|
isLive = !newSnapshot.hasEndTag;
|
||||||
}
|
}
|
||||||
|
primaryUrlSnapshot = newSnapshot;
|
||||||
primaryPlaylistListener.onPrimaryPlaylistRefreshed(newSnapshot);
|
primaryPlaylistListener.onPrimaryPlaylistRefreshed(newSnapshot);
|
||||||
// If the primary playlist is not the final one, we should schedule a refresh.
|
|
||||||
return !newSnapshot.hasEndTag;
|
|
||||||
}
|
}
|
||||||
return false;
|
int listenersSize = listeners.size();
|
||||||
|
for (int i = 0; i < listenersSize; i++) {
|
||||||
|
listeners.get(i).onPlaylistChanged();
|
||||||
|
}
|
||||||
|
// If the primary playlist is not the final one, we should schedule a refresh.
|
||||||
|
return url == primaryHlsUrl && !newSnapshot.hasEndTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyPlaylistBlacklisting(HlsUrl url, long blacklistMs) {
|
||||||
|
int listenersSize = listeners.size();
|
||||||
|
for (int i = 0; i < listenersSize; i++) {
|
||||||
|
listeners.get(i).onPlaylistBlacklisted(url, blacklistMs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -299,15 +361,16 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
return oldPlaylist;
|
return oldPlaylist;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HlsMediaPlaylist primaryPlaylistSnapshot =
|
// TODO: Once playlist type support is added, the snapshot's age can be added by using the
|
||||||
playlistBundles.get(primaryHlsUrl).latestPlaylistSnapshot;
|
// target duration.
|
||||||
|
long primarySnapshotStartTimeUs = primaryUrlSnapshot != null
|
||||||
|
? primaryUrlSnapshot.startTimeUs : 0;
|
||||||
if (oldPlaylist == null) {
|
if (oldPlaylist == null) {
|
||||||
if (primaryPlaylistSnapshot == null
|
if (newPlaylist.startTimeUs == primarySnapshotStartTimeUs) {
|
||||||
|| primaryPlaylistSnapshot.startTimeUs == newPlaylist.startTimeUs) {
|
|
||||||
// Playback has just started or is VOD so no adjustment is needed.
|
// Playback has just started or is VOD so no adjustment is needed.
|
||||||
return newPlaylist;
|
return newPlaylist;
|
||||||
} else {
|
} else {
|
||||||
return newPlaylist.copyWithStartTimeUs(primaryPlaylistSnapshot.startTimeUs);
|
return newPlaylist.copyWithStartTimeUs(primarySnapshotStartTimeUs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<Segment> oldSegments = oldPlaylist.segments;
|
List<Segment> oldSegments = oldPlaylist.segments;
|
||||||
|
|
@ -324,7 +387,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
return newPlaylist.copyWithStartTimeUs(adjustedNewPlaylistStartTimeUs);
|
return newPlaylist.copyWithStartTimeUs(adjustedNewPlaylistStartTimeUs);
|
||||||
}
|
}
|
||||||
// No segments overlap, we assume the new playlist start coincides with the primary playlist.
|
// No segments overlap, we assume the new playlist start coincides with the primary playlist.
|
||||||
return newPlaylist.copyWithStartTimeUs(primaryPlaylistSnapshot.startTimeUs);
|
return newPlaylist.copyWithStartTimeUs(primarySnapshotStartTimeUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -337,50 +400,49 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
private final Loader mediaPlaylistLoader;
|
private final Loader mediaPlaylistLoader;
|
||||||
private final ParsingLoadable<HlsPlaylist> mediaPlaylistLoadable;
|
private final ParsingLoadable<HlsPlaylist> mediaPlaylistLoadable;
|
||||||
|
|
||||||
private PlaylistRefreshCallback callback;
|
private HlsMediaPlaylist playlistSnapshot;
|
||||||
private HlsMediaPlaylist latestPlaylistSnapshot;
|
private long lastSnapshotAccessTimeMs;
|
||||||
|
private long blacklistUntilMs;
|
||||||
|
|
||||||
public MediaPlaylistBundle(HlsUrl playlistUrl) {
|
public MediaPlaylistBundle(HlsUrl playlistUrl, long initialLastSnapshotAccessTimeMs) {
|
||||||
this(playlistUrl, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MediaPlaylistBundle(HlsUrl playlistUrl, HlsMediaPlaylist initialSnapshot) {
|
|
||||||
this.playlistUrl = playlistUrl;
|
this.playlistUrl = playlistUrl;
|
||||||
latestPlaylistSnapshot = initialSnapshot;
|
lastSnapshotAccessTimeMs = initialLastSnapshotAccessTimeMs;
|
||||||
mediaPlaylistLoader = new Loader("HlsPlaylistTracker:MediaPlaylist");
|
mediaPlaylistLoader = new Loader("HlsPlaylistTracker:MediaPlaylist");
|
||||||
mediaPlaylistLoadable = new ParsingLoadable<>(dataSourceFactory.createDataSource(),
|
mediaPlaylistLoadable = new ParsingLoadable<>(dataSourceFactory.createDataSource(),
|
||||||
UriUtil.resolveToUri(masterPlaylist.baseUri, playlistUrl.url), C.DATA_TYPE_MANIFEST,
|
UriUtil.resolveToUri(masterPlaylist.baseUri, playlistUrl.url), C.DATA_TYPE_MANIFEST,
|
||||||
playlistParser);
|
playlistParser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HlsMediaPlaylist getPlaylistSnapshot() {
|
||||||
|
lastSnapshotAccessTimeMs = SystemClock.elapsedRealtime();
|
||||||
|
return playlistSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
public void release() {
|
public void release() {
|
||||||
mediaPlaylistLoader.release();
|
mediaPlaylistLoader.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadPlaylist() {
|
public void loadPlaylist() {
|
||||||
|
blacklistUntilMs = 0;
|
||||||
if (!mediaPlaylistLoader.isLoading()) {
|
if (!mediaPlaylistLoader.isLoading()) {
|
||||||
mediaPlaylistLoader.startLoading(mediaPlaylistLoadable, this, minRetryCount);
|
mediaPlaylistLoader.startLoading(mediaPlaylistLoadable, this, minRetryCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCallback(PlaylistRefreshCallback callback) {
|
public void adjustTimestampsOfPlaylist(int chunkMediaSequence, long adjustedChunkStartTimeUs) {
|
||||||
this.callback = callback;
|
int indexOfChunk = chunkMediaSequence - playlistSnapshot.mediaSequence;
|
||||||
}
|
if (playlistSnapshot.hasProgramDateTime || indexOfChunk < 0) {
|
||||||
|
|
||||||
public void adjustTimestampsOfPlaylist(int chunkMediaSequence, long adjustedStartTimeUs) {
|
|
||||||
int indexOfChunk = chunkMediaSequence - latestPlaylistSnapshot.mediaSequence;
|
|
||||||
if (latestPlaylistSnapshot.hasProgramDateTime || indexOfChunk < 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Segment actualSegment = latestPlaylistSnapshot.segments.get(indexOfChunk);
|
Segment actualSegment = playlistSnapshot.segments.get(indexOfChunk);
|
||||||
long segmentAbsoluteStartTimeUs =
|
long segmentAbsoluteStartTimeUs =
|
||||||
actualSegment.relativeStartTimeUs + latestPlaylistSnapshot.startTimeUs;
|
actualSegment.relativeStartTimeUs + playlistSnapshot.startTimeUs;
|
||||||
long timestampDriftUs = Math.abs(segmentAbsoluteStartTimeUs - adjustedStartTimeUs);
|
long timestampDriftUs = Math.abs(segmentAbsoluteStartTimeUs - adjustedChunkStartTimeUs);
|
||||||
if (timestampDriftUs < TIMESTAMP_ADJUSTMENT_THRESHOLD_US) {
|
if (timestampDriftUs < TIMESTAMP_ADJUSTMENT_THRESHOLD_US) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
latestPlaylistSnapshot = latestPlaylistSnapshot.copyWithStartTimeUs(
|
playlistSnapshot = playlistSnapshot.copyWithStartTimeUs(
|
||||||
adjustedStartTimeUs - actualSegment.relativeStartTimeUs);
|
adjustedChunkStartTimeUs - actualSegment.relativeStartTimeUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loader.Callback implementation.
|
// Loader.Callback implementation.
|
||||||
|
|
@ -403,18 +465,21 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
@Override
|
@Override
|
||||||
public int onLoadError(ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs,
|
public int onLoadError(ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs,
|
||||||
long loadDurationMs, IOException error) {
|
long loadDurationMs, IOException error) {
|
||||||
// TODO: Change primary playlist if this is the primary playlist bundle.
|
|
||||||
boolean isFatal = error instanceof ParserException;
|
boolean isFatal = error instanceof ParserException;
|
||||||
eventDispatcher.loadError(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs,
|
eventDispatcher.loadError(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs,
|
||||||
loadDurationMs, loadable.bytesLoaded(), error, isFatal);
|
loadDurationMs, loadable.bytesLoaded(), error, isFatal);
|
||||||
if (callback != null) {
|
|
||||||
callback.onPlaylistLoadError(playlistUrl, error);
|
|
||||||
}
|
|
||||||
if (isFatal) {
|
if (isFatal) {
|
||||||
return Loader.DONT_RETRY_FATAL;
|
return Loader.DONT_RETRY_FATAL;
|
||||||
} else {
|
|
||||||
return primaryHlsUrl == playlistUrl ? Loader.RETRY : Loader.DONT_RETRY;
|
|
||||||
}
|
}
|
||||||
|
boolean shouldRetry = true;
|
||||||
|
if (ChunkedTrackBlacklistUtil.shouldBlacklist(error)) {
|
||||||
|
blacklistUntilMs =
|
||||||
|
SystemClock.elapsedRealtime() + ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS;
|
||||||
|
notifyPlaylistBlacklisting(playlistUrl,
|
||||||
|
ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS);
|
||||||
|
shouldRetry = primaryHlsUrl == playlistUrl && !maybeSelectNewPrimaryUrl();
|
||||||
|
}
|
||||||
|
return shouldRetry ? Loader.RETRY : Loader.DONT_RETRY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Runnable implementation.
|
// Runnable implementation.
|
||||||
|
|
@ -427,21 +492,19 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
private void processLoadedPlaylist(HlsMediaPlaylist loadedMediaPlaylist) {
|
private void processLoadedPlaylist(HlsMediaPlaylist loadedMediaPlaylist) {
|
||||||
HlsMediaPlaylist oldPlaylist = latestPlaylistSnapshot;
|
HlsMediaPlaylist oldPlaylist = playlistSnapshot;
|
||||||
latestPlaylistSnapshot = adjustPlaylistTimestamps(oldPlaylist, loadedMediaPlaylist);
|
playlistSnapshot = adjustPlaylistTimestamps(oldPlaylist, loadedMediaPlaylist);
|
||||||
boolean shouldScheduleRefresh;
|
long refreshDelayUs = C.TIME_UNSET;
|
||||||
if (oldPlaylist != latestPlaylistSnapshot) {
|
if (oldPlaylist != playlistSnapshot) {
|
||||||
if (callback != null) {
|
if (onPlaylistUpdated(playlistUrl, playlistSnapshot)) {
|
||||||
callback.onPlaylistChanged();
|
refreshDelayUs = playlistSnapshot.targetDurationUs;
|
||||||
callback = null;
|
|
||||||
}
|
}
|
||||||
shouldScheduleRefresh = onPlaylistUpdated(playlistUrl, latestPlaylistSnapshot,
|
} else if (!loadedMediaPlaylist.hasEndTag) {
|
||||||
oldPlaylist == null);
|
refreshDelayUs = playlistSnapshot.targetDurationUs / 2;
|
||||||
} else {
|
|
||||||
shouldScheduleRefresh = !loadedMediaPlaylist.hasEndTag;
|
|
||||||
}
|
}
|
||||||
if (shouldScheduleRefresh) {
|
if (refreshDelayUs != C.TIME_UNSET) {
|
||||||
playlistRefreshHandler.postDelayed(this, PLAYLIST_REFRESH_PERIOD_MS);
|
// See HLS spec v20, section 6.3.4 for more information on media playlist refreshing.
|
||||||
|
playlistRefreshHandler.postDelayed(this, C.usToMs(refreshDelayUs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue