mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Add metadata field durationMs
PiperOrigin-RevId: 628038241
This commit is contained in:
parent
ed1cf35f30
commit
0e3b05c67d
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package androidx.media3.common;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
@ -53,6 +54,7 @@ public final class MediaMetadata implements Bundleable {
|
||||
@Nullable private CharSequence displayTitle;
|
||||
@Nullable private CharSequence subtitle;
|
||||
@Nullable private CharSequence description;
|
||||
@Nullable private Long durationMs;
|
||||
@Nullable private Rating userRating;
|
||||
@Nullable private Rating overallRating;
|
||||
@Nullable private byte[] artworkData;
|
||||
@ -95,6 +97,7 @@ public final class MediaMetadata implements Bundleable {
|
||||
this.displayTitle = mediaMetadata.displayTitle;
|
||||
this.subtitle = mediaMetadata.subtitle;
|
||||
this.description = mediaMetadata.description;
|
||||
this.durationMs = mediaMetadata.durationMs;
|
||||
this.userRating = mediaMetadata.userRating;
|
||||
this.overallRating = mediaMetadata.overallRating;
|
||||
this.artworkData = mediaMetadata.artworkData;
|
||||
@ -176,6 +179,23 @@ public final class MediaMetadata implements Bundleable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the optional duration, non-negative and in milliseconds.
|
||||
*
|
||||
* <p>The duration is populated by the app when building the metadata object and is for
|
||||
* informational purpose only. For retrieving the duration of the media item currently being
|
||||
* played, use {@link Player#getDuration()} instead.
|
||||
*
|
||||
* @throws IllegalArgumentException if the duration is negative.
|
||||
*/
|
||||
@UnstableApi
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setDurationMs(@Nullable Long durationMs) {
|
||||
checkArgument(durationMs == null || durationMs >= 0);
|
||||
this.durationMs = durationMs;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the user {@link Rating}. */
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setUserRating(@Nullable Rating userRating) {
|
||||
@ -496,6 +516,9 @@ public final class MediaMetadata implements Bundleable {
|
||||
if (mediaMetadata.description != null) {
|
||||
setDescription(mediaMetadata.description);
|
||||
}
|
||||
if (mediaMetadata.durationMs != null) {
|
||||
setDurationMs(mediaMetadata.durationMs);
|
||||
}
|
||||
if (mediaMetadata.userRating != null) {
|
||||
setUserRating(mediaMetadata.userRating);
|
||||
}
|
||||
@ -978,6 +1001,15 @@ public final class MediaMetadata implements Bundleable {
|
||||
/** Optional description. */
|
||||
@Nullable public final CharSequence description;
|
||||
|
||||
/**
|
||||
* Optional duration, non-negative and in milliseconds.
|
||||
*
|
||||
* <p>This field is populated by the app when building the metadata object and is for
|
||||
* informational purpose only. For retrieving the duration of the media item currently being
|
||||
* played, use {@link Player#getDuration()} instead.
|
||||
*/
|
||||
@UnstableApi @Nullable public final Long durationMs;
|
||||
|
||||
/** Optional user {@link Rating}. */
|
||||
@Nullable public final Rating userRating;
|
||||
|
||||
@ -1116,6 +1148,7 @@ public final class MediaMetadata implements Bundleable {
|
||||
this.displayTitle = builder.displayTitle;
|
||||
this.subtitle = builder.subtitle;
|
||||
this.description = builder.description;
|
||||
this.durationMs = builder.durationMs;
|
||||
this.userRating = builder.userRating;
|
||||
this.overallRating = builder.overallRating;
|
||||
this.artworkData = builder.artworkData;
|
||||
@ -1168,6 +1201,7 @@ public final class MediaMetadata implements Bundleable {
|
||||
&& Util.areEqual(displayTitle, that.displayTitle)
|
||||
&& Util.areEqual(subtitle, that.subtitle)
|
||||
&& Util.areEqual(description, that.description)
|
||||
&& Util.areEqual(durationMs, that.durationMs)
|
||||
&& Util.areEqual(userRating, that.userRating)
|
||||
&& Util.areEqual(overallRating, that.overallRating)
|
||||
&& Arrays.equals(artworkData, that.artworkData)
|
||||
@ -1207,6 +1241,7 @@ public final class MediaMetadata implements Bundleable {
|
||||
displayTitle,
|
||||
subtitle,
|
||||
description,
|
||||
durationMs,
|
||||
userRating,
|
||||
overallRating,
|
||||
Arrays.hashCode(artworkData),
|
||||
@ -1270,6 +1305,7 @@ public final class MediaMetadata implements Bundleable {
|
||||
private static final String FIELD_STATION = Util.intToStringMaxRadix(30);
|
||||
private static final String FIELD_MEDIA_TYPE = Util.intToStringMaxRadix(31);
|
||||
private static final String FIELD_IS_BROWSABLE = Util.intToStringMaxRadix(32);
|
||||
private static final String FIELD_DURATION_MS = Util.intToStringMaxRadix(33);
|
||||
private static final String FIELD_EXTRAS = Util.intToStringMaxRadix(1000);
|
||||
|
||||
@SuppressWarnings("deprecation") // Bundling deprecated fields.
|
||||
@ -1298,6 +1334,9 @@ public final class MediaMetadata implements Bundleable {
|
||||
if (description != null) {
|
||||
bundle.putCharSequence(FIELD_DESCRIPTION, description);
|
||||
}
|
||||
if (durationMs != null) {
|
||||
bundle.putLong(FIELD_DURATION_MS, durationMs);
|
||||
}
|
||||
if (artworkData != null) {
|
||||
bundle.putByteArray(FIELD_ARTWORK_DATA, artworkData);
|
||||
}
|
||||
@ -1428,6 +1467,9 @@ public final class MediaMetadata implements Bundleable {
|
||||
builder.setOverallRating(Rating.fromBundle(fieldBundle));
|
||||
}
|
||||
}
|
||||
if (bundle.containsKey(FIELD_DURATION_MS)) {
|
||||
builder.setDurationMs(bundle.getLong(FIELD_DURATION_MS));
|
||||
}
|
||||
if (bundle.containsKey(FIELD_TRACK_NUMBER)) {
|
||||
builder.setTrackNumber(bundle.getInt(FIELD_TRACK_NUMBER));
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ public class MediaMetadataTest {
|
||||
assertThat(mediaMetadata.displayTitle).isNull();
|
||||
assertThat(mediaMetadata.subtitle).isNull();
|
||||
assertThat(mediaMetadata.description).isNull();
|
||||
assertThat(mediaMetadata.durationMs).isNull();
|
||||
assertThat(mediaMetadata.userRating).isNull();
|
||||
assertThat(mediaMetadata.overallRating).isNull();
|
||||
assertThat(mediaMetadata.artworkData).isNull();
|
||||
@ -121,7 +122,7 @@ public class MediaMetadataTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void populate_withArtworkDataOnly_updatesBothArtWorkUriAndArtworkData() {
|
||||
public void populate_withArtworkDataOnly_updatesBothArtworkUriAndArtworkData() {
|
||||
byte[] artworkData = new byte[] {35, 12, 6, 77};
|
||||
MediaMetadata mediaMetadata =
|
||||
new MediaMetadata.Builder()
|
||||
@ -251,6 +252,7 @@ public class MediaMetadataTest {
|
||||
.setDisplayTitle("display title")
|
||||
.setSubtitle("subtitle")
|
||||
.setDescription("description")
|
||||
.setDurationMs(10_000L)
|
||||
.setUserRating(new HeartRating(false))
|
||||
.setOverallRating(new PercentageRating(87.4f))
|
||||
.setArtworkData(
|
||||
|
@ -315,49 +315,6 @@ import java.util.concurrent.TimeoutException;
|
||||
return period;
|
||||
}
|
||||
|
||||
/** Converts a {@link MediaItem} to a {@link MediaDescriptionCompat} */
|
||||
@SuppressWarnings("deprecation") // Converting deprecated fields.
|
||||
public static MediaDescriptionCompat convertToMediaDescriptionCompat(
|
||||
MediaItem item, @Nullable Bitmap artworkBitmap) {
|
||||
MediaDescriptionCompat.Builder builder =
|
||||
new MediaDescriptionCompat.Builder()
|
||||
.setMediaId(item.mediaId.equals(MediaItem.DEFAULT_MEDIA_ID) ? null : item.mediaId);
|
||||
MediaMetadata metadata = item.mediaMetadata;
|
||||
if (artworkBitmap != null) {
|
||||
builder.setIconBitmap(artworkBitmap);
|
||||
}
|
||||
@Nullable Bundle extras = metadata.extras;
|
||||
boolean hasFolderType =
|
||||
metadata.folderType != null && metadata.folderType != MediaMetadata.FOLDER_TYPE_NONE;
|
||||
boolean hasMediaType = metadata.mediaType != null;
|
||||
if (hasFolderType || hasMediaType) {
|
||||
if (extras == null) {
|
||||
extras = new Bundle();
|
||||
} else {
|
||||
extras = new Bundle(extras);
|
||||
}
|
||||
if (hasFolderType) {
|
||||
extras.putLong(
|
||||
MediaDescriptionCompat.EXTRA_BT_FOLDER_TYPE,
|
||||
convertToExtraBtFolderType(checkNotNull(metadata.folderType)));
|
||||
}
|
||||
if (hasMediaType) {
|
||||
extras.putLong(
|
||||
MediaConstants.EXTRAS_KEY_MEDIA_TYPE_COMPAT, checkNotNull(metadata.mediaType));
|
||||
}
|
||||
}
|
||||
return builder
|
||||
.setTitle(metadata.title)
|
||||
// The BT AVRPC service expects the subtitle of the media description to be the artist
|
||||
// (see https://github.com/androidx/media/issues/148).
|
||||
.setSubtitle(metadata.artist != null ? metadata.artist : 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) {
|
||||
@ -450,6 +407,15 @@ import java.util.concurrent.TimeoutException;
|
||||
.setOverallRating(
|
||||
convertToRating(metadataCompat.getRating(MediaMetadataCompat.METADATA_KEY_RATING)));
|
||||
|
||||
if (metadataCompat.containsKey(MediaMetadataCompat.METADATA_KEY_DURATION)) {
|
||||
long durationMs = metadataCompat.getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
|
||||
if (durationMs >= 0) {
|
||||
// Only set duration if a non-negative is set. Do not assert because we don't want the app
|
||||
// to crash because an external app sends a negative value that is valid in media1.
|
||||
builder.setDurationMs(durationMs);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Rating userRating =
|
||||
convertToRating(metadataCompat.getRating(MediaMetadataCompat.METADATA_KEY_USER_RATING));
|
||||
@ -621,6 +587,10 @@ import java.util.concurrent.TimeoutException;
|
||||
convertToExtraBtFolderType(metadata.folderType));
|
||||
}
|
||||
|
||||
if (durationMs == C.TIME_UNSET && metadata.durationMs != null) {
|
||||
// If the actual media duration is unknown, use the manually declared value if available.
|
||||
durationMs = metadata.durationMs;
|
||||
}
|
||||
if (durationMs != C.TIME_UNSET) {
|
||||
builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, durationMs);
|
||||
}
|
||||
@ -656,6 +626,49 @@ import java.util.concurrent.TimeoutException;
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/** Converts a {@link MediaItem} to a {@link MediaDescriptionCompat} */
|
||||
@SuppressWarnings("deprecation") // Converting deprecated fields.
|
||||
public static MediaDescriptionCompat convertToMediaDescriptionCompat(
|
||||
MediaItem item, @Nullable Bitmap artworkBitmap) {
|
||||
MediaDescriptionCompat.Builder builder =
|
||||
new MediaDescriptionCompat.Builder()
|
||||
.setMediaId(item.mediaId.equals(MediaItem.DEFAULT_MEDIA_ID) ? null : item.mediaId);
|
||||
MediaMetadata metadata = item.mediaMetadata;
|
||||
if (artworkBitmap != null) {
|
||||
builder.setIconBitmap(artworkBitmap);
|
||||
}
|
||||
@Nullable Bundle extras = metadata.extras;
|
||||
boolean hasFolderType =
|
||||
metadata.folderType != null && metadata.folderType != MediaMetadata.FOLDER_TYPE_NONE;
|
||||
boolean hasMediaType = metadata.mediaType != null;
|
||||
if (hasFolderType || hasMediaType) {
|
||||
if (extras == null) {
|
||||
extras = new Bundle();
|
||||
} else {
|
||||
extras = new Bundle(extras);
|
||||
}
|
||||
if (hasFolderType) {
|
||||
extras.putLong(
|
||||
MediaDescriptionCompat.EXTRA_BT_FOLDER_TYPE,
|
||||
convertToExtraBtFolderType(checkNotNull(metadata.folderType)));
|
||||
}
|
||||
if (hasMediaType) {
|
||||
extras.putLong(
|
||||
MediaConstants.EXTRAS_KEY_MEDIA_TYPE_COMPAT, checkNotNull(metadata.mediaType));
|
||||
}
|
||||
}
|
||||
return builder
|
||||
.setTitle(metadata.title)
|
||||
// The BT AVRPC service expects the subtitle of the media description to be the artist
|
||||
// (see https://github.com/androidx/media/issues/148).
|
||||
.setSubtitle(metadata.artist != null ? metadata.artist : metadata.subtitle)
|
||||
.setDescription(metadata.description)
|
||||
.setIconUri(metadata.artworkUri)
|
||||
.setMediaUri(item.requestMetadata.mediaUri)
|
||||
.setExtras(extras)
|
||||
.build();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Converting to deprecated constants.
|
||||
@MediaMetadata.FolderType
|
||||
private static int convertToFolderType(long extraBtFolderType) {
|
||||
|
@ -132,7 +132,7 @@ public final class LegacyConversionsTest {
|
||||
|
||||
@Test
|
||||
public void convertToQueueItem_withArtworkData() throws Exception {
|
||||
MediaItem mediaItem = createMediaItemWithArtworkData("testId");
|
||||
MediaItem mediaItem = createMediaItemWithArtworkData("testId", /* durationMs= */ 10_000L);
|
||||
MediaMetadata mediaMetadata = mediaItem.mediaMetadata;
|
||||
ListenableFuture<Bitmap> bitmapFuture = bitmapLoader.decodeBitmap(mediaMetadata.artworkData);
|
||||
@Nullable Bitmap bitmap = bitmapFuture.get(10, SECONDS);
|
||||
@ -158,9 +158,11 @@ public final class LegacyConversionsTest {
|
||||
.setTitle(title)
|
||||
.setDescription(description)
|
||||
.setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC)
|
||||
.setDurationMs(10_000L)
|
||||
.build();
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder().setMediaId(mediaId).setMediaMetadata(metadata).build();
|
||||
|
||||
MediaDescriptionCompat descriptionCompat =
|
||||
LegacyConversions.convertToMediaDescriptionCompat(mediaItem, /* artworkBitmap= */ null);
|
||||
|
||||
@ -212,7 +214,7 @@ public final class LegacyConversionsTest {
|
||||
@Test
|
||||
public void convertToMediaMetadata_roundTripViaMediaMetadataCompat_returnsEqualMediaItemMetadata()
|
||||
throws Exception {
|
||||
MediaItem testMediaItem = createMediaItemWithArtworkData("testZZZ");
|
||||
MediaItem testMediaItem = createMediaItemWithArtworkData("testZZZ", /* durationMs= */ 10_000L);
|
||||
MediaMetadata testMediaMetadata = testMediaItem.mediaMetadata;
|
||||
@Nullable Bitmap testArtworkBitmap = null;
|
||||
@Nullable
|
||||
@ -225,7 +227,7 @@ public final class LegacyConversionsTest {
|
||||
testMediaMetadata,
|
||||
"mediaId",
|
||||
Uri.parse("http://example.com"),
|
||||
/* durationMs= */ 100L,
|
||||
/* durationMs= */ C.TIME_UNSET,
|
||||
testArtworkBitmap);
|
||||
|
||||
MediaMetadata mediaMetadata =
|
||||
@ -239,7 +241,8 @@ public final class LegacyConversionsTest {
|
||||
public void
|
||||
convertToMediaMetadata_roundTripViaMediaDescriptionCompat_returnsEqualMediaItemMetadata()
|
||||
throws Exception {
|
||||
MediaItem testMediaItem = createMediaItemWithArtworkData("testZZZ");
|
||||
MediaItem testMediaItem =
|
||||
createMediaItemWithArtworkData("testZZZ", /* durationMs= */ C.TIME_UNSET);
|
||||
MediaMetadata testMediaMetadata = testMediaItem.mediaMetadata;
|
||||
@Nullable Bitmap testArtworkBitmap = null;
|
||||
@Nullable
|
||||
@ -1152,12 +1155,21 @@ public final class LegacyConversionsTest {
|
||||
return list.build();
|
||||
}
|
||||
|
||||
private static MediaItem createMediaItemWithArtworkData(String mediaId) {
|
||||
private static MediaItem createMediaItemWithArtworkData(String mediaId, long durationMs) {
|
||||
Bundle extras = new Bundle();
|
||||
extras.putLong(
|
||||
MediaConstants.EXTRAS_KEY_IS_EXPLICIT, MediaConstants.EXTRAS_VALUE_ATTRIBUTE_PRESENT);
|
||||
MediaMetadata.Builder mediaMetadataBuilder =
|
||||
new MediaMetadata.Builder()
|
||||
.setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
|
||||
.setIsBrowsable(false)
|
||||
.setIsPlayable(true);
|
||||
.setIsPlayable(true)
|
||||
.setExtras(extras);
|
||||
|
||||
if (durationMs != C.TIME_UNSET) {
|
||||
mediaMetadataBuilder.setDurationMs(durationMs);
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] artworkData;
|
||||
Bitmap bitmap =
|
||||
|
Loading…
x
Reference in New Issue
Block a user