mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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:
parent
e0316d1c16
commit
51a8635ba2
@ -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.
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user