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 ...@@ -364,15 +364,8 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb
private static String getTrackStatusString(TrackSelection selection, TrackGroup group, private static String getTrackStatusString(TrackSelection selection, TrackGroup group,
int trackIndex) { int trackIndex) {
boolean groupEnabled = selection != null && selection.group == group; return getTrackStatusString(selection != null && selection.getTrackGroup() == group
if (groupEnabled) { && selection.indexOf(trackIndex) != -1);
for (int i = 0; i < selection.length; i++) {
if (selection.getTrack(i) == trackIndex) {
return getTrackStatusString(true);
}
}
}
return getTrackStatusString(false);
} }
private static String getTrackStatusString(boolean enabled) { private static String getTrackStatusString(boolean enabled) {
......
...@@ -39,8 +39,6 @@ import com.google.android.exoplayer2.source.ExtractorMediaSource; ...@@ -39,8 +39,6 @@ import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.Timeline; import com.google.android.exoplayer2.source.Timeline;
import com.google.android.exoplayer2.source.TrackGroupArray; 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.DashMediaSource;
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource; import com.google.android.exoplayer2.source.hls.HlsMediaSource;
...@@ -48,13 +46,15 @@ import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource ...@@ -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.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.text.CaptionStyleCompat; import com.google.android.exoplayer2.text.CaptionStyleCompat;
import com.google.android.exoplayer2.text.Cue; 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.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo; 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.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.DebugTextViewHelper; import com.google.android.exoplayer2.ui.DebugTextViewHelper;
import com.google.android.exoplayer2.ui.PlayerControl; 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.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
...@@ -138,7 +138,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -138,7 +138,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
private String userAgent; private String userAgent;
private DataSource.Factory manifestDataSourceFactory; private DataSource.Factory manifestDataSourceFactory;
private DataSource.Factory mediaDataSourceFactory; private DataSource.Factory mediaDataSourceFactory;
private FormatEvaluator.Factory formatEvaluatorFactory; private DefaultBandwidthMeter bandwidthMeter;
private SimpleExoPlayer player; private SimpleExoPlayer player;
private MappingTrackSelector trackSelector; private MappingTrackSelector trackSelector;
private TrackSelectionHelper trackSelectionHelper; private TrackSelectionHelper trackSelectionHelper;
...@@ -155,9 +155,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -155,9 +155,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
userAgent = Util.getUserAgent(this, "ExoPlayerDemo"); userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
manifestDataSourceFactory = new DefaultDataSourceFactory(this, userAgent); manifestDataSourceFactory = new DefaultDataSourceFactory(this, userAgent);
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); bandwidthMeter = new DefaultBandwidthMeter();
mediaDataSourceFactory = new DefaultDataSourceFactory(this, userAgent, bandwidthMeter); mediaDataSourceFactory = new DefaultDataSourceFactory(this, userAgent, bandwidthMeter);
formatEvaluatorFactory = new AdaptiveEvaluator.Factory(bandwidthMeter);
mainHandler = new Handler(); mainHandler = new Handler();
setContentView(R.layout.player_activity); setContentView(R.layout.player_activity);
...@@ -284,12 +283,15 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -284,12 +283,15 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
return; return;
} }
} }
eventLogger = new EventLogger(); eventLogger = new EventLogger();
eventLogger.startSession(); eventLogger.startSession();
trackSelector = new DefaultTrackSelector(mainHandler); TrackSelection.Factory videoTrackSelectionFactory =
new AdaptiveVideoTrackSelection.Factory(bandwidthMeter);
trackSelector = new DefaultTrackSelector(mainHandler, videoTrackSelectionFactory);
trackSelector.addListener(this); trackSelector.addListener(this);
trackSelector.addListener(eventLogger); trackSelector.addListener(eventLogger);
trackSelectionHelper = new TrackSelectionHelper(trackSelector); trackSelectionHelper = new TrackSelectionHelper(trackSelector, videoTrackSelectionFactory);
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, new DefaultLoadControl(), player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, new DefaultLoadControl(),
drmSessionManager, preferExtensionDecoders); drmSessionManager, preferExtensionDecoders);
player.addListener(this); player.addListener(this);
...@@ -354,15 +356,14 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -354,15 +356,14 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
switch (type) { switch (type) {
case Util.TYPE_SS: case Util.TYPE_SS:
DefaultSsChunkSource.Factory factory = new DefaultSsChunkSource.Factory( DefaultSsChunkSource.Factory factory = new DefaultSsChunkSource.Factory(
mediaDataSourceFactory, formatEvaluatorFactory); mediaDataSourceFactory);
return new SsMediaSource(uri, manifestDataSourceFactory, factory, mainHandler, eventLogger); return new SsMediaSource(uri, manifestDataSourceFactory, factory, mainHandler, eventLogger);
case Util.TYPE_DASH: case Util.TYPE_DASH:
DefaultDashChunkSource.Factory factory2 = new DefaultDashChunkSource.Factory( DefaultDashChunkSource.Factory factory2 = new DefaultDashChunkSource.Factory(
mediaDataSourceFactory, formatEvaluatorFactory); mediaDataSourceFactory);
return new DashMediaSource(uri, mediaDataSourceFactory, factory2, mainHandler, eventLogger); return new DashMediaSource(uri, mediaDataSourceFactory, factory2, mainHandler, eventLogger);
case Util.TYPE_HLS: case Util.TYPE_HLS:
return new HlsMediaSource(uri, mediaDataSourceFactory, formatEvaluatorFactory, mainHandler, return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, eventLogger);
eventLogger);
case Util.TYPE_OTHER: case Util.TYPE_OTHER:
return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(), return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(),
mainHandler, eventLogger); mainHandler, eventLogger);
......
...@@ -19,8 +19,10 @@ import com.google.android.exoplayer2.Format; ...@@ -19,8 +19,10 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; 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;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.TrackInfo; 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.trackselection.TrackSelection;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
...@@ -45,6 +47,7 @@ import java.util.Locale; ...@@ -45,6 +47,7 @@ import java.util.Locale;
DialogInterface.OnClickListener { DialogInterface.OnClickListener {
private final MappingTrackSelector selector; private final MappingTrackSelector selector;
private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory;
private TrackInfo trackInfo; private TrackInfo trackInfo;
private int rendererIndex; private int rendererIndex;
...@@ -55,13 +58,18 @@ import java.util.Locale; ...@@ -55,13 +58,18 @@ import java.util.Locale;
private CheckedTextView disableView; private CheckedTextView disableView;
private CheckedTextView defaultView; private CheckedTextView defaultView;
private CheckedTextView enableRandomAdaptationView;
private CheckedTextView[][] trackViews; private CheckedTextView[][] trackViews;
/** /**
* @param selector The track selector. * @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.selector = selector;
this.adaptiveVideoTrackSelectionFactory = adaptiveVideoTrackSelectionFactory;
} }
/** /**
...@@ -80,8 +88,9 @@ import java.util.Locale; ...@@ -80,8 +88,9 @@ import java.util.Locale;
trackGroups = trackInfo.getTrackGroups(rendererIndex); trackGroups = trackInfo.getTrackGroups(rendererIndex);
trackGroupsAdaptive = new boolean[trackGroups.length]; trackGroupsAdaptive = new boolean[trackGroups.length];
for (int i = 0; i < trackGroups.length; i++) { for (int i = 0; i < trackGroups.length; i++) {
trackGroupsAdaptive[i] = trackInfo.getAdaptiveSupport(rendererIndex, i, false) trackGroupsAdaptive[i] = adaptiveVideoTrackSelectionFactory != null
!= RendererCapabilities.ADAPTIVE_NOT_SUPPORTED; && (trackInfo.getAdaptiveSupport(rendererIndex, i, false)
!= RendererCapabilities.ADAPTIVE_NOT_SUPPORTED);
} }
isDisabled = selector.getRendererDisabled(rendererIndex); isDisabled = selector.getRendererDisabled(rendererIndex);
override = selector.hasSelectionOverride(rendererIndex, trackGroups) override = selector.hasSelectionOverride(rendererIndex, trackGroups)
...@@ -118,17 +127,19 @@ import java.util.Locale; ...@@ -118,17 +127,19 @@ import java.util.Locale;
// Per-track views. // Per-track views.
boolean haveSupportedTracks = false; boolean haveSupportedTracks = false;
boolean haveAdaptiveTracks = false;
trackViews = new CheckedTextView[trackGroups.length][]; trackViews = new CheckedTextView[trackGroups.length][];
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
TrackGroup group = trackGroups.get(groupIndex); TrackGroup group = trackGroups.get(groupIndex);
boolean groupIsAdaptive = group.length > 1 && trackGroupsAdaptive[groupIndex];
haveAdaptiveTracks |= groupIsAdaptive;
trackViews[groupIndex] = new CheckedTextView[group.length]; trackViews[groupIndex] = new CheckedTextView[group.length];
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) { for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
if (trackIndex == 0) { if (trackIndex == 0) {
root.addView(inflater.inflate(R.layout.list_divider, root, false)); root.addView(inflater.inflate(R.layout.list_divider, root, false));
} }
int trackViewLayoutId = group.length < 2 || !trackGroupsAdaptive[groupIndex] int trackViewLayoutId = groupIsAdaptive ? android.R.layout.simple_list_item_multiple_choice
? android.R.layout.simple_list_item_single_choice : android.R.layout.simple_list_item_single_choice;
: android.R.layout.simple_list_item_multiple_choice;
CheckedTextView trackView = (CheckedTextView) inflater.inflate( CheckedTextView trackView = (CheckedTextView) inflater.inflate(
trackViewLayoutId, root, false); trackViewLayoutId, root, false);
trackView.setText(buildTrackName(group.getFormat(trackIndex))); trackView.setText(buildTrackName(group.getFormat(trackIndex)));
...@@ -148,6 +159,14 @@ import java.util.Locale; ...@@ -148,6 +159,14 @@ import java.util.Locale;
if (!haveSupportedTracks) { if (!haveSupportedTracks) {
// Indicate that the default selection will be nothing. // Indicate that the default selection will be nothing.
defaultView.setText(R.string.selection_default_none); 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(); updateViews();
...@@ -158,9 +177,18 @@ import java.util.Locale; ...@@ -158,9 +177,18 @@ import java.util.Locale;
disableView.setChecked(isDisabled); disableView.setChecked(isDisabled);
defaultView.setChecked(!isDisabled && override == null); defaultView.setChecked(!isDisabled && override == null);
for (int i = 0; i < trackViews.length; i++) { for (int i = 0; i < trackViews.length; i++) {
TrackGroup trackGroup = trackGroups.get(i);
for (int j = 0; j < trackViews[i].length; j++) { for (int j = 0; j < trackViews[i].length; j++) {
trackViews[i][j].setChecked( trackViews[i][j].setChecked(override != null && override.getTrackGroup() == trackGroup
override != null && override.group == trackGroups.get(i) && override.indexOf(j) != -1); && 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; ...@@ -191,6 +219,9 @@ import java.util.Locale;
} else if (view == defaultView) { } else if (view == defaultView) {
isDisabled = false; isDisabled = false;
override = null; override = null;
} else if (view == enableRandomAdaptationView) {
setOverride(override.getTrackGroup(), getTracks(override),
!enableRandomAdaptationView.isChecked());
} else { } else {
isDisabled = false; isDisabled = false;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
...@@ -198,31 +229,25 @@ import java.util.Locale; ...@@ -198,31 +229,25 @@ import java.util.Locale;
TrackGroup group = tag.first; TrackGroup group = tag.first;
int trackIndex = tag.second; int trackIndex = tag.second;
if (!trackGroupsAdaptive[trackGroups.indexOf(group)] || override == null) { if (!trackGroupsAdaptive[trackGroups.indexOf(group)] || override == null) {
override = new TrackSelection(group, trackIndex); override = new FixedTrackSelection(group, trackIndex);
} else { } else {
// The group being modified is adaptive and we already have a non-null override. // The group being modified is adaptive and we already have a non-null override.
boolean isEnabled = ((CheckedTextView) view).isChecked(); boolean isEnabled = ((CheckedTextView) view).isChecked();
int overrideLength = override.length();
if (isEnabled) { if (isEnabled) {
// Remove the track from the override. // Remove the track from the override.
if (override.length == 1) { if (overrideLength == 1) {
// The last track is being removed, so the override becomes empty. // The last track is being removed, so the override becomes empty.
override = null; override = null;
isDisabled = true; isDisabled = true;
} else { } else {
int[] tracks = new int[override.length - 1]; setOverride(group, getTracksRemoving(override, trackIndex),
int trackCount = 0; enableRandomAdaptationView.isChecked());
for (int i = 0; i < override.length; i++) {
if (override.getTrack(i) != trackIndex) {
tracks[trackCount++] = override.getTrack(i);
}
}
override = new TrackSelection(group, tracks);
} }
} else { } else {
// Add the track to the override. // Add the track to the override.
int[] tracks = Arrays.copyOf(override.getTracks(), override.length + 1); setOverride(group, getTracksAdding(override, trackIndex),
tracks[tracks.length - 1] = trackIndex; enableRandomAdaptationView.isChecked());
override = new TrackSelection(group, tracks);
} }
} }
} }
...@@ -230,6 +255,41 @@ import java.util.Locale; ...@@ -230,6 +255,41 @@ import java.util.Locale;
updateViews(); 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. // Track name construction.
private static String buildTrackName(Format format) { private static String buildTrackName(Format format) {
......
...@@ -33,6 +33,8 @@ ...@@ -33,6 +33,8 @@
<string name="selection_default_none">Default (none)</string> <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_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> <string name="error_drm_unsupported_scheme">This device does not support the required DRM scheme</string>
......
...@@ -834,9 +834,9 @@ import java.util.ArrayList; ...@@ -834,9 +834,9 @@ import java.util.ArrayList;
if (newSelection != null) { if (newSelection != null) {
// Replace the renderer's SampleStream so the transition to playing the next period // Replace the renderer's SampleStream so the transition to playing the next period
// can be seamless. // can be seamless.
Format[] formats = new Format[newSelection.length]; Format[] formats = new Format[newSelection.length()];
for (int j = 0; j < formats.length; j++) { 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], renderer.replaceStream(formats, readingPeriod.sampleStreams[i],
readingPeriod.offsetUs); readingPeriod.offsetUs);
...@@ -1087,9 +1087,9 @@ import java.util.ArrayList; ...@@ -1087,9 +1087,9 @@ import java.util.ArrayList;
// Consider as joining only if the renderer was previously disabled. // Consider as joining only if the renderer was previously disabled.
boolean joining = !rendererWasEnabledFlags[i] && playing; boolean joining = !rendererWasEnabledFlags[i] && playing;
// Build an array of formats contained by the selection. // 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++) { for (int j = 0; j < formats.length; j++) {
formats[j] = newSelection.group.getFormat(newSelection.getTrack(j)); formats[j] = newSelection.getFormat(j);
} }
// Enable the renderer. // Enable the renderer.
renderer.enable(formats, playingPeriod.sampleStreams[i], internalPositionUs, joining, renderer.enable(formats, playingPeriod.sampleStreams[i], internalPositionUs, joining,
......
...@@ -250,9 +250,9 @@ public final class ExtractorMediaSource implements MediaPeriod, MediaSource, ...@@ -250,9 +250,9 @@ public final class ExtractorMediaSource implements MediaPeriod, MediaSource,
SampleStream[] newStreams = new SampleStream[newSelections.size()]; SampleStream[] newStreams = new SampleStream[newSelections.size()];
for (int i = 0; i < newStreams.length; i++) { for (int i = 0; i < newStreams.length; i++) {
TrackSelection selection = newSelections.get(i); TrackSelection selection = newSelections.get(i);
Assertions.checkState(selection.length == 1); Assertions.checkState(selection.length() == 1);
Assertions.checkState(selection.getTrack(0) == 0); Assertions.checkState(selection.getIndexInTrackGroup(0) == 0);
int track = tracks.indexOf(selection.group); int track = tracks.indexOf(selection.getTrackGroup());
Assertions.checkState(!trackEnabledStates[track]); Assertions.checkState(!trackEnabledStates[track]);
enabledTrackCount++; enabledTrackCount++;
trackEnabledStates[track] = true; trackEnabledStates[track] = true;
......
...@@ -220,7 +220,7 @@ public final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callba ...@@ -220,7 +220,7 @@ public final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callba
TrackGroupArray periodTrackGroups = period.getTrackGroups(); TrackGroupArray periodTrackGroups = period.getTrackGroups();
for (int i = 0; i < allNewSelections.size(); i++) { for (int i = 0; i < allNewSelections.size(); i++) {
TrackSelection selection = allNewSelections.get(i); TrackSelection selection = allNewSelections.get(i);
if (periodTrackGroups.indexOf(selection.group) != -1) { if (periodTrackGroups.indexOf(selection.getTrackGroup()) != -1) {
newSelectionOriginalIndices[newSelections.size()] = i; newSelectionOriginalIndices[newSelections.size()] = i;
newSelections.add(selection); newSelections.add(selection);
} }
......
...@@ -150,7 +150,6 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S ...@@ -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. * This method should be called when the stream is no longer required.
*/ */
public void release() { public void release() {
chunkSource.release();
sampleQueue.disable(); sampleQueue.disable();
loader.release(); loader.release();
} }
......
...@@ -84,11 +84,4 @@ public interface ChunkSource { ...@@ -84,11 +84,4 @@ public interface ChunkSource {
*/ */
boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e); 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; ...@@ -222,7 +222,7 @@ import java.util.List;
private ChunkSampleStream<DashChunkSource> buildSampleStream(TrackSelection selection, private ChunkSampleStream<DashChunkSource> buildSampleStream(TrackSelection selection,
long positionUs) { long positionUs) {
int adaptationSetIndex = trackGroups.indexOf(selection.group); int adaptationSetIndex = trackGroups.indexOf(selection.getTrackGroup());
AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex); AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex);
DashChunkSource chunkSource = chunkSourceFactory.createDashChunkSource( DashChunkSource chunkSource = chunkSourceFactory.createDashChunkSource(
manifestLoaderErrorThrower, manifest, index, adaptationSetIndex, selection, manifestLoaderErrorThrower, manifest, index, adaptationSetIndex, selection,
......
...@@ -28,8 +28,6 @@ import com.google.android.exoplayer2.source.chunk.Chunk; ...@@ -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.ChunkExtractorWrapper;
import com.google.android.exoplayer2.source.chunk.ChunkHolder; import com.google.android.exoplayer2.source.chunk.ChunkHolder;
import com.google.android.exoplayer2.source.chunk.ContainerMediaChunk; 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.InitializationChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunk; import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.chunk.SingleSampleMediaChunk; import com.google.android.exoplayer2.source.chunk.SingleSampleMediaChunk;
...@@ -56,25 +54,19 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -56,25 +54,19 @@ public class DefaultDashChunkSource implements DashChunkSource {
public static final class Factory implements DashChunkSource.Factory { public static final class Factory implements DashChunkSource.Factory {
private final FormatEvaluator.Factory formatEvaluatorFactory;
private final DataSource.Factory dataSourceFactory; private final DataSource.Factory dataSourceFactory;
public Factory(DataSource.Factory dataSourceFactory, public Factory(DataSource.Factory dataSourceFactory) {
FormatEvaluator.Factory formatEvaluatorFactory) {
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.formatEvaluatorFactory = formatEvaluatorFactory;
} }
@Override @Override
public DashChunkSource createDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, public DashChunkSource createDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower,
DashManifest manifest, int periodIndex, int adaptationSetIndex, DashManifest manifest, int periodIndex, int adaptationSetIndex,
TrackSelection trackSelection, long elapsedRealtimeOffsetMs) { TrackSelection trackSelection, long elapsedRealtimeOffsetMs) {
FormatEvaluator adaptiveEvaluator = trackSelection.length > 1
? formatEvaluatorFactory.createFormatEvaluator() : null;
DataSource dataSource = dataSourceFactory.createDataSource(); DataSource dataSource = dataSourceFactory.createDataSource();
return new DefaultDashChunkSource(manifestLoaderErrorThrower, manifest, periodIndex, return new DefaultDashChunkSource(manifestLoaderErrorThrower, manifest, periodIndex,
adaptationSetIndex, trackSelection, dataSource, adaptiveEvaluator, adaptationSetIndex, trackSelection, dataSource, elapsedRealtimeOffsetMs);
elapsedRealtimeOffsetMs);
} }
} }
...@@ -83,16 +75,12 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -83,16 +75,12 @@ public class DefaultDashChunkSource implements DashChunkSource {
private final int adaptationSetIndex; private final int adaptationSetIndex;
private final TrackSelection trackSelection; private final TrackSelection trackSelection;
private final RepresentationHolder[] representationHolders; private final RepresentationHolder[] representationHolders;
private final boolean[] adaptiveFormatBlacklistFlags;
private final DataSource dataSource; private final DataSource dataSource;
private final FormatEvaluator adaptiveFormatEvaluator;
private final long elapsedRealtimeOffsetUs; private final long elapsedRealtimeOffsetUs;
private final Evaluation evaluation;
private DashManifest manifest; private DashManifest manifest;
private int periodIndex; private int periodIndex;
private boolean lastChunkWasInitialization;
private IOException fatalError; private IOException fatalError;
private boolean missingLastSegment; private boolean missingLastSegment;
...@@ -103,38 +91,28 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -103,38 +91,28 @@ public class DefaultDashChunkSource implements DashChunkSource {
* @param adaptationSetIndex The index of the adaptation set in the period. * @param adaptationSetIndex The index of the adaptation set in the period.
* @param trackSelection The track selection. * @param trackSelection The track selection.
* @param dataSource A {@link DataSource} suitable for loading the media data. * @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 * @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
* server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified * 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. * as the server's unix time minus the local elapsed time. If unknown, set to 0.
*/ */
public DefaultDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, public DefaultDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower,
DashManifest manifest, int periodIndex, int adaptationSetIndex, TrackSelection trackSelection, DashManifest manifest, int periodIndex, int adaptationSetIndex, TrackSelection trackSelection,
DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator, DataSource dataSource, long elapsedRealtimeOffsetMs) {
long elapsedRealtimeOffsetMs) {
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
this.manifest = manifest; this.manifest = manifest;
this.adaptationSetIndex = adaptationSetIndex; this.adaptationSetIndex = adaptationSetIndex;
this.trackSelection = trackSelection; this.trackSelection = trackSelection;
this.dataSource = dataSource; this.dataSource = dataSource;
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
this.periodIndex = periodIndex; this.periodIndex = periodIndex;
this.elapsedRealtimeOffsetUs = elapsedRealtimeOffsetMs * 1000; this.elapsedRealtimeOffsetUs = elapsedRealtimeOffsetMs * 1000;
this.evaluation = new Evaluation();
long periodDurationUs = getPeriodDurationUs(); long periodDurationUs = getPeriodDurationUs();
List<Representation> representations = getRepresentations(); List<Representation> representations = getRepresentations();
representationHolders = new RepresentationHolder[trackSelection.length]; representationHolders = new RepresentationHolder[trackSelection.length()];
for (int i = 0; i < trackSelection.length; i++) { for (int i = 0; i < representationHolders.length; i++) {
Representation representation = representations.get(trackSelection.getTrack(i)); Representation representation = representations.get(trackSelection.getIndexInTrackGroup(i));
representationHolders[i] = new RepresentationHolder(periodDurationUs, representation); representationHolders[i] = new RepresentationHolder(periodDurationUs, representation);
} }
if (adaptiveFormatEvaluator != null) {
adaptiveFormatEvaluator.enable(trackSelection.getFormats());
adaptiveFormatBlacklistFlags = new boolean[trackSelection.length];
} else {
adaptiveFormatBlacklistFlags = null;
}
} }
@Override @Override
...@@ -144,8 +122,8 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -144,8 +122,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
periodIndex = newPeriodIndex; periodIndex = newPeriodIndex;
long periodDurationUs = getPeriodDurationUs(); long periodDurationUs = getPeriodDurationUs();
List<Representation> representations = getRepresentations(); List<Representation> representations = getRepresentations();
for (int i = 0; i < trackSelection.length; i++) { for (int i = 0; i < representationHolders.length; i++) {
Representation representation = representations.get(trackSelection.getTrack(i)); Representation representation = representations.get(trackSelection.getIndexInTrackGroup(i));
representationHolders[i].updateRepresentation(periodDurationUs, representation); representationHolders[i].updateRepresentation(periodDurationUs, representation);
} }
} catch (BehindLiveWindowException e) { } catch (BehindLiveWindowException e) {
...@@ -164,11 +142,10 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -164,11 +142,10 @@ public class DefaultDashChunkSource implements DashChunkSource {
@Override @Override
public int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) { 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 queue.size();
} }
return adaptiveFormatEvaluator.evaluateQueueSize(playbackPositionUs, queue, return trackSelection.evaluateQueueSize(playbackPositionUs, queue);
adaptiveFormatBlacklistFlags);
} }
@Override @Override
...@@ -177,25 +154,11 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -177,25 +154,11 @@ public class DefaultDashChunkSource implements DashChunkSource {
return; return;
} }
if (evaluation.format == null || !lastChunkWasInitialization) { long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
if (trackSelection.length > 1) { trackSelection.updateSelectedTrack(bufferedDurationUs);
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;
}
RepresentationHolder representationHolder = RepresentationHolder representationHolder =
representationHolders[trackSelection.indexOf(selectedFormat)]; representationHolders[trackSelection.getSelectedIndex()];
Representation selectedRepresentation = representationHolder.representation; Representation selectedRepresentation = representationHolder.representation;
DashSegmentIndex segmentIndex = representationHolder.segmentIndex; DashSegmentIndex segmentIndex = representationHolder.segmentIndex;
...@@ -211,9 +174,8 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -211,9 +174,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
if (pendingInitializationUri != null || pendingIndexUri != null) { if (pendingInitializationUri != null || pendingIndexUri != null) {
// We have initialization and/or index requests to make. // We have initialization and/or index requests to make.
Chunk initializationChunk = newInitializationChunk(representationHolder, dataSource, Chunk initializationChunk = newInitializationChunk(representationHolder, dataSource,
selectedFormat, evaluation.reason, evaluation.data, pendingInitializationUri, trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(),
pendingIndexUri); trackSelection.getSelectionData(), pendingInitializationUri, pendingIndexUri);
lastChunkWasInitialization = true;
out.chunk = initializationChunk; out.chunk = initializationChunk;
return; return;
} }
...@@ -256,9 +218,9 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -256,9 +218,9 @@ public class DefaultDashChunkSource implements DashChunkSource {
return; return;
} }
Chunk nextMediaChunk = newMediaChunk(representationHolder, dataSource, selectedFormat, Chunk nextMediaChunk = newMediaChunk(representationHolder, dataSource,
evaluation.reason, evaluation.data, sampleFormat, segmentNum); trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(),
lastChunkWasInitialization = false; trackSelection.getSelectionData(), sampleFormat, segmentNum);
out.chunk = nextMediaChunk; out.chunk = nextMediaChunk;
} }
...@@ -303,13 +265,6 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -303,13 +265,6 @@ public class DefaultDashChunkSource implements DashChunkSource {
return false; return false;
} }
@Override
public void release() {
if (adaptiveFormatEvaluator != null) {
adaptiveFormatEvaluator.disable();
}
}
// Private methods. // Private methods.
private List<Representation> getRepresentations() { private List<Representation> getRepresentations() {
......
...@@ -28,7 +28,6 @@ import com.google.android.exoplayer2.source.SinglePeriodTimeline; ...@@ -28,7 +28,6 @@ import com.google.android.exoplayer2.source.SinglePeriodTimeline;
import com.google.android.exoplayer2.source.Timeline; import com.google.android.exoplayer2.source.Timeline;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; 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.HlsMasterPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist;
...@@ -64,7 +63,6 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource, ...@@ -64,7 +63,6 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
private final Uri manifestUri; private final Uri manifestUri;
private final DataSource.Factory dataSourceFactory; private final DataSource.Factory dataSourceFactory;
private final FormatEvaluator.Factory formatEvaluatorFactory;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final IdentityHashMap<SampleStream, HlsSampleStreamWrapper> sampleStreamSources; private final IdentityHashMap<SampleStream, HlsSampleStreamWrapper> sampleStreamSources;
...@@ -89,19 +87,17 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource, ...@@ -89,19 +87,17 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
private HlsSampleStreamWrapper[] enabledSampleStreamWrappers; private HlsSampleStreamWrapper[] enabledSampleStreamWrappers;
private CompositeSequenceableLoader sequenceableLoader; private CompositeSequenceableLoader sequenceableLoader;
public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory, public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory, Handler eventHandler,
FormatEvaluator.Factory formatEvaluatorFactory, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) { AdaptiveMediaSourceEventListener eventListener) {
this(manifestUri, dataSourceFactory, formatEvaluatorFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, this(manifestUri, dataSourceFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, eventHandler,
eventHandler, eventListener); eventListener);
} }
public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory, public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory,
FormatEvaluator.Factory formatEvaluatorFactory, int minLoadableRetryCount, int minLoadableRetryCount, Handler eventHandler,
Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) { AdaptiveMediaSourceEventListener eventListener) {
this.manifestUri = manifestUri; this.manifestUri = manifestUri;
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.formatEvaluatorFactory = formatEvaluatorFactory;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
eventDispatcher = new EventDispatcher(eventHandler, eventListener); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
...@@ -352,7 +348,7 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource, ...@@ -352,7 +348,7 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
Format.NO_VALUE); Format.NO_VALUE);
Variant[] variants = new Variant[] {new Variant(playlist.baseUri, format, null)}; Variant[] variants = new Variant[] {new Variant(playlist.baseUri, format, null)};
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants, sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants,
formatEvaluatorFactory.createFormatEvaluator(), null, null)); null, null));
return sampleStreamWrappers; return sampleStreamWrappers;
} }
...@@ -386,8 +382,7 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource, ...@@ -386,8 +382,7 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
Variant[] variants = new Variant[selectedVariants.size()]; Variant[] variants = new Variant[selectedVariants.size()];
selectedVariants.toArray(variants); selectedVariants.toArray(variants);
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants, sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, baseUri, variants,
formatEvaluatorFactory.createFormatEvaluator(), masterPlaylist.muxedAudioFormat, masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat));
masterPlaylist.muxedCaptionFormat));
} }
// Build the audio stream wrapper if applicable. // Build the audio stream wrapper if applicable.
...@@ -396,7 +391,7 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource, ...@@ -396,7 +391,7 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
Variant[] variants = new Variant[audioVariants.size()]; Variant[] variants = new Variant[audioVariants.size()];
audioVariants.toArray(variants); audioVariants.toArray(variants);
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO, baseUri, variants, null, sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO, baseUri, variants, null,
null, null)); null));
} }
// Build the text stream wrapper if applicable. // Build the text stream wrapper if applicable.
...@@ -405,18 +400,17 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource, ...@@ -405,18 +400,17 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
Variant[] variants = new Variant[subtitleVariants.size()]; Variant[] variants = new Variant[subtitleVariants.size()];
subtitleVariants.toArray(variants); subtitleVariants.toArray(variants);
sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_TEXT, baseUri, variants, null, sampleStreamWrappers.add(buildSampleStreamWrapper(C.TRACK_TYPE_TEXT, baseUri, variants, null,
null, null)); null));
} }
return sampleStreamWrappers; return sampleStreamWrappers;
} }
private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, String baseUri, private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, String baseUri,
Variant[] variants, FormatEvaluator formatEvaluator, Format muxedAudioFormat, Variant[] variants, Format muxedAudioFormat, Format muxedCaptionFormat) {
Format muxedCaptionFormat) {
DataSource dataSource = dataSourceFactory.createDataSource(); DataSource dataSource = dataSourceFactory.createDataSource();
HlsChunkSource defaultChunkSource = new HlsChunkSource(baseUri, variants, dataSource, HlsChunkSource defaultChunkSource = new HlsChunkSource(baseUri, variants, dataSource,
timestampAdjusterProvider, formatEvaluator); timestampAdjusterProvider);
return new HlsSampleStreamWrapper(trackType, this, defaultChunkSource, allocator, return new HlsSampleStreamWrapper(trackType, this, defaultChunkSource, allocator,
preparePositionUs, muxedAudioFormat, muxedCaptionFormat, minLoadableRetryCount, preparePositionUs, muxedAudioFormat, muxedCaptionFormat, minLoadableRetryCount,
eventDispatcher); eventDispatcher);
...@@ -440,7 +434,7 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource, ...@@ -440,7 +434,7 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
int[] newSelectionOriginalIndices = new int[allNewSelections.size()]; int[] newSelectionOriginalIndices = new int[allNewSelections.size()];
for (int i = 0; i < allNewSelections.size(); i++) { for (int i = 0; i < allNewSelections.size(); i++) {
TrackSelection selection = allNewSelections.get(i); TrackSelection selection = allNewSelections.get(i);
if (sampleStreamWrapperTrackGroups.indexOf(selection.group) != -1) { if (sampleStreamWrapperTrackGroups.indexOf(selection.getTrackGroup()) != -1) {
newSelectionOriginalIndices[newSelections.size()] = i; newSelectionOriginalIndices[newSelections.size()] = i;
newSelections.add(selection); newSelections.add(selection);
} }
......
...@@ -168,11 +168,10 @@ import java.util.List; ...@@ -168,11 +168,10 @@ import java.util.List;
SampleStream[] newStreams = new SampleStream[newSelections.size()]; SampleStream[] newStreams = new SampleStream[newSelections.size()];
for (int i = 0; i < newStreams.length; i++) { for (int i = 0; i < newStreams.length; i++) {
TrackSelection selection = newSelections.get(i); TrackSelection selection = newSelections.get(i);
int group = trackGroups.indexOf(selection.group); int group = trackGroups.indexOf(selection.getTrackGroup());
int[] tracks = selection.getTracks();
setTrackGroupEnabledState(group, true); setTrackGroupEnabledState(group, true);
if (group == primaryTrackGroupIndex) { if (group == primaryTrackGroupIndex) {
chunkSource.selectTracks(new TrackSelection(chunkSource.getTrackGroup(), tracks)); chunkSource.selectTracks(selection);
} }
newStreams[i] = new SampleStreamImpl(group); newStreams[i] = new SampleStreamImpl(group);
} }
...@@ -236,7 +235,6 @@ import java.util.List; ...@@ -236,7 +235,6 @@ import java.util.List;
} }
public void release() { public void release() {
chunkSource.release();
int sampleQueueCount = sampleQueues.size(); int sampleQueueCount = sampleQueues.size();
for (int i = 0; i < sampleQueueCount; i++) { for (int i = 0; i < sampleQueueCount; i++) {
sampleQueues.valueAt(i).disable(); sampleQueues.valueAt(i).disable();
......
...@@ -25,8 +25,6 @@ import com.google.android.exoplayer2.source.chunk.Chunk; ...@@ -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.ChunkExtractorWrapper;
import com.google.android.exoplayer2.source.chunk.ChunkHolder; import com.google.android.exoplayer2.source.chunk.ChunkHolder;
import com.google.android.exoplayer2.source.chunk.ContainerMediaChunk; 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.chunk.MediaChunk;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
...@@ -47,24 +45,19 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -47,24 +45,19 @@ public class DefaultSsChunkSource implements SsChunkSource {
public static final class Factory implements SsChunkSource.Factory { public static final class Factory implements SsChunkSource.Factory {
private final FormatEvaluator.Factory formatEvaluatorFactory;
private final DataSource.Factory dataSourceFactory; private final DataSource.Factory dataSourceFactory;
public Factory(DataSource.Factory dataSourceFactory, public Factory(DataSource.Factory dataSourceFactory) {
FormatEvaluator.Factory formatEvaluatorFactory) {
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.formatEvaluatorFactory = formatEvaluatorFactory;
} }
@Override @Override
public SsChunkSource createChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, public SsChunkSource createChunkSource(LoaderErrorThrower manifestLoaderErrorThrower,
SsManifest manifest, int elementIndex, TrackSelection trackSelection, SsManifest manifest, int elementIndex, TrackSelection trackSelection,
TrackEncryptionBox[] trackEncryptionBoxes) { TrackEncryptionBox[] trackEncryptionBoxes) {
FormatEvaluator adaptiveEvaluator = trackSelection.length > 1
? formatEvaluatorFactory.createFormatEvaluator() : null;
DataSource dataSource = dataSourceFactory.createDataSource(); DataSource dataSource = dataSourceFactory.createDataSource();
return new DefaultSsChunkSource(manifestLoaderErrorThrower, manifest, elementIndex, return new DefaultSsChunkSource(manifestLoaderErrorThrower, manifest, elementIndex,
trackSelection, dataSource, adaptiveEvaluator, trackEncryptionBoxes); trackSelection, dataSource, trackEncryptionBoxes);
} }
} }
...@@ -73,10 +66,7 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -73,10 +66,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
private final int elementIndex; private final int elementIndex;
private final TrackSelection trackSelection; private final TrackSelection trackSelection;
private final ChunkExtractorWrapper[] extractorWrappers; private final ChunkExtractorWrapper[] extractorWrappers;
private final boolean[] adaptiveFormatBlacklistFlags;
private final DataSource dataSource; private final DataSource dataSource;
private final Evaluation evaluation;
private final FormatEvaluator adaptiveFormatEvaluator;
private SsManifest manifest; private SsManifest manifest;
private int currentManifestChunkOffset; private int currentManifestChunkOffset;
...@@ -89,26 +79,23 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -89,26 +79,23 @@ public class DefaultSsChunkSource implements SsChunkSource {
* @param elementIndex The index of the stream element in the manifest. * @param elementIndex The index of the stream element in the manifest.
* @param trackSelection The track selection. * @param trackSelection The track selection.
* @param dataSource A {@link DataSource} suitable for loading the media data. * @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. * @param trackEncryptionBoxes Track encryption boxes for the stream.
*/ */
public DefaultSsChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, SsManifest manifest, public DefaultSsChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, SsManifest manifest,
int elementIndex, TrackSelection trackSelection, DataSource dataSource, int elementIndex, TrackSelection trackSelection, DataSource dataSource,
FormatEvaluator adaptiveFormatEvaluator, TrackEncryptionBox[] trackEncryptionBoxes) { TrackEncryptionBox[] trackEncryptionBoxes) {
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
this.manifest = manifest; this.manifest = manifest;
this.elementIndex = elementIndex; this.elementIndex = elementIndex;
this.trackSelection = trackSelection; this.trackSelection = trackSelection;
this.dataSource = dataSource; this.dataSource = dataSource;
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
this.evaluation = new Evaluation();
StreamElement streamElement = manifest.streamElements[elementIndex]; StreamElement streamElement = manifest.streamElements[elementIndex];
extractorWrappers = new ChunkExtractorWrapper[trackSelection.length]; extractorWrappers = new ChunkExtractorWrapper[trackSelection.length()];
for (int i = 0; i < trackSelection.length; i++) { for (int i = 0; i < extractorWrappers.length; i++) {
int manifestTrackIndex = trackSelection.getTrack(i); int manifestTrackIndex = trackSelection.getIndexInTrackGroup(i);
Format format = trackSelection.getFormat(i); Format format = streamElement.formats[i];
int nalUnitLengthFieldLength = streamElement.type == C.TRACK_TYPE_VIDEO ? 4 : -1; int nalUnitLengthFieldLength = streamElement.type == C.TRACK_TYPE_VIDEO ? 4 : -1;
Track track = new Track(manifestTrackIndex, streamElement.type, streamElement.timescale, Track track = new Track(manifestTrackIndex, streamElement.type, streamElement.timescale,
C.UNSET_TIME_US, manifest.durationUs, format, Track.TRANSFORMATION_NONE, C.UNSET_TIME_US, manifest.durationUs, format, Track.TRANSFORMATION_NONE,
...@@ -118,12 +105,6 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -118,12 +105,6 @@ public class DefaultSsChunkSource implements SsChunkSource {
| FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, track); | FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, track);
extractorWrappers[i] = new ChunkExtractorWrapper(extractor, format, false); extractorWrappers[i] = new ChunkExtractorWrapper(extractor, format, false);
} }
if (adaptiveFormatEvaluator != null) {
adaptiveFormatEvaluator.enable(trackSelection.getFormats());
adaptiveFormatBlacklistFlags = new boolean[trackSelection.length];
} else {
adaptiveFormatBlacklistFlags = null;
}
} }
@Override @Override
...@@ -162,11 +143,10 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -162,11 +143,10 @@ public class DefaultSsChunkSource implements SsChunkSource {
@Override @Override
public int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) { 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 queue.size();
} }
return adaptiveFormatEvaluator.evaluateQueueSize(playbackPositionUs, queue, return trackSelection.evaluateQueueSize(playbackPositionUs, queue);
adaptiveFormatBlacklistFlags);
} }
@Override @Override
...@@ -175,20 +155,8 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -175,20 +155,8 @@ public class DefaultSsChunkSource implements SsChunkSource {
return; return;
} }
if (trackSelection.length > 1) { long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0; trackSelection.updateSelectedTrack(bufferedDurationUs);
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;
}
StreamElement streamElement = manifest.streamElements[elementIndex]; StreamElement streamElement = manifest.streamElements[elementIndex];
if (streamElement.chunkCount == 0) { if (streamElement.chunkCount == 0) {
...@@ -219,14 +187,15 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -219,14 +187,15 @@ public class DefaultSsChunkSource implements SsChunkSource {
long chunkEndTimeUs = chunkStartTimeUs + streamElement.getChunkDurationUs(chunkIndex); long chunkEndTimeUs = chunkStartTimeUs + streamElement.getChunkDurationUs(chunkIndex);
int currentAbsoluteChunkIndex = chunkIndex + currentManifestChunkOffset; int currentAbsoluteChunkIndex = chunkIndex + currentManifestChunkOffset;
int trackSelectionIndex = trackSelection.indexOf(selectedFormat); int trackSelectionIndex = trackSelection.getSelectedIndex();
ChunkExtractorWrapper extractorWrapper = extractorWrappers[trackSelectionIndex]; ChunkExtractorWrapper extractorWrapper = extractorWrappers[trackSelectionIndex];
int manifestTrackIndex = trackSelection.getTrack(trackSelectionIndex); int manifestTrackIndex = trackSelection.getIndexInTrackGroup(trackSelectionIndex);
Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex); Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex);
out.chunk = newMediaChunk(selectedFormat, dataSource, uri, null, currentAbsoluteChunkIndex, out.chunk = newMediaChunk(trackSelection.getSelectedFormat(), dataSource, uri, null,
chunkStartTimeUs, chunkEndTimeUs, evaluation.reason, evaluation.data, extractorWrapper); currentAbsoluteChunkIndex, chunkStartTimeUs, chunkEndTimeUs,
trackSelection.getSelectionReason(), trackSelection.getSelectionData(), extractorWrapper);
} }
@Override @Override
...@@ -240,13 +209,6 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -240,13 +209,6 @@ public class DefaultSsChunkSource implements SsChunkSource {
return false; return false;
} }
@Override
public void release() {
if (adaptiveFormatEvaluator != null) {
adaptiveFormatEvaluator.disable();
}
}
// Private methods. // Private methods.
private static MediaChunk newMediaChunk(Format format, DataSource dataSource, Uri uri, private static MediaChunk newMediaChunk(Format format, DataSource dataSource, Uri uri,
......
...@@ -199,7 +199,7 @@ import java.util.List; ...@@ -199,7 +199,7 @@ import java.util.List;
private ChunkSampleStream<SsChunkSource> buildSampleStream(TrackSelection selection, private ChunkSampleStream<SsChunkSource> buildSampleStream(TrackSelection selection,
long positionUs) { long positionUs) {
int streamElementIndex = trackGroups.indexOf(selection.group); int streamElementIndex = trackGroups.indexOf(selection.getTrackGroup());
SsChunkSource chunkSource = chunkSourceFactory.createChunkSource(manifestLoaderErrorThrower, SsChunkSource chunkSource = chunkSourceFactory.createChunkSource(manifestLoaderErrorThrower,
manifest, streamElementIndex, selection, trackEncryptionBoxes); manifest, streamElementIndex, selection, trackEncryptionBoxes);
return new ChunkSampleStream<>(manifest.streamElements[streamElementIndex].type, chunkSource, 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 { ...@@ -51,6 +51,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private static final int[] NO_TRACKS = new int[0]; private static final int[] NO_TRACKS = new int[0];
private static final String TAG = "DefaultTrackSelector"; private static final String TAG = "DefaultTrackSelector";
private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory;
// Audio and text. // Audio and text.
private String preferredLanguage; private String preferredLanguage;
...@@ -64,8 +66,28 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -64,8 +66,28 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private int viewportWidth; private int viewportWidth;
private int viewportHeight; 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) { 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); super(eventHandler);
this.adaptiveVideoTrackSelectionFactory = adaptiveVideoTrackSelectionFactory;
allowNonSeamlessAdaptiveness = true; allowNonSeamlessAdaptiveness = true;
exceedVideoConstraintsIfNecessary = true; exceedVideoConstraintsIfNecessary = true;
maxVideoWidth = Integer.MAX_VALUE; maxVideoWidth = Integer.MAX_VALUE;
...@@ -203,11 +225,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -203,11 +225,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
rendererTrackSelections[i] = selectTrackForVideoRenderer(rendererCapabilities[i], rendererTrackSelections[i] = selectTrackForVideoRenderer(rendererCapabilities[i],
rendererTrackGroupArrays[i], rendererFormatSupports[i], maxVideoWidth, maxVideoHeight, rendererTrackGroupArrays[i], rendererFormatSupports[i], maxVideoWidth, maxVideoHeight,
allowNonSeamlessAdaptiveness, allowMixedMimeAdaptiveness, viewportWidth, allowNonSeamlessAdaptiveness, allowMixedMimeAdaptiveness, viewportWidth,
viewportHeight, orientationMayChange); viewportHeight, orientationMayChange, adaptiveVideoTrackSelectionFactory,
if (rendererTrackSelections[i] == null && exceedVideoConstraintsIfNecessary) { exceedVideoConstraintsIfNecessary);
rendererTrackSelections[i] = selectSmallestSupportedVideoTrack(
rendererTrackGroupArrays[i], rendererFormatSupports[i]);
}
break; break;
case C.TRACK_TYPE_AUDIO: case C.TRACK_TYPE_AUDIO:
rendererTrackSelections[i] = selectTrackForAudioRenderer(rendererTrackGroupArrays[i], rendererTrackSelections[i] = selectTrackForAudioRenderer(rendererTrackGroupArrays[i],
...@@ -232,28 +251,35 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -232,28 +251,35 @@ public class DefaultTrackSelector extends MappingTrackSelector {
RendererCapabilities rendererCapabilities, TrackGroupArray trackGroups, RendererCapabilities rendererCapabilities, TrackGroupArray trackGroups,
int[][] formatSupport, int maxVideoWidth, int maxVideoHeight, int[][] formatSupport, int maxVideoWidth, int maxVideoHeight,
boolean allowNonSeamlessAdaptiveness, boolean allowMixedMimeAdaptiveness, int viewportWidth, boolean allowNonSeamlessAdaptiveness, boolean allowMixedMimeAdaptiveness, int viewportWidth,
int viewportHeight, boolean orientationMayChange) throws ExoPlaybackException { int viewportHeight, boolean orientationMayChange,
int requiredAdaptiveSupport = allowNonSeamlessAdaptiveness TrackSelection.Factory adaptiveVideoTrackSelectionFactory,
? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS | RendererCapabilities.ADAPTIVE_SEAMLESS) boolean exceedVideoConstraintsIfNecessary) throws ExoPlaybackException {
: RendererCapabilities.ADAPTIVE_SEAMLESS; if (adaptiveVideoTrackSelectionFactory != null) {
boolean allowMixedMimeTypes = allowMixedMimeAdaptiveness int requiredAdaptiveSupport = allowNonSeamlessAdaptiveness
&& (rendererCapabilities.supportsMixedMimeTypeAdaptation() & requiredAdaptiveSupport) != 0; ? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS | RendererCapabilities.ADAPTIVE_SEAMLESS)
TrackGroup largestAdaptiveGroup = null; : RendererCapabilities.ADAPTIVE_SEAMLESS;
int[] largestAdaptiveGroupTracks = NO_TRACKS; boolean allowMixedMimeTypes = allowMixedMimeAdaptiveness
for (int i = 0; i < trackGroups.length; i++) { && (rendererCapabilities.supportsMixedMimeTypeAdaptation() & requiredAdaptiveSupport)
TrackGroup trackGroup = trackGroups.get(i); != 0;
int[] adaptiveTracks = getAdaptiveTracksOfGroup(trackGroup, formatSupport[i], TrackGroup largestAdaptiveGroup = null;
allowMixedMimeTypes, requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight, int[] largestAdaptiveGroupTracks = NO_TRACKS;
viewportWidth, viewportHeight, orientationMayChange); for (int i = 0; i < trackGroups.length; i++) {
if (adaptiveTracks.length > largestAdaptiveGroupTracks.length) { TrackGroup trackGroup = trackGroups.get(i);
largestAdaptiveGroup = trackGroup; int[] adaptiveTracks = getAdaptiveTracksOfGroup(trackGroup, formatSupport[i],
largestAdaptiveGroupTracks = adaptiveTracks; 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. // No adaptive tracks selection could be made, so we select the first supported video track.
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
TrackGroup trackGroup = trackGroups.get(groupIndex); TrackGroup trackGroup = trackGroups.get(groupIndex);
...@@ -261,10 +287,15 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -261,10 +287,15 @@ public class DefaultTrackSelector extends MappingTrackSelector {
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupportedVideoTrack(trackFormatSupport[trackIndex], trackGroup.getFormat(trackIndex), if (isSupportedVideoTrack(trackFormatSupport[trackIndex], trackGroup.getFormat(trackIndex),
maxVideoWidth, maxVideoHeight)) { maxVideoWidth, maxVideoHeight)) {
return new TrackSelection(trackGroup, trackIndex); return new FixedTrackSelection(trackGroup, trackIndex);
} }
} }
} }
if (exceedVideoConstraintsIfNecessary) {
return selectSmallestSupportedVideoTrack(trackGroups, formatSupport);
}
return null; return null;
} }
...@@ -350,8 +381,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -350,8 +381,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
} }
} }
} }
return trackGroupSelection != null return trackGroupSelection == null ? null
? new TrackSelection(trackGroupSelection, trackIndexSelection) : null; : new FixedTrackSelection(trackGroupSelection, trackIndexSelection);
} }
private static boolean isSupportedVideoTrack(int formatSupport, Format format, int maxVideoWidth, private static boolean isSupportedVideoTrack(int formatSupport, Format format, int maxVideoWidth,
...@@ -371,7 +402,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -371,7 +402,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex]) if (isSupported(trackFormatSupport[trackIndex])
&& formatHasLanguage(trackGroup.getFormat(trackIndex), preferredLanguage)) { && formatHasLanguage(trackGroup.getFormat(trackIndex), preferredLanguage)) {
return new TrackSelection(trackGroup, trackIndex); return new FixedTrackSelection(trackGroup, trackIndex);
} }
} }
} }
...@@ -398,12 +429,13 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -398,12 +429,13 @@ public class DefaultTrackSelector extends MappingTrackSelector {
firstForcedTrack = trackIndex; firstForcedTrack = trackIndex;
} }
if (formatHasLanguage(trackGroup.getFormat(trackIndex), preferredLanguage)) { 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. // General track selection methods.
...@@ -415,7 +447,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -415,7 +447,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
int[] trackFormatSupport = formatSupport[groupIndex]; int[] trackFormatSupport = formatSupport[groupIndex];
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex])) { if (isSupported(trackFormatSupport[trackIndex])) {
return new TrackSelection(trackGroup, trackIndex); return new FixedTrackSelection(trackGroup, trackIndex);
} }
} }
} }
...@@ -492,7 +524,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -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 // 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. // TVs that can show 4k resolution in a SurfaceView, so check for supported devices here.
if (Util.SDK_INT < 25) { 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")) { && context.getPackageManager().hasSystemFeature("com.sony.dtv.hardware.panel.qfhd")) {
return new Point(3840, 2160); return new Point(3840, 2160);
} else if ("NVIDIA".equals(Util.MANUFACTURER) && Util.MODEL != null } 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 @@ ...@@ -16,54 +16,48 @@
package com.google.android.exoplayer2.trackselection; package com.google.android.exoplayer2.trackselection;
import com.google.android.exoplayer2.Format; 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.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 * A track selection consisting of a static subset of selected tracks belonging to a
* it. The selected tracks are exposed in order of decreasing bandwidth. * {@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; interface Factory {
/**
* The number of selected tracks within the {@link TrackGroup}. Always greater than zero. /**
*/ * Creates a new selection.
public final int 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.
* @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. * Returns the number of tracks in the selection.
* @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) { int length();
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 format of the track at a given index in the selection. * Returns the format of the track at a given index in the selection.
...@@ -71,16 +65,15 @@ public final class TrackSelection { ...@@ -71,16 +65,15 @@ public final class TrackSelection {
* @param index The index in the selection. * @param index The index in the selection.
* @return The format of the selected track. * @return The format of the selected track.
*/ */
public Format getFormat(int index) { Format getFormat(int index);
return formats[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() { int getIndexInTrackGroup(int index);
return formats.clone();
}
/** /**
* Returns the index in the selection of the track with the specified format. * Returns the index in the selection of the track with the specified format.
...@@ -89,66 +82,82 @@ public final class TrackSelection { ...@@ -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 * @return The index in the selection, or -1 if the track with the specified format is not part of
* the selection. * the selection.
*/ */
public int indexOf(Format format) { 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. * Returns the index in the selection of the track with the specified index in the track group.
* *
* @param index The index in the selection. * @param indexInTrackGroup The index in the track group.
* @return The index of the selected track. * @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) { int indexOf(int indexInTrackGroup);
return tracks[index];
} // 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() { Format getSelectedFormat();
return tracks.clone();
}
/** /**
* 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. * @param bufferedDurationUs The duration of media currently buffered in microseconds.
* @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) { void updateSelectedTrack(long bufferedDurationUs);
for (int i = 0; i < length; i++) {
if (tracks[i] == trackIndex) {
return i;
}
}
return -1;
}
@Override /**
public int hashCode() { * May be called periodically by sources that load media in discrete {@link MediaChunk}s and
if (hashCode == 0) { * support discarding of buffered chunks in order to re-buffer using a different selected track.
hashCode = 31 * System.identityHashCode(group) + Arrays.hashCode(tracks); * Returns the number of chunks that should be retained in the queue.
} * <p>
return hashCode; * 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) { * Attempts to blacklist the track at the specified index in the selection, making it ineligible
if (this == obj) { * for selection by calls to {@link #updateSelectedTrack(long)} for the specified period of time.
return true; * 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
if (obj == null || getClass() != obj.getClass()) { * {@link #updateSelectedTrack(long)}.
return false; *
} * @param index The index of the track in the selection.
TrackSelection other = (TrackSelection) obj; * @param blacklistDurationMs The duration of time for which the track should be blacklisted, in
return group == other.group && Arrays.equals(tracks, other.tracks); * milliseconds.
} * @return Whether blacklisting was successful.
*/
boolean blacklist(int index, long blacklistDurationMs);
} }
...@@ -34,17 +34,18 @@ import com.google.android.exoplayer2.playbacktests.util.MetricsLogger; ...@@ -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.MediaSource;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; 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.DashMediaSource;
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; 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.MappingTrackSelector;
import com.google.android.exoplayer2.trackselection.RandomTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection; 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.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource; 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.Assertions;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
...@@ -680,15 +681,16 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit ...@@ -680,15 +681,16 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
this.isWidevineEncrypted = isWidevineEncrypted; this.isWidevineEncrypted = isWidevineEncrypted;
this.videoMimeType = videoMimeType; this.videoMimeType = videoMimeType;
this.isCddLimitedRetry = isCddLimitedRetry; this.isCddLimitedRetry = isCddLimitedRetry;
trackSelector = new DashTestTrackSelector(new String[] {audioFormat}, trackSelector = new DashTestTrackSelector(audioFormat, videoFormats,
videoFormats, canIncludeAdditionalVideoFormats); canIncludeAdditionalVideoFormats);
if (actionSchedule != null) { if (actionSchedule != null) {
setSchedule(actionSchedule); setSchedule(actionSchedule);
} }
} }
@Override @Override
protected MappingTrackSelector buildTrackSelector(HostActivity host) { protected MappingTrackSelector buildTrackSelector(HostActivity host,
BandwidthMeter bandwidthMeter) {
return trackSelector; return trackSelector;
} }
...@@ -731,19 +733,17 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit ...@@ -731,19 +733,17 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
} }
@Override @Override
public MediaSource buildSource(HostActivity host, String userAgent) { public MediaSource buildSource(HostActivity host, String userAgent,
TransferListener mediaTransferListener) {
DataSource.Factory manifestDataSourceFactory = new DefaultDataSourceFactory(host, userAgent); DataSource.Factory manifestDataSourceFactory = new DefaultDataSourceFactory(host, userAgent);
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
DataSource.Factory mediaDataSourceFactory = new DefaultDataSourceFactory(host, userAgent, DataSource.Factory mediaDataSourceFactory = new DefaultDataSourceFactory(host, userAgent,
bandwidthMeter); mediaTransferListener);
FormatEvaluator.Factory formatEvaluatorFactory = new AdaptiveEvaluator.Factory(
bandwidthMeter);
String manifestUrl = manifestPath; String manifestUrl = manifestPath;
manifestUrl += isWidevineEncrypted ? (needsSecureVideoDecoder ? WIDEVINE_L1_SUFFIX manifestUrl += isWidevineEncrypted ? (needsSecureVideoDecoder ? WIDEVINE_L1_SUFFIX
: WIDEVINE_L3_SUFFIX) : ""; : WIDEVINE_L3_SUFFIX) : "";
Uri manifestUri = Uri.parse(manifestUrl); Uri manifestUri = Uri.parse(manifestUrl);
DefaultDashChunkSource.Factory chunkSourceFactory = new DefaultDashChunkSource.Factory( DefaultDashChunkSource.Factory chunkSourceFactory = new DefaultDashChunkSource.Factory(
mediaDataSourceFactory, formatEvaluatorFactory); mediaDataSourceFactory);
return new DashMediaSource(manifestUri, manifestDataSourceFactory, chunkSourceFactory, return new DashMediaSource(manifestUri, manifestDataSourceFactory, chunkSourceFactory,
MIN_LOADABLE_RETRY_COUNT, null, null); MIN_LOADABLE_RETRY_COUNT, null, null);
} }
...@@ -801,16 +801,16 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit ...@@ -801,16 +801,16 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
private static final class DashTestTrackSelector extends MappingTrackSelector { private static final class DashTestTrackSelector extends MappingTrackSelector {
private final String[] audioFormatIds; private final String audioFormatId;
private final String[] videoFormatIds; private final String[] videoFormatIds;
private final boolean canIncludeAdditionalVideoFormats; private final boolean canIncludeAdditionalVideoFormats;
public boolean includedAdditionalVideoFormats; public boolean includedAdditionalVideoFormats;
private DashTestTrackSelector(String[] audioFormatIds, String[] videoFormatIds, private DashTestTrackSelector(String audioFormatId, String[] videoFormatIds,
boolean canIncludeAdditionalVideoFormats) { boolean canIncludeAdditionalVideoFormats) {
super(null); super(null);
this.audioFormatIds = audioFormatIds; this.audioFormatId = audioFormatId;
this.videoFormatIds = videoFormatIds; this.videoFormatIds = videoFormatIds;
this.canIncludeAdditionalVideoFormats = canIncludeAdditionalVideoFormats; this.canIncludeAdditionalVideoFormats = canIncludeAdditionalVideoFormats;
} }
...@@ -826,17 +826,17 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit ...@@ -826,17 +826,17 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
Assertions.checkState(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].length == 1); Assertions.checkState(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].length == 1);
Assertions.checkState(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].length == 1); Assertions.checkState(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].length == 1);
TrackSelection[] selections = new TrackSelection[rendererCapabilities.length]; TrackSelection[] selections = new TrackSelection[rendererCapabilities.length];
selections[VIDEO_RENDERER_INDEX] = new TrackSelection( selections[VIDEO_RENDERER_INDEX] = new RandomTrackSelection(
rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0), rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0),
getTrackIndices(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0), getTrackIndices(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0),
rendererFormatSupports[VIDEO_RENDERER_INDEX][0], videoFormatIds, rendererFormatSupports[VIDEO_RENDERER_INDEX][0], videoFormatIds,
canIncludeAdditionalVideoFormats)); canIncludeAdditionalVideoFormats),
selections[AUDIO_RENDERER_INDEX] = new TrackSelection( 0 /* seed */);
selections[AUDIO_RENDERER_INDEX] = new FixedTrackSelection(
rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0), rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0),
getTrackIndices(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0), getTrackIndex(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0), audioFormatId));
rendererFormatSupports[AUDIO_RENDERER_INDEX][0], audioFormatIds, false));
includedAdditionalVideoFormats = includedAdditionalVideoFormats =
selections[VIDEO_RENDERER_INDEX].length > videoFormatIds.length; selections[VIDEO_RENDERER_INDEX].length() > videoFormatIds.length;
return selections; return selections;
} }
...@@ -844,18 +844,9 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit ...@@ -844,18 +844,9 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
String[] formatIds, boolean canIncludeAdditionalFormats) { String[] formatIds, boolean canIncludeAdditionalFormats) {
List<Integer> trackIndices = new ArrayList<>(); List<Integer> trackIndices = new ArrayList<>();
// Always select explicitly listed representations, failing if they're missing. // Always select explicitly listed representations.
for (String formatId : formatIds) { for (String formatId : formatIds) {
boolean foundIndex = false; trackIndices.add(getTrackIndex(trackGroup, formatId));
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.");
}
} }
// Select additional video representations, if supported by the device. // Select additional video representations, if supported by the device.
...@@ -873,6 +864,15 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit ...@@ -873,6 +864,15 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
return trackIndicesArray; 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) { private static boolean isFormatHandled(int formatSupport) {
return (formatSupport & RendererCapabilities.FORMAT_SUPPORT_MASK) return (formatSupport & RendererCapabilities.FORMAT_SUPPORT_MASK)
== RendererCapabilities.FORMAT_HANDLED; == RendererCapabilities.FORMAT_HANDLED;
......
...@@ -27,15 +27,18 @@ import com.google.android.exoplayer2.drm.DrmSessionManager; ...@@ -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.playbacktests.util.HostActivity.HostedTest;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.Timeline; 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.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector; 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 com.google.android.exoplayer2.util.Util;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import android.view.Surface; import android.view.Surface;
import junit.framework.Assert; import junit.framework.Assert;
...@@ -123,11 +126,12 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen ...@@ -123,11 +126,12 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
@Override @Override
public final void onStart(HostActivity host, Surface surface) { public final void onStart(HostActivity host, Surface surface) {
// Build the player. // Build the player.
trackSelector = buildTrackSelector(host); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
trackSelector = buildTrackSelector(host, bandwidthMeter);
String userAgent = "ExoPlayerPlaybackTests"; String userAgent = "ExoPlayerPlaybackTests";
DrmSessionManager drmSessionManager = buildDrmSessionManager(userAgent); DrmSessionManager drmSessionManager = buildDrmSessionManager(userAgent);
player = buildExoPlayer(host, surface, trackSelector, drmSessionManager); 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.addListener(this);
player.setDebugListener(this); player.setDebugListener(this);
player.setPlayWhenReady(true); player.setPlayWhenReady(true);
...@@ -288,8 +292,9 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen ...@@ -288,8 +292,9 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
protected MappingTrackSelector buildTrackSelector(HostActivity host) { protected MappingTrackSelector buildTrackSelector(HostActivity host,
return new DefaultTrackSelector(null); BandwidthMeter bandwidthMeter) {
return new DefaultTrackSelector(null, new AdaptiveVideoTrackSelection.Factory(bandwidthMeter));
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
...@@ -302,7 +307,8 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen ...@@ -302,7 +307,8 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
protected abstract MediaSource buildSource(HostActivity host, String userAgent); protected abstract MediaSource buildSource(HostActivity host, String userAgent,
TransferListener mediaTransferListener);
@SuppressWarnings("unused") @SuppressWarnings("unused")
protected void onPlayerErrorInternal(ExoPlaybackException error) { 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