diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 59e3b6653f..959fdba3e1 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -179,6 +179,8 @@
of requests.
* Forward legacy `MediaController` calls to play media to
`MediaSession.Callback.onAddMediaItems` instead of `onSetMediaUri`.
+ * Support `setMediaItems(s)` methods when `MediaController` connects to a
+ legacy media session.
* Data sources:
* Rename `DummyDataSource` to `PlaceholderDataSource`.
* Workaround OkHttp interrupt handling.
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaController.java b/libraries/session/src/main/java/androidx/media3/session/MediaController.java
index 348750b813..624a0faa7a 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaController.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaController.java
@@ -81,13 +81,14 @@ import org.checkerframework.checker.initialization.qual.Initialized;
*
Backward Compatibility with legacy media sessions
*
*
* Controller Lifecycle
*
* When a controller is created with the {@link SessionToken} for a {@link MediaSession} (i.e.
* session token type is {@link SessionToken#TYPE_SESSION}), the controller will connect to the
- * specific session.
+ * specific session.F
*
*
When a controller is created with the {@link SessionToken} for a {@link MediaSessionService}
* (i.e. session token type is {@link SessionToken#TYPE_SESSION_SERVICE} or {@link
@@ -127,6 +128,34 @@ import org.checkerframework.checker.initialization.qual.Initialized;
*
*
* }
+ *
+ *
Backward Compatibility with legacy media sessions
+ *
+ * In addition to {@link MediaSession}, the controller also supports connecting to a legacy media
+ * session - {@linkplain android.media.session.MediaSession framework session} and {@linkplain
+ * MediaSessionCompat AndroidX session compat}.
+ *
+ *
To request legacy sessions to play media, use one of the {@link #setMediaItem} methods and set
+ * either {@link MediaItem#mediaId}, {@link MediaItem.RequestMetadata#mediaUri} or {@link
+ * MediaItem.RequestMetadata#searchQuery}. Once the controller is {@linkplain #prepare() prepared},
+ * the controller triggers one of the following callbacks depending on the provided information and
+ * the value of {@link #getPlayWhenReady()}:
+ *
+ *
+ * - {@link MediaSessionCompat.Callback#onPrepareFromUri onPrepareFromUri}
+ *
- {@link MediaSessionCompat.Callback#onPlayFromUri onPlayFromUri}
+ *
- {@link MediaSessionCompat.Callback#onPrepareFromMediaId onPrepareFromMediaId}
+ *
- {@link MediaSessionCompat.Callback#onPlayFromMediaId onPlayFromMediaId}
+ *
- {@link MediaSessionCompat.Callback#onPrepareFromSearch onPrepareFromSearch}
+ *
- {@link MediaSessionCompat.Callback#onPlayFromSearch onPlayFromSearch}
+ *
+ *
+ * Other playlist change methods, like {@link #addMediaItem} or {@link #removeMediaItem}, trigger
+ * the {@link MediaSessionCompat.Callback#onAddQueueItem onAddQueueItem} and {@link
+ * MediaSessionCompat.Callback#onRemoveQueueItem} onRemoveQueueItem} callbacks. Check {@link
+ * #getAvailableCommands()} to see if playlist modifications are {@linkplain
+ * androidx.media3.common.Player.Command#COMMAND_CHANGE_MEDIA_ITEMS supported} by the legacy
+ * session.
*/
public class MediaController implements Player {
@@ -478,13 +507,6 @@ public class MediaController implements Player {
return impl.isConnected();
}
- /**
- * {@inheritDoc}
- *
- * Interoperability: When connected to {@link
- * android.support.v4.media.session.MediaSessionCompat}, then this will be grouped together with
- * previously called {@link #setMediaUri}. See {@link #setMediaUri} for details.
- */
@Override
public void play() {
verifyApplicationThread();
@@ -505,13 +527,6 @@ public class MediaController implements Player {
impl.pause();
}
- /**
- * {@inheritDoc}
- *
- *
Interoperability: When connected to {@link
- * android.support.v4.media.session.MediaSessionCompat}, then this will be grouped together with
- * previously called {@link #setMediaUri}. See {@link #setMediaUri} for details.
- */
@Override
public void prepare() {
verifyApplicationThread();
@@ -980,44 +995,6 @@ public class MediaController implements Player {
*
The {@link Player.Listener#onTimelineChanged} and/or {@link
* Player.Listener#onMediaItemTransition} would be called when it's completed.
*
- *
Interoperability: When connected to {@link
- * android.support.v4.media.session.MediaSessionCompat}, this call will be grouped together with
- * later {@link #prepare} or {@link #play}, depending on the uri pattern as follows:
- *
- *
- * Uri patterns and following API calls for MediaControllerCompat methods
- *
- * Uri patterns | Following API calls | Method |
- *
- * {@code androidx://media3-session/setMediaUri?uri=[uri]} |
- * {@link #prepare} |
- * {@link MediaControllerCompat.TransportControls#prepareFromUri prepareFromUri}
- * |
- * {@link #play} |
- * {@link MediaControllerCompat.TransportControls#playFromUri playFromUri}
- * |
- * {@code androidx://media3-session/setMediaUri?id=[mediaId]} |
- * {@link #prepare} |
- * {@link MediaControllerCompat.TransportControls#prepareFromMediaId prepareFromMediaId}
- * |
- * {@link #play} |
- * {@link MediaControllerCompat.TransportControls#playFromMediaId playFromMediaId}
- * |
- * {@code androidx://media3-session/setMediaUri?query=[query]} |
- * {@link #prepare} |
- * {@link MediaControllerCompat.TransportControls#prepareFromSearch prepareFromSearch}
- * |
- * {@link #play} |
- * {@link MediaControllerCompat.TransportControls#playFromSearch playFromSearch}
- * |
- * Does not match with any pattern above |
- * {@link #prepare} |
- * {@link MediaControllerCompat.TransportControls#prepareFromUri prepareFromUri}
- * |
- * {@link #play} |
- * {@link MediaControllerCompat.TransportControls#playFromUri playFromUri}
- * |
- *
* Returned {@link ListenableFuture} will return {@link SessionResult#RESULT_SUCCESS} when it's
* handled together with {@link #prepare} or {@link #play}. If this API is called multiple times
* without prepare or play, then {@link SessionResult#RESULT_INFO_SKIPPED} will be returned for
@@ -1027,15 +1004,6 @@ public class MediaController implements Player {
* @param extras A {@link Bundle} to send extra information. May be empty.
* @return A {@link ListenableFuture} of {@link SessionResult} representing the pending
* completion.
- * @see MediaConstants#MEDIA_URI_AUTHORITY
- * @see MediaConstants#MEDIA_URI_PATH_PREPARE_FROM_MEDIA_ID
- * @see MediaConstants#MEDIA_URI_PATH_PLAY_FROM_MEDIA_ID
- * @see MediaConstants#MEDIA_URI_PATH_PREPARE_FROM_SEARCH
- * @see MediaConstants#MEDIA_URI_PATH_PLAY_FROM_SEARCH
- * @see MediaConstants#MEDIA_URI_PATH_SET_MEDIA_URI
- * @see MediaConstants#MEDIA_URI_QUERY_ID
- * @see MediaConstants#MEDIA_URI_QUERY_QUERY
- * @see MediaConstants#MEDIA_URI_QUERY_URI
*/
public ListenableFuture setMediaUri(Uri uri, Bundle extras) {
verifyApplicationThread();
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java
index 3855969830..05fb4335cf 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java
@@ -31,16 +31,12 @@ import static androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_USER_R
import static androidx.media3.common.Player.STATE_IDLE;
import static androidx.media3.common.Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED;
import static androidx.media3.common.util.Assertions.checkNotNull;
+import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.session.MediaConstants.ARGUMENT_CAPTIONING_ENABLED;
-import static androidx.media3.session.MediaConstants.MEDIA_URI_QUERY_ID;
-import static androidx.media3.session.MediaConstants.MEDIA_URI_QUERY_QUERY;
-import static androidx.media3.session.MediaConstants.MEDIA_URI_QUERY_URI;
-import static androidx.media3.session.MediaConstants.MEDIA_URI_SET_MEDIA_URI_PREFIX;
import static androidx.media3.session.MediaConstants.SESSION_COMMAND_ON_CAPTIONING_ENABLED_CHANGED;
import static androidx.media3.session.MediaUtils.POSITION_DIFF_TOLERANCE_MS;
import static androidx.media3.session.MediaUtils.calculateBufferedPercentage;
-import static androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED;
import static androidx.media3.session.SessionResult.RESULT_SUCCESS;
import static java.lang.Math.max;
import static java.lang.Math.min;
@@ -60,7 +56,6 @@ import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.MediaSessionCompat.QueueItem;
import android.support.v4.media.session.PlaybackStateCompat;
-import android.text.TextUtils;
import android.util.Pair;
import android.view.Surface;
import android.view.SurfaceHolder;
@@ -112,30 +107,19 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
private static final long AGGREGATES_CALLBACKS_WITHIN_TIMEOUT_MS = 500L;
private static final int VOLUME_FLAGS = AudioManager.FLAG_SHOW_UI;
- final Context context;
+ /* package */ final Context context;
+ /* package */ final MediaController instance;
private final SessionToken token;
-
- final MediaController instance;
-
private final ListenerSet listeners;
-
private final ControllerCompatCallback controllerCompatCallback;
@Nullable private MediaControllerCompat controllerCompat;
-
@Nullable private MediaBrowserCompat browserCompat;
-
private boolean released;
-
private boolean connected;
-
- @Nullable private SetMediaUriRequest pendingSetMediaUriRequest;
-
private LegacyPlayerInfo legacyPlayerInfo;
-
private LegacyPlayerInfo pendingLegacyPlayerInfo;
-
private ControllerInfo controllerInfo;
public MediaControllerImplLegacy(Context context, MediaController instance, SessionToken token) {
@@ -177,6 +161,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Override
public void stop() {
+ if (controllerInfo.playerInfo.playbackState == STATE_IDLE) {
+ return;
+ }
PlayerInfo maskedPlayerInfo =
controllerInfo.playerInfo.copyWithSessionPositionInfo(
createSessionPositionInfo(
@@ -244,6 +231,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Override
public void play() {
+ if (controllerInfo.playerInfo.playWhenReady) {
+ return;
+ }
ControllerInfo maskedControllerInfo =
new ControllerInfo(
controllerInfo.playerInfo.copyWithPlayWhenReady(
@@ -258,36 +248,16 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/* discontinuityReason= */ null,
/* mediaItemTransitionReason= */ null);
- if (pendingSetMediaUriRequest == null) {
+ if (isPrepared() && hasMedia()) {
controllerCompat.getTransportControls().play();
- } else {
- switch (pendingSetMediaUriRequest.type) {
- case MEDIA_URI_QUERY_ID:
- controllerCompat
- .getTransportControls()
- .playFromMediaId(pendingSetMediaUriRequest.value, pendingSetMediaUriRequest.extras);
- break;
- case MEDIA_URI_QUERY_QUERY:
- controllerCompat
- .getTransportControls()
- .playFromSearch(pendingSetMediaUriRequest.value, pendingSetMediaUriRequest.extras);
- break;
- case MEDIA_URI_QUERY_URI:
- controllerCompat
- .getTransportControls()
- .playFromUri(
- Uri.parse(pendingSetMediaUriRequest.value), pendingSetMediaUriRequest.extras);
- break;
- default:
- throw new IllegalStateException("Unexpected type " + pendingSetMediaUriRequest.type);
- }
- pendingSetMediaUriRequest.result.set(new SessionResult(RESULT_SUCCESS));
- pendingSetMediaUriRequest = null;
}
}
@Override
public void pause() {
+ if (!controllerInfo.playerInfo.playWhenReady) {
+ return;
+ }
ControllerInfo maskedControllerInfo =
new ControllerInfo(
controllerInfo.playerInfo.copyWithPlayWhenReady(
@@ -302,11 +272,16 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/* discontinuityReason= */ null,
/* mediaItemTransitionReason= */ null);
- controllerCompat.getTransportControls().pause();
+ if (isPrepared() && hasMedia()) {
+ controllerCompat.getTransportControls().pause();
+ }
}
@Override
public void prepare() {
+ if (controllerInfo.playerInfo.playbackState != STATE_IDLE) {
+ return;
+ }
ControllerInfo maskedControllerInfo =
new ControllerInfo(
controllerInfo.playerInfo.copyWithPlaybackState(
@@ -322,32 +297,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/* discontinuityReason= */ null,
/* mediaItemTransitionReason= */ null);
- if (pendingSetMediaUriRequest == null) {
- controllerCompat.getTransportControls().prepare();
- } else {
- switch (pendingSetMediaUriRequest.type) {
- case MEDIA_URI_QUERY_ID:
- controllerCompat
- .getTransportControls()
- .prepareFromMediaId(
- pendingSetMediaUriRequest.value, pendingSetMediaUriRequest.extras);
- break;
- case MEDIA_URI_QUERY_QUERY:
- controllerCompat
- .getTransportControls()
- .prepareFromSearch(pendingSetMediaUriRequest.value, pendingSetMediaUriRequest.extras);
- break;
- case MEDIA_URI_QUERY_URI:
- controllerCompat
- .getTransportControls()
- .prepareFromUri(
- Uri.parse(pendingSetMediaUriRequest.value), pendingSetMediaUriRequest.extras);
- break;
- default:
- throw new IllegalStateException("Unexpected type " + pendingSetMediaUriRequest.type);
- }
- pendingSetMediaUriRequest.result.set(new SessionResult(RESULT_SUCCESS));
- pendingSetMediaUriRequest = null;
+ if (hasMedia()) {
+ initializeLegacyPlaylist();
}
}
@@ -655,63 +606,71 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
}
@Override
- public void setMediaItem(MediaItem unusedMediaItem) {
- Log.w(TAG, "Session doesn't support setting media items");
+ public void setMediaItem(MediaItem mediaItem) {
+ setMediaItem(mediaItem, /* startPositionMs= */ C.TIME_UNSET);
}
@Override
- public void setMediaItem(MediaItem unusedMediaItem, long unusedStartPositionMs) {
- Log.w(TAG, "Session doesn't support setting media items");
+ public void setMediaItem(MediaItem mediaItem, long startPositionMs) {
+ setMediaItems(ImmutableList.of(mediaItem), /* startIndex= */ 0, startPositionMs);
}
@Override
- public void setMediaItem(MediaItem unusedMediaItem, boolean unusedResetPosition) {
- Log.w(TAG, "Session doesn't support setting media items");
+ public void setMediaItem(MediaItem mediaItem, boolean resetPosition) {
+ setMediaItem(mediaItem);
}
@Override
- public void setMediaItems(List unusedMediaItems) {
- Log.w(TAG, "Session doesn't support setting media items");
+ public void setMediaItems(List mediaItems) {
+ setMediaItems(mediaItems, /* startIndex= */ 0, /* startPositionMs= */ C.TIME_UNSET);
}
@Override
- public void setMediaItems(List unusedMediaItems, boolean unusedResetPosition) {
- Log.w(TAG, "Session doesn't support setting media items");
+ public void setMediaItems(List mediaItems, boolean resetPosition) {
+ setMediaItems(mediaItems);
}
@Override
- public void setMediaItems(
- List unusedMediaItems, int unusedStartIndex, long unusedStartPositionMs) {
- Log.w(TAG, "Session doesn't support setting media items");
+ public void setMediaItems(List mediaItems, int startIndex, long startPositionMs) {
+ if (mediaItems.isEmpty()) {
+ clearMediaItems();
+ return;
+ }
+ QueueTimeline newQueueTimeline =
+ QueueTimeline.DEFAULT.copyWithNewMediaItems(/* index= */ 0, mediaItems);
+ if (startPositionMs == C.TIME_UNSET) {
+ // Assume a default start position of 0 until we know more.
+ startPositionMs = 0;
+ }
+ PlayerInfo maskedPlayerInfo =
+ controllerInfo.playerInfo.copyWithTimelineAndSessionPositionInfo(
+ newQueueTimeline,
+ createSessionPositionInfo(
+ createPositionInfo(startIndex, mediaItems.get(startIndex), startPositionMs),
+ /* isPlayingAd= */ false,
+ /* durationMs= */ C.TIME_UNSET,
+ /* bufferedPositionMs= */ 0,
+ /* bufferedPercentage= */ 0,
+ /* totalBufferedDurationMs= */ 0));
+ ControllerInfo maskedControllerInfo =
+ new ControllerInfo(
+ maskedPlayerInfo,
+ controllerInfo.availableSessionCommands,
+ controllerInfo.availablePlayerCommands,
+ controllerInfo.customLayout);
+ updateStateMaskedControllerInfo(
+ maskedControllerInfo,
+ /* discontinuityReason= */ null,
+ /* mediaItemTransitionReason= */ null);
+ if (isPrepared()) {
+ initializeLegacyPlaylist();
+ }
}
@Override
public ListenableFuture setMediaUri(Uri uri, Bundle extras) {
- if (pendingSetMediaUriRequest != null) {
- Log.w(
- TAG,
- "SetMediaUri() is called multiple times without prepare() nor play()."
- + " Previous call will be skipped.");
- pendingSetMediaUriRequest.result.set(new SessionResult(RESULT_INFO_SKIPPED));
- pendingSetMediaUriRequest = null;
- }
- SettableFuture result = SettableFuture.create();
- if (uri.toString().startsWith(MEDIA_URI_SET_MEDIA_URI_PREFIX)
- && uri.getQueryParameterNames().size() == 1) {
- String queryParameterName = uri.getQueryParameterNames().iterator().next();
- if (TextUtils.equals(queryParameterName, MEDIA_URI_QUERY_ID)
- || TextUtils.equals(queryParameterName, MEDIA_URI_QUERY_QUERY)
- || TextUtils.equals(queryParameterName, MEDIA_URI_QUERY_URI)) {
- pendingSetMediaUriRequest =
- new SetMediaUriRequest(
- queryParameterName, uri.getQueryParameter(queryParameterName), extras, result);
- }
- }
- if (pendingSetMediaUriRequest == null) {
- pendingSetMediaUriRequest =
- new SetMediaUriRequest(MEDIA_URI_QUERY_URI, uri.toString(), extras, result);
- }
- return result;
+ Log.w(TAG, "Session doesn't support setMediaUri");
+ return Futures.immediateCancelledFuture();
}
@Override
@@ -744,9 +703,14 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
if (mediaItems.isEmpty()) {
return;
}
- index = min(index, getCurrentTimeline().getWindowCount());
-
QueueTimeline queueTimeline = (QueueTimeline) controllerInfo.playerInfo.timeline;
+ if (queueTimeline.isEmpty()) {
+ // Handle initial items in setMediaItems to ensure initial legacy session commands are called.
+ setMediaItems(mediaItems);
+ return;
+ }
+
+ index = min(index, getCurrentTimeline().getWindowCount());
QueueTimeline newQueueTimeline = queueTimeline.copyWithNewMediaItems(index, mediaItems);
int currentMediaItemIndex = getCurrentMediaItemIndex();
int newCurrentMediaItemIndex =
@@ -765,10 +729,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/* discontinuityReason= */ null,
/* mediaItemTransitionReason= */ null);
- for (int i = 0; i < mediaItems.size(); i++) {
- MediaItem mediaItem = mediaItems.get(i);
- controllerCompat.addQueueItem(
- MediaUtils.convertToMediaDescriptionCompat(mediaItem), index + i);
+ if (isPrepared()) {
+ for (int i = 0; i < mediaItems.size(); i++) {
+ MediaItem mediaItem = mediaItems.get(i);
+ controllerCompat.addQueueItem(
+ MediaUtils.convertToMediaDescriptionCompat(mediaItem), index + i);
+ }
}
}
@@ -815,8 +781,10 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/* discontinuityReason= */ null,
/* mediaItemTransitionReason= */ null);
- for (int i = fromIndex; i < toIndex && i < legacyPlayerInfo.queue.size(); i++) {
- controllerCompat.removeQueueItem(legacyPlayerInfo.queue.get(i).getDescription());
+ if (isPrepared()) {
+ for (int i = fromIndex; i < toIndex && i < legacyPlayerInfo.queue.size(); i++) {
+ controllerCompat.removeQueueItem(legacyPlayerInfo.queue.get(i).getDescription());
+ }
}
}
@@ -876,14 +844,16 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/* discontinuityReason= */ null,
/* mediaItemTransitionReason= */ null);
- ArrayList moveItems = new ArrayList<>();
- for (int i = 0; i < (toIndex - fromIndex); i++) {
- moveItems.add(legacyPlayerInfo.queue.get(fromIndex));
- controllerCompat.removeQueueItem(legacyPlayerInfo.queue.get(fromIndex).getDescription());
- }
- for (int i = 0; i < moveItems.size(); i++) {
- QueueItem item = moveItems.get(i);
- controllerCompat.addQueueItem(item.getDescription(), i + newIndex);
+ if (isPrepared()) {
+ ArrayList moveItems = new ArrayList<>();
+ for (int i = 0; i < (toIndex - fromIndex); i++) {
+ moveItems.add(legacyPlayerInfo.queue.get(fromIndex));
+ controllerCompat.removeQueueItem(legacyPlayerInfo.queue.get(fromIndex).getDescription());
+ }
+ for (int i = 0; i < moveItems.size(); i++) {
+ QueueItem item = moveItems.get(i);
+ controllerCompat.addQueueItem(item.getDescription(), i + newIndex);
+ }
}
}
@@ -1294,6 +1264,91 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
});
}
+ private boolean isPrepared() {
+ return controllerInfo.playerInfo.playbackState != STATE_IDLE;
+ }
+
+ private boolean hasMedia() {
+ return !controllerInfo.playerInfo.timeline.isEmpty();
+ }
+
+ private void initializeLegacyPlaylist() {
+ Window window = new Window();
+ checkState(isPrepared() && hasMedia());
+ QueueTimeline queueTimeline = (QueueTimeline) controllerInfo.playerInfo.timeline;
+ // Set the current item first as these calls are expected to replace the current playlist.
+ int currentIndex = controllerInfo.playerInfo.sessionPositionInfo.positionInfo.mediaItemIndex;
+ MediaItem currentMediaItem = queueTimeline.getWindow(currentIndex, window).mediaItem;
+ if (queueTimeline.getQueueId(currentIndex) != QueueItem.UNKNOWN_ID) {
+ // Current item is already known to the session. Just prepare or play.
+ if (controllerInfo.playerInfo.playWhenReady) {
+ controllerCompat.getTransportControls().play();
+ } else {
+ controllerCompat.getTransportControls().prepare();
+ }
+ } else if (currentMediaItem.requestMetadata.mediaUri != null) {
+ if (controllerInfo.playerInfo.playWhenReady) {
+ controllerCompat
+ .getTransportControls()
+ .playFromUri(
+ currentMediaItem.requestMetadata.mediaUri,
+ getOrEmptyBundle(currentMediaItem.requestMetadata.extras));
+ } else {
+ controllerCompat
+ .getTransportControls()
+ .prepareFromUri(
+ currentMediaItem.requestMetadata.mediaUri,
+ getOrEmptyBundle(currentMediaItem.requestMetadata.extras));
+ }
+ } else if (currentMediaItem.requestMetadata.searchQuery != null) {
+ if (controllerInfo.playerInfo.playWhenReady) {
+ controllerCompat
+ .getTransportControls()
+ .playFromSearch(
+ currentMediaItem.requestMetadata.searchQuery,
+ getOrEmptyBundle(currentMediaItem.requestMetadata.extras));
+ } else {
+ controllerCompat
+ .getTransportControls()
+ .prepareFromSearch(
+ currentMediaItem.requestMetadata.searchQuery,
+ getOrEmptyBundle(currentMediaItem.requestMetadata.extras));
+ }
+ } else {
+ if (controllerInfo.playerInfo.playWhenReady) {
+ controllerCompat
+ .getTransportControls()
+ .playFromMediaId(
+ currentMediaItem.mediaId,
+ getOrEmptyBundle(currentMediaItem.requestMetadata.extras));
+ } else {
+ controllerCompat
+ .getTransportControls()
+ .prepareFromMediaId(
+ currentMediaItem.mediaId,
+ getOrEmptyBundle(currentMediaItem.requestMetadata.extras));
+ }
+ }
+ // Seek to non-zero start positon if needed.
+ if (controllerInfo.playerInfo.sessionPositionInfo.positionInfo.positionMs != 0) {
+ controllerCompat
+ .getTransportControls()
+ .seekTo(controllerInfo.playerInfo.sessionPositionInfo.positionInfo.positionMs);
+ }
+ // Add all other items to the playlist if supported.
+ if (getAvailableCommands().contains(Player.COMMAND_CHANGE_MEDIA_ITEMS)) {
+ for (int i = 0; i < queueTimeline.getWindowCount(); i++) {
+ if (i == currentIndex || queueTimeline.getQueueId(i) != QueueItem.UNKNOWN_ID) {
+ // Skip the current item (added above) and all items already known to the session.
+ continue;
+ }
+ MediaItem mediaItem = queueTimeline.getWindow(/* windowIndex= */ i, window).mediaItem;
+ controllerCompat.addQueueItem(
+ MediaUtils.convertToMediaDescriptionCompat(mediaItem), /* index= */ i);
+ }
+ }
+ }
+
private void handleNewLegacyParameters(
boolean notifyConnected, LegacyPlayerInfo newLegacyPlayerInfo) {
if (released || !connected) {
@@ -1938,6 +1993,10 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
return state;
}
+ private static Bundle getOrEmptyBundle(@Nullable Bundle bundle) {
+ return bundle == null ? Bundle.EMPTY : bundle;
+ }
+
private static long getActiveQueueId(@Nullable PlaybackStateCompat playbackStateCompat) {
return playbackStateCompat == null
? QueueItem.UNKNOWN_ID
@@ -2088,22 +2147,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/* contentBufferedPositionMs= */ bufferedPositionMs);
}
- private static final class SetMediaUriRequest {
-
- public final String type;
- public final String value;
- public final Bundle extras;
- public final SettableFuture result;
-
- public SetMediaUriRequest(
- String type, String value, Bundle extras, SettableFuture result) {
- this.type = type;
- this.value = value;
- this.extras = extras;
- this.result = result;
- }
- }
-
// Media 1.0 variables
private static final class LegacyPlayerInfo {
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSession.java b/libraries/session/src/main/java/androidx/media3/session/MediaSession.java
index 194f074264..71edff1179 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaSession.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaSession.java
@@ -88,7 +88,7 @@ import java.util.List;
* Threading Model
* Media Key Events Mapping
* Supporting Multiple Sessions
- * Backward Compatibility with Legacy Session APIs
+ * Backward Compatibility with Legacy Session APIs
* Backward Compatibility with Legacy Controller APIs
*
*
@@ -201,10 +201,10 @@ import java.util.List;
*
* Backward Compatibility with Legacy Controller APIs
*
- * In addition to {@link MediaController}, session also supports connection from the legacy
- * controller APIs - {@link android.media.session.MediaController framework controller} and {@link
- * MediaControllerCompat AndroidX controller compat}. However, {@link ControllerInfo} may not be
- * precise for legacy controllers. See {@link ControllerInfo} for the details.
+ *
In addition to {@link MediaController}, the session also supports connections from the legacy
+ * controller APIs - {@linkplain android.media.session.MediaController framework controller} and
+ * {@linkplain MediaControllerCompat AndroidX controller compat}. However, {@link ControllerInfo}
+ * may not be precise for legacy controllers. See {@link ControllerInfo} for the details.
*
*
Unknown package name nor UID doesn't mean that you should disallow connection nor commands.
* For SDK levels where such issues happen, session tokens could only be obtained by trusted
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java b/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java
index f3459fccca..d85bc1194b 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java
@@ -30,6 +30,7 @@ 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;
@@ -1058,7 +1059,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
COMMAND_SEEK_TO_NEXT,
COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
COMMAND_GET_MEDIA_ITEMS_METADATA,
- COMMAND_GET_CURRENT_MEDIA_ITEM);
+ 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);
diff --git a/libraries/session/src/main/java/androidx/media3/session/QueueTimeline.java b/libraries/session/src/main/java/androidx/media3/session/QueueTimeline.java
index be92deea32..adaf65d707 100644
--- a/libraries/session/src/main/java/androidx/media3/session/QueueTimeline.java
+++ b/libraries/session/src/main/java/androidx/media3/session/QueueTimeline.java
@@ -79,11 +79,11 @@ import java.util.Map;
newMediaItemsBuilder.build(), unmodifiableMediaItemToQueueIdMap, fakeMediaItem);
}
- public QueueTimeline copyWithNewMediaItems(int addToIndex, List newMediaItems) {
+ public QueueTimeline copyWithNewMediaItems(int index, List newMediaItems) {
ImmutableList.Builder newMediaItemsBuilder = new ImmutableList.Builder<>();
- newMediaItemsBuilder.addAll(mediaItems.subList(0, addToIndex));
+ newMediaItemsBuilder.addAll(mediaItems.subList(0, index));
newMediaItemsBuilder.addAll(newMediaItems);
- newMediaItemsBuilder.addAll(mediaItems.subList(addToIndex, mediaItems.size()));
+ newMediaItemsBuilder.addAll(mediaItems.subList(index, mediaItems.size()));
return new QueueTimeline(
newMediaItemsBuilder.build(), unmodifiableMediaItemToQueueIdMap, fakeMediaItem);
}
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerWithMediaSessionCompatTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerWithMediaSessionCompatTest.java
index 882dc54dff..1ea60474e6 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerWithMediaSessionCompatTest.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerWithMediaSessionCompatTest.java
@@ -29,7 +29,6 @@ import static androidx.media3.common.Player.STATE_BUFFERING;
import static androidx.media3.common.Player.STATE_READY;
import static androidx.media3.session.MediaConstants.ARGUMENT_CAPTIONING_ENABLED;
import static androidx.media3.session.MediaConstants.SESSION_COMMAND_ON_CAPTIONING_ENABLED_CHANGED;
-import static androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED;
import static androidx.media3.session.SessionResult.RESULT_SUCCESS;
import static androidx.media3.test.session.common.CommonConstants.DEFAULT_TEST_NAME;
import static androidx.media3.test.session.common.CommonConstants.METADATA_ALBUM_TITLE;
@@ -37,10 +36,8 @@ import static androidx.media3.test.session.common.CommonConstants.METADATA_ARTIS
import static androidx.media3.test.session.common.CommonConstants.METADATA_DESCRIPTION;
import static androidx.media3.test.session.common.CommonConstants.METADATA_TITLE;
import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME;
-import static androidx.media3.test.session.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.app.PendingIntent;
@@ -85,8 +82,6 @@ import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -567,77 +562,6 @@ public class MediaControllerWithMediaSessionCompatTest {
assertThat(isPlayingAdRef.get()).isTrue();
}
- @Test
- public void setMediaUri_resultSetAfterPrepare() throws Exception {
- MediaController controller = controllerTestRule.createController(session.getSessionToken());
-
- Uri testUri = Uri.parse("androidx://test");
- ListenableFuture future =
- threadTestRule
- .getHandler()
- .postAndSync(() -> controller.setMediaUri(testUri, /* extras= */ Bundle.EMPTY));
-
- SessionResult result;
- try {
- result = future.get(NO_RESPONSE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- assertWithMessage("TimeoutException is expected").fail();
- } catch (TimeoutException e) {
- // expected.
- }
-
- threadTestRule.getHandler().postAndSync(controller::prepare);
-
- result = future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
- assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
- }
-
- @Test
- public void setMediaUri_resultSetAfterPlay() throws Exception {
- MediaController controller = controllerTestRule.createController(session.getSessionToken());
-
- Uri testUri = Uri.parse("androidx://test");
- ListenableFuture future =
- threadTestRule
- .getHandler()
- .postAndSync(() -> controller.setMediaUri(testUri, /* extras= */ Bundle.EMPTY));
-
- SessionResult result;
- try {
- result = future.get(NO_RESPONSE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- assertWithMessage("TimeoutException is expected").fail();
- } catch (TimeoutException e) {
- // expected.
- }
-
- threadTestRule.getHandler().postAndSync(controller::play);
-
- result = future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
- assertThat(result.resultCode).isEqualTo(RESULT_SUCCESS);
- }
-
- @Test
- public void setMediaUris_multipleCalls_previousCallReturnsResultInfoSkipped() throws Exception {
- MediaController controller = controllerTestRule.createController(session.getSessionToken());
-
- Uri testUri1 = Uri.parse("androidx://test1");
- Uri testUri2 = Uri.parse("androidx://test2");
- ListenableFuture future1 =
- threadTestRule
- .getHandler()
- .postAndSync(() -> controller.setMediaUri(testUri1, /* extras= */ Bundle.EMPTY));
- ListenableFuture future2 =
- threadTestRule
- .getHandler()
- .postAndSync(() -> controller.setMediaUri(testUri2, /* extras= */ Bundle.EMPTY));
-
- threadTestRule.getHandler().postAndSync(controller::prepare);
-
- SessionResult result1 = future1.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
- SessionResult result2 = future2.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
- assertThat(result1.resultCode).isEqualTo(RESULT_INFO_SKIPPED);
- assertThat(result2.resultCode).isEqualTo(RESULT_SUCCESS);
- }
-
@Test
public void seekToDefaultPosition_withMediaItemIndex_updatesExpectedMediaItemIndex()
throws Exception {
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCompatCallbackWithMediaControllerTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCompatCallbackWithMediaControllerTest.java
index 57996844a2..ca25291cf4 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCompatCallbackWithMediaControllerTest.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCompatCallbackWithMediaControllerTest.java
@@ -16,13 +16,6 @@
package androidx.media3.session;
import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS;
-import static androidx.media3.session.MediaConstants.MEDIA_URI_AUTHORITY;
-import static androidx.media3.session.MediaConstants.MEDIA_URI_PATH_SET_MEDIA_URI;
-import static androidx.media3.session.MediaConstants.MEDIA_URI_QUERY_ID;
-import static androidx.media3.session.MediaConstants.MEDIA_URI_QUERY_QUERY;
-import static androidx.media3.session.MediaConstants.MEDIA_URI_QUERY_URI;
-import static androidx.media3.session.MediaConstants.MEDIA_URI_SCHEME;
-import static androidx.media3.test.session.common.TestUtils.NO_RESPONSE_TIMEOUT_MS;
import static androidx.media3.test.session.common.TestUtils.VOLUME_CHANGE_TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@@ -78,6 +71,9 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
// The maximum time to wait for an operation.
private static final long TIMEOUT_MS = 3000L;
+ // Timeout used where the test expects no operation.
+ private static final long NOOP_TIMEOUT_MS = 500L;
+
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
@Rule public final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
@@ -122,6 +118,11 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
@Test
public void play() throws Exception {
+ List testList = MediaTestUtils.createMediaItems(/* size= */ 2);
+ List testQueue = MediaUtils.convertToQueueItemList(testList);
+ session.setQueue(testQueue);
+ session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
+ setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
RemoteMediaController controller = createControllerAndWaitConnection();
sessionCallback.reset(1);
@@ -132,6 +133,11 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
@Test
public void pause() throws Exception {
+ List testList = MediaTestUtils.createMediaItems(/* size= */ 2);
+ List testQueue = MediaUtils.convertToQueueItemList(testList);
+ session.setQueue(testQueue);
+ session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
+ setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
RemoteMediaController controller = createControllerAndWaitConnection();
sessionCallback.reset(1);
@@ -142,20 +148,31 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
@Test
public void prepare() throws Exception {
+ List testList = MediaTestUtils.createMediaItems(/* size= */ 2);
+ List testQueue = MediaUtils.convertToQueueItemList(testList);
+ session.setQueue(testQueue);
+ session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
RemoteMediaController controller = createControllerAndWaitConnection();
sessionCallback.reset(1);
controller.prepare();
+
assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
assertThat(sessionCallback.onPrepareCalled).isEqualTo(true);
}
@Test
public void stop() throws Exception {
+ List testList = MediaTestUtils.createMediaItems(/* size= */ 2);
+ List testQueue = MediaUtils.convertToQueueItemList(testList);
+ session.setQueue(testQueue);
+ session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
RemoteMediaController controller = createControllerAndWaitConnection();
+ controller.prepare();
sessionCallback.reset(1);
controller.stop();
+
assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
assertThat(sessionCallback.onStopCalled).isEqualTo(true);
}
@@ -314,6 +331,7 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
session.setQueue(testQueue);
session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
+ setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
RemoteMediaController controller = createControllerAndWaitConnection();
sessionCallback.reset(size);
@@ -338,6 +356,7 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
session.setQueue(MediaUtils.convertToQueueItemList(testList));
session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
+ setPlaybackState(PlaybackStateCompat.STATE_BUFFERING);
RemoteMediaController controller = createControllerAndWaitConnection();
sessionCallback.reset(count);
@@ -634,269 +653,6 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
assertThat(MediaUtils.convertToRating(sessionCallback.rating)).isEqualTo(rating);
}
- @Test
- public void setMediaUri_ignored() throws Exception {
- RemoteMediaController controller = createControllerAndWaitConnection();
- sessionCallback.reset(1);
-
- controller.setMediaUri(Uri.parse("androidx://test?test=xx"), /* extras= */ Bundle.EMPTY);
-
- assertThat(sessionCallback.await(NO_RESPONSE_TIMEOUT_MS)).isFalse();
- }
-
- @Test
- public void setMediaUri_followedByPrepare_callsPrepareFromMediaId() throws Exception {
- String testMediaId = "anyMediaId";
- Bundle testExtras = new Bundle();
- testExtras.putString("testKey", "testValue");
-
- RemoteMediaController controller = createControllerAndWaitConnection();
- sessionCallback.reset(1);
-
- controller.setMediaUri(
- new Uri.Builder()
- .scheme(MEDIA_URI_SCHEME)
- .authority(MEDIA_URI_AUTHORITY)
- .path(MEDIA_URI_PATH_SET_MEDIA_URI)
- .appendQueryParameter(MEDIA_URI_QUERY_ID, testMediaId)
- .build(),
- testExtras);
- controller.prepare();
-
- assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
- assertThat(sessionCallback.onPrepareFromMediaIdCalled).isTrue();
- assertThat(sessionCallback.mediaId).isEqualTo(testMediaId);
- assertThat(TestUtils.equals(testExtras, sessionCallback.extras)).isTrue();
- assertThat(sessionCallback.query).isNull();
- assertThat(sessionCallback.uri).isNull();
- assertThat(sessionCallback.onPrepareCalled).isFalse();
- }
-
- @Test
- public void setMediaUri_followedByPrepare_callsPrepareFromSearch() throws Exception {
- String testSearchQuery = "anyQuery";
- Bundle testExtras = new Bundle();
- testExtras.putString("testKey", "testValue");
-
- RemoteMediaController controller = createControllerAndWaitConnection();
- sessionCallback.reset(1);
-
- controller.setMediaUri(
- new Uri.Builder()
- .scheme(MEDIA_URI_SCHEME)
- .authority(MEDIA_URI_AUTHORITY)
- .path(MEDIA_URI_PATH_SET_MEDIA_URI)
- .appendQueryParameter(MEDIA_URI_QUERY_QUERY, testSearchQuery)
- .build(),
- testExtras);
- controller.prepare();
-
- assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
- assertThat(sessionCallback.onPrepareFromSearchCalled).isTrue();
- assertThat(sessionCallback.query).isEqualTo(testSearchQuery);
- assertThat(TestUtils.equals(testExtras, sessionCallback.extras)).isTrue();
- assertThat(sessionCallback.mediaId).isNull();
- assertThat(sessionCallback.uri).isNull();
- assertThat(sessionCallback.onPrepareCalled).isFalse();
- }
-
- @Test
- public void setMediaUri_followedByPrepare_callsPrepareFromUri() throws Exception {
- Uri testMediaUri = Uri.parse("androidx://jetpack/test?query=android%20media");
- Bundle testExtras = new Bundle();
- testExtras.putString("testKey", "testValue");
-
- RemoteMediaController controller = createControllerAndWaitConnection();
- sessionCallback.reset(1);
-
- controller.setMediaUri(
- new Uri.Builder()
- .scheme(MEDIA_URI_SCHEME)
- .authority(MEDIA_URI_AUTHORITY)
- .path(MEDIA_URI_PATH_SET_MEDIA_URI)
- .appendQueryParameter(MEDIA_URI_QUERY_URI, testMediaUri.toString())
- .build(),
- testExtras);
- controller.prepare();
-
- assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
- assertThat(sessionCallback.onPrepareFromUriCalled).isTrue();
- assertThat(sessionCallback.uri).isEqualTo(testMediaUri);
- assertThat(sessionCallback.mediaId).isNull();
- assertThat(sessionCallback.query).isNull();
- assertThat(sessionCallback.onPrepareCalled).isFalse();
- }
-
- @Test
- public void setMediaUri_withoutFormattingFollowedByPrepare_callsPrepareFromUri()
- throws Exception {
- Uri testMediaUri = Uri.parse("androidx://jetpack/test?query=android%20media");
- Bundle testExtras = new Bundle();
- testExtras.putString("testKey", "testValue");
-
- RemoteMediaController controller = createControllerAndWaitConnection();
- sessionCallback.reset(1);
-
- controller.setMediaUri(testMediaUri, testExtras);
- controller.prepare();
-
- assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
- assertThat(sessionCallback.onPrepareFromUriCalled).isTrue();
- assertThat(sessionCallback.uri).isEqualTo(testMediaUri);
- assertThat(sessionCallback.mediaId).isNull();
- assertThat(sessionCallback.query).isNull();
- assertThat(sessionCallback.onPrepareCalled).isFalse();
- }
-
- @Test
- public void setMediaUri_followedByPlay_callsPlayFromMediaId() throws Exception {
- String testMediaId = "anyMediaId";
- Bundle testExtras = new Bundle();
- testExtras.putString("testKey", "testValue");
-
- RemoteMediaController controller = createControllerAndWaitConnection();
- sessionCallback.reset(1);
-
- controller.setMediaUri(
- new Uri.Builder()
- .scheme(MEDIA_URI_SCHEME)
- .authority(MEDIA_URI_AUTHORITY)
- .path(MEDIA_URI_PATH_SET_MEDIA_URI)
- .appendQueryParameter(MEDIA_URI_QUERY_ID, testMediaId)
- .build(),
- testExtras);
- controller.play();
-
- assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
- assertThat(sessionCallback.onPlayFromMediaIdCalled).isTrue();
- assertThat(sessionCallback.mediaId).isEqualTo(testMediaId);
- assertThat(TestUtils.equals(testExtras, sessionCallback.extras)).isTrue();
- assertThat(sessionCallback.query).isNull();
- assertThat(sessionCallback.uri).isNull();
- assertThat(sessionCallback.onPlayCalledCount).isEqualTo(0);
- }
-
- @Test
- public void setMediaUri_followedByPlay_callsPlayFromSearch() throws Exception {
- String testSearchQuery = "anyQuery";
- Bundle testExtras = new Bundle();
- testExtras.putString("testKey", "testValue");
-
- RemoteMediaController controller = createControllerAndWaitConnection();
- sessionCallback.reset(1);
-
- controller.setMediaUri(
- new Uri.Builder()
- .scheme(MEDIA_URI_SCHEME)
- .authority(MEDIA_URI_AUTHORITY)
- .path(MEDIA_URI_PATH_SET_MEDIA_URI)
- .appendQueryParameter(MEDIA_URI_QUERY_QUERY, testSearchQuery)
- .build(),
- testExtras);
- controller.play();
-
- assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
- assertThat(sessionCallback.onPlayFromSearchCalled).isTrue();
- assertThat(sessionCallback.query).isEqualTo(testSearchQuery);
- assertThat(TestUtils.equals(testExtras, sessionCallback.extras)).isTrue();
- assertThat(sessionCallback.mediaId).isNull();
- assertThat(sessionCallback.uri).isNull();
- assertThat(sessionCallback.onPlayCalledCount).isEqualTo(0);
- }
-
- @Test
- public void setMediaUri_followedByPlay_callsPlayFromUri() throws Exception {
- Uri testMediaUri = Uri.parse("androidx://jetpack/test?query=android%20media");
- Bundle testExtras = new Bundle();
- testExtras.putString("testKey", "testValue");
-
- RemoteMediaController controller = createControllerAndWaitConnection();
- sessionCallback.reset(1);
-
- controller.setMediaUri(
- new Uri.Builder()
- .scheme(MEDIA_URI_SCHEME)
- .authority(MEDIA_URI_AUTHORITY)
- .path(MEDIA_URI_PATH_SET_MEDIA_URI)
- .appendQueryParameter(MEDIA_URI_QUERY_URI, testMediaUri.toString())
- .build(),
- testExtras);
- controller.play();
-
- assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
- assertThat(sessionCallback.onPlayFromUriCalled).isTrue();
- assertThat(sessionCallback.uri).isEqualTo(testMediaUri);
- assertThat(sessionCallback.mediaId).isNull();
- assertThat(sessionCallback.query).isNull();
- assertThat(sessionCallback.onPlayCalledCount).isEqualTo(0);
- }
-
- @Test
- public void setMediaUri_withoutFormattingFollowedByPlay_callsPlayFromUri() throws Exception {
- Uri testMediaUri = Uri.parse("androidx://jetpack/test?query=android%20media");
- Bundle testExtras = new Bundle();
- testExtras.putString("testKey", "testValue");
-
- RemoteMediaController controller = createControllerAndWaitConnection();
- sessionCallback.reset(1);
-
- controller.setMediaUri(testMediaUri, testExtras);
- controller.play();
-
- assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
- assertThat(sessionCallback.onPlayFromUriCalled).isTrue();
- assertThat(sessionCallback.uri).isEqualTo(testMediaUri);
- assertThat(sessionCallback.mediaId).isNull();
- assertThat(sessionCallback.query).isNull();
- assertThat(sessionCallback.onPlayCalledCount).isEqualTo(0);
- }
-
- @Test
- public void setMediaUri_followedByPrepareTwice_callsPrepareFromUriAndPrepare() throws Exception {
- RemoteMediaController controller = createControllerAndWaitConnection();
- sessionCallback.reset(2);
-
- controller.setMediaUri(Uri.parse("androidx://test"), /* extras= */ Bundle.EMPTY);
-
- controller.prepare();
- controller.prepare();
-
- assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
- assertThat(sessionCallback.onPrepareFromUriCalled).isTrue();
- assertThat(sessionCallback.onPrepareCalled).isTrue();
- }
-
- @Test
- public void setMediaUri_followedByPlayTwice_callsPlayFromUriAndPlay() throws Exception {
- RemoteMediaController controller = createControllerAndWaitConnection();
- sessionCallback.reset(2);
-
- controller.setMediaUri(Uri.parse("androidx://test"), /* extras= */ Bundle.EMPTY);
-
- controller.play();
- controller.play();
-
- assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
- assertThat(sessionCallback.onPlayFromUriCalled).isTrue();
- assertThat(sessionCallback.onPlayCalledCount).isEqualTo(1);
- }
-
- @Test
- public void setMediaUri_multipleCalls_skipped() throws Exception {
- RemoteMediaController controller = createControllerAndWaitConnection();
- sessionCallback.reset(2);
-
- Uri testUri1 = Uri.parse("androidx://test1");
- Uri testUri2 = Uri.parse("androidx://test2");
- controller.setMediaUri(testUri1, /* extras= */ Bundle.EMPTY);
- controller.setMediaUri(testUri2, /* extras= */ Bundle.EMPTY);
- controller.prepare();
-
- assertThat(sessionCallback.await(TIMEOUT_MS)).isFalse();
- assertThat(sessionCallback.onPrepareFromUriCalled).isTrue();
- assertThat(sessionCallback.uri).isEqualTo(testUri2);
- }
-
@Test
public void seekToNext_callsOnSkipToNext() throws Exception {
RemoteMediaController controller = createControllerAndWaitConnection();