Set duration in QueueTimeline

If the duration is reported in MediaMetadataCompat, it should
also be set in the QueueTimeline to match controller.getDuration()

PiperOrigin-RevId: 522022953
This commit is contained in:
tonihei 2023-04-05 13:17:58 +01:00 committed by Marc Baechinger
parent d66dd50263
commit 5b1370e686
3 changed files with 86 additions and 63 deletions

View file

@ -1804,6 +1804,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
long oldActiveQueueId = getActiveQueueId(oldLegacyPlayerInfo.playbackStateCompat); long oldActiveQueueId = getActiveQueueId(oldLegacyPlayerInfo.playbackStateCompat);
long newActiveQueueId = getActiveQueueId(newLegacyPlayerInfo.playbackStateCompat); long newActiveQueueId = getActiveQueueId(newLegacyPlayerInfo.playbackStateCompat);
boolean isCurrentActiveQueueIdChanged = (oldActiveQueueId != newActiveQueueId) || initialUpdate; boolean isCurrentActiveQueueIdChanged = (oldActiveQueueId != newActiveQueueId) || initialUpdate;
long durationMs = MediaUtils.convertToDurationMs(newLegacyPlayerInfo.mediaMetadataCompat);
if (isMetadataCompatChanged || isCurrentActiveQueueIdChanged || isQueueChanged) { if (isMetadataCompatChanged || isCurrentActiveQueueIdChanged || isQueueChanged) {
currentMediaItemIndex = findQueueItemIndex(newLegacyPlayerInfo.queue, newActiveQueueId); currentMediaItemIndex = findQueueItemIndex(newLegacyPlayerInfo.queue, newActiveQueueId);
boolean hasMediaMetadataCompat = newLegacyPlayerInfo.mediaMetadataCompat != null; boolean hasMediaMetadataCompat = newLegacyPlayerInfo.mediaMetadataCompat != null;
@ -1829,19 +1830,17 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
+ " MediaItem."); + " MediaItem.");
MediaItem fakeMediaItem = MediaItem fakeMediaItem =
MediaUtils.convertToMediaItem(newLegacyPlayerInfo.mediaMetadataCompat, ratingType); MediaUtils.convertToMediaItem(newLegacyPlayerInfo.mediaMetadataCompat, ratingType);
// Ad a tag to make sure the fake media item can't have an equal instance by accident. currentTimeline = currentTimeline.copyWithFakeMediaItem(fakeMediaItem, durationMs);
fakeMediaItem = fakeMediaItem.buildUpon().setTag(new Object()).build();
currentTimeline = currentTimeline.copyWithFakeMediaItem(fakeMediaItem);
currentMediaItemIndex = currentTimeline.getWindowCount() - 1; currentMediaItemIndex = currentTimeline.getWindowCount() - 1;
} else { } else {
currentTimeline = currentTimeline.copyWithFakeMediaItem(/* fakeMediaItem= */ null); currentTimeline = currentTimeline.copyWithClearedFakeMediaItem();
// Shouldn't be C.INDEX_UNSET to make getCurrentMediaItemIndex() return masked index. // Shouldn't be C.INDEX_UNSET to make getCurrentMediaItemIndex() return masked index.
// In other words, this index is either the currently playing media item index or the // In other words, this index is either the currently playing media item index or the
// would-be playing index when playing. // would-be playing index when playing.
currentMediaItemIndex = 0; currentMediaItemIndex = 0;
} }
} else if (currentMediaItemIndex != C.INDEX_UNSET) { } else if (currentMediaItemIndex != C.INDEX_UNSET) {
currentTimeline = currentTimeline.copyWithFakeMediaItem(/* fakeMediaItem= */ null); currentTimeline = currentTimeline.copyWithClearedFakeMediaItem();
if (hasMediaMetadataCompat) { if (hasMediaMetadataCompat) {
MediaItem mediaItem = MediaItem mediaItem =
MediaUtils.convertToMediaItem( MediaUtils.convertToMediaItem(
@ -1850,7 +1849,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
ratingType); ratingType);
currentTimeline = currentTimeline =
currentTimeline.copyWithNewMediaItem( currentTimeline.copyWithNewMediaItem(
/* replaceIndex= */ currentMediaItemIndex, mediaItem); /* replaceIndex= */ currentMediaItemIndex, mediaItem, durationMs);
} }
} else { } else {
// There's queue, but no valid queue item ID nor current media item metadata. // There's queue, but no valid queue item ID nor current media item metadata.
@ -1897,7 +1896,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
PlaybackException playerError = PlaybackException playerError =
MediaUtils.convertToPlaybackException(newLegacyPlayerInfo.playbackStateCompat); MediaUtils.convertToPlaybackException(newLegacyPlayerInfo.playbackStateCompat);
long durationMs = MediaUtils.convertToDurationMs(newLegacyPlayerInfo.mediaMetadataCompat);
long currentPositionMs = long currentPositionMs =
MediaUtils.convertToCurrentPositionMs( MediaUtils.convertToCurrentPositionMs(
newLegacyPlayerInfo.playbackStateCompat, newLegacyPlayerInfo.playbackStateCompat,

View file

@ -16,6 +16,7 @@
package androidx.media3.session; package androidx.media3.session;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Util.msToUs;
import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat.QueueItem; import android.support.v4.media.session.MediaSessionCompat.QueueItem;
@ -41,17 +42,18 @@ import java.util.List;
/* package */ final class QueueTimeline extends Timeline { /* package */ final class QueueTimeline extends Timeline {
public static final QueueTimeline DEFAULT = public static final QueueTimeline DEFAULT =
new QueueTimeline(ImmutableList.of(), /* fakeMediaItem= */ null); new QueueTimeline(ImmutableList.of(), /* fakeQueuedMediaItem= */ null);
private static final Object FAKE_WINDOW_UID = new Object(); private static final Object FAKE_WINDOW_UID = new Object();
private final ImmutableList<QueuedMediaItem> queuedMediaItems; private final ImmutableList<QueuedMediaItem> queuedMediaItems;
@Nullable private final MediaItem fakeMediaItem; @Nullable private final QueuedMediaItem fakeQueuedMediaItem;
private QueueTimeline( private QueueTimeline(
ImmutableList<QueuedMediaItem> queuedMediaItems, @Nullable MediaItem fakeMediaItem) { ImmutableList<QueuedMediaItem> queuedMediaItems,
@Nullable QueuedMediaItem fakeQueuedMediaItem) {
this.queuedMediaItems = queuedMediaItems; this.queuedMediaItems = queuedMediaItems;
this.fakeMediaItem = fakeMediaItem; this.fakeQueuedMediaItem = fakeQueuedMediaItem;
} }
/** Creates a {@link QueueTimeline} from a list of {@linkplain QueueItem queue items}. */ /** Creates a {@link QueueTimeline} from a list of {@linkplain QueueItem queue items}. */
@ -60,14 +62,15 @@ import java.util.List;
for (int i = 0; i < queue.size(); i++) { for (int i = 0; i < queue.size(); i++) {
QueueItem queueItem = queue.get(i); QueueItem queueItem = queue.get(i);
MediaItem mediaItem = MediaUtils.convertToMediaItem(queueItem); MediaItem mediaItem = MediaUtils.convertToMediaItem(queueItem);
queuedMediaItemsBuilder.add(new QueuedMediaItem(mediaItem, queueItem.getQueueId())); queuedMediaItemsBuilder.add(
new QueuedMediaItem(mediaItem, queueItem.getQueueId(), /* durationMs= */ C.TIME_UNSET));
} }
return new QueueTimeline(queuedMediaItemsBuilder.build(), /* fakeMediaItem= */ null); return new QueueTimeline(queuedMediaItemsBuilder.build(), /* fakeQueuedMediaItem= */ null);
} }
/** Returns a copy of the current queue timeline. */ /** Returns a copy of the current queue timeline. */
public QueueTimeline copy() { public QueueTimeline copy() {
return new QueueTimeline(queuedMediaItems, fakeMediaItem); return new QueueTimeline(queuedMediaItems, fakeQueuedMediaItem);
} }
/** /**
@ -87,10 +90,18 @@ import java.util.List;
* Copies the timeline with the given fake media item. * Copies the timeline with the given fake media item.
* *
* @param fakeMediaItem The fake media item. * @param fakeMediaItem The fake media item.
* @param durationMs The duration of the fake media item, in milliseconds, or {@link C#TIME_UNSET}
* if unknown.
* @return A new {@link QueueTimeline} reflecting the update. * @return A new {@link QueueTimeline} reflecting the update.
*/ */
public QueueTimeline copyWithFakeMediaItem(@Nullable MediaItem fakeMediaItem) { public QueueTimeline copyWithFakeMediaItem(MediaItem fakeMediaItem, long durationMs) {
return new QueueTimeline(queuedMediaItems, fakeMediaItem); return new QueueTimeline(
queuedMediaItems, new QueuedMediaItem(fakeMediaItem, QueueItem.UNKNOWN_ID, durationMs));
}
/** Copies the timeline while clearing any previously set fake media item. */
public QueueTimeline copyWithClearedFakeMediaItem() {
return new QueueTimeline(queuedMediaItems, /* fakeQueuedMediaItem= */ null);
} }
/** /**
@ -98,21 +109,25 @@ import java.util.List;
* *
* @param replaceIndex The index at which to replace the media item. * @param replaceIndex The index at which to replace the media item.
* @param newMediaItem The new media item that replaces the old one. * @param newMediaItem The new media item that replaces the old one.
* @param durationMs The duration of the media item, in milliseconds, or {@link C#TIME_UNSET} if
* unknown.
* @return A new {@link QueueTimeline} reflecting the update. * @return A new {@link QueueTimeline} reflecting the update.
*/ */
public QueueTimeline copyWithNewMediaItem(int replaceIndex, MediaItem newMediaItem) { public QueueTimeline copyWithNewMediaItem(
int replaceIndex, MediaItem newMediaItem, long durationMs) {
checkArgument( checkArgument(
replaceIndex < queuedMediaItems.size() replaceIndex < queuedMediaItems.size()
|| (replaceIndex == queuedMediaItems.size() && fakeMediaItem != null)); || (replaceIndex == queuedMediaItems.size() && fakeQueuedMediaItem != null));
if (replaceIndex == queuedMediaItems.size()) { if (replaceIndex == queuedMediaItems.size()) {
return new QueueTimeline(queuedMediaItems, newMediaItem); return new QueueTimeline(
queuedMediaItems, new QueuedMediaItem(newMediaItem, QueueItem.UNKNOWN_ID, durationMs));
} }
long queueId = queuedMediaItems.get(replaceIndex).queueId; long queueId = queuedMediaItems.get(replaceIndex).queueId;
ImmutableList.Builder<QueuedMediaItem> queuedItemsBuilder = new ImmutableList.Builder<>(); ImmutableList.Builder<QueuedMediaItem> queuedItemsBuilder = new ImmutableList.Builder<>();
queuedItemsBuilder.addAll(queuedMediaItems.subList(0, replaceIndex)); queuedItemsBuilder.addAll(queuedMediaItems.subList(0, replaceIndex));
queuedItemsBuilder.add(new QueuedMediaItem(newMediaItem, queueId)); queuedItemsBuilder.add(new QueuedMediaItem(newMediaItem, queueId, durationMs));
queuedItemsBuilder.addAll(queuedMediaItems.subList(replaceIndex + 1, queuedMediaItems.size())); queuedItemsBuilder.addAll(queuedMediaItems.subList(replaceIndex + 1, queuedMediaItems.size()));
return new QueueTimeline(queuedItemsBuilder.build(), fakeMediaItem); return new QueueTimeline(queuedItemsBuilder.build(), fakeQueuedMediaItem);
} }
/** /**
@ -127,10 +142,12 @@ import java.util.List;
ImmutableList.Builder<QueuedMediaItem> queuedItemsBuilder = new ImmutableList.Builder<>(); ImmutableList.Builder<QueuedMediaItem> queuedItemsBuilder = new ImmutableList.Builder<>();
queuedItemsBuilder.addAll(queuedMediaItems.subList(0, index)); queuedItemsBuilder.addAll(queuedMediaItems.subList(0, index));
for (int i = 0; i < newMediaItems.size(); i++) { for (int i = 0; i < newMediaItems.size(); i++) {
queuedItemsBuilder.add(new QueuedMediaItem(newMediaItems.get(i), QueueItem.UNKNOWN_ID)); queuedItemsBuilder.add(
new QueuedMediaItem(
newMediaItems.get(i), QueueItem.UNKNOWN_ID, /* durationMs= */ C.TIME_UNSET));
} }
queuedItemsBuilder.addAll(queuedMediaItems.subList(index, queuedMediaItems.size())); queuedItemsBuilder.addAll(queuedMediaItems.subList(index, queuedMediaItems.size()));
return new QueueTimeline(queuedItemsBuilder.build(), fakeMediaItem); return new QueueTimeline(queuedItemsBuilder.build(), fakeQueuedMediaItem);
} }
/** /**
@ -144,7 +161,7 @@ import java.util.List;
ImmutableList.Builder<QueuedMediaItem> queuedItemsBuilder = new ImmutableList.Builder<>(); ImmutableList.Builder<QueuedMediaItem> queuedItemsBuilder = new ImmutableList.Builder<>();
queuedItemsBuilder.addAll(queuedMediaItems.subList(0, fromIndex)); queuedItemsBuilder.addAll(queuedMediaItems.subList(0, fromIndex));
queuedItemsBuilder.addAll(queuedMediaItems.subList(toIndex, queuedMediaItems.size())); queuedItemsBuilder.addAll(queuedMediaItems.subList(toIndex, queuedMediaItems.size()));
return new QueueTimeline(queuedItemsBuilder.build(), fakeMediaItem); return new QueueTimeline(queuedItemsBuilder.build(), fakeQueuedMediaItem);
} }
/** /**
@ -158,12 +175,12 @@ import java.util.List;
public QueueTimeline copyWithMovedMediaItems(int fromIndex, int toIndex, int newIndex) { public QueueTimeline copyWithMovedMediaItems(int fromIndex, int toIndex, int newIndex) {
List<QueuedMediaItem> list = new ArrayList<>(queuedMediaItems); List<QueuedMediaItem> list = new ArrayList<>(queuedMediaItems);
Util.moveItems(list, fromIndex, toIndex, newIndex); Util.moveItems(list, fromIndex, toIndex, newIndex);
return new QueueTimeline(ImmutableList.copyOf(list), fakeMediaItem); return new QueueTimeline(ImmutableList.copyOf(list), fakeQueuedMediaItem);
} }
/** Returns whether the timeline contains the given {@link MediaItem}. */ /** Returns whether the timeline contains the given {@link MediaItem}. */
public boolean contains(MediaItem mediaItem) { public boolean contains(MediaItem mediaItem) {
if (mediaItem.equals(fakeMediaItem)) { if (fakeQueuedMediaItem != null && mediaItem.equals(fakeQueuedMediaItem.mediaItem)) {
return true; return true;
} }
for (int i = 0; i < queuedMediaItems.size(); i++) { for (int i = 0; i < queuedMediaItems.size(); i++) {
@ -176,27 +193,33 @@ import java.util.List;
@Nullable @Nullable
public MediaItem getMediaItemAt(int mediaItemIndex) { public MediaItem getMediaItemAt(int mediaItemIndex) {
if (mediaItemIndex >= 0 && mediaItemIndex < queuedMediaItems.size()) { return mediaItemIndex >= getWindowCount() ? null : getQueuedMediaItem(mediaItemIndex).mediaItem;
return queuedMediaItems.get(mediaItemIndex).mediaItem;
}
return (mediaItemIndex == queuedMediaItems.size()) ? fakeMediaItem : null;
} }
@Override @Override
public int getWindowCount() { public int getWindowCount() {
return queuedMediaItems.size() + ((fakeMediaItem == null) ? 0 : 1); return queuedMediaItems.size() + ((fakeQueuedMediaItem == null) ? 0 : 1);
} }
@Override @Override
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) { public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
// TODO(b/149713425): Set duration if it's available from MediaMetadataCompat. QueuedMediaItem queuedMediaItem = getQueuedMediaItem(windowIndex);
MediaItem mediaItem; window.set(
if (windowIndex == queuedMediaItems.size() && fakeMediaItem != null) { FAKE_WINDOW_UID,
mediaItem = fakeMediaItem; queuedMediaItem.mediaItem,
} else { /* manifest= */ null,
mediaItem = queuedMediaItems.get(windowIndex).mediaItem; /* presentationStartTimeMs= */ C.TIME_UNSET,
} /* windowStartTimeMs= */ C.TIME_UNSET,
return getWindow(window, mediaItem, windowIndex); /* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* liveConfiguration= */ null,
/* defaultPositionUs= */ 0,
/* durationUs= */ msToUs(queuedMediaItem.durationMs),
/* firstPeriodIndex= */ windowIndex,
/* lastPeriodIndex= */ windowIndex,
/* positionInFirstPeriodUs= */ 0);
return window;
} }
@Override @Override
@ -206,12 +229,12 @@ import java.util.List;
@Override @Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) { public Period getPeriod(int periodIndex, Period period, boolean setIds) {
// TODO(b/149713425): Set duration if it's available from MediaMetadataCompat. QueuedMediaItem queuedMediaItem = getQueuedMediaItem(periodIndex);
period.set( period.set(
/* id= */ null, /* id= */ queuedMediaItem.queueId,
/* uid= */ null, /* uid= */ null,
/* windowIndex= */ periodIndex, /* windowIndex= */ periodIndex,
/* durationUs= */ C.TIME_UNSET, /* durationUs= */ msToUs(queuedMediaItem.durationMs),
/* positionInWindowUs= */ 0); /* positionInWindowUs= */ 0);
return period; return period;
} }
@ -236,41 +259,30 @@ import java.util.List;
} }
QueueTimeline other = (QueueTimeline) obj; QueueTimeline other = (QueueTimeline) obj;
return Objects.equal(queuedMediaItems, other.queuedMediaItems) return Objects.equal(queuedMediaItems, other.queuedMediaItems)
&& Objects.equal(fakeMediaItem, other.fakeMediaItem); && Objects.equal(fakeQueuedMediaItem, other.fakeQueuedMediaItem);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hashCode(queuedMediaItems, fakeMediaItem); return Objects.hashCode(queuedMediaItems, fakeQueuedMediaItem);
} }
private static Window getWindow(Window window, MediaItem mediaItem, int windowIndex) { private QueuedMediaItem getQueuedMediaItem(int index) {
window.set( return index == queuedMediaItems.size() && fakeQueuedMediaItem != null
FAKE_WINDOW_UID, ? fakeQueuedMediaItem
mediaItem, : queuedMediaItems.get(index);
/* manifest= */ null,
/* presentationStartTimeMs= */ C.TIME_UNSET,
/* windowStartTimeMs= */ C.TIME_UNSET,
/* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* liveConfiguration= */ null,
/* defaultPositionUs= */ 0,
/* durationUs= */ C.TIME_UNSET,
/* firstPeriodIndex= */ windowIndex,
/* lastPeriodIndex= */ windowIndex,
/* positionInFirstPeriodUs= */ 0);
return window;
} }
private static final class QueuedMediaItem { private static final class QueuedMediaItem {
public final MediaItem mediaItem; public final MediaItem mediaItem;
public final long queueId; public final long queueId;
public final long durationMs;
public QueuedMediaItem(MediaItem mediaItem, long queueId) { public QueuedMediaItem(MediaItem mediaItem, long queueId, long durationMs) {
this.mediaItem = mediaItem; this.mediaItem = mediaItem;
this.queueId = queueId; this.queueId = queueId;
this.durationMs = durationMs;
} }
@Override @Override
@ -282,7 +294,9 @@ import java.util.List;
return false; return false;
} }
QueuedMediaItem that = (QueuedMediaItem) o; QueuedMediaItem that = (QueuedMediaItem) o;
return queueId == that.queueId && mediaItem.equals(that.mediaItem); return queueId == that.queueId
&& mediaItem.equals(that.mediaItem)
&& durationMs == that.durationMs;
} }
@Override @Override
@ -290,6 +304,7 @@ import java.util.List;
int result = 7; int result = 7;
result = 31 * result + (int) (queueId ^ (queueId >>> 32)); result = 31 * result + (int) (queueId ^ (queueId >>> 32));
result = 31 * result + mediaItem.hashCode(); result = 31 * result + mediaItem.hashCode();
result = 31 * result + (int) (durationMs ^ (durationMs >>> 32));
return result; return result;
} }
} }

View file

@ -246,6 +246,8 @@ public class MediaControllerWithMediaSessionCompatTest {
AtomicLong repeatModeRef = new AtomicLong(); AtomicLong repeatModeRef = new AtomicLong();
AtomicReference<MediaMetadata> playlistMetadataRef = new AtomicReference<>(); AtomicReference<MediaMetadata> playlistMetadataRef = new AtomicReference<>();
AtomicBoolean isPlayingAdRef = new AtomicBoolean(); AtomicBoolean isPlayingAdRef = new AtomicBoolean();
AtomicLong durationRef = new AtomicLong();
AtomicLong durationInTimelineRef = new AtomicLong();
threadTestRule threadTestRule
.getHandler() .getHandler()
.postAndSync( .postAndSync(
@ -260,6 +262,12 @@ public class MediaControllerWithMediaSessionCompatTest {
shuffleModeEnabledRef.set(controller.getShuffleModeEnabled()); shuffleModeEnabledRef.set(controller.getShuffleModeEnabled());
playlistMetadataRef.set(controller.getPlaylistMetadata()); playlistMetadataRef.set(controller.getPlaylistMetadata());
isPlayingAdRef.set(controller.isPlayingAd()); isPlayingAdRef.set(controller.isPlayingAd());
durationRef.set(controller.getDuration());
durationInTimelineRef.set(
controller
.getCurrentTimeline()
.getWindow(/* windowIndex= */ 0, new Timeline.Window())
.getDurationMs());
}); });
assertThat(positionRef.get()) assertThat(positionRef.get())
@ -273,6 +281,8 @@ public class MediaControllerWithMediaSessionCompatTest {
assertThat(repeatModeRef.get()).isEqualTo(Player.REPEAT_MODE_ALL); assertThat(repeatModeRef.get()).isEqualTo(Player.REPEAT_MODE_ALL);
assertThat(playlistMetadataRef.get().title.toString()).isEqualTo(queueTitle.toString()); assertThat(playlistMetadataRef.get().title.toString()).isEqualTo(queueTitle.toString());
assertThat(isPlayingAdRef.get()).isEqualTo(isPlayingAd); assertThat(isPlayingAdRef.get()).isEqualTo(isPlayingAd);
assertThat(durationRef.get()).isEqualTo(duration);
assertThat(durationInTimelineRef.get()).isEqualTo(duration);
} }
@Test @Test