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
This commit is contained in:
olly 2016-07-22 03:38:13 -07:00 committed by Oliver Woodman
parent 6f1b24f10f
commit 692d756ee6
20 changed files with 239 additions and 342 deletions

View file

@ -109,7 +109,7 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb
trackGroup.length, trackInfo.getAdaptiveSupport(rendererIndex, groupIndex, false));
Log.d(TAG, " Group:" + groupIndex + ", adaptive_supported=" + adaptiveSupport + " [");
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
String status = getTrackStatusString(trackSelection, groupIndex, trackIndex);
String status = getTrackStatusString(trackSelection, trackGroup, trackIndex);
String formatSupport = getFormatSupportString(
trackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex));
Log.d(TAG, " " + status + " Track:" + trackIndex + ", "
@ -353,9 +353,9 @@ public class EventLogger implements ExoPlayer.EventListener, SimpleExoPlayer.Deb
return builder.toString();
}
private static String getTrackStatusString(TrackSelection selection, int groupIndex,
private static String getTrackStatusString(TrackSelection selection, TrackGroup group,
int trackIndex) {
boolean groupEnabled = selection != null && selection.group == groupIndex;
boolean groupEnabled = selection != null && selection.group == group;
if (groupEnabled) {
for (int i = 0; i < selection.length; i++) {
if (selection.getTrack(i) == trackIndex) {

View file

@ -135,7 +135,7 @@ import java.util.Locale;
if (trackInfo.getTrackFormatSupport(rendererIndex, groupIndex, trackIndex)
== RendererCapabilities.FORMAT_HANDLED) {
haveSupportedTracks = true;
trackView.setTag(Pair.create(groupIndex, trackIndex));
trackView.setTag(Pair.create(group, trackIndex));
trackView.setOnClickListener(this);
} else {
trackView.setEnabled(false);
@ -160,7 +160,7 @@ import java.util.Locale;
for (int i = 0; i < trackViews.length; i++) {
for (int j = 0; j < trackViews[i].length; j++) {
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;
} else {
isDisabled = false;
@SuppressWarnings("unchecked")
Pair<Integer, Integer> tag = (Pair<Integer, Integer>) view.getTag();
int groupIndex = tag.first;
Pair<TrackGroup, Integer> tag = (Pair<TrackGroup, Integer>) view.getTag();
TrackGroup group = tag.first;
int trackIndex = tag.second;
if (!trackGroupsAdaptive[groupIndex] || override == null) {
override = new TrackSelection(groupIndex, trackIndex);
if (!trackGroupsAdaptive[trackGroups.indexOf(group)] || override == null) {
override = new TrackSelection(group, trackIndex);
} else {
// The group being modified is adaptive and we already have a non-null override.
boolean isEnabled = ((CheckedTextView) view).isChecked();
@ -216,13 +216,13 @@ import java.util.Locale;
tracks[trackCount++] = override.getTrack(i);
}
}
override = new TrackSelection(groupIndex, tracks);
override = new TrackSelection(group, tracks);
}
} else {
// Add the track to the override.
int[] tracks = Arrays.copyOf(override.getTracks(), override.length + 1);
tracks[tracks.length - 1] = trackIndex;
override = new TrackSelection(groupIndex, tracks);
override = new TrackSelection(group, tracks);
}
}
}

View file

@ -19,7 +19,6 @@ import com.google.android.exoplayer2.ExoPlayer.ExoPlayerMessage;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
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.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector;
@ -705,7 +704,6 @@ import java.util.ArrayList;
TrackSelectionArray oldTrackSelections = readingPeriod.trackSelections;
readingPeriod = readingPeriod.nextPeriod;
TrackSelectionArray newTrackSelections = readingPeriod.trackSelections;
TrackGroupArray groups = readingPeriod.mediaPeriod.getTrackGroups();
for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i];
TrackSelection oldSelection = oldTrackSelections.get(i);
@ -716,7 +714,7 @@ import java.util.ArrayList;
// can be seamless.
Format[] formats = new Format[newSelection.length];
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],
readingPeriod.offsetUs);
@ -956,7 +954,6 @@ import java.util.ArrayList;
throws ExoPlaybackException {
enabledRenderers = new Renderer[enabledRendererCount];
enabledRendererCount = 0;
TrackGroupArray trackGroups = playingPeriod.mediaPeriod.getTrackGroups();
for (int i = 0; i < renderers.length; i++) {
Renderer renderer = renderers[i];
TrackSelection newSelection = playingPeriod.trackSelections.get(i);
@ -970,7 +967,7 @@ import java.util.ArrayList;
// Build an array of formats contained by the selection.
Format[] formats = new Format[newSelection.length];
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.
renderer.enable(formats, playingPeriod.sampleStreams[i], internalPositionUs, joining,

View file

@ -252,7 +252,7 @@ public final class ExtractorMediaSource implements MediaPeriod, MediaSource,
TrackSelection selection = newSelections.get(i);
Assertions.checkState(selection.length == 1);
Assertions.checkState(selection.getTrack(0) == 0);
int track = selection.group;
int track = tracks.indexOf(selection.group);
Assertions.checkState(!trackEnabledStates[track]);
enabledTrackCount++;
trackEnabledStates[track] = true;

View file

@ -19,8 +19,6 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
import android.util.Pair;
import java.io.IOException;
import java.util.ArrayList;
import java.util.IdentityHashMap;
@ -183,9 +181,10 @@ public final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callba
TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount];
int trackGroupIndex = 0;
for (MediaPeriod period : periods) {
int periodTrackGroupCount = period.getTrackGroups().length;
TrackGroupArray periodTrackGroups = period.getTrackGroups();
int periodTrackGroupCount = periodTrackGroups.length;
for (int j = 0; j < periodTrackGroupCount; j++) {
trackGroupArray[trackGroupIndex++] = period.getTrackGroups().get(j);
trackGroupArray[trackGroupIndex++] = periodTrackGroups.get(j);
}
}
trackGroups = new TrackGroupArray(trackGroupArray);
@ -218,12 +217,12 @@ public final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callba
// Get the subset of the new selections for the period.
ArrayList<TrackSelection> newSelections = new ArrayList<>();
int[] newSelectionOriginalIndices = new int[allNewSelections.size()];
TrackGroupArray periodTrackGroups = period.getTrackGroups();
for (int i = 0; i < allNewSelections.size(); i++) {
TrackSelection selection = allNewSelections.get(i);
Pair<MediaPeriod, Integer> periodAndGroup = getPeriodAndGroup(selection.group);
if (periodAndGroup.first == period) {
if (periodTrackGroups.indexOf(selection.group) != -1) {
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.
@ -239,16 +238,4 @@ public final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callba
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();
}
}

View file

@ -72,6 +72,21 @@ public final class TrackGroup {
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
public int hashCode() {
if (hashCode == 0) {

View file

@ -50,6 +50,21 @@ public final class TrackGroupArray {
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
public int hashCode() {
if (hashCode == 0) {

View file

@ -15,9 +15,9 @@
*/
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.dash.manifest.DashManifest;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Loader;
/**
@ -28,7 +28,7 @@ public interface DashChunkSource extends ChunkSource {
interface Factory {
DashChunkSource createDashChunkSource(Loader manifestLoader, DashManifest manifest,
int periodIndex, int adaptationSetIndex, TrackGroup trackGroup, int[] tracks,
int periodIndex, int adaptationSetIndex, TrackSelection trackSelection,
long elapsedRealtimeOffsetMs);
}

View file

@ -236,12 +236,10 @@ import java.util.List;
private ChunkSampleStream<DashChunkSource> buildSampleStream(TrackSelection selection,
long positionUs) {
int[] selectedTracks = selection.getTracks();
int adaptationSetIndex = trackGroupAdaptationSetIndices[selection.group];
int adaptationSetIndex = trackGroupAdaptationSetIndices[trackGroups.indexOf(selection.group)];
AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex);
DashChunkSource chunkSource = chunkSourceFactory.createDashChunkSource(loader, manifest, index,
adaptationSetIndex, trackGroups.get(selection.group), selectedTracks,
elapsedRealtimeOffset);
adaptationSetIndex, selection, elapsedRealtimeOffset);
return new ChunkSampleStream<>(adaptationSet.type, chunkSource, this, allocator, positionUs,
minLoadableRetryCount, eventDispatcher);
}

View file

@ -17,13 +17,11 @@ package com.google.android.exoplayer2.source.dash;
import com.google.android.exoplayer2.C;
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.SeekMap;
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
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.ChunkExtractorWrapper;
import com.google.android.exoplayer2.source.chunk.ChunkHolder;
@ -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.RangedUri;
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.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException;
@ -46,7 +45,6 @@ import com.google.android.exoplayer2.util.Util;
import android.os.SystemClock;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/**
@ -67,22 +65,21 @@ public class DefaultDashChunkSource implements DashChunkSource {
@Override
public DashChunkSource createDashChunkSource(Loader manifestLoader, DashManifest manifest,
int periodIndex, int adaptationSetIndex, TrackGroup trackGroup, int[] tracks,
int periodIndex, int adaptationSetIndex, TrackSelection trackSelection,
long elapsedRealtimeOffsetMs) {
FormatEvaluator adaptiveEvaluator = tracks.length > 1
FormatEvaluator adaptiveEvaluator = trackSelection.length > 1
? formatEvaluatorFactory.createFormatEvaluator() : null;
DataSource dataSource = dataSourceFactory.createDataSource();
return new DefaultDashChunkSource(manifestLoader, manifest, periodIndex, adaptationSetIndex,
trackGroup, tracks, dataSource, adaptiveEvaluator, elapsedRealtimeOffsetMs);
trackSelection, dataSource, adaptiveEvaluator, elapsedRealtimeOffsetMs);
}
}
private final Loader manifestLoader;
private final int adaptationSetIndex;
private final TrackGroup trackGroup;
private final TrackSelection trackSelection;
private final RepresentationHolder[] representationHolders;
private final Format[] enabledFormats;
private final boolean[] adaptiveFormatBlacklistFlags;
private final DataSource dataSource;
private final FormatEvaluator adaptiveFormatEvaluator;
@ -100,8 +97,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
* @param manifest The initial manifest.
* @param periodIndex The index of the period in the manifest.
* @param adaptationSetIndex The index of the adaptation set in the period.
* @param trackGroup The track group corresponding to the adaptation set.
* @param tracks The indices of the selected tracks within the adaptation set.
* @param trackSelection The track selection.
* @param dataSource A {@link DataSource} suitable for loading the media data.
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
@ -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.
*/
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) {
this.manifestLoader = manifestLoader;
this.manifest = manifest;
this.adaptationSetIndex = adaptationSetIndex;
this.trackGroup = trackGroup;
this.trackSelection = trackSelection;
this.dataSource = dataSource;
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
this.elapsedRealtimeOffsetUs = elapsedRealtimeOffsetMs * 1000;
@ -122,20 +118,14 @@ public class DefaultDashChunkSource implements DashChunkSource {
long periodDurationUs = getPeriodDurationUs(periodIndex);
List<Representation> representations = getRepresentations(periodIndex);
representationHolders = new RepresentationHolder[representations.size()];
for (int i = 0; i < representations.size(); i++) {
Representation representation = representations.get(i);
representationHolders = new RepresentationHolder[trackSelection.length];
for (int i = 0; i < trackSelection.length; i++) {
Representation representation = representations.get(trackSelection.getTrack(i));
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) {
adaptiveFormatEvaluator.enable(enabledFormats);
adaptiveFormatBlacklistFlags = new boolean[tracks.length];
adaptiveFormatEvaluator.enable(trackSelection.getFormats());
adaptiveFormatBlacklistFlags = new boolean[trackSelection.length];
} else {
adaptiveFormatBlacklistFlags = null;
}
@ -147,8 +137,8 @@ public class DefaultDashChunkSource implements DashChunkSource {
manifest = newManifest;
long periodDurationUs = getPeriodDurationUs(periodIndex);
List<Representation> representations = getRepresentations(periodIndex);
for (int i = 0; i < representationHolders.length; i++) {
Representation representation = representations.get(i);
for (int i = 0; i < trackSelection.length; i++) {
Representation representation = representations.get(trackSelection.getTrack(i));
representationHolders[i].updateRepresentation(periodDurationUs, representation);
}
} catch (BehindLiveWindowException e) {
@ -167,7 +157,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
@Override
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 adaptiveFormatEvaluator.evaluateQueueSize(playbackPositionUs, queue,
@ -181,12 +171,12 @@ public class DefaultDashChunkSource implements DashChunkSource {
}
if (evaluation.format == null || !lastChunkWasInitialization) {
if (enabledFormats.length > 1) {
if (trackSelection.length > 1) {
long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
adaptiveFormatEvaluator.evaluateFormat(bufferedDurationUs, adaptiveFormatBlacklistFlags,
evaluation);
} else {
evaluation.format = enabledFormats[0];
evaluation.format = trackSelection.getFormat(0);
evaluation.trigger = FormatEvaluator.TRIGGER_UNKNOWN;
evaluation.data = null;
}
@ -198,7 +188,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
}
RepresentationHolder representationHolder =
representationHolders[getTrackIndex(selectedFormat)];
representationHolders[trackSelection.indexOf(selectedFormat)];
Representation selectedRepresentation = representationHolder.representation;
DashSegmentIndex segmentIndex = representationHolder.segmentIndex;
@ -270,7 +260,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
if (chunk instanceof InitializationChunk) {
InitializationChunk initializationChunk = (InitializationChunk) chunk;
RepresentationHolder representationHolder =
representationHolders[getTrackIndex(initializationChunk.format)];
representationHolders[trackSelection.indexOf(initializationChunk.format)];
Format sampleFormat = initializationChunk.getSampleFormat();
if (sampleFormat != null) {
representationHolder.setSampleFormat(sampleFormat);
@ -295,7 +285,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
&& e instanceof InvalidResponseCodeException
&& ((InvalidResponseCodeException) e).responseCode == 404) {
RepresentationHolder representationHolder =
representationHolders[getTrackIndex(chunk.format)];
representationHolders[trackSelection.indexOf(chunk.format)];
int lastAvailableSegmentNum = representationHolder.getLastSegmentNum();
if (((MediaChunk) chunk).chunkIndex >= lastAvailableSegmentNum) {
missingLastSegment = true;
@ -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) {
long durationMs = manifest.getPeriodDuration(periodIndex);
if (durationMs == -1) {

View file

@ -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.TsExtractor;
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.ChunkHolder;
import com.google.android.exoplayer2.source.chunk.DataChunk;
@ -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.HlsPlaylistParser;
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.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException;
@ -47,7 +49,6 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Locale;
/**
@ -78,6 +79,7 @@ public class HlsChunkSource {
private final PtsTimestampAdjusterProvider timestampAdjusterProvider;
private final Variant[] variants;
private final HlsMediaPlaylist[] variantPlaylists;
private final TrackGroup trackGroup;
private final long[] variantLastPlaylistLoadTimesMs;
private boolean seenFirstExternalTrackSelection;
@ -92,7 +94,7 @@ public class HlsChunkSource {
private byte[] encryptionIv;
// Properties of enabled variants.
private Variant[] enabledVariants;
private TrackSelection trackSelection;
private long[] enabledVariantBlacklistTimes;
private boolean[] enabledVariantBlacklistFlags;
@ -117,11 +119,15 @@ public class HlsChunkSource {
evaluation = new Evaluation();
variantPlaylists = new HlsMediaPlaylist[variants.length];
variantLastPlaylistLoadTimesMs = new long[variants.length];
Format[] variantFormats = new Format[variants.length];
int[] initialTrackSelection = new int[variants.length];
for (int i = 0; i < variants.length; i++) {
variantFormats[i] = variants[i].format;
initialTrackSelection[i] = i;
}
selectTracksInternal(initialTrackSelection, false);
trackGroup = new TrackGroup(adaptiveFormatEvaluator != null, variantFormats);
selectTracksInternal(new TrackSelection(trackGroup, initialTrackSelection), false);
}
/**
@ -136,19 +142,8 @@ 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.
* <p>
* This method should only be called after the source has been prepared.
*
* @return True if this is a live playback. False otherwise.
*/
@ -158,8 +153,6 @@ public class HlsChunkSource {
/**
* 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.
*/
@ -168,43 +161,25 @@ public class HlsChunkSource {
}
/**
* Returns the number of tracks exposed by the source.
* <p>
* This method should only be called after the source has been prepared.
* Returns the track group exposed by the source.
*
* @return The number of tracks.
* @return The track group.
*/
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 format of the track.
*/
public Format getTrackFormat(int index) {
return variants[index].format;
public TrackGroup getTrackGroup() {
return trackGroup;
}
/**
* 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) {
selectTracksInternal(tracks, true);
public void selectTracks(TrackSelection trackSelection) {
selectTracksInternal(trackSelection, true);
}
/**
* Resets the source.
* <p>
* This method should only be called after the source has been prepared.
*/
public void reset() {
fatalError = null;
@ -224,10 +199,9 @@ public class HlsChunkSource {
* @param out A holder to populate.
*/
public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, ChunkHolder out) {
int previousChunkVariantIndex =
previous != null ? getVariantIndex(previous.format) : -1;
int previousChunkVariantIndex = previous != null ? trackGroup.indexOf(previous.format) : -1;
updateFormatEvaluation(previous, playbackPositionUs);
int newVariantIndex = getVariantIndex(evaluation.format);
int newVariantIndex = trackGroup.indexOf(evaluation.format);
boolean switchingVariant = previousChunkVariantIndex != newVariantIndex;
HlsMediaPlaylist mediaPlaylist = variantPlaylists[newVariantIndex];
if (mediaPlaylist == null) {
@ -443,7 +417,7 @@ public class HlsChunkSource {
InvalidResponseCodeException responseCodeException = (InvalidResponseCodeException) e;
int responseCode = responseCodeException.responseCode;
if (responseCode == 404 || responseCode == 410) {
int enabledVariantIndex = getEnabledVariantIndex(chunk.format);
int enabledVariantIndex = trackSelection.indexOf(chunk.format);
boolean alreadyBlacklisted = enabledVariantBlacklistFlags[enabledVariantIndex];
enabledVariantBlacklistFlags[enabledVariantIndex] = true;
enabledVariantBlacklistTimes[enabledVariantIndex] = SystemClock.elapsedRealtime();
@ -471,37 +445,21 @@ public class HlsChunkSource {
// Private methods.
private void selectTracksInternal(int[] tracks, boolean isExternal) {
private void selectTracksInternal(TrackSelection trackSelection, boolean isExternal) {
this.trackSelection = trackSelection;
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.
enabledVariantBlacklistTimes = new long[enabledVariants.length];
enabledVariantBlacklistFlags = new boolean[enabledVariants.length];
enabledVariantBlacklistTimes = new long[trackSelection.length];
enabledVariantBlacklistFlags = new boolean[trackSelection.length];
if (!isExternal) {
return;
}
if (enabledVariants.length > 1) {
Format[] formats = new Format[enabledVariants.length];
for (int i = 0; i < formats.length; i++) {
formats[i] = enabledVariants[i].format;
}
if (trackSelection.length > 1) {
// TODO[REFACTOR]: We need to disable this at some point.
Format[] formats = trackSelection.getFormats();
adaptiveFormatEvaluator.enable(formats);
if (!Util.contains(formats, evaluation.format)) {
evaluation.format = null;
@ -515,23 +473,23 @@ public class HlsChunkSource {
private void updateFormatEvaluation(HlsMediaChunk previous, long playbackPositionUs) {
clearStaleBlacklistedVariants();
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.
evaluation.format = variants[0].format;
return;
}
// 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]) {
evaluation.format = enabledVariants[i].format;
evaluation.format = trackSelection.getFormat(i);
return;
}
}
// Should never happen.
throw new IllegalStateException();
}
if (enabledVariants.length == 1) {
evaluation.format = enabledVariants[0].format;
if (trackSelection.length == 1) {
evaluation.format = trackSelection.getFormat(0);
return;
}
long bufferedDurationUs;
@ -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 static final class MediaPlaylistChunk extends DataChunk {

View file

@ -43,7 +43,6 @@ import com.google.android.exoplayer2.util.MimeTypes;
import android.net.Uri;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Pair;
import java.io.IOException;
import java.util.ArrayList;
@ -429,13 +428,13 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
}
// Get the subset of the new selections for the wrapper.
ArrayList<TrackSelection> newSelections = new ArrayList<>();
TrackGroupArray sampleStreamWrapperTrackGroups = sampleStreamWrapper.getTrackGroups();
int[] newSelectionOriginalIndices = new int[allNewSelections.size()];
for (int i = 0; i < allNewSelections.size(); i++) {
TrackSelection selection = allNewSelections.get(i);
Pair<HlsSampleStreamWrapper, Integer> sourceAndGroup = getSourceAndGroup(selection.group);
if (sourceAndGroup.first == sampleStreamWrapper) {
if (sampleStreamWrapperTrackGroups.indexOf(selection.group) != -1) {
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.
@ -452,18 +451,6 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
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) {
String codecs = variant.codecs;
if (TextUtils.isEmpty(codecs)) {

View file

@ -164,11 +164,11 @@ import java.util.List;
SampleStream[] newStreams = new SampleStream[newSelections.size()];
for (int i = 0; i < newStreams.length; i++) {
TrackSelection selection = newSelections.get(i);
int group = selection.group;
int group = trackGroups.indexOf(selection.group);
int[] tracks = selection.getTracks();
setTrackGroupEnabledState(group, true);
if (group == primaryTrackGroupIndex) {
chunkSource.selectTracks(tracks);
chunkSource.selectTracks(new TrackSelection(chunkSource.getTrackGroup(), tracks));
}
newStreams[i] = new SampleStreamImpl(group);
}
@ -526,8 +526,8 @@ import java.util.List;
}
}
// Calculate the number of tracks that will be exposed.
int chunkSourceTrackCount = chunkSource.getTrackCount();
TrackGroup chunkSourceTrackGroup = chunkSource.getTrackGroup();
int chunkSourceTrackCount = chunkSourceTrackGroup.length;
// Instantiate the necessary internal data-structures.
primaryTrackGroupIndex = -1;
@ -540,9 +540,9 @@ import java.util.List;
if (i == primaryExtractorTrackIndex) {
Format[] formats = new Format[chunkSourceTrackCount];
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;
} else {
Format trackFormat = null;

View file

@ -17,12 +17,10 @@ package com.google.android.exoplayer2.source.smoothstreaming;
import com.google.android.exoplayer2.C;
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.Track;
import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox;
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.ChunkExtractorWrapper;
import com.google.android.exoplayer2.source.chunk.ChunkHolder;
@ -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.smoothstreaming.manifest.SsManifest;
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.DataSpec;
import com.google.android.exoplayer2.upstream.Loader;
import android.net.Uri;
import android.text.TextUtils;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/**
@ -61,23 +58,21 @@ public class DefaultSsChunkSource implements SsChunkSource {
@Override
public SsChunkSource createChunkSource(Loader manifestLoader, SsManifest manifest,
int elementIndex, TrackGroup trackGroup, int[] tracks,
int elementIndex, TrackSelection trackSelection,
TrackEncryptionBox[] trackEncryptionBoxes) {
FormatEvaluator adaptiveEvaluator = tracks.length > 1
FormatEvaluator adaptiveEvaluator = trackSelection.length > 1
? formatEvaluatorFactory.createFormatEvaluator() : null;
DataSource dataSource = dataSourceFactory.createDataSource();
return new DefaultSsChunkSource(manifestLoader, manifest, elementIndex,
trackGroup, tracks, dataSource, adaptiveEvaluator,
trackEncryptionBoxes);
return new DefaultSsChunkSource(manifestLoader, manifest, elementIndex, trackSelection,
dataSource, adaptiveEvaluator, trackEncryptionBoxes);
}
}
private final Loader manifestLoader;
private final int elementIndex;
private final TrackGroup trackGroup;
private final TrackSelection trackSelection;
private final ChunkExtractorWrapper[] extractorWrappers;
private final Format[] enabledFormats;
private final boolean[] adaptiveFormatBlacklistFlags;
private final DataSource dataSource;
private final Evaluation evaluation;
@ -92,45 +87,40 @@ public class DefaultSsChunkSource implements SsChunkSource {
* @param manifestLoader The {@link Loader} being used to load manifests.
* @param manifest The initial manifest.
* @param elementIndex The index of the stream element in the manifest.
* @param trackGroup The track group corresponding to the stream element.
* @param tracks The indices of the selected tracks within the stream element.
* @param trackSelection The track selection.
* @param dataSource A {@link DataSource} suitable for loading the media data.
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
* @param trackEncryptionBoxes Track encryption boxes for the stream.
*/
public DefaultSsChunkSource(Loader manifestLoader, SsManifest manifest, int elementIndex,
TrackGroup trackGroup, int[] tracks, DataSource dataSource,
FormatEvaluator adaptiveFormatEvaluator, TrackEncryptionBox[] trackEncryptionBoxes) {
TrackSelection trackSelection, DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator,
TrackEncryptionBox[] trackEncryptionBoxes) {
this.manifestLoader = manifestLoader;
this.manifest = manifest;
this.elementIndex = elementIndex;
this.trackGroup = trackGroup;
this.trackSelection = trackSelection;
this.dataSource = dataSource;
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
this.evaluation = new Evaluation();
StreamElement streamElement = manifest.streamElements[elementIndex];
Format[] formats = streamElement.formats;
extractorWrappers = new ChunkExtractorWrapper[formats.length];
for (int j = 0; j < formats.length; j++) {
extractorWrappers = new ChunkExtractorWrapper[trackSelection.length];
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;
Track track = new Track(j, streamElement.type, streamElement.timescale, C.UNSET_TIME_US,
manifest.durationUs, formats[j], Track.TRANSFORMATION_NONE, trackEncryptionBoxes,
nalUnitLengthFieldLength, null, null);
Track track = new Track(manifestTrackIndex, streamElement.type, streamElement.timescale,
C.UNSET_TIME_US, manifest.durationUs, format, Track.TRANSFORMATION_NONE,
trackEncryptionBoxes, nalUnitLengthFieldLength, null, null);
FragmentedMp4Extractor extractor = new FragmentedMp4Extractor(
FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
| 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) {
adaptiveFormatEvaluator.enable(enabledFormats);
adaptiveFormatBlacklistFlags = new boolean[tracks.length];
adaptiveFormatEvaluator.enable(trackSelection.getFormats());
adaptiveFormatBlacklistFlags = new boolean[trackSelection.length];
} else {
adaptiveFormatBlacklistFlags = null;
}
@ -172,7 +162,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
@Override
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 adaptiveFormatEvaluator.evaluateQueueSize(playbackPositionUs, queue,
@ -185,12 +175,12 @@ public class DefaultSsChunkSource implements SsChunkSource {
return;
}
if (enabledFormats.length > 1) {
if (trackSelection.length > 1) {
long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
adaptiveFormatEvaluator.evaluateFormat(bufferedDurationUs, adaptiveFormatBlacklistFlags,
evaluation);
} else {
evaluation.format = enabledFormats[0];
evaluation.format = trackSelection.getFormat(0);
evaluation.trigger = FormatEvaluator.TRIGGER_UNKNOWN;
evaluation.data = null;
}
@ -229,10 +219,10 @@ public class DefaultSsChunkSource implements SsChunkSource {
long chunkEndTimeUs = chunkStartTimeUs + streamElement.getChunkDurationUs(chunkIndex);
int currentAbsoluteChunkIndex = chunkIndex + currentManifestChunkOffset;
int trackGroupTrackIndex = getTrackGroupTrackIndex(trackGroup, selectedFormat);
ChunkExtractorWrapper extractorWrapper = extractorWrappers[trackGroupTrackIndex];
int trackSelectionIndex = trackSelection.indexOf(selectedFormat);
ChunkExtractorWrapper extractorWrapper = extractorWrappers[trackSelectionIndex];
int manifestTrackIndex = getManifestTrackIndex(streamElement, selectedFormat);
int manifestTrackIndex = trackSelection.getTrack(trackSelectionIndex);
Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex);
out.chunk = newMediaChunk(selectedFormat, dataSource, uri, null, currentAbsoluteChunkIndex,
@ -259,37 +249,6 @@ public class DefaultSsChunkSource implements SsChunkSource {
// 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,
String cacheKey, int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs,
int formatEvaluatorTrigger, Object formatEvaluatorData,

View file

@ -16,9 +16,9 @@
package com.google.android.exoplayer2.source.smoothstreaming;
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.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Loader;
/**
@ -29,7 +29,7 @@ public interface SsChunkSource extends ChunkSource {
interface Factory {
SsChunkSource createChunkSource(Loader manifestLoader, SsManifest manifest, int elementIndex,
TrackGroup trackGroup, int[] tracks, TrackEncryptionBox[] trackEncryptionBoxes);
TrackSelection trackSelection, TrackEncryptionBox[] trackEncryptionBoxes);
}

View file

@ -354,11 +354,9 @@ public final class SsMediaSource implements MediaPeriod, MediaSource,
private ChunkSampleStream<SsChunkSource> buildSampleStream(TrackSelection selection,
long positionUs) {
int[] selectedTracks = selection.getTracks();
int streamElementIndex = trackGroupElementIndices[selection.group];
SsChunkSource chunkSource = chunkSourceFactory.createChunkSource(manifestLoader,
manifest, streamElementIndex, trackGroups.get(selection.group), selectedTracks,
trackEncryptionBoxes);
int streamElementIndex = trackGroupElementIndices[trackGroups.indexOf(selection.group)];
SsChunkSource chunkSource = chunkSourceFactory.createChunkSource(manifestLoader, manifest,
streamElementIndex, selection, trackEncryptionBoxes);
return new ChunkSampleStream<>(manifest.streamElements[streamElementIndex].type, chunkSource,
this, allocator, positionUs, minLoadableRetryCount, eventDispatcher);
}

View file

@ -176,17 +176,18 @@ public class DefaultTrackSelector extends MappingTrackSelector {
: RendererCapabilities.ADAPTIVE_SEAMLESS;
boolean allowMixedMimeTypes = allowMixedMimeAdaptiveness
&& (rendererCapabilities.supportsMixedMimeTypeAdaptation() & requiredAdaptiveSupport) != 0;
int largestAdaptiveGroup = -1;
TrackGroup largestAdaptiveGroup = null;
int[] largestAdaptiveGroupTracks = NO_TRACKS;
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);
if (adaptiveTracks.length > largestAdaptiveGroupTracks.length) {
largestAdaptiveGroup = i;
largestAdaptiveGroup = trackGroup;
largestAdaptiveGroupTracks = adaptiveTracks;
}
}
if (largestAdaptiveGroup != -1) {
if (largestAdaptiveGroup != null) {
return new TrackSelection(largestAdaptiveGroup, largestAdaptiveGroupTracks);
}
@ -197,7 +198,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupportedVideoTrack(trackFormatSupport[trackIndex], trackGroup.getFormat(trackIndex),
maxVideoWidth, maxVideoHeight)) {
return new TrackSelection(groupIndex, trackIndex);
return new TrackSelection(trackGroup, trackIndex);
}
}
}
@ -267,7 +268,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private static TrackSelection selectSmallestSupportedVideoTrack(TrackGroupArray trackGroups,
int[][] formatSupport) {
int smallestPixelCount = Integer.MAX_VALUE;
int trackGroupIndexSelection = -1;
TrackGroup trackGroupSelection = null;
int trackIndexSelection = -1;
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
TrackGroup trackGroup = trackGroups.get(groupIndex);
@ -279,13 +280,13 @@ public class DefaultTrackSelector extends MappingTrackSelector {
&& isSupportedVideoTrack(trackFormatSupport[trackIndex], format, Integer.MAX_VALUE,
Integer.MAX_VALUE)) {
smallestPixelCount = pixelCount;
trackGroupIndexSelection = groupIndex;
trackGroupSelection = trackGroup;
trackIndexSelection = trackIndex;
}
}
}
return trackIndexSelection != -1
? new TrackSelection(trackGroupIndexSelection, trackIndexSelection) : null;
return trackGroupSelection != null
? new TrackSelection(trackGroupSelection, trackIndexSelection) : null;
}
private static boolean isSupportedVideoTrack(int formatSupport, Format format, int maxVideoWidth,
@ -305,7 +306,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex])
&& formatHasLanguage(trackGroup.getFormat(trackIndex), preferredLanguage)) {
return new TrackSelection(groupIndex, trackIndex);
return new TrackSelection(trackGroup, trackIndex);
}
}
}
@ -318,7 +319,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private static TrackSelection selectTrackForTextRenderer(TrackGroupArray trackGroups,
int[][] formatSupport, String preferredLanguage) {
int firstForcedGroup = -1;
TrackGroup firstForcedGroup = null;
int firstForcedTrack = -1;
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
TrackGroup trackGroup = trackGroups.get(groupIndex);
@ -327,17 +328,17 @@ public class DefaultTrackSelector extends MappingTrackSelector {
if (isSupported(trackFormatSupport[trackIndex])
&& (trackGroup.getFormat(trackIndex).selectionFlags
& Format.SELECTION_FLAG_FORCED) != 0) {
if (firstForcedGroup == -1) {
firstForcedGroup = groupIndex;
if (firstForcedGroup == null) {
firstForcedGroup = trackGroup;
firstForcedTrack = trackIndex;
}
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.
@ -349,7 +350,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
int[] trackFormatSupport = formatSupport[groupIndex];
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex])) {
return new TrackSelection(groupIndex, trackIndex);
return new TrackSelection(trackGroup, trackIndex);
}
}
}

View file

@ -272,39 +272,26 @@ public abstract class MappingTrackSelector extends TrackSelector {
TrackGroupArray unassociatedTrackGroupArray = new TrackGroupArray(Arrays.copyOf(
rendererTrackGroups[rendererCapabilities.length], unassociatedTrackGroupCount));
TrackSelection[] rendererTrackSelections = selectTracks(rendererCapabilities,
rendererTrackGroupArrays, rendererFormatSupports);
TrackSelection[] trackSelections = selectTracks(rendererCapabilities, rendererTrackGroupArrays,
rendererFormatSupports);
// Apply track disabling and overriding.
for (int i = 0; i < rendererCapabilities.length; i++) {
if (rendererDisabledFlags.get(i)) {
rendererTrackSelections[i] = null;
trackSelections[i] = null;
} else {
Map<TrackGroupArray, TrackSelection> override = trackSelectionOverrides.get(i);
TrackSelection overrideSelection = override == null ? null
: override.get(rendererTrackGroupArrays[i]);
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.
TrackSelectionArray trackSelectionArray = new TrackSelectionArray(trackSelections);
TrackInfo trackInfo = new TrackInfo(rendererTrackGroupArrays, rendererTrackSelections,
TrackInfo trackInfo = new TrackInfo(rendererTrackGroupArrays, trackSelections,
mixedMimeTypeAdaptationSupport, rendererFormatSupports, unassociatedTrackGroupArray);
return Pair.<TrackSelectionArray, Object>create(trackSelectionArray, trackInfo);
}
@ -403,23 +390,6 @@ public abstract class MappingTrackSelector extends TrackSelector {
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) {
if (eventHandler != null) {
eventHandler.post(new Runnable() {

View file

@ -15,44 +15,93 @@
*/
package com.google.android.exoplayer2.trackselection;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Format.DecreasingBandwidthComparator;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.util.Assertions;
import 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 {
/**
* 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.
*/
public final int length;
private final int[] tracks;
private final Format[] formats;
// Lazily initialized 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
* 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);
this.group = group;
this.tracks = tracks;
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]);
}
}
/**
* Gets the index of the selected track at a given index in the selection.
* 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 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.
@ -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.
*/
@ -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.
* @return True if the index is included in the selection. False otherwise.
* @param trackIndex The index in the track group.
* @return The index in the selection, or -1 if the track with the specified index is not part of
* the selection.
*/
public boolean containsTrack(int trackIndex) {
public int indexOf(int trackIndex) {
for (int i = 0; i < length; i++) {
if (tracks[i] == trackIndex) {
return true;
return i;
}
}
return false;
return -1;
}
@Override
public int hashCode() {
if (hashCode == 0) {
hashCode = 31 * group + Arrays.hashCode(tracks);
hashCode = 31 * System.identityHashCode(group) + Arrays.hashCode(tracks);
}
return hashCode;
}

View file

@ -826,11 +826,13 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
Assertions.checkState(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].length == 1);
Assertions.checkState(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].length == 1);
TrackSelection[] selections = new TrackSelection[rendererCapabilities.length];
selections[VIDEO_RENDERER_INDEX] = new TrackSelection(0,
selections[VIDEO_RENDERER_INDEX] = new TrackSelection(
rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0),
getTrackIndices(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0),
rendererFormatSupports[VIDEO_RENDERER_INDEX][0], videoFormatIds,
canIncludeAdditionalVideoFormats));
selections[AUDIO_RENDERER_INDEX] = new TrackSelection(0,
selections[AUDIO_RENDERER_INDEX] = new TrackSelection(
rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0),
getTrackIndices(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0),
rendererFormatSupports[AUDIO_RENDERER_INDEX][0], audioFormatIds, false));
includedAdditionalVideoFormats =