diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java index 22dc83818e..e3f075e562 100644 --- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java +++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastPlayer.java @@ -37,6 +37,7 @@ import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -66,6 +67,7 @@ import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.common.api.ResultCallback; import com.google.common.collect.ImmutableList; import java.util.List; +import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** @@ -101,7 +103,8 @@ public final class CastPlayer extends BasePlayer { COMMAND_GET_TIMELINE, COMMAND_GET_MEDIA_ITEMS_METADATA, COMMAND_SET_MEDIA_ITEMS_METADATA, - COMMAND_CHANGE_MEDIA_ITEMS) + COMMAND_CHANGE_MEDIA_ITEMS, + COMMAND_GET_TRACK_INFOS) .build(); public static final float MIN_SPEED_SUPPORTED = 0.5f; @@ -142,6 +145,7 @@ public final class CastPlayer extends BasePlayer { private CastTimeline currentTimeline; private TrackGroupArray currentTrackGroups; private TrackSelectionArray currentTrackSelection; + private TracksInfo currentTracksInfo; private Commands availableCommands; @Player.State private int playbackState; private int currentWindowIndex; @@ -219,6 +223,7 @@ public final class CastPlayer extends BasePlayer { currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE; currentTrackGroups = TrackGroupArray.EMPTY; currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY; + currentTracksInfo = TracksInfo.EMPTY; availableCommands = new Commands.Builder().addAll(PERMANENT_AVAILABLE_COMMANDS).build(); pendingSeekWindowIndex = C.INDEX_UNSET; pendingSeekPositionMs = C.TIME_UNSET; @@ -583,14 +588,19 @@ public final class CastPlayer extends BasePlayer { return false; } + @Override + public TrackGroupArray getCurrentTrackGroups() { + return currentTrackGroups; + } + @Override public TrackSelectionArray getCurrentTrackSelections() { return currentTrackSelection; } @Override - public TrackGroupArray getCurrentTrackGroups() { - return currentTrackGroups; + public TracksInfo getCurrentTracksInfo() { + return currentTracksInfo; } @Override @@ -871,6 +881,8 @@ public final class CastPlayer extends BasePlayer { listeners.queueEvent( Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(currentTrackGroups, currentTrackSelection)); + listeners.queueEvent( + Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksInfoChanged(currentTracksInfo)); } updateAvailableCommandsAndNotifyIfChanged(); listeners.flushEvents(); @@ -1032,6 +1044,7 @@ public final class CastPlayer extends BasePlayer { boolean hasChanged = !currentTrackGroups.isEmpty(); currentTrackGroups = TrackGroupArray.EMPTY; currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY; + currentTracksInfo = TracksInfo.EMPTY; return hasChanged; } long[] activeTrackIds = mediaStatus.getActiveTrackIds(); @@ -1040,7 +1053,9 @@ public final class CastPlayer extends BasePlayer { } TrackGroup[] trackGroups = new TrackGroup[castMediaTracks.size()]; - TrackSelection[] trackSelections = new TrackSelection[RENDERER_COUNT]; + @NullableType TrackSelection[] trackSelections = new TrackSelection[RENDERER_COUNT]; + TracksInfo.TrackGroupInfo[] trackGroupInfos = + new TracksInfo.TrackGroupInfo[castMediaTracks.size()]; for (int i = 0; i < castMediaTracks.size(); i++) { MediaTrack mediaTrack = castMediaTracks.get(i); trackGroups[i] = new TrackGroup(CastUtils.mediaTrackToFormat(mediaTrack)); @@ -1048,19 +1063,28 @@ public final class CastPlayer extends BasePlayer { long id = mediaTrack.getId(); @C.TrackType int trackType = MimeTypes.getTrackType(mediaTrack.getContentType()); int rendererIndex = getRendererIndexForTrackType(trackType); - if (isTrackActive(id, activeTrackIds) - && rendererIndex != C.INDEX_UNSET - && trackSelections[rendererIndex] == null) { + boolean supported = rendererIndex != C.INDEX_UNSET; + boolean selected = + isTrackActive(id, activeTrackIds) && supported && trackSelections[rendererIndex] == null; + if (selected) { trackSelections[rendererIndex] = new CastTrackSelection(trackGroups[i]); } + @C.FormatSupport + int[] trackSupport = new int[] {supported ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_TYPE}; + final boolean[] trackSelected = new boolean[] {selected}; + trackGroupInfos[i] = + new TracksInfo.TrackGroupInfo(trackGroups[i], trackSupport, trackType, trackSelected); } TrackGroupArray newTrackGroups = new TrackGroupArray(trackGroups); TrackSelectionArray newTrackSelections = new TrackSelectionArray(trackSelections); + TracksInfo newTracksInfo = new TracksInfo(ImmutableList.copyOf(trackGroupInfos)); if (!newTrackGroups.equals(currentTrackGroups) - || !newTrackSelections.equals(currentTrackSelection)) { - currentTrackSelection = new TrackSelectionArray(trackSelections); - currentTrackGroups = new TrackGroupArray(trackGroups); + || !newTrackSelections.equals(currentTrackSelection) + || !newTracksInfo.equals(currentTracksInfo)) { + currentTrackSelection = newTrackSelections; + currentTrackGroups = newTrackGroups; + currentTracksInfo = newTracksInfo; return true; } return false; diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java index 66612a17a2..a958449b2a 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.testutil.StubExoPlayer; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; @@ -240,6 +241,11 @@ import com.google.android.exoplayer2.util.ListenerSet; return new TrackSelectionArray(); } + @Override + public TracksInfo getCurrentTracksInfo() { + return TracksInfo.EMPTY; + } + @Override public TrackSelectionParameters getTrackSelectionParameters() { return TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/ForwardingPlayer.java b/library/common/src/main/java/com/google/android/exoplayer2/ForwardingPlayer.java index 4fa547d2dc..c45c48495b 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/ForwardingPlayer.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/ForwardingPlayer.java @@ -360,6 +360,11 @@ public class ForwardingPlayer implements Player { return player.getCurrentTrackSelections(); } + @Override + public TracksInfo getCurrentTracksInfo() { + return player.getCurrentTracksInfo(); + } + @Override public TrackSelectionParameters getTrackSelectionParameters() { return player.getTrackSelectionParameters(); @@ -645,6 +650,11 @@ public class ForwardingPlayer implements Player { eventListener.onTracksChanged(trackGroups, trackSelections); } + @Override + public void onTracksInfoChanged(TracksInfo tracksInfo) { + eventListener.onTracksInfoChanged(tracksInfo); + } + @Override public void onMediaMetadataChanged(MediaMetadata mediaMetadata) { eventListener.onMediaMetadataChanged(mediaMetadata); diff --git a/library/common/src/main/java/com/google/android/exoplayer2/Player.java b/library/common/src/main/java/com/google/android/exoplayer2/Player.java index 367194350b..96f6e12487 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/Player.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/Player.java @@ -57,11 +57,8 @@ import java.util.List; * */ public interface Player { @@ -122,10 +119,22 @@ public interface Player { * concrete implementation may include null elements if it has a fixed number of renderer * components, wishes to report a TrackSelection for each of them, and has one or more * renderer components that is not assigned any selected tracks. + * @deprecated Use {@link #onTracksInfoChanged(TracksInfo)} instead. */ + @Deprecated default void onTracksChanged( TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {} + /** + * Called when the available or selected tracks change. + * + *

{@link #onEvents(Player, Events)} will also be called to report this event along with + * other events that happen in the same {@link Looper} message queue iteration. + * + * @param tracksInfo The available tracks information. Never null, but may be of length zero. + */ + default void onTracksInfoChanged(TracksInfo tracksInfo) {} + /** * Called when the combined {@link MediaMetadata} changes. * @@ -693,6 +702,7 @@ public interface Player { COMMAND_SET_VIDEO_SURFACE, COMMAND_GET_TEXT, COMMAND_SET_TRACK_SELECTION_PARAMETERS, + COMMAND_GET_TRACK_INFOS, }; private final FlagSet.Builder flagsBuilder; @@ -923,8 +933,7 @@ public interface Player { @Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) {} @Override - default void onTracksChanged( - TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {} + default void onTracksInfoChanged(TracksInfo tracksInfo) {} @Override default void onIsLoadingChanged(boolean isLoading) {} @@ -1278,7 +1287,7 @@ public interface Player { int EVENT_TIMELINE_CHANGED = 0; /** {@link #getCurrentMediaItem()} changed or the player started repeating the current item. */ int EVENT_MEDIA_ITEM_TRANSITION = 1; - /** {@link #getCurrentTrackGroups()} or {@link #getCurrentTrackSelections()} changed. */ + /** {@link #getCurrentTracksInfo()} changed. */ int EVENT_TRACKS_CHANGED = 2; /** {@link #isLoading()} ()} changed. */ int EVENT_IS_LOADING_CHANGED = 3; @@ -1331,8 +1340,8 @@ public interface Player { * #COMMAND_CHANGE_MEDIA_ITEMS}, {@link #COMMAND_GET_AUDIO_ATTRIBUTES}, {@link * #COMMAND_GET_VOLUME}, {@link #COMMAND_GET_DEVICE_VOLUME}, {@link #COMMAND_SET_VOLUME}, {@link * #COMMAND_SET_DEVICE_VOLUME}, {@link #COMMAND_ADJUST_DEVICE_VOLUME}, {@link - * #COMMAND_SET_VIDEO_SURFACE}, {@link #COMMAND_GET_TEXT} or {@link - * #COMMAND_SET_TRACK_SELECTION_PARAMETERS}. + * #COMMAND_SET_VIDEO_SURFACE}, {@link #COMMAND_GET_TEXT}, {@link + * #COMMAND_SET_TRACK_SELECTION_PARAMETERS} or {@link #COMMAND_GET_TRACK_INFOS}. */ @Documented @Retention(RetentionPolicy.SOURCE) @@ -1366,6 +1375,7 @@ public interface Player { COMMAND_SET_VIDEO_SURFACE, COMMAND_GET_TEXT, COMMAND_SET_TRACK_SELECTION_PARAMETERS, + COMMAND_GET_TRACK_INFOS, }) @interface Command {} /** Command to start, pause or resume playback. */ @@ -1424,6 +1434,8 @@ public interface Player { int COMMAND_GET_TEXT = 27; /** Command to set the player's track selection parameters. */ int COMMAND_SET_TRACK_SELECTION_PARAMETERS = 28; + /** Command to get track infos. */ + int COMMAND_GET_TRACK_INFOS = 29; /** Represents an invalid {@link Command}. */ int COMMAND_INVALID = -1; @@ -1962,7 +1974,9 @@ public interface Player { * Returns the available track groups. * * @see Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray) + * @deprecated Use {@link #getCurrentTracksInfo()}. */ + @Deprecated TrackGroupArray getCurrentTrackGroups(); /** @@ -1973,10 +1987,23 @@ public interface Player { * components that is not assigned any selected tracks. * * @see Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray) + * @deprecated Use {@link #getCurrentTracksInfo()}. */ + @Deprecated TrackSelectionArray getCurrentTrackSelections(); - /** Returns the parameters constraining the track selection. */ + /** + * Returns the available tracks, as well as the tracks' support, type, and selection status. + * + * @see Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray) + */ + TracksInfo getCurrentTracksInfo(); + + /** + * Returns the parameters constraining the track selection. + * + * @see Listener#onTrackSelectionParametersChanged} + */ TrackSelectionParameters getTrackSelectionParameters(); /** diff --git a/library/common/src/main/java/com/google/android/exoplayer2/TracksInfo.java b/library/common/src/main/java/com/google/android/exoplayer2/TracksInfo.java new file mode 100644 index 0000000000..3f587ed8d0 --- /dev/null +++ b/library/common/src/main/java/com/google/android/exoplayer2/TracksInfo.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import static com.google.android.exoplayer2.util.Assertions.checkArgument; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.android.exoplayer2.util.BundleableUtil.fromBundleNullableList; +import static com.google.android.exoplayer2.util.BundleableUtil.fromNullableBundle; +import static com.google.android.exoplayer2.util.BundleableUtil.toBundleArrayList; + +import android.os.Bundle; +import androidx.annotation.IntDef; +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.source.TrackGroup; +import com.google.android.exoplayer2.trackselection.TrackSelectionParameters; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.google.common.primitives.Booleans; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.List; + +/** Immutable information ({@link TrackGroupInfo}) about tracks. */ +public final class TracksInfo implements Bundleable { + /** + * Information about tracks in a {@link TrackGroup}: their {@link C.TrackType}, if their format is + * supported by the player and if they are selected for playback. + */ + public static final class TrackGroupInfo implements Bundleable { + private final TrackGroup trackGroup; + @C.FormatSupport private final int[] trackSupport; + private final @C.TrackType int trackType; + private final boolean[] trackSelected; + + /** + * Constructs a TrackGroupInfo. + * + * @param trackGroup The {@link TrackGroup} described. + * @param trackSupport The {@link C.FormatSupport} of each track in the {@code trackGroup}. + * @param trackType The {@link C.TrackType} of the tracks in the {@code trackGroup}. + * @param tracksSelected Whether a track is selected for each track in {@code trackGroup}. + */ + public TrackGroupInfo( + TrackGroup trackGroup, + @C.FormatSupport int[] trackSupport, + @C.TrackType int trackType, + boolean[] tracksSelected) { + int length = trackGroup.length; + checkArgument(length == trackSupport.length && length == tracksSelected.length); + this.trackGroup = trackGroup; + this.trackSupport = trackSupport.clone(); + this.trackType = trackType; + this.trackSelected = tracksSelected.clone(); + } + + /** Returns the {@link TrackGroup} described by this {@code TrackGroupInfo}. */ + public TrackGroup getTrackGroup() { + return trackGroup; + } + + /** + * Returns the level of support for a track in a {@link TrackGroup}. + * + * @param trackIndex The index of the track in the {@link TrackGroup}. + * @return The {@link C.FormatSupport} of the track. + */ + @C.FormatSupport + public int getTrackSupport(int trackIndex) { + return trackSupport[trackIndex]; + } + + /** + * Returns if a track in a {@link TrackGroup} is supported for playback. + * + * @param trackIndex The index of the track in the {@link TrackGroup}. + * @return True if the track's format can be played, false otherwise. + */ + public boolean isTrackSupported(int trackIndex) { + return trackSupport[trackIndex] == C.FORMAT_HANDLED; + } + + /** Returns if at least one track in a {@link TrackGroup} is selected for playback. */ + public boolean isSelected() { + return Booleans.contains(trackSelected, true); + } + + /** Returns if at least one track in a {@link TrackGroup} is supported. */ + public boolean isSupported() { + for (int i = 0; i < trackSupport.length; i++) { + if (isTrackSupported(i)) { + return true; + } + } + return false; + } + + /** + * Returns if a track in a {@link TrackGroup} is selected for playback. + * + *

Multiple tracks of a track group may be selected, in which case the the active one + * (currently playing) is undefined. This is common in adaptive streaming, where multiple tracks + * of different quality are selected and the active one changes depending on the network and the + * {@link TrackSelectionParameters}. + * + * @param trackIndex The index of the track in the {@link TrackGroup}. + * @return true if the track is selected, false otherwise. + */ + public boolean isTrackSelected(int trackIndex) { + return trackSelected[trackIndex]; + } + + /** + * Returns the {@link C.TrackType} of the tracks in the {@link TrackGroup}. Tracks in a group + * are all of the same type. + */ + public @C.TrackType int getTrackType() { + return trackType; + } + + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + TrackGroupInfo that = (TrackGroupInfo) other; + return trackType == that.trackType + && trackGroup.equals(that.trackGroup) + && Arrays.equals(trackSupport, that.trackSupport) + && Arrays.equals(trackSelected, that.trackSelected); + } + + @Override + public int hashCode() { + int result = trackGroup.hashCode(); + result = 31 * result + Arrays.hashCode(trackSupport); + result = 31 * result + trackType; + result = 31 * result + Arrays.hashCode(trackSelected); + return result; + } + + // Bundleable implementation. + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_TRACK_GROUP, + FIELD_TRACK_SUPPORT, + FIELD_TRACK_TYPE, + FIELD_TRACK_SELECTED, + }) + private @interface FieldNumber {} + + private static final int FIELD_TRACK_GROUP = 0; + private static final int FIELD_TRACK_SUPPORT = 1; + private static final int FIELD_TRACK_TYPE = 2; + private static final int FIELD_TRACK_SELECTED = 3; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putBundle(keyForField(FIELD_TRACK_GROUP), trackGroup.toBundle()); + bundle.putIntArray(keyForField(FIELD_TRACK_SUPPORT), trackSupport); + bundle.putInt(keyForField(FIELD_TRACK_TYPE), trackType); + bundle.putBooleanArray(keyForField(FIELD_TRACK_SELECTED), trackSelected); + return bundle; + } + + /** Object that can restores a {@code TracksInfo} from a {@link Bundle}. */ + public static final Creator CREATOR = + bundle -> { + TrackGroup trackGroup = + fromNullableBundle( + TrackGroup.CREATOR, bundle.getBundle(keyForField(FIELD_TRACK_GROUP))); + checkNotNull(trackGroup); // Can't create a trackGroup info without a trackGroup + @C.FormatSupport + final int[] trackSupport = + MoreObjects.firstNonNull( + bundle.getIntArray(keyForField(FIELD_TRACK_SUPPORT)), new int[trackGroup.length]); + @C.TrackType + int trackType = bundle.getInt(keyForField(FIELD_TRACK_TYPE), C.TRACK_TYPE_UNKNOWN); + boolean[] selected = + MoreObjects.firstNonNull( + bundle.getBooleanArray(keyForField(FIELD_TRACK_SELECTED)), + new boolean[trackGroup.length]); + return new TrackGroupInfo(trackGroup, trackSupport, trackType, selected); + }; + + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } + } + + private final ImmutableList trackGroupInfos; + + /** An empty {@code TrackInfo} containing no {@link TrackGroupInfo}. */ + public static final TracksInfo EMPTY = new TracksInfo(ImmutableList.of()); + + /** Constructs {@code TracksInfo} from the provided {@link TrackGroupInfo}. */ + public TracksInfo(List trackGroupInfos) { + this.trackGroupInfos = ImmutableList.copyOf(trackGroupInfos); + } + + /** Returns the {@link TrackGroupInfo TrackGroupInfos}, describing each {@link TrackGroup}. */ + public ImmutableList getTrackGroupInfos() { + return trackGroupInfos; + } + + /** Returns if there is at least one track of type {@code trackType} but none are supported. */ + public boolean isTypeSupportedOrEmpty(@C.TrackType int trackType) { + boolean supported = true; + for (int i = 0; i < trackGroupInfos.size(); i++) { + if (trackGroupInfos.get(i).trackType == trackType) { + if (trackGroupInfos.get(i).isSupported()) { + return true; + } else { + supported = false; + } + } + } + return supported; + } + + /** Returns if at least one track of the type {@code trackType} is selected for playback. */ + public boolean isTypeSelected(@C.TrackType int trackType) { + for (int i = 0; i < trackGroupInfos.size(); i++) { + TrackGroupInfo trackGroupInfo = trackGroupInfos.get(i); + if (trackGroupInfo.isSelected() && trackGroupInfo.getTrackType() == trackType) { + return true; + } + } + return false; + } + + @Override + public boolean equals(@Nullable Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + TracksInfo that = (TracksInfo) other; + return trackGroupInfos.equals(that.trackGroupInfos); + } + + @Override + public int hashCode() { + return trackGroupInfos.hashCode(); + } + // Bundleable implementation. + + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FIELD_TRACK_GROUP_INFOS, + }) + private @interface FieldNumber {} + + private static final int FIELD_TRACK_GROUP_INFOS = 0; + + @Override + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList( + keyForField(FIELD_TRACK_GROUP_INFOS), toBundleArrayList(trackGroupInfos)); + return bundle; + } + + /** Object that can restore a {@code TracksInfo} from a {@link Bundle}. */ + public static final Creator CREATOR = + bundle -> { + List trackGroupInfos = + fromBundleNullableList( + TrackGroupInfo.CREATOR, + bundle.getParcelableArrayList(keyForField(FIELD_TRACK_GROUP_INFOS)), + /* defaultValue= */ ImmutableList.of()); + return new TracksInfo(trackGroupInfos); + }; + + private static String keyForField(@FieldNumber int field) { + return Integer.toString(field, Character.MAX_RADIX); + } +} diff --git a/library/common/src/test/java/com/google/android/exoplayer2/TracksInfoTest.java b/library/common/src/test/java/com/google/android/exoplayer2/TracksInfoTest.java new file mode 100644 index 0000000000..3e285c9b35 --- /dev/null +++ b/library/common/src/test/java/com/google/android/exoplayer2/TracksInfoTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.source.TrackGroup; +import com.google.common.collect.ImmutableList; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit test for {@link TracksInfo}. */ +@RunWith(AndroidJUnit4.class) +public class TracksInfoTest { + + @Test + public void roundTripViaBundle_ofEmptyTracksInfo_yieldsEqualInstance() { + TracksInfo before = TracksInfo.EMPTY; + TracksInfo after = TracksInfo.CREATOR.fromBundle(before.toBundle()); + assertThat(after).isEqualTo(before); + } + + @Test + public void roundTripViaBundle_ofTracksInfo_yieldsEqualInstance() { + TracksInfo before = + new TracksInfo( + ImmutableList.of( + new TracksInfo.TrackGroupInfo( + new TrackGroup(new Format.Builder().build()), + new int[] {C.FORMAT_EXCEEDS_CAPABILITIES}, + C.TRACK_TYPE_AUDIO, + new boolean[] {true}), + new TracksInfo.TrackGroupInfo( + new TrackGroup(new Format.Builder().build(), new Format.Builder().build()), + new int[] {C.FORMAT_UNSUPPORTED_DRM, C.FORMAT_UNSUPPORTED_TYPE}, + C.TRACK_TYPE_VIDEO, + new boolean[] {false, true}))); + TracksInfo after = TracksInfo.CREATOR.fromBundle(before.toBundle()); + assertThat(after).isEqualTo(before); + } + + @Test + public void tracksInfoGetters_withoutTrack_returnExpectedValues() { + TracksInfo tracksInfo = new TracksInfo(ImmutableList.of()); + + assertThat(tracksInfo.isTypeSupportedOrEmpty(C.TRACK_TYPE_AUDIO)).isTrue(); + assertThat(tracksInfo.isTypeSelected(C.TRACK_TYPE_AUDIO)).isFalse(); + ImmutableList trackGroupInfos = tracksInfo.getTrackGroupInfos(); + assertThat(trackGroupInfos).isEmpty(); + } + + @Test + public void tracksInfo_emptyStaticInstance_isEmpty() { + TracksInfo tracksInfo = TracksInfo.EMPTY; + + assertThat(tracksInfo.getTrackGroupInfos()).isEmpty(); + assertThat(tracksInfo).isEqualTo(new TracksInfo(ImmutableList.of())); + } + + @Test + public void tracksInfoGetters_ofComplexTracksInfo_returnExpectedValues() { + TracksInfo.TrackGroupInfo trackGroupInfo0 = + new TracksInfo.TrackGroupInfo( + new TrackGroup(new Format.Builder().build()), + new int[] {C.FORMAT_EXCEEDS_CAPABILITIES}, + C.TRACK_TYPE_AUDIO, + /* tracksSelected= */ new boolean[] {false}); + TracksInfo.TrackGroupInfo trackGroupInfo1 = + new TracksInfo.TrackGroupInfo( + new TrackGroup(new Format.Builder().build(), new Format.Builder().build()), + new int[] {C.FORMAT_UNSUPPORTED_DRM, C.FORMAT_HANDLED}, + C.TRACK_TYPE_VIDEO, + /* tracksSelected= */ new boolean[] {false, true}); + TracksInfo tracksInfo = new TracksInfo(ImmutableList.of(trackGroupInfo0, trackGroupInfo1)); + + assertThat(tracksInfo.isTypeSupportedOrEmpty(C.TRACK_TYPE_AUDIO)).isFalse(); + assertThat(tracksInfo.isTypeSupportedOrEmpty(C.TRACK_TYPE_VIDEO)).isTrue(); + assertThat(tracksInfo.isTypeSupportedOrEmpty(C.TRACK_TYPE_TEXT)).isTrue(); + assertThat(tracksInfo.isTypeSelected(C.TRACK_TYPE_AUDIO)).isFalse(); + assertThat(tracksInfo.isTypeSelected(C.TRACK_TYPE_VIDEO)).isTrue(); + ImmutableList trackGroupInfos = tracksInfo.getTrackGroupInfos(); + assertThat(trackGroupInfos).hasSize(2); + assertThat(trackGroupInfos.get(0)).isSameInstanceAs(trackGroupInfo0); + assertThat(trackGroupInfos.get(1)).isSameInstanceAs(trackGroupInfo1); + assertThat(trackGroupInfos.get(0).isTrackSupported(0)).isFalse(); + assertThat(trackGroupInfos.get(1).isTrackSupported(0)).isFalse(); + assertThat(trackGroupInfos.get(1).isTrackSupported(1)).isTrue(); + assertThat(trackGroupInfos.get(0).getTrackSupport(0)).isEqualTo(C.FORMAT_EXCEEDS_CAPABILITIES); + assertThat(trackGroupInfos.get(1).getTrackSupport(0)).isEqualTo(C.FORMAT_UNSUPPORTED_DRM); + assertThat(trackGroupInfos.get(1).getTrackSupport(1)).isEqualTo(C.FORMAT_HANDLED); + assertThat(trackGroupInfos.get(0).isTrackSelected(0)).isFalse(); + assertThat(trackGroupInfos.get(1).isTrackSelected(0)).isFalse(); + assertThat(trackGroupInfos.get(1).isTrackSelected(1)).isTrue(); + assertThat(trackGroupInfos.get(0).getTrackType()).isEqualTo(C.TRACK_TYPE_AUDIO); + assertThat(trackGroupInfos.get(1).getTrackType()).isEqualTo(C.TRACK_TYPE_VIDEO); + } +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index c91d0b746a..d3d041ce93 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -196,6 +196,7 @@ import java.util.concurrent.CopyOnWriteArraySet; new TrackSelectorResult( new RendererConfiguration[renderers.length], new ExoTrackSelection[renderers.length], + TracksInfo.EMPTY, /* info= */ null); period = new Timeline.Period(); permanentAvailableCommands = @@ -210,7 +211,8 @@ import java.util.concurrent.CopyOnWriteArraySet; COMMAND_GET_TIMELINE, COMMAND_GET_MEDIA_ITEMS_METADATA, COMMAND_SET_MEDIA_ITEMS_METADATA, - COMMAND_CHANGE_MEDIA_ITEMS) + COMMAND_CHANGE_MEDIA_ITEMS, + COMMAND_GET_TRACK_INFOS) .addIf(COMMAND_SET_TRACK_SELECTION_PARAMETERS, trackSelector.isSetParametersSupported()) .addAll(additionalPermanentAvailableCommands) .build(); @@ -951,6 +953,11 @@ import java.util.concurrent.CopyOnWriteArraySet; return new TrackSelectionArray(playbackInfo.trackSelectorResult.selections); } + @Override + public TracksInfo getCurrentTracksInfo() { + return playbackInfo.trackSelectorResult.tracksInfo; + } + @Override public TrackSelectionParameters getTrackSelectionParameters() { return trackSelector.getParameters(); @@ -1274,6 +1281,9 @@ import java.util.concurrent.CopyOnWriteArraySet; listeners.queueEvent( Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(newPlaybackInfo.trackGroups, newSelection)); + listeners.queueEvent( + Player.EVENT_TRACKS_CHANGED, + listener -> listener.onTracksInfoChanged(newPlaybackInfo.trackSelectorResult.tracksInfo)); } if (metadataChanged) { final MediaMetadata finalMediaMetadata = mediaMetadata; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index 00a5d66be0..eb5dc12060 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -1456,6 +1456,12 @@ public class SimpleExoPlayer extends BasePlayer return player.getCurrentTrackSelections(); } + @Override + public TracksInfo getCurrentTracksInfo() { + verifyApplicationThread(); + return player.getCurrentTracksInfo(); + } + @Override public TrackSelectionParameters getTrackSelectionParameters() { verifyApplicationThread(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java index 1e908e41e4..4d1bcd457f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java @@ -35,6 +35,7 @@ import com.google.android.exoplayer2.Player.PlaybackSuppressionReason; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; import com.google.android.exoplayer2.Timeline.Window; +import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.analytics.AnalyticsListener.EventTime; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioRendererEventListener; @@ -586,6 +587,7 @@ public class AnalyticsCollector } @Override + @SuppressWarnings("deprecation") // Implementing and calling deprecate listener method public final void onTracksChanged( TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); @@ -595,6 +597,15 @@ public class AnalyticsCollector listener -> listener.onTracksChanged(eventTime, trackGroups, trackSelections)); } + @Override + public void onTracksInfoChanged(TracksInfo tracksInfo) { + EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); + sendEvent( + eventTime, + AnalyticsListener.EVENT_TRACKS_CHANGED, + listener -> listener.onTracksInfoChanged(eventTime, tracksInfo)); + } + @SuppressWarnings("deprecation") // Calling deprecated listener method. @Override public final void onIsLoadingChanged(boolean isLoading) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java index 0fca47cf56..9887f397ea 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java @@ -36,6 +36,7 @@ import com.google.android.exoplayer2.Player.DiscontinuityReason; import com.google.android.exoplayer2.Player.PlaybackSuppressionReason; import com.google.android.exoplayer2.Player.TimelineChangeReason; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioSink; import com.google.android.exoplayer2.decoder.DecoderCounters; @@ -221,7 +222,8 @@ public interface AnalyticsListener { */ int EVENT_MEDIA_ITEM_TRANSITION = Player.EVENT_MEDIA_ITEM_TRANSITION; /** - * {@link Player#getCurrentTrackGroups()} or {@link Player#getCurrentTrackSelections()} changed. + * {@link Player#getCurrentTracksInfo()}, {@link Player#getCurrentTrackGroups()} or {@link + * Player#getCurrentTrackSelections()} changed. */ int EVENT_TRACKS_CHANGED = Player.EVENT_TRACKS_CHANGED; /** {@link Player#isLoading()} ()} changed. */ @@ -674,10 +676,20 @@ public interface AnalyticsListener { * @param eventTime The event time. * @param trackGroups The available tracks. May be empty. * @param trackSelections The track selections for each renderer. May contain null elements. + * @deprecated Use {@link #onTracksInfoChanged}. */ + @Deprecated default void onTracksChanged( EventTime eventTime, TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {} + /** + * Called when the available or selected tracks change. + * + * @param eventTime The event time. + * @param tracksInfo The available tracks information. Never null, but may be of length zero. + */ + default void onTracksInfoChanged(EventTime eventTime, TracksInfo tracksInfo) {} + /** * Called when the combined {@link MediaMetadata} changes. * 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 c45a69f45f..0287bb39b2 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 @@ -21,6 +21,7 @@ import static java.lang.Math.min; import android.util.Pair; import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C.FormatSupport; import com.google.android.exoplayer2.ExoPlaybackException; @@ -30,11 +31,13 @@ import com.google.android.exoplayer2.RendererCapabilities.AdaptiveSupport; import com.google.android.exoplayer2.RendererCapabilities.Capabilities; import com.google.android.exoplayer2.RendererConfiguration; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableList; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -106,7 +109,7 @@ public abstract class MappingTrackSelector extends TrackSelector { * renderer, track group and track (in that order). * @param unmappedTrackGroups {@link TrackGroup}s not mapped to any renderer. */ - @SuppressWarnings("deprecation") + @VisibleForTesting /* package */ MappedTrackInfo( String[] rendererNames, @C.TrackType int[] rendererTrackTypes, @@ -144,7 +147,7 @@ public abstract class MappingTrackSelector extends TrackSelector { * * @see Renderer#getTrackType() * @param rendererIndex The renderer index. - * @return One of the {@code TRACK_TYPE_*} constants defined in {@link C}. + * @return The {@link C.TrackType} of the renderer. */ public @C.TrackType int getRendererType(int rendererIndex) { return rendererTrackTypes[rendererIndex]; @@ -279,6 +282,7 @@ public abstract class MappingTrackSelector extends TrackSelector { String firstSampleMimeType = null; for (int i = 0; i < trackIndices.length; i++) { int trackIndex = trackIndices[i]; + @Nullable String sampleMimeType = rendererTrackGroups[rendererIndex].get(groupIndex).getFormat(trackIndex).sampleMimeType; if (handledTrackCount++ == 0) { @@ -406,7 +410,10 @@ public abstract class MappingTrackSelector extends TrackSelector { rendererMixedMimeTypeAdaptationSupports, periodId, timeline); - return new TrackSelectorResult(result.first, result.second, mappedTrackInfo); + + TracksInfo tracksInfo = buildTracksInfo(result.second, mappedTrackInfo); + + return new TrackSelectorResult(result.first, result.second, tracksInfo, mappedTrackInfo); } /** @@ -536,4 +543,49 @@ public abstract class MappingTrackSelector extends TrackSelector { } return mixedMimeTypeAdaptationSupport; } + + @VisibleForTesting + /* package */ static TracksInfo buildTracksInfo( + @NullableType TrackSelection[] selections, MappedTrackInfo mappedTrackInfo) { + ImmutableList.Builder builder = new ImmutableList.Builder<>(); + for (int rendererIndex = 0; + rendererIndex < mappedTrackInfo.getRendererCount(); + rendererIndex++) { + TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(rendererIndex); + @Nullable TrackSelection trackSelection = selections[rendererIndex]; + for (int groupIndex = 0; groupIndex < trackGroupArray.length; groupIndex++) { + TrackGroup trackGroup = trackGroupArray.get(groupIndex); + @C.FormatSupport int[] trackSupport = new int[trackGroup.length]; + boolean[] selected = new boolean[trackGroup.length]; + 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); + selected[trackIndex] = isTrackSelected; + } + @C.TrackType int trackGroupType = mappedTrackInfo.getRendererType(rendererIndex); + builder.add( + new TracksInfo.TrackGroupInfo(trackGroup, trackSupport, trackGroupType, selected)); + } + } + TrackGroupArray unmappedTrackGroups = mappedTrackInfo.getUnmappedTrackGroups(); + for (int groupIndex = 0; groupIndex < unmappedTrackGroups.length; groupIndex++) { + TrackGroup trackGroup = unmappedTrackGroups.get(groupIndex); + @C.FormatSupport int[] trackSupport = new int[trackGroup.length]; + Arrays.fill(trackSupport, C.FORMAT_UNSUPPORTED_TYPE); + // A track group only contains tracks of the same type, thus only consider the first track. + @C.TrackType + int trackGroupType = MimeTypes.getTrackType(trackGroup.getFormat(0).sampleMimeType); + boolean[] selected = new boolean[trackGroup.length]; // Initialized to false. + builder.add( + new TracksInfo.TrackGroupInfo(trackGroup, trackSupport, trackGroupType, selected)); + } + return new TracksInfo(builder.build()); + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java index e7f0caaedf..a7bef4324b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/TrackSelectorResult.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.trackselection; import androidx.annotation.Nullable; import com.google.android.exoplayer2.RendererConfiguration; +import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.util.Util; import org.checkerframework.checker.nullness.compatqual.NullableType; @@ -32,6 +33,8 @@ public final class TrackSelectorResult { public final @NullableType RendererConfiguration[] rendererConfigurations; /** A {@link ExoTrackSelection} array containing the track selection for each renderer. */ public final @NullableType ExoTrackSelection[] selections; + /** Describe the tracks and which one were selected. */ + public final TracksInfo tracksInfo; /** * An opaque object that will be returned to {@link TrackSelector#onSelectionActivated(Object)} * should the selections be activated. @@ -45,13 +48,34 @@ public final class TrackSelectorResult { * @param info An opaque object that will be returned to {@link * TrackSelector#onSelectionActivated(Object)} should the selection be activated. May be * {@code null}. + * @deprecated Use {@link #TrackSelectorResult(RendererConfiguration[], ExoTrackSelection[], + * TracksInfo, Object)}. */ + @Deprecated public TrackSelectorResult( @NullableType RendererConfiguration[] rendererConfigurations, @NullableType ExoTrackSelection[] selections, @Nullable Object info) { + this(rendererConfigurations, selections, TracksInfo.EMPTY, info); + } + + /** + * @param rendererConfigurations A {@link RendererConfiguration} for each renderer. A null entry + * indicates the corresponding renderer should be disabled. + * @param selections A {@link ExoTrackSelection} array containing the selection for each renderer. + * @param tracksInfo Description of the available tracks and which one were selected. + * @param info An opaque object that will be returned to {@link + * TrackSelector#onSelectionActivated(Object)} should the selection be activated. May be + * {@code null}. + */ + public TrackSelectorResult( + @NullableType RendererConfiguration[] rendererConfigurations, + @NullableType ExoTrackSelection[] selections, + TracksInfo tracksInfo, + @Nullable Object info) { this.rendererConfigurations = rendererConfigurations; this.selections = selections.clone(); + this.tracksInfo = tracksInfo; this.info = info; length = rendererConfigurations.length; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 105a90471b..1de7410140 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -23,6 +23,7 @@ import static com.google.android.exoplayer2.Player.COMMAND_GET_DEVICE_VOLUME; import static com.google.android.exoplayer2.Player.COMMAND_GET_MEDIA_ITEMS_METADATA; import static com.google.android.exoplayer2.Player.COMMAND_GET_TEXT; import static com.google.android.exoplayer2.Player.COMMAND_GET_TIMELINE; +import static com.google.android.exoplayer2.Player.COMMAND_GET_TRACK_INFOS; import static com.google.android.exoplayer2.Player.COMMAND_GET_VOLUME; import static com.google.android.exoplayer2.Player.COMMAND_PLAY_PAUSE; import static com.google.android.exoplayer2.Player.COMMAND_PREPARE_STOP; @@ -8344,6 +8345,7 @@ public final class ExoPlayerTest { assertThat(player.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)).isTrue(); assertThat(player.isCommandAvailable(COMMAND_GET_TEXT)).isTrue(); assertThat(player.isCommandAvailable(COMMAND_SET_TRACK_SELECTION_PARAMETERS)).isTrue(); + assertThat(player.isCommandAvailable(COMMAND_GET_TRACK_INFOS)).isTrue(); } @Test @@ -11238,7 +11240,8 @@ public final class ExoPlayerTest { COMMAND_ADJUST_DEVICE_VOLUME, COMMAND_SET_VIDEO_SURFACE, COMMAND_GET_TEXT, - COMMAND_SET_TRACK_SELECTION_PARAMETERS); + COMMAND_SET_TRACK_SELECTION_PARAMETERS, + COMMAND_GET_TRACK_INFOS); if (!isTimelineEmpty) { builder.add(COMMAND_SEEK_TO_PREVIOUS); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java index 58d0d0c47b..635167e658 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/MediaPeriodQueueTest.java @@ -771,7 +771,10 @@ public final class MediaPeriodQueueTest { mediaSourceList, getNextMediaPeriodInfo(), new TrackSelectorResult( - new RendererConfiguration[0], new ExoTrackSelection[0], /* info= */ null)); + new RendererConfiguration[0], + new ExoTrackSelection[0], + TracksInfo.EMPTY, + /* info= */ null)); } private void clear() { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java index 0753464f04..4be75e1443 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/analytics/AnalyticsCollectorTest.java @@ -84,6 +84,7 @@ import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Window; +import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.drm.DefaultDrmSessionManager; import com.google.android.exoplayer2.drm.DrmInitData; @@ -100,7 +101,6 @@ import com.google.android.exoplayer2.source.LoadEventInfo; import com.google.android.exoplayer2.source.MediaLoadData; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; -import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.testutil.ActionSchedule; import com.google.android.exoplayer2.testutil.ActionSchedule.PlayerRunnable; @@ -115,7 +115,6 @@ import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinit import com.google.android.exoplayer2.testutil.FakeVideoRenderer; import com.google.android.exoplayer2.testutil.TestExoPlayerBuilder; import com.google.android.exoplayer2.testutil.TestUtil; -import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.ConditionVariable; import com.google.android.exoplayer2.util.MimeTypes; @@ -2224,8 +2223,7 @@ public final class AnalyticsCollectorTest { } @Override - public void onTracksChanged( - EventTime eventTime, TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + public void onTracksInfoChanged(EventTime eventTime, TracksInfo tracksInfo) { reportedEvents.add(new ReportedEvent(EVENT_TRACKS_CHANGED, eventTime)); } 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 146e670798..e04a0530a7 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 @@ -38,6 +38,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererConfiguration; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -50,6 +51,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelector.InvalidationLi import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.util.HashMap; @@ -1717,6 +1719,32 @@ public final class DefaultTrackSelectorTest { assertFixedSelection(result.selections[0], trackGroups, formatAac); } + /** Tests audio track selection when there are multiple audio renderers. */ + @Test + public void selectTracks_multipleRenderer_allSelected() throws Exception { + RendererCapabilities[] rendererCapabilities = + new RendererCapabilities[] {VIDEO_CAPABILITIES, AUDIO_CAPABILITIES, AUDIO_CAPABILITIES}; + TrackGroupArray trackGroups = new TrackGroupArray(AUDIO_TRACK_GROUP); + + TrackSelectorResult result = + trackSelector.selectTracks(rendererCapabilities, trackGroups, periodId, TIMELINE); + + assertThat(result.length).isEqualTo(3); + assertThat(result.rendererConfigurations) + .asList() + .containsExactly(null, DEFAULT, null) + .inOrder(); + assertThat(result.selections[0]).isNull(); + assertFixedSelection(result.selections[1], trackGroups, trackGroups.get(0).getFormat(0)); + assertThat(result.selections[2]).isNull(); + ImmutableList trackGroupInfos = + result.tracksInfo.getTrackGroupInfos(); + assertThat(trackGroupInfos).hasSize(1); + assertThat(trackGroupInfos.get(0).getTrackGroup()).isEqualTo(AUDIO_TRACK_GROUP); + assertThat(trackGroupInfos.get(0).isTrackSelected(0)).isTrue(); + assertThat(trackGroupInfos.get(0).getTrackSupport(0)).isEqualTo(FORMAT_HANDLED); + } + private static void assertSelections(TrackSelectorResult result, TrackSelection[] expected) { assertThat(result.length).isEqualTo(expected.length); for (int i = 0; i < expected.length; i++) { 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 9a01f85aa9..ab1d3c14cc 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 @@ -27,12 +27,15 @@ import com.google.android.exoplayer2.RendererCapabilities.AdaptiveSupport; import com.google.android.exoplayer2.RendererCapabilities.Capabilities; import com.google.android.exoplayer2.RendererConfiguration; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.TracksInfo; +import com.google.android.exoplayer2.TracksInfo.TrackGroupInfo; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.testutil.FakeTimeline; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; +import com.google.common.collect.ImmutableList; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -129,6 +132,61 @@ public final class MappingTrackSelectorTest { return new TrackGroup(new Format.Builder().setSampleMimeType(sampleMimeType).build()); } + @Test + public void buildTrackInfos_withTestValues_isAsExpected() { + MappingTrackSelector.MappedTrackInfo mappedTrackInfo = + new MappingTrackSelector.MappedTrackInfo( + new String[] {"1", "2"}, + 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 TrackGroupArray( + new TrackGroup(new Format.Builder().build(), new Format.Builder().build())) + }, + new int[] { + RendererCapabilities.ADAPTIVE_SEAMLESS, RendererCapabilities.ADAPTIVE_NOT_SUPPORTED + }, + new int[][][] { + new int[][] {new int[] {C.FORMAT_HANDLED}, new int[] {C.FORMAT_UNSUPPORTED_SUBTYPE}}, + new int[][] {new int[] {C.FORMAT_UNSUPPORTED_DRM, C.FORMAT_EXCEEDS_CAPABILITIES}} + }, + new TrackGroupArray(new TrackGroup(new Format.Builder().build()))); + TrackSelection[] selections = + new TrackSelection[] { + new FixedTrackSelection(mappedTrackInfo.getTrackGroups(0).get(1), 0), + new FixedTrackSelection(mappedTrackInfo.getTrackGroups(1).get(0), 1) + }; + + TracksInfo tracksInfo = MappingTrackSelector.buildTracksInfo(selections, mappedTrackInfo); + + ImmutableList trackGroupInfos = tracksInfo.getTrackGroupInfos(); + assertThat(trackGroupInfos).hasSize(4); + assertThat(trackGroupInfos.get(0).getTrackGroup()) + .isEqualTo(mappedTrackInfo.getTrackGroups(0).get(0)); + assertThat(trackGroupInfos.get(1).getTrackGroup()) + .isEqualTo(mappedTrackInfo.getTrackGroups(0).get(1)); + assertThat(trackGroupInfos.get(2).getTrackGroup()) + .isEqualTo(mappedTrackInfo.getTrackGroups(1).get(0)); + assertThat(trackGroupInfos.get(3).getTrackGroup()) + .isEqualTo(mappedTrackInfo.getUnmappedTrackGroups().get(0)); + assertThat(trackGroupInfos.get(0).getTrackSupport(0)).isEqualTo(C.FORMAT_HANDLED); + assertThat(trackGroupInfos.get(1).getTrackSupport(0)).isEqualTo(C.FORMAT_UNSUPPORTED_SUBTYPE); + assertThat(trackGroupInfos.get(2).getTrackSupport(0)).isEqualTo(C.FORMAT_UNSUPPORTED_DRM); + assertThat(trackGroupInfos.get(2).getTrackSupport(1)).isEqualTo(C.FORMAT_EXCEEDS_CAPABILITIES); + assertThat(trackGroupInfos.get(3).getTrackSupport(0)).isEqualTo(C.FORMAT_UNSUPPORTED_TYPE); + assertThat(trackGroupInfos.get(0).isTrackSelected(0)).isFalse(); + assertThat(trackGroupInfos.get(1).isTrackSelected(0)).isTrue(); + assertThat(trackGroupInfos.get(2).isTrackSelected(0)).isFalse(); + assertThat(trackGroupInfos.get(2).isTrackSelected(1)).isTrue(); + assertThat(trackGroupInfos.get(3).isTrackSelected(0)).isFalse(); + assertThat(trackGroupInfos.get(0).getTrackType()).isEqualTo(C.TRACK_TYPE_AUDIO); + assertThat(trackGroupInfos.get(1).getTrackType()).isEqualTo(C.TRACK_TYPE_AUDIO); + assertThat(trackGroupInfos.get(2).getTrackType()).isEqualTo(C.TRACK_TYPE_VIDEO); + assertThat(trackGroupInfos.get(3).getTrackType()).isEqualTo(C.TRACK_TYPE_UNKNOWN); + } + /** * A {@link MappingTrackSelector} that stashes the {@link MappedTrackInfo} passed to {@link * #selectTracks(MappedTrackInfo, int[][][], int[], MediaPeriodId, Timeline)}. diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java index 7e7ead9440..7c6ba909c9 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Transformer.java @@ -45,6 +45,7 @@ import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; @@ -53,10 +54,8 @@ import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSourceFactory; -import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; -import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; @@ -636,8 +635,7 @@ public final class Transformer { } @Override - public void onTracksChanged( - EventTime eventTime, TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + public void onTracksInfoChanged(EventTime eventTime, TracksInfo tracksInfo) { if (muxerWrapper.getTrackCount() == 0) { handleTransformationEnded( new IllegalStateException( diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java index d96170d4e2..53255ff69d 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java @@ -56,7 +56,7 @@ import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player.DiscontinuityReason; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; -import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; @@ -1511,7 +1511,7 @@ public class PlayerView extends FrameLayout implements AdViewProvider { } @Override - public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray selections) { + public void onTracksInfoChanged(TracksInfo tracksInfo) { // Suppress the update if transitioning to an unprepared period within the same window. This // is necessary to avoid closing the shutter when such a transition occurs. See: // https://github.com/google/ExoPlayer/issues/5507. diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java index 72534209dc..6a30814673 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/StyledPlayerView.java @@ -57,7 +57,7 @@ import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player.DiscontinuityReason; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Period; -import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; @@ -1551,7 +1551,7 @@ public class StyledPlayerView extends FrameLayout implements AdViewProvider { } @Override - public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray selections) { + public void onTracksInfoChanged(TracksInfo tracksInfo) { // Suppress the update if transitioning to an unprepared period within the same window. This // is necessary to avoid closing the shutter when such a transition occurs. See: // https://github.com/google/ExoPlayer/issues/5507. diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java index d3cd7fccaf..4096548a5c 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExoPlayerTestRunner.java @@ -38,9 +38,7 @@ import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; -import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.util.Clock; import com.google.android.exoplayer2.util.HandlerWrapper; @@ -383,7 +381,6 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul private SimpleExoPlayer player; private Exception exception; - private TrackGroupArray trackGroups; private boolean playerWasPrepared; private ExoPlayerTestRunner( @@ -562,17 +559,6 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul assertThat(playbackStates).containsExactlyElementsIn(states).inOrder(); } - /** - * Asserts that the last track group array reported by {@link - * Player.Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray)} is equal to the provided - * track group array. - * - * @param trackGroupArray The expected {@link TrackGroupArray}. - */ - public void assertTrackGroupsEqual(TrackGroupArray trackGroupArray) { - assertThat(this.trackGroups).isEqualTo(trackGroupArray); - } - /** * Asserts that {@link Player.Listener#onPositionDiscontinuity(Player.PositionInfo, * Player.PositionInfo, int)} was not called. @@ -656,11 +642,6 @@ public final class ExoPlayerTestRunner implements Player.Listener, ActionSchedul mediaItemTransitionReasons.add(reason); } - @Override - public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { - this.trackGroups = trackGroups; - } - @Override public void onPlaybackStateChanged(@Player.State int playbackState) { playbackStates.add(playbackState); diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index 72efc2b81b..db307b52db 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -33,6 +33,7 @@ import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.PlayerMessage; import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.source.MediaSource; @@ -463,6 +464,11 @@ public class StubExoPlayer extends BasePlayer implements ExoPlayer { throw new UnsupportedOperationException(); } + @Override + public TracksInfo getCurrentTracksInfo() { + throw new UnsupportedOperationException(); + } + @Override public TrackSelectionParameters getTrackSelectionParameters() { throw new UnsupportedOperationException();