From c5e071e556931331cc780fda5177d556d51ff3da Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 10 Nov 2022 12:40:37 +0000 Subject: [PATCH] Align PlaybackStateCompat states with logic in MediaSessionConnector Creating the PlaybackStateCompat from a media3 Player state is done already by the MediaSessionConnector (and used widely). The media3 session module should set the same states under the same circumstances to ensure compatiblity and consistency. PlaybackStateCompat changes made in media3 session: - Use STATE_STOPPED when player is ended instead of STATE_PAUSED - Use STATE_PLAYING when playback is suppressed temporarily. - Set the playback speed to 0 if the player is not playing. - Add extras for mediaId and user-set playback speed. Part of the problem was that Player.isPlaying() was used to check the state. Unfortunately, MockPlayer.isPlaying() is implemented in a way that makes it hard to test these changes, because the value is set independently of playbackState, playWhenReady and suppression reason. To be able to write consistent, logical tests, this change also removes the independent setting of isPlaying in MockPlayer to align it better with a real player. This requires to update some other tests to use alternative methods. PiperOrigin-RevId: 487500859 --- .../media3/session/MediaConstants.java | 22 +- .../androidx/media3/session/MediaUtils.java | 9 +- .../media3/session/PlayerWrapper.java | 20 +- .../session/common/IRemoteMediaSession.aidl | 1 - .../test/session/common/CommonConstants.java | 1 - ...wserCompatWithMediaLibraryServiceTest.java | 7 +- ...lerCompatCallbackWithMediaSessionTest.java | 195 +++++++++++++++--- .../session/MediaControllerListenerTest.java | 26 +-- .../media3/session/MediaControllerTest.java | 19 +- .../session/MediaSessionKeyEventTest.java | 23 ++- .../session/MediaSessionProviderService.java | 12 -- .../androidx/media3/session/MockPlayer.java | 36 ++-- .../media3/session/RemoteMediaSession.java | 11 - 13 files changed, 280 insertions(+), 102 deletions(-) diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaConstants.java b/libraries/session/src/main/java/androidx/media3/session/MediaConstants.java index 2db4a53fec..0ebcd49075 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaConstants.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaConstants.java @@ -32,7 +32,7 @@ import androidx.media3.session.MediaLibraryService.MediaLibrarySession; public final class MediaConstants { /** - * The legacy error code for expired authentication. + * {@link Bundle} key used for the error code for expired authentication. * *

Use this error code to indicate an expired authentication when {@linkplain * LibraryResult#ofError(int, LibraryParams) creating a library result} for an unsuccessful @@ -43,7 +43,23 @@ public final class MediaConstants { public static final int ERROR_CODE_AUTHENTICATION_EXPIRED_COMPAT = 3; /** - * The extras key for the localized error resolution string. + * {@link Bundle} key used for the value of {@code Player.getPlaybackParameters().speed}. + * + *

Use this key in the extras bundle of the legacy {@link PlaybackStateCompat}. + */ + @UnstableApi public static final String EXTRAS_KEY_PLAYBACK_SPEED_COMPAT = "EXO_SPEED"; + + /** + * {@link Bundle} key used for the media id of the media being played. + * + *

Use this key in the extras bundle of the legacy {@link PlaybackStateCompat}. + */ + @UnstableApi + public static final String EXTRAS_KEY_MEDIA_ID_COMPAT = + androidx.media.utils.MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID; + + /** + * {@link Bundle} key used for a localized error resolution string. * *

Use this key to populate the extras bundle of the {@link LibraryParams} when {@linkplain * LibraryResult#ofError(int, LibraryParams) creating a library result} for an unsuccessful @@ -53,7 +69,7 @@ public final class MediaConstants { androidx.media.utils.MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL; /** - * The extras key for the error resolution intent. + * {@link Bundle} key used for an error resolution intent. * *

Use this key to populate the extras bundle of the {@link LibraryParams} when {@linkplain * LibraryResult#ofError(int, LibraryParams) creating a library result} for an unsuccessful diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java b/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java index f73ca3cfcb..ae17cc834f 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java @@ -728,20 +728,17 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; public static int convertToPlaybackStateCompatState( @Nullable PlaybackException playerError, @Player.State int playbackState, - boolean playWhenReady, - boolean isPlaying) { + boolean playWhenReady) { if (playerError != null) { return PlaybackStateCompat.STATE_ERROR; } - if (isPlaying) { - return PlaybackStateCompat.STATE_PLAYING; - } switch (playbackState) { case Player.STATE_IDLE: return PlaybackStateCompat.STATE_NONE; case Player.STATE_READY: + return playWhenReady ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED; case Player.STATE_ENDED: - return PlaybackStateCompat.STATE_PAUSED; + return PlaybackStateCompat.STATE_STOPPED; case Player.STATE_BUFFERING: return playWhenReady ? PlaybackStateCompat.STATE_BUFFERING 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 793fdd1136..a9eab3f1a3 100644 --- a/libraries/session/src/main/java/androidx/media3/session/PlayerWrapper.java +++ b/libraries/session/src/main/java/androidx/media3/session/PlayerWrapper.java @@ -18,6 +18,8 @@ package androidx.media3.session; import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Util.postOrRun; +import static androidx.media3.session.MediaConstants.EXTRAS_KEY_MEDIA_ID_COMPAT; +import static androidx.media3.session.MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT; import android.media.AudioManager; import android.os.Bundle; @@ -768,7 +770,7 @@ import java.util.List; @Nullable PlaybackException playerError = getPlayerError(); int state = MediaUtils.convertToPlaybackStateCompatState( - playerError, getPlaybackState(), getPlayWhenReady(), isPlaying()); + playerError, getPlaybackState(), getPlayWhenReady()); long allActions = PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_PAUSE @@ -798,16 +800,22 @@ import java.util.List; allActions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT; } long queueItemId = MediaUtils.convertToQueueItemId(getCurrentMediaItemIndex()); + float playbackSpeed = getPlaybackParameters().speed; + float sessionPlaybackSpeed = isPlaying() ? playbackSpeed : 0f; + Bundle extras = new Bundle(); + extras.putFloat(EXTRAS_KEY_PLAYBACK_SPEED_COMPAT, playbackSpeed); + @Nullable MediaItem currentMediaItem = getCurrentMediaItem(); + if (currentMediaItem != null && !MediaItem.DEFAULT_MEDIA_ID.equals(currentMediaItem.mediaId)) { + extras.putString(EXTRAS_KEY_MEDIA_ID_COMPAT, currentMediaItem.mediaId); + } PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder() .setState( - state, - getCurrentPosition(), - getPlaybackParameters().speed, - SystemClock.elapsedRealtime()) + state, getCurrentPosition(), sessionPlaybackSpeed, SystemClock.elapsedRealtime()) .setActions(allActions) .setActiveQueueItemId(queueItemId) - .setBufferedPosition(getBufferedPosition()); + .setBufferedPosition(getBufferedPosition()) + .setExtras(extras); for (int i = 0; i < customLayout.size(); i++) { CommandButton commandButton = customLayout.get(i); diff --git a/libraries/test_session_common/src/main/aidl/androidx/media3/test/session/common/IRemoteMediaSession.aidl b/libraries/test_session_common/src/main/aidl/androidx/media3/test/session/common/IRemoteMediaSession.aidl index 4c119fdf6a..5220083112 100644 --- a/libraries/test_session_common/src/main/aidl/androidx/media3/test/session/common/IRemoteMediaSession.aidl +++ b/libraries/test_session_common/src/main/aidl/androidx/media3/test/session/common/IRemoteMediaSession.aidl @@ -55,7 +55,6 @@ interface IRemoteMediaSession { void notifyPlayerError(String sessionId, in Bundle playerErrorBundle); void notifyPlayWhenReadyChanged(String sessionId, boolean playWhenReady, int reason); void notifyPlaybackStateChanged(String sessionId, int state); - void notifyIsPlayingChanged(String sessionId, boolean isPlaying); void notifyIsLoadingChanged(String sessionId, boolean isLoading); void notifyPositionDiscontinuity(String sessionId, in Bundle oldPositionBundle, in Bundle newPositionBundle, int reason); diff --git a/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/CommonConstants.java b/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/CommonConstants.java index 64a92bd2a8..b7c70c4825 100644 --- a/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/CommonConstants.java +++ b/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/CommonConstants.java @@ -90,7 +90,6 @@ public class CommonConstants { public static final String KEY_PLAY_WHEN_READY = "playWhenReady"; public static final String KEY_PLAYBACK_SUPPRESSION_REASON = "playbackSuppressionReason"; public static final String KEY_PLAYBACK_STATE = "playbackState"; - public static final String KEY_IS_PLAYING = "isPlaying"; public static final String KEY_IS_LOADING = "isLoading"; public static final String KEY_REPEAT_MODE = "repeatMode"; public static final String KEY_SHUFFLE_MODE_ENABLED = "shuffleModeEnabled"; diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserCompatWithMediaLibraryServiceTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserCompatWithMediaLibraryServiceTest.java index d365a80edf..e9ec255abc 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserCompatWithMediaLibraryServiceTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaBrowserCompatWithMediaLibraryServiceTest.java @@ -338,7 +338,6 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest } }); assertThat(errorLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(lastReportedPlaybackStateCompat).isNotNull(); assertThat(lastReportedPlaybackStateCompat.getState()) .isEqualTo(PlaybackStateCompat.STATE_ERROR); assertThat( @@ -361,7 +360,11 @@ public class MediaBrowserCompatWithMediaLibraryServiceTest // Any successful calls remove the error state, assertThat(lastReportedPlaybackStateCompat.getState()) .isNotEqualTo(PlaybackStateCompat.STATE_ERROR); - assertThat(lastReportedPlaybackStateCompat.getExtras()).isNull(); + assertThat( + lastReportedPlaybackStateCompat + .getExtras() + .getString(MediaConstants.EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT)) + .isNull(); } @Test 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 ac53cdf47f..f44d842839 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 @@ -123,6 +123,7 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder() .setPlaybackState(testState) + .setPlayWhenReady(true) .setBufferedPosition(testBufferingPosition) .setPlaybackParameters(new PlaybackParameters(testSpeed)) .setTimeline(testTimeline) @@ -153,7 +154,7 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { .isEqualTo(testState); assertThat(controller.getPlaybackState().getBufferedPosition()) .isEqualTo(testBufferingPosition); - assertThat(controller.getPlaybackState().getPlaybackSpeed()).isWithin(EPSILON).of(testSpeed); + assertThat(controller.getPlaybackState().getPlaybackSpeed()).isEqualTo(testSpeed); assertThat(controller.getMetadata().getString(METADATA_KEY_MEDIA_ID)) .isEqualTo(testMediaItems.get(testItemIndex).mediaId); @@ -380,9 +381,7 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { assertThat(latchForPlaybackState.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(playbackStateRef.get().getBufferedPosition()).isEqualTo(testBufferedPositionMs); assertThat(playbackStateRef.get().getPosition()).isEqualTo(testCurrentPositionMs); - assertThat(playbackStateRef.get().getPlaybackSpeed()) - .isWithin(EPSILON) - .of(playbackParameters.speed); + assertThat(playbackStateRef.get().getPlaybackSpeed()).isEqualTo(playbackParameters.speed); assertThat(latchForMetadata.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(metadataRef.get().getString(METADATA_KEY_MEDIA_ID)) @@ -604,7 +603,10 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { @Test public void onPlaybackParametersChanged_notifiesPlaybackStateCompatChanges() throws Exception { PlaybackParameters playbackParameters = new PlaybackParameters(/* speed= */ 1.5f); - + session.getMockPlayer().setPlaybackState(Player.STATE_READY); + session + .getMockPlayer() + .setPlayWhenReady(/* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE); AtomicReference playbackStateRef = new AtomicReference<>(); CountDownLatch latch = new CountDownLatch(1); MediaControllerCompat.Callback callback = @@ -619,12 +621,21 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { session.getMockPlayer().notifyPlaybackParametersChanged(playbackParameters); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(playbackStateRef.get().getPlaybackSpeed()) - .isWithin(EPSILON) - .of(playbackParameters.speed); + assertThat(playbackStateRef.get().getPlaybackSpeed()).isEqualTo(playbackParameters.speed); + assertThat( + playbackStateRef + .get() + .getExtras() + .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT)) + .isEqualTo(playbackParameters.speed); assertThat(controllerCompat.getPlaybackState().getPlaybackSpeed()) - .isWithin(EPSILON) - .of(playbackParameters.speed); + .isEqualTo(playbackParameters.speed); + assertThat( + controllerCompat + .getPlaybackState() + .getExtras() + .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT)) + .isEqualTo(playbackParameters.speed); } @Test @@ -632,11 +643,8 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { throws Exception { session .getMockPlayer() - .setPlayWhenReady( - /* playWhenReady= */ true, - Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS); + .setPlayWhenReady(/* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE); session.getMockPlayer().notifyPlaybackStateChanged(STATE_READY); - session.getMockPlayer().notifyIsPlayingChanged(false); AtomicReference playbackStateCompatRef = new AtomicReference<>(); CountDownLatch latch = new CountDownLatch(1); @@ -657,6 +665,28 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(playbackStateCompatRef.get().getState()).isEqualTo(PlaybackStateCompat.STATE_PAUSED); + assertThat(playbackStateCompatRef.get().getPlaybackSpeed()).isEqualTo(0f); + assertThat( + playbackStateCompatRef + .get() + .getExtras() + .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT)) + .isEqualTo(1f); + assertThat( + playbackStateCompatRef + .get() + .getExtras() + .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT)) + .isEqualTo(1f); + assertThat(controllerCompat.getPlaybackState().getState()) + .isEqualTo(PlaybackStateCompat.STATE_PAUSED); + assertThat(controllerCompat.getPlaybackState().getPlaybackSpeed()).isEqualTo(0f); + assertThat( + controllerCompat + .getPlaybackState() + .getExtras() + .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT)) + .isEqualTo(1f); } @Test @@ -666,7 +696,6 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { .getMockPlayer() .setPlayWhenReady(/* playWhenReady= */ false, Player.PLAYBACK_SUPPRESSION_REASON_NONE); session.getMockPlayer().notifyPlaybackStateChanged(Player.STATE_BUFFERING); - session.getMockPlayer().notifyIsPlayingChanged(false); AtomicReference playbackStateCompatRef = new AtomicReference<>(); CountDownLatch latch = new CountDownLatch(1); @@ -687,17 +716,36 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { assertThat(playbackStateCompatRef.get().getState()) .isEqualTo(PlaybackStateCompat.STATE_BUFFERING); + assertThat(playbackStateCompatRef.get().getPlaybackSpeed()).isEqualTo(0f); + assertThat( + playbackStateCompatRef + .get() + .getExtras() + .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT)) + .isEqualTo(1f); + assertThat( + playbackStateCompatRef + .get() + .getExtras() + .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT)) + .isEqualTo(1f); + assertThat(controllerCompat.getPlaybackState().getState()) + .isEqualTo(PlaybackStateCompat.STATE_BUFFERING); + assertThat(controllerCompat.getPlaybackState().getPlaybackSpeed()).isEqualTo(0f); + assertThat( + controllerCompat + .getPlaybackState() + .getExtras() + .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT)) + .isEqualTo(1f); } @Test - public void playbackStateChange_playbackStateBecomesEnded_notifiesPaused() throws Exception { + public void playbackStateChange_playbackStateBecomesEnded_notifiesStopped() throws Exception { session .getMockPlayer() - .setPlayWhenReady( - /* playWhenReady= */ true, - Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS); + .setPlayWhenReady(/* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE); session.getMockPlayer().notifyPlaybackStateChanged(STATE_READY); - session.getMockPlayer().notifyIsPlayingChanged(false); AtomicReference playbackStateCompatRef = new AtomicReference<>(); CountDownLatch latch = new CountDownLatch(1); @@ -714,12 +762,39 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { session.getMockPlayer().notifyPlaybackStateChanged(STATE_ENDED); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(playbackStateCompatRef.get().getState()).isEqualTo(PlaybackStateCompat.STATE_PAUSED); + assertThat(playbackStateCompatRef.get().getState()) + .isEqualTo(PlaybackStateCompat.STATE_STOPPED); + assertThat(playbackStateCompatRef.get().getPlaybackSpeed()).isEqualTo(0f); + assertThat( + playbackStateCompatRef + .get() + .getExtras() + .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT)) + .isEqualTo(1f); + assertThat( + playbackStateCompatRef + .get() + .getExtras() + .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT)) + .isEqualTo(1f); + assertThat(controllerCompat.getPlaybackState().getState()) + .isEqualTo(PlaybackStateCompat.STATE_STOPPED); + assertThat(controllerCompat.getPlaybackState().getPlaybackSpeed()).isEqualTo(0f); + assertThat( + controllerCompat + .getPlaybackState() + .getExtras() + .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT)) + .isEqualTo(1f); } @Test - public void playbackStateChange_isPlayingBecomesTrue_notifiesPlaying() throws Exception { - session.getMockPlayer().notifyIsPlayingChanged(false); + public void playbackStateChange_withPlaybackSuppression_notifiesPlayingWithSpeedZero() + throws Exception { + session.getMockPlayer().setPlaybackState(Player.STATE_READY); + session + .getMockPlayer() + .setPlayWhenReady(/* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE); AtomicReference playbackStateCompatRef = new AtomicReference<>(); CountDownLatch latch = new CountDownLatch(1); @@ -733,11 +808,83 @@ public class MediaControllerCompatCallbackWithMediaSessionTest { }; controllerCompat.registerCallback(callback, handler); - session.getMockPlayer().notifyIsPlayingChanged(true); + session + .getMockPlayer() + .notifyPlayWhenReadyChanged( + /* playWhenReady= */ true, + Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(playbackStateCompatRef.get().getState()) .isEqualTo(PlaybackStateCompat.STATE_PLAYING); + assertThat(playbackStateCompatRef.get().getPlaybackSpeed()).isEqualTo(0f); + assertThat( + playbackStateCompatRef + .get() + .getExtras() + .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT)) + .isEqualTo(1f); + assertThat( + playbackStateCompatRef + .get() + .getExtras() + .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT)) + .isEqualTo(1f); + assertThat(controllerCompat.getPlaybackState().getState()) + .isEqualTo(PlaybackStateCompat.STATE_PLAYING); + assertThat(controllerCompat.getPlaybackState().getPlaybackSpeed()).isEqualTo(0f); + assertThat( + controllerCompat + .getPlaybackState() + .getExtras() + .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT)) + .isEqualTo(1f); + } + + @Test + public void playbackStateChange_playWhenReadyBecomesTrueWhenReady_notifiesPlaying() + throws Exception { + session.getMockPlayer().setPlaybackState(Player.STATE_READY); + session + .getMockPlayer() + .setPlayWhenReady(/* playWhenReady= */ false, Player.PLAYBACK_SUPPRESSION_REASON_NONE); + + AtomicReference playbackStateCompatRef = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + MediaControllerCompat.Callback callback = + new MediaControllerCompat.Callback() { + @Override + public void onPlaybackStateChanged(PlaybackStateCompat playbackStateCompat) { + playbackStateCompatRef.set(playbackStateCompat); + latch.countDown(); + } + }; + controllerCompat.registerCallback(callback, handler); + + session + .getMockPlayer() + .notifyPlayWhenReadyChanged( + /* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE); + assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); + + assertThat(playbackStateCompatRef.get().getState()) + .isEqualTo(PlaybackStateCompat.STATE_PLAYING); + assertThat(playbackStateCompatRef.get().getPlaybackSpeed()).isEqualTo(1f); + assertThat( + playbackStateCompatRef + .get() + .getExtras() + .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT)) + .isEqualTo(1f); + assertThat(controllerCompat.getPlaybackState().getState()) + .isEqualTo(PlaybackStateCompat.STATE_PLAYING); + assertThat(controllerCompat.getPlaybackState().getPlaybackSpeed()).isEqualTo(1f); + assertThat( + controllerCompat + .getPlaybackState() + .getExtras() + .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT)) + .isEqualTo(1f); } @Test diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerTest.java index 36a598f988..3a69aeb043 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerTest.java @@ -1818,8 +1818,9 @@ public class MediaControllerListenerTest { @Test public void onIsPlayingChanged_isNotified() throws Exception { - boolean testIsPlaying = true; - remoteSession.getMockPlayer().notifyIsPlayingChanged(false); + remoteSession + .getMockPlayer() + .setPlayWhenReady(/* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE); MediaController controller = controllerTestRule.createController(remoteSession.getToken()); CountDownLatch latch = new CountDownLatch(2); AtomicBoolean isPlayingGetterRef = new AtomicBoolean(); @@ -1844,18 +1845,17 @@ public class MediaControllerListenerTest { }; threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener)); - remoteSession.getMockPlayer().notifyIsPlayingChanged(testIsPlaying); + remoteSession.getMockPlayer().notifyPlaybackStateChanged(Player.STATE_READY); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(isPlayingParamRef.get()).isEqualTo(testIsPlaying); - assertThat(isPlayingGetterRef.get()).isEqualTo(testIsPlaying); - assertThat(isPlayingOnEventsRef.get()).isEqualTo(testIsPlaying); - assertThat(getEventsAsList(eventsRef.get())).containsExactly(Player.EVENT_IS_PLAYING_CHANGED); + assertThat(isPlayingParamRef.get()).isTrue(); + assertThat(isPlayingGetterRef.get()).isTrue(); + assertThat(isPlayingOnEventsRef.get()).isTrue(); + assertThat(getEventsAsList(eventsRef.get())).contains(Player.EVENT_IS_PLAYING_CHANGED); } @Test public void onIsPlayingChanged_updatesGetters() throws Exception { - boolean testIsPlaying = true; long testCurrentPositionMs = 11; long testContentPositionMs = testCurrentPositionMs; // Not playing an ad long testBufferedPositionMs = 100; @@ -1863,7 +1863,9 @@ public class MediaControllerListenerTest { long testTotalBufferedDurationMs = 120; long testCurrentLiveOffsetMs = 10; long testContentBufferedPositionMs = 240; - remoteSession.getMockPlayer().notifyIsPlayingChanged(false); + remoteSession + .getMockPlayer() + .setPlayWhenReady(/* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE); MediaController controller = controllerTestRule.createController(remoteSession.getToken()); threadTestRule.getHandler().postAndSync(() -> controller.setTimeDiffMs(/* timeDiff= */ 0L)); CountDownLatch latch = new CountDownLatch(2); @@ -1906,10 +1908,10 @@ public class MediaControllerListenerTest { remoteSession.getMockPlayer().setTotalBufferedDuration(testTotalBufferedDurationMs); remoteSession.getMockPlayer().setCurrentLiveOffset(testCurrentLiveOffsetMs); remoteSession.getMockPlayer().setContentBufferedPosition(testContentBufferedPositionMs); - remoteSession.getMockPlayer().notifyIsPlayingChanged(testIsPlaying); + remoteSession.getMockPlayer().notifyPlaybackStateChanged(Player.STATE_READY); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(isPlayingRef.get()).isEqualTo(testIsPlaying); + assertThat(isPlayingRef.get()).isEqualTo(true); assertThat(currentPositionMsRef.get()).isEqualTo(testCurrentPositionMs); assertThat(contentPositionMsRef.get()).isEqualTo(testContentPositionMs); assertThat(bufferedPositionMsRef.get()).isEqualTo(testBufferedPositionMs); @@ -1917,7 +1919,7 @@ public class MediaControllerListenerTest { assertThat(totalBufferedDurationMsRef.get()).isEqualTo(testTotalBufferedDurationMs); assertThat(currentLiveOffsetMsRef.get()).isEqualTo(testCurrentLiveOffsetMs); assertThat(contentBufferedPositionMsRef.get()).isEqualTo(testContentBufferedPositionMs); - assertThat(getEventsAsList(eventsRef.get())).containsExactly(Player.EVENT_IS_PLAYING_CHANGED); + assertThat(getEventsAsList(eventsRef.get())).contains(Player.EVENT_IS_PLAYING_CHANGED); } @Test diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerTest.java index 046eede07a..3c13e756ce 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerTest.java @@ -279,7 +279,6 @@ public class MediaControllerTest { @Player.PlaybackSuppressionReason int playbackSuppressionReason = Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS; @Player.State int playbackState = Player.STATE_READY; - boolean isPlaying = true; boolean isLoading = true; boolean isShuffleModeEnabled = true; @RepeatMode int repeatMode = Player.REPEAT_MODE_ONE; @@ -327,7 +326,6 @@ public class MediaControllerTest { .setPlayWhenReady(playWhenReady) .setPlaybackSuppressionReason(playbackSuppressionReason) .setPlaybackState(playbackState) - .setIsPlaying(isPlaying) .setIsLoading(isLoading) .setShuffleModeEnabled(isShuffleModeEnabled) .setRepeatMode(repeatMode) @@ -360,7 +358,6 @@ public class MediaControllerTest { AtomicBoolean playWhenReadyRef = new AtomicBoolean(); AtomicInteger playbackSuppressionReasonRef = new AtomicInteger(); AtomicInteger playbackStateRef = new AtomicInteger(); - AtomicBoolean isPlayingRef = new AtomicBoolean(); AtomicBoolean isLoadingRef = new AtomicBoolean(); AtomicBoolean isShuffleModeEnabledRef = new AtomicBoolean(); AtomicInteger repeatModeRef = new AtomicInteger(); @@ -393,7 +390,6 @@ public class MediaControllerTest { playWhenReadyRef.set(controller.getPlayWhenReady()); playbackSuppressionReasonRef.set(controller.getPlaybackSuppressionReason()); playbackStateRef.set(controller.getPlaybackState()); - isPlayingRef.set(controller.isPlaying()); isLoadingRef.set(controller.isLoading()); isShuffleModeEnabledRef.set(controller.getShuffleModeEnabled()); repeatModeRef.set(controller.getRepeatMode()); @@ -423,7 +419,6 @@ public class MediaControllerTest { assertThat(playWhenReadyRef.get()).isEqualTo(playWhenReady); assertThat(playbackSuppressionReasonRef.get()).isEqualTo(playbackSuppressionReason); assertThat(playbackStateRef.get()).isEqualTo(playbackState); - assertThat(isPlayingRef.get()).isEqualTo(isPlaying); assertThat(isLoadingRef.get()).isEqualTo(isLoading); assertThat(isShuffleModeEnabledRef.get()).isEqualTo(isShuffleModeEnabled); assertThat(repeatModeRef.get()).isEqualTo(repeatMode); @@ -842,7 +837,7 @@ public class MediaControllerTest { long testCurrentPositionMs = 100L; Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder() - .setIsPlaying(false) + .setPlaybackState(Player.STATE_BUFFERING) .setCurrentPosition(testCurrentPositionMs) .setDuration(10_000L) .build(); @@ -868,7 +863,8 @@ public class MediaControllerTest { long testTimeDiff = 50L; Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder() - .setIsPlaying(true) + .setPlaybackState(Player.STATE_READY) + .setPlayWhenReady(true) .setCurrentPosition(testCurrentPosition) .setDuration(10_000L) .setPlaybackParameters(testPlaybackParameters) @@ -897,7 +893,8 @@ public class MediaControllerTest { new RemoteMediaSession.MockPlayerConfigBuilder() .setContentPosition(testContentPosition) .setDuration(10_000L) - .setIsPlaying(true) + .setPlaybackState(Player.STATE_READY) + .setPlayWhenReady(true) .setIsPlayingAd(true) .setCurrentAdGroupIndex(0) .setCurrentAdIndexInAdGroup(0) @@ -925,7 +922,8 @@ public class MediaControllerTest { .setCurrentPosition(100L) .setContentPosition(100L) // Same as current position b/c not playing an ad .setDuration(10_000L) - .setIsPlaying(true) + .setPlayWhenReady(true) + .setPlaybackState(Player.STATE_READY) .setIsPlayingAd(false) .setPlaybackParameters(new PlaybackParameters(/* speed= */ 2.0f)) .build(); @@ -956,7 +954,8 @@ public class MediaControllerTest { .setCurrentPosition(10L) .setContentPosition(50L) .setDuration(10_000L) - .setIsPlaying(true) + .setPlayWhenReady(true) + .setPlaybackState(Player.STATE_READY) .setIsPlayingAd(true) .setCurrentAdGroupIndex(0) .setCurrentAdIndexInAdGroup(0) diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionKeyEventTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionKeyEventTest.java index 1ef9e15947..a6a1f2327f 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionKeyEventTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionKeyEventTest.java @@ -21,6 +21,7 @@ import static androidx.media3.test.session.common.TestUtils.LONG_TIMEOUT_MS; import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS; import static com.google.common.truth.Truth.assertThat; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.Assume.assumeTrue; import android.content.Context; import android.media.AudioManager; @@ -98,8 +99,14 @@ public class MediaSessionKeyEventTest { // Here's the requirement for an app to receive media key events via MediaSession. // - SDK < 26: Player should be playing for receiving key events // - SDK >= 26: Play a media item in the same process of the session for receiving key events. - handler.postAndSync(() -> player.notifyIsPlayingChanged(/* isPlaying= */ true)); - if (Util.SDK_INT >= 26) { + if (Util.SDK_INT < 26) { + handler.postAndSync( + () -> { + player.notifyPlayWhenReadyChanged( + /* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE); + player.notifyPlaybackStateChanged(Player.STATE_READY); + }); + } else { CountDownLatch latch = new CountDownLatch(1); handler.postAndSync( () -> { @@ -168,6 +175,10 @@ public class MediaSessionKeyEventTest { @Test public void playPauseKeyEvent_paused_play() throws Exception { + // We don't receive media key events when we are not playing on API < 26, so we can't test this + // case as it's not supported. + assumeTrue(Util.SDK_INT >= 26); + handler.postAndSync( () -> { player.playbackState = Player.STATE_READY; @@ -180,6 +191,10 @@ public class MediaSessionKeyEventTest { @Test public void playPauseKeyEvent_fromIdle_prepareAndPlay() throws Exception { + // We don't receive media key events when we are not playing on API < 26, so we can't test this + // case as it's not supported. + assumeTrue(Util.SDK_INT >= 26); + handler.postAndSync( () -> { player.playbackState = Player.STATE_IDLE; @@ -193,6 +208,10 @@ public class MediaSessionKeyEventTest { @Test public void playPauseKeyEvent_playWhenReadyAndEnded_seekAndPlay() throws Exception { + // We don't receive media key events when we are not playing on API < 26, so we can't test this + // case as it's not supported. + assumeTrue(Util.SDK_INT >= 26); + handler.postAndSync( () -> { player.playWhenReady = true; diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java index fa312abd31..ce0c68eb09 100644 --- a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java +++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java @@ -36,7 +36,6 @@ import static androidx.media3.test.session.common.CommonConstants.KEY_DEVICE_MUT import static androidx.media3.test.session.common.CommonConstants.KEY_DEVICE_VOLUME; import static androidx.media3.test.session.common.CommonConstants.KEY_DURATION; import static androidx.media3.test.session.common.CommonConstants.KEY_IS_LOADING; -import static androidx.media3.test.session.common.CommonConstants.KEY_IS_PLAYING; import static androidx.media3.test.session.common.CommonConstants.KEY_IS_PLAYING_AD; import static androidx.media3.test.session.common.CommonConstants.KEY_MAX_SEEK_TO_PREVIOUS_POSITION_MS; import static androidx.media3.test.session.common.CommonConstants.KEY_MEDIA_METADATA; @@ -374,7 +373,6 @@ public class MediaSessionProviderService extends Service { player.playbackSuppressionReason = config.getInt(KEY_PLAYBACK_SUPPRESSION_REASON, player.playbackSuppressionReason); player.playbackState = config.getInt(KEY_PLAYBACK_STATE, player.playbackState); - player.isPlaying = config.getBoolean(KEY_IS_PLAYING, player.isPlaying); player.isLoading = config.getBoolean(KEY_IS_LOADING, player.isLoading); player.repeatMode = config.getInt(KEY_REPEAT_MODE, player.repeatMode); player.shuffleModeEnabled = @@ -695,16 +693,6 @@ public class MediaSessionProviderService extends Service { }); } - @Override - public void notifyIsPlayingChanged(String sessionId, boolean isPlaying) throws RemoteException { - runOnHandler( - () -> { - MediaSession session = sessionMap.get(sessionId); - MockPlayer player = (MockPlayer) session.getPlayer(); - player.notifyIsPlayingChanged(isPlaying); - }); - } - @Override public void notifyIsLoadingChanged(String sessionId, boolean isLoading) throws RemoteException { runOnHandler( diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MockPlayer.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MockPlayer.java index 282c88ef3f..2f1460035e 100644 --- a/libraries/test_session_current/src/main/java/androidx/media3/session/MockPlayer.java +++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MockPlayer.java @@ -245,7 +245,6 @@ public class MockPlayer implements Player { public boolean playWhenReady; public @PlaybackSuppressionReason int playbackSuppressionReason; public @State int playbackState; - public boolean isPlaying; public boolean isLoading; public MediaMetadata mediaMetadata; public Commands commands; @@ -521,6 +520,12 @@ public class MockPlayer implements Player { } } + /** + * Changes the values returned from {@link #getPlayWhenReady()} and {@link + * #getPlaybackSuppressionReason()}, and triggers {@link Player.Listener#onPlayWhenReadyChanged}, + * {@link Player.Listener#onPlaybackSuppressionReasonChanged} or {@link + * Player.Listener#onIsPlayingChanged} as appropriate. + */ public void notifyPlayWhenReadyChanged( boolean playWhenReady, @PlayWhenReadyChangeReason int reason) { boolean playWhenReadyChanged = (this.playWhenReady != playWhenReady); @@ -529,8 +534,10 @@ public class MockPlayer implements Player { return; } + boolean wasPlaying = isPlaying(); this.playWhenReady = playWhenReady; this.playbackSuppressionReason = reason; + boolean isPlaying = isPlaying(); for (Listener listener : listeners) { if (playWhenReadyChanged) { listener.onPlayWhenReadyChanged( @@ -539,26 +546,29 @@ public class MockPlayer implements Player { if (playbackSuppressionReasonChanged) { listener.onPlaybackSuppressionReasonChanged(reason); } + if (isPlaying != wasPlaying) { + listener.onIsPlayingChanged(isPlaying); + } } } + /** + * Changes the value returned from {@link #getPlaybackState()} and triggers {@link + * Player.Listener#onPlaybackStateChanged} and/or {@link Player.Listener#onIsPlayingChanged} as + * appropriate. + */ public void notifyPlaybackStateChanged(@State int playbackState) { if (this.playbackState == playbackState) { return; } + boolean wasPlaying = isPlaying(); this.playbackState = playbackState; + boolean isPlaying = isPlaying(); for (Listener listener : listeners) { listener.onPlaybackStateChanged(playbackState); - } - } - - public void notifyIsPlayingChanged(boolean isPlaying) { - if (this.isPlaying == isPlaying) { - return; - } - this.isPlaying = isPlaying; - for (Listener listener : listeners) { - listener.onIsPlayingChanged(isPlaying); + if (isPlaying != wasPlaying) { + listener.onIsPlayingChanged(isPlaying); + } } } @@ -698,7 +708,9 @@ public class MockPlayer implements Player { @Override public boolean isPlaying() { - return isPlaying; + return playWhenReady + && playbackState == Player.STATE_READY + && playbackSuppressionReason == Player.PLAYBACK_SUPPRESSION_REASON_NONE; } @Override diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/RemoteMediaSession.java b/libraries/test_session_current/src/main/java/androidx/media3/session/RemoteMediaSession.java index e6647a6981..7d5cadfa8d 100644 --- a/libraries/test_session_current/src/main/java/androidx/media3/session/RemoteMediaSession.java +++ b/libraries/test_session_current/src/main/java/androidx/media3/session/RemoteMediaSession.java @@ -35,7 +35,6 @@ import static androidx.media3.test.session.common.CommonConstants.KEY_DEVICE_MUT import static androidx.media3.test.session.common.CommonConstants.KEY_DEVICE_VOLUME; import static androidx.media3.test.session.common.CommonConstants.KEY_DURATION; import static androidx.media3.test.session.common.CommonConstants.KEY_IS_LOADING; -import static androidx.media3.test.session.common.CommonConstants.KEY_IS_PLAYING; import static androidx.media3.test.session.common.CommonConstants.KEY_IS_PLAYING_AD; import static androidx.media3.test.session.common.CommonConstants.KEY_MAX_SEEK_TO_PREVIOUS_POSITION_MS; import static androidx.media3.test.session.common.CommonConstants.KEY_MEDIA_METADATA; @@ -296,10 +295,6 @@ public class RemoteMediaSession { binder.notifyPlaybackStateChanged(sessionId, state); } - public void notifyIsPlayingChanged(boolean isPlaying) throws RemoteException { - binder.notifyIsPlayingChanged(sessionId, isPlaying); - } - public void notifyIsLoadingChanged(boolean isLoading) throws RemoteException { binder.notifyIsLoadingChanged(sessionId, isLoading); } @@ -687,12 +682,6 @@ public class RemoteMediaSession { return this; } - @CanIgnoreReturnValue - public MockPlayerConfigBuilder setIsPlaying(boolean isPlaying) { - bundle.putBoolean(KEY_IS_PLAYING, isPlaying); - return this; - } - @CanIgnoreReturnValue public MockPlayerConfigBuilder setIsLoading(boolean isLoading) { bundle.putBoolean(KEY_IS_LOADING, isLoading);