Skip to content

Commit

Permalink
Add Player.isPlaying and Player.getPlaybackSuppressionReason
Browse files Browse the repository at this point in the history
The player may suppress playback when waiting for audio focus even if the
state==Player.READY. There is currently no getter or callback to obtain this
piece of information for UI updates or analytics.

Also, it's a important derived state to know whether the playback position is
advancing. Add isPlaying and the corresponding callback to allow retrieving
this information more easily.

Issue:#6203
PiperOrigin-RevId: 268921721
  • Loading branch information
tonihei authored and ojw28 committed Sep 20, 2019
1 parent 9557868 commit f8d81d0
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 17 deletions.
5 changes: 5 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

### 2.10.5 ###

* Add `Player.isPlaying` and `EventListener.onIsPlayingChanged` to check whether
the playback position is advancing. This helps to determine if playback is
suppressed due to audio focus loss. Also add
`Player.getPlaybackSuppressedReason` to determine the reason of the
suppression ([#6203](https://github.com/google/ExoPlayer/issues/6203)).
* Track selection
* Add `allowAudioMixedChannelCountAdaptiveness` parameter to
`DefaultTrackSelector` to allow adaptive selections of audio tracks with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,12 @@ public int getPlaybackState() {
return playbackState;
}

@Override
@PlaybackSuppressionReason
public int getPlaybackSuppressionReason() {
return Player.PLAYBACK_SUPPRESSION_REASON_NONE;
}

@Override
@Nullable
public ExoPlaybackException getPlaybackError() {
Expand Down Expand Up @@ -542,6 +548,7 @@ private void updateInternalState() {
return;
}

boolean wasPlaying = playbackState == Player.STATE_READY && playWhenReady;
int playbackState = fetchPlaybackState(remoteMediaClient);
boolean playWhenReady = !remoteMediaClient.isPaused();
if (this.playbackState != playbackState
Expand All @@ -552,6 +559,11 @@ private void updateInternalState() {
new ListenerNotificationTask(
listener -> listener.onPlayerStateChanged(this.playWhenReady, this.playbackState)));
}
boolean isPlaying = playbackState == Player.STATE_READY && playWhenReady;
if (wasPlaying != isPlaying) {
notificationsBatch.add(
new ListenerNotificationTask(listener -> listener.onIsPlayingChanged(isPlaying)));
}
@RepeatMode int repeatMode = fetchRepeatMode(remoteMediaClient);
if (this.repeatMode != repeatMode) {
this.repeatMode = repeatMode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ public BasePlayer() {
window = new Timeline.Window();
}

@Override
public final boolean isPlaying() {
return getPlaybackState() == Player.STATE_READY
&& getPlayWhenReady()
&& getPlaybackSuppressionReason() == PLAYBACK_SUPPRESSION_REASON_NONE;
}

@Override
public final void seekToDefaultPosition() {
seekToDefaultPosition(getCurrentWindowIndex());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@

private MediaSource mediaSource;
private boolean playWhenReady;
private boolean internalPlayWhenReady;
private @RepeatMode int repeatMode;
@PlaybackSuppressionReason private int playbackSuppressionReason;
@RepeatMode private int repeatMode;
private boolean shuffleModeEnabled;
private int pendingOperationAcks;
private boolean hasPendingPrepare;
Expand Down Expand Up @@ -119,6 +119,7 @@ public ExoPlayerImpl(
period = new Timeline.Period();
playbackParameters = PlaybackParameters.DEFAULT;
seekParameters = SeekParameters.DEFAULT;
playbackSuppressionReason = PLAYBACK_SUPPRESSION_REASON_NONE;
eventHandler =
new Handler(looper) {
@Override
Expand Down Expand Up @@ -197,8 +198,14 @@ public int getPlaybackState() {
return playbackInfo.playbackState;
}

@PlaybackSuppressionReason
public int getPlaybackSuppressionReason() {
return playbackSuppressionReason;
}

@Override
public @Nullable ExoPlaybackException getPlaybackError() {
@Nullable
public ExoPlaybackException getPlaybackError() {
return playbackError;
}

Expand Down Expand Up @@ -239,19 +246,35 @@ public void prepare(MediaSource mediaSource, boolean resetPosition, boolean rese

@Override
public void setPlayWhenReady(boolean playWhenReady) {
setPlayWhenReady(playWhenReady, /* suppressPlayback= */ false);
setPlayWhenReady(playWhenReady, PLAYBACK_SUPPRESSION_REASON_NONE);
}

public void setPlayWhenReady(boolean playWhenReady, boolean suppressPlayback) {
boolean internalPlayWhenReady = playWhenReady && !suppressPlayback;
if (this.internalPlayWhenReady != internalPlayWhenReady) {
this.internalPlayWhenReady = internalPlayWhenReady;
public void setPlayWhenReady(
boolean playWhenReady, @PlaybackSuppressionReason int playbackSuppressionReason) {
boolean oldIsPlaying = isPlaying();
boolean oldInternalPlayWhenReady =
this.playWhenReady && this.playbackSuppressionReason == PLAYBACK_SUPPRESSION_REASON_NONE;
boolean internalPlayWhenReady =
playWhenReady && playbackSuppressionReason == PLAYBACK_SUPPRESSION_REASON_NONE;
if (oldInternalPlayWhenReady != internalPlayWhenReady) {
internalPlayer.setPlayWhenReady(internalPlayWhenReady);
}
if (this.playWhenReady != playWhenReady) {
this.playWhenReady = playWhenReady;
boolean playWhenReadyChanged = this.playWhenReady != playWhenReady;
this.playWhenReady = playWhenReady;
this.playbackSuppressionReason = playbackSuppressionReason;
boolean isPlaying = isPlaying();
boolean isPlayingChanged = oldIsPlaying != isPlaying;
if (playWhenReadyChanged || isPlayingChanged) {
int playbackState = playbackInfo.playbackState;
notifyListeners(listener -> listener.onPlayerStateChanged(playWhenReady, playbackState));
notifyListeners(
listener -> {
if (playWhenReadyChanged) {
listener.onPlayerStateChanged(playWhenReady, playbackState);
}
if (isPlayingChanged) {
listener.onIsPlayingChanged(isPlaying);
}
});
}
}

Expand Down Expand Up @@ -697,9 +720,11 @@ private void updatePlaybackInfo(
@Player.DiscontinuityReason int positionDiscontinuityReason,
@Player.TimelineChangeReason int timelineChangeReason,
boolean seekProcessed) {
boolean previousIsPlaying = isPlaying();
// Assign playback info immediately such that all getters return the right values.
PlaybackInfo previousPlaybackInfo = this.playbackInfo;
this.playbackInfo = playbackInfo;
boolean isPlaying = isPlaying();
notifyListeners(
new PlaybackInfoUpdate(
playbackInfo,
Expand All @@ -710,7 +735,8 @@ private void updatePlaybackInfo(
positionDiscontinuityReason,
timelineChangeReason,
seekProcessed,
playWhenReady));
playWhenReady,
/* isPlayingChanged= */ previousIsPlaying != isPlaying));
}

private void notifyListeners(ListenerInvocation listenerInvocation) {
Expand Down Expand Up @@ -755,17 +781,19 @@ private static final class PlaybackInfoUpdate implements Runnable {
private final boolean isLoadingChanged;
private final boolean trackSelectorResultChanged;
private final boolean playWhenReady;
private final boolean isPlayingChanged;

public PlaybackInfoUpdate(
PlaybackInfo playbackInfo,
PlaybackInfo previousPlaybackInfo,
CopyOnWriteArrayList<ListenerHolder> listeners,
TrackSelector trackSelector,
boolean positionDiscontinuity,
@Player.DiscontinuityReason int positionDiscontinuityReason,
@Player.TimelineChangeReason int timelineChangeReason,
@DiscontinuityReason int positionDiscontinuityReason,
@TimelineChangeReason int timelineChangeReason,
boolean seekProcessed,
boolean playWhenReady) {
boolean playWhenReady,
boolean isPlayingChanged) {
this.playbackInfo = playbackInfo;
this.listenerSnapshot = new CopyOnWriteArrayList<>(listeners);
this.trackSelector = trackSelector;
Expand All @@ -774,6 +802,7 @@ public PlaybackInfoUpdate(
this.timelineChangeReason = timelineChangeReason;
this.seekProcessed = seekProcessed;
this.playWhenReady = playWhenReady;
this.isPlayingChanged = isPlayingChanged;
playbackStateChanged = previousPlaybackInfo.playbackState != playbackInfo.playbackState;
timelineOrManifestChanged =
previousPlaybackInfo.timeline != playbackInfo.timeline
Expand Down Expand Up @@ -813,6 +842,12 @@ public void run() {
listenerSnapshot,
listener -> listener.onPlayerStateChanged(playWhenReady, playbackInfo.playbackState));
}
if (isPlayingChanged) {
invokeAll(
listenerSnapshot,
listener ->
listener.onIsPlayingChanged(playbackInfo.playbackState == Player.STATE_READY));
}
if (seekProcessed) {
invokeAll(listenerSnapshot, EventListener::onSeekProcessed);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,13 @@ default void onLoadingChanged(boolean isLoading) {}
*/
default void onPlayerStateChanged(boolean playWhenReady, int playbackState) {}

/**
* Called when the value of {@link #isPlaying()} changes.
*
* @param isPlaying Whether the player is playing.
*/
default void onIsPlayingChanged(boolean isPlaying) {}

/**
* Called when the value of {@link #getRepeatMode()} changes.
*
Expand Down Expand Up @@ -462,6 +469,20 @@ public void onTimelineChanged(Timeline timeline, @Nullable Object manifest) {
*/
int STATE_ENDED = 4;

/**
* Reason why playback is suppressed even if {@link #getPlaybackState()} is {@link #STATE_READY}
* and {@link #getPlayWhenReady()} is {@code true}. One of {@link
* #PLAYBACK_SUPPRESSION_REASON_NONE} or {@link #PLAYBACK_SUPPRESSION_REASON_AUDIO_FOCUS_LOSS}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({PLAYBACK_SUPPRESSION_REASON_NONE, PLAYBACK_SUPPRESSION_REASON_AUDIO_FOCUS_LOSS})
@interface PlaybackSuppressionReason {}
/** Playback is not suppressed. */
int PLAYBACK_SUPPRESSION_REASON_NONE = 0;
/** Playback is suppressed because audio focus is lost or can't be acquired. */
int PLAYBACK_SUPPRESSION_REASON_AUDIO_FOCUS_LOSS = 1;

/**
* Repeat modes for playback. One of {@link #REPEAT_MODE_OFF}, {@link #REPEAT_MODE_ONE} or {@link
* #REPEAT_MODE_ALL}.
Expand Down Expand Up @@ -587,12 +608,42 @@ public void onTimelineChanged(Timeline timeline, @Nullable Object manifest) {
*/
int getPlaybackState();

/**
* Returns reason why playback is suppressed even if {@link #getPlaybackState()} is {@link
* #STATE_READY} and {@link #getPlayWhenReady()} is {@code true}.
*
* <p>Note that {@link #PLAYBACK_SUPPRESSION_REASON_NONE} indicates that playback is not
* suppressed.
*
* @return The current {@link PlaybackSuppressionReason}.
*/
@PlaybackSuppressionReason
int getPlaybackSuppressionReason();

/**
* Returns whether the player is playing, i.e. {@link #getContentPosition()} is advancing.
*
* <p>If {@code false}, then at least one of the following is true:
*
* <ul>
* <li>The {@link #getPlaybackState() playback state} is not {@link #STATE_READY ready}.
* <li>There is no {@link #getPlayWhenReady() intention to play}.
* <li>Playback is {@link #getPlaybackSuppressionReason() suppressed for other reasons}.
* </ul>
*
* @return Whether the player is playing.
*/
boolean isPlaying();

/**
* Returns the error that caused playback to fail. This is the same error that will have been
* reported via {@link Player.EventListener#onPlayerError(ExoPlaybackException)} at the time of
* failure. It can be queried using this method until {@code stop(true)} is called or the player
* is re-prepared.
*
* <p>Note that this method will always return {@code null} if {@link #getPlaybackState()} is not
* {@link #STATE_IDLE}.
*
* @return The error, or {@code null}.
*/
@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -878,8 +878,15 @@ public int getPlaybackState() {
return player.getPlaybackState();
}

@PlaybackSuppressionReason
public int getPlaybackSuppressionReason() {
verifyApplicationThread();
return player.getPlaybackSuppressionReason();
}

@Override
public @Nullable ExoPlaybackException getPlaybackError() {
@Nullable
public ExoPlaybackException getPlaybackError() {
verifyApplicationThread();
return player.getPlaybackError();
}
Expand Down Expand Up @@ -1221,9 +1228,13 @@ private void sendVolumeToRenderers() {

private void updatePlayWhenReady(
boolean playWhenReady, @AudioFocusManager.PlayerCommand int playerCommand) {
int playbackSuppressionReason =
playerCommand == AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY
? Player.PLAYBACK_SUPPRESSION_REASON_NONE
: Player.PLAYBACK_SUPPRESSION_REASON_AUDIO_FOCUS_LOSS;
player.setPlayWhenReady(
playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY,
playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY);
playbackSuppressionReason);
}

private void verifyApplicationThread() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ public int getPlaybackState() {
throw new UnsupportedOperationException();
}

@Override
@PlaybackSuppressionReason
public int getPlaybackSuppressionReason() {
throw new UnsupportedOperationException();
}

@Override
public ExoPlaybackException getPlaybackError() {
throw new UnsupportedOperationException();
Expand Down

0 comments on commit f8d81d0

Please sign in to comment.