Commit 3501332d by olly Committed by Oliver Woodman

Converge track selection to a single place.

This change merges the duties of FormatEvaluator into
TrackSelection classes, so that both the static and
dynamic parts of track selection are implemented in a
single place.

New feature: Demo app now allows you to enable random
adaptation in the track selection dialog.

Notes:

- It should be quite easy to allow application side
track blacklisting in addition to source side, as an
extension to this. That would effectively allow
applications to do seamless/deferred track selection
by creating a TrackSelection with all tracks enabled,
and then toggling the blacklist flags to select the
ones they want to be active.

- It should be trivial to implement format blacklisting
for DASH and SS as an extension to this. Will do in a
follow up CL.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=128707517
parent 5eb61906
Showing with 712 additions and 360 deletions
......@@ -364,15 +364,8 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb
private static String getTrackStatusString(TrackSelection selection, TrackGroup group,
int trackIndex) {
boolean groupEnabled = selection != null && selection.group == group;
if (groupEnabled) {
for (int i = 0; i < selection.length; i++) {
if (selection.getTrack(i) == trackIndex) {
return getTrackStatusString(true);
}
}
}
return getTrackStatusString(false);
return getTrackStatusString(selection != null && selection.getTrackGroup() == group
&& selection.indexOf(trackIndex) != -1);
}
private static String getTrackStatusString(boolean enabled) {
......
......@@ -39,8 +39,6 @@ import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.Timeline;
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.source.hls.HlsMediaSource;
......@@ -48,13 +46,15 @@ import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.text.CaptionStyleCompat;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.ui.SubtitleView;
import com.google.android.exoplayer2.trackselection.AdaptiveVideoTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.DebugTextViewHelper;
import com.google.android.exoplayer2.ui.PlayerControl;
import com.google.android.exoplayer2.ui.SubtitleView;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
......@@ -138,7 +138,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
private String userAgent;
private DataSource.Factory manifestDataSourceFactory;
private DataSource.Factory mediaDataSourceFactory;
private FormatEvaluator.Factory formatEvaluatorFactory;
private DefaultBandwidthMeter bandwidthMeter;
private SimpleExoPlayer player;
private MappingTrackSelector trackSelector;
private TrackSelectionHelper trackSelectionHelper;
......@@ -155,9 +155,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
super.onCreate(savedInstanceState);
userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
manifestDataSourceFactory = new DefaultDataSourceFactory(this, userAgent);
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
bandwidthMeter = new DefaultBandwidthMeter();
mediaDataSourceFactory = new DefaultDataSourceFactory(this, userAgent, bandwidthMeter);
formatEvaluatorFactory = new AdaptiveEvaluator.Factory(bandwidthMeter);
mainHandler = new Handler();
setContentView(R.layout.player_activity);
......@@ -284,12 +283,15 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
return;
}
}
eventLogger = new EventLogger();
eventLogger.startSession();
trackSelector = new DefaultTrackSelector(mainHandler);
TrackSelection.Factory videoTrackSelectionFactory =
new AdaptiveVideoTrackSelection.Factory(bandwidthMeter);
trackSelector = new DefaultTrackSelector(mainHandler, videoTrackSelectionFactory);
trackSelector.addListener(this);
trackSelector.addListener(eventLogger);
trackSelectionHelper = new TrackSelectionHelper(trackSelector);
trackSelectionHelper = new TrackSelectionHelper(trackSelector, videoTrackSelectionFactory);
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, new DefaultLoadControl(),
drmSessionManager, preferExtensionDecoders);
player.addListener(this);
......@@ -354,15 +356,14 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
switch (type) {
case Util.TYPE_SS:
DefaultSsChunkSource.Factory factory = new DefaultSsChunkSource.Factory(
mediaDataSourceFactory, formatEvaluatorFactory);
mediaDataSourceFactory);
return new SsMediaSource(uri, manifestDataSourceFactory, factory, mainHandler, eventLogger);
case Util.TYPE_DASH:
DefaultDashChunkSource.Factory factory2 = new DefaultDashChunkSource.Factory(
mediaDataSourceFactory, formatEvaluatorFactory);
mediaDataSourceFactory);
return new DashMediaSource(uri, mediaDataSourceFactory, factory2, mainHandler, eventLogger);
case Util.TYPE_HLS:
return new HlsMediaSource(uri, mediaDataSourceFactory, formatEvaluatorFactory, mainHandler,
eventLogger);
return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, eventLogger);
case Util.TYPE_OTHER:
return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(),
mainHandler, eventLogger);
......
......@@ -19,8 +19,10 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo;
import com.google.android.exoplayer2.trackselection.RandomTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.util.MimeTypes;
......@@ -45,6 +47,7 @@ import java.util.Locale;
DialogInterface.OnClickListener {
private final MappingTrackSelector selector;
private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory;
private TrackInfo trackInfo;
private int rendererIndex;
......@@ -55,13 +58,18 @@ import java.util.Locale;
private CheckedTextView disableView;
private CheckedTextView defaultView;
private CheckedTextView enableRandomAdaptationView;
private CheckedTextView[][] trackViews;
/**
* @param selector The track selector.
* @param adaptiveVideoTrackSelectionFactory A factory for adaptive video {@link TrackSelection}s,
* or null if the selection helper should not support adaptive video.
*/
public TrackSelectionHelper(MappingTrackSelector selector) {
public TrackSelectionHelper(MappingTrackSelector selector,
TrackSelection.Factory adaptiveVideoTrackSelectionFactory) {
this.selector = selector;
this.adaptiveVideoTrackSelectionFactory = adaptiveVideoTrackSelectionFactory;
}
/**
......@@ -80,8 +88,9 @@ import java.util.Locale;
trackGroups = trackInfo.getTrackGroups(rendererIndex);
trackGroupsAdaptive = new boolean[trackGroups.length];
for (int i = 0; i < trackGroups.length; i++) {
trackGroupsAdaptive[i] = trackInfo.getAdaptiveSupport(rendererIndex, i, false)
!= RendererCapabilities.ADAPTIVE_NOT_SUPPORTED;
trackGroupsAdaptive[i] = adaptiveVideoTrackSelectionFactory != null
&& (trackInfo.getAdaptiveSupport(rendererIndex, i, false)
!= RendererCapabilities.ADAPTIVE_NOT_SUPPORTED);
}
isDisabled = selector.getRendererDisabled(rendererIndex);
override = selector.hasSelectionOverride(rendererIndex, trackGroups)
......@@ -118,17 +127,19 @@ import java.util.Locale;
// Per-track views.
boolean haveSupportedTracks = false;
boolean haveAdaptiveTracks = false;
trackViews = new CheckedTextView[trackGroups.length][];
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
TrackGroup group = trackGroups.get(groupIndex);
boolean groupIsAdaptive = group.length > 1 && trackGroupsAdaptive[groupIndex];
haveAdaptiveTracks |= groupIsAdaptive;
trackViews[groupIndex] = new CheckedTextView[group.length];
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
if (trackIndex == 0) {
root.addView(inflater.inflate(R.layout.list_divider, root, false));
}
int trackViewLayoutId = group.length < 2 || !trackGroupsAdaptive[groupIndex]
? android.R.layout.simple_list_item_single_choice
: android.R.layout.simple_list_item_multiple_choice;
int trackViewLayoutId = groupIsAdaptive ? android.R.layout.simple_list_item_multiple_choice
: android.R.layout.simple_list_item_single_choice;
CheckedTextView trackView = (CheckedTextView) inflater.inflate(
trackViewLayoutId, root, false);
trackView.setText(buildTrackName(group.getFormat(trackIndex)));
......@@ -148,6 +159,14 @@ import java.util.Locale;
if (!haveSupportedTracks) {
// Indicate that the default selection will be nothing.
defaultView.setText(R.string.selection_default_none);
} else if (haveAdaptiveTracks) {
// View for using random adaptation.
enableRandomAdaptationView = (CheckedTextView) inflater.inflate(
android.R.layout.simple_list_item_multiple_choice, root, false);
enableRandomAdaptationView.setText(R.string.enable_random_adaptation);
enableRandomAdaptationView.setOnClickListener(this);
root.addView(inflater.inflate(R.layout.list_divider, root, false));
root.addView(enableRandomAdaptationView);
}
updateViews();
......@@ -158,9 +177,18 @@ import java.util.Locale;
disableView.setChecked(isDisabled);
defaultView.setChecked(!isDisabled && override == null);
for (int i = 0; i < trackViews.length; i++) {
TrackGroup trackGroup = trackGroups.get(i);
for (int j = 0; j < trackViews[i].length; j++) {
trackViews[i][j].setChecked(
override != null && override.group == trackGroups.get(i) && override.indexOf(j) != -1);
trackViews[i][j].setChecked(override != null && override.getTrackGroup() == trackGroup
&& override.indexOf(trackGroup.getFormat(j)) != -1);
}
}
if (enableRandomAdaptationView != null) {
enableRandomAdaptationView.setEnabled(!isDisabled && override != null
&& override.length() > 1);
if (enableRandomAdaptationView.isEnabled()) {
enableRandomAdaptationView.setChecked(!isDisabled
&& override instanceof RandomTrackSelection);
}
}
}
......@@ -191,6 +219,9 @@ import java.util.Locale;
} else if (view == defaultView) {
isDisabled = false;
override = null;
} else if (view == enableRandomAdaptationView) {
setOverride(override.getTrackGroup(), getTracks(override),
!enableRandomAdaptationView.isChecked());
} else {
isDisabled = false;
@SuppressWarnings("unchecked")
......@@ -198,31 +229,25 @@ import java.util.Locale;
TrackGroup group = tag.first;
int trackIndex = tag.second;
if (!trackGroupsAdaptive[trackGroups.indexOf(group)] || override == null) {
override = new TrackSelection(group, trackIndex);
override = new FixedTrackSelection(group, trackIndex);
} else {
// The group being modified is adaptive and we already have a non-null override.
boolean isEnabled = ((CheckedTextView) view).isChecked();
int overrideLength = override.length();
if (isEnabled) {
// Remove the track from the override.
if (override.length == 1) {
if (overrideLength == 1) {
// The last track is being removed, so the override becomes empty.
override = null;
isDisabled = true;
} else {
int[] tracks = new int[override.length - 1];
int trackCount = 0;
for (int i = 0; i < override.length; i++) {
if (override.getTrack(i) != trackIndex) {
tracks[trackCount++] = override.getTrack(i);
}
}
override = new TrackSelection(group, tracks);
setOverride(group, getTracksRemoving(override, trackIndex),
enableRandomAdaptationView.isChecked());
}
} else {
// Add the track to the override.
int[] tracks = Arrays.copyOf(override.getTracks(), override.length + 1);
tracks[tracks.length - 1] = trackIndex;
override = new TrackSelection(group, tracks);
setOverride(group, getTracksAdding(override, trackIndex),
enableRandomAdaptationView.isChecked());
}
}
}
......@@ -230,6 +255,41 @@ import java.util.Locale;
updateViews();
}
private void setOverride(TrackGroup group, int[] tracks, boolean enableRandomAdaptation) {
override = tracks.length == 1 ? new FixedTrackSelection(group, tracks[0])
: (enableRandomAdaptation ? new RandomTrackSelection(group, tracks)
: adaptiveVideoTrackSelectionFactory.createTrackSelection(group, tracks));
}
// Track array manipulation.
private static int[] getTracks(TrackSelection trackSelection) {
int[] tracks = new int[trackSelection.length()];
for (int i = 0; i < tracks.length; i++) {
tracks[i] = trackSelection.getIndexInTrackGroup(i);
}
return tracks;
}
private static int[] getTracksAdding(TrackSelection trackSelection, int addedTrack) {
int[] tracks = getTracks(trackSelection);
tracks = Arrays.copyOf(tracks, tracks.length + 1);
tracks[tracks.length - 1] = addedTrack;
return tracks;
}
private static int[] getTracksRemoving(TrackSelection trackSelection, int removedTrack) {
int[] tracks = new int[trackSelection.length() - 1];
int trackCount = 0;
for (int i = 0; i < tracks.length + 1; i++) {
int track = trackSelection.getIndexInTrackGroup(i);
if (track != removedTrack) {
tracks[trackCount++] = track;
}
}
return tracks;
}
// Track name construction.
private static String buildTrackName(Format format) {
......
......@@ -33,6 +33,8 @@
<string name="selection_default_none">Default (none)</string>
<string name="enable_random_adaptation">Enable random adaptation</string>
<string name="error_drm_not_supported">Protected content not supported on API levels below 18</string>
<string name="error_drm_unsupported_scheme">This device does not support the required DRM scheme</string>
......
......@@ -834,9 +834,9 @@ import java.util.ArrayList;
if (newSelection != null) {
// Replace the renderer's SampleStream so the transition to playing the next period
// can be seamless.
Format[] formats = new Format[newSelection.length];
Format[] formats = new Format[newSelection.length()];
for (int j = 0; j < formats.length; j++) {
formats[j] = newSelection.group.getFormat(newSelection.getTrack(j));
formats[j] = newSelection.getFormat(j);
}
renderer.replaceStream(formats, readingPeriod.sampleStreams[i],
readingPeriod.offsetUs);
......@@ -1087,9 +1087,9 @@ import java.util.ArrayList;
// Consider as joining only if the renderer was previously disabled.
boolean joining = !rendererWasEnabledFlags[i] && playing;
// Build an array of formats contained by the selection.
Format[] formats = new Format[newSelection.length];
Format[] formats = new Format[newSelection.length()];
for (int j = 0; j < formats.length; j++) {
formats[j] = newSelection.group.getFormat(newSelection.getTrack(j));
formats[j] = newSelection.getFormat(j);
}
// Enable the renderer.
renderer.enable(formats, playingPeriod.sampleStreams[i], internalPositionUs, joining,
......
......@@ -250,9 +250,9 @@ public final class ExtractorMediaSource implements MediaPeriod, MediaSource,
SampleStream[] newStreams = new SampleStream[newSelections.size()];
for (int i = 0; i < newStreams.length; i++) {
TrackSelection selection = newSelections.get(i);
Assertions.checkState(selection.length == 1);
Assertions.checkState(selection.getTrack(0) == 0);
int track = tracks.indexOf(selection.group);
Assertions.checkState(selection.length() == 1);
Assertions.checkState(selection.getIndexInTrackGroup(0) == 0);
int track = tracks.indexOf(selection.getTrackGroup());
Assertions.checkState(!trackEnabledStates[track]);
enabledTrackCount++;
trackEnabledStates[track] = true;
......
......@@ -220,7 +220,7 @@ public final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callba
TrackGroupArray periodTrackGroups = period.getTrackGroups();
for (int i = 0; i < allNewSelections.size(); i++) {
TrackSelection selection = allNewSelections.get(i);
if (periodTrackGroups.indexOf(selection.group) != -1) {
if (periodTrackGroups.indexOf(selection.getTrackGroup()) != -1) {
newSelectionOriginalIndices[newSelections.size()] = i;
newSelections.add(selection);
}
......
......@@ -150,7 +150,6 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
* This method should be called when the stream is no longer required.
*/
public void release() {
chunkSource.release();
sampleQueue.disable();
loader.release();
}
......
......@@ -84,11 +84,4 @@ public interface ChunkSource {
*/
boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e);
/**
* Releases the source.
* <p>
* This method should be called when the source is no longer required.
*/
void release();
}
......@@ -222,7 +222,7 @@ import java.util.List;
private ChunkSampleStream<DashChunkSource> 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,
......
......@@ -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<Representation> 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<Representation> 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<Representation> getRepresentations() {
......
......@@ -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<SampleStream, HlsSampleStreamWrapper> 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);
}
......
......@@ -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();
......
......@@ -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,
......
......@@ -199,7 +199,7 @@ import java.util.List;
private ChunkSampleStream<SsChunkSource> 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,
......
/*
* 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);
}
}
......@@ -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
......
/*
* 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;
}
}
/*
* 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;
}
}
......@@ -16,54 +16,48 @@
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.
* <p>
* 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 {
/**
* 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);
private final int[] tracks;
private final Format[] formats;
}
// Lazily initialized hashcode.
private int hashCode;
/**
* Returns the {@link TrackGroup} to which the selected tracks belong.
*/
TrackGroup getTrackGroup();
// Static subset of selected tracks.
/**
* @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.
* Returns the number of tracks in the selection.
*/
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]);
}
}
int length();
/**
* Returns the format of the track at a given index in the selection.
......@@ -71,16 +65,15 @@ public final class TrackSelection {
* @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;
}
int indexOf(Format format);
/**
* Returns the index in the track group of the track at a given index in the selection.
* Returns the index in the selection of the track with the specified index in the track group.
*
* @param index The index in the selection.
* @return The index of the selected track.
* @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 getTrack(int index) {
return tracks[index];
}
int indexOf(int indexInTrackGroup);
// Individual selected track.
/**
* Returns a copy of the selected tracks in the track group.
* Returns the {@link Format} of the individual selected track.
*/
public int[] getTracks() {
return tracks.clone();
}
Format getSelectedFormat();
/**
* Returns the index in the selection of the track with the specified index in the track group.
* 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 trackIndex 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.
* @param bufferedDurationUs The duration of media currently buffered in microseconds.
*/
public int indexOf(int trackIndex) {
for (int i = 0; i < length; i++) {
if (tracks[i] == trackIndex) {
return i;
}
}
return -1;
}
void updateSelectedTrack(long bufferedDurationUs);
@Override
public int hashCode() {
if (hashCode == 0) {
hashCode = 31 * System.identityHashCode(group) + Arrays.hashCode(tracks);
}
return hashCode;
}
/**
* 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.
* <p>
* 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);
@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);
}
/**
* 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);
}
......@@ -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<HostActivit
this.isWidevineEncrypted = isWidevineEncrypted;
this.videoMimeType = videoMimeType;
this.isCddLimitedRetry = isCddLimitedRetry;
trackSelector = new DashTestTrackSelector(new String[] {audioFormat},
videoFormats, canIncludeAdditionalVideoFormats);
trackSelector = new DashTestTrackSelector(audioFormat, videoFormats,
canIncludeAdditionalVideoFormats);
if (actionSchedule != null) {
setSchedule(actionSchedule);
}
}
@Override
protected MappingTrackSelector buildTrackSelector(HostActivity host) {
protected MappingTrackSelector buildTrackSelector(HostActivity host,
BandwidthMeter bandwidthMeter) {
return trackSelector;
}
......@@ -731,19 +733,17 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
}
@Override
public MediaSource buildSource(HostActivity host, String userAgent) {
public MediaSource buildSource(HostActivity host, String userAgent,
TransferListener mediaTransferListener) {
DataSource.Factory manifestDataSourceFactory = new DefaultDataSourceFactory(host, userAgent);
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
DataSource.Factory mediaDataSourceFactory = new DefaultDataSourceFactory(host, userAgent,
bandwidthMeter);
FormatEvaluator.Factory formatEvaluatorFactory = new AdaptiveEvaluator.Factory(
bandwidthMeter);
mediaTransferListener);
String manifestUrl = manifestPath;
manifestUrl += isWidevineEncrypted ? (needsSecureVideoDecoder ? WIDEVINE_L1_SUFFIX
: WIDEVINE_L3_SUFFIX) : "";
Uri manifestUri = Uri.parse(manifestUrl);
DefaultDashChunkSource.Factory chunkSourceFactory = new DefaultDashChunkSource.Factory(
mediaDataSourceFactory, formatEvaluatorFactory);
mediaDataSourceFactory);
return new DashMediaSource(manifestUri, manifestDataSourceFactory, chunkSourceFactory,
MIN_LOADABLE_RETRY_COUNT, null, null);
}
......@@ -801,16 +801,16 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
private static final class DashTestTrackSelector extends MappingTrackSelector {
private final String[] audioFormatIds;
private final String audioFormatId;
private final String[] videoFormatIds;
private final boolean canIncludeAdditionalVideoFormats;
public boolean includedAdditionalVideoFormats;
private DashTestTrackSelector(String[] audioFormatIds, String[] videoFormatIds,
private DashTestTrackSelector(String audioFormatId, String[] videoFormatIds,
boolean canIncludeAdditionalVideoFormats) {
super(null);
this.audioFormatIds = audioFormatIds;
this.audioFormatId = audioFormatId;
this.videoFormatIds = videoFormatIds;
this.canIncludeAdditionalVideoFormats = canIncludeAdditionalVideoFormats;
}
......@@ -826,17 +826,17 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
Assertions.checkState(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].length == 1);
Assertions.checkState(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].length == 1);
TrackSelection[] selections = new TrackSelection[rendererCapabilities.length];
selections[VIDEO_RENDERER_INDEX] = new TrackSelection(
selections[VIDEO_RENDERER_INDEX] = new RandomTrackSelection(
rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0),
getTrackIndices(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0),
rendererFormatSupports[VIDEO_RENDERER_INDEX][0], videoFormatIds,
canIncludeAdditionalVideoFormats));
selections[AUDIO_RENDERER_INDEX] = new TrackSelection(
canIncludeAdditionalVideoFormats),
0 /* seed */);
selections[AUDIO_RENDERER_INDEX] = new FixedTrackSelection(
rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0),
getTrackIndices(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0),
rendererFormatSupports[AUDIO_RENDERER_INDEX][0], audioFormatIds, false));
getTrackIndex(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0), audioFormatId));
includedAdditionalVideoFormats =
selections[VIDEO_RENDERER_INDEX].length > videoFormatIds.length;
selections[VIDEO_RENDERER_INDEX].length() > videoFormatIds.length;
return selections;
}
......@@ -844,18 +844,9 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
String[] formatIds, boolean canIncludeAdditionalFormats) {
List<Integer> 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<HostActivit
return trackIndicesArray;
}
private static int getTrackIndex(TrackGroup trackGroup, String formatId) {
for (int i = 0; i < trackGroup.length; i++) {
if (trackGroup.getFormat(i).id.equals(formatId)) {
return i;
}
}
throw new IllegalStateException("Format " + formatId + " not found.");
}
private static boolean isFormatHandled(int formatSupport) {
return (formatSupport & RendererCapabilities.FORMAT_SUPPORT_MASK)
== RendererCapabilities.FORMAT_HANDLED;
......
......@@ -27,15 +27,18 @@ import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.playbacktests.util.HostActivity.HostedTest;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.Timeline;
import com.google.android.exoplayer2.trackselection.AdaptiveVideoTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Util;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import android.view.Surface;
import junit.framework.Assert;
......@@ -123,11 +126,12 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
@Override
public final void onStart(HostActivity host, Surface surface) {
// Build the player.
trackSelector = buildTrackSelector(host);
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
trackSelector = buildTrackSelector(host, bandwidthMeter);
String userAgent = "ExoPlayerPlaybackTests";
DrmSessionManager drmSessionManager = buildDrmSessionManager(userAgent);
player = buildExoPlayer(host, surface, trackSelector, drmSessionManager);
player.setMediaSource(buildSource(host, Util.getUserAgent(host, userAgent)));
player.setMediaSource(buildSource(host, Util.getUserAgent(host, userAgent), bandwidthMeter));
player.addListener(this);
player.setDebugListener(this);
player.setPlayWhenReady(true);
......@@ -288,8 +292,9 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
}
@SuppressWarnings("unused")
protected MappingTrackSelector buildTrackSelector(HostActivity host) {
return new DefaultTrackSelector(null);
protected MappingTrackSelector buildTrackSelector(HostActivity host,
BandwidthMeter bandwidthMeter) {
return new DefaultTrackSelector(null, new AdaptiveVideoTrackSelection.Factory(bandwidthMeter));
}
@SuppressWarnings("unused")
......@@ -302,7 +307,8 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
}
@SuppressWarnings("unused")
protected abstract MediaSource buildSource(HostActivity host, String userAgent);
protected abstract MediaSource buildSource(HostActivity host, String userAgent,
TransferListener mediaTransferListener);
@SuppressWarnings("unused")
protected void onPlayerErrorInternal(ExoPlaybackException error) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment