Make HlsChunkSource sane again.

There was a mess where we were indexing into both a list of variants
and a (differently ordered and possibly of differing length) list of
formats. This sanitises everything.
This commit is contained in:
Oliver Woodman 2015-06-18 18:01:47 +01:00
parent e0316d1c16
commit 51a8635ba2
2 changed files with 165 additions and 122 deletions

View File

@ -5,9 +5,11 @@
* Support for extracting Matroska streams (implemented by WebmExtractor). * Support for extracting Matroska streams (implemented by WebmExtractor).
* Support for tx3g captions in MP4 streams. * Support for tx3g captions in MP4 streams.
* Support for H.265 in MPEG-TS streams on supported devices. * Support for H.265 in MPEG-TS streams on supported devices.
* HLS: Improved robustness against missing chunks and variants.
* TTML: Improved handling of whitespace.
* DASH: Support Mpd.Location element.
* Add option to TsExtractor to allow non-IDR keyframes. * Add option to TsExtractor to allow non-IDR keyframes.
* Added MulticastDataSource for connecting to multicast streams. * Added MulticastDataSource for connecting to multicast streams.
* DASH: Support Mpd.Location element.
* (WorkInProgress) - First steps to supporting seeking in DASH DVR window. * (WorkInProgress) - First steps to supporting seeking in DASH DVR window.
* (WorkInProgress) - First steps to supporting styled + positioned subtitles. * (WorkInProgress) - First steps to supporting styled + positioned subtitles.
* Misc bug fixes. * Misc bug fixes.

View File

@ -43,7 +43,7 @@ import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -117,8 +117,6 @@ public class HlsChunkSource {
private final DataSource dataSource; private final DataSource dataSource;
private final HlsPlaylistParser playlistParser; private final HlsPlaylistParser playlistParser;
private final List<Variant> variants;
private final Format[] enabledFormats;
private final BandwidthMeter bandwidthMeter; private final BandwidthMeter bandwidthMeter;
private final int adaptiveMode; private final int adaptiveMode;
private final String baseUri; private final String baseUri;
@ -128,14 +126,21 @@ public class HlsChunkSource {
private final long maxBufferDurationToSwitchDownUs; private final long maxBufferDurationToSwitchDownUs;
private final AudioCapabilities audioCapabilities; private final AudioCapabilities audioCapabilities;
/* package */ byte[] scratchSpace; // A list of variants considered during playback, ordered by decreasing bandwidth. The following
/* package */ final HlsMediaPlaylist[] mediaPlaylists; // three arrays are of the same length and are ordered in the same way (i.e. variantPlaylists[i],
/* package */ final long[] mediaPlaylistBlacklistTimesMs; // variantLastPlaylistLoadTimesMs[i] and variantBlacklistTimes[i] all correspond to variants[i]).
/* package */ final long[] lastMediaPlaylistLoadTimesMs; private final Variant[] variants;
/* package */ boolean live; private final HlsMediaPlaylist[] variantPlaylists;
/* package */ long durationUs; private final long[] variantLastPlaylistLoadTimesMs;
private final long[] variantBlacklistTimes;
// The index in variants of the currently selected variant.
private int selectedVariantIndex;
private byte[] scratchSpace;
private boolean live;
private long durationUs;
private int formatIndex;
private Uri encryptionKeyUri; private Uri encryptionKeyUri;
private byte[] encryptionKey; private byte[] encryptionKey;
private String encryptionIvString; private String encryptionIvString;
@ -181,38 +186,44 @@ public class HlsChunkSource {
playlistParser = new HlsPlaylistParser(); playlistParser = new HlsPlaylistParser();
if (playlist.type == HlsPlaylist.TYPE_MEDIA) { if (playlist.type == HlsPlaylist.TYPE_MEDIA) {
variants = Collections.singletonList(new Variant(0, playlistUrl, 0, null, -1, -1)); variants = new Variant[] {new Variant(0, playlistUrl, 0, null, -1, -1)};
variantIndices = null; variantPlaylists = new HlsMediaPlaylist[] {(HlsMediaPlaylist) playlist};
mediaPlaylists = new HlsMediaPlaylist[1]; variantLastPlaylistLoadTimesMs = new long[1];
mediaPlaylistBlacklistTimesMs = new long[1]; variantBlacklistTimes = new long[1];
lastMediaPlaylistLoadTimesMs = new long[1]; // We won't be adapting between different variants.
setMediaPlaylist(0, (HlsMediaPlaylist) playlist); maxWidth = -1;
maxHeight = -1;
} else { } else {
variants = ((HlsMasterPlaylist) playlist).variants; List<Variant> masterPlaylistVariants = ((HlsMasterPlaylist) playlist).variants;
int variantCount = variants.size(); variants = buildOrderedVariants(masterPlaylistVariants, variantIndices);
mediaPlaylists = new HlsMediaPlaylist[variantCount]; variantPlaylists = new HlsMediaPlaylist[variants.length];
mediaPlaylistBlacklistTimesMs = new long[variantCount]; variantLastPlaylistLoadTimesMs = new long[variants.length];
lastMediaPlaylistLoadTimesMs = new long[variantCount]; variantBlacklistTimes = new long[variants.length];
} int maxWidth = -1;
int maxHeight = -1;
enabledFormats = buildEnabledFormats(variants, variantIndices); // Select the variant that comes first in their original order in the master playlist.
int minOriginalVariantIndex = Integer.MAX_VALUE;
int maxWidth = -1; for (int i = 0; i < variants.length; i++) {
int maxHeight = -1; int originalVariantIndex = masterPlaylistVariants.indexOf(variants[i]);
// Select the first variant from the master playlist that's enabled. if (originalVariantIndex < minOriginalVariantIndex) {
int minEnabledVariantIndex = Integer.MAX_VALUE; minOriginalVariantIndex = originalVariantIndex;
for (int i = 0; i < enabledFormats.length; i++) { selectedVariantIndex = i;
int variantIndex = getVariantIndex(enabledFormats[i]); }
if (variantIndex < minEnabledVariantIndex) { Format variantFormat = variants[i].format;
minEnabledVariantIndex = variantIndex; maxWidth = Math.max(variantFormat.width, maxWidth);
formatIndex = i; maxHeight = Math.max(variantFormat.height, maxHeight);
}
if (variants.length <= 1 || adaptiveMode == ADAPTIVE_MODE_NONE) {
// We won't be adapting between different variants.
this.maxWidth = -1;
this.maxHeight = -1;
} else {
// We will be adapting between different variants.
// TODO: We should allow the default values to be passed through the constructor.
this.maxWidth = maxWidth > 0 ? maxWidth : 1920;
this.maxHeight = maxHeight > 0 ? maxHeight : 1080;
} }
maxWidth = Math.max(enabledFormats[i].width, maxWidth);
maxHeight = Math.max(enabledFormats[i].height, maxHeight);
} }
// TODO: We should allow the default values to be passed through the constructor.
this.maxWidth = maxWidth > 0 ? maxWidth : 1920;
this.maxHeight = maxHeight > 0 ? maxHeight : 1080;
} }
public long getDurationUs() { public long getDurationUs() {
@ -228,6 +239,10 @@ public class HlsChunkSource {
* @param out The {@link MediaFormat} on which the maximum video dimensions should be set. * @param out The {@link MediaFormat} on which the maximum video dimensions should be set.
*/ */
public void getMaxVideoDimensions(MediaFormat out) { public void getMaxVideoDimensions(MediaFormat out) {
if (maxWidth == -1 || maxHeight == -1) {
// Not adaptive.
return;
}
out.setMaxVideoDimensions(maxWidth, maxHeight); out.setMaxVideoDimensions(maxWidth, maxHeight);
} }
@ -242,36 +257,35 @@ public class HlsChunkSource {
*/ */
public Chunk getChunkOperation(TsChunk previousTsChunk, long seekPositionUs, public Chunk getChunkOperation(TsChunk previousTsChunk, long seekPositionUs,
long playbackPositionUs) { long playbackPositionUs) {
int nextFormatIndex; int nextVariantIndex;
boolean switchingVariantSpliced; boolean switchingVariantSpliced;
if (adaptiveMode == ADAPTIVE_MODE_NONE) { if (adaptiveMode == ADAPTIVE_MODE_NONE) {
nextFormatIndex = formatIndex; nextVariantIndex = selectedVariantIndex;
switchingVariantSpliced = false; switchingVariantSpliced = false;
} else { } else {
nextFormatIndex = getNextFormatIndex(previousTsChunk, playbackPositionUs); nextVariantIndex = getNextVariantIndex(previousTsChunk, playbackPositionUs);
switchingVariantSpliced = nextFormatIndex != formatIndex switchingVariantSpliced = nextVariantIndex != selectedVariantIndex
&& adaptiveMode == ADAPTIVE_MODE_SPLICE; && adaptiveMode == ADAPTIVE_MODE_SPLICE;
} }
int variantIndex = getVariantIndex(enabledFormats[nextFormatIndex]); HlsMediaPlaylist mediaPlaylist = variantPlaylists[nextVariantIndex];
HlsMediaPlaylist mediaPlaylist = mediaPlaylists[variantIndex];
if (mediaPlaylist == null) { if (mediaPlaylist == null) {
// We don't have the media playlist for the next variant. Request it now. // We don't have the media playlist for the next variant. Request it now.
return newMediaPlaylistChunk(variantIndex); return newMediaPlaylistChunk(nextVariantIndex);
} }
formatIndex = nextFormatIndex; selectedVariantIndex = nextVariantIndex;
int chunkMediaSequence = 0; int chunkMediaSequence = 0;
boolean liveDiscontinuity = false; boolean liveDiscontinuity = false;
if (live) { if (live) {
if (previousTsChunk == null) { if (previousTsChunk == null) {
chunkMediaSequence = getLiveStartChunkMediaSequence(variantIndex); chunkMediaSequence = getLiveStartChunkMediaSequence(nextVariantIndex);
} else { } else {
chunkMediaSequence = switchingVariantSpliced chunkMediaSequence = switchingVariantSpliced
? previousTsChunk.chunkIndex : previousTsChunk.chunkIndex + 1; ? previousTsChunk.chunkIndex : previousTsChunk.chunkIndex + 1;
if (chunkMediaSequence < mediaPlaylist.mediaSequence) { if (chunkMediaSequence < mediaPlaylist.mediaSequence) {
// If the chunk is no longer in the playlist. Skip ahead and start again. // If the chunk is no longer in the playlist. Skip ahead and start again.
chunkMediaSequence = getLiveStartChunkMediaSequence(variantIndex); chunkMediaSequence = getLiveStartChunkMediaSequence(nextVariantIndex);
liveDiscontinuity = true; liveDiscontinuity = true;
} }
} }
@ -288,8 +302,8 @@ public class HlsChunkSource {
int chunkIndex = chunkMediaSequence - mediaPlaylist.mediaSequence; int chunkIndex = chunkMediaSequence - mediaPlaylist.mediaSequence;
if (chunkIndex >= mediaPlaylist.segments.size()) { if (chunkIndex >= mediaPlaylist.segments.size()) {
if (mediaPlaylist.live && shouldRerequestMediaPlaylist(variantIndex)) { if (mediaPlaylist.live && shouldRerequestMediaPlaylist(nextVariantIndex)) {
return newMediaPlaylistChunk(variantIndex); return newMediaPlaylistChunk(nextVariantIndex);
} else { } else {
return null; return null;
} }
@ -303,7 +317,7 @@ public class HlsChunkSource {
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri); Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri);
if (!keyUri.equals(encryptionKeyUri)) { if (!keyUri.equals(encryptionKeyUri)) {
// Encryption is specified and the key has changed. // Encryption is specified and the key has changed.
Chunk toReturn = newEncryptionKeyChunk(keyUri, segment.encryptionIV, variantIndex); Chunk toReturn = newEncryptionKeyChunk(keyUri, segment.encryptionIV, selectedVariantIndex);
return toReturn; return toReturn;
} }
if (!Util.areEqual(segment.encryptionIV, encryptionIvString)) { if (!Util.areEqual(segment.encryptionIV, encryptionIvString)) {
@ -333,7 +347,7 @@ public class HlsChunkSource {
long endTimeUs = startTimeUs + (long) (segment.durationSecs * C.MICROS_PER_SECOND); long endTimeUs = startTimeUs + (long) (segment.durationSecs * C.MICROS_PER_SECOND);
boolean isLastChunk = !mediaPlaylist.live && chunkIndex == mediaPlaylist.segments.size() - 1; boolean isLastChunk = !mediaPlaylist.live && chunkIndex == mediaPlaylist.segments.size() - 1;
int trigger = Chunk.TRIGGER_UNSPECIFIED; int trigger = Chunk.TRIGGER_UNSPECIFIED;
Format format = enabledFormats[formatIndex]; Format format = variants[selectedVariantIndex].format;
// Configure the extractor that will read the chunk. // Configure the extractor that will read the chunk.
HlsExtractorWrapper extractorWrapper; HlsExtractorWrapper extractorWrapper;
@ -399,17 +413,23 @@ public class HlsChunkSource {
EncryptionKeyChunk encryptionChunk = (EncryptionKeyChunk) chunk; EncryptionKeyChunk encryptionChunk = (EncryptionKeyChunk) chunk;
variantIndex = encryptionChunk.variantIndex; variantIndex = encryptionChunk.variantIndex;
} }
mediaPlaylistBlacklistTimesMs[variantIndex] = SystemClock.elapsedRealtime(); boolean alreadyBlacklisted = variantBlacklistTimes[variantIndex] != 0;
if (!allPlaylistsBlacklisted()) { variantBlacklistTimes[variantIndex] = SystemClock.elapsedRealtime();
// We've handled the 404/410 by blacklisting the playlist. if (alreadyBlacklisted) {
Log.w(TAG, "Blacklisted playlist (" + responseCode + "): " // The playlist was already blacklisted.
Log.w(TAG, "Already blacklisted variant (" + responseCode + "): "
+ chunk.dataSpec.uri);
return false;
} else if (!allVariantsBlacklisted()) {
// We've handled the 404/410 by blacklisting the variant.
Log.w(TAG, "Blacklisted variant (" + responseCode + "): "
+ chunk.dataSpec.uri); + chunk.dataSpec.uri);
return true; return true;
} else { } else {
// This was the last non-blacklisted playlist. Don't blacklist it. // This was the last non-blacklisted playlist. Don't blacklist it.
Log.w(TAG, "Final playlist not blacklisted (" + responseCode + "): " Log.w(TAG, "Final variant not blacklisted (" + responseCode + "): "
+ chunk.dataSpec.uri); + chunk.dataSpec.uri);
mediaPlaylistBlacklistTimesMs[variantIndex] = 0; variantBlacklistTimes[variantIndex] = 0;
return false; return false;
} }
} }
@ -417,71 +437,78 @@ public class HlsChunkSource {
return false; return false;
} }
private int getNextFormatIndex(TsChunk previousTsChunk, long playbackPositionUs) { private int getNextVariantIndex(TsChunk previousTsChunk, long playbackPositionUs) {
clearStaleBlacklistedPlaylists(); clearStaleBlacklistedVariants();
long bitrateEstimate = bandwidthMeter.getBitrateEstimate();
if (variantBlacklistTimes[selectedVariantIndex] != 0) {
// The current variant has been blacklisted, so we have no choice but to re-evaluate.
return getVariantIndexForBandwidth(bitrateEstimate);
}
if (previousTsChunk == null) { if (previousTsChunk == null) {
// Don't consider switching if we don't have a previous chunk. // Don't consider switching if we don't have a previous chunk.
return formatIndex; return selectedVariantIndex;
} }
long bitrateEstimate = bandwidthMeter.getBitrateEstimate();
if (bitrateEstimate == BandwidthMeter.NO_ESTIMATE) { if (bitrateEstimate == BandwidthMeter.NO_ESTIMATE) {
// Don't consider switching if we don't have a bandwidth estimate. // Don't consider switching if we don't have a bandwidth estimate.
return formatIndex; return selectedVariantIndex;
} }
int idealFormatIndex = getFormatIndexForBandwidth( int idealIndex = getVariantIndexForBandwidth(bitrateEstimate);
(int) (bitrateEstimate * BANDWIDTH_FRACTION)); if (idealIndex == selectedVariantIndex) {
if (idealFormatIndex == formatIndex) { // We're already using the ideal variant.
// We're already using the ideal format. return selectedVariantIndex;
return formatIndex;
} }
// We're not using the ideal format for the available bandwidth, but only switch if the // We're not using the ideal variant for the available bandwidth, but only switch if the
// conditions are appropriate. // conditions are appropriate.
long bufferedPositionUs = adaptiveMode == ADAPTIVE_MODE_SPLICE ? previousTsChunk.startTimeUs long bufferedPositionUs = adaptiveMode == ADAPTIVE_MODE_SPLICE ? previousTsChunk.startTimeUs
: previousTsChunk.endTimeUs; : previousTsChunk.endTimeUs;
long bufferedUs = bufferedPositionUs - playbackPositionUs; long bufferedUs = bufferedPositionUs - playbackPositionUs;
if (mediaPlaylistBlacklistTimesMs[formatIndex] != 0 if (variantBlacklistTimes[selectedVariantIndex] != 0
|| (idealFormatIndex > formatIndex && bufferedUs < maxBufferDurationToSwitchDownUs) || (idealIndex > selectedVariantIndex && bufferedUs < maxBufferDurationToSwitchDownUs)
|| (idealFormatIndex < formatIndex && bufferedUs > minBufferDurationToSwitchUpUs)) { || (idealIndex < selectedVariantIndex && bufferedUs > minBufferDurationToSwitchUpUs)) {
// Switch format. // Switch variant.
return idealFormatIndex; return idealIndex;
} }
// Stick with the current format for now. // Stick with the current variant for now.
return formatIndex; return selectedVariantIndex;
} }
private int getFormatIndexForBandwidth(int bitrate) { private int getVariantIndexForBandwidth(long bitrateEstimate) {
int lowestQualityEnabledFormatIndex = -1; if (bitrateEstimate == BandwidthMeter.NO_ESTIMATE) {
for (int i = 0; i < enabledFormats.length; i++) { // Select the lowest quality.
int variantIndex = getVariantIndex(enabledFormats[i]); bitrateEstimate = 0;
if (mediaPlaylistBlacklistTimesMs[variantIndex] == 0) { }
if (enabledFormats[i].bitrate <= bitrate) { int effectiveBitrate = (int) (bitrateEstimate * BANDWIDTH_FRACTION);
int lowestQualityEnabledVariantIndex = -1;
for (int i = 0; i < variants.length; i++) {
if (variantBlacklistTimes[i] == 0) {
if (variants[i].format.bitrate <= effectiveBitrate) {
return i; return i;
} }
lowestQualityEnabledFormatIndex = i; lowestQualityEnabledVariantIndex = i;
} }
} }
// At least one format should always be enabled. // At least one variant should always be enabled.
Assertions.checkState(lowestQualityEnabledFormatIndex != -1); Assertions.checkState(lowestQualityEnabledVariantIndex != -1);
return lowestQualityEnabledFormatIndex; return lowestQualityEnabledVariantIndex;
} }
private boolean shouldRerequestMediaPlaylist(int variantIndex) { private boolean shouldRerequestMediaPlaylist(int nextVariantIndex) {
// Don't re-request media playlist more often than one-half of the target duration. // Don't re-request media playlist more often than one-half of the target duration.
HlsMediaPlaylist mediaPlaylist = mediaPlaylists[variantIndex]; HlsMediaPlaylist mediaPlaylist = variantPlaylists[nextVariantIndex];
long timeSinceLastMediaPlaylistLoadMs = long timeSinceLastMediaPlaylistLoadMs =
SystemClock.elapsedRealtime() - lastMediaPlaylistLoadTimesMs[variantIndex]; SystemClock.elapsedRealtime() - variantLastPlaylistLoadTimesMs[nextVariantIndex];
return timeSinceLastMediaPlaylistLoadMs >= (mediaPlaylist.targetDurationSecs * 1000) / 2; return timeSinceLastMediaPlaylistLoadMs >= (mediaPlaylist.targetDurationSecs * 1000) / 2;
} }
private int getLiveStartChunkMediaSequence(int variantIndex) { private int getLiveStartChunkMediaSequence(int variantIndex) {
// For live start playback from the third chunk from the end. // For live start playback from the third chunk from the end.
HlsMediaPlaylist mediaPlaylist = mediaPlaylists[variantIndex]; HlsMediaPlaylist mediaPlaylist = variantPlaylists[variantIndex];
int chunkIndex = mediaPlaylist.segments.size() > 3 ? mediaPlaylist.segments.size() - 3 : 0; int chunkIndex = mediaPlaylist.segments.size() > 3 ? mediaPlaylist.segments.size() - 3 : 0;
return chunkIndex + mediaPlaylist.mediaSequence; return chunkIndex + mediaPlaylist.mediaSequence;
} }
private MediaPlaylistChunk newMediaPlaylistChunk(int variantIndex) { private MediaPlaylistChunk newMediaPlaylistChunk(int variantIndex) {
Uri mediaPlaylistUri = UriUtil.resolveToUri(baseUri, variants.get(variantIndex).url); Uri mediaPlaylistUri = UriUtil.resolveToUri(baseUri, variants[variantIndex].url);
DataSpec dataSpec = new DataSpec(mediaPlaylistUri, 0, C.LENGTH_UNBOUNDED, null, DataSpec dataSpec = new DataSpec(mediaPlaylistUri, 0, C.LENGTH_UNBOUNDED, null,
DataSpec.FLAG_ALLOW_GZIP); DataSpec.FLAG_ALLOW_GZIP);
return new MediaPlaylistChunk(dataSource, dataSpec, scratchSpace, playlistParser, variantIndex, return new MediaPlaylistChunk(dataSource, dataSpec, scratchSpace, playlistParser, variantIndex,
@ -521,27 +548,36 @@ public class HlsChunkSource {
} }
/* package */ void setMediaPlaylist(int variantIndex, HlsMediaPlaylist mediaPlaylist) { /* package */ void setMediaPlaylist(int variantIndex, HlsMediaPlaylist mediaPlaylist) {
lastMediaPlaylistLoadTimesMs[variantIndex] = SystemClock.elapsedRealtime(); variantLastPlaylistLoadTimesMs[variantIndex] = SystemClock.elapsedRealtime();
mediaPlaylists[variantIndex] = mediaPlaylist; variantPlaylists[variantIndex] = mediaPlaylist;
live |= mediaPlaylist.live; live |= mediaPlaylist.live;
durationUs = mediaPlaylist.durationUs; durationUs = mediaPlaylist.durationUs;
} }
private static Format[] buildEnabledFormats(List<Variant> variants, int[] variantIndices) { /**
ArrayList<Variant> enabledVariants = new ArrayList<>(); * Selects a list of variants to use, returning them in order of decreasing bandwidth.
if (variantIndices != null) { *
for (int i = 0; i < variantIndices.length; i++) { * @param originalVariants The original list of variants.
enabledVariants.add(variants.get(variantIndices[i])); * @param originalVariantIndices Indices of variants that in the original list that can be
* considered, or null to allow all variants to be considered.
* @return The set of enabled variants in decreasing bandwidth order.
*/
private static Variant[] buildOrderedVariants(List<Variant> originalVariants,
int[] originalVariantIndices) {
ArrayList<Variant> enabledVariantList = new ArrayList<>();
if (originalVariantIndices != null) {
for (int i = 0; i < originalVariantIndices.length; i++) {
enabledVariantList.add(originalVariants.get(originalVariantIndices[i]));
} }
} else { } else {
// If variantIndices is null then all variants are initially considered. // If variantIndices is null then all variants are initially considered.
enabledVariants.addAll(variants); enabledVariantList.addAll(originalVariants);
} }
ArrayList<Variant> definiteVideoVariants = new ArrayList<>(); ArrayList<Variant> definiteVideoVariants = new ArrayList<>();
ArrayList<Variant> definiteAudioOnlyVariants = new ArrayList<>(); ArrayList<Variant> definiteAudioOnlyVariants = new ArrayList<>();
for (int i = 0; i < enabledVariants.size(); i++) { for (int i = 0; i < enabledVariantList.size(); i++) {
Variant variant = enabledVariants.get(i); Variant variant = enabledVariantList.get(i);
if (variant.format.height > 0 || variantHasExplicitCodecWithPrefix(variant, "avc")) { if (variant.format.height > 0 || variantHasExplicitCodecWithPrefix(variant, "avc")) {
definiteVideoVariants.add(variant); definiteVideoVariants.add(variant);
} else if (variantHasExplicitCodecWithPrefix(variant, "mp4a")) { } else if (variantHasExplicitCodecWithPrefix(variant, "mp4a")) {
@ -553,22 +589,27 @@ public class HlsChunkSource {
// We've identified some variants as definitely containing video. Assume variants within the // We've identified some variants as definitely containing video. Assume variants within the
// master playlist are marked consistently, and hence that we have the full set. Filter out // master playlist are marked consistently, and hence that we have the full set. Filter out
// any other variants, which are likely to be audio only. // any other variants, which are likely to be audio only.
enabledVariants = definiteVideoVariants; enabledVariantList = definiteVideoVariants;
} else if (definiteAudioOnlyVariants.size() < enabledVariants.size()) { } else if (definiteAudioOnlyVariants.size() < enabledVariantList.size()) {
// We've identified some variants, but not all, as being audio only. Filter them out to leave // We've identified some variants, but not all, as being audio only. Filter them out to leave
// the remaining variants, which are likely to contain video. // the remaining variants, which are likely to contain video.
enabledVariants.removeAll(definiteAudioOnlyVariants); enabledVariantList.removeAll(definiteAudioOnlyVariants);
} else { } else {
// Leave the enabled variants unchanged. They're likely either all video or all audio. // Leave the enabled variants unchanged. They're likely either all video or all audio.
} }
Format[] enabledFormats = new Format[enabledVariants.size()]; Variant[] enabledVariants = new Variant[enabledVariantList.size()];
for (int i = 0; i < enabledFormats.length; i++) { enabledVariantList.toArray(enabledVariants);
enabledFormats[i] = enabledVariants.get(i).format; Arrays.sort(enabledVariants, new Comparator<Variant>() {
} private final Comparator<Format> formatComparator =
new Format.DecreasingBandwidthComparator();
@Override
public int compare(Variant first, Variant second) {
return formatComparator.compare(first.format, second.format);
}
});
Arrays.sort(enabledFormats, new Format.DecreasingBandwidthComparator()); return enabledVariants;
return enabledFormats;
} }
private static boolean variantHasExplicitCodecWithPrefix(Variant variant, String prefix) { private static boolean variantHasExplicitCodecWithPrefix(Variant variant, String prefix) {
@ -585,28 +626,28 @@ public class HlsChunkSource {
return false; return false;
} }
private boolean allPlaylistsBlacklisted() { private boolean allVariantsBlacklisted() {
for (int i = 0; i < mediaPlaylistBlacklistTimesMs.length; i++) { for (int i = 0; i < variantBlacklistTimes.length; i++) {
if (mediaPlaylistBlacklistTimesMs[i] == 0) { if (variantBlacklistTimes[i] == 0) {
return false; return false;
} }
} }
return true; return true;
} }
private void clearStaleBlacklistedPlaylists() { private void clearStaleBlacklistedVariants() {
long currentTime = SystemClock.elapsedRealtime(); long currentTime = SystemClock.elapsedRealtime();
for (int i = 0; i < mediaPlaylistBlacklistTimesMs.length; i++) { for (int i = 0; i < variantBlacklistTimes.length; i++) {
if (mediaPlaylistBlacklistTimesMs[i] != 0 if (variantBlacklistTimes[i] != 0
&& currentTime - mediaPlaylistBlacklistTimesMs[i] > DEFAULT_PLAYLIST_BLACKLIST_MS) { && currentTime - variantBlacklistTimes[i] > DEFAULT_PLAYLIST_BLACKLIST_MS) {
mediaPlaylistBlacklistTimesMs[i] = 0; variantBlacklistTimes[i] = 0;
} }
} }
} }
private int getVariantIndex(Format format) { private int getVariantIndex(Format format) {
for (int i = 0; i < variants.size(); i++) { for (int i = 0; i < variants.length; i++) {
if (variants.get(i).format.equals(format)) { if (variants[i].format.equals(format)) {
return i; return i;
} }
} }