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

View File

@ -4392,7 +4392,9 @@ public class ExoPlayerTest {
assertThat(playWhenReady).isFalse();
assertThat(suppressionReason).isEqualTo(Player.PLAYBACK_SUPPRESSION_REASON_NONE);
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

View File

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

View File

@ -59,7 +59,7 @@ interface IRemoteMediaSession {
void increaseDeviceVolume(String sessionId, int flags);
void setDeviceMuted(String sessionId, boolean muted, int flags);
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 notifyIsLoadingChanged(String sessionId, boolean isLoading);
void notifyPositionDiscontinuity(String sessionId,

View File

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

View File

@ -1454,7 +1454,9 @@ public class MediaControllerListenerTest {
};
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(playWhenReadyParamRef.get()).isEqualTo(testPlayWhenReady);
@ -1469,6 +1471,44 @@ public class MediaControllerListenerTest {
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
public void onPlayWhenReadyChanged_updatesGetters() throws Exception {
boolean testPlayWhenReady = true;
@ -1550,7 +1590,10 @@ public class MediaControllerListenerTest {
remoteSession.getMockPlayer().setTotalBufferedDuration(testTotalBufferedDurationMs);
remoteSession.getMockPlayer().setCurrentLiveOffset(testCurrentLiveOffsetMs);
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(playWhenReadyRef.get()).isEqualTo(testPlayWhenReady);
@ -1604,7 +1647,10 @@ public class MediaControllerListenerTest {
};
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(playbackSuppressionReasonParamRef.get()).isEqualTo(testReason);
@ -1686,7 +1732,10 @@ public class MediaControllerListenerTest {
remoteSession.getMockPlayer().setTotalBufferedDuration(testTotalBufferedDurationMs);
remoteSession.getMockPlayer().setCurrentLiveOffset(testCurrentLiveOffsetMs);
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(playbackSuppressionReasonRef.get()).isEqualTo(testReason);

View File

@ -225,7 +225,9 @@ public class MediaSessionAndControllerTest {
.postAndSync(
() ->
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(playWhenReadyRef.get()).isEqualTo(testPlayWhenReady);

View File

@ -101,7 +101,9 @@ public class MediaSessionKeyEventTest {
handler.postAndSync(
() -> {
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);
});
} else {

View File

@ -856,13 +856,17 @@ public class MediaSessionProviderService extends Service {
@Override
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 {
runOnHandler(
() -> {
MediaSession session = sessionMap.get(sessionId);
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 boolean deviceMuted;
public boolean playWhenReady;
public @PlayWhenReadyChangeReason int playWhenReadyChangeReason;
public @PlaybackSuppressionReason int playbackSuppressionReason;
public @State int playbackState;
public boolean isLoading;
@ -404,7 +405,9 @@ public class MockPlayer implements Player {
checkNotNull(conditionVariables.get(METHOD_PLAY)).open();
if (changePlayerStateWithTransportControl) {
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();
if (changePlayerStateWithTransportControl) {
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.
*/
public void notifyPlayWhenReadyChanged(
boolean playWhenReady, @PlaybackSuppressionReason int playbackSuppressionReason) {
boolean playWhenReadyChanged = (this.playWhenReady != playWhenReady);
boolean playWhenReady,
@PlayWhenReadyChangeReason int playWhenReadyChangeReason,
@PlaybackSuppressionReason int playbackSuppressionReason) {
boolean playWhenReadyChanged = this.playWhenReady != playWhenReady;
boolean playWhenReadyReasonChanged =
this.playWhenReadyChangeReason != playWhenReadyChangeReason;
boolean playbackSuppressionReasonChanged =
(this.playbackSuppressionReason != playbackSuppressionReason);
if (!playWhenReadyChanged && !playbackSuppressionReasonChanged) {
this.playbackSuppressionReason != playbackSuppressionReason;
if (!playWhenReadyChanged && !playbackSuppressionReasonChanged && !playWhenReadyReasonChanged) {
return;
}
boolean wasPlaying = isPlaying();
this.playWhenReady = playWhenReady;
this.playWhenReadyChangeReason = playWhenReadyChangeReason;
this.playbackSuppressionReason = playbackSuppressionReason;
boolean isPlaying = isPlaying();
for (Listener listener : listeners) {
if (playWhenReadyChanged) {
listener.onPlayWhenReadyChanged(
playWhenReady, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
if (playWhenReadyChanged || playWhenReadyReasonChanged) {
listener.onPlayWhenReadyChanged(playWhenReady, playWhenReadyChangeReason);
}
if (playbackSuppressionReasonChanged) {
listener.onPlaybackSuppressionReasonChanged(playbackSuppressionReason);

View File

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