/*
* Copyright 2019 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 androidx.media3.session;
import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS;
import static androidx.media3.common.Player.COMMAND_ADJUST_DEVICE_VOLUME;
import static androidx.media3.common.Player.COMMAND_CHANGE_MEDIA_ITEMS;
import static androidx.media3.common.Player.COMMAND_GET_CURRENT_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_GET_DEVICE_VOLUME;
import static androidx.media3.common.Player.COMMAND_GET_MEDIA_ITEMS_METADATA;
import static androidx.media3.common.Player.COMMAND_GET_TIMELINE;
import static androidx.media3.common.Player.COMMAND_PLAY_PAUSE;
import static androidx.media3.common.Player.COMMAND_PREPARE;
import static androidx.media3.common.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME;
import static androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_SET_REPEAT_MODE;
import static androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE;
import static androidx.media3.common.Player.COMMAND_SET_SPEED_AND_PITCH;
import static androidx.media3.common.Player.COMMAND_STOP;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Util.castNonNull;
import static androidx.media3.common.util.Util.constrainValue;
import static java.lang.Math.max;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.RatingCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat.QueueItem;
import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v4.media.session.PlaybackStateCompat.CustomAction;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.media.AudioAttributesCompat;
import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
import androidx.media3.common.AdPlaybackState;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.DeviceInfo;
import androidx.media3.common.HeartRating;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.PercentageRating;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.Player;
import androidx.media3.common.Player.Command;
import androidx.media3.common.Player.Commands;
import androidx.media3.common.Rating;
import androidx.media3.common.StarRating;
import androidx.media3.common.ThumbRating;
import androidx.media3.common.Timeline;
import androidx.media3.common.Timeline.Period;
import androidx.media3.common.Timeline.Window;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util;
import androidx.media3.session.MediaLibraryService.LibraryParams;
import com.google.common.collect.ImmutableList;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;
import org.checkerframework.checker.nullness.compatqual.NullableType;
/* package */ final class MediaUtils {
public static final String TAG = "MediaUtils";
public static final int TRANSACTION_SIZE_LIMIT_IN_BYTES = 256 * 1024; // 256KB
/** Constant to identify whether two calculated positions are considered as same */
public static final long POSITION_DIFF_TOLERANCE_MS = 100;
// Stub BrowserRoot for accepting any connection here.
public static final BrowserRoot defaultBrowserRoot =
new BrowserRoot(MediaLibraryService.SERVICE_INTERFACE, null);
/** Returns whether two {@link PlaybackStateCompat} have equal error. */
public static boolean areEqualError(
@Nullable PlaybackStateCompat a, @Nullable PlaybackStateCompat b) {
boolean aHasError = a != null && a.getState() == PlaybackStateCompat.STATE_ERROR;
boolean bHasError = b != null && b.getState() == PlaybackStateCompat.STATE_ERROR;
if (aHasError && bHasError) {
return castNonNull(a).getErrorCode() == castNonNull(b).getErrorCode()
&& TextUtils.equals(castNonNull(a).getErrorMessage(), castNonNull(b).getErrorMessage());
}
return aHasError == bHasError;
}
/** Converts {@link PlaybackStateCompat} to {@link PlaybackException}. */
@Nullable
public static PlaybackException convertToPlaybackException(
@Nullable PlaybackStateCompat playbackStateCompat) {
if (playbackStateCompat == null
|| playbackStateCompat.getState() != PlaybackStateCompat.STATE_ERROR) {
return null;
}
StringBuilder stringBuilder = new StringBuilder();
if (!TextUtils.isEmpty(playbackStateCompat.getErrorMessage())) {
stringBuilder.append(playbackStateCompat.getErrorMessage().toString()).append(", ");
}
stringBuilder.append("code=").append(playbackStateCompat.getErrorCode());
String errorMessage = stringBuilder.toString();
return new PlaybackException(
errorMessage, /* cause= */ null, PlaybackException.ERROR_CODE_REMOTE_ERROR);
}
/** Converts a {@link MediaItem} to a {@link MediaBrowserCompat.MediaItem}. */
public static MediaBrowserCompat.MediaItem convertToBrowserItem(MediaItem item) {
MediaDescriptionCompat description = convertToMediaDescriptionCompat(item);
MediaMetadata metadata = item.mediaMetadata;
int flags = 0;
if (metadata.folderType != null && metadata.folderType != MediaMetadata.FOLDER_TYPE_NONE) {
flags |= MediaBrowserCompat.MediaItem.FLAG_BROWSABLE;
}
if (metadata.isPlayable != null && metadata.isPlayable) {
flags |= MediaBrowserCompat.MediaItem.FLAG_PLAYABLE;
}
return new MediaBrowserCompat.MediaItem(description, flags);
}
/** Converts a list of {@link MediaItem} to a list of {@link MediaBrowserCompat.MediaItem}. */
public static List<MediaBrowserCompat.MediaItem> convertToBrowserItemList(List<MediaItem> items) {
List<MediaBrowserCompat.MediaItem> result = new ArrayList<>();
for (int i = 0; i < items.size(); i++) {
result.add(convertToBrowserItem(items.get(i)));
}
return result;
}
/** Converts a {@link MediaBrowserCompat.MediaItem} to a {@link MediaItem}. */
public static MediaItem convertToMediaItem(MediaBrowserCompat.MediaItem item) {
return convertToMediaItem(item.getDescription(), item.isBrowsable(), item.isPlayable());
}
/** Converts a {@link QueueItem} to a {@link MediaItem}. */
public static MediaItem convertToMediaItem(QueueItem item) {
return convertToMediaItem(item.getDescription());
}
/** Converts a {@link QueueItem} to a {@link MediaItem}. */
public static MediaItem convertToMediaItem(MediaDescriptionCompat description) {
checkNotNull(description);
return convertToMediaItem(description, /* browsable= */ false, /* playable= */ true);
}
/** Converts a {@link MediaMetadataCompat} to a {@link MediaItem}. */
public static MediaItem convertToMediaItem(
MediaMetadataCompat metadataCompat, @RatingCompat.Style int ratingType) {
return convertToMediaItem(
metadataCompat.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID),
metadataCompat,
ratingType);
}
/** Converts a {@code mediaId} and {@link MediaMetadataCompat} to a {@link MediaItem}. */
public static MediaItem convertToMediaItem(
@Nullable String mediaId,
MediaMetadataCompat metadataCompat,
@RatingCompat.Style int ratingType) {
MediaItem.Builder builder = new MediaItem.Builder();
if (mediaId != null) {
builder.setMediaId(mediaId);
}
@Nullable
String mediaUriString = metadataCompat.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI);
if (mediaUriString != null) {
builder.setRequestMetadata(
new MediaItem.RequestMetadata.Builder().setMediaUri(Uri.parse(mediaUriString)).build());
}
builder.setMediaMetadata(convertToMediaMetadata(metadataCompat, ratingType));
return builder.build();
}
private static MediaItem convertToMediaItem(
MediaDescriptionCompat descriptionCompat, boolean browsable, boolean playable) {
@Nullable String mediaId = descriptionCompat.getMediaId();
return new MediaItem.Builder()
.setMediaId(mediaId == null ? MediaItem.DEFAULT_MEDIA_ID : mediaId)
.setRequestMetadata(
new MediaItem.RequestMetadata.Builder()
.setMediaUri(descriptionCompat.getMediaUri())
.build())
.setMediaMetadata(
convertToMediaMetadata(
descriptionCompat, RatingCompat.RATING_NONE, browsable, playable))
.build();
}
/** Converts a list of {@link MediaBrowserCompat.MediaItem} to a list of {@link MediaItem}. */
public static ImmutableList<MediaItem> convertBrowserItemListToMediaItemList(
List<MediaBrowserCompat.MediaItem> items) {
ImmutableList.Builder<MediaItem> builder = new ImmutableList.Builder<>();
for (int i = 0; i < items.size(); i++) {
builder.add(convertToMediaItem(items.get(i)));
}
return builder.build();
}
/** Converts a {@link Timeline} to a list of {@link MediaItem MediaItems}. */
public static List<MediaItem> convertToMediaItemList(Timeline timeline) {
List<MediaItem> mediaItems = new ArrayList<>();
Timeline.Window window = new Timeline.Window();
for (int i = 0; i < timeline.getWindowCount(); i++) {
mediaItems.add(timeline.getWindow(i, window).mediaItem);
}
return mediaItems;
}
/**
* Converts a list of {@link MediaItem} to a list of {@link QueueItem}. The index of the item
* would be used as the queue ID to match the behavior of {@link MediaController}.
*/
public static List<QueueItem> convertToQueueItemList(List<MediaItem> items) {
List<QueueItem> result = new ArrayList<>();
for (int i = 0; i < items.size(); i++) {
MediaItem item = items.get(i);
MediaDescriptionCompat description = convertToMediaDescriptionCompat(item);
long id = convertToQueueItemId(i);
result.add(new QueueItem(description, id));
}
return result;
}
/** Converts the index of a {@link MediaItem} in a playlist into id of {@link QueueItem}. */
public static long convertToQueueItemId(int mediaItemIndex) {
if (mediaItemIndex == C.INDEX_UNSET) {
return QueueItem.UNKNOWN_ID;
}
return mediaItemIndex;
}
public static Window convertToWindow(MediaItem mediaItem, int periodIndex) {
Window window = new Window();
window.set(
/* uid= */ 0,
mediaItem,
/* manifest= */ null,
/* presentationStartTimeMs= */ 0,
/* windowStartTimeMs= */ 0,
/* elapsedRealtimeEpochOffsetMs= */ 0,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* liveConfiguration= */ null,
/* defaultPositionUs= */ 0,
/* durationUs= */ C.TIME_UNSET,
/* firstPeriodIndex= */ periodIndex,
/* lastPeriodIndex= */ periodIndex,
/* positionInFirstPeriodUs= */ 0);
return window;
}
public static Period convertToPeriod(int windowIndex) {
Period period = new Period();
period.set(
/* id= */ null,
/* uid= */ null,
windowIndex,
/* durationUs= */ C.TIME_UNSET,
/* positionInWindowUs= */ 0,
/* adPlaybackState= */ AdPlaybackState.NONE,
/* isPlaceholder= */ true);
return period;
}
/**
* Returns a list which consists of first {@code N} items of the given list with the same order.
* {@code N} is determined as the maximum number of items whose total parcelled size is less than
* {@code sizeLimitInBytes}.
*/
public static <T extends Parcelable> List<T> truncateListBySize(
List<T> list, int sizeLimitInBytes) {
List<T> result = new ArrayList<>();
Parcel parcel = Parcel.obtain();
try {
for (int i = 0; i < list.size(); i++) {
// Calculate the size.
T item = list.get(i);
parcel.writeParcelable(item, 0);
if (parcel.dataSize() < sizeLimitInBytes) {
result.add(item);
} else {
break;
}
}
} finally {
parcel.recycle();
}
return result;
}
/** Converts a {@link MediaItem} to a {@link MediaDescriptionCompat}. */
public static MediaDescriptionCompat convertToMediaDescriptionCompat(MediaItem item) {
MediaDescriptionCompat.Builder builder =
new MediaDescriptionCompat.Builder()
.setMediaId(item.mediaId.equals(MediaItem.DEFAULT_MEDIA_ID) ? null : item.mediaId);
MediaMetadata metadata = item.mediaMetadata;
if (metadata.artworkData != null) {
Bitmap artwork =
BitmapFactory.decodeByteArray(metadata.artworkData, 0, metadata.artworkData.length);
builder.setIconBitmap(artwork);
}
@Nullable Bundle extras = metadata.extras;
if (metadata.folderType != null && metadata.folderType != MediaMetadata.FOLDER_TYPE_NONE) {
if (extras == null) {
extras = new Bundle();
} else {
extras = new Bundle(extras);
}
extras.putLong(
MediaDescriptionCompat.EXTRA_BT_FOLDER_TYPE,
convertToExtraBtFolderType(metadata.folderType));
}
return builder
.setTitle(metadata.title)
.setSubtitle(metadata.subtitle)
.setDescription(metadata.description)
.setIconUri(metadata.artworkUri)
.setMediaUri(item.requestMetadata.mediaUri)
.setExtras(extras)
.build();
}
/** Creates {@link MediaMetadata} from the {@link CharSequence queue title}. */
public static MediaMetadata convertToMediaMetadata(@Nullable CharSequence queueTitle) {
if (queueTitle == null) {
return MediaMetadata.EMPTY;
}
return new MediaMetadata.Builder()
.setTitle(queueTitle)
.setFolderType(MediaMetadata.FOLDER_TYPE_MIXED)
.setIsPlayable(true)
.build();
}
public static MediaMetadata convertToMediaMetadata(
@Nullable MediaDescriptionCompat descriptionCompat, @RatingCompat.Style int ratingType) {
return convertToMediaMetadata(
descriptionCompat, ratingType, /* browsable= */ false, /* playable= */ true);
}
private static MediaMetadata convertToMediaMetadata(
@Nullable MediaDescriptionCompat descriptionCompat,
@RatingCompat.Style int ratingType,
boolean browsable,
boolean playable) {
if (descriptionCompat == null) {
return MediaMetadata.EMPTY;
}
MediaMetadata.Builder builder = new MediaMetadata.Builder();
builder
.setTitle(descriptionCompat.getTitle())
.setSubtitle(descriptionCompat.getSubtitle())
.setDescription(descriptionCompat.getDescription())
.setArtworkUri(descriptionCompat.getIconUri())
.setUserRating(convertToRating(RatingCompat.newUnratedRating(ratingType)));
@Nullable Bitmap iconBitmap = descriptionCompat.getIconBitmap();
if (iconBitmap != null) {
@Nullable byte[] artworkData = null;
try {
artworkData = convertToByteArray(iconBitmap);
} catch (IOException e) {
Log.w(TAG, "Failed to convert iconBitmap to artworkData", e);
}
builder.setArtworkData(artworkData, MediaMetadata.PICTURE_TYPE_FRONT_COVER);
}
@Nullable Bundle extras = descriptionCompat.getExtras();
builder.setExtras(extras);
if (extras != null && extras.containsKey(MediaDescriptionCompat.EXTRA_BT_FOLDER_TYPE)) {
builder.setFolderType(
convertToFolderType(extras.getLong(MediaDescriptionCompat.EXTRA_BT_FOLDER_TYPE)));
} else if (browsable) {
builder.setFolderType(MediaMetadata.FOLDER_TYPE_MIXED);
} else {
builder.setFolderType(MediaMetadata.FOLDER_TYPE_NONE);
}
builder.setIsPlayable(playable);
return builder.build();
}
/** Creates {@link MediaMetadata} from the {@link MediaMetadataCompat} and rating type. */
public static MediaMetadata convertToMediaMetadata(
@Nullable MediaMetadataCompat metadataCompat, @RatingCompat.Style int ratingType) {
if (metadataCompat == null) {
return MediaMetadata.EMPTY;
}
MediaMetadata.Builder builder = new MediaMetadata.Builder();
builder
.setTitle(
getFirstText(
metadataCompat,
MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE,
MediaMetadataCompat.METADATA_KEY_TITLE))
.setSubtitle(metadataCompat.getText(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE))
.setDescription(
metadataCompat.getText(MediaMetadataCompat.METADATA_KEY_DISPLAY_DESCRIPTION))
.setArtist(metadataCompat.getText(MediaMetadataCompat.METADATA_KEY_ARTIST))
.setAlbumTitle(metadataCompat.getText(MediaMetadataCompat.METADATA_KEY_ALBUM))
.setAlbumArtist(metadataCompat.getText(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST))
.setOverallRating(
convertToRating(metadataCompat.getRating(MediaMetadataCompat.METADATA_KEY_RATING)));
@Nullable
Rating userRating =
convertToRating(metadataCompat.getRating(MediaMetadataCompat.METADATA_KEY_USER_RATING));
if (userRating != null) {
builder.setUserRating(userRating);
} else {
builder.setUserRating(convertToRating(RatingCompat.newUnratedRating(ratingType)));
}
if (metadataCompat.containsKey(MediaMetadataCompat.METADATA_KEY_YEAR)) {
long year = metadataCompat.getLong(MediaMetadataCompat.METADATA_KEY_YEAR);
builder.setRecordingYear((int) year);
}
@Nullable
String artworkUriString =
getFirstString(
metadataCompat,
MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI,
MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI);
if (artworkUriString != null) {
builder.setArtworkUri(Uri.parse(artworkUriString));
}
@Nullable
Bitmap artworkBitmap =
getFirstBitmap(
metadataCompat,
MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON,
MediaMetadataCompat.METADATA_KEY_ALBUM_ART);
if (artworkBitmap != null) {
try {
byte[] artworkData = convertToByteArray(artworkBitmap);
builder.setArtworkData(artworkData, MediaMetadata.PICTURE_TYPE_FRONT_COVER);
} catch (IOException e) {
Log.w(TAG, "Failed to convert artworkBitmap to artworkData", e);
}
}
if (metadataCompat.containsKey(MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE)) {
builder.setFolderType(
convertToFolderType(
metadataCompat.getLong(MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE)));
} else {
builder.setFolderType(MediaMetadata.FOLDER_TYPE_NONE);
}
builder.setIsPlayable(true);
return builder.build();
}
@Nullable
private static Bitmap getFirstBitmap(MediaMetadataCompat mediaMetadataCompat, String... keys) {
for (String key : keys) {
if (mediaMetadataCompat.containsKey(key)) {
return mediaMetadataCompat.getBitmap(key);
}
}
return null;
}
@Nullable
private static String getFirstString(MediaMetadataCompat mediaMetadataCompat, String... keys) {
for (String key : keys) {
if (mediaMetadataCompat.containsKey(key)) {
return mediaMetadataCompat.getString(key);
}
}
return null;
}
@Nullable
private static CharSequence getFirstText(
MediaMetadataCompat mediaMetadataCompat, String... keys) {
for (String key : keys) {
if (mediaMetadataCompat.containsKey(key)) {
return mediaMetadataCompat.getText(key);
}
}
return null;
}
/** Converts a {@link MediaItem} to a {@link MediaMetadataCompat}. */
public static MediaMetadataCompat convertToMediaMetadataCompat(
MediaItem mediaItem, long durationMs, @Nullable Bitmap artworkBitmap) {
MediaMetadataCompat.Builder builder =
new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mediaItem.mediaId);
MediaMetadata metadata = mediaItem.mediaMetadata;
if (metadata.title != null) {
builder.putText(MediaMetadataCompat.METADATA_KEY_TITLE, metadata.title);
builder.putText(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, metadata.title);
}
if (metadata.subtitle != null) {
builder.putText(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, metadata.subtitle);
}
if (metadata.description != null) {
builder.putText(MediaMetadataCompat.METADATA_KEY_DISPLAY_DESCRIPTION, metadata.description);
}
if (metadata.artist != null) {
builder.putText(MediaMetadataCompat.METADATA_KEY_ARTIST, metadata.artist);
}
if (metadata.albumTitle != null) {
builder.putText(MediaMetadataCompat.METADATA_KEY_ALBUM, metadata.albumTitle);
}
if (metadata.albumArtist != null) {
builder.putText(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, metadata.albumArtist);
}
if (metadata.recordingYear != null) {
builder.putLong(MediaMetadataCompat.METADATA_KEY_YEAR, metadata.recordingYear);
}
if (mediaItem.requestMetadata.mediaUri != null) {
builder.putString(
MediaMetadataCompat.METADATA_KEY_MEDIA_URI,
mediaItem.requestMetadata.mediaUri.toString());
}
if (metadata.artworkUri != null) {
builder.putString(
MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, metadata.artworkUri.toString());
builder.putString(
MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, metadata.artworkUri.toString());
}
if (artworkBitmap != null) {
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, artworkBitmap);
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, artworkBitmap);
}
if (metadata.folderType != null && metadata.folderType != MediaMetadata.FOLDER_TYPE_NONE) {
builder.putLong(
MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE,
convertToExtraBtFolderType(metadata.folderType));
}
if (durationMs != C.TIME_UNSET) {
builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, durationMs);
}
@Nullable
RatingCompat userRatingCompat = convertToRatingCompat(mediaItem.mediaMetadata.userRating);
if (userRatingCompat != null) {
builder.putRating(MediaMetadataCompat.METADATA_KEY_USER_RATING, userRatingCompat);
}
@Nullable
RatingCompat overallRatingCompat = convertToRatingCompat(mediaItem.mediaMetadata.overallRating);
if (overallRatingCompat != null) {
builder.putRating(MediaMetadataCompat.METADATA_KEY_RATING, overallRatingCompat);
}
return builder.build();
}
@MediaMetadata.FolderType
private static int convertToFolderType(long extraBtFolderType) {
if (extraBtFolderType == MediaDescriptionCompat.BT_FOLDER_TYPE_MIXED) {
return MediaMetadata.FOLDER_TYPE_MIXED;
} else if (extraBtFolderType == MediaDescriptionCompat.BT_FOLDER_TYPE_TITLES) {
return MediaMetadata.FOLDER_TYPE_TITLES;
} else if (extraBtFolderType == MediaDescriptionCompat.BT_FOLDER_TYPE_ALBUMS) {
return MediaMetadata.FOLDER_TYPE_ALBUMS;
} else if (extraBtFolderType == MediaDescriptionCompat.BT_FOLDER_TYPE_ARTISTS) {
return MediaMetadata.FOLDER_TYPE_ARTISTS;
} else if (extraBtFolderType == MediaDescriptionCompat.BT_FOLDER_TYPE_GENRES) {
return MediaMetadata.FOLDER_TYPE_GENRES;
} else if (extraBtFolderType == MediaDescriptionCompat.BT_FOLDER_TYPE_PLAYLISTS) {
return MediaMetadata.FOLDER_TYPE_PLAYLISTS;
} else if (extraBtFolderType == MediaDescriptionCompat.BT_FOLDER_TYPE_YEARS) {
return MediaMetadata.FOLDER_TYPE_YEARS;
} else {
return MediaMetadata.FOLDER_TYPE_NONE;
}
}
private static long convertToExtraBtFolderType(@MediaMetadata.FolderType int folderType) {
switch (folderType) {
case MediaMetadata.FOLDER_TYPE_MIXED:
return MediaDescriptionCompat.BT_FOLDER_TYPE_MIXED;
case MediaMetadata.FOLDER_TYPE_TITLES:
return MediaDescriptionCompat.BT_FOLDER_TYPE_TITLES;
case MediaMetadata.FOLDER_TYPE_ALBUMS:
return MediaDescriptionCompat.BT_FOLDER_TYPE_ALBUMS;
case MediaMetadata.FOLDER_TYPE_ARTISTS:
return MediaDescriptionCompat.BT_FOLDER_TYPE_ARTISTS;
case MediaMetadata.FOLDER_TYPE_GENRES:
return MediaDescriptionCompat.BT_FOLDER_TYPE_GENRES;
case MediaMetadata.FOLDER_TYPE_PLAYLISTS:
return MediaDescriptionCompat.BT_FOLDER_TYPE_PLAYLISTS;
case MediaMetadata.FOLDER_TYPE_YEARS:
return MediaDescriptionCompat.BT_FOLDER_TYPE_YEARS;
case MediaMetadata.FOLDER_TYPE_NONE:
default:
throw new IllegalArgumentException("Unrecognized FolderType: " + folderType);
}
}
/**
* Creates a {@link Rating} from the {@link RatingCompat}.
*
* @param ratingCompat A {@link RatingCompat} object.
* @return The newly created {@link Rating} object.
*/
@Nullable
public static Rating convertToRating(@Nullable RatingCompat ratingCompat) {
if (ratingCompat == null) {
return null;
}
switch (ratingCompat.getRatingStyle()) {
case RatingCompat.RATING_3_STARS:
return ratingCompat.isRated()
? new StarRating(3, ratingCompat.getStarRating())
: new StarRating(3);
case RatingCompat.RATING_4_STARS:
return ratingCompat.isRated()
? new StarRating(4, ratingCompat.getStarRating())
: new StarRating(4);
case RatingCompat.RATING_5_STARS:
return ratingCompat.isRated()
? new StarRating(5, ratingCompat.getStarRating())
: new StarRating(5);
case RatingCompat.RATING_HEART:
return ratingCompat.isRated()
? new HeartRating(ratingCompat.hasHeart())
: new HeartRating();
case RatingCompat.RATING_THUMB_UP_DOWN:
return ratingCompat.isRated()
? new ThumbRating(ratingCompat.isThumbUp())
: new ThumbRating();
case RatingCompat.RATING_PERCENTAGE:
return ratingCompat.isRated()
? new PercentageRating(ratingCompat.getPercentRating())
: new PercentageRating();
case RatingCompat.RATING_NONE:
default:
return null;
}
}
/**
* Creates a {@link RatingCompat} from the {@link Rating}.
*
* @param rating A {@link Rating} object.
* @return The newly created {@link RatingCompat} object.
*/
@SuppressLint("WrongConstant") // for @StarStyle
@Nullable
public static RatingCompat convertToRatingCompat(@Nullable Rating rating) {
if (rating == null) {
return null;
}
int ratingCompatStyle = getRatingCompatStyle(rating);
if (!rating.isRated()) {
return RatingCompat.newUnratedRating(ratingCompatStyle);
}
switch (ratingCompatStyle) {
case RatingCompat.RATING_3_STARS:
case RatingCompat.RATING_4_STARS:
case RatingCompat.RATING_5_STARS:
return RatingCompat.newStarRating(ratingCompatStyle, ((StarRating) rating).getStarRating());
case RatingCompat.RATING_HEART:
return RatingCompat.newHeartRating(((HeartRating) rating).isHeart());
case RatingCompat.RATING_THUMB_UP_DOWN:
return RatingCompat.newThumbRating(((ThumbRating) rating).isThumbsUp());
case RatingCompat.RATING_PERCENTAGE:
return RatingCompat.newPercentageRating(((PercentageRating) rating).getPercent());
case RatingCompat.RATING_NONE:
default:
return null;
}
}
/** Converts {@link Player}' states to state of {@link PlaybackStateCompat}. */
@PlaybackStateCompat.State
public static int convertToPlaybackStateCompatState(
@Nullable PlaybackException playerError,
@Player.State int playbackState,
boolean playWhenReady) {
if (playerError != null) {
return PlaybackStateCompat.STATE_ERROR;
}
switch (playbackState) {
case Player.STATE_IDLE:
return PlaybackStateCompat.STATE_NONE;
case Player.STATE_READY:
return playWhenReady ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED;
case Player.STATE_ENDED:
return PlaybackStateCompat.STATE_STOPPED;
case Player.STATE_BUFFERING:
return playWhenReady
? PlaybackStateCompat.STATE_BUFFERING
: PlaybackStateCompat.STATE_PAUSED;
default:
throw new IllegalArgumentException("Unrecognized State: " + playbackState);
}
}
/** Converts a {@link PlaybackStateCompat} to {@link PlaybackParameters}. */
public static PlaybackParameters convertToPlaybackParameters(
@Nullable PlaybackStateCompat playbackStateCompat) {
return playbackStateCompat == null
? PlaybackParameters.DEFAULT
: new PlaybackParameters(playbackStateCompat.getPlaybackSpeed());
}
/** Converts a {@link PlaybackStateCompat} to {@link Player}'s play when ready. */
public static boolean convertToPlayWhenReady(@Nullable PlaybackStateCompat playbackState) {
if (playbackState == null) {
return false;
}
switch (playbackState.getState()) {
case PlaybackStateCompat.STATE_BUFFERING:
case PlaybackStateCompat.STATE_FAST_FORWARDING:
case PlaybackStateCompat.STATE_PLAYING:
case PlaybackStateCompat.STATE_REWINDING:
case PlaybackStateCompat.STATE_SKIPPING_TO_NEXT:
case PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS:
case PlaybackStateCompat.STATE_SKIPPING_TO_QUEUE_ITEM:
return true;
case PlaybackStateCompat.STATE_CONNECTING:
case PlaybackStateCompat.STATE_ERROR:
case PlaybackStateCompat.STATE_NONE:
case PlaybackStateCompat.STATE_PAUSED:
case PlaybackStateCompat.STATE_STOPPED:
return false;
}
return false;
}
/** Converts a {@link PlaybackStateCompat} to {@link Player.State} */
@Player.State
public static int convertToPlaybackState(
@Nullable PlaybackStateCompat playbackStateCompat,
@Nullable MediaMetadataCompat currentMediaMetadata,
long timeDiffMs) {
if (playbackStateCompat == null) {
return Player.STATE_IDLE;
}
switch (playbackStateCompat.getState()) {
case PlaybackStateCompat.STATE_CONNECTING:
case PlaybackStateCompat.STATE_ERROR:
case PlaybackStateCompat.STATE_NONE:
case PlaybackStateCompat.STATE_STOPPED:
return Player.STATE_IDLE;
case PlaybackStateCompat.STATE_BUFFERING:
case PlaybackStateCompat.STATE_FAST_FORWARDING:
case PlaybackStateCompat.STATE_REWINDING:
case PlaybackStateCompat.STATE_SKIPPING_TO_NEXT:
case PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS:
case PlaybackStateCompat.STATE_SKIPPING_TO_QUEUE_ITEM:
return Player.STATE_BUFFERING;
case PlaybackStateCompat.STATE_PLAYING:
return Player.STATE_READY;
case PlaybackStateCompat.STATE_PAUSED:
long duration = convertToDurationMs(currentMediaMetadata);
if (duration == C.TIME_UNSET) {
return Player.STATE_READY;
}
long currentPosition =
convertToCurrentPositionMs(playbackStateCompat, currentMediaMetadata, timeDiffMs);
return (currentPosition < duration) ? Player.STATE_READY : Player.STATE_ENDED;
default:
throw new IllegalStateException(
"Unrecognized PlaybackStateCompat: " + playbackStateCompat.getState());
}
}
/** Converts a {@link PlaybackStateCompat} to isPlaying, defined by {@link Player#isPlaying()}. */
public static boolean convertToIsPlaying(@Nullable PlaybackStateCompat playbackStateCompat) {
if (playbackStateCompat == null) {
return false;
}
return playbackStateCompat.getState() == PlaybackStateCompat.STATE_PLAYING;
}
/** Converts a {@link PlaybackStateCompat} to isPlaying, defined by {@link Player#isPlaying()}. */
public static boolean convertToIsPlayingAd(@Nullable MediaMetadataCompat metadataCompat) {
if (metadataCompat == null) {
return false;
}
return metadataCompat.getLong(MediaMetadataCompat.METADATA_KEY_ADVERTISEMENT) != 0;
}
/** Gets the current position. {@code 0} will be returned if unknown. */
public static long convertToCurrentPositionMs(
@Nullable PlaybackStateCompat playbackStateCompat,
@Nullable MediaMetadataCompat metadataCompat,
long timeDiffMs) {
if (playbackStateCompat == null) {
return 0;
}
long positionMs =
playbackStateCompat.getState() == PlaybackStateCompat.STATE_PLAYING
? getCurrentPosition(playbackStateCompat, timeDiffMs)
: playbackStateCompat.getPosition();
long durationMs = convertToDurationMs(metadataCompat);
return durationMs == C.TIME_UNSET
? max(0, positionMs)
: constrainValue(positionMs, /* min= */ 0, durationMs);
}
@SuppressWarnings("nullness:argument") // PlaybackStateCompat#getCurrentPosition can take null.
private static long getCurrentPosition(PlaybackStateCompat playbackStateCompat, long timeDiffMs) {
return playbackStateCompat.getCurrentPosition(timeDiffMs == C.TIME_UNSET ? null : timeDiffMs);
}
/** Gets the duration. {@link C#TIME_UNSET} will be returned if unknown. */
public static long convertToDurationMs(@Nullable MediaMetadataCompat metadataCompat) {
if (metadataCompat == null
|| !metadataCompat.containsKey(MediaMetadataCompat.METADATA_KEY_DURATION)) {
return C.TIME_UNSET;
}
long legacyDurationMs = metadataCompat.getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
return legacyDurationMs <= 0 ? C.TIME_UNSET : legacyDurationMs;
}
/** Gets the buffered position. {@code 0} will be returned if unknown. */
public static long convertToBufferedPositionMs(
@Nullable PlaybackStateCompat playbackStateCompat,
@Nullable MediaMetadataCompat metadataCompat,
long timeDiffMs) {
long legacyBufferedPositionMs =
(playbackStateCompat == null) ? 0 : playbackStateCompat.getBufferedPosition();
long currentPositionMs =
convertToCurrentPositionMs(playbackStateCompat, metadataCompat, timeDiffMs);
long durationMs = convertToDurationMs(metadataCompat);
return (durationMs == C.TIME_UNSET)
? max(currentPositionMs, legacyBufferedPositionMs)
: constrainValue(legacyBufferedPositionMs, currentPositionMs, durationMs);
}
/** Gets the total buffered duration. {@code 0} will be returned if unknown. */
public static long convertToTotalBufferedDurationMs(
@Nullable PlaybackStateCompat playbackStateCompat,
@Nullable MediaMetadataCompat metadataCompat,
long timeDiffMs) {
long bufferedPositionMs =
convertToBufferedPositionMs(playbackStateCompat, metadataCompat, timeDiffMs);
long currentPositionMs =
convertToCurrentPositionMs(playbackStateCompat, metadataCompat, timeDiffMs);
return bufferedPositionMs - currentPositionMs;
}
/** Gets the buffered percentage. {@code 0} will be returned if unknown. */
public static int convertToBufferedPercentage(
@Nullable PlaybackStateCompat playbackStateCompat,
@Nullable MediaMetadataCompat mediaMetadataCompat,
long timeDiffMs) {
long bufferedPositionMs =
MediaUtils.convertToBufferedPositionMs(
playbackStateCompat, mediaMetadataCompat, timeDiffMs);
long durationMs = MediaUtils.convertToDurationMs(mediaMetadataCompat);
return calculateBufferedPercentage(bufferedPositionMs, durationMs);
}
public static @RatingCompat.Style int getRatingCompatStyle(@Nullable Rating rating) {
if (rating instanceof HeartRating) {
return RatingCompat.RATING_HEART;
} else if (rating instanceof ThumbRating) {
return RatingCompat.RATING_THUMB_UP_DOWN;
} else if (rating instanceof StarRating) {
switch (((StarRating) rating).getMaxStars()) {
case 3:
return RatingCompat.RATING_3_STARS;
case 4:
return RatingCompat.RATING_4_STARS;
case 5:
return RatingCompat.RATING_5_STARS;
}
} else if (rating instanceof PercentageRating) {
return RatingCompat.RATING_PERCENTAGE;
}
return RatingCompat.RATING_NONE;
}
/** Converts {@link PlaybackStateCompat.RepeatMode} to {@link Player.RepeatMode}. */
@Player.RepeatMode
public static int convertToRepeatMode(
@PlaybackStateCompat.RepeatMode int playbackStateCompatRepeatMode) {
switch (playbackStateCompatRepeatMode) {
case PlaybackStateCompat.REPEAT_MODE_INVALID:
case PlaybackStateCompat.REPEAT_MODE_NONE:
return Player.REPEAT_MODE_OFF;
case PlaybackStateCompat.REPEAT_MODE_ONE:
return Player.REPEAT_MODE_ONE;
case PlaybackStateCompat.REPEAT_MODE_ALL:
case PlaybackStateCompat.REPEAT_MODE_GROUP:
return Player.REPEAT_MODE_ALL;
default:
throw new IllegalArgumentException(
"Unrecognized PlaybackStateCompat.RepeatMode: " + playbackStateCompatRepeatMode);
}
}
/** Converts {@link Player.RepeatMode} to {@link PlaybackStateCompat.RepeatMode} */
@PlaybackStateCompat.RepeatMode
public static int convertToPlaybackStateCompatRepeatMode(@Player.RepeatMode int repeatMode) {
switch (repeatMode) {
case Player.REPEAT_MODE_OFF:
return PlaybackStateCompat.REPEAT_MODE_NONE;
case Player.REPEAT_MODE_ONE:
return PlaybackStateCompat.REPEAT_MODE_ONE;
case Player.REPEAT_MODE_ALL:
return PlaybackStateCompat.REPEAT_MODE_ALL;
default:
throw new IllegalArgumentException("Unrecognized RepeatMode: " + repeatMode);
}
}
/** Converts {@link PlaybackStateCompat.ShuffleMode} to shuffle mode enabled. */
public static boolean convertToShuffleModeEnabled(
@PlaybackStateCompat.ShuffleMode int playbackStateCompatShuffleMode) {
switch (playbackStateCompatShuffleMode) {
case PlaybackStateCompat.SHUFFLE_MODE_INVALID:
case PlaybackStateCompat.SHUFFLE_MODE_NONE:
return false;
case PlaybackStateCompat.SHUFFLE_MODE_ALL:
case PlaybackStateCompat.SHUFFLE_MODE_GROUP:
return true;
default:
throw new IllegalArgumentException(
"Unrecognized ShuffleMode: " + playbackStateCompatShuffleMode);
}
}
/** Converts shuffle mode enabled to {@link PlaybackStateCompat.ShuffleMode} */
@PlaybackStateCompat.ShuffleMode
public static int convertToPlaybackStateCompatShuffleMode(boolean shuffleModeEnabled) {
return shuffleModeEnabled
? PlaybackStateCompat.SHUFFLE_MODE_ALL
: PlaybackStateCompat.SHUFFLE_MODE_NONE;
}
/** Converts the rootHints, option, and extra to the {@link LibraryParams}. */
@Nullable
public static LibraryParams convertToLibraryParams(
Context context, @Nullable Bundle legacyBundle) {
if (legacyBundle == null) {
return null;
}
try {
legacyBundle.setClassLoader(context.getClassLoader());
return new LibraryParams.Builder()
.setExtras(legacyBundle)
.setRecent(legacyBundle.getBoolean(BrowserRoot.EXTRA_RECENT))
.setOffline(legacyBundle.getBoolean(BrowserRoot.EXTRA_OFFLINE))
.setSuggested(legacyBundle.getBoolean(BrowserRoot.EXTRA_SUGGESTED))
.build();
} catch (Exception e) {
// Failure when unpacking the legacy bundle.
return new LibraryParams.Builder().setExtras(legacyBundle).build();
}
}
/** Converts {@link LibraryParams} to the root hints. */
@Nullable
public static Bundle convertToRootHints(@Nullable LibraryParams params) {
if (params == null) {
return null;
}
Bundle rootHints = new Bundle(params.extras);
rootHints.putBoolean(BrowserRoot.EXTRA_RECENT, params.isRecent);
rootHints.putBoolean(BrowserRoot.EXTRA_OFFLINE, params.isOffline);
rootHints.putBoolean(BrowserRoot.EXTRA_SUGGESTED, params.isSuggested);
return rootHints;
}
/** Returns a new list that only contains non-null elements of the original list. */
public static <T> List<T> removeNullElements(List<@NullableType T> list) {
List<T> newList = new ArrayList<>();
for (@Nullable T item : list) {
if (item != null) {
newList.add(item);
}
}
return newList;
}
/**
* Converts {@link MediaControllerCompat#getFlags() session flags} and {@link
* MediaControllerCompat#isSessionReady whether session is ready} to {@link Player.Commands}.
*
* @param sessionFlags The session flag.
* @param isSessionReady Whether the session compat is ready.
* @return The converted player commands.
*/
public static Player.Commands convertToPlayerCommands(long sessionFlags, boolean isSessionReady) {
Commands.Builder playerCommandsBuilder = new Commands.Builder();
playerCommandsBuilder.addAll(
COMMAND_PLAY_PAUSE,
COMMAND_PREPARE,
COMMAND_STOP,
COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM,
COMMAND_SET_SPEED_AND_PITCH,
COMMAND_GET_DEVICE_VOLUME,
COMMAND_SET_DEVICE_VOLUME,
COMMAND_ADJUST_DEVICE_VOLUME,
COMMAND_GET_TIMELINE,
COMMAND_SEEK_TO_PREVIOUS,
COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM,
COMMAND_SEEK_TO_NEXT,
COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
COMMAND_GET_MEDIA_ITEMS_METADATA,
COMMAND_GET_CURRENT_MEDIA_ITEM,
COMMAND_SET_MEDIA_ITEM);
boolean includePlaylistCommands = (sessionFlags & FLAG_HANDLES_QUEUE_COMMANDS) != 0;
if (includePlaylistCommands) {
playerCommandsBuilder.add(COMMAND_CHANGE_MEDIA_ITEMS);
}
if (isSessionReady) {
playerCommandsBuilder.addAll(COMMAND_SET_SHUFFLE_MODE, COMMAND_SET_REPEAT_MODE);
}
return playerCommandsBuilder.build();
}
/**
* Converts {@link PlaybackStateCompat} to {@link SessionCommands}.
*
* <p>This ignores {@link PlaybackStateCompat#getActions() actions} in the {@link
* PlaybackStateCompat} to workaround media apps' issues that they don't set playback state
* correctly.
*
* @param state playback state
* @param isSessionReady Whether the session compat is ready.
* @return the converted session commands
*/
public static SessionCommands convertToSessionCommands(
@Nullable PlaybackStateCompat state, boolean isSessionReady) {
SessionCommands.Builder sessionCommandsBuilder = new SessionCommands.Builder();
sessionCommandsBuilder.addAllSessionCommands();
if (!isSessionReady) {
// Disables rating function when session isn't ready because of the
// MediaController#setRating(RatingCompat, Bundle) and MediaController#getRatingType().
sessionCommandsBuilder.remove(SessionCommand.COMMAND_CODE_SESSION_SET_RATING);
}
if (state != null && state.getCustomActions() != null) {
for (CustomAction customAction : state.getCustomActions()) {
String action = customAction.getAction();
@Nullable Bundle extras = customAction.getExtras();
sessionCommandsBuilder.add(
new SessionCommand(action, extras == null ? Bundle.EMPTY : extras));
}
}
return sessionCommandsBuilder.build();
}
/**
* Converts {@link CustomAction} in the {@link PlaybackStateCompat} to the custom layout which is
* the list of the {@link CommandButton}.
*
* @param state playback state
* @return custom layout. Always non-null.
*/
public static ImmutableList<CommandButton> convertToCustomLayout(
@Nullable PlaybackStateCompat state) {
if (state == null) {
return ImmutableList.of();
}
ImmutableList.Builder<CommandButton> layout = new ImmutableList.Builder<>();
for (CustomAction customAction : state.getCustomActions()) {
String action = customAction.getAction();
@Nullable Bundle extras = customAction.getExtras();
CommandButton button =
new CommandButton.Builder()
.setSessionCommand(new SessionCommand(action, extras == null ? Bundle.EMPTY : extras))
.setDisplayName(customAction.getName())
.setEnabled(true)
.setIconResId(customAction.getIcon())
.build();
layout.add(button);
}
return layout.build();
}
/** Converts {@link AudioAttributesCompat} into {@link AudioAttributes}. */
/*
* @AudioAttributesCompat.AttributeUsage and @C.AudioUsage both use the same constant values,
* defined by AudioAttributes in the platform.
*/
@SuppressLint("WrongConstant")
public static AudioAttributes convertToAudioAttributes(
@Nullable AudioAttributesCompat audioAttributesCompat) {
if (audioAttributesCompat == null) {
return AudioAttributes.DEFAULT;
}
return new AudioAttributes.Builder()
.setContentType(audioAttributesCompat.getContentType())
.setFlags(audioAttributesCompat.getFlags())
.setUsage(audioAttributesCompat.getUsage())
.build();
}
/** Converts {@link MediaControllerCompat.PlaybackInfo} to {@link AudioAttributes}. */
public static AudioAttributes convertToAudioAttributes(
@Nullable MediaControllerCompat.PlaybackInfo playbackInfoCompat) {
if (playbackInfoCompat == null) {
return AudioAttributes.DEFAULT;
}
return MediaUtils.convertToAudioAttributes(playbackInfoCompat.getAudioAttributes());
}
/** Converts {@link AudioAttributes} into {@link AudioAttributesCompat}. */
public static AudioAttributesCompat convertToAudioAttributesCompat(
AudioAttributes audioAttributes) {
return new AudioAttributesCompat.Builder()
.setContentType(audioAttributes.contentType)
.setFlags(audioAttributes.flags)
.setUsage(audioAttributes.usage)
.build();
}
/**
* Gets the legacy stream type from {@link AudioAttributes}.
*
* @param audioAttributes audio attributes
* @return int legacy stream type from {@link AudioManager}
*/
public static int getLegacyStreamType(AudioAttributes audioAttributes) {
int legacyStreamType =
MediaUtils.convertToAudioAttributesCompat(audioAttributes).getLegacyStreamType();
if (legacyStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
// Usually, AudioAttributesCompat#getLegacyStreamType() does not return
// USE_DEFAULT_STREAM_TYPE unless the developer sets it with
// AudioAttributesCompat.Builder#setLegacyStreamType().
// But for safety, let's convert USE_DEFAULT_STREAM_TYPE to STREAM_MUSIC here.
return AudioManager.STREAM_MUSIC;
}
return legacyStreamType;
}
public static <T> T getFutureResult(Future<T> future, long timeoutMs)
throws ExecutionException, TimeoutException {
long initialTimeMs = SystemClock.elapsedRealtime();
long remainingTimeMs = timeoutMs;
boolean interrupted = false;
try {
while (true) {
try {
return future.get(remainingTimeMs, MILLISECONDS);
} catch (InterruptedException e) {
interrupted = true;
long elapsedTimeMs = SystemClock.elapsedRealtime() - initialTimeMs;
if (elapsedTimeMs >= timeoutMs) {
throw new TimeoutException();
}
remainingTimeMs = timeoutMs - elapsedTimeMs;
}
}
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
/** Converts {@link MediaControllerCompat.PlaybackInfo} to {@link DeviceInfo}. */
public static DeviceInfo convertToDeviceInfo(
@Nullable MediaControllerCompat.PlaybackInfo playbackInfoCompat) {
if (playbackInfoCompat == null) {
return DeviceInfo.UNKNOWN;
}
return new DeviceInfo(
playbackInfoCompat.getPlaybackType()
== MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE
? DeviceInfo.PLAYBACK_TYPE_REMOTE
: DeviceInfo.PLAYBACK_TYPE_LOCAL,
/* minVolume= */ 0,
playbackInfoCompat.getMaxVolume());
}
/** Converts {@link MediaControllerCompat.PlaybackInfo} to device volume. */
public static int convertToDeviceVolume(
@Nullable MediaControllerCompat.PlaybackInfo playbackInfoCompat) {
if (playbackInfoCompat == null) {
return 0;
}
return playbackInfoCompat.getCurrentVolume();
}
/** Converts {@link MediaControllerCompat.PlaybackInfo} to device muted. */
public static boolean convertToIsDeviceMuted(
@Nullable MediaControllerCompat.PlaybackInfo playbackInfoCompat) {
if (playbackInfoCompat == null) {
return false;
}
return playbackInfoCompat.getCurrentVolume() == 0;
}
public static Commands createPlayerCommandsWith(@Command int command) {
return new Commands.Builder().add(command).build();
}
public static Commands createPlayerCommandsWithout(@Command int command) {
return new Commands.Builder().addAllCommands().remove(command).build();
}
/**
* Returns the intersection of {@link Player.Command commands} from the given two {@link
* Commands}.
*/
public static Commands intersect(Commands commands1, Commands commands2) {
Commands.Builder intersectCommandsBuilder = new Commands.Builder();
for (int i = 0; i < commands1.size(); i++) {
if (commands2.contains(commands1.get(i))) {
intersectCommandsBuilder.add(commands1.get(i));
}
}
return intersectCommandsBuilder.build();
}
private static byte[] convertToByteArray(Bitmap bitmap) throws IOException {
try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
bitmap.compress(Bitmap.CompressFormat.PNG, /* ignored */ 0, stream);
return stream.toByteArray();
}
}
public static int[] generateUnshuffledIndices(int n) {
int[] indices = new int[n];
for (int i = 0; i < n; i++) {
indices[i] = i;
}
return indices;
}
public static int calculateBufferedPercentage(long bufferedPositionMs, long durationMs) {
return bufferedPositionMs == C.TIME_UNSET || durationMs == C.TIME_UNSET
? 0
: durationMs == 0
? 100
: Util.constrainValue((int) ((bufferedPositionMs * 100) / durationMs), 0, 100);
}
private MediaUtils() {}
}