blob: 8f723a1859afa5ba5c541abe7a05fd8ff1fd9caf [file] [log] [blame]
/*
* 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.
*
* <p>Multiple tracks of a track group may be selected. This is common in adaptive streaming,
* where multiple tracks of different quality are selected and the player switches between them
* depending on the network and the {@link TrackSelectionParameters}.
*
* <p>While this class doesn't provide which selected track is currently playing, some player
* implementations have ways of getting such information. For example ExoPlayer provides this
* information in {@code ExoTrackSelection.getSelectedFormat}.
*
* @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<TrackGroupInfo> 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<TrackGroupInfo> 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<TrackGroupInfo> trackGroupInfos) {
this.trackGroupInfos = ImmutableList.copyOf(trackGroupInfos);
}
/** Returns the {@link TrackGroupInfo TrackGroupInfos}, describing each {@link TrackGroup}. */
public ImmutableList<TrackGroupInfo> 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<TracksInfo> CREATOR =
bundle -> {
List<TrackGroupInfo> 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);
}
}