Clarify that onPlayWhenReadyChanged can be called again with new reason

Sometimes the reason for the current state may change. If we don't
report this again, users have no way of knowing that the reason
changed.

Also adjust ExoPlayerImpl and MediaControllerImplBase accordingly.
SimpleBasePlayer already adheres to this logic.

#cherrypick

PiperOrigin-RevId: 644970236
This commit is contained in:
tonihei 2024-06-20 03:55:17 -07:00 committed by Copybara-Service
parent c0abd6f91e
commit 1d26d1891e
12 changed files with 113 additions and 27 deletions

View File

@ -922,6 +922,9 @@ public interface Player {
/** /**
* Called when the value returned from {@link #getPlayWhenReady()} changes. * Called when the value returned from {@link #getPlayWhenReady()} changes.
* *
* <p>The current {@code playWhenReady} value may be re-reported if the {@code reason} for this
* value changes.
*
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with * <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
* other events that happen in the same {@link Looper} message queue iteration. * other events that happen in the same {@link Looper} message queue iteration.
* *

View File

@ -2154,7 +2154,9 @@ import java.util.concurrent.TimeoutException;
Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYBACK_STATE_CHANGED,
listener -> listener.onPlaybackStateChanged(newPlaybackInfo.playbackState)); listener -> listener.onPlaybackStateChanged(newPlaybackInfo.playbackState));
} }
if (playWhenReadyChanged) { if (playWhenReadyChanged
|| previousPlaybackInfo.playWhenReadyChangeReason
!= newPlaybackInfo.playWhenReadyChangeReason) {
listeners.queueEvent( listeners.queueEvent(
Player.EVENT_PLAY_WHEN_READY_CHANGED, Player.EVENT_PLAY_WHEN_READY_CHANGED,
listener -> listener ->
@ -2798,7 +2800,8 @@ import java.util.concurrent.TimeoutException;
@PlaybackSuppressionReason @PlaybackSuppressionReason
int playbackSuppressionReason = computePlaybackSuppressionReason(playWhenReady, playerCommand); int playbackSuppressionReason = computePlaybackSuppressionReason(playWhenReady, playerCommand);
if (playbackInfo.playWhenReady == playWhenReady if (playbackInfo.playWhenReady == playWhenReady
&& playbackInfo.playbackSuppressionReason == playbackSuppressionReason) { && playbackInfo.playbackSuppressionReason == playbackSuppressionReason
&& playbackInfo.playWhenReadyChangeReason == playWhenReadyChangeReason) {
return; return;
} }
updatePlaybackInfoForPlayWhenReadyAndSuppressionReasonStates( updatePlaybackInfoForPlayWhenReadyAndSuppressionReasonStates(

View File

@ -4392,7 +4392,9 @@ public class ExoPlayerTest {
assertThat(playWhenReady).isFalse(); assertThat(playWhenReady).isFalse();
assertThat(suppressionReason).isEqualTo(Player.PLAYBACK_SUPPRESSION_REASON_NONE); assertThat(suppressionReason).isEqualTo(Player.PLAYBACK_SUPPRESSION_REASON_NONE);
verify(listener, never()).onPlaybackSuppressionReasonChanged(anyInt()); verify(listener, never()).onPlaybackSuppressionReasonChanged(anyInt());
// TODO: Fix behavior and assert that audio focus loss is reported via onPlayWhenReadyChanged. verify(listener)
.onPlayWhenReadyChanged(
/* playWhenReady= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS);
} }
@Test @Test

View File

@ -2746,7 +2746,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
@Nullable @Nullable
@Player.PlayWhenReadyChangeReason @Player.PlayWhenReadyChangeReason
Integer playWhenReadyChangeReason = Integer playWhenReadyChangeReason =
oldPlayerInfo.playWhenReady != finalPlayerInfo.playWhenReady oldPlayerInfo.playWhenReadyChangeReason != finalPlayerInfo.playWhenReadyChangeReason
|| oldPlayerInfo.playWhenReady != finalPlayerInfo.playWhenReady
? finalPlayerInfo.playWhenReadyChangeReason ? finalPlayerInfo.playWhenReadyChangeReason
: null; : null;

View File

@ -59,7 +59,7 @@ interface IRemoteMediaSession {
void increaseDeviceVolume(String sessionId, int flags); void increaseDeviceVolume(String sessionId, int flags);
void setDeviceMuted(String sessionId, boolean muted, int flags); void setDeviceMuted(String sessionId, boolean muted, int flags);
void notifyPlayerError(String sessionId, in Bundle playerErrorBundle); void notifyPlayerError(String sessionId, in Bundle playerErrorBundle);
void notifyPlayWhenReadyChanged(String sessionId, boolean playWhenReady, int reason); void notifyPlayWhenReadyChanged(String sessionId, boolean playWhenReady, int playWhenReadyChangeReason, int suppressionReason);
void notifyPlaybackStateChanged(String sessionId, int state); void notifyPlaybackStateChanged(String sessionId, int state);
void notifyIsLoadingChanged(String sessionId, boolean isLoading); void notifyIsLoadingChanged(String sessionId, boolean isLoading);
void notifyPositionDiscontinuity(String sessionId, void notifyPositionDiscontinuity(String sessionId,

View File

@ -789,7 +789,9 @@ public class MediaControllerCompatCallbackWithMediaSessionTest {
session session
.getMockPlayer() .getMockPlayer()
.notifyPlayWhenReadyChanged( .notifyPlayWhenReadyChanged(
/* playWhenReady= */ false, Player.PLAYBACK_SUPPRESSION_REASON_NONE); /* playWhenReady= */ false,
Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
Player.PLAYBACK_SUPPRESSION_REASON_NONE);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playbackStateCompatRef.get().getState()).isEqualTo(PlaybackStateCompat.STATE_PAUSED); assertThat(playbackStateCompatRef.get().getState()).isEqualTo(PlaybackStateCompat.STATE_PAUSED);
@ -833,7 +835,9 @@ public class MediaControllerCompatCallbackWithMediaSessionTest {
session session
.getMockPlayer() .getMockPlayer()
.notifyPlayWhenReadyChanged( .notifyPlayWhenReadyChanged(
/* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE); /* playWhenReady= */ true,
Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
Player.PLAYBACK_SUPPRESSION_REASON_NONE);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playbackStateCompatRef.get().getState()) assertThat(playbackStateCompatRef.get().getState())
@ -919,6 +923,7 @@ public class MediaControllerCompatCallbackWithMediaSessionTest {
.getMockPlayer() .getMockPlayer()
.notifyPlayWhenReadyChanged( .notifyPlayWhenReadyChanged(
/* playWhenReady= */ true, /* playWhenReady= */ true,
Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS); Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
@ -969,6 +974,7 @@ public class MediaControllerCompatCallbackWithMediaSessionTest {
.getMockPlayer() .getMockPlayer()
.notifyPlayWhenReadyChanged( .notifyPlayWhenReadyChanged(
/* playWhenReady= */ true, /* playWhenReady= */ true,
Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS); Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
@ -1015,7 +1021,9 @@ public class MediaControllerCompatCallbackWithMediaSessionTest {
session session
.getMockPlayer() .getMockPlayer()
.notifyPlayWhenReadyChanged( .notifyPlayWhenReadyChanged(
/* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE); /* playWhenReady= */ true,
Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
Player.PLAYBACK_SUPPRESSION_REASON_NONE);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playbackStateCompatRef.get().getState()) assertThat(playbackStateCompatRef.get().getState())

View File

@ -1454,7 +1454,9 @@ public class MediaControllerListenerTest {
}; };
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener)); threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
remoteSession.getMockPlayer().notifyPlayWhenReadyChanged(testPlayWhenReady, testReason); remoteSession
.getMockPlayer()
.notifyPlayWhenReadyChanged(testPlayWhenReady, testReason, testSuppressionReason);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playWhenReadyParamRef.get()).isEqualTo(testPlayWhenReady); assertThat(playWhenReadyParamRef.get()).isEqualTo(testPlayWhenReady);
@ -1469,6 +1471,44 @@ public class MediaControllerListenerTest {
Player.EVENT_PLAY_WHEN_READY_CHANGED, Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED); Player.EVENT_PLAY_WHEN_READY_CHANGED, Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED);
} }
@Test
public void onPlayWhenReadyReasonChanged_isNotified() throws Exception {
remoteSession
.getMockPlayer()
.setPlayWhenReady(
/* playWhenReady= */ false,
Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
MediaController controller = controllerTestRule.createController(remoteSession.getToken());
CountDownLatch latch = new CountDownLatch(1);
AtomicBoolean playWhenReadyParamRef = new AtomicBoolean();
AtomicBoolean playWhenReadyGetterRef = new AtomicBoolean();
AtomicInteger playWhenReadyReasonParamRef = new AtomicInteger();
Player.Listener listener =
new Player.Listener() {
@Override
public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
playWhenReadyParamRef.set(playWhenReady);
playWhenReadyGetterRef.set(controller.getPlayWhenReady());
playWhenReadyReasonParamRef.set(reason);
latch.countDown();
}
};
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
remoteSession
.getMockPlayer()
.notifyPlayWhenReadyChanged(
/* playWhenReady= */ false,
Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS,
Player.PLAYBACK_SUPPRESSION_REASON_NONE);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playWhenReadyParamRef.get()).isFalse();
assertThat(playWhenReadyGetterRef.get()).isFalse();
assertThat(playWhenReadyReasonParamRef.get())
.isEqualTo(Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS);
}
@Test @Test
public void onPlayWhenReadyChanged_updatesGetters() throws Exception { public void onPlayWhenReadyChanged_updatesGetters() throws Exception {
boolean testPlayWhenReady = true; boolean testPlayWhenReady = true;
@ -1550,7 +1590,10 @@ public class MediaControllerListenerTest {
remoteSession.getMockPlayer().setTotalBufferedDuration(testTotalBufferedDurationMs); remoteSession.getMockPlayer().setTotalBufferedDuration(testTotalBufferedDurationMs);
remoteSession.getMockPlayer().setCurrentLiveOffset(testCurrentLiveOffsetMs); remoteSession.getMockPlayer().setCurrentLiveOffset(testCurrentLiveOffsetMs);
remoteSession.getMockPlayer().setContentBufferedPosition(testContentBufferedPositionMs); remoteSession.getMockPlayer().setContentBufferedPosition(testContentBufferedPositionMs);
remoteSession.getMockPlayer().notifyPlayWhenReadyChanged(testPlayWhenReady, testReason); remoteSession
.getMockPlayer()
.notifyPlayWhenReadyChanged(
testPlayWhenReady, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, testReason);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playWhenReadyRef.get()).isEqualTo(testPlayWhenReady); assertThat(playWhenReadyRef.get()).isEqualTo(testPlayWhenReady);
@ -1604,7 +1647,10 @@ public class MediaControllerListenerTest {
}; };
threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener)); threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener));
remoteSession.getMockPlayer().notifyPlayWhenReadyChanged(testPlayWhenReady, testReason); remoteSession
.getMockPlayer()
.notifyPlayWhenReadyChanged(
testPlayWhenReady, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, testReason);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playbackSuppressionReasonParamRef.get()).isEqualTo(testReason); assertThat(playbackSuppressionReasonParamRef.get()).isEqualTo(testReason);
@ -1686,7 +1732,10 @@ public class MediaControllerListenerTest {
remoteSession.getMockPlayer().setTotalBufferedDuration(testTotalBufferedDurationMs); remoteSession.getMockPlayer().setTotalBufferedDuration(testTotalBufferedDurationMs);
remoteSession.getMockPlayer().setCurrentLiveOffset(testCurrentLiveOffsetMs); remoteSession.getMockPlayer().setCurrentLiveOffset(testCurrentLiveOffsetMs);
remoteSession.getMockPlayer().setContentBufferedPosition(testContentBufferedPositionMs); remoteSession.getMockPlayer().setContentBufferedPosition(testContentBufferedPositionMs);
remoteSession.getMockPlayer().notifyPlayWhenReadyChanged(testPlayWhenReady, testReason); remoteSession
.getMockPlayer()
.notifyPlayWhenReadyChanged(
testPlayWhenReady, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, testReason);
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playbackSuppressionReasonRef.get()).isEqualTo(testReason); assertThat(playbackSuppressionReasonRef.get()).isEqualTo(testReason);

View File

@ -225,7 +225,9 @@ public class MediaSessionAndControllerTest {
.postAndSync( .postAndSync(
() -> () ->
player.notifyPlayWhenReadyChanged( player.notifyPlayWhenReadyChanged(
testPlayWhenReady, Player.PLAYBACK_SUPPRESSION_REASON_NONE)); testPlayWhenReady,
Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
Player.PLAYBACK_SUPPRESSION_REASON_NONE));
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playWhenReadyRef.get()).isEqualTo(testPlayWhenReady); assertThat(playWhenReadyRef.get()).isEqualTo(testPlayWhenReady);

View File

@ -101,7 +101,9 @@ public class MediaSessionKeyEventTest {
handler.postAndSync( handler.postAndSync(
() -> { () -> {
player.notifyPlayWhenReadyChanged( player.notifyPlayWhenReadyChanged(
/* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE); /* playWhenReady= */ true,
Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
Player.PLAYBACK_SUPPRESSION_REASON_NONE);
player.notifyPlaybackStateChanged(Player.STATE_READY); player.notifyPlaybackStateChanged(Player.STATE_READY);
}); });
} else { } else {

View File

@ -856,13 +856,17 @@ public class MediaSessionProviderService extends Service {
@Override @Override
public void notifyPlayWhenReadyChanged( public void notifyPlayWhenReadyChanged(
String sessionId, boolean playWhenReady, @Player.PlaybackSuppressionReason int reason) String sessionId,
boolean playWhenReady,
@Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason,
@Player.PlaybackSuppressionReason int suppressionReason)
throws RemoteException { throws RemoteException {
runOnHandler( runOnHandler(
() -> { () -> {
MediaSession session = sessionMap.get(sessionId); MediaSession session = sessionMap.get(sessionId);
MockPlayer player = (MockPlayer) session.getPlayer(); MockPlayer player = (MockPlayer) session.getPlayer();
player.notifyPlayWhenReadyChanged(playWhenReady, reason); player.notifyPlayWhenReadyChanged(
playWhenReady, playWhenReadyChangeReason, suppressionReason);
}); });
} }

View File

@ -310,6 +310,7 @@ public class MockPlayer implements Player {
public int deviceVolume; public int deviceVolume;
public boolean deviceMuted; public boolean deviceMuted;
public boolean playWhenReady; public boolean playWhenReady;
public @PlayWhenReadyChangeReason int playWhenReadyChangeReason;
public @PlaybackSuppressionReason int playbackSuppressionReason; public @PlaybackSuppressionReason int playbackSuppressionReason;
public @State int playbackState; public @State int playbackState;
public boolean isLoading; public boolean isLoading;
@ -404,7 +405,9 @@ public class MockPlayer implements Player {
checkNotNull(conditionVariables.get(METHOD_PLAY)).open(); checkNotNull(conditionVariables.get(METHOD_PLAY)).open();
if (changePlayerStateWithTransportControl) { if (changePlayerStateWithTransportControl) {
notifyPlayWhenReadyChanged( notifyPlayWhenReadyChanged(
/* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE); /* playWhenReady= */ true,
Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
Player.PLAYBACK_SUPPRESSION_REASON_NONE);
} }
} }
@ -413,7 +416,9 @@ public class MockPlayer implements Player {
checkNotNull(conditionVariables.get(METHOD_PAUSE)).open(); checkNotNull(conditionVariables.get(METHOD_PAUSE)).open();
if (changePlayerStateWithTransportControl) { if (changePlayerStateWithTransportControl) {
notifyPlayWhenReadyChanged( notifyPlayWhenReadyChanged(
/* playWhenReady= */ false, Player.PLAYBACK_SUPPRESSION_REASON_NONE); /* playWhenReady= */ false,
Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
Player.PLAYBACK_SUPPRESSION_REASON_NONE);
} }
} }
@ -585,22 +590,26 @@ public class MockPlayer implements Player {
* Player.Listener#onIsPlayingChanged} as appropriate. * Player.Listener#onIsPlayingChanged} as appropriate.
*/ */
public void notifyPlayWhenReadyChanged( public void notifyPlayWhenReadyChanged(
boolean playWhenReady, @PlaybackSuppressionReason int playbackSuppressionReason) { boolean playWhenReady,
boolean playWhenReadyChanged = (this.playWhenReady != playWhenReady); @PlayWhenReadyChangeReason int playWhenReadyChangeReason,
@PlaybackSuppressionReason int playbackSuppressionReason) {
boolean playWhenReadyChanged = this.playWhenReady != playWhenReady;
boolean playWhenReadyReasonChanged =
this.playWhenReadyChangeReason != playWhenReadyChangeReason;
boolean playbackSuppressionReasonChanged = boolean playbackSuppressionReasonChanged =
(this.playbackSuppressionReason != playbackSuppressionReason); this.playbackSuppressionReason != playbackSuppressionReason;
if (!playWhenReadyChanged && !playbackSuppressionReasonChanged) { if (!playWhenReadyChanged && !playbackSuppressionReasonChanged && !playWhenReadyReasonChanged) {
return; return;
} }
boolean wasPlaying = isPlaying(); boolean wasPlaying = isPlaying();
this.playWhenReady = playWhenReady; this.playWhenReady = playWhenReady;
this.playWhenReadyChangeReason = playWhenReadyChangeReason;
this.playbackSuppressionReason = playbackSuppressionReason; this.playbackSuppressionReason = playbackSuppressionReason;
boolean isPlaying = isPlaying(); boolean isPlaying = isPlaying();
for (Listener listener : listeners) { for (Listener listener : listeners) {
if (playWhenReadyChanged) { if (playWhenReadyChanged || playWhenReadyReasonChanged) {
listener.onPlayWhenReadyChanged( listener.onPlayWhenReadyChanged(playWhenReady, playWhenReadyChangeReason);
playWhenReady, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
} }
if (playbackSuppressionReasonChanged) { if (playbackSuppressionReasonChanged) {
listener.onPlaybackSuppressionReasonChanged(playbackSuppressionReason); listener.onPlaybackSuppressionReasonChanged(playbackSuppressionReason);

View File

@ -314,9 +314,12 @@ public class RemoteMediaSession {
} }
public void notifyPlayWhenReadyChanged( public void notifyPlayWhenReadyChanged(
boolean playWhenReady, @Player.PlaybackSuppressionReason int reason) boolean playWhenReady,
@Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason,
@Player.PlaybackSuppressionReason int suppressionReason)
throws RemoteException { throws RemoteException {
binder.notifyPlayWhenReadyChanged(sessionId, playWhenReady, reason); binder.notifyPlayWhenReadyChanged(
sessionId, playWhenReady, playWhenReadyChangeReason, suppressionReason);
} }
public void notifyPlaybackStateChanged(@Player.State int state) throws RemoteException { public void notifyPlaybackStateChanged(@Player.State int state) throws RemoteException {