Skip to content

Commit e266051

Browse files
microkatzchristosts
authored andcommitted
Add onSetMediaItems listener with access to start index and position
Added onSetMediaItems callback listener to allow the session to modify/set MediaItem list, starting index and position before call to Player.setMediaItem(s). Added conditional check in MediaSessionStub.setMediaItem methods to only call player.setMediaItem rather than setMediaItems if player does not support COMMAND_CHANGE_MEDIA_ITEMS PiperOrigin-RevId: 503427927 (cherry picked from commit bb11e02)
1 parent 107a481 commit e266051

File tree

9 files changed

+621
-99
lines changed

9 files changed

+621
-99
lines changed

RELEASENOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646
* Add the media session as an argument of `getMediaButtons()` of the
4747
`DefaultMediaNotificationProvider` and use immutable lists for clarity
4848
([#216](https://github.com/androidx/media/issues/216)).
49+
* Add `onSetMediaItems` callback listener to provide means to modify/set
50+
`MediaItem` list, starting index and position by session before setting
51+
onto Player ([#156](https://github.com/androidx/media/issues/156)).
4952
* Metadata:
5053
* Parse multiple null-separated values from ID3 frames, as permitted by
5154
ID3 v2.4.

libraries/session/src/main/java/androidx/media3/session/MediaSession.java

Lines changed: 160 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
import androidx.media3.common.util.Util;
5959
import androidx.media3.session.MediaLibraryService.LibraryParams;
6060
import com.google.common.base.Objects;
61+
import com.google.common.collect.ImmutableList;
62+
import com.google.common.primitives.Longs;
6163
import com.google.common.util.concurrent.Futures;
6264
import com.google.common.util.concurrent.ListenableFuture;
6365
import java.util.HashMap;
@@ -1055,13 +1057,13 @@ default ListenableFuture<SessionResult> onCustomCommand(
10551057

10561058
/**
10571059
* Called when a controller requested to add new {@linkplain MediaItem media items} to the
1058-
* playlist via one of the {@code Player.addMediaItem(s)} or {@code Player.setMediaItem(s)}
1059-
* methods.
1060+
* playlist via one of the {@code Player.addMediaItem(s)} methods. Unless overriden, {@link
1061+
* Callback#onSetMediaItems} will direct {@code Player.setMediaItem(s)} to this method as well.
10601062
*
1061-
* <p>This callback is also called when an app is using a legacy {@link
1062-
* MediaControllerCompat.TransportControls} to prepare or play media (for instance when browsing
1063-
* the catalogue and then selecting an item for preparation from Android Auto that is using the
1064-
* legacy Media1 library).
1063+
* <p>In addition, unless {@link Callback#onSetMediaItems} is overridden, this callback is also
1064+
* called when an app is using a legacy {@link MediaControllerCompat.TransportControls} to
1065+
* prepare or play media (for instance when browsing the catalogue and then selecting an item
1066+
* for preparation from Android Auto that is using the legacy Media1 library).
10651067
*
10661068
* <p>Note that the requested {@linkplain MediaItem media items} don't have a {@link
10671069
* MediaItem.LocalConfiguration} (for example, a URI) and need to be updated to make them
@@ -1074,8 +1076,8 @@ default ListenableFuture<SessionResult> onCustomCommand(
10741076
* the {@link MediaItem media items} have been resolved, the session will call {@link
10751077
* Player#setMediaItems} or {@link Player#addMediaItems} as requested.
10761078
*
1077-
* <p>Interoperability: This method will be called in response to the following {@link
1078-
* MediaControllerCompat} methods:
1079+
* <p>Interoperability: This method will be called, unless {@link Callback#onSetMediaItems} is
1080+
* overridden, in response to the following {@link MediaControllerCompat} methods:
10791081
*
10801082
* <ul>
10811083
* <li>{@link MediaControllerCompat.TransportControls#prepareFromUri prepareFromUri}
@@ -1103,6 +1105,156 @@ default ListenableFuture<List<MediaItem>> onAddMediaItems(
11031105
MediaSession mediaSession, ControllerInfo controller, List<MediaItem> mediaItems) {
11041106
return Futures.immediateFailedFuture(new UnsupportedOperationException());
11051107
}
1108+
1109+
/**
1110+
* Called when a controller requested to set {@linkplain MediaItem media items} to the playlist
1111+
* via one of the {@code Player.setMediaItem(s)} methods. The default implementation calls
1112+
* {@link Callback#onAddMediaItems}. Override this method if you want to modify/set the starting
1113+
* index/position for the {@code Player.setMediaItem(s)} methods.
1114+
*
1115+
* <p>This callback is also called when an app is using a legacy {@link
1116+
* MediaControllerCompat.TransportControls} to prepare or play media (for instance when browsing
1117+
* the catalogue and then selecting an item for preparation from Android Auto that is using the
1118+
* legacy Media1 library).
1119+
*
1120+
* <p>Note that the requested {@linkplain MediaItem media items} in the
1121+
* MediaItemsWithStartPosition don't have a {@link MediaItem.LocalConfiguration} (for example, a
1122+
* URI) and need to be updated to make them playable by the underlying {@link Player}.
1123+
* Typically, this implementation should be able to identify the correct item by its {@link
1124+
* MediaItem#mediaId} and/or the {@link MediaItem#requestMetadata}.
1125+
*
1126+
* <p>Return a {@link ListenableFuture} with the resolved {@linkplain
1127+
* MediaItemsWithStartPosition media items and starting index and position}. You can also return
1128+
* the items directly by using Guava's {@link Futures#immediateFuture(Object)}. Once the {@link
1129+
* MediaItemsWithStartPosition} has been resolved, the session will call {@link
1130+
* Player#setMediaItems} as requested. If the resolved {@link
1131+
* MediaItemsWithStartPosition#startIndex startIndex} is {@link
1132+
* androidx.media3.common.C#INDEX_UNSET C.INDEX_UNSET} and {@link
1133+
* MediaItemsWithStartPosition#startPositionMs startPositionMs} is {@link
1134+
* androidx.media3.common.C#TIME_UNSET C.TIME_UNSET} then the session will call {@link
1135+
* Player#setMediaItem(MediaItem, boolean)} with {@code resetPosition} set to {@code true}.
1136+
*
1137+
* <p>Interoperability: This method will be called in response to the following {@link
1138+
* MediaControllerCompat} methods:
1139+
*
1140+
* <ul>
1141+
* <li>{@link MediaControllerCompat.TransportControls#prepareFromUri prepareFromUri}
1142+
* <li>{@link MediaControllerCompat.TransportControls#playFromUri playFromUri}
1143+
* <li>{@link MediaControllerCompat.TransportControls#prepareFromMediaId prepareFromMediaId}
1144+
* <li>{@link MediaControllerCompat.TransportControls#playFromMediaId playFromMediaId}
1145+
* <li>{@link MediaControllerCompat.TransportControls#prepareFromSearch prepareFromSearch}
1146+
* <li>{@link MediaControllerCompat.TransportControls#playFromSearch playFromSearch}
1147+
* <li>{@link MediaControllerCompat.TransportControls#addQueueItem addQueueItem}
1148+
* </ul>
1149+
*
1150+
* The values of {@link MediaItem#mediaId}, {@link MediaItem.RequestMetadata#mediaUri}, {@link
1151+
* MediaItem.RequestMetadata#searchQuery} and {@link MediaItem.RequestMetadata#extras} will be
1152+
* set to match the legacy method call. The session will call {@link Player#setMediaItems} or
1153+
* {@link Player#addMediaItems}, followed by {@link Player#prepare()} and {@link Player#play()}
1154+
* as appropriate once the {@link MediaItem} has been resolved.
1155+
*
1156+
* @param mediaSession The session for this event.
1157+
* @param controller The controller information.
1158+
* @param mediaItems The list of requested {@linkplain MediaItem media items}.
1159+
* @param startIndex The start index in the {@link MediaItem} list from which to start playing.
1160+
* If startIndex is {@link androidx.media3.common.C#INDEX_UNSET C.INDEX_UNSET} and
1161+
* startPositionMs is {@link androidx.media3.common.C#TIME_UNSET C.TIME_UNSET} then caller
1162+
* is requesting to set media items with default index and position.
1163+
* @param startPositionMs The starting position in the media item from where to start playing.
1164+
* If startIndex is {@link androidx.media3.common.C#INDEX_UNSET C.INDEX_UNSET} and
1165+
* startPositionMs is {@link androidx.media3.common.C#TIME_UNSET C.TIME_UNSET} then caller
1166+
* is requesting to set media items with default index and position.
1167+
* @return A {@link ListenableFuture} with a {@link MediaItemsWithStartPosition} containing a
1168+
* list of resolved {@linkplain MediaItem media items}, and a starting index and position
1169+
* that are playable by the underlying {@link Player}. If returned {@link
1170+
* MediaItemsWithStartPosition#startIndex} is {@link androidx.media3.common.C#INDEX_UNSET
1171+
* C.INDEX_UNSET} and {@link MediaItemsWithStartPosition#startPositionMs} is {@link
1172+
* androidx.media3.common.C#TIME_UNSET C.TIME_UNSET}, then {@linkplain
1173+
* Player#setMediaItems(List, boolean) Player#setMediaItems(List, true)} will be called to
1174+
* set media items with default index and position.
1175+
*/
1176+
@UnstableApi
1177+
default ListenableFuture<MediaItemsWithStartPosition> onSetMediaItems(
1178+
MediaSession mediaSession,
1179+
ControllerInfo controller,
1180+
List<MediaItem> mediaItems,
1181+
int startIndex,
1182+
long startPositionMs) {
1183+
return Util.transformFutureAsync(
1184+
onAddMediaItems(mediaSession, controller, mediaItems),
1185+
(mediaItemList) ->
1186+
Futures.immediateFuture(
1187+
new MediaItemsWithStartPosition(mediaItemList, startIndex, startPositionMs)));
1188+
}
1189+
}
1190+
1191+
/** Representation of list of media items and where to start playing */
1192+
@UnstableApi
1193+
public static final class MediaItemsWithStartPosition {
1194+
/** List of {@link MediaItem media items}. */
1195+
public final ImmutableList<MediaItem> mediaItems;
1196+
/**
1197+
* Index to start playing at in {@link MediaItem} list.
1198+
*
1199+
* <p>If startIndex is {@link androidx.media3.common.C#INDEX_UNSET C.INDEX_UNSET} and
1200+
* startPositionMs is {@link androidx.media3.common.C#TIME_UNSET C.TIME_UNSET} then the
1201+
* requested start is the default index and position. If only startIndex is {@link
1202+
* androidx.media3.common.C#INDEX_UNSET C.INDEX_UNSET}, then the requested start is the
1203+
* {@linkplain Player#getCurrentMediaItemIndex() current index} and {@linkplain
1204+
* Player#getContentPosition() position}.
1205+
*/
1206+
public final int startIndex;
1207+
/**
1208+
* Position to start playing from in starting media item.
1209+
*
1210+
* <p>If startIndex is {@link androidx.media3.common.C#INDEX_UNSET C.INDEX_UNSET} and
1211+
* startPositionMs is {@link androidx.media3.common.C#TIME_UNSET C.TIME_UNSET} then the
1212+
* requested start is the default start index that takes into account whether {@link
1213+
* Player#getShuffleModeEnabled() shuffling is enabled} and the {@linkplain
1214+
* Timeline.Window#defaultPositionUs} default position}. If only startIndex is {@link
1215+
* androidx.media3.common.C#INDEX_UNSET C.INDEX_UNSET}, then the requested start is the
1216+
* {@linkplain Player#getCurrentMediaItemIndex() current index} and {@linkplain
1217+
* Player#getContentPosition() position}.
1218+
*/
1219+
public final long startPositionMs;
1220+
1221+
/**
1222+
* Create an instance.
1223+
*
1224+
* @param mediaItems List of {@link MediaItem media items}.
1225+
* @param startIndex Index to start playing at in {@link MediaItem} list.
1226+
* @param startPositionMs Position to start playing from in starting media item.
1227+
*/
1228+
public MediaItemsWithStartPosition(
1229+
List<MediaItem> mediaItems, int startIndex, long startPositionMs) {
1230+
this.mediaItems = ImmutableList.copyOf(mediaItems);
1231+
this.startIndex = startIndex;
1232+
this.startPositionMs = startPositionMs;
1233+
}
1234+
1235+
@Override
1236+
public boolean equals(@Nullable Object obj) {
1237+
if (this == obj) {
1238+
return true;
1239+
}
1240+
if (!(obj instanceof MediaItemsWithStartPosition)) {
1241+
return false;
1242+
}
1243+
1244+
MediaItemsWithStartPosition other = (MediaItemsWithStartPosition) obj;
1245+
1246+
return mediaItems.equals(other.mediaItems)
1247+
&& Util.areEqual(startIndex, other.startIndex)
1248+
&& Util.areEqual(startPositionMs, other.startPositionMs);
1249+
}
1250+
1251+
@Override
1252+
public int hashCode() {
1253+
int result = mediaItems.hashCode();
1254+
result = 31 * result + startIndex;
1255+
result = 31 * result + Longs.hashCode(startPositionMs);
1256+
return result;
1257+
}
11061258
}
11071259

11081260
/**

libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import androidx.media3.common.util.Util;
7070
import androidx.media3.session.MediaSession.ControllerCb;
7171
import androidx.media3.session.MediaSession.ControllerInfo;
72+
import androidx.media3.session.MediaSession.MediaItemsWithStartPosition;
7273
import androidx.media3.session.SequencedFutureManager.SequencedFuture;
7374
import com.google.common.collect.ImmutableList;
7475
import com.google.common.util.concurrent.Futures;
@@ -524,6 +525,13 @@ protected ListenableFuture<List<MediaItem>> onAddMediaItemsOnHandler(
524525
"onAddMediaItems must return a non-null future");
525526
}
526527

528+
protected ListenableFuture<MediaItemsWithStartPosition> onSetMediaItemsOnHandler(
529+
ControllerInfo controller, List<MediaItem> mediaItems, int startIndex, long startPositionMs) {
530+
return checkNotNull(
531+
callback.onSetMediaItems(instance, controller, mediaItems, startIndex, startPositionMs),
532+
"onSetMediaItems must return a non-null future");
533+
}
534+
527535
protected boolean isReleased() {
528536
synchronized (lock) {
529537
return closed;

libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
import androidx.media3.common.util.Util;
8686
import androidx.media3.session.MediaSession.ControllerCb;
8787
import androidx.media3.session.MediaSession.ControllerInfo;
88+
import androidx.media3.session.MediaSession.MediaItemsWithStartPosition;
8889
import androidx.media3.session.SessionCommand.CommandCode;
8990
import com.google.common.collect.ImmutableList;
9091
import com.google.common.util.concurrent.FutureCallback;
@@ -711,18 +712,26 @@ private void handleMediaRequest(MediaItem mediaItem, boolean play) {
711712
dispatchSessionTaskWithPlayerCommand(
712713
COMMAND_SET_MEDIA_ITEM,
713714
controller -> {
714-
ListenableFuture<List<MediaItem>> mediaItemsFuture =
715-
sessionImpl.onAddMediaItemsOnHandler(controller, ImmutableList.of(mediaItem));
715+
ListenableFuture<MediaItemsWithStartPosition> mediaItemsFuture =
716+
sessionImpl.onSetMediaItemsOnHandler(
717+
controller, ImmutableList.of(mediaItem), C.INDEX_UNSET, C.TIME_UNSET);
716718
Futures.addCallback(
717719
mediaItemsFuture,
718-
new FutureCallback<List<MediaItem>>() {
720+
new FutureCallback<MediaItemsWithStartPosition>() {
719721
@Override
720-
public void onSuccess(List<MediaItem> mediaItems) {
722+
public void onSuccess(MediaItemsWithStartPosition mediaItemsWithStartPosition) {
721723
postOrRun(
722724
sessionImpl.getApplicationHandler(),
723725
() -> {
724726
PlayerWrapper player = sessionImpl.getPlayerWrapper();
725-
player.setMediaItems(mediaItems);
727+
if (mediaItemsWithStartPosition.startIndex == C.INDEX_UNSET
728+
&& mediaItemsWithStartPosition.startPositionMs == C.TIME_UNSET) {
729+
MediaUtils.setMediaItemsWithDefaultStartIndexAndPosition(
730+
player, mediaItemsWithStartPosition);
731+
} else {
732+
MediaUtils.setMediaItemsWithSpecifiedStartIndexAndPosition(
733+
player, mediaItemsWithStartPosition);
734+
}
726735
@Player.State int playbackState = player.getPlaybackState();
727736
if (playbackState == Player.STATE_IDLE) {
728737
player.prepareIfCommandAvailable();

0 commit comments

Comments
 (0)