mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Add Player.isPlaying and Player.getPlaybackSuppressionReason
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
This commit is contained in:
parent
9557868c18
commit
f8d81d05a4
7 changed files with 144 additions and 17 deletions
|
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
### 2.10.5 ###
|
### 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
|
* Track selection
|
||||||
* Add `allowAudioMixedChannelCountAdaptiveness` parameter to
|
* Add `allowAudioMixedChannelCountAdaptiveness` parameter to
|
||||||
`DefaultTrackSelector` to allow adaptive selections of audio tracks with
|
`DefaultTrackSelector` to allow adaptive selections of audio tracks with
|
||||||
|
|
|
||||||
|
|
@ -327,6 +327,12 @@ public final class CastPlayer extends BasePlayer {
|
||||||
return playbackState;
|
return playbackState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@PlaybackSuppressionReason
|
||||||
|
public int getPlaybackSuppressionReason() {
|
||||||
|
return Player.PLAYBACK_SUPPRESSION_REASON_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public ExoPlaybackException getPlaybackError() {
|
public ExoPlaybackException getPlaybackError() {
|
||||||
|
|
@ -542,6 +548,7 @@ public final class CastPlayer extends BasePlayer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean wasPlaying = playbackState == Player.STATE_READY && playWhenReady;
|
||||||
int playbackState = fetchPlaybackState(remoteMediaClient);
|
int playbackState = fetchPlaybackState(remoteMediaClient);
|
||||||
boolean playWhenReady = !remoteMediaClient.isPaused();
|
boolean playWhenReady = !remoteMediaClient.isPaused();
|
||||||
if (this.playbackState != playbackState
|
if (this.playbackState != playbackState
|
||||||
|
|
@ -552,6 +559,11 @@ public final class CastPlayer extends BasePlayer {
|
||||||
new ListenerNotificationTask(
|
new ListenerNotificationTask(
|
||||||
listener -> listener.onPlayerStateChanged(this.playWhenReady, this.playbackState)));
|
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);
|
@RepeatMode int repeatMode = fetchRepeatMode(remoteMediaClient);
|
||||||
if (this.repeatMode != repeatMode) {
|
if (this.repeatMode != repeatMode) {
|
||||||
this.repeatMode = repeatMode;
|
this.repeatMode = repeatMode;
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,13 @@ public abstract class BasePlayer implements Player {
|
||||||
window = new Timeline.Window();
|
window = new Timeline.Window();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean isPlaying() {
|
||||||
|
return getPlaybackState() == Player.STATE_READY
|
||||||
|
&& getPlayWhenReady()
|
||||||
|
&& getPlaybackSuppressionReason() == PLAYBACK_SUPPRESSION_REASON_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void seekToDefaultPosition() {
|
public final void seekToDefaultPosition() {
|
||||||
seekToDefaultPosition(getCurrentWindowIndex());
|
seekToDefaultPosition(getCurrentWindowIndex());
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
private MediaSource mediaSource;
|
private MediaSource mediaSource;
|
||||||
private boolean playWhenReady;
|
private boolean playWhenReady;
|
||||||
private boolean internalPlayWhenReady;
|
@PlaybackSuppressionReason private int playbackSuppressionReason;
|
||||||
private @RepeatMode int repeatMode;
|
@RepeatMode private int repeatMode;
|
||||||
private boolean shuffleModeEnabled;
|
private boolean shuffleModeEnabled;
|
||||||
private int pendingOperationAcks;
|
private int pendingOperationAcks;
|
||||||
private boolean hasPendingPrepare;
|
private boolean hasPendingPrepare;
|
||||||
|
|
@ -119,6 +119,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
period = new Timeline.Period();
|
period = new Timeline.Period();
|
||||||
playbackParameters = PlaybackParameters.DEFAULT;
|
playbackParameters = PlaybackParameters.DEFAULT;
|
||||||
seekParameters = SeekParameters.DEFAULT;
|
seekParameters = SeekParameters.DEFAULT;
|
||||||
|
playbackSuppressionReason = PLAYBACK_SUPPRESSION_REASON_NONE;
|
||||||
eventHandler =
|
eventHandler =
|
||||||
new Handler(looper) {
|
new Handler(looper) {
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -197,8 +198,14 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
return playbackInfo.playbackState;
|
return playbackInfo.playbackState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PlaybackSuppressionReason
|
||||||
|
public int getPlaybackSuppressionReason() {
|
||||||
|
return playbackSuppressionReason;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable ExoPlaybackException getPlaybackError() {
|
@Nullable
|
||||||
|
public ExoPlaybackException getPlaybackError() {
|
||||||
return playbackError;
|
return playbackError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,19 +246,35 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlayWhenReady(boolean playWhenReady) {
|
public void setPlayWhenReady(boolean playWhenReady) {
|
||||||
setPlayWhenReady(playWhenReady, /* suppressPlayback= */ false);
|
setPlayWhenReady(playWhenReady, PLAYBACK_SUPPRESSION_REASON_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPlayWhenReady(boolean playWhenReady, boolean suppressPlayback) {
|
public void setPlayWhenReady(
|
||||||
boolean internalPlayWhenReady = playWhenReady && !suppressPlayback;
|
boolean playWhenReady, @PlaybackSuppressionReason int playbackSuppressionReason) {
|
||||||
if (this.internalPlayWhenReady != internalPlayWhenReady) {
|
boolean oldIsPlaying = isPlaying();
|
||||||
this.internalPlayWhenReady = internalPlayWhenReady;
|
boolean oldInternalPlayWhenReady =
|
||||||
|
this.playWhenReady && this.playbackSuppressionReason == PLAYBACK_SUPPRESSION_REASON_NONE;
|
||||||
|
boolean internalPlayWhenReady =
|
||||||
|
playWhenReady && playbackSuppressionReason == PLAYBACK_SUPPRESSION_REASON_NONE;
|
||||||
|
if (oldInternalPlayWhenReady != internalPlayWhenReady) {
|
||||||
internalPlayer.setPlayWhenReady(internalPlayWhenReady);
|
internalPlayer.setPlayWhenReady(internalPlayWhenReady);
|
||||||
}
|
}
|
||||||
if (this.playWhenReady != playWhenReady) {
|
boolean playWhenReadyChanged = this.playWhenReady != playWhenReady;
|
||||||
this.playWhenReady = playWhenReady;
|
this.playWhenReady = playWhenReady;
|
||||||
|
this.playbackSuppressionReason = playbackSuppressionReason;
|
||||||
|
boolean isPlaying = isPlaying();
|
||||||
|
boolean isPlayingChanged = oldIsPlaying != isPlaying;
|
||||||
|
if (playWhenReadyChanged || isPlayingChanged) {
|
||||||
int playbackState = playbackInfo.playbackState;
|
int playbackState = playbackInfo.playbackState;
|
||||||
notifyListeners(listener -> listener.onPlayerStateChanged(playWhenReady, playbackState));
|
notifyListeners(
|
||||||
|
listener -> {
|
||||||
|
if (playWhenReadyChanged) {
|
||||||
|
listener.onPlayerStateChanged(playWhenReady, playbackState);
|
||||||
|
}
|
||||||
|
if (isPlayingChanged) {
|
||||||
|
listener.onIsPlayingChanged(isPlaying);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -697,9 +720,11 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
@Player.DiscontinuityReason int positionDiscontinuityReason,
|
@Player.DiscontinuityReason int positionDiscontinuityReason,
|
||||||
@Player.TimelineChangeReason int timelineChangeReason,
|
@Player.TimelineChangeReason int timelineChangeReason,
|
||||||
boolean seekProcessed) {
|
boolean seekProcessed) {
|
||||||
|
boolean previousIsPlaying = isPlaying();
|
||||||
// Assign playback info immediately such that all getters return the right values.
|
// Assign playback info immediately such that all getters return the right values.
|
||||||
PlaybackInfo previousPlaybackInfo = this.playbackInfo;
|
PlaybackInfo previousPlaybackInfo = this.playbackInfo;
|
||||||
this.playbackInfo = playbackInfo;
|
this.playbackInfo = playbackInfo;
|
||||||
|
boolean isPlaying = isPlaying();
|
||||||
notifyListeners(
|
notifyListeners(
|
||||||
new PlaybackInfoUpdate(
|
new PlaybackInfoUpdate(
|
||||||
playbackInfo,
|
playbackInfo,
|
||||||
|
|
@ -710,7 +735,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
positionDiscontinuityReason,
|
positionDiscontinuityReason,
|
||||||
timelineChangeReason,
|
timelineChangeReason,
|
||||||
seekProcessed,
|
seekProcessed,
|
||||||
playWhenReady));
|
playWhenReady,
|
||||||
|
/* isPlayingChanged= */ previousIsPlaying != isPlaying));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyListeners(ListenerInvocation listenerInvocation) {
|
private void notifyListeners(ListenerInvocation listenerInvocation) {
|
||||||
|
|
@ -755,6 +781,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
private final boolean isLoadingChanged;
|
private final boolean isLoadingChanged;
|
||||||
private final boolean trackSelectorResultChanged;
|
private final boolean trackSelectorResultChanged;
|
||||||
private final boolean playWhenReady;
|
private final boolean playWhenReady;
|
||||||
|
private final boolean isPlayingChanged;
|
||||||
|
|
||||||
public PlaybackInfoUpdate(
|
public PlaybackInfoUpdate(
|
||||||
PlaybackInfo playbackInfo,
|
PlaybackInfo playbackInfo,
|
||||||
|
|
@ -762,10 +789,11 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
CopyOnWriteArrayList<ListenerHolder> listeners,
|
CopyOnWriteArrayList<ListenerHolder> listeners,
|
||||||
TrackSelector trackSelector,
|
TrackSelector trackSelector,
|
||||||
boolean positionDiscontinuity,
|
boolean positionDiscontinuity,
|
||||||
@Player.DiscontinuityReason int positionDiscontinuityReason,
|
@DiscontinuityReason int positionDiscontinuityReason,
|
||||||
@Player.TimelineChangeReason int timelineChangeReason,
|
@TimelineChangeReason int timelineChangeReason,
|
||||||
boolean seekProcessed,
|
boolean seekProcessed,
|
||||||
boolean playWhenReady) {
|
boolean playWhenReady,
|
||||||
|
boolean isPlayingChanged) {
|
||||||
this.playbackInfo = playbackInfo;
|
this.playbackInfo = playbackInfo;
|
||||||
this.listenerSnapshot = new CopyOnWriteArrayList<>(listeners);
|
this.listenerSnapshot = new CopyOnWriteArrayList<>(listeners);
|
||||||
this.trackSelector = trackSelector;
|
this.trackSelector = trackSelector;
|
||||||
|
|
@ -774,6 +802,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
this.timelineChangeReason = timelineChangeReason;
|
this.timelineChangeReason = timelineChangeReason;
|
||||||
this.seekProcessed = seekProcessed;
|
this.seekProcessed = seekProcessed;
|
||||||
this.playWhenReady = playWhenReady;
|
this.playWhenReady = playWhenReady;
|
||||||
|
this.isPlayingChanged = isPlayingChanged;
|
||||||
playbackStateChanged = previousPlaybackInfo.playbackState != playbackInfo.playbackState;
|
playbackStateChanged = previousPlaybackInfo.playbackState != playbackInfo.playbackState;
|
||||||
timelineOrManifestChanged =
|
timelineOrManifestChanged =
|
||||||
previousPlaybackInfo.timeline != playbackInfo.timeline
|
previousPlaybackInfo.timeline != playbackInfo.timeline
|
||||||
|
|
@ -813,6 +842,12 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
listenerSnapshot,
|
listenerSnapshot,
|
||||||
listener -> listener.onPlayerStateChanged(playWhenReady, playbackInfo.playbackState));
|
listener -> listener.onPlayerStateChanged(playWhenReady, playbackInfo.playbackState));
|
||||||
}
|
}
|
||||||
|
if (isPlayingChanged) {
|
||||||
|
invokeAll(
|
||||||
|
listenerSnapshot,
|
||||||
|
listener ->
|
||||||
|
listener.onIsPlayingChanged(playbackInfo.playbackState == Player.STATE_READY));
|
||||||
|
}
|
||||||
if (seekProcessed) {
|
if (seekProcessed) {
|
||||||
invokeAll(listenerSnapshot, EventListener::onSeekProcessed);
|
invokeAll(listenerSnapshot, EventListener::onSeekProcessed);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -365,6 +365,13 @@ public interface Player {
|
||||||
*/
|
*/
|
||||||
default void onPlayerStateChanged(boolean playWhenReady, int playbackState) {}
|
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.
|
* Called when the value of {@link #getRepeatMode()} changes.
|
||||||
*
|
*
|
||||||
|
|
@ -462,6 +469,20 @@ public interface Player {
|
||||||
*/
|
*/
|
||||||
int STATE_ENDED = 4;
|
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 modes for playback. One of {@link #REPEAT_MODE_OFF}, {@link #REPEAT_MODE_ONE} or {@link
|
||||||
* #REPEAT_MODE_ALL}.
|
* #REPEAT_MODE_ALL}.
|
||||||
|
|
@ -587,12 +608,42 @@ public interface Player {
|
||||||
*/
|
*/
|
||||||
int getPlaybackState();
|
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
|
* 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
|
* 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
|
* failure. It can be queried using this method until {@code stop(true)} is called or the player
|
||||||
* is re-prepared.
|
* 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}.
|
* @return The error, or {@code null}.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|
|
||||||
|
|
@ -878,8 +878,15 @@ public class SimpleExoPlayer extends BasePlayer
|
||||||
return player.getPlaybackState();
|
return player.getPlaybackState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PlaybackSuppressionReason
|
||||||
|
public int getPlaybackSuppressionReason() {
|
||||||
|
verifyApplicationThread();
|
||||||
|
return player.getPlaybackSuppressionReason();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable ExoPlaybackException getPlaybackError() {
|
@Nullable
|
||||||
|
public ExoPlaybackException getPlaybackError() {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
return player.getPlaybackError();
|
return player.getPlaybackError();
|
||||||
}
|
}
|
||||||
|
|
@ -1221,9 +1228,13 @@ public class SimpleExoPlayer extends BasePlayer
|
||||||
|
|
||||||
private void updatePlayWhenReady(
|
private void updatePlayWhenReady(
|
||||||
boolean playWhenReady, @AudioFocusManager.PlayerCommand int playerCommand) {
|
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(
|
player.setPlayWhenReady(
|
||||||
playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY,
|
playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY,
|
||||||
playerCommand != AudioFocusManager.PLAYER_COMMAND_PLAY_WHEN_READY);
|
playbackSuppressionReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyApplicationThread() {
|
private void verifyApplicationThread() {
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,12 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@PlaybackSuppressionReason
|
||||||
|
public int getPlaybackSuppressionReason() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ExoPlaybackException getPlaybackError() {
|
public ExoPlaybackException getPlaybackError() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue