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 playlist API ([#6161](https://github.com/google/ExoPlayer/issues/6161)).
* Add `play` and `pause` methods to `Player`. * Add `play` and `pause` methods to `Player`.
* Add `Player.getCurrentLiveOffset` to conveniently return the live offset. * Add `Player.getCurrentLiveOffset` to conveniently return the live offset.
* Add `Player.onPlayWhenReadyChanged` with reasons.
* Make `MediaSourceEventListener.LoadEventInfo` and * Make `MediaSourceEventListener.LoadEventInfo` and
`MediaSourceEventListener.MediaLoadData` top-level classes. `MediaSourceEventListener.MediaLoadData` top-level classes.
* Rename `MediaCodecRenderer.onOutputFormatChanged` to * 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 // 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, // 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. // 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(); flushNotifications();
PendingResult<MediaChannelResult> pendingResult = PendingResult<MediaChannelResult> pendingResult =
playWhenReady ? remoteMediaClient.play() : remoteMediaClient.pause(); playWhenReady ? remoteMediaClient.play() : remoteMediaClient.pause();
@ -625,8 +626,14 @@ public final class CastPlayer extends BasePlayer {
newPlayWhenReadyValue = !remoteMediaClient.isPaused(); newPlayWhenReadyValue = !remoteMediaClient.isPaused();
playWhenReady.clearPendingResultCallback(); 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. // 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") @RequiresNonNull("remoteMediaClient")
@ -718,13 +725,21 @@ public final class CastPlayer extends BasePlayer {
} }
private void setPlayerStateAndNotifyIfChanged( private void setPlayerStateAndNotifyIfChanged(
boolean playWhenReady, @Player.State int playbackState) { boolean playWhenReady,
if (this.playWhenReady.value != playWhenReady || this.playbackState != playbackState) { @Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason,
this.playWhenReady.value = playWhenReady; @Player.State int playbackState) {
boolean playWhenReadyChanged = this.playWhenReady.value != playWhenReady;
if (playWhenReadyChanged || this.playbackState != playbackState) {
this.playbackState = playbackState; this.playbackState = playbackState;
this.playWhenReady.value = playWhenReady;
notificationsBatch.add( notificationsBatch.add(
new ListenerNotificationTask( 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()); verify(mockPendingResult).setResultCallback(setResultCallbackArgumentCaptor.capture());
assertThat(castPlayer.getPlayWhenReady()).isTrue(); assertThat(castPlayer.getPlayWhenReady()).isTrue();
verify(mockListener).onPlayerStateChanged(true, Player.STATE_IDLE); 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. // There is a status update in the middle, which should be hidden by masking.
remoteMediaClientListener.onStatusUpdated(); remoteMediaClientListener.onStatusUpdated();
@ -111,21 +113,42 @@ public class CastPlayerTest {
verify(mockPendingResult).setResultCallback(setResultCallbackArgumentCaptor.capture()); verify(mockPendingResult).setResultCallback(setResultCallbackArgumentCaptor.capture());
assertThat(castPlayer.getPlayWhenReady()).isTrue(); assertThat(castPlayer.getPlayWhenReady()).isTrue();
verify(mockListener).onPlayerStateChanged(true, Player.STATE_IDLE); 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. // Upon result, the remote media client is still paused. The state should reflect that.
setResultCallbackArgumentCaptor setResultCallbackArgumentCaptor
.getValue() .getValue()
.onResult(Mockito.mock(RemoteMediaClient.MediaChannelResult.class)); .onResult(Mockito.mock(RemoteMediaClient.MediaChannelResult.class));
verify(mockListener).onPlayerStateChanged(false, Player.STATE_IDLE); verify(mockListener).onPlayerStateChanged(false, Player.STATE_IDLE);
verify(mockListener).onPlayWhenReadyChanged(false, Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE);
assertThat(castPlayer.getPlayWhenReady()).isFalse(); 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 @Test
public void testPlayWhenReady_changesOnStatusUpdates() { public void testPlayWhenReady_changesOnStatusUpdates() {
assertThat(castPlayer.getPlayWhenReady()).isFalse(); assertThat(castPlayer.getPlayWhenReady()).isFalse();
when(mockRemoteMediaClient.isPaused()).thenReturn(false); when(mockRemoteMediaClient.isPaused()).thenReturn(false);
remoteMediaClientListener.onStatusUpdated(); remoteMediaClientListener.onStatusUpdated();
verify(mockListener).onPlayerStateChanged(true, Player.STATE_IDLE); verify(mockListener).onPlayerStateChanged(true, Player.STATE_IDLE);
verify(mockListener).onPlayWhenReadyChanged(true, Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE);
assertThat(castPlayer.getPlayWhenReady()).isTrue(); assertThat(castPlayer.getPlayWhenReady()).isTrue();
} }

View File

@ -93,12 +93,17 @@ import java.util.ArrayList;
/** Sets the {@link Player.State} of this player. */ /** Sets the {@link Player.State} of this player. */
public void setState(@Player.State int state, boolean playWhenReady) { 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.state = state;
this.playWhenReady = playWhenReady; this.playWhenReady = playWhenReady;
if (notify) { if (playerStateChanged) {
for (Player.EventListener listener : listeners) { for (Player.EventListener listener : listeners) {
listener.onPlayerStateChanged(playWhenReady, state); 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 @Override
public void setPlayWhenReady(boolean playWhenReady) { 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( public void setPlayWhenReady(
boolean playWhenReady, @PlaybackSuppressionReason int playbackSuppressionReason) { boolean playWhenReady,
@PlaybackSuppressionReason int playbackSuppressionReason,
@PlayWhenReadyChangeReason int playWhenReadyChangeReason) {
boolean oldIsPlaying = isPlaying(); boolean oldIsPlaying = isPlaying();
boolean oldInternalPlayWhenReady = boolean oldInternalPlayWhenReady =
this.playWhenReady && this.playbackSuppressionReason == PLAYBACK_SUPPRESSION_REASON_NONE; this.playWhenReady && this.playbackSuppressionReason == PLAYBACK_SUPPRESSION_REASON_NONE;
@ -450,6 +455,9 @@ import java.util.concurrent.TimeoutException;
if (playWhenReadyChanged) { if (playWhenReadyChanged) {
listener.onPlayerStateChanged(playWhenReady, playbackState); listener.onPlayerStateChanged(playWhenReady, playbackState);
} }
if (playWhenReadyChanged) {
listener.onPlayWhenReadyChanged(playWhenReady, playWhenReadyChangeReason);
}
if (suppressionReasonChanged) { if (suppressionReasonChanged) {
listener.onPlaybackSuppressionReasonChanged(playbackSuppressionReason); listener.onPlaybackSuppressionReasonChanged(playbackSuppressionReason);
} }

View File

@ -418,6 +418,15 @@ public interface Player {
*/ */
default void onPlayerStateChanged(boolean playWhenReady, @State int playbackState) {} 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. * Called when the value returned from {@link #getPlaybackSuppressionReason()} changes.
* *
@ -549,6 +558,31 @@ public interface Player {
*/ */
int STATE_ENDED = 4; 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 * Reason why playback is suppressed even though {@link #getPlayWhenReady()} is {@code true}. One
* of {@link #PLAYBACK_SUPPRESSION_REASON_NONE} or {@link * of {@link #PLAYBACK_SUPPRESSION_REASON_NONE} or {@link

View File

@ -688,11 +688,13 @@ public class SimpleExoPlayer extends BasePlayer
} }
} }
boolean playWhenReady = getPlayWhenReady();
@AudioFocusManager.PlayerCommand @AudioFocusManager.PlayerCommand
int playerCommand = int playerCommand =
audioFocusManager.setAudioAttributes( audioFocusManager.setAudioAttributes(
handleAudioFocus ? audioAttributes : null, getPlayWhenReady(), getPlaybackState()); handleAudioFocus ? audioAttributes : null, playWhenReady, getPlaybackState());
updatePlayWhenReady(getPlayWhenReady(), playerCommand); updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
} }
@Override @Override
@ -1179,9 +1181,11 @@ public class SimpleExoPlayer extends BasePlayer
@Override @Override
public void prepare() { public void prepare() {
verifyApplicationThread(); verifyApplicationThread();
boolean playWhenReady = getPlayWhenReady();
@AudioFocusManager.PlayerCommand @AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.handlePrepare(getPlayWhenReady()); int playerCommand = audioFocusManager.handlePrepare(playWhenReady);
updatePlayWhenReady(getPlayWhenReady(), playerCommand); updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
player.prepare(); player.prepare();
} }
@ -1318,7 +1322,8 @@ public class SimpleExoPlayer extends BasePlayer
verifyApplicationThread(); verifyApplicationThread();
@AudioFocusManager.PlayerCommand @AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.handleSetPlayWhenReady(playWhenReady, getPlaybackState()); int playerCommand = audioFocusManager.handleSetPlayWhenReady(playWhenReady, getPlaybackState());
updatePlayWhenReady(playWhenReady, playerCommand); updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
} }
@Override @Override
@ -1634,14 +1639,16 @@ public class SimpleExoPlayer extends BasePlayer
} }
private void updatePlayWhenReady( 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; playWhenReady = playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY;
@PlaybackSuppressionReason @PlaybackSuppressionReason
int playbackSuppressionReason = int playbackSuppressionReason =
playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY
? Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS ? Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS
: Player.PLAYBACK_SUPPRESSION_REASON_NONE; : Player.PLAYBACK_SUPPRESSION_REASON_NONE;
player.setPlayWhenReady(playWhenReady, playbackSuppressionReason); player.setPlayWhenReady(playWhenReady, playbackSuppressionReason, playWhenReadyChangeReason);
} }
private void verifyApplicationThread() { 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 private final class ComponentListener
implements VideoRendererEventListener, implements VideoRendererEventListener,
AudioRendererEventListener, AudioRendererEventListener,
@ -1872,14 +1885,23 @@ public class SimpleExoPlayer extends BasePlayer
@Override @Override
public void executePlayerCommand(@AudioFocusManager.PlayerCommand int playerCommand) { public void executePlayerCommand(@AudioFocusManager.PlayerCommand int playerCommand) {
updatePlayWhenReady(getPlayWhenReady(), playerCommand); boolean playWhenReady = getPlayWhenReady();
updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand));
} }
// AudioBecomingNoisyManager.EventListener implementation. // AudioBecomingNoisyManager.EventListener implementation.
@Override @Override
public void onAudioBecomingNoisy() { 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. // 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 @Override
public void onPlaybackSuppressionReasonChanged( public void onPlaybackSuppressionReasonChanged(
@PlaybackSuppressionReason int playbackSuppressionReason) { @PlaybackSuppressionReason int playbackSuppressionReason) {

View File

@ -133,6 +133,16 @@ public interface AnalyticsListener {
default void onPlayerStateChanged( default void onPlayerStateChanged(
EventTime eventTime, boolean playWhenReady, @Player.State int playbackState) {} 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. * Called when playback suppression reason changed.
* *

View File

@ -98,7 +98,16 @@ public class EventLogger implements AnalyticsListener {
@Override @Override
public void onPlayerStateChanged( public void onPlayerStateChanged(
EventTime eventTime, boolean playWhenReady, @Player.State int state) { 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 @Override
@ -637,4 +646,20 @@ public class EventLogger implements AnalyticsListener {
return "?"; 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 "?";
}
}
} }