implements SampleStream, S
* This method should be called when the stream is no longer required.
*/
public void release() {
- chunkSource.release();
sampleQueue.disable();
loader.release();
}
diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java
index df3654f80d..00865822e1 100644
--- a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java
+++ b/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSource.java
@@ -84,11 +84,4 @@ public interface ChunkSource {
*/
boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e);
- /**
- * Releases the source.
- *
- * This method should be called when the source is no longer required.
- */
- void release();
-
}
diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/FormatEvaluator.java b/library/src/main/java/com/google/android/exoplayer2/source/chunk/FormatEvaluator.java
deleted file mode 100644
index 55a0c2cad6..0000000000
--- a/library/src/main/java/com/google/android/exoplayer2/source/chunk/FormatEvaluator.java
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.source.chunk;
-
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.upstream.BandwidthMeter;
-
-import java.util.List;
-import java.util.Random;
-
-/**
- * Selects from a number of available formats during playback.
- */
-public interface FormatEvaluator {
-
- /**
- * A factory for {@link FormatEvaluator} instances.
- */
- interface Factory {
-
- /**
- * Creates a {@link FormatEvaluator} instance.
- */
- FormatEvaluator createFormatEvaluator();
-
- }
-
- /**
- * Enables the evaluator.
- *
- * @param formats The formats from which to select, ordered by decreasing bandwidth.
- */
- void enable(Format[] formats);
-
- /**
- * Disables the evaluator.
- */
- void disable();
-
- /**
- * Update the supplied evaluation.
- *
- * When called, {@code evaluation} must contain the currently selected format (null for an initial
- * evaluation), the most recent reason ({@link C#SELECTION_REASON_INITIAL} for an initial
- * evaluation) and the most recent evaluation data (null for an initial evaluation).
- *
- * @param bufferedDurationUs The duration of media currently buffered in microseconds.
- * @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.
- */
- void evaluateFormat(long bufferedDurationUs, boolean[] blacklistFlags,
- Evaluation evaluation);
-
- /**
- * Evaluates whether to discard {@link MediaChunk}s from the queue.
- *
- * @param playbackPositionUs The current playback position in microseconds.
- * @param queue The queue of buffered {@link MediaChunk}s.
- * @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}.
- * @return The preferred queue size.
- */
- int evaluateQueueSize(long playbackPositionUs, List extends MediaChunk> queue,
- boolean[] blacklistFlags);
-
- /**
- * A format evaluation.
- */
- final class Evaluation {
-
- /**
- * The selected format.
- */
- public Format format;
-
- /**
- * The sticky reason for the format selection.
- */
- public int reason;
-
- /**
- * Sticky optional data relating to the evaluation.
- */
- public Object data;
-
- public Evaluation() {
- reason = C.SELECTION_REASON_INITIAL;
- }
-
- }
-
- /**
- * Selects randomly between the available formats, excluding those that are blacklisted.
- */
- final class RandomEvaluator implements FormatEvaluator {
-
- public static class Factory implements FormatEvaluator.Factory {
-
- private final int seed;
- private final boolean seedIsSet;
-
- public Factory() {
- seed = 0;
- seedIsSet = false;
- }
-
- public Factory(int seed) {
- this.seed = seed;
- seedIsSet = true;
- }
-
- @Override
- public FormatEvaluator createFormatEvaluator() {
- return seedIsSet ? new RandomEvaluator(seed) : new RandomEvaluator();
- }
-
- }
-
- private final Random random;
-
- private Format[] formats;
-
- public RandomEvaluator() {
- this.random = new Random();
- }
-
- /**
- * @param seed A seed for the underlying random number generator.
- */
- public RandomEvaluator(int seed) {
- this.random = new Random(seed);
- }
-
- @Override
- public void enable(Format[] formats) {
- this.formats = formats;
- }
-
- @Override
- public void disable() {
- formats = null;
- }
-
- @Override
- public void evaluateFormat(long bufferedDurationUs, boolean[] blacklistFlags,
- Evaluation evaluation) {
- // Count the number of non-blacklisted formats.
- int nonBlacklistedFormatCount = 0;
- for (boolean blacklistFlag : blacklistFlags) {
- if (!blacklistFlag) {
- 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.reason = C.SELECTION_REASON_ADAPTIVE;
- }
- evaluation.format = newFormat;
- }
-
- @Override
- public int evaluateQueueSize(long playbackPositionUs, List extends MediaChunk> queue,
- boolean[] blacklistFlags) {
- return queue.size();
- }
-
- }
-
- /**
- * An adaptive evaluator for video formats, which attempts to select the best quality possible
- * given the current network conditions and state of the buffer.
- *
- * This implementation should be used for video only, and should not be used for audio. It is a
- * reference implementation only. It is recommended that application developers implement their
- * own adaptive evaluator to more precisely suit their use case.
- */
- final class AdaptiveEvaluator implements FormatEvaluator {
-
- public static class Factory implements FormatEvaluator.Factory {
-
- private final BandwidthMeter bandwidthMeter;
-
- public Factory(BandwidthMeter bandwidthMeter) {
- this.bandwidthMeter = bandwidthMeter;
- }
-
- @Override
- public FormatEvaluator createFormatEvaluator() {
- return new AdaptiveEvaluator(bandwidthMeter);
- }
-
- }
-
- private static final int DEFAULT_MAX_INITIAL_BITRATE = 800000;
-
- private static final int DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS = 10000;
- private static final int DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS = 25000;
- private static final int DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS = 25000;
- private 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.
- */
- public AdaptiveEvaluator(BandwidthMeter bandwidthMeter) {
- this (bandwidthMeter, DEFAULT_MAX_INITIAL_BITRATE,
- DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
- DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
- DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, DEFAULT_BANDWIDTH_FRACTION);
- }
-
- /**
- * @param bandwidthMeter Provides an estimate of the currently available bandwidth.
- * @param maxInitialBitrate The maximum bitrate in bits per second that should be assumed
- * when bandwidthMeter cannot provide an estimate due to playback having only just started.
- * @param minDurationForQualityIncreaseMs The minimum duration of buffered data required for
- * the evaluator to consider switching to a higher quality format.
- * @param maxDurationForQualityDecreaseMs The maximum duration of buffered data required for
- * the evaluator to consider switching to a lower quality format.
- * @param minDurationToRetainAfterDiscardMs When switching to a significantly higher quality
- * format, the evaluator may discard some of the media that it has already buffered at the
- * lower quality, so as to switch up to the higher quality faster. This is the minimum
- * duration of media that must be retained at the lower quality.
- * @param bandwidthFraction The fraction of the available bandwidth that the evaluator should
- * consider available for use. Setting to a value less than 1 is recommended to account
- * for inaccuracies in the bandwidth estimator.
- */
- public AdaptiveEvaluator(BandwidthMeter bandwidthMeter,
- int maxInitialBitrate,
- int minDurationForQualityIncreaseMs,
- int maxDurationForQualityDecreaseMs,
- int minDurationToRetainAfterDiscardMs,
- float bandwidthFraction) {
- this.bandwidthMeter = bandwidthMeter;
- this.maxInitialBitrate = maxInitialBitrate;
- this.minDurationForQualityIncreaseUs = minDurationForQualityIncreaseMs * 1000L;
- this.maxDurationForQualityDecreaseUs = maxDurationForQualityDecreaseMs * 1000L;
- this.minDurationToRetainAfterDiscardUs = minDurationToRetainAfterDiscardMs * 1000L;
- this.bandwidthFraction = bandwidthFraction;
- }
-
- @Override
- public void enable(Format[] formats) {
- this.formats = formats;
- }
-
- @Override
- public void disable() {
- formats = null;
- }
-
- @Override
- public void evaluateFormat(long bufferedDurationUs, boolean[] blacklistFlags,
- Evaluation evaluation) {
- Format current = evaluation.format;
- Format selected = determineIdealFormat(formats, blacklistFlags,
- bandwidthMeter.getBitrateEstimate());
- if (current != null && isEnabledFormat(current, blacklistFlags)) {
- if (selected.bitrate > current.bitrate
- && bufferedDurationUs < minDurationForQualityIncreaseUs) {
- // The ideal format is a higher quality, but we have insufficient buffer to safely switch
- // up. Defer switching up for now.
- selected = current;
- } else if (selected.bitrate < current.bitrate
- && bufferedDurationUs >= maxDurationForQualityDecreaseUs) {
- // The ideal format is a lower quality, but we have sufficient buffer to defer switching
- // down for now.
- selected = current;
- }
- }
- if (current != null && selected != current) {
- evaluation.reason = C.SELECTION_REASON_ADAPTIVE;
- }
- evaluation.format = selected;
- }
-
- @Override
- public int evaluateQueueSize(long playbackPositionUs, List extends MediaChunk> queue,
- boolean[] blacklistFlags) {
- if (queue.isEmpty()) {
- return 0;
- }
- int queueSize = queue.size();
- long bufferedDurationUs = queue.get(queueSize - 1).endTimeUs - playbackPositionUs;
- if (bufferedDurationUs < minDurationToRetainAfterDiscardUs) {
- return queueSize;
- }
- Format current = queue.get(queueSize - 1).trackFormat;
- Format ideal = determineIdealFormat(formats, blacklistFlags,
- bandwidthMeter.getBitrateEstimate());
- if (ideal.bitrate <= current.bitrate) {
- return queueSize;
- }
- // Discard from the first SD chunk beyond minDurationToRetainAfterDiscardUs whose resolution
- // and bitrate are both lower than the ideal format.
- for (int i = 0; i < queueSize; i++) {
- MediaChunk thisChunk = queue.get(i);
- long durationBeforeThisSegmentUs = thisChunk.startTimeUs - playbackPositionUs;
- if (durationBeforeThisSegmentUs >= minDurationToRetainAfterDiscardUs
- && thisChunk.trackFormat.bitrate < ideal.bitrate
- && thisChunk.trackFormat.height < ideal.height
- && thisChunk.trackFormat.height < 720
- && thisChunk.trackFormat.width < 1280) {
- // Discard chunks from this one onwards.
- return i;
- }
- }
- return queueSize;
- }
-
- /**
- * Compute the ideal format ignoring buffer health.
- */
- 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 (!blacklistFlags[i]) {
- if (format.bitrate <= effectiveBitrate) {
- return format;
- } else {
- lowestBitrateNonBlacklistedIndex = i;
- }
- }
- }
- return formats[lowestBitrateNonBlacklistedIndex];
- }
-
- private boolean isEnabledFormat(Format format, boolean[] blacklistFlags) {
- for (int i = 0; i < formats.length; i++) {
- if (format == formats[i]) {
- return !blacklistFlags[i];
- }
- }
- return false;
- }
-
- }
-
-}
diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java
index b65272e997..ef3e7cdb16 100644
--- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java
+++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java
@@ -222,7 +222,7 @@ import java.util.List;
private ChunkSampleStream buildSampleStream(TrackSelection selection,
long positionUs) {
- int adaptationSetIndex = trackGroups.indexOf(selection.group);
+ int adaptationSetIndex = trackGroups.indexOf(selection.getTrackGroup());
AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex);
DashChunkSource chunkSource = chunkSourceFactory.createDashChunkSource(
manifestLoaderErrorThrower, manifest, index, adaptationSetIndex, selection,
diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java
index 04f1a8d227..0a2d6e0b0e 100644
--- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java
+++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java
@@ -28,8 +28,6 @@ import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper;
import com.google.android.exoplayer2.source.chunk.ChunkHolder;
import com.google.android.exoplayer2.source.chunk.ContainerMediaChunk;
-import com.google.android.exoplayer2.source.chunk.FormatEvaluator;
-import com.google.android.exoplayer2.source.chunk.FormatEvaluator.Evaluation;
import com.google.android.exoplayer2.source.chunk.InitializationChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.chunk.SingleSampleMediaChunk;
@@ -56,25 +54,19 @@ public class DefaultDashChunkSource implements DashChunkSource {
public static final class Factory implements DashChunkSource.Factory {
- private final FormatEvaluator.Factory formatEvaluatorFactory;
private final DataSource.Factory dataSourceFactory;
- public Factory(DataSource.Factory dataSourceFactory,
- FormatEvaluator.Factory formatEvaluatorFactory) {
+ public Factory(DataSource.Factory dataSourceFactory) {
this.dataSourceFactory = dataSourceFactory;
- this.formatEvaluatorFactory = formatEvaluatorFactory;
}
@Override
public DashChunkSource createDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower,
DashManifest manifest, int periodIndex, int adaptationSetIndex,
TrackSelection trackSelection, long elapsedRealtimeOffsetMs) {
- FormatEvaluator adaptiveEvaluator = trackSelection.length > 1
- ? formatEvaluatorFactory.createFormatEvaluator() : null;
DataSource dataSource = dataSourceFactory.createDataSource();
- return new DefaultDashChunkSource(manifestLoaderErrorThrower, manifest, periodIndex,
- adaptationSetIndex, trackSelection, dataSource, adaptiveEvaluator,
- elapsedRealtimeOffsetMs);
+ return new DefaultDashChunkSource(manifestLoaderErrorThrower, manifest, periodIndex,
+ adaptationSetIndex, trackSelection, dataSource, elapsedRealtimeOffsetMs);
}
}
@@ -83,16 +75,12 @@ public class DefaultDashChunkSource implements DashChunkSource {
private final int adaptationSetIndex;
private final TrackSelection trackSelection;
private final RepresentationHolder[] representationHolders;
- private final boolean[] adaptiveFormatBlacklistFlags;
private final DataSource dataSource;
- private final FormatEvaluator adaptiveFormatEvaluator;
private final long elapsedRealtimeOffsetUs;
- private final Evaluation evaluation;
private DashManifest manifest;
private int periodIndex;
- private boolean lastChunkWasInitialization;
private IOException fatalError;
private boolean missingLastSegment;
@@ -103,38 +91,28 @@ public class DefaultDashChunkSource implements DashChunkSource {
* @param adaptationSetIndex The index of the adaptation set in the period.
* @param trackSelection The track selection.
* @param dataSource A {@link DataSource} suitable for loading the media data.
- * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
* server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified
* as the server's unix time minus the local elapsed time. If unknown, set to 0.
*/
public DefaultDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower,
DashManifest manifest, int periodIndex, int adaptationSetIndex, TrackSelection trackSelection,
- DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
- long elapsedRealtimeOffsetMs) {
+ DataSource dataSource, long elapsedRealtimeOffsetMs) {
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
this.manifest = manifest;
this.adaptationSetIndex = adaptationSetIndex;
this.trackSelection = trackSelection;
this.dataSource = dataSource;
- this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
this.periodIndex = periodIndex;
this.elapsedRealtimeOffsetUs = elapsedRealtimeOffsetMs * 1000;
- this.evaluation = new Evaluation();
long periodDurationUs = getPeriodDurationUs();
List representations = getRepresentations();
- representationHolders = new RepresentationHolder[trackSelection.length];
- for (int i = 0; i < trackSelection.length; i++) {
- Representation representation = representations.get(trackSelection.getTrack(i));
+ representationHolders = new RepresentationHolder[trackSelection.length()];
+ for (int i = 0; i < representationHolders.length; i++) {
+ Representation representation = representations.get(trackSelection.getIndexInTrackGroup(i));
representationHolders[i] = new RepresentationHolder(periodDurationUs, representation);
}
- if (adaptiveFormatEvaluator != null) {
- adaptiveFormatEvaluator.enable(trackSelection.getFormats());
- adaptiveFormatBlacklistFlags = new boolean[trackSelection.length];
- } else {
- adaptiveFormatBlacklistFlags = null;
- }
}
@Override
@@ -144,8 +122,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
periodIndex = newPeriodIndex;
long periodDurationUs = getPeriodDurationUs();
List representations = getRepresentations();
- for (int i = 0; i < trackSelection.length; i++) {
- Representation representation = representations.get(trackSelection.getTrack(i));
+ for (int i = 0; i < representationHolders.length; i++) {
+ Representation representation = representations.get(trackSelection.getIndexInTrackGroup(i));
representationHolders[i].updateRepresentation(periodDurationUs, representation);
}
} catch (BehindLiveWindowException e) {
@@ -164,11 +142,10 @@ public class DefaultDashChunkSource implements DashChunkSource {
@Override
public int getPreferredQueueSize(long playbackPositionUs, List extends MediaChunk> queue) {
- if (fatalError != null || trackSelection.length < 2) {
+ if (fatalError != null || trackSelection.length() < 2) {
return queue.size();
}
- return adaptiveFormatEvaluator.evaluateQueueSize(playbackPositionUs, queue,
- adaptiveFormatBlacklistFlags);
+ return trackSelection.evaluateQueueSize(playbackPositionUs, queue);
}
@Override
@@ -177,25 +154,11 @@ public class DefaultDashChunkSource implements DashChunkSource {
return;
}
- if (evaluation.format == null || !lastChunkWasInitialization) {
- if (trackSelection.length > 1) {
- long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
- adaptiveFormatEvaluator.evaluateFormat(bufferedDurationUs, adaptiveFormatBlacklistFlags,
- evaluation);
- } else {
- evaluation.format = trackSelection.getFormat(0);
- evaluation.reason = C.SELECTION_REASON_UNKNOWN;
- evaluation.data = null;
- }
- }
-
- Format selectedFormat = evaluation.format;
- if (selectedFormat == null) {
- return;
- }
+ long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
+ trackSelection.updateSelectedTrack(bufferedDurationUs);
RepresentationHolder representationHolder =
- representationHolders[trackSelection.indexOf(selectedFormat)];
+ representationHolders[trackSelection.getSelectedIndex()];
Representation selectedRepresentation = representationHolder.representation;
DashSegmentIndex segmentIndex = representationHolder.segmentIndex;
@@ -211,9 +174,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
if (pendingInitializationUri != null || pendingIndexUri != null) {
// We have initialization and/or index requests to make.
Chunk initializationChunk = newInitializationChunk(representationHolder, dataSource,
- selectedFormat, evaluation.reason, evaluation.data, pendingInitializationUri,
- pendingIndexUri);
- lastChunkWasInitialization = true;
+ trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(),
+ trackSelection.getSelectionData(), pendingInitializationUri, pendingIndexUri);
out.chunk = initializationChunk;
return;
}
@@ -256,9 +218,9 @@ public class DefaultDashChunkSource implements DashChunkSource {
return;
}
- Chunk nextMediaChunk = newMediaChunk(representationHolder, dataSource, selectedFormat,
- evaluation.reason, evaluation.data, sampleFormat, segmentNum);
- lastChunkWasInitialization = false;
+ Chunk nextMediaChunk = newMediaChunk(representationHolder, dataSource,
+ trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(),
+ trackSelection.getSelectionData(), sampleFormat, segmentNum);
out.chunk = nextMediaChunk;
}
@@ -303,13 +265,6 @@ public class DefaultDashChunkSource implements DashChunkSource {
return false;
}
- @Override
- public void release() {
- if (adaptiveFormatEvaluator != null) {
- adaptiveFormatEvaluator.disable();
- }
- }
-
// Private methods.
private List getRepresentations() {
diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java
index f254f82854..1b37d20ac8 100644
--- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java
+++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java
@@ -27,11 +27,10 @@ import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.chunk.ChunkHolder;
import com.google.android.exoplayer2.source.chunk.DataChunk;
-import com.google.android.exoplayer2.source.chunk.FormatEvaluator;
-import com.google.android.exoplayer2.source.chunk.FormatEvaluator.Evaluation;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser;
import com.google.android.exoplayer2.source.hls.playlist.Variant;
+import com.google.android.exoplayer2.trackselection.BaseTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
@@ -73,8 +72,6 @@ public class HlsChunkSource {
private final String baseUri;
private final DataSource dataSource;
- private final FormatEvaluator formatEvaluator;
- private final Evaluation evaluation;
private final HlsPlaylistParser playlistParser;
private final PtsTimestampAdjusterProvider timestampAdjusterProvider;
private final Variant[] variants;
@@ -82,8 +79,6 @@ public class HlsChunkSource {
private final TrackGroup trackGroup;
private final long[] variantLastPlaylistLoadTimesMs;
- private boolean seenFirstExternalTrackSelection;
- private boolean formatEvaluatorEnabled;
private byte[] scratchSpace;
private boolean live;
private long durationUs;
@@ -94,10 +89,10 @@ public class HlsChunkSource {
private String encryptionIvString;
private byte[] encryptionIv;
- // Properties of enabled variants.
+ // Note: The track group in the selection is typically *not* equal to trackGroup. This is due to
+ // the way in which HlsSampleStreamWrapper generates track groups. Use only index based methods
+ // in TrackSelection to avoid unexpected behavior.
private TrackSelection trackSelection;
- private long[] enabledVariantBlacklistTimes;
- private boolean[] enabledVariantBlacklistFlags;
/**
* @param baseUri The playlist's base uri.
@@ -106,17 +101,14 @@ public class HlsChunkSource {
* @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 formatEvaluator For adaptive tracks, selects from the available formats.
*/
public HlsChunkSource(String baseUri, Variant[] variants, DataSource dataSource,
- PtsTimestampAdjusterProvider timestampAdjusterProvider, FormatEvaluator formatEvaluator) {
+ PtsTimestampAdjusterProvider timestampAdjusterProvider) {
this.baseUri = baseUri;
this.variants = variants;
this.dataSource = dataSource;
- this.formatEvaluator = formatEvaluator;
this.timestampAdjusterProvider = timestampAdjusterProvider;
playlistParser = new HlsPlaylistParser();
- evaluation = new Evaluation();
variantPlaylists = new HlsMediaPlaylist[variants.length];
variantLastPlaylistLoadTimesMs = new long[variants.length];
@@ -127,7 +119,7 @@ public class HlsChunkSource {
initialTrackSelection[i] = i;
}
trackGroup = new TrackGroup(variantFormats);
- selectTracksInternal(new TrackSelection(trackGroup, initialTrackSelection), false);
+ trackSelection = new InitializationTrackSelection(trackGroup, initialTrackSelection);
}
/**
@@ -169,7 +161,7 @@ public class HlsChunkSource {
* @param trackSelection The track selection.
*/
public void selectTracks(TrackSelection trackSelection) {
- selectTracksInternal(trackSelection, true);
+ this.trackSelection = trackSelection;
}
/**
@@ -195,13 +187,14 @@ public class HlsChunkSource {
public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, ChunkHolder out) {
int previousChunkVariantIndex = previous != null ? trackGroup.indexOf(previous.trackFormat)
: -1;
- updateFormatEvaluation(previous, playbackPositionUs);
- int newVariantIndex = trackGroup.indexOf(evaluation.format);
+ updateSelectedTrack(previous, playbackPositionUs);
+ int newVariantIndex = trackSelection.getSelectedIndexInTrackGroup();
boolean switchingVariant = previousChunkVariantIndex != newVariantIndex;
HlsMediaPlaylist mediaPlaylist = variantPlaylists[newVariantIndex];
if (mediaPlaylist == null) {
// We don't have the media playlist for the next variant. Request it now.
- out.chunk = newMediaPlaylistChunk(newVariantIndex, evaluation.reason, evaluation.data);
+ out.chunk = newMediaPlaylistChunk(newVariantIndex, trackSelection.getSelectionReason(),
+ trackSelection.getSelectionData());
return;
}
@@ -236,7 +229,8 @@ public class HlsChunkSource {
if (!mediaPlaylist.live) {
out.endOfStream = true;
} else if (shouldRerequestLiveMediaPlaylist(newVariantIndex)) {
- out.chunk = newMediaPlaylistChunk(newVariantIndex, evaluation.reason, evaluation.data);
+ out.chunk = newMediaPlaylistChunk(newVariantIndex,
+ trackSelection.getSelectionReason(), trackSelection.getSelectionData());
}
return;
}
@@ -250,7 +244,7 @@ public class HlsChunkSource {
if (!keyUri.equals(encryptionKeyUri)) {
// Encryption is specified and the key has changed.
out.chunk = newEncryptionKeyChunk(keyUri, segment.encryptionIV, newVariantIndex,
- evaluation.reason, evaluation.data);
+ trackSelection.getSelectionReason(), trackSelection.getSelectionData());
return;
}
if (!Util.areEqual(segment.encryptionIV, encryptionIvString)) {
@@ -332,7 +326,8 @@ public class HlsChunkSource {
extractorNeedsInit = false;
}
- out.chunk = new HlsMediaChunk(dataSource, dataSpec, format, evaluation.reason, evaluation.data,
+ out.chunk = new HlsMediaChunk(dataSource, dataSpec, format,
+ trackSelection.getSelectionReason(), trackSelection.getSelectionData(),
startTimeUs, endTimeUs, chunkMediaSequence, segment.discontinuitySequenceNumber, extractor,
extractorNeedsInit, switchingVariant, encryptionKey, encryptionIv);
}
@@ -412,86 +407,29 @@ public class HlsChunkSource {
InvalidResponseCodeException responseCodeException = (InvalidResponseCodeException) e;
int responseCode = responseCodeException.responseCode;
if (responseCode == 404 || responseCode == 410) {
- int enabledVariantIndex = trackSelection.indexOf(chunk.trackFormat);
- boolean alreadyBlacklisted = enabledVariantBlacklistFlags[enabledVariantIndex];
- enabledVariantBlacklistFlags[enabledVariantIndex] = true;
- enabledVariantBlacklistTimes[enabledVariantIndex] = SystemClock.elapsedRealtime();
- if (alreadyBlacklisted) {
- // The playlist was already blacklisted.
- Log.w(TAG, "Already blacklisted variant (" + responseCode + "): "
- + chunk.dataSpec.uri);
- return false;
- } else if (!allEnabledVariantsBlacklisted()) {
- // We've handled the 404/410 by blacklisting the variant.
- Log.w(TAG, "Blacklisted variant (" + responseCode + "): "
- + chunk.dataSpec.uri);
- return true;
- } else {
- // This was the last non-blacklisted playlist. Don't blacklist it.
- Log.w(TAG, "Final variant not blacklisted (" + responseCode + "): "
- + chunk.dataSpec.uri);
- enabledVariantBlacklistFlags[enabledVariantIndex] = false;
- return false;
+ int trackSelectionIndex = trackSelection.indexOf(trackGroup.indexOf(chunk.trackFormat));
+ if (trackSelectionIndex != -1) {
+ boolean blacklisted = trackSelection.blacklist(trackSelectionIndex,
+ DEFAULT_PLAYLIST_BLACKLIST_MS);
+ if (blacklisted) {
+ // We've handled the 404/410 by blacklisting the variant.
+ Log.w(TAG, "Blacklisted variant (" + responseCode + "): " + chunk.dataSpec.uri);
+ return true;
+ } else {
+ // This was the last non-blacklisted playlist. Don't blacklist it.
+ Log.w(TAG, "Final variant not blacklisted (" + responseCode + "): "
+ + chunk.dataSpec.uri);
+ return false;
+ }
}
}
}
return false;
}
- public void release() {
- disableFormatEvaluator();
- }
-
// Private methods.
- private void selectTracksInternal(TrackSelection trackSelection, boolean isExternal) {
- this.trackSelection = trackSelection;
- seenFirstExternalTrackSelection |= isExternal;
-
- // Reset the enabled variant blacklist flags.
- enabledVariantBlacklistTimes = new long[trackSelection.length];
- enabledVariantBlacklistFlags = new boolean[trackSelection.length];
-
- if (!isExternal) {
- return;
- }
-
- disableFormatEvaluator();
- if (trackSelection.length > 1) {
- Format[] formats = trackSelection.getFormats();
- formatEvaluator.enable(formats);
- formatEvaluatorEnabled = true;
- if (!Util.contains(formats, evaluation.format)) {
- evaluation.format = null;
- }
- } else {
- evaluation.reason = C.SELECTION_REASON_UNKNOWN;
- evaluation.data = null;
- }
- }
-
- private void updateFormatEvaluation(HlsMediaChunk previous, long playbackPositionUs) {
- clearStaleBlacklistedVariants();
- if (!seenFirstExternalTrackSelection) {
- if (!enabledVariantBlacklistFlags[trackSelection.indexOf(variants[0].format)]) {
- // Use the first variant prior to external track selection, unless it's been blacklisted.
- evaluation.format = variants[0].format;
- return;
- }
- // Try from lowest bitrate to highest.
- for (int i = trackSelection.length - 1; i >= 0; i--) {
- if (!enabledVariantBlacklistFlags[i]) {
- evaluation.format = trackSelection.getFormat(i);
- return;
- }
- }
- // Should never happen.
- throw new IllegalStateException();
- }
- if (trackSelection.length == 1) {
- evaluation.format = trackSelection.getFormat(0);
- return;
- }
+ private void updateSelectedTrack(HlsMediaChunk previous, long playbackPositionUs) {
long bufferedDurationUs;
if (previous != null) {
// Use start time of the previous chunk rather than its end time because switching format
@@ -500,7 +438,7 @@ public class HlsChunkSource {
} else {
bufferedDurationUs = 0;
}
- formatEvaluator.evaluateFormat(bufferedDurationUs, enabledVariantBlacklistFlags, evaluation);
+ trackSelection.updateSelectedTrack(bufferedDurationUs);
}
private boolean shouldRerequestLiveMediaPlaylist(int variantIndex) {
@@ -562,34 +500,54 @@ public class HlsChunkSource {
durationUs = live ? C.UNSET_TIME_US : mediaPlaylist.durationUs;
}
- private boolean allEnabledVariantsBlacklisted() {
- for (boolean enabledVariantBlacklistFlag : enabledVariantBlacklistFlags) {
- if (!enabledVariantBlacklistFlag) {
- return false;
- }
- }
- return true;
- }
-
- private void clearStaleBlacklistedVariants() {
- long currentTime = SystemClock.elapsedRealtime();
- for (int i = 0; i < enabledVariantBlacklistFlags.length; i++) {
- if (enabledVariantBlacklistFlags[i]
- && currentTime - enabledVariantBlacklistTimes[i] > DEFAULT_PLAYLIST_BLACKLIST_MS) {
- enabledVariantBlacklistFlags[i] = false;
- }
- }
- }
-
- private void disableFormatEvaluator() {
- if (formatEvaluatorEnabled) {
- formatEvaluator.disable();
- formatEvaluatorEnabled = false;
- }
- }
-
// Private classes.
+ /**
+ * A {@link TrackSelection} to use for initialization.
+ */
+ public static final class InitializationTrackSelection extends BaseTrackSelection {
+
+ private int selectedIndex;
+
+ public InitializationTrackSelection(TrackGroup group, int[] tracks) {
+ super(group, tracks);
+ selectedIndex = indexOf(group.getFormat(0));
+ }
+
+ @Override
+ public void updateSelectedTrack(long bufferedDurationUs) {
+ long nowMs = SystemClock.elapsedRealtime();
+ if (!isBlacklisted(selectedIndex, nowMs)) {
+ return;
+ }
+ // Try from lowest bitrate to highest.
+ for (int i = length - 1; i >= 0; i--) {
+ if (!isBlacklisted(i, nowMs)) {
+ selectedIndex = i;
+ return;
+ }
+ }
+ // Should never happen.
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public int getSelectedIndex() {
+ return selectedIndex;
+ }
+
+ @Override
+ public int getSelectionReason() {
+ return C.SELECTION_REASON_UNKNOWN;
+ }
+
+ @Override
+ public Object getSelectionData() {
+ return null;
+ }
+
+ }
+
private static final class MediaPlaylistChunk extends DataChunk {
public final int variantIndex;
diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
index ae2c85acec..74e649cc9c 100644
--- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
+++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
@@ -28,7 +28,6 @@ import com.google.android.exoplayer2.source.SinglePeriodTimeline;
import com.google.android.exoplayer2.source.Timeline;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
-import com.google.android.exoplayer2.source.chunk.FormatEvaluator;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist;
@@ -64,7 +63,6 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
private final Uri manifestUri;
private final DataSource.Factory dataSourceFactory;
- private final FormatEvaluator.Factory formatEvaluatorFactory;
private final int minLoadableRetryCount;
private final EventDispatcher eventDispatcher;
private final IdentityHashMap sampleStreamSources;
@@ -89,19 +87,17 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
private HlsSampleStreamWrapper[] enabledSampleStreamWrappers;
private CompositeSequenceableLoader sequenceableLoader;
- public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory,
- FormatEvaluator.Factory formatEvaluatorFactory, Handler eventHandler,
+ public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) {
- this(manifestUri, dataSourceFactory, formatEvaluatorFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT,
- eventHandler, eventListener);
+ this(manifestUri, dataSourceFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, eventHandler,
+ eventListener);
}
public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory,
- FormatEvaluator.Factory formatEvaluatorFactory, int minLoadableRetryCount,
- Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) {
+ int minLoadableRetryCount, Handler eventHandler,
+ AdaptiveMediaSourceEventListener eventListener) {
this.manifestUri = manifestUri;
this.dataSourceFactory = dataSourceFactory;
- this.formatEvaluatorFactory = formatEvaluatorFactory;
this.minLoadableRetryCount = minLoadableRetryCount;
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
@@ -352,7 +348,7 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
Format.NO_VALUE);
Variant[] variants = new Variant[] {new Variant(playlist.baseUri, format, null)};
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants,
- formatEvaluatorFactory.createFormatEvaluator(), null, null));
+ null, null));
return sampleStreamWrappers;
}
@@ -386,8 +382,7 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
Variant[] variants = new Variant[selectedVariants.size()];
selectedVariants.toArray(variants);
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants,
- formatEvaluatorFactory.createFormatEvaluator(), masterPlaylist.muxedAudioFormat,
- masterPlaylist.muxedCaptionFormat));
+ masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat));
}
// Build the audio stream wrapper if applicable.
@@ -396,7 +391,7 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
Variant[] variants = new Variant[audioVariants.size()];
audioVariants.toArray(variants);
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO, baseUri, variants, null,
- null, null));
+ null));
}
// Build the text stream wrapper if applicable.
@@ -405,18 +400,17 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
Variant[] variants = new Variant[subtitleVariants.size()];
subtitleVariants.toArray(variants);
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_TEXT, baseUri, variants, null,
- null, null));
+ null));
}
return sampleStreamWrappers;
}
private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, String baseUri,
- Variant[] variants, FormatEvaluator formatEvaluator, Format muxedAudioFormat,
- Format muxedCaptionFormat) {
+ Variant[] variants, Format muxedAudioFormat, Format muxedCaptionFormat) {
DataSource dataSource = dataSourceFactory.createDataSource();
HlsChunkSource defaultChunkSource = new HlsChunkSource(baseUri, variants, dataSource,
- timestampAdjusterProvider, formatEvaluator);
+ timestampAdjusterProvider);
return new HlsSampleStreamWrapper(trackType, this, defaultChunkSource, allocator,
preparePositionUs, muxedAudioFormat, muxedCaptionFormat, minLoadableRetryCount,
eventDispatcher);
@@ -440,7 +434,7 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
int[] newSelectionOriginalIndices = new int[allNewSelections.size()];
for (int i = 0; i < allNewSelections.size(); i++) {
TrackSelection selection = allNewSelections.get(i);
- if (sampleStreamWrapperTrackGroups.indexOf(selection.group) != -1) {
+ if (sampleStreamWrapperTrackGroups.indexOf(selection.getTrackGroup()) != -1) {
newSelectionOriginalIndices[newSelections.size()] = i;
newSelections.add(selection);
}
diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java
index 52865d38c7..0547231d23 100644
--- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java
+++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java
@@ -168,11 +168,10 @@ import java.util.List;
SampleStream[] newStreams = new SampleStream[newSelections.size()];
for (int i = 0; i < newStreams.length; i++) {
TrackSelection selection = newSelections.get(i);
- int group = trackGroups.indexOf(selection.group);
- int[] tracks = selection.getTracks();
+ int group = trackGroups.indexOf(selection.getTrackGroup());
setTrackGroupEnabledState(group, true);
if (group == primaryTrackGroupIndex) {
- chunkSource.selectTracks(new TrackSelection(chunkSource.getTrackGroup(), tracks));
+ chunkSource.selectTracks(selection);
}
newStreams[i] = new SampleStreamImpl(group);
}
@@ -236,7 +235,6 @@ import java.util.List;
}
public void release() {
- chunkSource.release();
int sampleQueueCount = sampleQueues.size();
for (int i = 0; i < sampleQueueCount; i++) {
sampleQueues.valueAt(i).disable();
diff --git a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java
index bc2db315de..c4fcd98344 100644
--- a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java
+++ b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java
@@ -25,8 +25,6 @@ import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper;
import com.google.android.exoplayer2.source.chunk.ChunkHolder;
import com.google.android.exoplayer2.source.chunk.ContainerMediaChunk;
-import com.google.android.exoplayer2.source.chunk.FormatEvaluator;
-import com.google.android.exoplayer2.source.chunk.FormatEvaluator.Evaluation;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
@@ -47,24 +45,19 @@ public class DefaultSsChunkSource implements SsChunkSource {
public static final class Factory implements SsChunkSource.Factory {
- private final FormatEvaluator.Factory formatEvaluatorFactory;
private final DataSource.Factory dataSourceFactory;
- public Factory(DataSource.Factory dataSourceFactory,
- FormatEvaluator.Factory formatEvaluatorFactory) {
+ public Factory(DataSource.Factory dataSourceFactory) {
this.dataSourceFactory = dataSourceFactory;
- this.formatEvaluatorFactory = formatEvaluatorFactory;
}
@Override
public SsChunkSource createChunkSource(LoaderErrorThrower manifestLoaderErrorThrower,
SsManifest manifest, int elementIndex, TrackSelection trackSelection,
TrackEncryptionBox[] trackEncryptionBoxes) {
- FormatEvaluator adaptiveEvaluator = trackSelection.length > 1
- ? formatEvaluatorFactory.createFormatEvaluator() : null;
DataSource dataSource = dataSourceFactory.createDataSource();
return new DefaultSsChunkSource(manifestLoaderErrorThrower, manifest, elementIndex,
- trackSelection, dataSource, adaptiveEvaluator, trackEncryptionBoxes);
+ trackSelection, dataSource, trackEncryptionBoxes);
}
}
@@ -73,10 +66,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
private final int elementIndex;
private final TrackSelection trackSelection;
private final ChunkExtractorWrapper[] extractorWrappers;
- private final boolean[] adaptiveFormatBlacklistFlags;
private final DataSource dataSource;
- private final Evaluation evaluation;
- private final FormatEvaluator adaptiveFormatEvaluator;
private SsManifest manifest;
private int currentManifestChunkOffset;
@@ -89,26 +79,23 @@ public class DefaultSsChunkSource implements SsChunkSource {
* @param elementIndex The index of the stream element in the manifest.
* @param trackSelection The track selection.
* @param dataSource A {@link DataSource} suitable for loading the media data.
- * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
* @param trackEncryptionBoxes Track encryption boxes for the stream.
*/
public DefaultSsChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, SsManifest manifest,
int elementIndex, TrackSelection trackSelection, DataSource dataSource,
- FormatEvaluator adaptiveFormatEvaluator, TrackEncryptionBox[] trackEncryptionBoxes) {
+ TrackEncryptionBox[] trackEncryptionBoxes) {
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
this.manifest = manifest;
this.elementIndex = elementIndex;
this.trackSelection = trackSelection;
this.dataSource = dataSource;
- this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
- this.evaluation = new Evaluation();
StreamElement streamElement = manifest.streamElements[elementIndex];
- extractorWrappers = new ChunkExtractorWrapper[trackSelection.length];
- for (int i = 0; i < trackSelection.length; i++) {
- int manifestTrackIndex = trackSelection.getTrack(i);
- Format format = trackSelection.getFormat(i);
+ extractorWrappers = new ChunkExtractorWrapper[trackSelection.length()];
+ for (int i = 0; i < extractorWrappers.length; i++) {
+ int manifestTrackIndex = trackSelection.getIndexInTrackGroup(i);
+ Format format = streamElement.formats[i];
int nalUnitLengthFieldLength = streamElement.type == C.TRACK_TYPE_VIDEO ? 4 : -1;
Track track = new Track(manifestTrackIndex, streamElement.type, streamElement.timescale,
C.UNSET_TIME_US, manifest.durationUs, format, Track.TRANSFORMATION_NONE,
@@ -118,12 +105,6 @@ public class DefaultSsChunkSource implements SsChunkSource {
| FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, track);
extractorWrappers[i] = new ChunkExtractorWrapper(extractor, format, false);
}
- if (adaptiveFormatEvaluator != null) {
- adaptiveFormatEvaluator.enable(trackSelection.getFormats());
- adaptiveFormatBlacklistFlags = new boolean[trackSelection.length];
- } else {
- adaptiveFormatBlacklistFlags = null;
- }
}
@Override
@@ -162,11 +143,10 @@ public class DefaultSsChunkSource implements SsChunkSource {
@Override
public int getPreferredQueueSize(long playbackPositionUs, List extends MediaChunk> queue) {
- if (fatalError != null || trackSelection.length < 2) {
+ if (fatalError != null || trackSelection.length() < 2) {
return queue.size();
}
- return adaptiveFormatEvaluator.evaluateQueueSize(playbackPositionUs, queue,
- adaptiveFormatBlacklistFlags);
+ return trackSelection.evaluateQueueSize(playbackPositionUs, queue);
}
@Override
@@ -175,20 +155,8 @@ public class DefaultSsChunkSource implements SsChunkSource {
return;
}
- if (trackSelection.length > 1) {
- long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
- adaptiveFormatEvaluator.evaluateFormat(bufferedDurationUs, adaptiveFormatBlacklistFlags,
- evaluation);
- } else {
- evaluation.format = trackSelection.getFormat(0);
- evaluation.reason = C.SELECTION_REASON_UNKNOWN;
- evaluation.data = null;
- }
-
- Format selectedFormat = evaluation.format;
- if (selectedFormat == null) {
- return;
- }
+ long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
+ trackSelection.updateSelectedTrack(bufferedDurationUs);
StreamElement streamElement = manifest.streamElements[elementIndex];
if (streamElement.chunkCount == 0) {
@@ -219,14 +187,15 @@ public class DefaultSsChunkSource implements SsChunkSource {
long chunkEndTimeUs = chunkStartTimeUs + streamElement.getChunkDurationUs(chunkIndex);
int currentAbsoluteChunkIndex = chunkIndex + currentManifestChunkOffset;
- int trackSelectionIndex = trackSelection.indexOf(selectedFormat);
+ int trackSelectionIndex = trackSelection.getSelectedIndex();
ChunkExtractorWrapper extractorWrapper = extractorWrappers[trackSelectionIndex];
- int manifestTrackIndex = trackSelection.getTrack(trackSelectionIndex);
+ int manifestTrackIndex = trackSelection.getIndexInTrackGroup(trackSelectionIndex);
Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex);
- out.chunk = newMediaChunk(selectedFormat, dataSource, uri, null, currentAbsoluteChunkIndex,
- chunkStartTimeUs, chunkEndTimeUs, evaluation.reason, evaluation.data, extractorWrapper);
+ out.chunk = newMediaChunk(trackSelection.getSelectedFormat(), dataSource, uri, null,
+ currentAbsoluteChunkIndex, chunkStartTimeUs, chunkEndTimeUs,
+ trackSelection.getSelectionReason(), trackSelection.getSelectionData(), extractorWrapper);
}
@Override
@@ -240,13 +209,6 @@ public class DefaultSsChunkSource implements SsChunkSource {
return false;
}
- @Override
- public void release() {
- if (adaptiveFormatEvaluator != null) {
- adaptiveFormatEvaluator.disable();
- }
- }
-
// Private methods.
private static MediaChunk newMediaChunk(Format format, DataSource dataSource, Uri uri,
diff --git a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java
index ad4f7696db..6ca7772c05 100644
--- a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java
+++ b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaPeriod.java
@@ -199,7 +199,7 @@ import java.util.List;
private ChunkSampleStream buildSampleStream(TrackSelection selection,
long positionUs) {
- int streamElementIndex = trackGroups.indexOf(selection.group);
+ int streamElementIndex = trackGroups.indexOf(selection.getTrackGroup());
SsChunkSource chunkSource = chunkSourceFactory.createChunkSource(manifestLoaderErrorThrower,
manifest, streamElementIndex, selection, trackEncryptionBoxes);
return new ChunkSampleStream<>(manifest.streamElements[streamElementIndex].type, chunkSource,
diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveVideoTrackSelection.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveVideoTrackSelection.java
new file mode 100644
index 0000000000..99db909ffd
--- /dev/null
+++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveVideoTrackSelection.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.trackselection;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.source.TrackGroup;
+import com.google.android.exoplayer2.source.chunk.MediaChunk;
+import com.google.android.exoplayer2.upstream.BandwidthMeter;
+
+import android.os.SystemClock;
+
+import java.util.List;
+
+/**
+ * A bandwidth based adaptive {@link TrackSelection} for video, whose selected track is updated to
+ * be the one of highest quality given the current network conditions and the state of the buffer.
+ */
+public class AdaptiveVideoTrackSelection extends BaseTrackSelection {
+
+ /**
+ * Factory for {@link AdaptiveVideoTrackSelection} instances.
+ */
+ public static final class Factory implements TrackSelection.Factory {
+
+ private final BandwidthMeter bandwidthMeter;
+ private final int maxInitialBitrate;
+ private final int minDurationForQualityIncreaseMs;
+ private final int maxDurationForQualityDecreaseMs;
+ private final int minDurationToRetainAfterDiscardMs;
+ private final float bandwidthFraction;
+
+ /**
+ * @param bandwidthMeter Provides an estimate of the currently available bandwidth.
+ */
+ public Factory(BandwidthMeter bandwidthMeter) {
+ this (bandwidthMeter, DEFAULT_MAX_INITIAL_BITRATE,
+ DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
+ DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
+ DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, DEFAULT_BANDWIDTH_FRACTION);
+ }
+
+ /**
+ * @param bandwidthMeter Provides an estimate of the currently available bandwidth.
+ * @param maxInitialBitrate The maximum bitrate in bits per second that should be assumed
+ * when a bandwidth estimate is unavailable.
+ * @param minDurationForQualityIncreaseMs The minimum duration of buffered data required for
+ * the selected track to switch to one of higher quality.
+ * @param maxDurationForQualityDecreaseMs The maximum duration of buffered data required for
+ * the selected track to switch to one of lower quality.
+ * @param minDurationToRetainAfterDiscardMs When switching to a track of significantly higher
+ * quality, the selection may indicate that media already buffered at the lower quality can
+ * be discarded to speed up the switch. This is the minimum duration of media that must be
+ * retained at the lower quality.
+ * @param bandwidthFraction The fraction of the available bandwidth that the selection should
+ * consider available for use. Setting to a value less than 1 is recommended to account
+ * for inaccuracies in the bandwidth estimator.
+ */
+ public Factory(BandwidthMeter bandwidthMeter, int maxInitialBitrate,
+ int minDurationForQualityIncreaseMs, int maxDurationForQualityDecreaseMs,
+ int minDurationToRetainAfterDiscardMs, float bandwidthFraction) {
+ this.bandwidthMeter = bandwidthMeter;
+ this.maxInitialBitrate = maxInitialBitrate;
+ this.minDurationForQualityIncreaseMs = minDurationForQualityIncreaseMs;
+ this.maxDurationForQualityDecreaseMs = maxDurationForQualityDecreaseMs;
+ this.minDurationToRetainAfterDiscardMs = minDurationToRetainAfterDiscardMs;
+ this.bandwidthFraction = bandwidthFraction;
+ }
+
+ @Override
+ public AdaptiveVideoTrackSelection createTrackSelection(TrackGroup group, int... tracks) {
+ return new AdaptiveVideoTrackSelection(group, tracks, bandwidthMeter, maxInitialBitrate,
+ minDurationForQualityIncreaseMs, maxDurationForQualityDecreaseMs,
+ minDurationToRetainAfterDiscardMs, bandwidthFraction);
+ }
+
+ }
+
+ public static final int DEFAULT_MAX_INITIAL_BITRATE = 800000;
+ public static final int DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS = 10000;
+ public static final int DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS = 25000;
+ public static final int DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS = 25000;
+ 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 int selectedIndex;
+ private int reason;
+
+ /**
+ * @param group The {@link TrackGroup}. Must not be null.
+ * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be
+ * null or empty. May be in any order.
+ * @param bandwidthMeter Provides an estimate of the currently available bandwidth.
+ */
+ public AdaptiveVideoTrackSelection(TrackGroup group, int[] tracks,
+ BandwidthMeter bandwidthMeter) {
+ this (group, tracks, bandwidthMeter, DEFAULT_MAX_INITIAL_BITRATE,
+ DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS,
+ DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS,
+ DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, DEFAULT_BANDWIDTH_FRACTION);
+ }
+
+ /**
+ * @param group The {@link TrackGroup}. Must not be null.
+ * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be
+ * null or empty. May be in any order.
+ * @param bandwidthMeter Provides an estimate of the currently available bandwidth.
+ * @param maxInitialBitrate The maximum bitrate in bits per second that should be assumed when a
+ * bandwidth estimate is unavailable.
+ * @param minDurationForQualityIncreaseMs The minimum duration of buffered data required for the
+ * selected track to switch to one of higher quality.
+ * @param maxDurationForQualityDecreaseMs The maximum duration of buffered data required for the
+ * selected track to switch to one of lower quality.
+ * @param minDurationToRetainAfterDiscardMs When switching to a track of significantly higher
+ * quality, the selection may indicate that media already buffered at the lower quality can
+ * be discarded to speed up the switch. This is the minimum duration of media that must be
+ * retained at the lower quality.
+ * @param bandwidthFraction The fraction of the available bandwidth that the selection should
+ * consider available for use. Setting to a value less than 1 is recommended to account
+ * for inaccuracies in the bandwidth estimator.
+ */
+ public AdaptiveVideoTrackSelection(TrackGroup group, int[] tracks, BandwidthMeter bandwidthMeter,
+ int maxInitialBitrate, int minDurationForQualityIncreaseMs,
+ int maxDurationForQualityDecreaseMs, int minDurationToRetainAfterDiscardMs,
+ float bandwidthFraction) {
+ super(group, tracks);
+ this.bandwidthMeter = bandwidthMeter;
+ this.maxInitialBitrate = maxInitialBitrate;
+ this.minDurationForQualityIncreaseUs = minDurationForQualityIncreaseMs * 1000L;
+ this.maxDurationForQualityDecreaseUs = maxDurationForQualityDecreaseMs * 1000L;
+ this.minDurationToRetainAfterDiscardUs = minDurationToRetainAfterDiscardMs * 1000L;
+ this.bandwidthFraction = bandwidthFraction;
+ selectedIndex = determineIdealSelectedIndex(Long.MIN_VALUE);
+ reason = C.SELECTION_REASON_INITIAL;
+ }
+
+ @Override
+ public void updateSelectedTrack(long bufferedDurationUs) {
+ long nowMs = SystemClock.elapsedRealtime();
+ // Get the current and ideal selections.
+ int currentSelectedIndex = selectedIndex;
+ Format currentFormat = getSelectedFormat();
+ int idealSelectedIndex = determineIdealSelectedIndex(nowMs);
+ Format idealFormat = getFormat(idealSelectedIndex);
+ // Assume we can switch to the ideal selection.
+ selectedIndex = idealSelectedIndex;
+ // Revert back to the current selection if conditions are not suitable for switching.
+ if (currentFormat != null && !isBlacklisted(selectedIndex, nowMs)) {
+ if (idealFormat.bitrate > currentFormat.bitrate
+ && bufferedDurationUs < minDurationForQualityIncreaseUs) {
+ // The ideal track is a higher quality, but we have insufficient buffer to safely switch
+ // up. Defer switching up for now.
+ selectedIndex = currentSelectedIndex;
+ } else if (idealFormat.bitrate < currentFormat.bitrate
+ && bufferedDurationUs >= maxDurationForQualityDecreaseUs) {
+ // The ideal track is a lower quality, but we have sufficient buffer to defer switching
+ // down for now.
+ selectedIndex = currentSelectedIndex;
+ }
+ }
+ // If we adapted, update the trigger.
+ if (selectedIndex != currentSelectedIndex) {
+ reason = C.SELECTION_REASON_ADAPTIVE;
+ }
+ }
+
+ @Override
+ public int getSelectedIndex() {
+ return selectedIndex;
+ }
+
+ @Override
+ public int getSelectionReason() {
+ return reason;
+ }
+
+ @Override
+ public Object getSelectionData() {
+ return null;
+ }
+
+ @Override
+ public int evaluateQueueSize(long playbackPositionUs, List extends MediaChunk> queue) {
+ if (queue.isEmpty()) {
+ return 0;
+ }
+ int queueSize = queue.size();
+ long bufferedDurationUs = queue.get(queueSize - 1).endTimeUs - playbackPositionUs;
+ if (bufferedDurationUs < minDurationToRetainAfterDiscardUs) {
+ return queueSize;
+ }
+ int idealSelectedIndex = determineIdealSelectedIndex(SystemClock.elapsedRealtime());
+ Format idealFormat = getFormat(idealSelectedIndex);
+ // Discard from the first SD chunk beyond minDurationToRetainAfterDiscardUs whose resolution and
+ // bitrate are both lower than the ideal track.
+ for (int i = 0; i < queueSize; i++) {
+ MediaChunk chunk = queue.get(i);
+ long durationBeforeThisChunkUs = chunk.startTimeUs - playbackPositionUs;
+ if (durationBeforeThisChunkUs >= minDurationToRetainAfterDiscardUs
+ && chunk.trackFormat.bitrate < idealFormat.bitrate
+ && chunk.trackFormat.height < idealFormat.height
+ && chunk.trackFormat.height < 720 && chunk.trackFormat.width < 1280) {
+ return i;
+ }
+ }
+ return queueSize;
+ }
+
+ /**
+ * Computes the ideal selected index ignoring buffer health.
+ *
+ * @param nowMs The current time in the timebase of {@link SystemClock#elapsedRealtime()}, or
+ * {@link Long#MIN_VALUE} to ignore blacklisting.
+ */
+ private int determineIdealSelectedIndex(long nowMs) {
+ long bitrateEstimate = bandwidthMeter.getBitrateEstimate();
+ long effectiveBitrate = bitrateEstimate == BandwidthMeter.NO_ESTIMATE
+ ? maxInitialBitrate : (long) (bitrateEstimate * bandwidthFraction);
+ int lowestBitrateNonBlacklistedIndex = 0;
+ for (int i = 0; i < length; i++) {
+ if (nowMs == Long.MIN_VALUE || !isBlacklisted(i, nowMs)) {
+ Format format = getFormat(i);
+ if (format.bitrate <= effectiveBitrate) {
+ return i;
+ } else {
+ lowestBitrateNonBlacklistedIndex = i;
+ }
+ }
+ }
+ return lowestBitrateNonBlacklistedIndex;
+ }
+
+}
diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java
new file mode 100644
index 0000000000..8e9303a178
--- /dev/null
+++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/BaseTrackSelection.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.trackselection;
+
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.Format.DecreasingBandwidthComparator;
+import com.google.android.exoplayer2.source.TrackGroup;
+import com.google.android.exoplayer2.source.chunk.MediaChunk;
+import com.google.android.exoplayer2.util.Assertions;
+
+import android.os.SystemClock;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * An abstract base class suitable for most {@link TrackSelection} implementations.
+ */
+public abstract class BaseTrackSelection implements TrackSelection {
+
+ /**
+ * The selected {@link TrackGroup}.
+ */
+ protected final TrackGroup group;
+ /**
+ * The number of selected tracks within the {@link TrackGroup}. Always greater than zero.
+ */
+ protected final int length;
+
+ /**
+ * The indices of the selected tracks in {@link #group}, in order of decreasing bandwidth.
+ */
+ private final int[] tracks;
+ /**
+ * The {@link Format}s of the selected tracks, in order of decreasing bandwidth.
+ */
+ private final Format[] formats;
+ /**
+ * Selected track blacklist timestamps, in order of decreasing bandwidth.
+ */
+ private final long[] blacklistUntilTimes;
+
+ // Lazily initialized hashcode.
+ private int hashCode;
+
+ /**
+ * @param group The {@link TrackGroup}. Must not be null.
+ * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be
+ * null or empty. May be in any order.
+ */
+ public BaseTrackSelection(TrackGroup group, int... tracks) {
+ Assertions.checkState(tracks.length > 0);
+ this.group = Assertions.checkNotNull(group);
+ this.length = tracks.length;
+ // Set the formats, sorted in order of decreasing bandwidth.
+ formats = new Format[length];
+ for (int i = 0; i < tracks.length; i++) {
+ formats[i] = group.getFormat(tracks[i]);
+ }
+ Arrays.sort(formats, new DecreasingBandwidthComparator());
+ // Set the format indices in the same order.
+ this.tracks = new int[length];
+ for (int i = 0; i < length; i++) {
+ this.tracks[i] = group.indexOf(formats[i]);
+ }
+ blacklistUntilTimes = new long[length];
+ }
+
+ @Override
+ public final TrackGroup getTrackGroup() {
+ return group;
+ }
+
+ @Override
+ public final int length() {
+ return tracks.length;
+ }
+
+ @Override
+ public final Format getFormat(int index) {
+ return formats[index];
+ }
+
+ @Override
+ public final int getIndexInTrackGroup(int index) {
+ return tracks[index];
+ }
+
+ @Override
+ public final int indexOf(Format format) {
+ for (int i = 0; i < length; i++) {
+ if (formats[i] == format) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public final int indexOf(int indexInTrackGroup) {
+ for (int i = 0; i < length; i++) {
+ if (tracks[i] == indexInTrackGroup) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public final Format getSelectedFormat() {
+ return formats[getSelectedIndex()];
+ }
+
+ @Override
+ public final int getSelectedIndexInTrackGroup() {
+ return tracks[getSelectedIndex()];
+ }
+
+ @Override
+ public int evaluateQueueSize(long playbackPositionUs, List extends MediaChunk> queue) {
+ return queue.size();
+ }
+
+ @Override
+ public final boolean blacklist(int index, long blacklistDurationMs) {
+ long nowMs = SystemClock.elapsedRealtime();
+ boolean canBlacklist = isBlacklisted(index, nowMs);
+ for (int i = 0; i < length && !canBlacklist; i++) {
+ canBlacklist = i != index && !isBlacklisted(index, nowMs);
+ }
+ if (!canBlacklist) {
+ return false;
+ }
+ blacklistUntilTimes[index] = Math.max(blacklistUntilTimes[index], nowMs + blacklistDurationMs);
+ return true;
+ }
+
+ /**
+ * Returns whether the track at the specified index in the selection is blaclisted.
+ *
+ * @param index The index of the track in the selection.
+ * @param nowMs The current time in the timebase of {@link SystemClock#elapsedRealtime()}.
+ */
+ protected final boolean isBlacklisted(int index, long nowMs) {
+ return blacklistUntilTimes[index] > nowMs;
+ }
+
+ // Object overrides.
+
+ @Override
+ public int hashCode() {
+ if (hashCode == 0) {
+ hashCode = 31 * System.identityHashCode(group) + Arrays.hashCode(tracks);
+ }
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ BaseTrackSelection other = (BaseTrackSelection) obj;
+ return group == other.group && Arrays.equals(tracks, other.tracks);
+ }
+
+}
diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
index e721059ae4..d95e64f02e 100644
--- a/library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
+++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
@@ -51,6 +51,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private static final int[] NO_TRACKS = new int[0];
private static final String TAG = "DefaultTrackSelector";
+ private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory;
+
// Audio and text.
private String preferredLanguage;
@@ -64,8 +66,28 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private int viewportWidth;
private int viewportHeight;
+ /**
+ * Constructs an instance that does not support adaptive video.
+ *
+ * @param eventHandler A handler to use when delivering events to listeners. May be null if
+ * listeners will not be added.
+ */
public DefaultTrackSelector(Handler eventHandler) {
+ this(eventHandler, null);
+ }
+
+ /**
+ * Constructs an instance that uses a factory to create adaptive video track selections.
+ *
+ * @param eventHandler A handler to use when delivering events to listeners. May be null if
+ * listeners will not be added.
+ * @param adaptiveVideoTrackSelectionFactory A factory for adaptive video {@link TrackSelection}s,
+ * or null if the selector should not support adaptive video.
+ */
+ public DefaultTrackSelector(Handler eventHandler,
+ TrackSelection.Factory adaptiveVideoTrackSelectionFactory) {
super(eventHandler);
+ this.adaptiveVideoTrackSelectionFactory = adaptiveVideoTrackSelectionFactory;
allowNonSeamlessAdaptiveness = true;
exceedVideoConstraintsIfNecessary = true;
maxVideoWidth = Integer.MAX_VALUE;
@@ -203,11 +225,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
rendererTrackSelections[i] = selectTrackForVideoRenderer(rendererCapabilities[i],
rendererTrackGroupArrays[i], rendererFormatSupports[i], maxVideoWidth, maxVideoHeight,
allowNonSeamlessAdaptiveness, allowMixedMimeAdaptiveness, viewportWidth,
- viewportHeight, orientationMayChange);
- if (rendererTrackSelections[i] == null && exceedVideoConstraintsIfNecessary) {
- rendererTrackSelections[i] = selectSmallestSupportedVideoTrack(
- rendererTrackGroupArrays[i], rendererFormatSupports[i]);
- }
+ viewportHeight, orientationMayChange, adaptiveVideoTrackSelectionFactory,
+ exceedVideoConstraintsIfNecessary);
break;
case C.TRACK_TYPE_AUDIO:
rendererTrackSelections[i] = selectTrackForAudioRenderer(rendererTrackGroupArrays[i],
@@ -232,28 +251,35 @@ public class DefaultTrackSelector extends MappingTrackSelector {
RendererCapabilities rendererCapabilities, TrackGroupArray trackGroups,
int[][] formatSupport, int maxVideoWidth, int maxVideoHeight,
boolean allowNonSeamlessAdaptiveness, boolean allowMixedMimeAdaptiveness, int viewportWidth,
- int viewportHeight, boolean orientationMayChange) throws ExoPlaybackException {
- int requiredAdaptiveSupport = allowNonSeamlessAdaptiveness
- ? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS | RendererCapabilities.ADAPTIVE_SEAMLESS)
- : RendererCapabilities.ADAPTIVE_SEAMLESS;
- boolean allowMixedMimeTypes = allowMixedMimeAdaptiveness
- && (rendererCapabilities.supportsMixedMimeTypeAdaptation() & requiredAdaptiveSupport) != 0;
- TrackGroup largestAdaptiveGroup = null;
- int[] largestAdaptiveGroupTracks = NO_TRACKS;
- for (int i = 0; i < trackGroups.length; i++) {
- TrackGroup trackGroup = trackGroups.get(i);
- int[] adaptiveTracks = getAdaptiveTracksOfGroup(trackGroup, formatSupport[i],
- allowMixedMimeTypes, requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight,
- viewportWidth, viewportHeight, orientationMayChange);
- if (adaptiveTracks.length > largestAdaptiveGroupTracks.length) {
- largestAdaptiveGroup = trackGroup;
- largestAdaptiveGroupTracks = adaptiveTracks;
+ int viewportHeight, boolean orientationMayChange,
+ TrackSelection.Factory adaptiveVideoTrackSelectionFactory,
+ boolean exceedVideoConstraintsIfNecessary) throws ExoPlaybackException {
+ if (adaptiveVideoTrackSelectionFactory != null) {
+ int requiredAdaptiveSupport = allowNonSeamlessAdaptiveness
+ ? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS | RendererCapabilities.ADAPTIVE_SEAMLESS)
+ : RendererCapabilities.ADAPTIVE_SEAMLESS;
+ boolean allowMixedMimeTypes = allowMixedMimeAdaptiveness
+ && (rendererCapabilities.supportsMixedMimeTypeAdaptation() & requiredAdaptiveSupport)
+ != 0;
+ TrackGroup largestAdaptiveGroup = null;
+ int[] largestAdaptiveGroupTracks = NO_TRACKS;
+ for (int i = 0; i < trackGroups.length; i++) {
+ TrackGroup trackGroup = trackGroups.get(i);
+ int[] adaptiveTracks = getAdaptiveTracksOfGroup(trackGroup, formatSupport[i],
+ allowMixedMimeTypes, requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight,
+ viewportWidth, viewportHeight, orientationMayChange);
+ if (adaptiveTracks.length > largestAdaptiveGroupTracks.length) {
+ largestAdaptiveGroup = trackGroup;
+ largestAdaptiveGroupTracks = adaptiveTracks;
+ }
+ }
+ if (largestAdaptiveGroup != null) {
+ return adaptiveVideoTrackSelectionFactory.createTrackSelection(largestAdaptiveGroup,
+ largestAdaptiveGroupTracks);
}
}
- if (largestAdaptiveGroup != null) {
- return new TrackSelection(largestAdaptiveGroup, largestAdaptiveGroupTracks);
- }
+ // TODO: Should select the best supported video track, not the first one.
// No adaptive tracks selection could be made, so we select the first supported video track.
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
TrackGroup trackGroup = trackGroups.get(groupIndex);
@@ -261,10 +287,15 @@ public class DefaultTrackSelector extends MappingTrackSelector {
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupportedVideoTrack(trackFormatSupport[trackIndex], trackGroup.getFormat(trackIndex),
maxVideoWidth, maxVideoHeight)) {
- return new TrackSelection(trackGroup, trackIndex);
+ return new FixedTrackSelection(trackGroup, trackIndex);
}
}
}
+
+ if (exceedVideoConstraintsIfNecessary) {
+ return selectSmallestSupportedVideoTrack(trackGroups, formatSupport);
+ }
+
return null;
}
@@ -350,8 +381,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
}
}
- return trackGroupSelection != null
- ? new TrackSelection(trackGroupSelection, trackIndexSelection) : null;
+ return trackGroupSelection == null ? null
+ : new FixedTrackSelection(trackGroupSelection, trackIndexSelection);
}
private static boolean isSupportedVideoTrack(int formatSupport, Format format, int maxVideoWidth,
@@ -371,7 +402,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex])
&& formatHasLanguage(trackGroup.getFormat(trackIndex), preferredLanguage)) {
- return new TrackSelection(trackGroup, trackIndex);
+ return new FixedTrackSelection(trackGroup, trackIndex);
}
}
}
@@ -398,12 +429,13 @@ public class DefaultTrackSelector extends MappingTrackSelector {
firstForcedTrack = trackIndex;
}
if (formatHasLanguage(trackGroup.getFormat(trackIndex), preferredLanguage)) {
- return new TrackSelection(trackGroup, trackIndex);
+ return new FixedTrackSelection(trackGroup, trackIndex);
}
}
}
}
- return firstForcedGroup != null ? new TrackSelection(firstForcedGroup, firstForcedTrack) : null;
+ return firstForcedGroup == null ? null
+ : new FixedTrackSelection(firstForcedGroup, firstForcedTrack);
}
// General track selection methods.
@@ -415,7 +447,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
int[] trackFormatSupport = formatSupport[groupIndex];
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex])) {
- return new TrackSelection(trackGroup, trackIndex);
+ return new FixedTrackSelection(trackGroup, trackIndex);
}
}
}
@@ -492,7 +524,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
// Before API 25 the platform Display object does not provide a working way to identify Android
// TVs that can show 4k resolution in a SurfaceView, so check for supported devices here.
if (Util.SDK_INT < 25) {
- if ("Sony".equals(Util.MANUFACTURER) && Util.MODEL != null && Util.MODEL.startsWith("BRAVIA")
+ if ("Sony".equals(Util.MANUFACTURER) && Util.MODEL.startsWith("BRAVIA")
&& context.getPackageManager().hasSystemFeature("com.sony.dtv.hardware.panel.qfhd")) {
return new Point(3840, 2160);
} else if ("NVIDIA".equals(Util.MANUFACTURER) && Util.MODEL != null
diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/FixedTrackSelection.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/FixedTrackSelection.java
new file mode 100644
index 0000000000..9cf4f0371b
--- /dev/null
+++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/FixedTrackSelection.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.trackselection;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.source.TrackGroup;
+
+/**
+ * A {@link TrackSelection} consisting of a single track.
+ */
+public final class FixedTrackSelection extends BaseTrackSelection {
+
+ private final int reason;
+ private final Object data;
+
+ /**
+ * @param group The {@link TrackGroup}. Must not be null.
+ * @param track The index of the selected track within the {@link TrackGroup}.
+ */
+ public FixedTrackSelection(TrackGroup group, int track) {
+ this(group, track, C.SELECTION_REASON_UNKNOWN, null);
+ }
+
+ /**
+ * @param group The {@link TrackGroup}. Must not be null.
+ * @param track The index of the selected track within the {@link TrackGroup}.
+ * @param reason A reason for the track selection.
+ * @param data Optional data associated with the track selection.
+ */
+ public FixedTrackSelection(TrackGroup group, int track, int reason, Object data) {
+ super(group, track);
+ this.reason = reason;
+ this.data = data;
+ }
+
+ @Override
+ public void updateSelectedTrack(long bufferedDurationUs) {
+ // Do nothing.
+ }
+
+ @Override
+ public int getSelectedIndex() {
+ return 0;
+ }
+
+ @Override
+ public int getSelectionReason() {
+ return reason;
+ }
+
+ @Override
+ public Object getSelectionData() {
+ return data;
+ }
+
+}
diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/RandomTrackSelection.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/RandomTrackSelection.java
new file mode 100644
index 0000000000..a4ba2cae8c
--- /dev/null
+++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/RandomTrackSelection.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.trackselection;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.source.TrackGroup;
+
+import android.os.SystemClock;
+
+import java.util.Random;
+
+/**
+ * A {@link TrackSelection} whose selected track is updated randomly.
+ */
+public final class RandomTrackSelection extends BaseTrackSelection {
+
+ private final Random random;
+
+ private int selectedIndex;
+
+ /**
+ * @param group The {@link TrackGroup}. Must not be null.
+ * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be
+ * null or empty. May be in any order.
+ */
+ public RandomTrackSelection(TrackGroup group, int... tracks) {
+ super(group, tracks);
+ random = new Random();
+ selectedIndex = random.nextInt(length);
+ }
+
+ /**
+ * @param group The {@link TrackGroup}. Must not be null.
+ * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be
+ * null or empty. May be in any order.
+ * @param seed A seed for the {@link Random} instance used to update the selected track.
+ */
+ public RandomTrackSelection(TrackGroup group, int[] tracks, long seed) {
+ super(group, tracks);
+ random = new Random(seed);
+ selectedIndex = random.nextInt(length);
+ }
+
+ @Override
+ public void updateSelectedTrack(long bufferedDurationUs) {
+ // Count the number of non-blacklisted formats.
+ long nowMs = SystemClock.elapsedRealtime();
+ int nonBlacklistedFormatCount = 0;
+ for (int i = 0; i < length; i++) {
+ if (!isBlacklisted(i, nowMs)) {
+ nonBlacklistedFormatCount++;
+ }
+ }
+
+ selectedIndex = random.nextInt(nonBlacklistedFormatCount);
+ if (nonBlacklistedFormatCount != length) {
+ // Adjust the format index to account for blacklisted formats.
+ nonBlacklistedFormatCount = 0;
+ for (int i = 0; i < length; i++) {
+ if (!isBlacklisted(i, nowMs) && selectedIndex == nonBlacklistedFormatCount++) {
+ selectedIndex = i;
+ return;
+ }
+ }
+ }
+ }
+
+ @Override
+ public int getSelectedIndex() {
+ return selectedIndex;
+ }
+
+ @Override
+ public int getSelectionReason() {
+ return C.SELECTION_REASON_ADAPTIVE;
+ }
+
+ @Override
+ public Object getSelectionData() {
+ return null;
+ }
+
+}
diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java
index 05fc6061c6..edf8d7dfee 100644
--- a/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java
+++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java
@@ -16,71 +16,64 @@
package com.google.android.exoplayer2.trackselection;
import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.Format.DecreasingBandwidthComparator;
import com.google.android.exoplayer2.source.TrackGroup;
-import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.source.chunk.MediaChunk;
-import java.util.Arrays;
+import java.util.List;
/**
- * A track selection, consisting of a {@link TrackGroup} and a selected subset of the tracks within
- * it. The selected tracks are exposed in order of decreasing bandwidth.
+ * A track selection consisting of a static subset of selected tracks belonging to a
+ * {@link TrackGroup}, and a possibly varying individual selected track from the subset.
+ *
+ * Tracks belonging to the subset are exposed in decreasing bandwidth order. The individual selected
+ * track may change as a result of calling {@link #updateSelectedTrack(long)}.
*/
-public final class TrackSelection {
+public interface TrackSelection {
/**
- * The selected {@link TrackGroup}.
+ * Factory for {@link TrackSelection} instances.
*/
- public final TrackGroup group;
- /**
- * The number of selected tracks within the {@link TrackGroup}. Always greater than zero.
- */
- public final int length;
+ interface Factory {
- private final int[] tracks;
- private final Format[] formats;
+ /**
+ * Creates a new selection.
+ *
+ * @param group The {@link TrackGroup}. Must not be null.
+ * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be
+ * null or empty. May be in any order.
+ * @return The created selection.
+ */
+ TrackSelection createTrackSelection(TrackGroup group, int... tracks);
- // Lazily initialized hashcode.
- private int hashCode;
-
- /**
- * @param group The {@link TrackGroup}. Must not be null.
- * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be
- * null or empty. May be in any order.
- */
- public TrackSelection(TrackGroup group, int... tracks) {
- Assertions.checkState(tracks.length > 0);
- this.group = Assertions.checkNotNull(group);
- this.length = tracks.length;
- // Set the formats, sorted in order of decreasing bandwidth.
- formats = new Format[length];
- for (int i = 0; i < tracks.length; i++) {
- formats[i] = group.getFormat(tracks[i]);
- }
- Arrays.sort(formats, new DecreasingBandwidthComparator());
- // Set the format indices in the same order.
- this.tracks = new int[length];
- for (int i = 0; i < length; i++) {
- this.tracks[i] = group.indexOf(formats[i]);
- }
}
+ /**
+ * Returns the {@link TrackGroup} to which the selected tracks belong.
+ */
+ TrackGroup getTrackGroup();
+
+ // Static subset of selected tracks.
+
+ /**
+ * Returns the number of tracks in the selection.
+ */
+ int length();
+
/**
* Returns the format of the track at a given index in the selection.
*
* @param index The index in the selection.
* @return The format of the selected track.
*/
- public Format getFormat(int index) {
- return formats[index];
- }
+ Format getFormat(int index);
/**
- * Returns a copy of the formats of the selected tracks.
+ * Returns the index in the track group of the track at a given index in the selection.
+ *
+ * @param index The index in the selection.
+ * @return The index of the selected track.
*/
- public Format[] getFormats() {
- return formats.clone();
- }
+ int getIndexInTrackGroup(int index);
/**
* Returns the index in the selection of the track with the specified format.
@@ -89,66 +82,82 @@ public final class TrackSelection {
* @return The index in the selection, or -1 if the track with the specified format is not part of
* the selection.
*/
- public int indexOf(Format format) {
- for (int i = 0; i < length; i++) {
- if (formats[i] == format) {
- return i;
- }
- }
- return -1;
- }
-
- /**
- * Returns the index in the track group of the track at a given index in the selection.
- *
- * @param index The index in the selection.
- * @return The index of the selected track.
- */
- public int getTrack(int index) {
- return tracks[index];
- }
-
- /**
- * Returns a copy of the selected tracks in the track group.
- */
- public int[] getTracks() {
- return tracks.clone();
- }
+ int indexOf(Format format);
/**
* Returns the index in the selection of the track with the specified index in the track group.
*
- * @param trackIndex The index in the track group.
+ * @param indexInTrackGroup The index in the track group.
* @return The index in the selection, or -1 if the track with the specified index is not part of
* the selection.
*/
- public int indexOf(int trackIndex) {
- for (int i = 0; i < length; i++) {
- if (tracks[i] == trackIndex) {
- return i;
- }
- }
- return -1;
- }
+ int indexOf(int indexInTrackGroup);
- @Override
- public int hashCode() {
- if (hashCode == 0) {
- hashCode = 31 * System.identityHashCode(group) + Arrays.hashCode(tracks);
- }
- return hashCode;
- }
+ // Individual selected track.
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null || getClass() != obj.getClass()) {
- return false;
- }
- TrackSelection other = (TrackSelection) obj;
- return group == other.group && Arrays.equals(tracks, other.tracks);
- }
+ /**
+ * Returns the {@link Format} of the individual selected track.
+ */
+ Format getSelectedFormat();
+
+ /**
+ * Returns the index in the track group of the individual selected track.
+ */
+ int getSelectedIndexInTrackGroup();
+
+ /**
+ * Returns the index of the selected track.
+ */
+ int getSelectedIndex();
+
+ /**
+ * Returns the reason for the current track selection.
+ */
+ int getSelectionReason();
+
+ /**
+ * Returns optional data associated with the current track selection.
+ */
+ Object getSelectionData();
+
+ // Adaptation.
+
+ /**
+ * Updates the selected track.
+ *
+ * @param bufferedDurationUs The duration of media currently buffered in microseconds.
+ */
+ void updateSelectedTrack(long bufferedDurationUs);
+
+ /**
+ * May be called periodically by sources that load media in discrete {@link MediaChunk}s and
+ * support discarding of buffered chunks in order to re-buffer using a different selected track.
+ * Returns the number of chunks that should be retained in the queue.
+ *
+ * To avoid excessive re-buffering, implementations should normally return the size of the queue.
+ * An example of a case where a smaller value may be returned is if network conditions have
+ * improved dramatically, allowing chunks to be discarded and re-buffered in a track of
+ * significantly higher quality. Discarding chunks may allow faster switching to a higher quality
+ * track in this case.
+ *
+ * @param playbackPositionUs The current playback position in microseconds.
+ * @param queue The queue of buffered {@link MediaChunk}s. Must not be modified.
+ * @return The number of chunks to retain in the queue.
+ */
+ int evaluateQueueSize(long playbackPositionUs, List extends MediaChunk> queue);
+
+ /**
+ * Attempts to blacklist the track at the specified index in the selection, making it ineligible
+ * for selection by calls to {@link #updateSelectedTrack(long)} for the specified period of time.
+ * Blacklisting will fail if all other tracks are currently blacklisted. If blacklisting the
+ * currently selected track, note that it will remain selected until the next call to
+ * {@link #updateSelectedTrack(long)}.
+ *
+ * @param index The index of the track in the selection.
+ * @param blacklistDurationMs The duration of time for which the track should be blacklisted, in
+ * milliseconds.
+ * @return Whether blacklisting was successful.
+ */
+ boolean blacklist(int index, long blacklistDurationMs);
}
diff --git a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java
index 7a63b8d393..446f4b6281 100644
--- a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java
+++ b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java
@@ -34,17 +34,18 @@ import com.google.android.exoplayer2.playbacktests.util.MetricsLogger;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
-import com.google.android.exoplayer2.source.chunk.FormatEvaluator;
-import com.google.android.exoplayer2.source.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
+import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
+import com.google.android.exoplayer2.trackselection.RandomTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
+import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
-import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;
+import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
@@ -680,15 +681,16 @@ public final class DashTest extends ActivityInstrumentationTestCase2 videoFormatIds.length;
+ selections[VIDEO_RENDERER_INDEX].length() > videoFormatIds.length;
return selections;
}
@@ -844,18 +844,9 @@ public final class DashTest extends ActivityInstrumentationTestCase2 trackIndices = new ArrayList<>();
- // Always select explicitly listed representations, failing if they're missing.
+ // Always select explicitly listed representations.
for (String formatId : formatIds) {
- boolean foundIndex = false;
- for (int j = 0; j < trackGroup.length && !foundIndex; j++) {
- if (trackGroup.getFormat(j).id.equals(formatId)) {
- trackIndices.add(j);
- foundIndex = true;
- }
- }
- if (!foundIndex) {
- throw new IllegalStateException("Format " + formatId + " not found.");
- }
+ trackIndices.add(getTrackIndex(trackGroup, formatId));
}
// Select additional video representations, if supported by the device.
@@ -873,6 +864,15 @@ public final class DashTest extends ActivityInstrumentationTestCase2