Skip to content

Commit

Permalink
Load bitmaps for MediaSessionCompat.QueueItem.
Browse files Browse the repository at this point in the history
When receiving the `onTimelineChanged` callback, we convert the timeline to the list of `QueueItem`s, where decoding a bitmap is needed for building each of the `QueueItem`s. The strategy is similar to what we did in <unknown commit> for list of `MediaBrowserCompat.MediaItem` - set the queue item list until the bitmaps decoding for all the `MediaItem`s are completed.

PiperOrigin-RevId: 490283587
  • Loading branch information
tianyif authored and icbaker committed Nov 24, 2022
1 parent af1d7b9 commit 8ce1213
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,14 @@
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import org.checkerframework.checker.initialization.qual.Initialized;
import org.checkerframework.checker.nullness.compatqual.NullableType;

// Getting the commands from MediaControllerCompat'
/* package */ class MediaSessionLegacyStub extends MediaSessionCompat.Callback {
Expand Down Expand Up @@ -394,7 +397,7 @@ public void onSkipToQueueItem(long queueId) {
controller -> {
PlayerWrapper playerWrapper = sessionImpl.getPlayerWrapper();
// Use queueId as an index as we've published {@link QueueItem} as so.
// see: {@link MediaUtils#convertToQueueItemList}.
// see: {@link MediaUtils#convertToQueueItem}.
playerWrapper.seekToDefaultPosition((int) queueId);
},
sessionCompat.getCurrentControllerInfo());
Expand Down Expand Up @@ -1011,8 +1014,59 @@ public void onTimelineChanged(
setQueue(sessionCompat, null);
return;
}

updateQueue(timeline);

// Duration might be unknown at onMediaItemTransition and become available afterward.
updateMetadataIfChanged();
}

private void updateQueue(Timeline timeline) {
List<MediaItem> mediaItemList = MediaUtils.convertToMediaItemList(timeline);
List<QueueItem> queueItemList = MediaUtils.convertToQueueItemList(mediaItemList);
List<@NullableType ListenableFuture<Bitmap>> bitmapFutures = new ArrayList<>();
final AtomicInteger resultCount = new AtomicInteger(0);
Runnable handleBitmapFuturesTask =
() -> {
int completedBitmapFutureCount = resultCount.incrementAndGet();
if (completedBitmapFutureCount == mediaItemList.size()) {
handleBitmapFuturesAllCompletedAndSetQueue(bitmapFutures, timeline, mediaItemList);
}
};

for (int i = 0; i < mediaItemList.size(); i++) {
MediaItem mediaItem = mediaItemList.get(i);
MediaMetadata metadata = mediaItem.mediaMetadata;
if (metadata.artworkData == null) {
bitmapFutures.add(null);
handleBitmapFuturesTask.run();
} else {
ListenableFuture<Bitmap> bitmapFuture =
sessionImpl.getBitmapLoader().decodeBitmap(metadata.artworkData);
bitmapFutures.add(bitmapFuture);
bitmapFuture.addListener(
handleBitmapFuturesTask, sessionImpl.getApplicationHandler()::post);
}
}
}

private void handleBitmapFuturesAllCompletedAndSetQueue(
List<@NullableType ListenableFuture<Bitmap>> bitmapFutures,
Timeline timeline,
List<MediaItem> mediaItems) {
List<QueueItem> queueItemList = new ArrayList<>();
for (int i = 0; i < bitmapFutures.size(); i++) {
@Nullable ListenableFuture<Bitmap> future = bitmapFutures.get(i);
@Nullable Bitmap bitmap = null;
if (future != null) {
try {
bitmap = Futures.getDone(future);
} catch (CancellationException | ExecutionException e) {
Log.d(TAG, "Failed to get bitmap");
}
}
queueItemList.add(MediaUtils.convertToQueueItem(mediaItems.get(i), i, bitmap));
}

if (Util.SDK_INT < 21) {
// In order to avoid TransactionTooLargeException for below API 21, we need to
// cut the list so that it doesn't exceed the binder transaction limit.
Expand All @@ -1029,9 +1083,6 @@ public void onTimelineChanged(
// which means we can safely send long lists.
sessionCompat.setQueue(queueItemList);
}

// Duration might be unknown at onMediaItemTransition and become available afterward.
updateMetadataIfChanged();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,18 +230,14 @@ public static List<MediaItem> convertToMediaItemList(Timeline timeline) {
}

/**
* Converts a list of {@link MediaItem} to a list of {@link QueueItem}. The index of the item
* Converts a {@link MediaItem} to a {@link QueueItem}. The index of the item in the playlist
* 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;
public static QueueItem convertToQueueItem(
MediaItem item, int mediaItemIndex, @Nullable Bitmap artworkBitmap) {
MediaDescriptionCompat description = convertToMediaDescriptionCompat(item, artworkBitmap);
long id = convertToQueueItemId(mediaItemIndex);
return new QueueItem(description, id);
}

/** Converts the index of a {@link MediaItem} in a playlist into id of {@link QueueItem}. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1121,7 +1121,7 @@ public void onQueueChanged(List<QueueItem> queue) {
MediaItem mediaItem =
new MediaItem.Builder()
.setMediaId("mediaItem_withSampleMediaMetadata")
.setMediaMetadata(MediaTestUtils.createMediaMetadata())
.setMediaMetadata(MediaTestUtils.createMediaMetadataWithArtworkData())
.build();
Timeline timeline = new PlaylistTimeline(ImmutableList.of(mediaItem));

Expand All @@ -1140,6 +1140,7 @@ public void onQueueChanged(List<QueueItem> queue) {
.isTrue();
assertThat(description.getIconUri()).isEqualTo(mediaItem.mediaMetadata.artworkUri);
assertThat(description.getMediaUri()).isEqualTo(mediaItem.requestMetadata.mediaUri);
assertThat(description.getIconBitmap()).isNotNull();
assertThat(TestUtils.equals(description.getExtras(), mediaItem.mediaMetadata.extras)).isTrue();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

import android.content.Context;
import android.graphics.Bitmap;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.RatingCompat;
import android.support.v4.media.session.MediaSessionCompat;
Expand All @@ -42,6 +45,7 @@
import androidx.media3.common.Player.PositionInfo;
import androidx.media3.common.Timeline;
import androidx.media3.common.Timeline.Window;
import androidx.media3.common.util.Util;
import androidx.media3.test.session.common.HandlerThreadTestRule;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
Expand Down Expand Up @@ -73,11 +77,13 @@ public class MediaControllerMediaSessionCompatCallbackAggregationTest {

private Context context;
private RemoteMediaSessionCompat session;
private BitmapLoader bitmapLoader;

@Before
public void setUp() throws Exception {
context = ApplicationProvider.getApplicationContext();
session = new RemoteMediaSessionCompat(DEFAULT_TEST_NAME, context);
bitmapLoader = new CacheBitmapLoader(new SimpleBitmapLoader());
}

@After
Expand All @@ -88,8 +94,8 @@ public void cleanUp() throws Exception {
@Test
public void getters_withValidQueueAndQueueIdAndMetadata() throws Exception {
int testSize = 3;
List<MediaItem> testMediaItems = MediaTestUtils.createMediaItems(testSize);
List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testMediaItems);
List<MediaItem> testMediaItems = MediaTestUtils.createMediaItemsWithArtworkData(testSize);
List<QueueItem> testQueue = convertToQueueItems(testMediaItems);
int testMediaItemIndex = 1;
MediaMetadataCompat testMediaMetadataCompat = createMediaMetadataCompat();
@RatingCompat.Style int testRatingType = RatingCompat.RATING_HEART;
Expand Down Expand Up @@ -173,8 +179,28 @@ public void onEvents(Player player, Events events) {
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(mediaItemRef.get()).isEqualTo(testCurrentMediaItem);
for (int i = 0; i < timelineRef.get().getWindowCount(); i++) {
assertThat(timelineRef.get().getWindow(i, new Window()).mediaItem)
.isEqualTo(i == testMediaItemIndex ? testCurrentMediaItem : testMediaItems.get(i));
MediaItem mediaItem = timelineRef.get().getWindow(i, new Window()).mediaItem;
MediaItem expectedMediaItem =
(i == testMediaItemIndex) ? testCurrentMediaItem : testMediaItems.get(i);
if (Util.SDK_INT < 21) {
// Bitmap conversion and back gives not exactly the same byte array below API 21
MediaMetadata mediaMetadata =
mediaItem
.mediaMetadata
.buildUpon()
.setArtworkData(/* artworkData= */ null, /* artworkDataType= */ null)
.build();
MediaMetadata expectedMediaMetadata =
expectedMediaItem
.mediaMetadata
.buildUpon()
.setArtworkData(/* artworkData= */ null, /* artworkDataType= */ null)
.build();
mediaItem = mediaItem.buildUpon().setMediaMetadata(mediaMetadata).build();
expectedMediaItem =
expectedMediaItem.buildUpon().setMediaMetadata(expectedMediaMetadata).build();
}
assertThat(mediaItem).isEqualTo(expectedMediaItem);
}
assertThat(timelineChangeReasonRef.get()).isEqualTo(TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED);
assertThat(mediaItemTransitionReasonRef.get())
Expand Down Expand Up @@ -202,7 +228,7 @@ public void onEvents(Player player, Events events) {
public void getters_withValidQueueAndMetadataButWithInvalidQueueId() throws Exception {
int testSize = 3;
List<MediaItem> testMediaItems = MediaTestUtils.createMediaItems(testSize);
List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testMediaItems);
List<QueueItem> testQueue = MediaTestUtils.convertToQueueItemsWithoutBitmap(testMediaItems);
MediaMetadataCompat testMediaMetadataCompat = createMediaMetadataCompat();
@RatingCompat.Style int testRatingType = RatingCompat.RATING_HEART;
MediaMetadata testMediaMetadata =
Expand Down Expand Up @@ -306,7 +332,7 @@ public void onEvents(Player player, Events events) {
public void getters_withValidQueueAndQueueIdWithoutMetadata() throws Exception {
int testSize = 3;
List<MediaItem> testMediaItems = MediaTestUtils.createMediaItems(testSize);
List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testMediaItems);
List<QueueItem> testQueue = MediaTestUtils.convertToQueueItemsWithoutBitmap(testMediaItems);
@RatingCompat.Style int testRatingType = RatingCompat.RATING_HEART;
Events testEvents =
new Events(
Expand Down Expand Up @@ -511,4 +537,18 @@ private static void assertTimelineEqualsToMediaItems(
.isEqualTo(mediaItems.get(i));
}
}

private List<MediaSessionCompat.QueueItem> convertToQueueItems(List<MediaItem> mediaItems)
throws Exception {
List<MediaSessionCompat.QueueItem> list = new ArrayList<>();
for (int i = 0; i < mediaItems.size(); i++) {
MediaItem item = mediaItems.get(i);
@Nullable
Bitmap bitmap = bitmapLoader.decodeBitmap(item.mediaMetadata.artworkData).get(10, SECONDS);
MediaDescriptionCompat description = MediaUtils.convertToMediaDescriptionCompat(item, bitmap);
long id = MediaUtils.convertToQueueItemId(i);
list.add(new MediaSessionCompat.QueueItem(description, id));
}
return list;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ public void onEvents(Player player, Player.Events events) {
@Test
public void seekTo_withNewMediaItemIndex() throws Exception {
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems(3);
List<QueueItem> queue = MediaUtils.convertToQueueItemList(mediaItems);
List<QueueItem> queue = MediaTestUtils.convertToQueueItemsWithoutBitmap(mediaItems);
long initialPosition = 8_000;
long initialBufferedPosition = 9_200;
int initialIndex = 0;
Expand Down Expand Up @@ -701,7 +701,7 @@ public void onEvents(Player player, Player.Events events) {
@Test
public void addMediaItems() throws Exception {
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems("a", "b", "c");
List<QueueItem> queue = MediaUtils.convertToQueueItemList(mediaItems);
List<QueueItem> queue = MediaTestUtils.convertToQueueItemsWithoutBitmap(mediaItems);
long testPosition = 200L;
int testCurrentMediaItemIndex = 1;
MediaItem testCurrentMediaItem = mediaItems.get(testCurrentMediaItemIndex);
Expand Down Expand Up @@ -767,7 +767,7 @@ public void onEvents(Player player, Player.Events events) {
public void addMediaItems_beforeCurrentMediaItemIndex_shiftsCurrentMediaItemIndex()
throws Exception {
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems("a", "b", "c");
List<QueueItem> queue = MediaUtils.convertToQueueItemList(mediaItems);
List<QueueItem> queue = MediaTestUtils.convertToQueueItemsWithoutBitmap(mediaItems);
long testPosition = 200L;
int initialMediaItemIndex = 2;
MediaItem testCurrentMediaItem = mediaItems.get(initialMediaItemIndex);
Expand Down Expand Up @@ -833,7 +833,7 @@ public void onEvents(Player player, Player.Events events) {
@Test
public void removeMediaItems() throws Exception {
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems(5);
List<QueueItem> queue = MediaUtils.convertToQueueItemList(mediaItems);
List<QueueItem> queue = MediaTestUtils.convertToQueueItemsWithoutBitmap(mediaItems);
long testPosition = 200L;
int testCurrentMediaItemIndex = 0;
MediaItem testCurrentMediaItem = mediaItems.get(testCurrentMediaItemIndex);
Expand Down Expand Up @@ -898,7 +898,7 @@ public void onEvents(Player player, Player.Events events) {
public void removeMediaItems_beforeCurrentMediaItemIndex_shiftsCurrentMediaItemIndex()
throws Exception {
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems(5);
List<QueueItem> queue = MediaUtils.convertToQueueItemList(mediaItems);
List<QueueItem> queue = MediaTestUtils.convertToQueueItemsWithoutBitmap(mediaItems);
long testPosition = 200L;
int initialMediaItemIndex = 4;
MediaItem testCurrentMediaItem = mediaItems.get(initialMediaItemIndex);
Expand Down Expand Up @@ -963,7 +963,7 @@ public void onEvents(Player player, Player.Events events) {
@Test
public void removeMediaItems_includeCurrentMediaItem_movesCurrentItem() throws Exception {
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems(5);
List<QueueItem> queue = MediaUtils.convertToQueueItemList(mediaItems);
List<QueueItem> queue = MediaTestUtils.convertToQueueItemsWithoutBitmap(mediaItems);
long testPosition = 200L;
int initialMediaItemIndex = 2;
MediaItem testCurrentMediaItem = mediaItems.get(initialMediaItemIndex);
Expand Down Expand Up @@ -1025,7 +1025,7 @@ public void onEvents(Player player, Player.Events events) {
@Test
public void moveMediaItems() throws Exception {
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems(5);
List<QueueItem> queue = MediaUtils.convertToQueueItemList(mediaItems);
List<QueueItem> queue = MediaTestUtils.convertToQueueItemsWithoutBitmap(mediaItems);
long testPosition = 200L;
int testCurrentMediaItemIndex = 0;
MediaItem testCurrentMediaItem = mediaItems.get(testCurrentMediaItemIndex);
Expand Down Expand Up @@ -1090,7 +1090,7 @@ public void onEvents(Player player, Player.Events events) {
@Test
public void moveMediaItems_withMovingCurrentMediaItem_changesCurrentItem() throws Exception {
List<MediaItem> mediaItems = MediaTestUtils.createMediaItems(5);
List<QueueItem> queue = MediaUtils.convertToQueueItemList(mediaItems);
List<QueueItem> queue = MediaTestUtils.convertToQueueItemsWithoutBitmap(mediaItems);
long testPosition = 200L;
int initialCurrentMediaItemIndex = 1;
session.setPlaybackState(
Expand Down
Loading

0 comments on commit 8ce1213

Please sign in to comment.