Commit 692d756e by olly Committed by Oliver Woodman

Simplification: Move common logic to TrackSelection

- TrackSelection now exposes the selected formats, ordered
  by decreasing bandwidth. This removes the need for DASH,
  SS and HLS to do the sorting individually.
- The change also removes the need to reconstruct TrackSelection
  instances with a different group index in various places
  (e.g. MergingMediaPeriod).
- This is also a step toward potentially packaging the
  FormatEvaluator inside of the TrackSelection (TBD).

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=128159064
parent 6f1b24f1
Showing with 239 additions and 342 deletions
...@@ -109,7 +109,7 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb ...@@ -109,7 +109,7 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb
trackGroup.length, trackInfo.getAdaptiveSupport(rendererIndex, groupIndex, false)); trackGroup.length, trackInfo.getAdaptiveSupport(rendererIndex, groupIndex, false));
Log.d(TAG, " Group:" + groupIndex + ", adaptive_supported=" + adaptiveSupport + " ["); Log.d(TAG, " Group:" + groupIndex + ", adaptive_supported=" + adaptiveSupport + " [");
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
String status = getTrackStatusString(trackSelection, groupIndex, trackIndex); String status = getTrackStatusString(trackSelection, trackGroup, trackIndex);
String formatSupport = getFormatSupportString( String formatSupport = getFormatSupportString(
trackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex)); trackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex));
Log.d(TAG, " " + status + " Track:" + trackIndex + ", " Log.d(TAG, " " + status + " Track:" + trackIndex + ", "
...@@ -353,9 +353,9 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb ...@@ -353,9 +353,9 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb
return builder.toString(); return builder.toString();
} }
private static String getTrackStatusString(TrackSelection selection, int groupIndex, private static String getTrackStatusString(TrackSelection selection, TrackGroup group,
int trackIndex) { int trackIndex) {
boolean groupEnabled = selection != null && selection.group == groupIndex; boolean groupEnabled = selection != null && selection.group == group;
if (groupEnabled) { if (groupEnabled) {
for (int i = 0; i < selection.length; i++) { for (int i = 0; i < selection.length; i++) {
if (selection.getTrack(i) == trackIndex) { if (selection.getTrack(i) == trackIndex) {
......
...@@ -135,7 +135,7 @@ import java.util.Locale; ...@@ -135,7 +135,7 @@ import java.util.Locale;
if (trackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex) if (trackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex)
== RendererCapabilities.FORMAT_HANDLED) { == RendererCapabilities.FORMAT_HANDLED) {
haveSupportedTracks = true; haveSupportedTracks = true;
trackView.setTag(Pair.create(groupIndex, trackIndex)); trackView.setTag(Pair.create(group, trackIndex));
trackView.setOnClickListener(this); trackView.setOnClickListener(this);
} else { } else {
trackView.setEnabled(false); trackView.setEnabled(false);
...@@ -160,7 +160,7 @@ import java.util.Locale; ...@@ -160,7 +160,7 @@ import java.util.Locale;
for (int i = 0; i < trackViews.length; i++) { for (int i = 0; i < trackViews.length; 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.group == i && override.containsTrack(j)); override != null && override.group == trackGroups.get(i) && override.indexOf(j) != -1);
} }
} }
} }
...@@ -194,11 +194,11 @@ import java.util.Locale; ...@@ -194,11 +194,11 @@ import java.util.Locale;
} else { } else {
isDisabled = false; isDisabled = false;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Pair<Integer, Integer> tag = (Pair<Integer, Integer>) view.getTag(); Pair<TrackGroup, Integer> tag = (Pair<TrackGroup, Integer>) view.getTag();
int groupIndex = tag.first; TrackGroup group = tag.first;
int trackIndex = tag.second; int trackIndex = tag.second;
if (!trackGroupsAdaptive[groupIndex] || override == null) { if (!trackGroupsAdaptive[trackGroups.indexOf(group)] || override == null) {
override = new TrackSelection(groupIndex, trackIndex); override = new TrackSelection(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();
...@@ -216,13 +216,13 @@ import java.util.Locale; ...@@ -216,13 +216,13 @@ import java.util.Locale;
tracks[trackCount++] = override.getTrack(i); tracks[trackCount++] = override.getTrack(i);
} }
} }
override = new TrackSelection(groupIndex, tracks); 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); int[] tracks = Arrays.copyOf(override.getTracks(), override.length + 1);
tracks[tracks.length - 1] = trackIndex; tracks[tracks.length - 1] = trackIndex;
override = new TrackSelection(groupIndex, tracks); override = new TrackSelection(group, tracks);
} }
} }
} }
......
...@@ -19,7 +19,6 @@ import com.google.android.exoplayer2.ExoPlayer.ExoPlayerMessage; ...@@ -19,7 +19,6 @@ import com.google.android.exoplayer2.ExoPlayer.ExoPlayerMessage;
import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector;
...@@ -705,7 +704,6 @@ import java.util.ArrayList; ...@@ -705,7 +704,6 @@ import java.util.ArrayList;
TrackSelectionArray oldTrackSelections = readingPeriod.trackSelections; TrackSelectionArray oldTrackSelections = readingPeriod.trackSelections;
readingPeriod = readingPeriod.nextPeriod; readingPeriod = readingPeriod.nextPeriod;
TrackSelectionArray newTrackSelections = readingPeriod.trackSelections; TrackSelectionArray newTrackSelections = readingPeriod.trackSelections;
TrackGroupArray groups = readingPeriod.mediaPeriod.getTrackGroups();
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i]; Renderer renderer = renderers[i];
TrackSelection oldSelection = oldTrackSelections.get(i); TrackSelection oldSelection = oldTrackSelections.get(i);
...@@ -716,7 +714,7 @@ import java.util.ArrayList; ...@@ -716,7 +714,7 @@ import java.util.ArrayList;
// 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] = groups.get(newSelection.group).getFormat(newSelection.getTrack(j)); formats[j] = newSelection.group.getFormat(newSelection.getTrack(j));
} }
renderer.replaceStream(formats, readingPeriod.sampleStreams[i], renderer.replaceStream(formats, readingPeriod.sampleStreams[i],
readingPeriod.offsetUs); readingPeriod.offsetUs);
...@@ -956,7 +954,6 @@ import java.util.ArrayList; ...@@ -956,7 +954,6 @@ import java.util.ArrayList;
throws ExoPlaybackException { throws ExoPlaybackException {
enabledRenderers = new Renderer[enabledRendererCount]; enabledRenderers = new Renderer[enabledRendererCount];
enabledRendererCount = 0; enabledRendererCount = 0;
TrackGroupArray trackGroups = playingPeriod.mediaPeriod.getTrackGroups();
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i]; Renderer renderer = renderers[i];
TrackSelection newSelection = playingPeriod.trackSelections.get(i); TrackSelection newSelection = playingPeriod.trackSelections.get(i);
...@@ -970,7 +967,7 @@ import java.util.ArrayList; ...@@ -970,7 +967,7 @@ import java.util.ArrayList;
// 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] = trackGroups.get(newSelection.group).getFormat(newSelection.getTrack(j)); formats[j] = newSelection.group.getFormat(newSelection.getTrack(j));
} }
// Enable the renderer. // Enable the renderer.
renderer.enable(formats, playingPeriod.sampleStreams[i], internalPositionUs, joining, renderer.enable(formats, playingPeriod.sampleStreams[i], internalPositionUs, joining,
......
...@@ -252,7 +252,7 @@ public final class ExtractorMediaSource implements MediaPeriod, MediaSource, ...@@ -252,7 +252,7 @@ public final class ExtractorMediaSource implements MediaPeriod, MediaSource,
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.getTrack(0) == 0);
int track = selection.group; int track = tracks.indexOf(selection.group);
Assertions.checkState(!trackEnabledStates[track]); Assertions.checkState(!trackEnabledStates[track]);
enabledTrackCount++; enabledTrackCount++;
trackEnabledStates[track] = true; trackEnabledStates[track] = true;
......
...@@ -19,8 +19,6 @@ import com.google.android.exoplayer2.C; ...@@ -19,8 +19,6 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import android.util.Pair;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
...@@ -183,9 +181,10 @@ public final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callba ...@@ -183,9 +181,10 @@ public final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callba
TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount]; TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount];
int trackGroupIndex = 0; int trackGroupIndex = 0;
for (MediaPeriod period : periods) { for (MediaPeriod period : periods) {
int periodTrackGroupCount = period.getTrackGroups().length; TrackGroupArray periodTrackGroups = period.getTrackGroups();
int periodTrackGroupCount = periodTrackGroups.length;
for (int j = 0; j < periodTrackGroupCount; j++) { for (int j = 0; j < periodTrackGroupCount; j++) {
trackGroupArray[trackGroupIndex++] = period.getTrackGroups().get(j); trackGroupArray[trackGroupIndex++] = periodTrackGroups.get(j);
} }
} }
trackGroups = new TrackGroupArray(trackGroupArray); trackGroups = new TrackGroupArray(trackGroupArray);
...@@ -218,12 +217,12 @@ public final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callba ...@@ -218,12 +217,12 @@ public final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callba
// Get the subset of the new selections for the period. // Get the subset of the new selections for the period.
ArrayList<TrackSelection> newSelections = new ArrayList<>(); ArrayList<TrackSelection> newSelections = new ArrayList<>();
int[] newSelectionOriginalIndices = new int[allNewSelections.size()]; int[] newSelectionOriginalIndices = new int[allNewSelections.size()];
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);
Pair<MediaPeriod, Integer> periodAndGroup = getPeriodAndGroup(selection.group); if (periodTrackGroups.indexOf(selection.group) != -1) {
if (periodAndGroup.first == period) {
newSelectionOriginalIndices[newSelections.size()] = i; newSelectionOriginalIndices[newSelections.size()] = i;
newSelections.add(new TrackSelection(periodAndGroup.second, selection.getTracks())); newSelections.add(selection);
} }
} }
// Do nothing if nothing has changed, except during the first selection. // Do nothing if nothing has changed, except during the first selection.
...@@ -239,16 +238,4 @@ public final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callba ...@@ -239,16 +238,4 @@ public final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callba
return newSelections.size() - oldStreams.size(); return newSelections.size() - oldStreams.size();
} }
private Pair<MediaPeriod, Integer> getPeriodAndGroup(int group) {
int totalTrackGroupCount = 0;
for (MediaPeriod period : periods) {
int periodTrackGroupCount = period.getTrackGroups().length;
if (group < totalTrackGroupCount + periodTrackGroupCount) {
return Pair.create(period, group - totalTrackGroupCount);
}
totalTrackGroupCount += periodTrackGroupCount;
}
throw new IndexOutOfBoundsException();
}
} }
...@@ -72,6 +72,21 @@ public final class TrackGroup { ...@@ -72,6 +72,21 @@ public final class TrackGroup {
return formats[index]; return formats[index];
} }
/**
* Gets the index of the track with the given format in the group.
*
* @param format The format.
* @return The index of the track, or -1 if no such track exists.
*/
public int indexOf(Format format) {
for (int i = 0; i < formats.length; i++) {
if (format == formats[i]) {
return i;
}
}
return -1;
}
@Override @Override
public int hashCode() { public int hashCode() {
if (hashCode == 0) { if (hashCode == 0) {
......
...@@ -50,6 +50,21 @@ public final class TrackGroupArray { ...@@ -50,6 +50,21 @@ public final class TrackGroupArray {
return trackGroups[index]; return trackGroups[index];
} }
/**
* Gets the index of a group within the array.
*
* @param group The group.
* @return The index of the group, or -1 if no such group exists.
*/
public int indexOf(TrackGroup group) {
for (int i = 0; i < length; i++) {
if (trackGroups[i] == group) {
return i;
}
}
return -1;
}
@Override @Override
public int hashCode() { public int hashCode() {
if (hashCode == 0) { if (hashCode == 0) {
......
...@@ -15,9 +15,9 @@ ...@@ -15,9 +15,9 @@
*/ */
package com.google.android.exoplayer2.source.dash; package com.google.android.exoplayer2.source.dash;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.ChunkSource; import com.google.android.exoplayer2.source.chunk.ChunkSource;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.Loader;
/** /**
...@@ -28,7 +28,7 @@ public interface DashChunkSource extends ChunkSource { ...@@ -28,7 +28,7 @@ public interface DashChunkSource extends ChunkSource {
interface Factory { interface Factory {
DashChunkSource createDashChunkSource(Loader manifestLoader, DashManifest manifest, DashChunkSource createDashChunkSource(Loader manifestLoader, DashManifest manifest,
int periodIndex, int adaptationSetIndex, TrackGroup trackGroup, int[] tracks, int periodIndex, int adaptationSetIndex, TrackSelection trackSelection,
long elapsedRealtimeOffsetMs); long elapsedRealtimeOffsetMs);
} }
......
...@@ -236,12 +236,10 @@ import java.util.List; ...@@ -236,12 +236,10 @@ import java.util.List;
private ChunkSampleStream<DashChunkSource> buildSampleStream(TrackSelection selection, private ChunkSampleStream<DashChunkSource> buildSampleStream(TrackSelection selection,
long positionUs) { long positionUs) {
int[] selectedTracks = selection.getTracks(); int adaptationSetIndex = trackGroupAdaptationSetIndices[trackGroups.indexOf(selection.group)];
int adaptationSetIndex = trackGroupAdaptationSetIndices[selection.group];
AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex); AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex);
DashChunkSource chunkSource = chunkSourceFactory.createDashChunkSource(loader, manifest, index, DashChunkSource chunkSource = chunkSourceFactory.createDashChunkSource(loader, manifest, index,
adaptationSetIndex, trackGroups.get(selection.group), selectedTracks, adaptationSetIndex, selection, elapsedRealtimeOffset);
elapsedRealtimeOffset);
return new ChunkSampleStream<>(adaptationSet.type, chunkSource, this, allocator, positionUs, return new ChunkSampleStream<>(adaptationSet.type, chunkSource, this, allocator, positionUs,
minLoadableRetryCount, eventDispatcher); minLoadableRetryCount, eventDispatcher);
} }
......
...@@ -17,13 +17,11 @@ package com.google.android.exoplayer2.source.dash; ...@@ -17,13 +17,11 @@ package com.google.android.exoplayer2.source.dash;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Format.DecreasingBandwidthComparator;
import com.google.android.exoplayer2.extractor.ChunkIndex; import com.google.android.exoplayer2.extractor.ChunkIndex;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.Chunk; 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;
...@@ -36,6 +34,7 @@ import com.google.android.exoplayer2.source.chunk.SingleSampleMediaChunk; ...@@ -36,6 +34,7 @@ import com.google.android.exoplayer2.source.chunk.SingleSampleMediaChunk;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
import com.google.android.exoplayer2.source.dash.manifest.RangedUri; import com.google.android.exoplayer2.source.dash.manifest.RangedUri;
import com.google.android.exoplayer2.source.dash.manifest.Representation; import com.google.android.exoplayer2.source.dash.manifest.Representation;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException;
...@@ -46,7 +45,6 @@ import com.google.android.exoplayer2.util.Util; ...@@ -46,7 +45,6 @@ import com.google.android.exoplayer2.util.Util;
import android.os.SystemClock; import android.os.SystemClock;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.List; import java.util.List;
/** /**
...@@ -67,22 +65,21 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -67,22 +65,21 @@ public class DefaultDashChunkSource implements DashChunkSource {
@Override @Override
public DashChunkSource createDashChunkSource(Loader manifestLoader, DashManifest manifest, public DashChunkSource createDashChunkSource(Loader manifestLoader, DashManifest manifest,
int periodIndex, int adaptationSetIndex, TrackGroup trackGroup, int[] tracks, int periodIndex, int adaptationSetIndex, TrackSelection trackSelection,
long elapsedRealtimeOffsetMs) { long elapsedRealtimeOffsetMs) {
FormatEvaluator adaptiveEvaluator = tracks.length > 1 FormatEvaluator adaptiveEvaluator = trackSelection.length > 1
? formatEvaluatorFactory.createFormatEvaluator() : null; ? formatEvaluatorFactory.createFormatEvaluator() : null;
DataSource dataSource = dataSourceFactory.createDataSource(); DataSource dataSource = dataSourceFactory.createDataSource();
return new DefaultDashChunkSource(manifestLoader, manifest, periodIndex, adaptationSetIndex, return new DefaultDashChunkSource(manifestLoader, manifest, periodIndex, adaptationSetIndex,
trackGroup, tracks, dataSource, adaptiveEvaluator, elapsedRealtimeOffsetMs); trackSelection, dataSource, adaptiveEvaluator, elapsedRealtimeOffsetMs);
} }
} }
private final Loader manifestLoader; private final Loader manifestLoader;
private final int adaptationSetIndex; private final int adaptationSetIndex;
private final TrackGroup trackGroup; private final TrackSelection trackSelection;
private final RepresentationHolder[] representationHolders; private final RepresentationHolder[] representationHolders;
private final Format[] enabledFormats;
private final boolean[] adaptiveFormatBlacklistFlags; private final boolean[] adaptiveFormatBlacklistFlags;
private final DataSource dataSource; private final DataSource dataSource;
private final FormatEvaluator adaptiveFormatEvaluator; private final FormatEvaluator adaptiveFormatEvaluator;
...@@ -100,8 +97,7 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -100,8 +97,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
* @param manifest The initial manifest. * @param manifest The initial manifest.
* @param periodIndex The index of the period in the manifest. * @param periodIndex The index of the period in the manifest.
* @param adaptationSetIndex The index of the adaptation set in the period. * @param adaptationSetIndex The index of the adaptation set in the period.
* @param trackGroup The track group corresponding to the adaptation set. * @param trackSelection The track selection.
* @param tracks The indices of the selected tracks within the adaptation set.
* @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 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
...@@ -109,12 +105,12 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -109,12 +105,12 @@ public class DefaultDashChunkSource implements DashChunkSource {
* 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(Loader manifestLoader, DashManifest manifest, int periodIndex, public DefaultDashChunkSource(Loader manifestLoader, DashManifest manifest, int periodIndex,
int adaptationSetIndex, TrackGroup trackGroup, int[] tracks, DataSource dataSource, int adaptationSetIndex, TrackSelection trackSelection, DataSource dataSource,
FormatEvaluator adaptiveFormatEvaluator, long elapsedRealtimeOffsetMs) { FormatEvaluator adaptiveFormatEvaluator, long elapsedRealtimeOffsetMs) {
this.manifestLoader = manifestLoader; this.manifestLoader = manifestLoader;
this.manifest = manifest; this.manifest = manifest;
this.adaptationSetIndex = adaptationSetIndex; this.adaptationSetIndex = adaptationSetIndex;
this.trackGroup = trackGroup; this.trackSelection = trackSelection;
this.dataSource = dataSource; this.dataSource = dataSource;
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator; this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
this.elapsedRealtimeOffsetUs = elapsedRealtimeOffsetMs * 1000; this.elapsedRealtimeOffsetUs = elapsedRealtimeOffsetMs * 1000;
...@@ -122,20 +118,14 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -122,20 +118,14 @@ public class DefaultDashChunkSource implements DashChunkSource {
long periodDurationUs = getPeriodDurationUs(periodIndex); long periodDurationUs = getPeriodDurationUs(periodIndex);
List<Representation> representations = getRepresentations(periodIndex); List<Representation> representations = getRepresentations(periodIndex);
representationHolders = new RepresentationHolder[representations.size()]; representationHolders = new RepresentationHolder[trackSelection.length];
for (int i = 0; i < trackSelection.length; i++) {
for (int i = 0; i < representations.size(); i++) { Representation representation = representations.get(trackSelection.getTrack(i));
Representation representation = representations.get(i);
representationHolders[i] = new RepresentationHolder(periodDurationUs, representation); representationHolders[i] = new RepresentationHolder(periodDurationUs, representation);
} }
enabledFormats = new Format[tracks.length];
for (int i = 0; i < tracks.length; i++) {
enabledFormats[i] = trackGroup.getFormat(tracks[i]);
}
Arrays.sort(enabledFormats, new DecreasingBandwidthComparator());
if (adaptiveFormatEvaluator != null) { if (adaptiveFormatEvaluator != null) {
adaptiveFormatEvaluator.enable(enabledFormats); adaptiveFormatEvaluator.enable(trackSelection.getFormats());
adaptiveFormatBlacklistFlags = new boolean[tracks.length]; adaptiveFormatBlacklistFlags = new boolean[trackSelection.length];
} else { } else {
adaptiveFormatBlacklistFlags = null; adaptiveFormatBlacklistFlags = null;
} }
...@@ -147,8 +137,8 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -147,8 +137,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
manifest = newManifest; manifest = newManifest;
long periodDurationUs = getPeriodDurationUs(periodIndex); long periodDurationUs = getPeriodDurationUs(periodIndex);
List<Representation> representations = getRepresentations(periodIndex); List<Representation> representations = getRepresentations(periodIndex);
for (int i = 0; i < representationHolders.length; i++) { for (int i = 0; i < trackSelection.length; i++) {
Representation representation = representations.get(i); Representation representation = representations.get(trackSelection.getTrack(i));
representationHolders[i].updateRepresentation(periodDurationUs, representation); representationHolders[i].updateRepresentation(periodDurationUs, representation);
} }
} catch (BehindLiveWindowException e) { } catch (BehindLiveWindowException e) {
...@@ -167,7 +157,7 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -167,7 +157,7 @@ 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 || enabledFormats.length < 2) { if (fatalError != null || trackSelection.length < 2) {
return queue.size(); return queue.size();
} }
return adaptiveFormatEvaluator.evaluateQueueSize(playbackPositionUs, queue, return adaptiveFormatEvaluator.evaluateQueueSize(playbackPositionUs, queue,
...@@ -181,12 +171,12 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -181,12 +171,12 @@ public class DefaultDashChunkSource implements DashChunkSource {
} }
if (evaluation.format == null || !lastChunkWasInitialization) { if (evaluation.format == null || !lastChunkWasInitialization) {
if (enabledFormats.length > 1) { if (trackSelection.length > 1) {
long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0; long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
adaptiveFormatEvaluator.evaluateFormat(bufferedDurationUs, adaptiveFormatBlacklistFlags, adaptiveFormatEvaluator.evaluateFormat(bufferedDurationUs, adaptiveFormatBlacklistFlags,
evaluation); evaluation);
} else { } else {
evaluation.format = enabledFormats[0]; evaluation.format = trackSelection.getFormat(0);
evaluation.trigger = FormatEvaluator.TRIGGER_UNKNOWN; evaluation.trigger = FormatEvaluator.TRIGGER_UNKNOWN;
evaluation.data = null; evaluation.data = null;
} }
...@@ -198,7 +188,7 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -198,7 +188,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
} }
RepresentationHolder representationHolder = RepresentationHolder representationHolder =
representationHolders[getTrackIndex(selectedFormat)]; representationHolders[trackSelection.indexOf(selectedFormat)];
Representation selectedRepresentation = representationHolder.representation; Representation selectedRepresentation = representationHolder.representation;
DashSegmentIndex segmentIndex = representationHolder.segmentIndex; DashSegmentIndex segmentIndex = representationHolder.segmentIndex;
...@@ -270,7 +260,7 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -270,7 +260,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
if (chunk instanceof InitializationChunk) { if (chunk instanceof InitializationChunk) {
InitializationChunk initializationChunk = (InitializationChunk) chunk; InitializationChunk initializationChunk = (InitializationChunk) chunk;
RepresentationHolder representationHolder = RepresentationHolder representationHolder =
representationHolders[getTrackIndex(initializationChunk.format)]; representationHolders[trackSelection.indexOf(initializationChunk.format)];
Format sampleFormat = initializationChunk.getSampleFormat(); Format sampleFormat = initializationChunk.getSampleFormat();
if (sampleFormat != null) { if (sampleFormat != null) {
representationHolder.setSampleFormat(sampleFormat); representationHolder.setSampleFormat(sampleFormat);
...@@ -295,7 +285,7 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -295,7 +285,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
&& e instanceof InvalidResponseCodeException && e instanceof InvalidResponseCodeException
&& ((InvalidResponseCodeException) e).responseCode == 404) { && ((InvalidResponseCodeException) e).responseCode == 404) {
RepresentationHolder representationHolder = RepresentationHolder representationHolder =
representationHolders[getTrackIndex(chunk.format)]; representationHolders[trackSelection.indexOf(chunk.format)];
int lastAvailableSegmentNum = representationHolder.getLastSegmentNum(); int lastAvailableSegmentNum = representationHolder.getLastSegmentNum();
if (((MediaChunk) chunk).chunkIndex >= lastAvailableSegmentNum) { if (((MediaChunk) chunk).chunkIndex >= lastAvailableSegmentNum) {
missingLastSegment = true; missingLastSegment = true;
...@@ -368,16 +358,6 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -368,16 +358,6 @@ public class DefaultDashChunkSource implements DashChunkSource {
} }
} }
private int getTrackIndex(Format format) {
for (int i = 0; i < trackGroup.length; i++) {
if (trackGroup.getFormat(i) == format) {
return i;
}
}
// Should never happen.
throw new IllegalStateException("Invalid format: " + format);
}
private long getPeriodDurationUs(int periodIndex) { private long getPeriodDurationUs(int periodIndex) {
long durationMs = manifest.getPeriodDuration(periodIndex); long durationMs = manifest.getPeriodDuration(periodIndex);
if (durationMs == -1) { if (durationMs == -1) {
......
...@@ -23,6 +23,7 @@ import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; ...@@ -23,6 +23,7 @@ import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer2.extractor.ts.PtsTimestampAdjuster; import com.google.android.exoplayer2.extractor.ts.PtsTimestampAdjuster;
import com.google.android.exoplayer2.extractor.ts.TsExtractor; import com.google.android.exoplayer2.extractor.ts.TsExtractor;
import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.Chunk; import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.chunk.ChunkHolder; import com.google.android.exoplayer2.source.chunk.ChunkHolder;
import com.google.android.exoplayer2.source.chunk.DataChunk; import com.google.android.exoplayer2.source.chunk.DataChunk;
...@@ -31,6 +32,7 @@ import com.google.android.exoplayer2.source.chunk.FormatEvaluator.Evaluation; ...@@ -31,6 +32,7 @@ import com.google.android.exoplayer2.source.chunk.FormatEvaluator.Evaluation;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser;
import com.google.android.exoplayer2.source.hls.playlist.Variant; import com.google.android.exoplayer2.source.hls.playlist.Variant;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException;
...@@ -47,7 +49,6 @@ import java.io.ByteArrayInputStream; ...@@ -47,7 +49,6 @@ import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator;
import java.util.Locale; import java.util.Locale;
/** /**
...@@ -78,6 +79,7 @@ public class HlsChunkSource { ...@@ -78,6 +79,7 @@ public class HlsChunkSource {
private final PtsTimestampAdjusterProvider timestampAdjusterProvider; private final PtsTimestampAdjusterProvider timestampAdjusterProvider;
private final Variant[] variants; private final Variant[] variants;
private final HlsMediaPlaylist[] variantPlaylists; private final HlsMediaPlaylist[] variantPlaylists;
private final TrackGroup trackGroup;
private final long[] variantLastPlaylistLoadTimesMs; private final long[] variantLastPlaylistLoadTimesMs;
private boolean seenFirstExternalTrackSelection; private boolean seenFirstExternalTrackSelection;
...@@ -92,7 +94,7 @@ public class HlsChunkSource { ...@@ -92,7 +94,7 @@ public class HlsChunkSource {
private byte[] encryptionIv; private byte[] encryptionIv;
// Properties of enabled variants. // Properties of enabled variants.
private Variant[] enabledVariants; private TrackSelection trackSelection;
private long[] enabledVariantBlacklistTimes; private long[] enabledVariantBlacklistTimes;
private boolean[] enabledVariantBlacklistFlags; private boolean[] enabledVariantBlacklistFlags;
...@@ -117,11 +119,15 @@ public class HlsChunkSource { ...@@ -117,11 +119,15 @@ public class HlsChunkSource {
evaluation = new Evaluation(); evaluation = new Evaluation();
variantPlaylists = new HlsMediaPlaylist[variants.length]; variantPlaylists = new HlsMediaPlaylist[variants.length];
variantLastPlaylistLoadTimesMs = new long[variants.length]; variantLastPlaylistLoadTimesMs = new long[variants.length];
Format[] variantFormats = new Format[variants.length];
int[] initialTrackSelection = new int[variants.length]; int[] initialTrackSelection = new int[variants.length];
for (int i = 0; i < variants.length; i++) { for (int i = 0; i < variants.length; i++) {
variantFormats[i] = variants[i].format;
initialTrackSelection[i] = i; initialTrackSelection[i] = i;
} }
selectTracksInternal(initialTrackSelection, false); trackGroup = new TrackGroup(adaptiveFormatEvaluator != null, variantFormats);
selectTracksInternal(new TrackSelection(trackGroup, initialTrackSelection), false);
} }
/** /**
...@@ -137,18 +143,7 @@ public class HlsChunkSource { ...@@ -137,18 +143,7 @@ public class HlsChunkSource {
} }
/** /**
* Returns whether this source supports adaptation between its tracks.
*
* @return Whether this source supports adaptation between its tracks.
*/
public boolean isAdaptive() {
return adaptiveFormatEvaluator != null;
}
/**
* Returns whether this is a live playback. * Returns whether this is a live playback.
* <p>
* This method should only be called after the source has been prepared.
* *
* @return True if this is a live playback. False otherwise. * @return True if this is a live playback. False otherwise.
*/ */
...@@ -158,8 +153,6 @@ public class HlsChunkSource { ...@@ -158,8 +153,6 @@ public class HlsChunkSource {
/** /**
* Returns the duration of the source, or {@link C#UNSET_TIME_US} if the duration is unknown. * Returns the duration of the source, or {@link C#UNSET_TIME_US} if the duration is unknown.
* <p>
* This method should only be called after the source has been prepared.
* *
* @return The number of tracks. * @return The number of tracks.
*/ */
...@@ -168,43 +161,25 @@ public class HlsChunkSource { ...@@ -168,43 +161,25 @@ public class HlsChunkSource {
} }
/** /**
* Returns the number of tracks exposed by the source. * Returns the track group exposed by the source.
* <p>
* This method should only be called after the source has been prepared.
*
* @return The number of tracks.
*/
public int getTrackCount() {
return variants.length;
}
/**
* Returns the format of the track at the specified index.
* <p>
* This method should only be called after the source has been prepared.
* *
* @param index The track index. * @return The track group.
* @return The format of the track.
*/ */
public Format getTrackFormat(int index) { public TrackGroup getTrackGroup() {
return variants[index].format; return trackGroup;
} }
/** /**
* Selects tracks for use. * Selects tracks for use.
* <p>
* This method should only be called after the source has been prepared.
* *
* @param tracks The track indices. * @param trackSelection The track selection.
*/ */
public void selectTracks(int[] tracks) { public void selectTracks(TrackSelection trackSelection) {
selectTracksInternal(tracks, true); selectTracksInternal(trackSelection, true);
} }
/** /**
* Resets the source. * Resets the source.
* <p>
* This method should only be called after the source has been prepared.
*/ */
public void reset() { public void reset() {
fatalError = null; fatalError = null;
...@@ -224,10 +199,9 @@ public class HlsChunkSource { ...@@ -224,10 +199,9 @@ public class HlsChunkSource {
* @param out A holder to populate. * @param out A holder to populate.
*/ */
public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, ChunkHolder out) { public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, ChunkHolder out) {
int previousChunkVariantIndex = int previousChunkVariantIndex = previous != null ? trackGroup.indexOf(previous.format) : -1;
previous != null ? getVariantIndex(previous.format) : -1;
updateFormatEvaluation(previous, playbackPositionUs); updateFormatEvaluation(previous, playbackPositionUs);
int newVariantIndex = getVariantIndex(evaluation.format); int newVariantIndex = trackGroup.indexOf(evaluation.format);
boolean switchingVariant = previousChunkVariantIndex != newVariantIndex; boolean switchingVariant = previousChunkVariantIndex != newVariantIndex;
HlsMediaPlaylist mediaPlaylist = variantPlaylists[newVariantIndex]; HlsMediaPlaylist mediaPlaylist = variantPlaylists[newVariantIndex];
if (mediaPlaylist == null) { if (mediaPlaylist == null) {
...@@ -443,7 +417,7 @@ public class HlsChunkSource { ...@@ -443,7 +417,7 @@ public class HlsChunkSource {
InvalidResponseCodeException responseCodeException = (InvalidResponseCodeException) e; InvalidResponseCodeException responseCodeException = (InvalidResponseCodeException) e;
int responseCode = responseCodeException.responseCode; int responseCode = responseCodeException.responseCode;
if (responseCode == 404 || responseCode == 410) { if (responseCode == 404 || responseCode == 410) {
int enabledVariantIndex = getEnabledVariantIndex(chunk.format); int enabledVariantIndex = trackSelection.indexOf(chunk.format);
boolean alreadyBlacklisted = enabledVariantBlacklistFlags[enabledVariantIndex]; boolean alreadyBlacklisted = enabledVariantBlacklistFlags[enabledVariantIndex];
enabledVariantBlacklistFlags[enabledVariantIndex] = true; enabledVariantBlacklistFlags[enabledVariantIndex] = true;
enabledVariantBlacklistTimes[enabledVariantIndex] = SystemClock.elapsedRealtime(); enabledVariantBlacklistTimes[enabledVariantIndex] = SystemClock.elapsedRealtime();
...@@ -471,37 +445,21 @@ public class HlsChunkSource { ...@@ -471,37 +445,21 @@ public class HlsChunkSource {
// Private methods. // Private methods.
private void selectTracksInternal(int[] tracks, boolean isExternal) { private void selectTracksInternal(TrackSelection trackSelection, boolean isExternal) {
this.trackSelection = trackSelection;
seenFirstExternalTrackSelection |= isExternal; seenFirstExternalTrackSelection |= isExternal;
// Construct and sort the enabled variants.
enabledVariants = new Variant[tracks.length];
for (int i = 0; i < tracks.length; i++) {
enabledVariants[i] = variants[tracks[i]];
}
Arrays.sort(enabledVariants, new Comparator<Variant>() {
private final Comparator<Format> formatComparator =
new Format.DecreasingBandwidthComparator();
@Override
public int compare(Variant first, Variant second) {
return formatComparator.compare(first.format, second.format);
}
});
// Reset the enabled variant blacklist flags. // Reset the enabled variant blacklist flags.
enabledVariantBlacklistTimes = new long[enabledVariants.length]; enabledVariantBlacklistTimes = new long[trackSelection.length];
enabledVariantBlacklistFlags = new boolean[enabledVariants.length]; enabledVariantBlacklistFlags = new boolean[trackSelection.length];
if (!isExternal) { if (!isExternal) {
return; return;
} }
if (enabledVariants.length > 1) { if (trackSelection.length > 1) {
Format[] formats = new Format[enabledVariants.length];
for (int i = 0; i < formats.length; i++) {
formats[i] = enabledVariants[i].format;
}
// TODO[REFACTOR]: We need to disable this at some point. // TODO[REFACTOR]: We need to disable this at some point.
Format[] formats = trackSelection.getFormats();
adaptiveFormatEvaluator.enable(formats); adaptiveFormatEvaluator.enable(formats);
if (!Util.contains(formats, evaluation.format)) { if (!Util.contains(formats, evaluation.format)) {
evaluation.format = null; evaluation.format = null;
...@@ -515,23 +473,23 @@ public class HlsChunkSource { ...@@ -515,23 +473,23 @@ public class HlsChunkSource {
private void updateFormatEvaluation(HlsMediaChunk previous, long playbackPositionUs) { private void updateFormatEvaluation(HlsMediaChunk previous, long playbackPositionUs) {
clearStaleBlacklistedVariants(); clearStaleBlacklistedVariants();
if (!seenFirstExternalTrackSelection) { if (!seenFirstExternalTrackSelection) {
if (!enabledVariantBlacklistFlags[getEnabledVariantIndex(variants[0].format)]) { if (!enabledVariantBlacklistFlags[trackSelection.indexOf(variants[0].format)]) {
// Use the first variant prior to external track selection, unless it's been blacklisted. // Use the first variant prior to external track selection, unless it's been blacklisted.
evaluation.format = variants[0].format; evaluation.format = variants[0].format;
return; return;
} }
// Try from lowest bitrate to highest. // Try from lowest bitrate to highest.
for (int i = enabledVariants.length - 1; i >= 0; i--) { for (int i = trackSelection.length - 1; i >= 0; i--) {
if (!enabledVariantBlacklistFlags[i]) { if (!enabledVariantBlacklistFlags[i]) {
evaluation.format = enabledVariants[i].format; evaluation.format = trackSelection.getFormat(i);
return; return;
} }
} }
// Should never happen. // Should never happen.
throw new IllegalStateException(); throw new IllegalStateException();
} }
if (enabledVariants.length == 1) { if (trackSelection.length == 1) {
evaluation.format = enabledVariants[0].format; evaluation.format = trackSelection.getFormat(0);
return; return;
} }
long bufferedDurationUs; long bufferedDurationUs;
...@@ -624,26 +582,6 @@ public class HlsChunkSource { ...@@ -624,26 +582,6 @@ public class HlsChunkSource {
} }
} }
private int getEnabledVariantIndex(Format format) {
for (int i = 0; i < enabledVariants.length; i++) {
if (enabledVariants[i].format == format) {
return i;
}
}
// Should never happen.
throw new IllegalStateException("Invalid format: " + format);
}
private int getVariantIndex(Format format) {
for (int i = 0; i < variants.length; i++) {
if (variants[i].format == format) {
return i;
}
}
// Should never happen.
throw new IllegalStateException("Invalid format: " + format);
}
// Private classes. // Private classes.
private static final class MediaPlaylistChunk extends DataChunk { private static final class MediaPlaylistChunk extends DataChunk {
......
...@@ -43,7 +43,6 @@ import com.google.android.exoplayer2.util.MimeTypes; ...@@ -43,7 +43,6 @@ import com.google.android.exoplayer2.util.MimeTypes;
import android.net.Uri; import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -429,13 +428,13 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource, ...@@ -429,13 +428,13 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
} }
// Get the subset of the new selections for the wrapper. // Get the subset of the new selections for the wrapper.
ArrayList<TrackSelection> newSelections = new ArrayList<>(); ArrayList<TrackSelection> newSelections = new ArrayList<>();
TrackGroupArray sampleStreamWrapperTrackGroups = sampleStreamWrapper.getTrackGroups();
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);
Pair<HlsSampleStreamWrapper, Integer> sourceAndGroup = getSourceAndGroup(selection.group); if (sampleStreamWrapperTrackGroups.indexOf(selection.group) != -1) {
if (sourceAndGroup.first == sampleStreamWrapper) {
newSelectionOriginalIndices[newSelections.size()] = i; newSelectionOriginalIndices[newSelections.size()] = i;
newSelections.add(new TrackSelection(sourceAndGroup.second, selection.getTracks())); newSelections.add(selection);
} }
} }
// Do nothing if nothing has changed, except during the first selection. // Do nothing if nothing has changed, except during the first selection.
...@@ -452,18 +451,6 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource, ...@@ -452,18 +451,6 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
return newSelections.size() - oldStreams.size(); return newSelections.size() - oldStreams.size();
} }
private Pair<HlsSampleStreamWrapper, Integer> getSourceAndGroup(int group) {
int totalTrackGroupCount = 0;
for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
int sourceTrackGroupCount = sampleStreamWrapper.getTrackGroups().length;
if (group < totalTrackGroupCount + sourceTrackGroupCount) {
return Pair.create(sampleStreamWrapper, group - totalTrackGroupCount);
}
totalTrackGroupCount += sourceTrackGroupCount;
}
throw new IndexOutOfBoundsException();
}
private static boolean variantHasExplicitCodecWithPrefix(Variant variant, String prefix) { private static boolean variantHasExplicitCodecWithPrefix(Variant variant, String prefix) {
String codecs = variant.codecs; String codecs = variant.codecs;
if (TextUtils.isEmpty(codecs)) { if (TextUtils.isEmpty(codecs)) {
......
...@@ -164,11 +164,11 @@ import java.util.List; ...@@ -164,11 +164,11 @@ 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 = selection.group; int group = trackGroups.indexOf(selection.group);
int[] tracks = selection.getTracks(); int[] tracks = selection.getTracks();
setTrackGroupEnabledState(group, true); setTrackGroupEnabledState(group, true);
if (group == primaryTrackGroupIndex) { if (group == primaryTrackGroupIndex) {
chunkSource.selectTracks(tracks); chunkSource.selectTracks(new TrackSelection(chunkSource.getTrackGroup(), tracks));
} }
newStreams[i] = new SampleStreamImpl(group); newStreams[i] = new SampleStreamImpl(group);
} }
...@@ -526,8 +526,8 @@ import java.util.List; ...@@ -526,8 +526,8 @@ import java.util.List;
} }
} }
// Calculate the number of tracks that will be exposed. TrackGroup chunkSourceTrackGroup = chunkSource.getTrackGroup();
int chunkSourceTrackCount = chunkSource.getTrackCount(); int chunkSourceTrackCount = chunkSourceTrackGroup.length;
// Instantiate the necessary internal data-structures. // Instantiate the necessary internal data-structures.
primaryTrackGroupIndex = -1; primaryTrackGroupIndex = -1;
...@@ -540,9 +540,9 @@ import java.util.List; ...@@ -540,9 +540,9 @@ import java.util.List;
if (i == primaryExtractorTrackIndex) { if (i == primaryExtractorTrackIndex) {
Format[] formats = new Format[chunkSourceTrackCount]; Format[] formats = new Format[chunkSourceTrackCount];
for (int j = 0; j < chunkSourceTrackCount; j++) { for (int j = 0; j < chunkSourceTrackCount; j++) {
formats[j] = getSampleFormat(chunkSource.getTrackFormat(j), sampleFormat); formats[j] = getSampleFormat(chunkSourceTrackGroup.getFormat(j), sampleFormat);
} }
trackGroups[i] = new TrackGroup(chunkSource.isAdaptive(), formats); trackGroups[i] = new TrackGroup(chunkSourceTrackGroup.adaptive, formats);
primaryTrackGroupIndex = i; primaryTrackGroupIndex = i;
} else { } else {
Format trackFormat = null; Format trackFormat = null;
......
...@@ -17,12 +17,10 @@ package com.google.android.exoplayer2.source.smoothstreaming; ...@@ -17,12 +17,10 @@ package com.google.android.exoplayer2.source.smoothstreaming;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Format.DecreasingBandwidthComparator;
import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer2.extractor.mp4.Track; import com.google.android.exoplayer2.extractor.mp4.Track;
import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox; import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox;
import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.Chunk; 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;
...@@ -32,15 +30,14 @@ import com.google.android.exoplayer2.source.chunk.FormatEvaluator.Evaluation; ...@@ -32,15 +30,14 @@ 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;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.Loader;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.List; import java.util.List;
/** /**
...@@ -61,23 +58,21 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -61,23 +58,21 @@ public class DefaultSsChunkSource implements SsChunkSource {
@Override @Override
public SsChunkSource createChunkSource(Loader manifestLoader, SsManifest manifest, public SsChunkSource createChunkSource(Loader manifestLoader, SsManifest manifest,
int elementIndex, TrackGroup trackGroup, int[] tracks, int elementIndex, TrackSelection trackSelection,
TrackEncryptionBox[] trackEncryptionBoxes) { TrackEncryptionBox[] trackEncryptionBoxes) {
FormatEvaluator adaptiveEvaluator = tracks.length > 1 FormatEvaluator adaptiveEvaluator = trackSelection.length > 1
? formatEvaluatorFactory.createFormatEvaluator() : null; ? formatEvaluatorFactory.createFormatEvaluator() : null;
DataSource dataSource = dataSourceFactory.createDataSource(); DataSource dataSource = dataSourceFactory.createDataSource();
return new DefaultSsChunkSource(manifestLoader, manifest, elementIndex, return new DefaultSsChunkSource(manifestLoader, manifest, elementIndex, trackSelection,
trackGroup, tracks, dataSource, adaptiveEvaluator, dataSource, adaptiveEvaluator, trackEncryptionBoxes);
trackEncryptionBoxes);
} }
} }
private final Loader manifestLoader; private final Loader manifestLoader;
private final int elementIndex; private final int elementIndex;
private final TrackGroup trackGroup; private final TrackSelection trackSelection;
private final ChunkExtractorWrapper[] extractorWrappers; private final ChunkExtractorWrapper[] extractorWrappers;
private final Format[] enabledFormats;
private final boolean[] adaptiveFormatBlacklistFlags; private final boolean[] adaptiveFormatBlacklistFlags;
private final DataSource dataSource; private final DataSource dataSource;
private final Evaluation evaluation; private final Evaluation evaluation;
...@@ -92,45 +87,40 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -92,45 +87,40 @@ public class DefaultSsChunkSource implements SsChunkSource {
* @param manifestLoader The {@link Loader} being used to load manifests. * @param manifestLoader The {@link Loader} being used to load manifests.
* @param manifest The initial manifest. * @param manifest The initial manifest.
* @param elementIndex The index of the stream element in the manifest. * @param elementIndex The index of the stream element in the manifest.
* @param trackGroup The track group corresponding to the stream element. * @param trackSelection The track selection.
* @param tracks The indices of the selected tracks within the stream element.
* @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 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(Loader manifestLoader, SsManifest manifest, int elementIndex, public DefaultSsChunkSource(Loader manifestLoader, SsManifest manifest, int elementIndex,
TrackGroup trackGroup, int[] tracks, DataSource dataSource, TrackSelection trackSelection, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
FormatEvaluator adaptiveFormatEvaluator, TrackEncryptionBox[] trackEncryptionBoxes) { TrackEncryptionBox[] trackEncryptionBoxes) {
this.manifestLoader = manifestLoader; this.manifestLoader = manifestLoader;
this.manifest = manifest; this.manifest = manifest;
this.elementIndex = elementIndex; this.elementIndex = elementIndex;
this.trackGroup = trackGroup; this.trackSelection = trackSelection;
this.dataSource = dataSource; this.dataSource = dataSource;
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator; this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
this.evaluation = new Evaluation(); this.evaluation = new Evaluation();
StreamElement streamElement = manifest.streamElements[elementIndex]; StreamElement streamElement = manifest.streamElements[elementIndex];
Format[] formats = streamElement.formats;
extractorWrappers = new ChunkExtractorWrapper[formats.length]; extractorWrappers = new ChunkExtractorWrapper[trackSelection.length];
for (int j = 0; j < formats.length; j++) { for (int i = 0; i < trackSelection.length; i++) {
int manifestTrackIndex = trackSelection.getTrack(i);
Format format = trackSelection.getFormat(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(j, streamElement.type, streamElement.timescale, C.UNSET_TIME_US, Track track = new Track(manifestTrackIndex, streamElement.type, streamElement.timescale,
manifest.durationUs, formats[j], Track.TRANSFORMATION_NONE, trackEncryptionBoxes, C.UNSET_TIME_US, manifest.durationUs, format, Track.TRANSFORMATION_NONE,
nalUnitLengthFieldLength, null, null); trackEncryptionBoxes, nalUnitLengthFieldLength, null, null);
FragmentedMp4Extractor extractor = new FragmentedMp4Extractor( FragmentedMp4Extractor extractor = new FragmentedMp4Extractor(
FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
| FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, track); | FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, track);
extractorWrappers[j] = new ChunkExtractorWrapper(extractor, formats[j], false); extractorWrappers[i] = new ChunkExtractorWrapper(extractor, format, false);
}
enabledFormats = new Format[tracks.length];
for (int i = 0; i < tracks.length; i++) {
enabledFormats[i] = trackGroup.getFormat(tracks[i]);
} }
Arrays.sort(enabledFormats, new DecreasingBandwidthComparator());
if (adaptiveFormatEvaluator != null) { if (adaptiveFormatEvaluator != null) {
adaptiveFormatEvaluator.enable(enabledFormats); adaptiveFormatEvaluator.enable(trackSelection.getFormats());
adaptiveFormatBlacklistFlags = new boolean[tracks.length]; adaptiveFormatBlacklistFlags = new boolean[trackSelection.length];
} else { } else {
adaptiveFormatBlacklistFlags = null; adaptiveFormatBlacklistFlags = null;
} }
...@@ -172,7 +162,7 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -172,7 +162,7 @@ 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 || enabledFormats.length < 2) { if (fatalError != null || trackSelection.length < 2) {
return queue.size(); return queue.size();
} }
return adaptiveFormatEvaluator.evaluateQueueSize(playbackPositionUs, queue, return adaptiveFormatEvaluator.evaluateQueueSize(playbackPositionUs, queue,
...@@ -185,12 +175,12 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -185,12 +175,12 @@ public class DefaultSsChunkSource implements SsChunkSource {
return; return;
} }
if (enabledFormats.length > 1) { if (trackSelection.length > 1) {
long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0; long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
adaptiveFormatEvaluator.evaluateFormat(bufferedDurationUs, adaptiveFormatBlacklistFlags, adaptiveFormatEvaluator.evaluateFormat(bufferedDurationUs, adaptiveFormatBlacklistFlags,
evaluation); evaluation);
} else { } else {
evaluation.format = enabledFormats[0]; evaluation.format = trackSelection.getFormat(0);
evaluation.trigger = FormatEvaluator.TRIGGER_UNKNOWN; evaluation.trigger = FormatEvaluator.TRIGGER_UNKNOWN;
evaluation.data = null; evaluation.data = null;
} }
...@@ -229,10 +219,10 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -229,10 +219,10 @@ 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 trackGroupTrackIndex = getTrackGroupTrackIndex(trackGroup, selectedFormat); int trackSelectionIndex = trackSelection.indexOf(selectedFormat);
ChunkExtractorWrapper extractorWrapper = extractorWrappers[trackGroupTrackIndex]; ChunkExtractorWrapper extractorWrapper = extractorWrappers[trackSelectionIndex];
int manifestTrackIndex = getManifestTrackIndex(streamElement, selectedFormat); int manifestTrackIndex = trackSelection.getTrack(trackSelectionIndex);
Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex); Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex);
out.chunk = newMediaChunk(selectedFormat, dataSource, uri, null, currentAbsoluteChunkIndex, out.chunk = newMediaChunk(selectedFormat, dataSource, uri, null, currentAbsoluteChunkIndex,
...@@ -259,37 +249,6 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -259,37 +249,6 @@ public class DefaultSsChunkSource implements SsChunkSource {
// Private methods. // Private methods.
/**
* Gets the index of a format in a track group, using referential equality.
*/
private static int getTrackGroupTrackIndex(TrackGroup trackGroup, Format format) {
for (int i = 0; i < trackGroup.length; i++) {
if (trackGroup.getFormat(i) == format) {
return i;
}
}
// Should never happen.
throw new IllegalStateException("Invalid format: " + format);
}
/**
* Gets the index of a format in an element, using format.id equality.
* <p>
* This method will return the same index as {@link #getTrackGroupTrackIndex(TrackGroup, Format)}
* except in the case where a live manifest is refreshed and the ordering of the tracks in the
* manifest has changed.
*/
private static int getManifestTrackIndex(StreamElement element, Format format) {
Format[] formats = element.formats;
for (int i = 0; i < formats.length; i++) {
if (TextUtils.equals(formats[i].id, format.id)) {
return i;
}
}
// Should never happen.
throw new IllegalStateException("Invalid format: " + format);
}
private static MediaChunk newMediaChunk(Format format, DataSource dataSource, Uri uri, private static MediaChunk newMediaChunk(Format format, DataSource dataSource, Uri uri,
String cacheKey, int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs, String cacheKey, int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs,
int formatEvaluatorTrigger, Object formatEvaluatorData, int formatEvaluatorTrigger, Object formatEvaluatorData,
......
...@@ -16,9 +16,9 @@ ...@@ -16,9 +16,9 @@
package com.google.android.exoplayer2.source.smoothstreaming; package com.google.android.exoplayer2.source.smoothstreaming;
import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox; import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.ChunkSource; import com.google.android.exoplayer2.source.chunk.ChunkSource;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.Loader;
/** /**
...@@ -29,7 +29,7 @@ public interface SsChunkSource extends ChunkSource { ...@@ -29,7 +29,7 @@ public interface SsChunkSource extends ChunkSource {
interface Factory { interface Factory {
SsChunkSource createChunkSource(Loader manifestLoader, SsManifest manifest, int elementIndex, SsChunkSource createChunkSource(Loader manifestLoader, SsManifest manifest, int elementIndex,
TrackGroup trackGroup, int[] tracks, TrackEncryptionBox[] trackEncryptionBoxes); TrackSelection trackSelection, TrackEncryptionBox[] trackEncryptionBoxes);
} }
......
...@@ -354,11 +354,9 @@ public final class SsMediaSource implements MediaPeriod, MediaSource, ...@@ -354,11 +354,9 @@ public final class SsMediaSource implements MediaPeriod, MediaSource,
private ChunkSampleStream<SsChunkSource> buildSampleStream(TrackSelection selection, private ChunkSampleStream<SsChunkSource> buildSampleStream(TrackSelection selection,
long positionUs) { long positionUs) {
int[] selectedTracks = selection.getTracks(); int streamElementIndex = trackGroupElementIndices[trackGroups.indexOf(selection.group)];
int streamElementIndex = trackGroupElementIndices[selection.group]; SsChunkSource chunkSource = chunkSourceFactory.createChunkSource(manifestLoader, manifest,
SsChunkSource chunkSource = chunkSourceFactory.createChunkSource(manifestLoader, streamElementIndex, selection, trackEncryptionBoxes);
manifest, streamElementIndex, trackGroups.get(selection.group), selectedTracks,
trackEncryptionBoxes);
return new ChunkSampleStream<>(manifest.streamElements[streamElementIndex].type, chunkSource, return new ChunkSampleStream<>(manifest.streamElements[streamElementIndex].type, chunkSource,
this, allocator, positionUs, minLoadableRetryCount, eventDispatcher); this, allocator, positionUs, minLoadableRetryCount, eventDispatcher);
} }
......
...@@ -176,17 +176,18 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -176,17 +176,18 @@ public class DefaultTrackSelector extends MappingTrackSelector {
: RendererCapabilities.ADAPTIVE_SEAMLESS; : RendererCapabilities.ADAPTIVE_SEAMLESS;
boolean allowMixedMimeTypes = allowMixedMimeAdaptiveness boolean allowMixedMimeTypes = allowMixedMimeAdaptiveness
&& (rendererCapabilities.supportsMixedMimeTypeAdaptation() & requiredAdaptiveSupport) != 0; && (rendererCapabilities.supportsMixedMimeTypeAdaptation() & requiredAdaptiveSupport) != 0;
int largestAdaptiveGroup = -1; TrackGroup largestAdaptiveGroup = null;
int[] largestAdaptiveGroupTracks = NO_TRACKS; int[] largestAdaptiveGroupTracks = NO_TRACKS;
for (int i = 0; i < trackGroups.length; i++) { for (int i = 0; i < trackGroups.length; i++) {
int[] adaptiveTracks = getAdaptiveTracksOfGroup(trackGroups.get(i), formatSupport[i], TrackGroup trackGroup = trackGroups.get(i);
int[] adaptiveTracks = getAdaptiveTracksOfGroup(trackGroup, formatSupport[i],
allowMixedMimeTypes, requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight); allowMixedMimeTypes, requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight);
if (adaptiveTracks.length > largestAdaptiveGroupTracks.length) { if (adaptiveTracks.length > largestAdaptiveGroupTracks.length) {
largestAdaptiveGroup = i; largestAdaptiveGroup = trackGroup;
largestAdaptiveGroupTracks = adaptiveTracks; largestAdaptiveGroupTracks = adaptiveTracks;
} }
} }
if (largestAdaptiveGroup != -1) { if (largestAdaptiveGroup != null) {
return new TrackSelection(largestAdaptiveGroup, largestAdaptiveGroupTracks); return new TrackSelection(largestAdaptiveGroup, largestAdaptiveGroupTracks);
} }
...@@ -197,7 +198,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -197,7 +198,7 @@ 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(groupIndex, trackIndex); return new TrackSelection(trackGroup, trackIndex);
} }
} }
} }
...@@ -267,7 +268,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -267,7 +268,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private static TrackSelection selectSmallestSupportedVideoTrack(TrackGroupArray trackGroups, private static TrackSelection selectSmallestSupportedVideoTrack(TrackGroupArray trackGroups,
int[][] formatSupport) { int[][] formatSupport) {
int smallestPixelCount = Integer.MAX_VALUE; int smallestPixelCount = Integer.MAX_VALUE;
int trackGroupIndexSelection = -1; TrackGroup trackGroupSelection = null;
int trackIndexSelection = -1; int trackIndexSelection = -1;
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);
...@@ -279,13 +280,13 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -279,13 +280,13 @@ public class DefaultTrackSelector extends MappingTrackSelector {
&& isSupportedVideoTrack(trackFormatSupport[trackIndex], format, Integer.MAX_VALUE, && isSupportedVideoTrack(trackFormatSupport[trackIndex], format, Integer.MAX_VALUE,
Integer.MAX_VALUE)) { Integer.MAX_VALUE)) {
smallestPixelCount = pixelCount; smallestPixelCount = pixelCount;
trackGroupIndexSelection = groupIndex; trackGroupSelection = trackGroup;
trackIndexSelection = trackIndex; trackIndexSelection = trackIndex;
} }
} }
} }
return trackIndexSelection != -1 return trackGroupSelection != null
? new TrackSelection(trackGroupIndexSelection, trackIndexSelection) : null; ? new TrackSelection(trackGroupSelection, trackIndexSelection) : null;
} }
private static boolean isSupportedVideoTrack(int formatSupport, Format format, int maxVideoWidth, private static boolean isSupportedVideoTrack(int formatSupport, Format format, int maxVideoWidth,
...@@ -305,7 +306,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -305,7 +306,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(groupIndex, trackIndex); return new TrackSelection(trackGroup, trackIndex);
} }
} }
} }
...@@ -318,7 +319,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -318,7 +319,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private static TrackSelection selectTrackForTextRenderer(TrackGroupArray trackGroups, private static TrackSelection selectTrackForTextRenderer(TrackGroupArray trackGroups,
int[][] formatSupport, String preferredLanguage) { int[][] formatSupport, String preferredLanguage) {
int firstForcedGroup = -1; TrackGroup firstForcedGroup = null;
int firstForcedTrack = -1; int firstForcedTrack = -1;
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);
...@@ -327,17 +328,17 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -327,17 +328,17 @@ public class DefaultTrackSelector extends MappingTrackSelector {
if (isSupported(trackFormatSupport[trackIndex]) if (isSupported(trackFormatSupport[trackIndex])
&& (trackGroup.getFormat(trackIndex).selectionFlags && (trackGroup.getFormat(trackIndex).selectionFlags
& Format.SELECTION_FLAG_FORCED) != 0) { & Format.SELECTION_FLAG_FORCED) != 0) {
if (firstForcedGroup == -1) { if (firstForcedGroup == null) {
firstForcedGroup = groupIndex; firstForcedGroup = trackGroup;
firstForcedTrack = trackIndex; firstForcedTrack = trackIndex;
} }
if (formatHasLanguage(trackGroup.getFormat(trackIndex), preferredLanguage)) { if (formatHasLanguage(trackGroup.getFormat(trackIndex), preferredLanguage)) {
return new TrackSelection(groupIndex, trackIndex); return new TrackSelection(trackGroup, trackIndex);
} }
} }
} }
} }
return firstForcedGroup != -1 ? new TrackSelection(firstForcedGroup, firstForcedTrack) : null; return firstForcedGroup != null ? new TrackSelection(firstForcedGroup, firstForcedTrack) : null;
} }
// General track selection methods. // General track selection methods.
...@@ -349,7 +350,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { ...@@ -349,7 +350,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(groupIndex, trackIndex); return new TrackSelection(trackGroup, trackIndex);
} }
} }
} }
......
...@@ -272,39 +272,26 @@ public abstract class MappingTrackSelector extends TrackSelector { ...@@ -272,39 +272,26 @@ public abstract class MappingTrackSelector extends TrackSelector {
TrackGroupArray unassociatedTrackGroupArray = new TrackGroupArray(Arrays.copyOf( TrackGroupArray unassociatedTrackGroupArray = new TrackGroupArray(Arrays.copyOf(
rendererTrackGroups[rendererCapabilities.length], unassociatedTrackGroupCount)); rendererTrackGroups[rendererCapabilities.length], unassociatedTrackGroupCount));
TrackSelection[] rendererTrackSelections = selectTracks(rendererCapabilities, TrackSelection[] trackSelections = selectTracks(rendererCapabilities, rendererTrackGroupArrays,
rendererTrackGroupArrays, rendererFormatSupports); rendererFormatSupports);
// Apply track disabling and overriding. // Apply track disabling and overriding.
for (int i = 0; i < rendererCapabilities.length; i++) { for (int i = 0; i < rendererCapabilities.length; i++) {
if (rendererDisabledFlags.get(i)) { if (rendererDisabledFlags.get(i)) {
rendererTrackSelections[i] = null; trackSelections[i] = null;
} else { } else {
Map<TrackGroupArray, TrackSelection> override = trackSelectionOverrides.get(i); Map<TrackGroupArray, TrackSelection> override = trackSelectionOverrides.get(i);
TrackSelection overrideSelection = override == null ? null TrackSelection overrideSelection = override == null ? null
: override.get(rendererTrackGroupArrays[i]); : override.get(rendererTrackGroupArrays[i]);
if (overrideSelection != null) { if (overrideSelection != null) {
rendererTrackSelections[i] = overrideSelection; trackSelections[i] = overrideSelection;
} }
} }
} }
// The track selections above index into the track group arrays associated to each renderer,
// and not to the original track groups passed to this method. Build the corresponding track
// selections into the original track groups to pass back as the final selection.
TrackSelection[] trackSelections = new TrackSelection[rendererCapabilities.length];
for (int i = 0; i < rendererCapabilities.length; i++) {
TrackSelection selection = rendererTrackSelections[i];
if (selection != null) {
TrackGroup group = rendererTrackGroupArrays[i].get(selection.group);
int originalGroupIndex = findGroupInGroupArray(trackGroups, group);
trackSelections[i] = new TrackSelection(originalGroupIndex, selection.getTracks());
}
}
// Package up the track information and selections. // Package up the track information and selections.
TrackSelectionArray trackSelectionArray = new TrackSelectionArray(trackSelections); TrackSelectionArray trackSelectionArray = new TrackSelectionArray(trackSelections);
TrackInfo trackInfo = new TrackInfo(rendererTrackGroupArrays, rendererTrackSelections, TrackInfo trackInfo = new TrackInfo(rendererTrackGroupArrays, trackSelections,
mixedMimeTypeAdaptationSupport, rendererFormatSupports, unassociatedTrackGroupArray); mixedMimeTypeAdaptationSupport, rendererFormatSupports, unassociatedTrackGroupArray);
return Pair.<TrackSelectionArray, Object>create(trackSelectionArray, trackInfo); return Pair.<TrackSelectionArray, Object>create(trackSelectionArray, trackInfo);
} }
...@@ -403,23 +390,6 @@ public abstract class MappingTrackSelector extends TrackSelector { ...@@ -403,23 +390,6 @@ public abstract class MappingTrackSelector extends TrackSelector {
return mixedMimeTypeAdaptationSupport; return mixedMimeTypeAdaptationSupport;
} }
/**
* Finds the specified group in a group array, using referential equality.
*
* @param groupArray The group array to search.
* @param group The group to search for.
* @return The index of the group in the group array.
* @throws IllegalStateException If the group was not found.
*/
private static int findGroupInGroupArray(TrackGroupArray groupArray, TrackGroup group) {
for (int i = 0; i < groupArray.length; i++) {
if (groupArray.get(i) == group) {
return i;
}
}
throw new IllegalStateException();
}
private void notifyTrackInfoChanged(final TrackInfo trackInfo) { private void notifyTrackInfoChanged(final TrackInfo trackInfo) {
if (eventHandler != null) { if (eventHandler != null) {
eventHandler.post(new Runnable() { eventHandler.post(new Runnable() {
......
...@@ -15,44 +15,93 @@ ...@@ -15,44 +15,93 @@
*/ */
package com.google.android.exoplayer2.trackselection; 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.TrackGroup;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.util.Arrays; import java.util.Arrays;
/** /**
* Defines a track selection. * A track selection, consisting of a {@link TrackGroup} and a selected subset of the tracks within
* it. The selected tracks are exposed in order of decreasing bandwidth.
*/ */
public final class TrackSelection { public final class TrackSelection {
/** /**
* The index of the selected {@link TrackGroup}. * The selected {@link TrackGroup}.
*/ */
public final int group; public final TrackGroup group;
/** /**
* The number of selected tracks within the {@link TrackGroup}. Always greater than zero. * The number of selected tracks within the {@link TrackGroup}. Always greater than zero.
*/ */
public final int length; public final int length;
private final int[] tracks; private final int[] tracks;
private final Format[] formats;
// Lazily initialized hashcode. // Lazily initialized hashcode.
private int hashCode; private int hashCode;
/** /**
* @param group The index of the {@link TrackGroup}. * @param group The {@link TrackGroup}. Must not be null.
* @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be
* null or empty. * null or empty. May be in any order.
*/ */
public TrackSelection(int group, int... tracks) { public TrackSelection(TrackGroup group, int... tracks) {
Assertions.checkState(tracks.length > 0); Assertions.checkState(tracks.length > 0);
this.group = group; this.group = Assertions.checkNotNull(group);
this.tracks = tracks;
this.length = tracks.length; 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]);
}
}
/**
* Gets the format of the track at a given index in the selection.
*
* @param index The index in the selection.
* @return The format of the selected track.
*/
public Format getFormat(int index) {
return formats[index];
}
/**
* Gets a copy of the formats of the selected tracks.
*
* @return The track formats.
*/
public Format[] getFormats() {
return formats.clone();
}
/**
* Gets the index in the selection of the track with the specified format.
*
* @param format The format.
* @return The index in the selection, or -1 if the track with the specified format is not part of
* the selection.
*/
public int indexOf(Format format) {
for (int i = 0; i < length; i++) {
if (formats[i] == format) {
return i;
}
}
return -1;
} }
/** /**
* Gets the index of the selected track at a given index in the selection. * Gets the index in the track group of the track at a given index in the selection.
* *
* @param index The index in the selection. * @param index The index in the selection.
* @return The index of the selected track. * @return The index of the selected track.
...@@ -62,7 +111,7 @@ public final class TrackSelection { ...@@ -62,7 +111,7 @@ public final class TrackSelection {
} }
/** /**
* Gets a copy of the individual track indices. * Gets a copy of the selected tracks in the track group.
* *
* @return The track indices. * @return The track indices.
*/ */
...@@ -71,24 +120,25 @@ public final class TrackSelection { ...@@ -71,24 +120,25 @@ public final class TrackSelection {
} }
/** /**
* Gets whether a given track index is included in the selection. * Gets the index in the selection of the track with the specified index in the track group.
* *
* @param trackIndex The track index. * @param trackIndex The index in the track group.
* @return True if the index is included in the selection. False otherwise. * @return The index in the selection, or -1 if the track with the specified index is not part of
* the selection.
*/ */
public boolean containsTrack(int trackIndex) { public int indexOf(int trackIndex) {
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
if (tracks[i] == trackIndex) { if (tracks[i] == trackIndex) {
return true; return i;
} }
} }
return false; return -1;
} }
@Override @Override
public int hashCode() { public int hashCode() {
if (hashCode == 0) { if (hashCode == 0) {
hashCode = 31 * group + Arrays.hashCode(tracks); hashCode = 31 * System.identityHashCode(group) + Arrays.hashCode(tracks);
} }
return hashCode; return hashCode;
} }
......
...@@ -826,11 +826,13 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit ...@@ -826,11 +826,13 @@ 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(0, selections[VIDEO_RENDERER_INDEX] = new TrackSelection(
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, selections[AUDIO_RENDERER_INDEX] = new TrackSelection(
rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0),
getTrackIndices(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0), getTrackIndices(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0),
rendererFormatSupports[AUDIO_RENDERER_INDEX][0], audioFormatIds, false)); rendererFormatSupports[AUDIO_RENDERER_INDEX][0], audioFormatIds, false));
includedAdditionalVideoFormats = includedAdditionalVideoFormats =
......
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