Support setMediaItem(s) in MediaControllerImplLegacy
These calls were not implemented so far as they require a mix of initial prepareFrom/playFrom calls and addQueueItem. We can also support clients without queue handling to set single MediaItems. To make the calls consistent and predictable in the session, we need to ensure that none of the play/pause/addQueueItem/ removeQueueItem/prepare/playFromXYZ/prepareFromXYZ are called before the controller is prepared and has media. #minor-release PiperOrigin-RevId: 455110246 (cherry picked from commit b475f1f2daba8e0ed2497cbf17f4b834e58c59a4)
This commit is contained in:
parent
080b1862c2
commit
6ed3e40681
@ -179,6 +179,8 @@
|
|||||||
of requests.
|
of requests.
|
||||||
* Forward legacy `MediaController` calls to play media to
|
* Forward legacy `MediaController` calls to play media to
|
||||||
`MediaSession.Callback.onAddMediaItems` instead of `onSetMediaUri`.
|
`MediaSession.Callback.onAddMediaItems` instead of `onSetMediaUri`.
|
||||||
|
* Support `setMediaItems(s)` methods when `MediaController` connects to a
|
||||||
|
legacy media session.
|
||||||
* Data sources:
|
* Data sources:
|
||||||
* Rename `DummyDataSource` to `PlaceholderDataSource`.
|
* Rename `DummyDataSource` to `PlaceholderDataSource`.
|
||||||
* Workaround OkHttp interrupt handling.
|
* Workaround OkHttp interrupt handling.
|
||||||
|
@ -81,13 +81,14 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
|||||||
* <li><a href="#ControllerLifeCycle">Controller Lifecycle</a>
|
* <li><a href="#ControllerLifeCycle">Controller Lifecycle</a>
|
||||||
* <li><a href="#ThreadingModel">Threading Model</a>
|
* <li><a href="#ThreadingModel">Threading Model</a>
|
||||||
* <li><a href="#PackageVisibilityFilter">Package Visibility Filter</a>
|
* <li><a href="#PackageVisibilityFilter">Package Visibility Filter</a>
|
||||||
|
* <li><a href="#BackwardCompatibility">Backward Compatibility with legacy media sessions</a>
|
||||||
* </ol>
|
* </ol>
|
||||||
*
|
*
|
||||||
* <h2 id="ControllerLifeCycle">Controller Lifecycle</h2>
|
* <h2 id="ControllerLifeCycle">Controller Lifecycle</h2>
|
||||||
*
|
*
|
||||||
* <p>When a controller is created with the {@link SessionToken} for a {@link MediaSession} (i.e.
|
* <p>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
|
* session token type is {@link SessionToken#TYPE_SESSION}), the controller will connect to the
|
||||||
* specific session.
|
* specific session.F
|
||||||
*
|
*
|
||||||
* <p>When a controller is created with the {@link SessionToken} for a {@link MediaSessionService}
|
* <p>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
|
* (i.e. session token type is {@link SessionToken#TYPE_SESSION_SERVICE} or {@link
|
||||||
@ -127,6 +128,34 @@ import org.checkerframework.checker.initialization.qual.Initialized;
|
|||||||
* <!-- Or, as a package name -->
|
* <!-- Or, as a package name -->
|
||||||
* <package android:name="package_name_of_the_other_app" />
|
* <package android:name="package_name_of_the_other_app" />
|
||||||
* }</pre>
|
* }</pre>
|
||||||
|
*
|
||||||
|
* <h2 id="BackwardCompatibility">Backward Compatibility with legacy media sessions</h2>
|
||||||
|
*
|
||||||
|
* <p>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}.
|
||||||
|
*
|
||||||
|
* <p>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()}:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link MediaSessionCompat.Callback#onPrepareFromUri onPrepareFromUri}
|
||||||
|
* <li>{@link MediaSessionCompat.Callback#onPlayFromUri onPlayFromUri}
|
||||||
|
* <li>{@link MediaSessionCompat.Callback#onPrepareFromMediaId onPrepareFromMediaId}
|
||||||
|
* <li>{@link MediaSessionCompat.Callback#onPlayFromMediaId onPlayFromMediaId}
|
||||||
|
* <li>{@link MediaSessionCompat.Callback#onPrepareFromSearch onPrepareFromSearch}
|
||||||
|
* <li>{@link MediaSessionCompat.Callback#onPlayFromSearch onPlayFromSearch}
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* 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 {
|
public class MediaController implements Player {
|
||||||
|
|
||||||
@ -478,13 +507,6 @@ public class MediaController implements Player {
|
|||||||
return impl.isConnected();
|
return impl.isConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
* <p>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
|
@Override
|
||||||
public void play() {
|
public void play() {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
@ -505,13 +527,6 @@ public class MediaController implements Player {
|
|||||||
impl.pause();
|
impl.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
* <p>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
|
@Override
|
||||||
public void prepare() {
|
public void prepare() {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
@ -980,44 +995,6 @@ public class MediaController implements Player {
|
|||||||
* <p>The {@link Player.Listener#onTimelineChanged} and/or {@link
|
* <p>The {@link Player.Listener#onTimelineChanged} and/or {@link
|
||||||
* Player.Listener#onMediaItemTransition} would be called when it's completed.
|
* Player.Listener#onMediaItemTransition} would be called when it's completed.
|
||||||
*
|
*
|
||||||
* <p>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:
|
|
||||||
*
|
|
||||||
* <table>
|
|
||||||
* <caption>Uri patterns and following API calls for MediaControllerCompat methods</caption>
|
|
||||||
* <tr>
|
|
||||||
* <th>Uri patterns</th><th>Following API calls</th><th>Method</th>
|
|
||||||
* </tr><tr>
|
|
||||||
* <td rowspan="2">{@code androidx://media3-session/setMediaUri?uri=[uri]}</td>
|
|
||||||
* <td>{@link #prepare}</td>
|
|
||||||
* <td>{@link MediaControllerCompat.TransportControls#prepareFromUri prepareFromUri}
|
|
||||||
* </tr><tr>
|
|
||||||
* <td>{@link #play}</td>
|
|
||||||
* <td>{@link MediaControllerCompat.TransportControls#playFromUri playFromUri}
|
|
||||||
* </tr><tr>
|
|
||||||
* <td rowspan="2">{@code androidx://media3-session/setMediaUri?id=[mediaId]}</td>
|
|
||||||
* <td>{@link #prepare}</td>
|
|
||||||
* <td>{@link MediaControllerCompat.TransportControls#prepareFromMediaId prepareFromMediaId}
|
|
||||||
* </tr><tr>
|
|
||||||
* <td>{@link #play}</td>
|
|
||||||
* <td>{@link MediaControllerCompat.TransportControls#playFromMediaId playFromMediaId}
|
|
||||||
* </tr><tr>
|
|
||||||
* <td rowspan="2">{@code androidx://media3-session/setMediaUri?query=[query]}</td>
|
|
||||||
* <td>{@link #prepare}</td>
|
|
||||||
* <td>{@link MediaControllerCompat.TransportControls#prepareFromSearch prepareFromSearch}
|
|
||||||
* </tr><tr>
|
|
||||||
* <td>{@link #play}</td>
|
|
||||||
* <td>{@link MediaControllerCompat.TransportControls#playFromSearch playFromSearch}
|
|
||||||
* </tr><tr>
|
|
||||||
* <td rowspan="2">Does not match with any pattern above</td>
|
|
||||||
* <td>{@link #prepare}</td>
|
|
||||||
* <td>{@link MediaControllerCompat.TransportControls#prepareFromUri prepareFromUri}
|
|
||||||
* </tr><tr>
|
|
||||||
* <td>{@link #play}</td>
|
|
||||||
* <td>{@link MediaControllerCompat.TransportControls#playFromUri playFromUri}
|
|
||||||
* </tr></table>
|
|
||||||
*
|
|
||||||
* <p>Returned {@link ListenableFuture} will return {@link SessionResult#RESULT_SUCCESS} when it's
|
* <p>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
|
* 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
|
* 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.
|
* @param extras A {@link Bundle} to send extra information. May be empty.
|
||||||
* @return A {@link ListenableFuture} of {@link SessionResult} representing the pending
|
* @return A {@link ListenableFuture} of {@link SessionResult} representing the pending
|
||||||
* completion.
|
* 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<SessionResult> setMediaUri(Uri uri, Bundle extras) {
|
public ListenableFuture<SessionResult> setMediaUri(Uri uri, Bundle extras) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
|
@ -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.STATE_IDLE;
|
||||||
import static androidx.media3.common.Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED;
|
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.checkNotNull;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||||
import static androidx.media3.session.MediaConstants.ARGUMENT_CAPTIONING_ENABLED;
|
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.MediaConstants.SESSION_COMMAND_ON_CAPTIONING_ENABLED_CHANGED;
|
||||||
import static androidx.media3.session.MediaUtils.POSITION_DIFF_TOLERANCE_MS;
|
import static androidx.media3.session.MediaUtils.POSITION_DIFF_TOLERANCE_MS;
|
||||||
import static androidx.media3.session.MediaUtils.calculateBufferedPercentage;
|
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 androidx.media3.session.SessionResult.RESULT_SUCCESS;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
import static java.lang.Math.min;
|
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;
|
||||||
import android.support.v4.media.session.MediaSessionCompat.QueueItem;
|
import android.support.v4.media.session.MediaSessionCompat.QueueItem;
|
||||||
import android.support.v4.media.session.PlaybackStateCompat;
|
import android.support.v4.media.session.PlaybackStateCompat;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.SurfaceHolder;
|
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 long AGGREGATES_CALLBACKS_WITHIN_TIMEOUT_MS = 500L;
|
||||||
private static final int VOLUME_FLAGS = AudioManager.FLAG_SHOW_UI;
|
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;
|
private final SessionToken token;
|
||||||
|
|
||||||
final MediaController instance;
|
|
||||||
|
|
||||||
private final ListenerSet<Listener> listeners;
|
private final ListenerSet<Listener> listeners;
|
||||||
|
|
||||||
private final ControllerCompatCallback controllerCompatCallback;
|
private final ControllerCompatCallback controllerCompatCallback;
|
||||||
|
|
||||||
@Nullable private MediaControllerCompat controllerCompat;
|
@Nullable private MediaControllerCompat controllerCompat;
|
||||||
|
|
||||||
@Nullable private MediaBrowserCompat browserCompat;
|
@Nullable private MediaBrowserCompat browserCompat;
|
||||||
|
|
||||||
private boolean released;
|
private boolean released;
|
||||||
|
|
||||||
private boolean connected;
|
private boolean connected;
|
||||||
|
|
||||||
@Nullable private SetMediaUriRequest pendingSetMediaUriRequest;
|
|
||||||
|
|
||||||
private LegacyPlayerInfo legacyPlayerInfo;
|
private LegacyPlayerInfo legacyPlayerInfo;
|
||||||
|
|
||||||
private LegacyPlayerInfo pendingLegacyPlayerInfo;
|
private LegacyPlayerInfo pendingLegacyPlayerInfo;
|
||||||
|
|
||||||
private ControllerInfo controllerInfo;
|
private ControllerInfo controllerInfo;
|
||||||
|
|
||||||
public MediaControllerImplLegacy(Context context, MediaController instance, SessionToken token) {
|
public MediaControllerImplLegacy(Context context, MediaController instance, SessionToken token) {
|
||||||
@ -177,6 +161,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
|
if (controllerInfo.playerInfo.playbackState == STATE_IDLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
PlayerInfo maskedPlayerInfo =
|
PlayerInfo maskedPlayerInfo =
|
||||||
controllerInfo.playerInfo.copyWithSessionPositionInfo(
|
controllerInfo.playerInfo.copyWithSessionPositionInfo(
|
||||||
createSessionPositionInfo(
|
createSessionPositionInfo(
|
||||||
@ -244,6 +231,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void play() {
|
public void play() {
|
||||||
|
if (controllerInfo.playerInfo.playWhenReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ControllerInfo maskedControllerInfo =
|
ControllerInfo maskedControllerInfo =
|
||||||
new ControllerInfo(
|
new ControllerInfo(
|
||||||
controllerInfo.playerInfo.copyWithPlayWhenReady(
|
controllerInfo.playerInfo.copyWithPlayWhenReady(
|
||||||
@ -258,36 +248,16 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
/* discontinuityReason= */ null,
|
/* discontinuityReason= */ null,
|
||||||
/* mediaItemTransitionReason= */ null);
|
/* mediaItemTransitionReason= */ null);
|
||||||
|
|
||||||
if (pendingSetMediaUriRequest == null) {
|
if (isPrepared() && hasMedia()) {
|
||||||
controllerCompat.getTransportControls().play();
|
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
|
@Override
|
||||||
public void pause() {
|
public void pause() {
|
||||||
|
if (!controllerInfo.playerInfo.playWhenReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ControllerInfo maskedControllerInfo =
|
ControllerInfo maskedControllerInfo =
|
||||||
new ControllerInfo(
|
new ControllerInfo(
|
||||||
controllerInfo.playerInfo.copyWithPlayWhenReady(
|
controllerInfo.playerInfo.copyWithPlayWhenReady(
|
||||||
@ -302,11 +272,16 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
/* discontinuityReason= */ null,
|
/* discontinuityReason= */ null,
|
||||||
/* mediaItemTransitionReason= */ null);
|
/* mediaItemTransitionReason= */ null);
|
||||||
|
|
||||||
|
if (isPrepared() && hasMedia()) {
|
||||||
controllerCompat.getTransportControls().pause();
|
controllerCompat.getTransportControls().pause();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepare() {
|
public void prepare() {
|
||||||
|
if (controllerInfo.playerInfo.playbackState != STATE_IDLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ControllerInfo maskedControllerInfo =
|
ControllerInfo maskedControllerInfo =
|
||||||
new ControllerInfo(
|
new ControllerInfo(
|
||||||
controllerInfo.playerInfo.copyWithPlaybackState(
|
controllerInfo.playerInfo.copyWithPlaybackState(
|
||||||
@ -322,32 +297,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
/* discontinuityReason= */ null,
|
/* discontinuityReason= */ null,
|
||||||
/* mediaItemTransitionReason= */ null);
|
/* mediaItemTransitionReason= */ null);
|
||||||
|
|
||||||
if (pendingSetMediaUriRequest == null) {
|
if (hasMedia()) {
|
||||||
controllerCompat.getTransportControls().prepare();
|
initializeLegacyPlaylist();
|
||||||
} 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -655,63 +606,71 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMediaItem(MediaItem unusedMediaItem) {
|
public void setMediaItem(MediaItem mediaItem) {
|
||||||
Log.w(TAG, "Session doesn't support setting media items");
|
setMediaItem(mediaItem, /* startPositionMs= */ C.TIME_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMediaItem(MediaItem unusedMediaItem, long unusedStartPositionMs) {
|
public void setMediaItem(MediaItem mediaItem, long startPositionMs) {
|
||||||
Log.w(TAG, "Session doesn't support setting media items");
|
setMediaItems(ImmutableList.of(mediaItem), /* startIndex= */ 0, startPositionMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMediaItem(MediaItem unusedMediaItem, boolean unusedResetPosition) {
|
public void setMediaItem(MediaItem mediaItem, boolean resetPosition) {
|
||||||
Log.w(TAG, "Session doesn't support setting media items");
|
setMediaItem(mediaItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMediaItems(List<MediaItem> unusedMediaItems) {
|
public void setMediaItems(List<MediaItem> mediaItems) {
|
||||||
Log.w(TAG, "Session doesn't support setting media items");
|
setMediaItems(mediaItems, /* startIndex= */ 0, /* startPositionMs= */ C.TIME_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMediaItems(List<MediaItem> unusedMediaItems, boolean unusedResetPosition) {
|
public void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition) {
|
||||||
Log.w(TAG, "Session doesn't support setting media items");
|
setMediaItems(mediaItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMediaItems(
|
public void setMediaItems(List<MediaItem> mediaItems, int startIndex, long startPositionMs) {
|
||||||
List<MediaItem> unusedMediaItems, int unusedStartIndex, long unusedStartPositionMs) {
|
if (mediaItems.isEmpty()) {
|
||||||
Log.w(TAG, "Session doesn't support setting media items");
|
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
|
@Override
|
||||||
public ListenableFuture<SessionResult> setMediaUri(Uri uri, Bundle extras) {
|
public ListenableFuture<SessionResult> setMediaUri(Uri uri, Bundle extras) {
|
||||||
if (pendingSetMediaUriRequest != null) {
|
Log.w(TAG, "Session doesn't support setMediaUri");
|
||||||
Log.w(
|
return Futures.immediateCancelledFuture();
|
||||||
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<SessionResult> 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -744,9 +703,14 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
if (mediaItems.isEmpty()) {
|
if (mediaItems.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
index = min(index, getCurrentTimeline().getWindowCount());
|
|
||||||
|
|
||||||
QueueTimeline queueTimeline = (QueueTimeline) controllerInfo.playerInfo.timeline;
|
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);
|
QueueTimeline newQueueTimeline = queueTimeline.copyWithNewMediaItems(index, mediaItems);
|
||||||
int currentMediaItemIndex = getCurrentMediaItemIndex();
|
int currentMediaItemIndex = getCurrentMediaItemIndex();
|
||||||
int newCurrentMediaItemIndex =
|
int newCurrentMediaItemIndex =
|
||||||
@ -765,12 +729,14 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
/* discontinuityReason= */ null,
|
/* discontinuityReason= */ null,
|
||||||
/* mediaItemTransitionReason= */ null);
|
/* mediaItemTransitionReason= */ null);
|
||||||
|
|
||||||
|
if (isPrepared()) {
|
||||||
for (int i = 0; i < mediaItems.size(); i++) {
|
for (int i = 0; i < mediaItems.size(); i++) {
|
||||||
MediaItem mediaItem = mediaItems.get(i);
|
MediaItem mediaItem = mediaItems.get(i);
|
||||||
controllerCompat.addQueueItem(
|
controllerCompat.addQueueItem(
|
||||||
MediaUtils.convertToMediaDescriptionCompat(mediaItem), index + i);
|
MediaUtils.convertToMediaDescriptionCompat(mediaItem), index + i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeMediaItem(int index) {
|
public void removeMediaItem(int index) {
|
||||||
@ -815,10 +781,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
/* discontinuityReason= */ null,
|
/* discontinuityReason= */ null,
|
||||||
/* mediaItemTransitionReason= */ null);
|
/* mediaItemTransitionReason= */ null);
|
||||||
|
|
||||||
|
if (isPrepared()) {
|
||||||
for (int i = fromIndex; i < toIndex && i < legacyPlayerInfo.queue.size(); i++) {
|
for (int i = fromIndex; i < toIndex && i < legacyPlayerInfo.queue.size(); i++) {
|
||||||
controllerCompat.removeQueueItem(legacyPlayerInfo.queue.get(i).getDescription());
|
controllerCompat.removeQueueItem(legacyPlayerInfo.queue.get(i).getDescription());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearMediaItems() {
|
public void clearMediaItems() {
|
||||||
@ -876,6 +844,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
/* discontinuityReason= */ null,
|
/* discontinuityReason= */ null,
|
||||||
/* mediaItemTransitionReason= */ null);
|
/* mediaItemTransitionReason= */ null);
|
||||||
|
|
||||||
|
if (isPrepared()) {
|
||||||
ArrayList<QueueItem> moveItems = new ArrayList<>();
|
ArrayList<QueueItem> moveItems = new ArrayList<>();
|
||||||
for (int i = 0; i < (toIndex - fromIndex); i++) {
|
for (int i = 0; i < (toIndex - fromIndex); i++) {
|
||||||
moveItems.add(legacyPlayerInfo.queue.get(fromIndex));
|
moveItems.add(legacyPlayerInfo.queue.get(fromIndex));
|
||||||
@ -886,6 +855,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
controllerCompat.addQueueItem(item.getDescription(), i + newIndex);
|
controllerCompat.addQueueItem(item.getDescription(), i + newIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCurrentPeriodIndex() {
|
public int getCurrentPeriodIndex() {
|
||||||
@ -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(
|
private void handleNewLegacyParameters(
|
||||||
boolean notifyConnected, LegacyPlayerInfo newLegacyPlayerInfo) {
|
boolean notifyConnected, LegacyPlayerInfo newLegacyPlayerInfo) {
|
||||||
if (released || !connected) {
|
if (released || !connected) {
|
||||||
@ -1938,6 +1993,10 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Bundle getOrEmptyBundle(@Nullable Bundle bundle) {
|
||||||
|
return bundle == null ? Bundle.EMPTY : bundle;
|
||||||
|
}
|
||||||
|
|
||||||
private static long getActiveQueueId(@Nullable PlaybackStateCompat playbackStateCompat) {
|
private static long getActiveQueueId(@Nullable PlaybackStateCompat playbackStateCompat) {
|
||||||
return playbackStateCompat == null
|
return playbackStateCompat == null
|
||||||
? QueueItem.UNKNOWN_ID
|
? QueueItem.UNKNOWN_ID
|
||||||
@ -2088,22 +2147,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
/* contentBufferedPositionMs= */ bufferedPositionMs);
|
/* contentBufferedPositionMs= */ bufferedPositionMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class SetMediaUriRequest {
|
|
||||||
|
|
||||||
public final String type;
|
|
||||||
public final String value;
|
|
||||||
public final Bundle extras;
|
|
||||||
public final SettableFuture<SessionResult> result;
|
|
||||||
|
|
||||||
public SetMediaUriRequest(
|
|
||||||
String type, String value, Bundle extras, SettableFuture<SessionResult> result) {
|
|
||||||
this.type = type;
|
|
||||||
this.value = value;
|
|
||||||
this.extras = extras;
|
|
||||||
this.result = result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Media 1.0 variables
|
// Media 1.0 variables
|
||||||
private static final class LegacyPlayerInfo {
|
private static final class LegacyPlayerInfo {
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ import java.util.List;
|
|||||||
* <li><a href="#ThreadingModel">Threading Model</a>
|
* <li><a href="#ThreadingModel">Threading Model</a>
|
||||||
* <li><a href="#KeyEvents">Media Key Events Mapping</a>
|
* <li><a href="#KeyEvents">Media Key Events Mapping</a>
|
||||||
* <li><a href="#MultipleSessions">Supporting Multiple Sessions</a>
|
* <li><a href="#MultipleSessions">Supporting Multiple Sessions</a>
|
||||||
* <li><a href="#CompatibilitySession">Backward Compatibility with Legacy Session APIs</a>
|
* <li><a href="#BackwardCompatibility">Backward Compatibility with Legacy Session APIs</a>
|
||||||
* <li><a href="#CompatibilityController">Backward Compatibility with Legacy Controller APIs</a>
|
* <li><a href="#CompatibilityController">Backward Compatibility with Legacy Controller APIs</a>
|
||||||
* </ol>
|
* </ol>
|
||||||
*
|
*
|
||||||
@ -201,10 +201,10 @@ import java.util.List;
|
|||||||
*
|
*
|
||||||
* <h2 id="CompatibilityController">Backward Compatibility with Legacy Controller APIs</h2>
|
* <h2 id="CompatibilityController">Backward Compatibility with Legacy Controller APIs</h2>
|
||||||
*
|
*
|
||||||
* <p>In addition to {@link MediaController}, session also supports connection from the legacy
|
* <p>In addition to {@link MediaController}, the session also supports connections from the legacy
|
||||||
* controller APIs - {@link android.media.session.MediaController framework controller} and {@link
|
* controller APIs - {@linkplain android.media.session.MediaController framework controller} and
|
||||||
* MediaControllerCompat AndroidX controller compat}. However, {@link ControllerInfo} may not be
|
* {@linkplain MediaControllerCompat AndroidX controller compat}. However, {@link ControllerInfo}
|
||||||
* precise for legacy controllers. See {@link ControllerInfo} for the details.
|
* may not be precise for legacy controllers. See {@link ControllerInfo} for the details.
|
||||||
*
|
*
|
||||||
* <p>Unknown package name nor UID doesn't mean that you should disallow connection nor commands.
|
* <p>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
|
* For SDK levels where such issues happen, session tokens could only be obtained by trusted
|
||||||
|
@ -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;
|
||||||
import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
|
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_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_REPEAT_MODE;
|
||||||
import static androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE;
|
import static androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE;
|
||||||
import static androidx.media3.common.Player.COMMAND_SET_SPEED_AND_PITCH;
|
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,
|
||||||
COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
|
COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
|
||||||
COMMAND_GET_MEDIA_ITEMS_METADATA,
|
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;
|
boolean includePlaylistCommands = (sessionFlags & FLAG_HANDLES_QUEUE_COMMANDS) != 0;
|
||||||
if (includePlaylistCommands) {
|
if (includePlaylistCommands) {
|
||||||
playerCommandsBuilder.add(COMMAND_CHANGE_MEDIA_ITEMS);
|
playerCommandsBuilder.add(COMMAND_CHANGE_MEDIA_ITEMS);
|
||||||
|
@ -79,11 +79,11 @@ import java.util.Map;
|
|||||||
newMediaItemsBuilder.build(), unmodifiableMediaItemToQueueIdMap, fakeMediaItem);
|
newMediaItemsBuilder.build(), unmodifiableMediaItemToQueueIdMap, fakeMediaItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueueTimeline copyWithNewMediaItems(int addToIndex, List<MediaItem> newMediaItems) {
|
public QueueTimeline copyWithNewMediaItems(int index, List<MediaItem> newMediaItems) {
|
||||||
ImmutableList.Builder<MediaItem> newMediaItemsBuilder = new ImmutableList.Builder<>();
|
ImmutableList.Builder<MediaItem> newMediaItemsBuilder = new ImmutableList.Builder<>();
|
||||||
newMediaItemsBuilder.addAll(mediaItems.subList(0, addToIndex));
|
newMediaItemsBuilder.addAll(mediaItems.subList(0, index));
|
||||||
newMediaItemsBuilder.addAll(newMediaItems);
|
newMediaItemsBuilder.addAll(newMediaItems);
|
||||||
newMediaItemsBuilder.addAll(mediaItems.subList(addToIndex, mediaItems.size()));
|
newMediaItemsBuilder.addAll(mediaItems.subList(index, mediaItems.size()));
|
||||||
return new QueueTimeline(
|
return new QueueTimeline(
|
||||||
newMediaItemsBuilder.build(), unmodifiableMediaItemToQueueIdMap, fakeMediaItem);
|
newMediaItemsBuilder.build(), unmodifiableMediaItemToQueueIdMap, fakeMediaItem);
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,6 @@ import static androidx.media3.common.Player.STATE_BUFFERING;
|
|||||||
import static androidx.media3.common.Player.STATE_READY;
|
import static androidx.media3.common.Player.STATE_READY;
|
||||||
import static androidx.media3.session.MediaConstants.ARGUMENT_CAPTIONING_ENABLED;
|
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.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.session.SessionResult.RESULT_SUCCESS;
|
||||||
import static androidx.media3.test.session.common.CommonConstants.DEFAULT_TEST_NAME;
|
import static androidx.media3.test.session.common.CommonConstants.DEFAULT_TEST_NAME;
|
||||||
import static androidx.media3.test.session.common.CommonConstants.METADATA_ALBUM_TITLE;
|
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_DESCRIPTION;
|
||||||
import static androidx.media3.test.session.common.CommonConstants.METADATA_TITLE;
|
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.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 androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
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.MILLISECONDS;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
@ -85,8 +82,6 @@ import com.google.common.util.concurrent.ListenableFuture;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
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.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
@ -567,77 +562,6 @@ public class MediaControllerWithMediaSessionCompatTest {
|
|||||||
assertThat(isPlayingAdRef.get()).isTrue();
|
assertThat(isPlayingAdRef.get()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setMediaUri_resultSetAfterPrepare() throws Exception {
|
|
||||||
MediaController controller = controllerTestRule.createController(session.getSessionToken());
|
|
||||||
|
|
||||||
Uri testUri = Uri.parse("androidx://test");
|
|
||||||
ListenableFuture<SessionResult> 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<SessionResult> 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<SessionResult> future1 =
|
|
||||||
threadTestRule
|
|
||||||
.getHandler()
|
|
||||||
.postAndSync(() -> controller.setMediaUri(testUri1, /* extras= */ Bundle.EMPTY));
|
|
||||||
ListenableFuture<SessionResult> 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
|
@Test
|
||||||
public void seekToDefaultPosition_withMediaItemIndex_updatesExpectedMediaItemIndex()
|
public void seekToDefaultPosition_withMediaItemIndex_updatesExpectedMediaItemIndex()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
@ -16,13 +16,6 @@
|
|||||||
package androidx.media3.session;
|
package androidx.media3.session;
|
||||||
|
|
||||||
import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS;
|
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 androidx.media3.test.session.common.TestUtils.VOLUME_CHANGE_TIMEOUT_MS;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
@ -78,6 +71,9 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
|
|||||||
// The maximum time to wait for an operation.
|
// The maximum time to wait for an operation.
|
||||||
private static final long TIMEOUT_MS = 3000L;
|
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();
|
@ClassRule public static MainLooperTestRule mainLooperTestRule = new MainLooperTestRule();
|
||||||
|
|
||||||
@Rule public final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
|
@Rule public final HandlerThreadTestRule threadTestRule = new HandlerThreadTestRule(TAG);
|
||||||
@ -122,6 +118,11 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void play() throws Exception {
|
public void play() throws Exception {
|
||||||
|
List<MediaItem> testList = MediaTestUtils.createMediaItems(/* size= */ 2);
|
||||||
|
List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testList);
|
||||||
|
session.setQueue(testQueue);
|
||||||
|
session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
|
||||||
|
setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
|
||||||
RemoteMediaController controller = createControllerAndWaitConnection();
|
RemoteMediaController controller = createControllerAndWaitConnection();
|
||||||
sessionCallback.reset(1);
|
sessionCallback.reset(1);
|
||||||
|
|
||||||
@ -132,6 +133,11 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void pause() throws Exception {
|
public void pause() throws Exception {
|
||||||
|
List<MediaItem> testList = MediaTestUtils.createMediaItems(/* size= */ 2);
|
||||||
|
List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testList);
|
||||||
|
session.setQueue(testQueue);
|
||||||
|
session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
|
||||||
|
setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
|
||||||
RemoteMediaController controller = createControllerAndWaitConnection();
|
RemoteMediaController controller = createControllerAndWaitConnection();
|
||||||
sessionCallback.reset(1);
|
sessionCallback.reset(1);
|
||||||
|
|
||||||
@ -142,20 +148,31 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void prepare() throws Exception {
|
public void prepare() throws Exception {
|
||||||
|
List<MediaItem> testList = MediaTestUtils.createMediaItems(/* size= */ 2);
|
||||||
|
List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testList);
|
||||||
|
session.setQueue(testQueue);
|
||||||
|
session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
|
||||||
RemoteMediaController controller = createControllerAndWaitConnection();
|
RemoteMediaController controller = createControllerAndWaitConnection();
|
||||||
sessionCallback.reset(1);
|
sessionCallback.reset(1);
|
||||||
|
|
||||||
controller.prepare();
|
controller.prepare();
|
||||||
|
|
||||||
assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
|
assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
|
||||||
assertThat(sessionCallback.onPrepareCalled).isEqualTo(true);
|
assertThat(sessionCallback.onPrepareCalled).isEqualTo(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void stop() throws Exception {
|
public void stop() throws Exception {
|
||||||
|
List<MediaItem> testList = MediaTestUtils.createMediaItems(/* size= */ 2);
|
||||||
|
List<QueueItem> testQueue = MediaUtils.convertToQueueItemList(testList);
|
||||||
|
session.setQueue(testQueue);
|
||||||
|
session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
|
||||||
RemoteMediaController controller = createControllerAndWaitConnection();
|
RemoteMediaController controller = createControllerAndWaitConnection();
|
||||||
|
controller.prepare();
|
||||||
sessionCallback.reset(1);
|
sessionCallback.reset(1);
|
||||||
|
|
||||||
controller.stop();
|
controller.stop();
|
||||||
|
|
||||||
assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
|
assertThat(sessionCallback.await(TIMEOUT_MS)).isTrue();
|
||||||
assertThat(sessionCallback.onStopCalled).isEqualTo(true);
|
assertThat(sessionCallback.onStopCalled).isEqualTo(true);
|
||||||
}
|
}
|
||||||
@ -314,6 +331,7 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
|
|||||||
|
|
||||||
session.setQueue(testQueue);
|
session.setQueue(testQueue);
|
||||||
session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
|
session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
|
||||||
|
setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
|
||||||
RemoteMediaController controller = createControllerAndWaitConnection();
|
RemoteMediaController controller = createControllerAndWaitConnection();
|
||||||
sessionCallback.reset(size);
|
sessionCallback.reset(size);
|
||||||
|
|
||||||
@ -338,6 +356,7 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
|
|||||||
|
|
||||||
session.setQueue(MediaUtils.convertToQueueItemList(testList));
|
session.setQueue(MediaUtils.convertToQueueItemList(testList));
|
||||||
session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
|
session.setFlags(FLAG_HANDLES_QUEUE_COMMANDS);
|
||||||
|
setPlaybackState(PlaybackStateCompat.STATE_BUFFERING);
|
||||||
RemoteMediaController controller = createControllerAndWaitConnection();
|
RemoteMediaController controller = createControllerAndWaitConnection();
|
||||||
sessionCallback.reset(count);
|
sessionCallback.reset(count);
|
||||||
|
|
||||||
@ -634,269 +653,6 @@ public class MediaSessionCompatCallbackWithMediaControllerTest {
|
|||||||
assertThat(MediaUtils.convertToRating(sessionCallback.rating)).isEqualTo(rating);
|
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
|
@Test
|
||||||
public void seekToNext_callsOnSkipToNext() throws Exception {
|
public void seekToNext_callsOnSkipToNext() throws Exception {
|
||||||
RemoteMediaController controller = createControllerAndWaitConnection();
|
RemoteMediaController controller = createControllerAndWaitConnection();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user