Commit 1cbc0fc6 by aquilescanta Committed by Oliver Woodman

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
parent 8765b198
...@@ -51,9 +51,9 @@ public final class ChunkedTrackBlacklistUtil { ...@@ -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 { ...@@ -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) {
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 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) { if (e instanceof InvalidResponseCodeException) {
InvalidResponseCodeException responseCodeException = (InvalidResponseCodeException) e; int responseCode = ((InvalidResponseCodeException) e).responseCode;
int responseCode = responseCodeException.responseCode; return responseCode == 404 || responseCode == 410;
if (responseCode == 404 || responseCode == 410) {
boolean blacklisted = trackSelection.blacklist(trackSelectionIndex, blacklistDurationMs);
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 false; return false;
} }
......
...@@ -278,9 +278,10 @@ import java.util.Locale; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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, ...@@ -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; ...@@ -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 { ...@@ -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 { ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment