diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 76f72c3181..787bd91eb5 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -9,6 +9,11 @@ track matching system Locale language) over technical track selection constraints (for example, preferred MIME type, or maximum channel count). + * Prohibit duplicate `TrackGroup`s in a `TrackGroupArray`. `TrackGroup`s + can always be made distinguishable by setting an `id` in the + `TrackGroup` constructor. This fixes a crash when resuming playback + after backgrounding the app with an active track override + ((#9718)[https://github.com/google/ExoPlayer/issues/9718]). * Extractors: * Fix inconsistency with spec in H.265 SPS nal units parsing ((#9719)[https://github.com/google/ExoPlayer/issues/9719]). diff --git a/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java b/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java index 6ddff84e38..9a7ff4d290 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/source/TrackGroupArray.java @@ -21,32 +21,38 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.Bundleable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.BundleableUtil; +import com.google.android.exoplayer2.util.Log; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Arrays; import java.util.List; /** An immutable array of {@link TrackGroup}s. */ public final class TrackGroupArray implements Bundleable { + private static final String TAG = "TrackGroupArray"; + /** The empty array. */ public static final TrackGroupArray EMPTY = new TrackGroupArray(); /** The number of groups in the array. Greater than or equal to zero. */ public final int length; - private final TrackGroup[] trackGroups; + private final ImmutableList trackGroups; // Lazily initialized hashcode. private int hashCode; - /** Construct a {@code TrackGroupArray} from an array of (possibly empty) trackGroups. */ + /** + * Construct a {@code TrackGroupArray} from an array of {@link TrackGroup TrackGroups}. + * + *

The groups must not contain duplicates. + */ public TrackGroupArray(TrackGroup... trackGroups) { - this.trackGroups = trackGroups; + this.trackGroups = ImmutableList.copyOf(trackGroups); this.length = trackGroups.length; + verifyCorrectness(); } /** @@ -56,7 +62,7 @@ public final class TrackGroupArray implements Bundleable { * @return The group. */ public TrackGroup get(int index) { - return trackGroups[index]; + return trackGroups.get(index); } /** @@ -65,16 +71,9 @@ public final class TrackGroupArray implements Bundleable { * @param group The group. * @return The index of the group, or {@link C#INDEX_UNSET} if no such group exists. */ - @SuppressWarnings("ReferenceEquality") public int indexOf(TrackGroup group) { - for (int i = 0; i < length; i++) { - // Suppressed reference equality warning because this is looking for the index of a specific - // TrackGroup object, not the index of a potential equal TrackGroup. - if (trackGroups[i] == group) { - return i; - } - } - return C.INDEX_UNSET; + int index = trackGroups.indexOf(group); + return index >= 0 ? index : C.INDEX_UNSET; } /** Returns whether this track group array is empty. */ @@ -85,7 +84,7 @@ public final class TrackGroupArray implements Bundleable { @Override public int hashCode() { if (hashCode == 0) { - hashCode = Arrays.hashCode(trackGroups); + hashCode = trackGroups.hashCode(); } return hashCode; } @@ -99,7 +98,7 @@ public final class TrackGroupArray implements Bundleable { return false; } TrackGroupArray other = (TrackGroupArray) obj; - return length == other.length && Arrays.equals(trackGroups, other.trackGroups); + return length == other.length && trackGroups.equals(other.trackGroups); } // Bundleable implementation. @@ -117,8 +116,7 @@ public final class TrackGroupArray implements Bundleable { public Bundle toBundle() { Bundle bundle = new Bundle(); bundle.putParcelableArrayList( - keyForField(FIELD_TRACK_GROUPS), - BundleableUtil.toBundleArrayList(Lists.newArrayList(trackGroups))); + keyForField(FIELD_TRACK_GROUPS), BundleableUtil.toBundleArrayList(trackGroups)); return bundle; } @@ -133,6 +131,20 @@ public final class TrackGroupArray implements Bundleable { return new TrackGroupArray(trackGroups.toArray(new TrackGroup[0])); }; + private void verifyCorrectness() { + for (int i = 0; i < trackGroups.size(); i++) { + for (int j = i + 1; j < trackGroups.size(); j++) { + if (trackGroups.get(i).equals(trackGroups.get(j))) { + Log.e( + TAG, + "", + new IllegalArgumentException( + "Multiple identical TrackGroups added to one TrackGroupArray.")); + } + } + } + } + private static String keyForField(@FieldNumber int field) { return Integer.toString(field, Character.MAX_RADIX); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java index 575b71fa2f..83d609b106 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java @@ -832,8 +832,6 @@ public final class DownloadHelper { * Runs the track selection for a given period index with the current parameters. The selected * tracks will be added to {@link #trackSelectionsByPeriodAndRenderer}. */ - // Intentional reference comparison of track group instances. - @SuppressWarnings("ReferenceEquality") @RequiresNonNull({ "trackGroupArrays", "trackSelectionsByPeriodAndRenderer", @@ -858,7 +856,7 @@ public final class DownloadHelper { boolean mergedWithExistingSelection = false; for (int j = 0; j < existingSelectionList.size(); j++) { ExoTrackSelection existingSelection = existingSelectionList.get(j); - if (existingSelection.getTrackGroup() == newSelection.getTrackGroup()) { + if (existingSelection.getTrackGroup().equals(newSelection.getTrackGroup())) { // Merge with existing selection. scratchSet.clear(); for (int k = 0; k < existingSelection.length(); k++) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java index 0287bb39b2..226e7b24ee 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java @@ -560,13 +560,10 @@ public abstract class MappingTrackSelector extends TrackSelector { for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { trackSupport[trackIndex] = mappedTrackInfo.getTrackSupport(rendererIndex, groupIndex, trackIndex); - // Suppressing reference equality warning because the track group stored in the track - // selection must point to the exact track group object to be considered part of it. - @SuppressWarnings("ReferenceEquality") boolean isTrackSelected = - (trackSelection != null) - && (trackSelection.getTrackGroup() == trackGroup) - && (trackSelection.indexOf(trackIndex) != C.INDEX_UNSET); + trackSelection != null + && trackSelection.getTrackGroup().equals(trackGroup) + && trackSelection.indexOf(trackIndex) != C.INDEX_UNSET; selected[trackIndex] = isTrackSelected; } @C.TrackType int trackGroupType = mappedTrackInfo.getRendererType(rendererIndex); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java index 22ac296652..565f3eb880 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java @@ -651,14 +651,11 @@ public class EventLogger implements AnalyticsListener { } } - // Suppressing reference equality warning because the track group stored in the track selection - // must point to the exact track group object to be considered part of it. - @SuppressWarnings("ReferenceEquality") private static String getTrackStatusString( @Nullable TrackSelection selection, TrackGroup group, int trackIndex) { return getTrackStatusString( selection != null - && selection.getTrackGroup() == group + && selection.getTrackGroup().equals(group) && selection.indexOf(trackIndex) != C.INDEX_UNSET); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index e7bf61f9ba..db5f4712e9 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -248,6 +248,8 @@ public final class DefaultTrackSelectorTest { @Test public void selectTracks_withEmptyTrackOverrideForDifferentTracks_hasNoEffect() throws ExoPlaybackException { + TrackGroup videoGroup0 = VIDEO_TRACK_GROUP.copyWithId("0"); + TrackGroup videoGroup1 = VIDEO_TRACK_GROUP.copyWithId("1"); trackSelector.setParameters( trackSelector .buildUponParameters() @@ -261,11 +263,16 @@ public final class DefaultTrackSelectorTest { TrackSelectorResult result = trackSelector.selectTracks( RENDERER_CAPABILITIES, - new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP, VIDEO_TRACK_GROUP), + new TrackGroupArray(videoGroup0, AUDIO_TRACK_GROUP, videoGroup1), periodId, TIMELINE); - assertThat(result.selections).asList().containsExactlyElementsIn(TRACK_SELECTIONS).inOrder(); + assertThat(result.selections) + .asList() + .containsExactly( + new FixedTrackSelection(videoGroup0, /* track= */ 0), + new FixedTrackSelection(AUDIO_TRACK_GROUP, /* track= */ 0)) + .inOrder(); assertThat(result.rendererConfigurations) .isEqualTo(new RendererConfiguration[] {DEFAULT, DEFAULT}); } @@ -364,17 +371,24 @@ public final class DefaultTrackSelectorTest { /** Tests that an override is not applied for a different set of available track groups. */ @Test public void selectTracksWithNullOverrideForDifferentTracks() throws ExoPlaybackException { + TrackGroup videoGroup0 = VIDEO_TRACK_GROUP.copyWithId("0"); + TrackGroup videoGroup1 = VIDEO_TRACK_GROUP.copyWithId("1"); trackSelector.setParameters( trackSelector .buildUponParameters() - .setSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP), null)); + .setSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP.copyWithId("2")), null)); TrackSelectorResult result = trackSelector.selectTracks( RENDERER_CAPABILITIES, - new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP, VIDEO_TRACK_GROUP), + new TrackGroupArray(videoGroup0, AUDIO_TRACK_GROUP, videoGroup1), periodId, TIMELINE); - assertSelections(result, TRACK_SELECTIONS); + assertThat(result.selections) + .asList() + .containsExactly( + new FixedTrackSelection(videoGroup0, /* track= */ 0), + new FixedTrackSelection(AUDIO_TRACK_GROUP, /* track= */ 0)) + .inOrder(); assertThat(result.rendererConfigurations) .isEqualTo(new RendererConfiguration[] {DEFAULT, DEFAULT}); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java index ab1d3c14cc..64f98549cd 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java @@ -95,10 +95,13 @@ public final class MappingTrackSelectorTest { @Test public void selectTracks_multipleVideoAndAudioTracks_mappedToSameRenderer() throws ExoPlaybackException { + TrackGroup videoGroup0 = VIDEO_TRACK_GROUP.copyWithId("0"); + TrackGroup videoGroup1 = VIDEO_TRACK_GROUP.copyWithId("1"); + TrackGroup audioGroup0 = AUDIO_TRACK_GROUP.copyWithId("0"); + TrackGroup audioGroup1 = AUDIO_TRACK_GROUP.copyWithId("1"); FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(); TrackGroupArray trackGroups = - new TrackGroupArray( - VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP, AUDIO_TRACK_GROUP, VIDEO_TRACK_GROUP); + new TrackGroupArray(videoGroup0, audioGroup0, audioGroup1, videoGroup1); RendererCapabilities[] rendererCapabilities = new RendererCapabilities[] { VIDEO_CAPABILITIES, AUDIO_CAPABILITIES, VIDEO_CAPABILITIES, AUDIO_CAPABILITIES @@ -106,16 +109,18 @@ public final class MappingTrackSelectorTest { trackSelector.selectTracks(rendererCapabilities, trackGroups, periodId, TIMELINE); - trackSelector.assertMappedTrackGroups(0, VIDEO_TRACK_GROUP, VIDEO_TRACK_GROUP); - trackSelector.assertMappedTrackGroups(1, AUDIO_TRACK_GROUP, AUDIO_TRACK_GROUP); + trackSelector.assertMappedTrackGroups(0, videoGroup0, videoGroup1); + trackSelector.assertMappedTrackGroups(1, audioGroup0, audioGroup1); } @Test public void selectTracks_multipleMetadataTracks_mappedToDifferentRenderers() throws ExoPlaybackException { + TrackGroup metadataGroup0 = METADATA_TRACK_GROUP.copyWithId("0"); + TrackGroup metadataGroup1 = METADATA_TRACK_GROUP.copyWithId("1"); FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(); TrackGroupArray trackGroups = - new TrackGroupArray(VIDEO_TRACK_GROUP, METADATA_TRACK_GROUP, METADATA_TRACK_GROUP); + new TrackGroupArray(VIDEO_TRACK_GROUP, metadataGroup0, metadataGroup1); RendererCapabilities[] rendererCapabilities = new RendererCapabilities[] { VIDEO_CAPABILITIES, METADATA_CAPABILITIES, METADATA_CAPABILITIES @@ -124,8 +129,8 @@ public final class MappingTrackSelectorTest { trackSelector.selectTracks(rendererCapabilities, trackGroups, periodId, TIMELINE); trackSelector.assertMappedTrackGroups(0, VIDEO_TRACK_GROUP); - trackSelector.assertMappedTrackGroups(1, METADATA_TRACK_GROUP); - trackSelector.assertMappedTrackGroups(2, METADATA_TRACK_GROUP); + trackSelector.assertMappedTrackGroups(1, metadataGroup0); + trackSelector.assertMappedTrackGroups(2, metadataGroup1); } private static TrackGroup buildTrackGroup(String sampleMimeType) { @@ -140,10 +145,10 @@ public final class MappingTrackSelectorTest { new int[] {C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_VIDEO}, new TrackGroupArray[] { new TrackGroupArray( - new TrackGroup(new Format.Builder().build()), - new TrackGroup(new Format.Builder().build())), + new TrackGroup("0", new Format.Builder().build()), + new TrackGroup("1", new Format.Builder().build())), new TrackGroupArray( - new TrackGroup(new Format.Builder().build(), new Format.Builder().build())) + new TrackGroup("2", new Format.Builder().build(), new Format.Builder().build())) }, new int[] { RendererCapabilities.ADAPTIVE_SEAMLESS, RendererCapabilities.ADAPTIVE_NOT_SUPPORTED