diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 05474847c5..bceeb90220 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -26,6 +26,9 @@ and how to resolve the error if possible. * Publish the code for the media3 controller test app that can be used to test interactions with apps publishing a media session. + * Propagate extras passed to media3's + `MediaSession[Builder].setSessionExtras()` to a media1 controller's + `PlaybackStateCompat.getExtras()`. * UI: * Add customisation of various icons in `PlayerControlView` through xml attributes to allow different drawables per `PlayerView` instance, diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java index 422834dd11..6d3e64bbf7 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java @@ -240,7 +240,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; playIfSuppressed, customLayout, connectionResult.availableSessionCommands, - connectionResult.availablePlayerCommands); + connectionResult.availablePlayerCommands, + sessionExtras); this.playerWrapper = playerWrapper; postOrRun( applicationHandler, @@ -265,7 +266,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; playIfSuppressed, playerWrapper.getCustomLayout(), playerWrapper.getAvailableSessionCommands(), - playerWrapper.getAvailablePlayerCommands())); + playerWrapper.getAvailablePlayerCommands(), + playerWrapper.getLegacyExtras())); } private void setPlayerInternal( diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java index 3998a39054..543371d660 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java @@ -1113,6 +1113,8 @@ import org.checkerframework.checker.initialization.qual.Initialized; @Override public void onSessionExtrasChanged(int seq, Bundle sessionExtras) { sessionCompat.setExtras(sessionExtras); + sessionImpl.getPlayerWrapper().setLegacyExtras(sessionExtras); + sessionCompat.setPlaybackState(sessionImpl.getPlayerWrapper().createPlaybackStateCompat()); } @Override diff --git a/libraries/session/src/main/java/androidx/media3/session/PlayerWrapper.java b/libraries/session/src/main/java/androidx/media3/session/PlayerWrapper.java index 1cf421b443..46dd513c24 100644 --- a/libraries/session/src/main/java/androidx/media3/session/PlayerWrapper.java +++ b/libraries/session/src/main/java/androidx/media3/session/PlayerWrapper.java @@ -15,6 +15,7 @@ */ package androidx.media3.session; +import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Util.msToUs; @@ -70,6 +71,7 @@ import java.util.List; private int legacyStatusCode; @Nullable private String legacyErrorMessage; @Nullable private Bundle legacyErrorExtras; + @Nullable private Bundle legacyExtras; private ImmutableList customLayout; private SessionCommands availableSessionCommands; private Commands availablePlayerCommands; @@ -79,12 +81,14 @@ import java.util.List; boolean playIfSuppressed, ImmutableList customLayout, SessionCommands availableSessionCommands, - Commands availablePlayerCommands) { + Commands availablePlayerCommands, + @Nullable Bundle legacyExtras) { super(player); this.playIfSuppressed = playIfSuppressed; this.customLayout = customLayout; this.availableSessionCommands = availableSessionCommands; this.availablePlayerCommands = availablePlayerCommands; + this.legacyExtras = legacyExtras; legacyStatusCode = STATUS_CODE_SUCCESS_COMPAT; } @@ -110,6 +114,19 @@ import java.util.List; return customLayout; } + public void setLegacyExtras(@Nullable Bundle extras) { + if (extras != null) { + checkArgument(!extras.containsKey(EXTRAS_KEY_PLAYBACK_SPEED_COMPAT)); + checkArgument(!extras.containsKey(EXTRAS_KEY_MEDIA_ID_COMPAT)); + } + this.legacyExtras = extras; + } + + @Nullable + public Bundle getLegacyExtras() { + return legacyExtras; + } + /** * Sets the legacy error code. * @@ -1000,6 +1017,13 @@ import java.util.List; public PlaybackStateCompat createPlaybackStateCompat() { if (legacyStatusCode != STATUS_CODE_SUCCESS_COMPAT) { + Bundle extras; + if (legacyExtras != null) { + extras = new Bundle(checkNotNull(legacyErrorExtras)); + extras.putAll(legacyExtras); + } else { + extras = checkNotNull(legacyErrorExtras); + } return new PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_ERROR, @@ -1009,7 +1033,7 @@ import java.util.List; .setActions(0) .setBufferedPosition(0) .setErrorMessage(legacyStatusCode, checkNotNull(legacyErrorMessage)) - .setExtras(checkNotNull(legacyErrorExtras)) + .setExtras(extras) .build(); } @Nullable PlaybackException playerError = getPlayerError(); @@ -1027,7 +1051,7 @@ import java.util.List; : MediaSessionCompat.QueueItem.UNKNOWN_ID; float playbackSpeed = getPlaybackParameters().speed; float sessionPlaybackSpeed = isPlaying() ? playbackSpeed : 0f; - Bundle extras = new Bundle(); + Bundle extras = legacyExtras != null ? new Bundle(legacyExtras) : new Bundle(); extras.putFloat(EXTRAS_KEY_PLAYBACK_SPEED_COMPAT, playbackSpeed); @Nullable MediaItem currentMediaItem = getCurrentMediaItemWithCommandCheck(); if (currentMediaItem != null && !MediaItem.DEFAULT_MEDIA_ID.equals(currentMediaItem.mediaId)) { diff --git a/libraries/session/src/test/java/androidx/media3/session/PlayerWrapperTest.java b/libraries/session/src/test/java/androidx/media3/session/PlayerWrapperTest.java index 82ea7a4a28..9790fdf7d7 100644 --- a/libraries/session/src/test/java/androidx/media3/session/PlayerWrapperTest.java +++ b/libraries/session/src/test/java/androidx/media3/session/PlayerWrapperTest.java @@ -49,7 +49,8 @@ public class PlayerWrapperTest { /* playIfSuppressed= */ true, ImmutableList.of(), SessionCommands.EMPTY, - Player.Commands.EMPTY); + Player.Commands.EMPTY, + /* legacyExtras= */ null); when(player.isCommandAvailable(anyInt())).thenReturn(true); when(player.getApplicationLooper()).thenReturn(Looper.myLooper()); } diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatCallbackWithMediaSessionTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatCallbackWithMediaSessionTest.java index 1ea3e83bb9..d9d5769b97 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatCallbackWithMediaSessionTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatCallbackWithMediaSessionTest.java @@ -25,6 +25,7 @@ import static androidx.media3.test.session.common.MediaSessionConstants.TEST_MED import static androidx.media3.test.session.common.MediaSessionConstants.TEST_SET_SHOW_PLAY_BUTTON_IF_SUPPRESSED_TO_FALSE; import static androidx.media3.test.session.common.TestUtils.LONG_TIMEOUT_MS; import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS; +import static androidx.test.ext.truth.os.BundleSubject.assertThat; import static com.google.common.truth.Truth.assertThat; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -999,33 +1000,106 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { } @Test - public void setSessionExtras_cnExtrasChangedCalled() throws Exception { + public void setSessionExtras_toAllControllers_extrasAndStateCallbacks() throws Exception { Bundle sessionExtras = new Bundle(); sessionExtras.putString("key-0", "value-0"); - CountDownLatch latch = new CountDownLatch(1); - List receivedSessionExtras = new ArrayList<>(); + CountDownLatch onExtrasChangedCalled = new CountDownLatch(1); + CountDownLatch onPlaybackStateChangedCalled = new CountDownLatch(1); + AtomicReference sessionExtrasFromCallback = new AtomicReference<>(); + AtomicReference sessionExtrasFromController = new AtomicReference<>(); + AtomicReference playbackStateExtrasFromCallback = new AtomicReference<>(); + AtomicReference playbackStateExtrasFromController = new AtomicReference<>(); MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() { @Override public void onExtrasChanged(Bundle extras) { - receivedSessionExtras.add(extras); - receivedSessionExtras.add(controllerCompat.getExtras()); - latch.countDown(); + sessionExtrasFromCallback.set(extras); + sessionExtrasFromController.set(controllerCompat.getExtras()); + onExtrasChangedCalled.countDown(); + } + + @Override + public void onPlaybackStateChanged(PlaybackStateCompat state) { + playbackStateExtrasFromCallback.set(state.getExtras()); + playbackStateExtrasFromController.set(controllerCompat.getPlaybackState().getExtras()); + onPlaybackStateChangedCalled.countDown(); } }; controllerCompat.registerCallback(callback, handler); session.setSessionExtras(sessionExtras); - assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(TestUtils.equals(receivedSessionExtras.get(0), sessionExtras)).isTrue(); - assertThat(TestUtils.equals(receivedSessionExtras.get(1), sessionExtras)).isTrue(); + assertThat(onExtrasChangedCalled.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); + assertThat(onPlaybackStateChangedCalled.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); + assertThat(sessionExtrasFromCallback.get()).hasSize(1); + assertThat(sessionExtrasFromCallback.get()).string("key-0").isEqualTo("value-0"); + assertThat(sessionExtrasFromController.get()).hasSize(1); + assertThat(sessionExtrasFromController.get()).string("key-0").isEqualTo("value-0"); + assertThat(playbackStateExtrasFromCallback.get()).hasSize(2); + assertThat(playbackStateExtrasFromCallback.get()) + .doubleFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT) + .isEqualTo(0.0); + assertThat(playbackStateExtrasFromCallback.get()).string("key-0").isEqualTo("value-0"); + assertThat(playbackStateExtrasFromController.get()).hasSize(2); + assertThat(playbackStateExtrasFromController.get()) + .doubleFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT) + .isEqualTo(0.0); + assertThat(playbackStateExtrasFromController.get()).string("key-0").isEqualTo("value-0"); + } + + @Test + public void setSessionExtras_toMediaNotificationControllers_extrasAndStateCallbacks() + throws Exception { + Bundle sessionExtras = new Bundle(); + sessionExtras.putString("key-0", "value-0"); + CountDownLatch onExtrasChangedCalled = new CountDownLatch(1); + CountDownLatch onPlaybackStateChangedCalled = new CountDownLatch(1); + AtomicReference sessionExtrasFromCallback = new AtomicReference<>(); + AtomicReference sessionExtrasFromController = new AtomicReference<>(); + AtomicReference playbackStateExtrasFromCallback = new AtomicReference<>(); + AtomicReference playbackStateExtrasFromController = new AtomicReference<>(); + MediaControllerCompat.Callback callback = + new MediaControllerCompat.Callback() { + @Override + public void onExtrasChanged(Bundle extras) { + sessionExtrasFromCallback.set(extras); + sessionExtrasFromController.set(controllerCompat.getExtras()); + onExtrasChangedCalled.countDown(); + } + + @Override + public void onPlaybackStateChanged(PlaybackStateCompat state) { + playbackStateExtrasFromCallback.set(state.getExtras()); + playbackStateExtrasFromController.set(controllerCompat.getPlaybackState().getExtras()); + onPlaybackStateChangedCalled.countDown(); + } + }; + controllerCompat.registerCallback(callback, handler); + + session.setSessionExtras(/* controllerKey= */ NOTIFICATION_CONTROLLER_KEY, sessionExtras); + + assertThat(onExtrasChangedCalled.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); + assertThat(onPlaybackStateChangedCalled.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); + assertThat(sessionExtrasFromCallback.get()).hasSize(1); + assertThat(sessionExtrasFromCallback.get()).string("key-0").isEqualTo("value-0"); + assertThat(sessionExtrasFromController.get()).hasSize(1); + assertThat(sessionExtrasFromController.get()).string("key-0").isEqualTo("value-0"); + assertThat(playbackStateExtrasFromCallback.get()).hasSize(2); + assertThat(playbackStateExtrasFromCallback.get()) + .doubleFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT) + .isEqualTo(0.0); + assertThat(playbackStateExtrasFromCallback.get()).string("key-0").isEqualTo("value-0"); + assertThat(playbackStateExtrasFromController.get()).hasSize(2); + assertThat(playbackStateExtrasFromController.get()) + .doubleFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT) + .isEqualTo(0.0); + assertThat(playbackStateExtrasFromController.get()).string("key-0").isEqualTo("value-0"); } @Test public void sendError_toAllControllers_onPlaybackStateChangedToErrorStateAndWithCorrectErrorData() throws Exception { - CountDownLatch latch = new CountDownLatch(2); + CountDownLatch latch = new CountDownLatch(3); List playbackStates = new ArrayList<>(); MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() { @@ -1036,9 +1110,12 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { } }; controllerCompat.registerCallback(callback, handler); + Bundle sessionExtras = new Bundle(); + sessionExtras.putString("initialKey", "initialValue"); + session.setSessionExtras(sessionExtras); PlaybackStateCompat initialPlaybackStateCompat = controllerCompat.getPlaybackState(); Bundle errorBundle = new Bundle(); - errorBundle.putInt("intKey", 99); + errorBundle.putInt("errorKey", 99); session.sendError( /* controllerKey= */ null, @@ -1047,13 +1124,19 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { errorBundle); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(playbackStates).hasSize(2); - PlaybackStateCompat errorPlaybackStateCompat = playbackStates.get(0); + assertThat(playbackStates).hasSize(3); + + // Skip the playback state from the first setSessionExtras() call. + PlaybackStateCompat errorPlaybackStateCompat = playbackStates.get(1); assertThat(errorPlaybackStateCompat.getState()).isEqualTo(PlaybackStateCompat.STATE_ERROR); assertThat(errorPlaybackStateCompat.getErrorCode()).isEqualTo(1); assertThat(errorPlaybackStateCompat.getErrorMessage().toString()) .isEqualTo(context.getString(R.string.authentication_required)); - PlaybackStateCompat resolvedPlaybackStateCompat = playbackStates.get(1); + assertThat(errorPlaybackStateCompat.getExtras()).hasSize(2); + assertThat(errorPlaybackStateCompat.getExtras()).integer("errorKey").isEqualTo(99); + assertThat(errorPlaybackStateCompat.getExtras()).string("initialKey").isEqualTo("initialValue"); + + PlaybackStateCompat resolvedPlaybackStateCompat = playbackStates.get(2); assertThat(resolvedPlaybackStateCompat.getState()) .isEqualTo(initialPlaybackStateCompat.getState()); assertThat(resolvedPlaybackStateCompat.getErrorCode()) @@ -1061,13 +1144,18 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { assertThat(resolvedPlaybackStateCompat.getErrorMessage()).isNull(); assertThat(resolvedPlaybackStateCompat.getActions()) .isEqualTo(initialPlaybackStateCompat.getActions()); + assertThat(resolvedPlaybackStateCompat.getExtras()) + .hasSize(initialPlaybackStateCompat.getExtras().size()); + assertThat(resolvedPlaybackStateCompat.getExtras()) + .string("initialKey") + .isEqualTo(initialPlaybackStateCompat.getExtras().getString("initialKey")); } @Test public void sendError_toMediaNotificationControllers_onPlaybackStateChangedToErrorStateAndWithCorrectErrorData() throws Exception { - CountDownLatch latch = new CountDownLatch(2); + CountDownLatch latch = new CountDownLatch(3); List playbackStates = new ArrayList<>(); MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() { @@ -1078,10 +1166,13 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { } }; controllerCompat.registerCallback(callback, handler); + Bundle sessionExtras = new Bundle(); + sessionExtras.putString("initialKey", "initialValue"); + session.setSessionExtras(/* controllerKey= */ NOTIFICATION_CONTROLLER_KEY, sessionExtras); PlaybackStateCompat initialPlaybackStateCompat = controllerCompat.getPlaybackState(); - Bundle errorBundle = new Bundle(); - errorBundle.putInt("intKey", 99); + Bundle errorBundle = new Bundle(); + errorBundle.putInt("errorKey", 99); session.sendError( /* controllerKey= */ NOTIFICATION_CONTROLLER_KEY, /* errorCode= */ 1, @@ -1089,15 +1180,20 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { errorBundle); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(playbackStates).hasSize(2); - PlaybackStateCompat errorPlaybackStateCompat = playbackStates.get(0); + assertThat(playbackStates).hasSize(3); + + // Skip the playback state from the first setSessionExtras() call. + PlaybackStateCompat errorPlaybackStateCompat = playbackStates.get(1); assertThat(errorPlaybackStateCompat.getState()).isEqualTo(PlaybackStateCompat.STATE_ERROR); assertThat(errorPlaybackStateCompat.getErrorCode()).isEqualTo(1); assertThat(errorPlaybackStateCompat.getErrorMessage().toString()) .isEqualTo(context.getString(R.string.authentication_required)); assertThat(errorPlaybackStateCompat.getActions()).isEqualTo(0); - assertThat(TestUtils.equals(errorPlaybackStateCompat.getExtras(), errorBundle)).isTrue(); - PlaybackStateCompat resolvedPlaybackStateCompat = playbackStates.get(1); + assertThat(errorPlaybackStateCompat.getExtras()).hasSize(2); + assertThat(errorPlaybackStateCompat.getExtras()).string("initialKey").isEqualTo("initialValue"); + assertThat(errorPlaybackStateCompat.getExtras()).integer("errorKey").isEqualTo(99); + + PlaybackStateCompat resolvedPlaybackStateCompat = playbackStates.get(2); assertThat(resolvedPlaybackStateCompat.getState()) .isEqualTo(initialPlaybackStateCompat.getState()); assertThat(resolvedPlaybackStateCompat.getErrorCode()) @@ -1105,6 +1201,11 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { assertThat(resolvedPlaybackStateCompat.getErrorMessage()).isNull(); assertThat(resolvedPlaybackStateCompat.getActions()) .isEqualTo(initialPlaybackStateCompat.getActions()); + assertThat(resolvedPlaybackStateCompat.getExtras()) + .hasSize(initialPlaybackStateCompat.getExtras().size()); + assertThat(resolvedPlaybackStateCompat.getExtras()) + .string("initialKey") + .isEqualTo(initialPlaybackStateCompat.getExtras().getString("initialKey")); } @Test