diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java index df6b9f8af7..71620e7a3f 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/EventLogger.java @@ -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) { diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java b/demo/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java index cab1ba588d..98113228aa 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java @@ -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 tag = (Pair) view.getTag(); - int groupIndex = tag.first; + Pair tag = (Pair) 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); } } } diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index aaeb561ec7..aef3ebee04 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -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, diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java index 0928173645..9628aa6ff8 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java @@ -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; diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java index 92dfb55c8f..5e9b81523c 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java @@ -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 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 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 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(); - } - } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java b/library/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java index d760371a59..d83d6df047 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/TrackGroup.java @@ -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) { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java b/library/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java index b15909ab09..7f3169aa5b 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java @@ -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) { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java index 17a85d4993..7f295d106e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashChunkSource.java @@ -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); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java index efd977f229..2e6a0398b7 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java @@ -236,12 +236,10 @@ import java.util.List; private ChunkSampleStream 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); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index cc14c31e1a..fc615f5e1f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -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 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 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 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) { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index c91d90ccb9..d09cc8e982 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -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. - *

- * 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. - *

- * 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. - *

- * 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. - *

- * 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. - *

- * 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. - *

- * 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() { - private final Comparator 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 { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index d1a0d2f79a..0177acfcf3 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -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 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 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 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)) { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index bbce69708b..862ed55004 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -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; diff --git a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java index 1c8acef2d1..0fcac6958e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java @@ -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 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. - *

- * 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, diff --git a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java index b43961a347..5ab50b1c9f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsChunkSource.java @@ -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); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index cbe0f5c7e0..e33c4e76ab 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -354,11 +354,9 @@ public final class SsMediaSource implements MediaPeriod, MediaSource, private ChunkSampleStream 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); } diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index 280e93314f..4c2f0cbf8d 100644 --- a/library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -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); } } } diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java index 4d2d647c4e..7c596c1894 100644 --- a/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java +++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java @@ -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 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.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() { diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java index c1ff14487b..b22e1d881b 100644 --- a/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java +++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelection.java @@ -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; } diff --git a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java index 82b8e82b5d..63bcde61cc 100644 --- a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java +++ b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/gts/DashTest.java @@ -826,11 +826,13 @@ public final class DashTest extends ActivityInstrumentationTestCase2