diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java index e14a695b11..b56af18ee9 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplBase.java @@ -393,10 +393,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; dispatchRemoteSessionTaskWithPlayerCommand( (iSession, seq) -> iSession.play(controllerStub, seq)); - setPlayWhenReady( - /* playWhenReady= */ true, - Player.PLAYBACK_SUPPRESSION_REASON_NONE, - Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST); + setPlayWhenReady(/* playWhenReady= */ true, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST); } @Override @@ -408,10 +405,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; dispatchRemoteSessionTaskWithPlayerCommand( (iSession, seq) -> iSession.pause(controllerStub, seq)); - setPlayWhenReady( - /* playWhenReady= */ false, - Player.PLAYBACK_SUPPRESSION_REASON_NONE, - Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST); + setPlayWhenReady(/* playWhenReady= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST); } @Override @@ -533,10 +527,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; dispatchRemoteSessionTaskWithPlayerCommand( (iSession, seq) -> iSession.setPlayWhenReady(controllerStub, seq, playWhenReady)); - setPlayWhenReady( - playWhenReady, - Player.PLAYBACK_SUPPRESSION_REASON_NONE, - Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST); + setPlayWhenReady(playWhenReady, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST); } @Override @@ -2154,11 +2145,15 @@ import org.checkerframework.checker.nullness.qual.NonNull; } private void setPlayWhenReady( - boolean playWhenReady, - @Player.PlaybackSuppressionReason int playbackSuppressionReason, - @Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason) { + boolean playWhenReady, @Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason) { + // Transient audio focus loss will be resolved by requesting focus again, so eagerly remove it + // here in the masked value. + @Player.PlaybackSuppressionReason int maskedSuppressionReason = getPlaybackSuppressionReason(); + if (maskedSuppressionReason == Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS) { + maskedSuppressionReason = Player.PLAYBACK_SUPPRESSION_REASON_NONE; + } if (playerInfo.playWhenReady == playWhenReady - && playerInfo.playbackSuppressionReason == playbackSuppressionReason) { + && playerInfo.playbackSuppressionReason == maskedSuppressionReason) { return; } @@ -2167,7 +2162,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; lastSetPlayWhenReadyCalledTimeMs = SystemClock.elapsedRealtime(); PlayerInfo newPlayerInfo = this.playerInfo.copyWithPlayWhenReady( - playWhenReady, playWhenReadyChangeReason, playbackSuppressionReason); + playWhenReady, playWhenReadyChangeReason, maskedSuppressionReason); updatePlayerInfo( newPlayerInfo, /* timelineChangeReason= */ null, diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerStateMaskingTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerStateMaskingTest.java index a221f256b5..2f38b614e5 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerStateMaskingTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerStateMaskingTest.java @@ -170,6 +170,103 @@ public class MediaControllerStateMaskingTest { assertThat(isPlayingFromGetterRef.get()).isEqualTo(testIsPlaying); } + @Test + public void setPlayWhenReady_forTrueWhenPlaybackSuppressed_shouldNotChangePlaybackSuppression() + throws Exception { + CountDownLatch eventCallsCountDownLatch = new CountDownLatch(1); + AtomicReference<@Player.PlaybackSuppressionReason Integer> playbackSuppressionReasonChangedRef = + new AtomicReference<>(); + AtomicReference eventsRef = new AtomicReference<>(); + MediaController controller = + getMediaControllerToTestPlaybackSuppression( + /* initialPlayWhenReadyState= */ false, + playbackSuppressionReasonChangedRef, + eventsRef, + eventCallsCountDownLatch); + + threadTestRule.getHandler().postAndSync(() -> controller.setPlayWhenReady(true)); + + assertNoPlaybackSuppressionReasonChange( + playbackSuppressionReasonChangedRef, eventsRef, eventCallsCountDownLatch); + } + + @Test + public void setPlayWhenReady_withFalseWhenPlaybackSuppressed_shouldNotChangePlaybackSuppression() + throws Exception { + CountDownLatch eventCallsCountDownLatch = new CountDownLatch(1); + AtomicReference<@Player.PlaybackSuppressionReason Integer> playbackSuppressionReasonChangedRef = + new AtomicReference<>(); + AtomicReference eventsRef = new AtomicReference<>(); + MediaController controller = + getMediaControllerToTestPlaybackSuppression( + /* initialPlayWhenReadyState= */ true, + playbackSuppressionReasonChangedRef, + eventsRef, + eventCallsCountDownLatch); + + threadTestRule.getHandler().postAndSync(() -> controller.setPlayWhenReady(false)); + + assertNoPlaybackSuppressionReasonChange( + playbackSuppressionReasonChangedRef, eventsRef, eventCallsCountDownLatch); + } + + private MediaController getMediaControllerToTestPlaybackSuppression( + boolean initialPlayWhenReadyState, + AtomicReference<@Player.PlaybackSuppressionReason Integer> + playbackSuppressionReasonChangedRef, + AtomicReference eventsRef, + CountDownLatch countDownLatchForOnEventCalls) + throws Exception { + Bundle playerConfig = + new RemoteMediaSession.MockPlayerConfigBuilder() + .setPlaybackState(Player.STATE_READY) + .setPlayWhenReady(initialPlayWhenReadyState) + .setPlaybackSuppressionReason( + Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT) + .build(); + remoteSession.setPlayer(playerConfig); + MediaController controller = controllerTestRule.createController(remoteSession.getToken()); + threadTestRule + .getHandler() + .postAndSync( + () -> + controller.addListener( + getPlayerListenerToCapturePlaybackSuppression( + playbackSuppressionReasonChangedRef, + eventsRef, + countDownLatchForOnEventCalls))); + return controller; + } + + private Player.Listener getPlayerListenerToCapturePlaybackSuppression( + AtomicReference<@Player.PlaybackSuppressionReason Integer> + playbackSuppressionReasonChangedRef, + AtomicReference eventsRef, + CountDownLatch countDownLatchForOnEventCalls) { + return new Player.Listener() { + @Override + public void onEvents(Player player, Player.Events events) { + eventsRef.set(events); + if (events.contains(Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED)) { + playbackSuppressionReasonChangedRef.set(player.getPlaybackSuppressionReason()); + } + countDownLatchForOnEventCalls.countDown(); + } + }; + } + + private void assertNoPlaybackSuppressionReasonChange( + AtomicReference<@Player.PlaybackSuppressionReason Integer> + playbackSuppressionReasonChangedRef, + AtomicReference eventsRef, + CountDownLatch countDownLatchForOnEventCalls) + throws Exception { + assertThat(countDownLatchForOnEventCalls.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); + assertThat(playbackSuppressionReasonChangedRef.get()).isNull(); + assertThat(eventsRef.get().contains(Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED)) + .isFalse(); + } + @Test public void setShuffleModeEnabled() throws Exception { boolean testShuffleModeEnabled = true;