Send ConnectionState
as in-process bundle if possible
#minor-release PiperOrigin-RevId: 573849858
This commit is contained in:
parent
681eadeb85
commit
d5f093f43c
@ -18,12 +18,14 @@ package androidx.media3.session;
|
|||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
|
import android.os.Binder;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.BundleCompat;
|
import androidx.core.app.BundleCompat;
|
||||||
import androidx.media3.common.Bundleable;
|
import androidx.media3.common.Bundleable;
|
||||||
import androidx.media3.common.Player;
|
import androidx.media3.common.Player;
|
||||||
|
import androidx.media3.common.util.BundleUtil;
|
||||||
import androidx.media3.common.util.BundleableUtil;
|
import androidx.media3.common.util.BundleableUtil;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
@ -69,13 +71,13 @@ import java.util.List;
|
|||||||
this.libraryVersion = libraryVersion;
|
this.libraryVersion = libraryVersion;
|
||||||
this.sessionInterfaceVersion = sessionInterfaceVersion;
|
this.sessionInterfaceVersion = sessionInterfaceVersion;
|
||||||
this.sessionBinder = sessionBinder;
|
this.sessionBinder = sessionBinder;
|
||||||
|
this.sessionActivity = sessionActivity;
|
||||||
|
this.customLayout = customLayout;
|
||||||
this.sessionCommands = sessionCommands;
|
this.sessionCommands = sessionCommands;
|
||||||
this.playerCommandsFromSession = playerCommandsFromSession;
|
this.playerCommandsFromSession = playerCommandsFromSession;
|
||||||
this.playerCommandsFromPlayer = playerCommandsFromPlayer;
|
this.playerCommandsFromPlayer = playerCommandsFromPlayer;
|
||||||
this.sessionActivity = sessionActivity;
|
|
||||||
this.tokenExtras = tokenExtras;
|
this.tokenExtras = tokenExtras;
|
||||||
this.playerInfo = playerInfo;
|
this.playerInfo = playerInfo;
|
||||||
this.customLayout = customLayout;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
@ -90,8 +92,9 @@ import java.util.List;
|
|||||||
private static final String FIELD_TOKEN_EXTRAS = Util.intToStringMaxRadix(6);
|
private static final String FIELD_TOKEN_EXTRAS = Util.intToStringMaxRadix(6);
|
||||||
private static final String FIELD_PLAYER_INFO = Util.intToStringMaxRadix(7);
|
private static final String FIELD_PLAYER_INFO = Util.intToStringMaxRadix(7);
|
||||||
private static final String FIELD_SESSION_INTERFACE_VERSION = Util.intToStringMaxRadix(8);
|
private static final String FIELD_SESSION_INTERFACE_VERSION = Util.intToStringMaxRadix(8);
|
||||||
|
private static final String FIELD_IN_PROCESS_BINDER = Util.intToStringMaxRadix(10);
|
||||||
|
|
||||||
// Next field key = 10
|
// Next field key = 11
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
@ -119,10 +122,24 @@ import java.util.List;
|
|||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Bundle} that stores a direct object reference to this class for in-process
|
||||||
|
* sharing.
|
||||||
|
*/
|
||||||
|
public Bundle toBundleInProcess() {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
BundleUtil.putBinder(bundle, FIELD_IN_PROCESS_BINDER, new InProcessBinder());
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
/** Object that can restore a {@link ConnectionState} from a {@link Bundle}. */
|
/** Object that can restore a {@link ConnectionState} from a {@link Bundle}. */
|
||||||
public static final Creator<ConnectionState> CREATOR = ConnectionState::fromBundle;
|
public static final Creator<ConnectionState> CREATOR = ConnectionState::fromBundle;
|
||||||
|
|
||||||
private static ConnectionState fromBundle(Bundle bundle) {
|
private static ConnectionState fromBundle(Bundle bundle) {
|
||||||
|
@Nullable IBinder inProcessBinder = BundleUtil.getBinder(bundle, FIELD_IN_PROCESS_BINDER);
|
||||||
|
if (inProcessBinder instanceof InProcessBinder) {
|
||||||
|
return ((InProcessBinder) inProcessBinder).getConnectionState();
|
||||||
|
}
|
||||||
int libraryVersion = bundle.getInt(FIELD_LIBRARY_VERSION, /* defaultValue= */ 0);
|
int libraryVersion = bundle.getInt(FIELD_LIBRARY_VERSION, /* defaultValue= */ 0);
|
||||||
int sessionInterfaceVersion =
|
int sessionInterfaceVersion =
|
||||||
bundle.getInt(FIELD_SESSION_INTERFACE_VERSION, /* defaultValue= */ 0);
|
bundle.getInt(FIELD_SESSION_INTERFACE_VERSION, /* defaultValue= */ 0);
|
||||||
@ -169,4 +186,10 @@ import java.util.List;
|
|||||||
tokenExtras == null ? Bundle.EMPTY : tokenExtras,
|
tokenExtras == null ? Bundle.EMPTY : tokenExtras,
|
||||||
playerInfo);
|
playerInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class InProcessBinder extends Binder {
|
||||||
|
public ConnectionState getConnectionState() {
|
||||||
|
return ConnectionState.this;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -524,7 +524,10 @@ import java.util.concurrent.ExecutionException;
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
caller.onConnected(
|
caller.onConnected(
|
||||||
sequencedFutureManager.obtainNextSequenceNumber(), state.toBundle());
|
sequencedFutureManager.obtainNextSequenceNumber(),
|
||||||
|
caller instanceof MediaControllerStub
|
||||||
|
? state.toBundleInProcess()
|
||||||
|
: state.toBundle());
|
||||||
connected = true;
|
connected = true;
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
// Controller may be died prematurely.
|
// Controller may be died prematurely.
|
||||||
|
@ -30,18 +30,21 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MediaLibraryInfo;
|
import androidx.media3.common.MediaLibraryInfo;
|
||||||
import androidx.media3.common.Player;
|
import androidx.media3.common.Player;
|
||||||
import androidx.media3.common.Rating;
|
import androidx.media3.common.Rating;
|
||||||
import androidx.media3.common.StarRating;
|
import androidx.media3.common.StarRating;
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
import androidx.media3.session.MediaSession.ConnectionResult.AcceptedResultBuilder;
|
import androidx.media3.session.MediaSession.ConnectionResult.AcceptedResultBuilder;
|
||||||
import androidx.media3.session.MediaSession.ControllerInfo;
|
import androidx.media3.session.MediaSession.ControllerInfo;
|
||||||
import androidx.media3.test.session.R;
|
import androidx.media3.test.session.R;
|
||||||
import androidx.media3.test.session.common.HandlerThreadTestRule;
|
import androidx.media3.test.session.common.HandlerThreadTestRule;
|
||||||
import androidx.media3.test.session.common.MainLooperTestRule;
|
import androidx.media3.test.session.common.MainLooperTestRule;
|
||||||
import androidx.media3.test.session.common.TestUtils;
|
import androidx.media3.test.session.common.TestUtils;
|
||||||
|
import androidx.media3.test.utils.TestExoPlayerBuilder;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import androidx.test.filters.LargeTest;
|
import androidx.test.filters.LargeTest;
|
||||||
@ -650,7 +653,7 @@ public class MediaSessionCallbackTest {
|
|||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
controllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.setMediaItems(mediaItems, /* startWindowIndex= */ 1, /* startPositionMs= */ 1234);
|
controller.setMediaItems(mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 1234);
|
||||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
||||||
|
|
||||||
assertThat(requestedMediaItems.get()).containsExactlyElementsIn(mediaItems).inOrder();
|
assertThat(requestedMediaItems.get()).containsExactlyElementsIn(mediaItems).inOrder();
|
||||||
@ -950,7 +953,7 @@ public class MediaSessionCallbackTest {
|
|||||||
new MediaSession.MediaItemsWithStartPosition(
|
new MediaSession.MediaItemsWithStartPosition(
|
||||||
updateMediaItemsWithLocalConfiguration(mediaItems),
|
updateMediaItemsWithLocalConfiguration(mediaItems),
|
||||||
startIndex,
|
startIndex,
|
||||||
/* startPosition= */ 200));
|
/* startPositionMs= */ 200));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
MediaSession session =
|
MediaSession session =
|
||||||
@ -959,7 +962,7 @@ public class MediaSessionCallbackTest {
|
|||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
controllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.setMediaItems(mediaItems, /* startWindowIndex= */ 1, /* startPositionMs= */ 100);
|
controller.setMediaItems(mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 100);
|
||||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
||||||
|
|
||||||
assertThat(requestedMediaItems.get()).containsExactlyElementsIn(mediaItems).inOrder();
|
assertThat(requestedMediaItems.get()).containsExactlyElementsIn(mediaItems).inOrder();
|
||||||
@ -999,7 +1002,7 @@ public class MediaSessionCallbackTest {
|
|||||||
RemoteMediaController controller =
|
RemoteMediaController controller =
|
||||||
controllerTestRule.createRemoteController(session.getToken());
|
controllerTestRule.createRemoteController(session.getToken());
|
||||||
|
|
||||||
controller.setMediaItems(mediaItems, /* startWindowIndex= */ 1, /* startPositionMs= */ 100);
|
controller.setMediaItems(mediaItems, /* startIndex= */ 1, /* startPositionMs= */ 100);
|
||||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS);
|
||||||
|
|
||||||
assertThat(requestedMediaItems.get()).containsExactlyElementsIn(mediaItems).inOrder();
|
assertThat(requestedMediaItems.get()).containsExactlyElementsIn(mediaItems).inOrder();
|
||||||
@ -1045,7 +1048,7 @@ public class MediaSessionCallbackTest {
|
|||||||
player.currentPosition = 200;
|
player.currentPosition = 200;
|
||||||
|
|
||||||
// Re-set media items with start index and position as current index and position
|
// Re-set media items with start index and position as current index and position
|
||||||
controller.setMediaItems(mediaItems, C.INDEX_UNSET, /* startPosition= */ 0);
|
controller.setMediaItems(mediaItems, C.INDEX_UNSET, /* startPositionMs= */ 0);
|
||||||
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS);
|
||||||
|
|
||||||
assertThat(requestedMediaItems.get()).containsExactlyElementsIn(mediaItems).inOrder();
|
assertThat(requestedMediaItems.get()).containsExactlyElementsIn(mediaItems).inOrder();
|
||||||
@ -1201,6 +1204,66 @@ public class MediaSessionCallbackTest {
|
|||||||
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekToNextMediaItem_inProcessController_correctMediaItemTransitionsEvents()
|
||||||
|
throws Exception {
|
||||||
|
MediaItem mediaItem1 =
|
||||||
|
new MediaItem.Builder().setMediaId("id1").setUri("http://www.example.com/1").build();
|
||||||
|
MediaItem mediaItem2 =
|
||||||
|
new MediaItem.Builder().setMediaId("id2").setUri("http://www.example.com/2").build();
|
||||||
|
ExoPlayer testPlayer =
|
||||||
|
threadTestRule
|
||||||
|
.getHandler()
|
||||||
|
.postAndSync(
|
||||||
|
() -> {
|
||||||
|
ExoPlayer exoPlayer = new TestExoPlayerBuilder(context).build();
|
||||||
|
exoPlayer.setMediaItems(ImmutableList.of(mediaItem1, mediaItem2));
|
||||||
|
return exoPlayer;
|
||||||
|
});
|
||||||
|
List<String> capturedMediaItemIds = new ArrayList<>();
|
||||||
|
List<Player.Events> capturedEvents = new ArrayList<>();
|
||||||
|
List<String> eventOrder = new ArrayList<>();
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
MediaSession session =
|
||||||
|
sessionTestRule.ensureReleaseAfterTest(
|
||||||
|
new MediaSession.Builder(context, testPlayer)
|
||||||
|
.setId("seekToNextMediaItem_inProcessController_correctMediaItemTransitionsEvents")
|
||||||
|
.build());
|
||||||
|
MediaController controller =
|
||||||
|
new MediaController.Builder(ApplicationProvider.getApplicationContext(), session.getToken())
|
||||||
|
.setApplicationLooper(threadTestRule.getHandler().getLooper())
|
||||||
|
.buildAsync()
|
||||||
|
.get();
|
||||||
|
controller.addListener(
|
||||||
|
new Player.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onMediaItemTransition(@Nullable MediaItem mediaItem, int reason) {
|
||||||
|
capturedMediaItemIds.add(controller.getCurrentMediaItem().mediaId);
|
||||||
|
eventOrder.add("onMediaItemTransition");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEvents(Player player, Player.Events events) {
|
||||||
|
if (events.contains(Player.EVENT_MEDIA_ITEM_TRANSITION)) {
|
||||||
|
capturedMediaItemIds.add(controller.getCurrentMediaItem().mediaId);
|
||||||
|
capturedEvents.add(events);
|
||||||
|
eventOrder.add("onEvents");
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
threadTestRule.getHandler().postAndSync(testPlayer::seekToNextMediaItem);
|
||||||
|
|
||||||
|
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
|
||||||
|
assertThat(capturedMediaItemIds).containsExactly("id2", "id2").inOrder();
|
||||||
|
assertThat(eventOrder).containsExactly("onMediaItemTransition", "onEvents").inOrder();
|
||||||
|
assertThat(capturedEvents).hasSize(1);
|
||||||
|
assertThat(capturedEvents.get(0).size()).isEqualTo(2);
|
||||||
|
assertThat(capturedEvents.get(0).contains(Player.EVENT_MEDIA_ITEM_TRANSITION)).isTrue();
|
||||||
|
assertThat(capturedEvents.get(0).contains(Player.EVENT_POSITION_DISCONTINUITY)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
private static MediaItem updateMediaItemWithLocalConfiguration(MediaItem mediaItem) {
|
private static MediaItem updateMediaItemWithLocalConfiguration(MediaItem mediaItem) {
|
||||||
return mediaItem.buildUpon().setUri(METADATA_MEDIA_URI).build();
|
return mediaItem.buildUpon().setUri(METADATA_MEDIA_URI).build();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user