add playWhenReady changed callback with reasons

PiperOrigin-RevId: 292194805
This commit is contained in:
bachinger 2020-01-29 20:25:06 +00:00 committed by Oliver Woodman
parent 6424403907
commit 21fe13d3d7
10 changed files with 172 additions and 20 deletions

View File

@ -6,6 +6,7 @@
* Add playlist API ([#6161](https://github.com/google/ExoPlayer/issues/6161)).
* Add `play` and `pause` methods to `Player`.
* Add `Player.getCurrentLiveOffset` to conveniently return the live offset.
* Add `Player.onPlayWhenReadyChanged` with reasons.
* Make `MediaSourceEventListener.LoadEventInfo` and
`MediaSourceEventListener.MediaLoadData` top-level classes.
* Rename `MediaCodecRenderer.onOutputFormatChanged` to

View File

@ -351,7 +351,8 @@ public final class CastPlayer extends BasePlayer {
// We update the local state and send the message to the receiver app, which will cause the
// operation to be perceived as synchronous by the user. When the operation reports a result,
// the local state will be updated to reflect the state reported by the Cast SDK.
setPlayerStateAndNotifyIfChanged(playWhenReady, playbackState);
setPlayerStateAndNotifyIfChanged(
playWhenReady, PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, playbackState);
flushNotifications();
PendingResult<MediaChannelResult> pendingResult =
playWhenReady ? remoteMediaClient.play() : remoteMediaClient.pause();
@ -625,8 +626,14 @@ public final class CastPlayer extends BasePlayer {
newPlayWhenReadyValue = !remoteMediaClient.isPaused();
playWhenReady.clearPendingResultCallback();
}
@PlayWhenReadyChangeReason
int playWhenReadyChangeReason =
newPlayWhenReadyValue != playWhenReady.value
? PLAY_WHEN_READY_CHANGE_REASON_REMOTE
: PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST;
// We do not mask the playback state, so try setting it regardless of the playWhenReady masking.
setPlayerStateAndNotifyIfChanged(newPlayWhenReadyValue, fetchPlaybackState(remoteMediaClient));
setPlayerStateAndNotifyIfChanged(
newPlayWhenReadyValue, playWhenReadyChangeReason, fetchPlaybackState(remoteMediaClient));
}
@RequiresNonNull("remoteMediaClient")
@ -718,13 +725,21 @@ public final class CastPlayer extends BasePlayer {
}
private void setPlayerStateAndNotifyIfChanged(
boolean playWhenReady, @Player.State int playbackState) {
if (this.playWhenReady.value != playWhenReady || this.playbackState != playbackState) {
this.playWhenReady.value = playWhenReady;
boolean playWhenReady,
@Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason,
@Player.State int playbackState) {
boolean playWhenReadyChanged = this.playWhenReady.value != playWhenReady;
if (playWhenReadyChanged || this.playbackState != playbackState) {
this.playbackState = playbackState;
this.playWhenReady.value = playWhenReady;
notificationsBatch.add(
new ListenerNotificationTask(
listener -> listener.onPlayerStateChanged(playWhenReady, playbackState)));
listener -> {
listener.onPlayerStateChanged(playWhenReady, playbackState);
if (playWhenReadyChanged) {
listener.onPlayWhenReadyChanged(playWhenReady, playWhenReadyChangeReason);
}
}));
}
}

View File

@ -89,6 +89,8 @@ public class CastPlayerTest {
verify(mockPendingResult).setResultCallback(setResultCallbackArgumentCaptor.capture());
assertThat(castPlayer.getPlayWhenReady()).isTrue();
verify(mockListener).onPlayerStateChanged(true, Player.STATE_IDLE);
verify(mockListener)
.onPlayWhenReadyChanged(true, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
// There is a status update in the middle, which should be hidden by masking.
remoteMediaClientListener.onStatusUpdated();
@ -111,21 +113,42 @@ public class CastPlayerTest {
verify(mockPendingResult).setResultCallback(setResultCallbackArgumentCaptor.capture());
assertThat(castPlayer.getPlayWhenReady()).isTrue();
verify(mockListener).onPlayerStateChanged(true, Player.STATE_IDLE);
verify(mockListener)
.onPlayWhenReadyChanged(true, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
// Upon result, the remote media client is still paused. The state should reflect that.
setResultCallbackArgumentCaptor
.getValue()
.onResult(Mockito.mock(RemoteMediaClient.MediaChannelResult.class));
verify(mockListener).onPlayerStateChanged(false, Player.STATE_IDLE);
verify(mockListener).onPlayWhenReadyChanged(false, Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE);
assertThat(castPlayer.getPlayWhenReady()).isFalse();
}
@Test
public void testSetPlayWhenReady_correctChangeReasonOnPause() {
when(mockRemoteMediaClient.play()).thenReturn(mockPendingResult);
when(mockRemoteMediaClient.pause()).thenReturn(mockPendingResult);
castPlayer.play();
assertThat(castPlayer.getPlayWhenReady()).isTrue();
verify(mockListener).onPlayerStateChanged(true, Player.STATE_IDLE);
verify(mockListener)
.onPlayWhenReadyChanged(true, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
castPlayer.pause();
assertThat(castPlayer.getPlayWhenReady()).isFalse();
verify(mockListener).onPlayerStateChanged(false, Player.STATE_IDLE);
verify(mockListener)
.onPlayWhenReadyChanged(false, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
}
@Test
public void testPlayWhenReady_changesOnStatusUpdates() {
assertThat(castPlayer.getPlayWhenReady()).isFalse();
when(mockRemoteMediaClient.isPaused()).thenReturn(false);
remoteMediaClientListener.onStatusUpdated();
verify(mockListener).onPlayerStateChanged(true, Player.STATE_IDLE);
verify(mockListener).onPlayWhenReadyChanged(true, Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE);
assertThat(castPlayer.getPlayWhenReady()).isTrue();
}

View File

@ -93,12 +93,17 @@ import java.util.ArrayList;
/** Sets the {@link Player.State} of this player. */
public void setState(@Player.State int state, boolean playWhenReady) {
boolean notify = this.state != state || this.playWhenReady != playWhenReady;
boolean playWhenReadyChanged = this.playWhenReady != playWhenReady;
boolean playerStateChanged = this.state != state || playWhenReadyChanged;
this.state = state;
this.playWhenReady = playWhenReady;
if (notify) {
if (playerStateChanged) {
for (Player.EventListener listener : listeners) {
listener.onPlayerStateChanged(playWhenReady, state);
if (playWhenReadyChanged) {
listener.onPlayWhenReadyChanged(
playWhenReady, PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
}
}
}
}

View File

@ -424,11 +424,16 @@ import java.util.concurrent.TimeoutException;
@Override
public void setPlayWhenReady(boolean playWhenReady) {
setPlayWhenReady(playWhenReady, PLAYBACK_SUPPRESSION_REASON_NONE);
setPlayWhenReady(
playWhenReady,
PLAYBACK_SUPPRESSION_REASON_NONE,
PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
}
public void setPlayWhenReady(
boolean playWhenReady, @PlaybackSuppressionReason int playbackSuppressionReason) {
boolean playWhenReady,
@PlaybackSuppressionReason int playbackSuppressionReason,
@PlayWhenReadyChangeReason int playWhenReadyChangeReason) {
boolean oldIsPlaying = isPlaying();
boolean oldInternalPlayWhenReady =
this.playWhenReady && this.playbackSuppressionReason == PLAYBACK_SUPPRESSION_REASON_NONE;
@ -450,6 +455,9 @@ import java.util.concurrent.TimeoutException;
if (playWhenReadyChanged) {
listener.onPlayerStateChanged(playWhenReady, playbackState);
}
if (playWhenReadyChanged) {
listener.onPlayWhenReadyChanged(playWhenReady, playWhenReadyChangeReason);
}
if (suppressionReasonChanged) {
listener.onPlaybackSuppressionReasonChanged(playbackSuppressionReason);
}

View File

@ -418,6 +418,15 @@ public interface Player {
*/
default void onPlayerStateChanged(boolean playWhenReady, @State int playbackState) {}
/**
* Called when the value returned from {@link #getPlayWhenReady()} changes.
*
* @param playWhenReady Whether playback will proceed when ready.
* @param reason The {@link PlayWhenReadyChangeReason reason} for the change.
*/
default void onPlayWhenReadyChanged(
boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {}
/**
* Called when the value returned from {@link #getPlaybackSuppressionReason()} changes.
*
@ -549,6 +558,31 @@ public interface Player {
*/
int STATE_ENDED = 4;
/**
* Reasons for {@link #getPlayWhenReady() playWhenReady} changes. One of {@link
* #PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST}, {@link
* #PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS}, {@link
* #PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY} or {@link
* #PLAY_WHEN_READY_CHANGE_REASON_REMOTE}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS,
PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY,
PLAY_WHEN_READY_CHANGE_REASON_REMOTE
})
@interface PlayWhenReadyChangeReason {}
/** Playback has been started or paused by the user. */
int PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST = 1;
/** Playback has been paused because of a loss of audio focus. */
int PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS = 2;
/** Playback has been paused to avoid becoming noisy. */
int PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY = 3;
/** Playback has been started or paused because of a remote change. */
int PLAY_WHEN_READY_CHANGE_REASON_REMOTE = 4;
/**
* Reason why playback is suppressed even though {@link #getPlayWhenReady()} is {@code true}. One
* of {@link #PLAYBACK_SUPPRESSION_REASON_NONE} or {@link

View File

@ -688,11 +688,13 @@ public class SimpleExoPlayer extends BasePlayer
}
}
boolean playWhenReady = getPlayWhenReady();
@AudioFocusManager.PlayerCommand
int playerCommand =
audioFocusManager.setAudioAttributes(
handleAudioFocus ? audioAttributes : null, getPlayWhenReady(), getPlaybackState());
updatePlayWhenReady(getPlayWhenReady(), playerCommand);
handleAudioFocus ? audioAttributes : null, playWhenReady, getPlaybackState());
updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
}
@Override
@ -1179,9 +1181,11 @@ public class SimpleExoPlayer extends BasePlayer
@Override
public void prepare() {
verifyApplicationThread();
boolean playWhenReady = getPlayWhenReady();
@AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.handlePrepare(getPlayWhenReady());
updatePlayWhenReady(getPlayWhenReady(), playerCommand);
int playerCommand = audioFocusManager.handlePrepare(playWhenReady);
updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
player.prepare();
}
@ -1318,7 +1322,8 @@ public class SimpleExoPlayer extends BasePlayer
verifyApplicationThread();
@AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.handleSetPlayWhenReady(playWhenReady, getPlaybackState());
updatePlayWhenReady(playWhenReady, playerCommand);
updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
}
@Override
@ -1634,14 +1639,16 @@ public class SimpleExoPlayer extends BasePlayer
}
private void updatePlayWhenReady(
boolean playWhenReady, @AudioFocusManager.PlayerCommand int playerCommand) {
boolean playWhenReady,
@AudioFocusManager.PlayerCommand int playerCommand,
@Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason) {
playWhenReady = playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY;
@PlaybackSuppressionReason
int playbackSuppressionReason =
playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY
? Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS
: Player.PLAYBACK_SUPPRESSION_REASON_NONE;
player.setPlayWhenReady(playWhenReady, playbackSuppressionReason);
player.setPlayWhenReady(playWhenReady, playbackSuppressionReason, playWhenReadyChangeReason);
}
private void verifyApplicationThread() {
@ -1655,6 +1662,12 @@ public class SimpleExoPlayer extends BasePlayer
}
}
private static int getPlayWhenReadyChangeReason(boolean playWhenReady, int playerCommand) {
return playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY
? PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS
: PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST;
}
private final class ComponentListener
implements VideoRendererEventListener,
AudioRendererEventListener,
@ -1872,14 +1885,23 @@ public class SimpleExoPlayer extends BasePlayer
@Override
public void executePlayerCommand(@AudioFocusManager.PlayerCommand int playerCommand) {
updatePlayWhenReady(getPlayWhenReady(), playerCommand);
boolean playWhenReady = getPlayWhenReady();
updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
}
// AudioBecomingNoisyManager.EventListener implementation.
@Override
public void onAudioBecomingNoisy() {
pause();
// Command is always PLAYER_COMMAND_DO_NOT_PLAY but the call is needed to abandon the
// audio focus if the focus is currently held.
int playerCommand =
audioFocusManager.handleSetPlayWhenReady(/* playWhenReady= */ false, getPlaybackState());
updatePlayWhenReady(
/* playWhenReady= */ false,
playerCommand,
Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY);
}
// Player.EventListener implementation.

View File

@ -446,6 +446,15 @@ public class AnalyticsCollector
}
}
@Override
public final void onPlayWhenReadyChanged(
boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
for (AnalyticsListener listener : listeners) {
listener.onPlayWhenReadyChanged(eventTime, playWhenReady, reason);
}
}
@Override
public void onPlaybackSuppressionReasonChanged(
@PlaybackSuppressionReason int playbackSuppressionReason) {

View File

@ -133,6 +133,16 @@ public interface AnalyticsListener {
default void onPlayerStateChanged(
EventTime eventTime, boolean playWhenReady, @Player.State int playbackState) {}
/**
* Called when the value changed that indicates whether playback will proceed when ready.
*
* @param eventTime The event time.
* @param playWhenReady Whether playback will proceed when ready.
* @param reason The {@link Player.PlayWhenReadyChangeReason reason} of the change.
*/
default void onPlayWhenReadyChanged(
EventTime eventTime, boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {}
/**
* Called when playback suppression reason changed.
*

View File

@ -98,7 +98,16 @@ public class EventLogger implements AnalyticsListener {
@Override
public void onPlayerStateChanged(
EventTime eventTime, boolean playWhenReady, @Player.State int state) {
logd(eventTime, "state", playWhenReady + ", " + getStateString(state));
logd(eventTime, "state", getStateString(state));
}
@Override
public void onPlayWhenReadyChanged(
EventTime eventTime, boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
logd(
eventTime,
"playWhenReady",
playWhenReady + ", " + getPlayWhenReadyChangeReasonString(reason));
}
@Override
@ -637,4 +646,20 @@ public class EventLogger implements AnalyticsListener {
return "?";
}
}
private static String getPlayWhenReadyChangeReasonString(
@Player.PlayWhenReadyChangeReason int reason) {
switch (reason) {
case Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY:
return "AUDIO_BECOMING_NOISY";
case Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS:
return "AUDIO_FOCUS_LOSS";
case Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE:
return "REMOTE";
case Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST:
return "USER_REQUEST";
default:
return "?";
}
}
}