diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java b/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java index 0f88525b68..7b2420add9 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java @@ -28,8 +28,10 @@ public interface FormatEvaluator { /** * Enables the evaluator. + * + * @param formats The formats from which to select, ordered by decreasing bandwidth. */ - void enable(); + void enable(Format[] formats); /** * Disables the evaluator. @@ -39,22 +41,27 @@ public interface FormatEvaluator { /** * Update the supplied evaluation. *

- * When the method is invoked, {@code evaluation} will contain the currently selected - * format (null for the first evaluation), the most recent trigger (TRIGGER_INITIAL for the - * first evaluation) and the current queue size. The implementation should update these - * fields as necessary. - *

- * The trigger should be considered "sticky" for as long as a given representation is selected, - * and so should only be changed if the representation is also changed. + * When invoked, {@code evaluation} must contain the currently selected format (null for an + * initial evaluation), the most recent trigger (@link Chunk#TRIGGER_INITIAL} for an initial + * evaluation) and the size of {@code queue}. The invocation will update the format and trigger, + * and may also reduce {@link Evaluation#queueSize} to indicate that chunks should be discarded + * from the end of the queue to allow re-buffering in a different format. The evaluation will + * always retain the first chunk in the queue, if there is one. * - * @param queue A read only representation of the currently buffered {@link MediaChunk}s. - * @param playbackPositionUs The current playback position. - * @param formats The formats from which to select, ordered by decreasing bandwidth. - * @param evaluation The evaluation. + * @param queue A read only representation of currently buffered chunks. Must not be empty unless + * the evaluation is at the start of playback or immediately follows a seek. All but the first + * chunk may be discarded. A caller may pass a singleton list containing only the most + * recently buffered chunk in the case that it does not support discarding of chunks. + * @param playbackPositionUs The current playback position in microseconds. + * @param switchingOverlapUs If switching format requires downloading overlapping media then this + * is the duration of the required overlap in microseconds. 0 otherwise. + * @param blacklistFlags An array whose length is equal to the number of available formats. A + * {@code true} element indicates that a format is currently blacklisted and should not be + * selected by the evaluation. At least one element must be {@code false}. + * @param evaluation The evaluation to be updated. */ - // TODO: Pass more useful information into this method, and finalize the interface. - void evaluate(List queue, long playbackPositionUs, Format[] formats, - Evaluation evaluation); + void evaluate(List queue, long playbackPositionUs, + long switchingOverlapUs, boolean[] blacklistFlags, Evaluation evaluation); /** * A format evaluation. @@ -62,9 +69,9 @@ public interface FormatEvaluator { public static final class Evaluation { /** - * The desired size of the queue. + * The selected format. */ - public int queueSize; + public Format format; /** * The sticky reason for the format selection. @@ -72,46 +79,33 @@ public interface FormatEvaluator { public int trigger; /** - * The selected format. + * The desired size of the queue. */ - public Format format; + public int queueSize; public Evaluation() { trigger = Chunk.TRIGGER_INITIAL; } + /** + * Clears {@link #format} and sets {@link #trigger} to {@link Chunk#TRIGGER_INITIAL}. + */ + public void clear() { + format = null; + trigger = Chunk.TRIGGER_INITIAL; + } + } /** - * Always selects the first format. - */ - public static final class FixedEvaluator implements FormatEvaluator { - - @Override - public void enable() { - // Do nothing. - } - - @Override - public void disable() { - // Do nothing. - } - - @Override - public void evaluate(List queue, long playbackPositionUs, - Format[] formats, Evaluation evaluation) { - evaluation.format = formats[0]; - } - - } - - /** - * Selects randomly between the available formats. + * Selects randomly between the available formats, excluding those that are blacklisted. */ public static final class RandomEvaluator implements FormatEvaluator { private final Random random; + private Format[] formats; + public RandomEvaluator() { this.random = new Random(); } @@ -124,20 +118,39 @@ public interface FormatEvaluator { } @Override - public void enable() { - // Do nothing. + public void enable(Format[] formats) { + this.formats = formats; } @Override public void disable() { - // Do nothing. + formats = null; } @Override public void evaluate(List queue, long playbackPositionUs, - Format[] formats, Evaluation evaluation) { - Format newFormat = formats[random.nextInt(formats.length)]; - if (evaluation.format != null && !evaluation.format.equals(newFormat)) { + long switchingOverlapUs, boolean[] blacklistFlags, Evaluation evaluation) { + // Count the number of non-blacklisted formats. + int nonBlacklistedFormatCount = 0; + for (int i = 0; i < blacklistFlags.length; i++) { + if (!blacklistFlags[i]) { + nonBlacklistedFormatCount++; + } + } + + int formatIndex = random.nextInt(nonBlacklistedFormatCount); + if (nonBlacklistedFormatCount != formats.length) { + // Adjust the format index to account for blacklisted formats. + nonBlacklistedFormatCount = 0; + for (int i = 0; i < blacklistFlags.length; i++) { + if (!blacklistFlags[i] && formatIndex == nonBlacklistedFormatCount++) { + formatIndex = i; + break; + } + } + } + Format newFormat = formats[formatIndex]; + if (evaluation.format != null && evaluation.format != newFormat) { evaluation.trigger = Chunk.TRIGGER_ADAPTIVE; } evaluation.format = newFormat; @@ -163,13 +176,14 @@ public interface FormatEvaluator { public static final float DEFAULT_BANDWIDTH_FRACTION = 0.75f; private final BandwidthMeter bandwidthMeter; - private final int maxInitialBitrate; private final long minDurationForQualityIncreaseUs; private final long maxDurationForQualityDecreaseUs; private final long minDurationToRetainAfterDiscardUs; private final float bandwidthFraction; + private Format[] formats; + /** * @param bandwidthMeter Provides an estimate of the currently available bandwidth. */ @@ -211,22 +225,26 @@ public interface FormatEvaluator { } @Override - public void enable() { - // Do nothing. + public void enable(Format[] formats) { + this.formats = formats; } @Override public void disable() { - // Do nothing. + formats = null; } @Override public void evaluate(List queue, long playbackPositionUs, - Format[] formats, Evaluation evaluation) { + long switchingOverlapUs, boolean[] blacklistFlags, Evaluation evaluation) { long bufferedDurationUs = queue.isEmpty() ? 0 : queue.get(queue.size() - 1).endTimeUs - playbackPositionUs; + if (switchingOverlapUs > 0) { + bufferedDurationUs = Math.max(0, bufferedDurationUs - switchingOverlapUs); + } Format current = evaluation.format; - Format ideal = determineIdealFormat(formats, bandwidthMeter.getBitrateEstimate()); + Format ideal = determineIdealFormat(formats, blacklistFlags, + bandwidthMeter.getBitrateEstimate()); boolean isHigher = ideal != null && current != null && ideal.bitrate > current.bitrate; boolean isLower = ideal != null && current != null && ideal.bitrate < current.bitrate; if (isHigher) { @@ -267,17 +285,22 @@ public interface FormatEvaluator { /** * Compute the ideal format ignoring buffer health. */ - private Format determineIdealFormat(Format[] formats, long bitrateEstimate) { + private Format determineIdealFormat(Format[] formats, boolean[] blacklistFlags, + long bitrateEstimate) { + int lowestBitrateNonBlacklistedIndex = 0; long effectiveBitrate = bitrateEstimate == BandwidthMeter.NO_ESTIMATE ? maxInitialBitrate : (long) (bitrateEstimate * bandwidthFraction); for (int i = 0; i < formats.length; i++) { Format format = formats[i]; - if (format.bitrate <= effectiveBitrate) { - return format; + if (!blacklistFlags[i]) { + if (format.bitrate <= effectiveBitrate) { + return format; + } else { + lowestBitrateNonBlacklistedIndex = i; + } } } - // We didn't manage to calculate a suitable format. Return the lowest quality format. - return formats[formats.length - 1]; + return formats[lowestBitrateNonBlacklistedIndex]; } } diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java index c4e98f32ec..0a78498d5f 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java @@ -131,6 +131,7 @@ public class DashChunkSource implements ChunkSource { // Properties of enabled tracks. private Format[] enabledFormats; + private boolean[] adaptiveFormatBlacklistFlags; /** * @param manifestFetcher A fetcher for the manifest. @@ -266,7 +267,8 @@ public class DashChunkSource implements ChunkSource { } Arrays.sort(enabledFormats, new DecreasingBandwidthComparator()); if (enabledFormats.length > 1) { - adaptiveFormatEvaluator.enable(); + adaptiveFormatEvaluator.enable(enabledFormats); + adaptiveFormatBlacklistFlags = new boolean[tracks.length]; } processManifest(manifestFetcher.getManifest()); } @@ -311,7 +313,8 @@ public class DashChunkSource implements ChunkSource { evaluation.queueSize = queue.size(); if (evaluation.format == null || !lastChunkWasInitialization) { if (enabledFormats.length > 1) { - adaptiveFormatEvaluator.evaluate(queue, playbackPositionUs, enabledFormats, evaluation); + adaptiveFormatEvaluator.evaluate(queue, playbackPositionUs, 0, adaptiveFormatBlacklistFlags, + evaluation); } else { evaluation.format = enabledFormats[0]; evaluation.trigger = Chunk.TRIGGER_MANUAL; @@ -479,7 +482,7 @@ public class DashChunkSource implements ChunkSource { adaptiveFormatEvaluator.disable(); } periodHolders.clear(); - evaluation.format = null; + evaluation.clear(); availableRange = null; fatalError = null; enabledFormats = null; diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index 3a9c4753a7..0f3c11c156 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -22,6 +22,8 @@ import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener; import com.google.android.exoplayer.chunk.Chunk; import com.google.android.exoplayer.chunk.ChunkOperationHolder; import com.google.android.exoplayer.chunk.DataChunk; +import com.google.android.exoplayer.chunk.FormatEvaluator; +import com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation; import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.mp3.Mp3Extractor; import com.google.android.exoplayer.extractor.ts.AdtsExtractor; @@ -31,7 +33,6 @@ import com.google.android.exoplayer.upstream.BandwidthMeter; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException; -import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.UriUtil; @@ -99,18 +100,6 @@ public class HlsChunkSource { */ public static final int ADAPTIVE_MODE_ABRUPT = 3; - /** - * The default minimum duration of media that needs to be buffered for a switch to a higher - * quality variant to be considered. - */ - public static final long DEFAULT_MIN_BUFFER_TO_SWITCH_UP_MS = 5000; - - /** - * The default maximum duration of media that needs to be buffered for a switch to a lower - * quality variant to be considered. - */ - public static final long DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS = 20000; - /** * The default time for which a media playlist should be blacklisted. */ @@ -121,19 +110,16 @@ public class HlsChunkSource { private static final String MP3_FILE_EXTENSION = ".mp3"; private static final String VTT_FILE_EXTENSION = ".vtt"; private static final String WEBVTT_FILE_EXTENSION = ".webvtt"; - private static final float BANDWIDTH_FRACTION = 0.8f; private final ManifestFetcher manifestFetcher; private final int type; private final DataSource dataSource; + private final FormatEvaluator adaptiveFormatEvaluator; + private final Evaluation evaluation; private final HlsPlaylistParser playlistParser; - private final BandwidthMeter bandwidthMeter; private final PtsTimestampAdjusterProvider timestampAdjusterProvider; private final int adaptiveMode; - private final long minBufferDurationToSwitchUpUs; - private final long maxBufferDurationToSwitchDownUs; - private boolean manifestFetcherEnabled; private byte[] scratchSpace; private boolean live; @@ -155,6 +141,7 @@ public class HlsChunkSource { private HlsMediaPlaylist[] enabledVariantPlaylists; private long[] enabledVariantLastPlaylistLoadTimesMs; private long[] enabledVariantBlacklistTimes; + private boolean[] enabledVariantBlacklistFlags; private int selectedVariantIndex; /** @@ -170,43 +157,17 @@ public class HlsChunkSource { * {@link #ADAPTIVE_MODE_NONE}, {@link #ADAPTIVE_MODE_ABRUPT} and * {@link #ADAPTIVE_MODE_SPLICE}. */ - public HlsChunkSource(ManifestFetcher manifestFetcher, int type, - DataSource dataSource, BandwidthMeter bandwidthMeter, - PtsTimestampAdjusterProvider timestampAdjusterProvider, int adaptiveMode) { - this(manifestFetcher, type, dataSource, bandwidthMeter, timestampAdjusterProvider, - adaptiveMode, DEFAULT_MIN_BUFFER_TO_SWITCH_UP_MS, DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS); - } - - /** - * @param manifestFetcher A fetcher for the playlist. - * @param type The type of chunk provided by the source. One of {@link #TYPE_DEFAULT} and - * {@link #TYPE_VTT}. - * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param bandwidthMeter Provides an estimate of the currently available bandwidth. - * @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If - * multiple {@link HlsChunkSource}s are used for a single playback, they should all share the - * same provider. - * @param adaptiveMode The mode for switching from one variant to another. One of - * {@link #ADAPTIVE_MODE_NONE}, {@link #ADAPTIVE_MODE_ABRUPT} and - * {@link #ADAPTIVE_MODE_SPLICE}. - * @param minBufferDurationToSwitchUpMs The minimum duration of media that needs to be buffered - * for a switch to a higher quality variant to be considered. - * @param maxBufferDurationToSwitchDownMs The maximum duration of media that needs to be buffered - * for a switch to a lower quality variant to be considered. - */ public HlsChunkSource(ManifestFetcher manifestFetcher, int type, DataSource dataSource, BandwidthMeter bandwidthMeter, - PtsTimestampAdjusterProvider timestampAdjusterProvider, int adaptiveMode, - long minBufferDurationToSwitchUpMs, long maxBufferDurationToSwitchDownMs) { + PtsTimestampAdjusterProvider timestampAdjusterProvider, int adaptiveMode) { this.manifestFetcher = manifestFetcher; this.type = type; this.dataSource = dataSource; - this.bandwidthMeter = bandwidthMeter; + this.adaptiveFormatEvaluator = new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter); this.timestampAdjusterProvider = timestampAdjusterProvider; this.adaptiveMode = adaptiveMode; - minBufferDurationToSwitchUpUs = minBufferDurationToSwitchUpMs * 1000; - maxBufferDurationToSwitchDownUs = maxBufferDurationToSwitchDownMs * 1000; playlistParser = new HlsPlaylistParser(); + evaluation = new Evaluation(); } /** @@ -304,17 +265,19 @@ public class HlsChunkSource { } /** - * Selects a tracks for use. + * Selects tracks for use. *

* This method should only be called after the source has been prepared. * * @param tracks The track indices. */ public void selectTracks(int[] tracks) { + evaluation.clear(); enabledVariants = new Variant[tracks.length]; enabledVariantPlaylists = new HlsMediaPlaylist[enabledVariants.length]; enabledVariantLastPlaylistLoadTimesMs = new long[enabledVariants.length]; enabledVariantBlacklistTimes = new long[enabledVariants.length]; + enabledVariantBlacklistFlags = new boolean[enabledVariants.length]; // Construct and sort the enabled variants. for (int i = 0; i < tracks.length; i++) { enabledVariants[i] = exposedVariants[tracks[i]]; @@ -327,15 +290,13 @@ public class HlsChunkSource { return formatComparator.compare(first.format, second.format); } }); - // Determine the initial variant index and maximum video dimensions. - selectedVariantIndex = 0; - int minOriginalVariantIndex = Integer.MAX_VALUE; - for (int i = 0; i < enabledVariants.length; i++) { - int originalVariantIndex = masterPlaylist.variants.indexOf(enabledVariants[i]); - if (originalVariantIndex < minOriginalVariantIndex) { - minOriginalVariantIndex = originalVariantIndex; - selectedVariantIndex = i; + if (enabledVariants.length > 1) { + // TODO[REFACTOR]: We need to disable this at some point. + Format[] formats = new Format[enabledVariants.length]; + for (int i = 0; i < formats.length; i++) { + formats[i] = enabledVariants[i].format; } + adaptiveFormatEvaluator.enable(formats); } } @@ -560,7 +521,8 @@ public class HlsChunkSource { EncryptionKeyChunk encryptionChunk = (EncryptionKeyChunk) chunk; enabledVariantIndex = encryptionChunk.variantIndex; } - boolean alreadyBlacklisted = enabledVariantBlacklistTimes[enabledVariantIndex] != 0; + boolean alreadyBlacklisted = enabledVariantBlacklistFlags[enabledVariantIndex]; + enabledVariantBlacklistFlags[enabledVariantIndex] = true; enabledVariantBlacklistTimes[enabledVariantIndex] = SystemClock.elapsedRealtime(); if (alreadyBlacklisted) { // The playlist was already blacklisted. @@ -576,7 +538,7 @@ public class HlsChunkSource { // This was the last non-blacklisted playlist. Don't blacklist it. Log.w(TAG, "Final variant not blacklisted (" + responseCode + "): " + chunk.dataSpec.uri); - enabledVariantBlacklistTimes[enabledVariantIndex] = 0; + enabledVariantBlacklistFlags[enabledVariantIndex] = false; return false; } } @@ -644,57 +606,29 @@ public class HlsChunkSource { private int getNextVariantIndex(TsChunk previousTsChunk, long playbackPositionUs) { clearStaleBlacklistedVariants(); - long bitrateEstimate = bandwidthMeter.getBitrateEstimate(); - if (enabledVariantBlacklistTimes[selectedVariantIndex] != 0) { - // The current variant has been blacklisted, so we have no choice but to re-evaluate. - return getVariantIndexForBandwidth(bitrateEstimate); + long switchingOverlapUs; + List queue; + if (previousTsChunk != null) { + switchingOverlapUs = adaptiveMode == ADAPTIVE_MODE_SPLICE + ? previousTsChunk.endTimeUs - previousTsChunk.startTimeUs : 0; + queue = Collections.singletonList(previousTsChunk); + } else { + switchingOverlapUs = 0; + queue = Collections.emptyList(); } - if (previousTsChunk == null) { - // Don't consider switching if we don't have a previous chunk. - return selectedVariantIndex; + if (enabledVariants.length > 1) { + adaptiveFormatEvaluator.evaluate(queue, playbackPositionUs, switchingOverlapUs, + enabledVariantBlacklistFlags, evaluation); + } else { + evaluation.format = enabledVariants[0].format; + evaluation.trigger = Chunk.TRIGGER_MANUAL; } - if (bitrateEstimate == BandwidthMeter.NO_ESTIMATE) { - // Don't consider switching if we don't have a bandwidth estimate. - return selectedVariantIndex; - } - int idealIndex = getVariantIndexForBandwidth(bitrateEstimate); - if (idealIndex == selectedVariantIndex) { - // We're already using the ideal variant. - return selectedVariantIndex; - } - // We're not using the ideal variant for the available bandwidth, but only switch if the - // conditions are appropriate. - long bufferedPositionUs = adaptiveMode == ADAPTIVE_MODE_SPLICE ? previousTsChunk.startTimeUs - : previousTsChunk.endTimeUs; - long bufferedUs = bufferedPositionUs - playbackPositionUs; - if (enabledVariantBlacklistTimes[selectedVariantIndex] != 0 - || (idealIndex > selectedVariantIndex && bufferedUs < maxBufferDurationToSwitchDownUs) - || (idealIndex < selectedVariantIndex && bufferedUs > minBufferDurationToSwitchUpUs)) { - // Switch variant. - return idealIndex; - } - // Stick with the current variant for now. - return selectedVariantIndex; - } - - private int getVariantIndexForBandwidth(long bitrateEstimate) { - if (bitrateEstimate == BandwidthMeter.NO_ESTIMATE) { - // Select the lowest quality. - bitrateEstimate = 0; - } - int effectiveBitrate = (int) (bitrateEstimate * BANDWIDTH_FRACTION); - int lowestQualityEnabledVariantIndex = -1; for (int i = 0; i < enabledVariants.length; i++) { - if (enabledVariantBlacklistTimes[i] == 0) { - if (enabledVariants[i].format.bitrate <= effectiveBitrate) { - return i; - } - lowestQualityEnabledVariantIndex = i; + if (enabledVariants[i].format == evaluation.format) { + return i; } } - // At least one variant should always be enabled. - Assertions.checkState(lowestQualityEnabledVariantIndex != -1); - return lowestQualityEnabledVariantIndex; + throw new IllegalStateException(); } private boolean shouldRerequestLiveMediaPlaylist(int nextVariantIndex) { @@ -760,8 +694,8 @@ public class HlsChunkSource { } private boolean allVariantsBlacklisted() { - for (int i = 0; i < enabledVariantBlacklistTimes.length; i++) { - if (enabledVariantBlacklistTimes[i] == 0) { + for (int i = 0; i < enabledVariantBlacklistFlags.length; i++) { + if (!enabledVariantBlacklistFlags[i]) { return false; } } @@ -770,10 +704,10 @@ public class HlsChunkSource { private void clearStaleBlacklistedVariants() { long currentTime = SystemClock.elapsedRealtime(); - for (int i = 0; i < enabledVariantBlacklistTimes.length; i++) { - if (enabledVariantBlacklistTimes[i] != 0 + for (int i = 0; i < enabledVariantBlacklistFlags.length; i++) { + if (enabledVariantBlacklistFlags[i] && currentTime - enabledVariantBlacklistTimes[i] > DEFAULT_PLAYLIST_BLACKLIST_MS) { - enabledVariantBlacklistTimes[i] = 0; + enabledVariantBlacklistFlags[i] = false; } } } diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java index 4b52818925..9d4f78e2aa 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java @@ -81,6 +81,7 @@ public class SmoothStreamingChunkSource implements ChunkSource { // Properties of enabled tracks. private Format[] enabledFormats; + private boolean[] adaptiveFormatBlacklistFlags; private IOException fatalError; @@ -171,7 +172,8 @@ public class SmoothStreamingChunkSource implements ChunkSource { } Arrays.sort(enabledFormats, new DecreasingBandwidthComparator()); if (enabledFormats.length > 1) { - adaptiveFormatEvaluator.enable(); + adaptiveFormatEvaluator.enable(enabledFormats); + adaptiveFormatBlacklistFlags = new boolean[tracks.length]; } } @@ -221,7 +223,8 @@ public class SmoothStreamingChunkSource implements ChunkSource { evaluation.queueSize = queue.size(); if (enabledFormats.length > 1) { - adaptiveFormatEvaluator.evaluate(queue, playbackPositionUs, enabledFormats, evaluation); + adaptiveFormatEvaluator.evaluate(queue, playbackPositionUs, 0, adaptiveFormatBlacklistFlags, + evaluation); } else { evaluation.format = enabledFormats[0]; evaluation.trigger = Chunk.TRIGGER_MANUAL; @@ -316,7 +319,7 @@ public class SmoothStreamingChunkSource implements ChunkSource { if (enabledFormats.length > 1) { adaptiveFormatEvaluator.disable(); } - evaluation.format = null; + evaluation.clear(); fatalError = null; } diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.java b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.java index ba2297341b..6511f2d240 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultBandwidthMeter.java @@ -30,15 +30,21 @@ public final class DefaultBandwidthMeter implements BandwidthMeter { public static final int DEFAULT_MAX_WEIGHT = 2000; + private static final int ELAPSED_MILLIS_FOR_ESTIMATE = 2000; + private static final int BYTES_TRANSFERRED_FOR_ESTIMATE = 512 * 1024; + private final Handler eventHandler; private final EventListener eventListener; private final Clock clock; private final SlidingPercentile slidingPercentile; - private long bytesAccumulator; - private long startTimeMs; - private long bitrateEstimate; private int streamCount; + private long sampleStartTimeMs; + private long sampleBytesTransferred; + + private long totalElapsedTimeMs; + private long totalBytesTransferred; + private long bitrateEstimate; public DefaultBandwidthMeter() { this(null, null); @@ -73,34 +79,38 @@ public final class DefaultBandwidthMeter implements BandwidthMeter { @Override public synchronized void onTransferStart() { if (streamCount == 0) { - startTimeMs = clock.elapsedRealtime(); + sampleStartTimeMs = clock.elapsedRealtime(); } streamCount++; } @Override public synchronized void onBytesTransferred(int bytes) { - bytesAccumulator += bytes; + sampleBytesTransferred += bytes; } @Override public synchronized void onTransferEnd() { Assertions.checkState(streamCount > 0); long nowMs = clock.elapsedRealtime(); - int elapsedMs = (int) (nowMs - startTimeMs); - if (elapsedMs > 0) { - float bitsPerSecond = (bytesAccumulator * 8000) / elapsedMs; - slidingPercentile.addSample((int) Math.sqrt(bytesAccumulator), bitsPerSecond); - float bandwidthEstimateFloat = slidingPercentile.getPercentile(0.5f); - bitrateEstimate = Float.isNaN(bandwidthEstimateFloat) ? NO_ESTIMATE - : (long) bandwidthEstimateFloat; - notifyBandwidthSample(elapsedMs, bytesAccumulator, bitrateEstimate); + int sampleElapsedTimeMs = (int) (nowMs - sampleStartTimeMs); + totalElapsedTimeMs += sampleElapsedTimeMs; + totalBytesTransferred += sampleBytesTransferred; + if (sampleElapsedTimeMs > 0) { + float bitsPerSecond = (sampleBytesTransferred * 8000) / sampleElapsedTimeMs; + slidingPercentile.addSample((int) Math.sqrt(sampleBytesTransferred), bitsPerSecond); + if (totalElapsedTimeMs >= ELAPSED_MILLIS_FOR_ESTIMATE + || totalBytesTransferred >= BYTES_TRANSFERRED_FOR_ESTIMATE) { + float bitrateEstimateFloat = slidingPercentile.getPercentile(0.5f); + bitrateEstimate = Float.isNaN(bitrateEstimateFloat) ? NO_ESTIMATE + : (long) bitrateEstimateFloat; + } } - streamCount--; - if (streamCount > 0) { - startTimeMs = nowMs; + notifyBandwidthSample(sampleElapsedTimeMs, sampleBytesTransferred, bitrateEstimate); + if (--streamCount > 0) { + sampleStartTimeMs = nowMs; } - bytesAccumulator = 0; + sampleBytesTransferred = 0; } private void notifyBandwidthSample(final int elapsedMs, final long bytes, final long bitrate) {