Skip to content

Commit

Permalink
Show play button during playback suppression by default
Browse files Browse the repository at this point in the history
This changes the default logic of shouldShowPlayButton to show a play
button while the playback is temporarily suppressed. This helps to
provide better UI feedback to the fact that playback stopped and
provides a quick way for users to override the suppression and attempt
to restart playback.

Some apps may want to keep the legacy behavior depending on their app's
needs. Hence, we also add a config parameter to set this behavior both
in MediaSession and our default UI components.

Issue: google/ExoPlayer#11213
PiperOrigin-RevId: 557129171
  • Loading branch information
tonihei authored and microkatz committed Sep 29, 2023
1 parent 276e6d3 commit 77101f0
Show file tree
Hide file tree
Showing 19 changed files with 637 additions and 72 deletions.
7 changes: 7 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@
and nullable array element types are not detected as nullable. Examples
are `TrackSelectorResult` and `SimpleDecoder` method parameters
([6792](https://github.com/google/ExoPlayer/issues/6792)).
* Change default UI and notification behavior in
`Util.shouldShowPlayButton` to show a "play" button while playback is
temporarily suppressed (e.g. due to transient audio focus loss). The
legacy behavior can be maintained by using
`PlayerView.setShowPlayButtonIfPlaybackIsSuppressed(false)` or
`MediaSession.Builder.setShowPlayButtonIfPlaybackIsSuppressed(false)`
([#11213](https://github.com/google/ExoPlayer/issues/11213)).
* ExoPlayer:
* Fix seeking issues in AC4 streams caused by not identifying decode-only
samples correctly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3171,23 +3171,42 @@ public static String intToStringMaxRadix(int i) {
* <p>Use {@link #handlePlayPauseButtonAction}, {@link #handlePlayButtonAction} or {@link
* #handlePauseButtonAction} to handle the interaction with the play or pause button UI element.
*
* @param player The {@link Player}. May be null.
* @param player The {@link Player}. May be {@code null}.
*/
@EnsuresNonNullIf(result = false, expression = "#1")
public static boolean shouldShowPlayButton(@Nullable Player player) {
return shouldShowPlayButton(player, /* playIfSuppressed= */ true);
}

/**
* Returns whether a play button should be presented on a UI element for playback control. If
* {@code false}, a pause button should be shown instead.
*
* <p>Use {@link #handlePlayPauseButtonAction}, {@link #handlePlayButtonAction} or {@link
* #handlePauseButtonAction} to handle the interaction with the play or pause button UI element.
*
* @param player The {@link Player}. May be {@code null}.
* @param playIfSuppressed Whether to show a play button if playback is {@linkplain
* Player#getPlaybackSuppressionReason() suppressed}.
*/
@UnstableApi
@EnsuresNonNullIf(result = false, expression = "#1")
public static boolean shouldShowPlayButton(@Nullable Player player, boolean playIfSuppressed) {
return player == null
|| !player.getPlayWhenReady()
|| player.getPlaybackState() == Player.STATE_IDLE
|| player.getPlaybackState() == Player.STATE_ENDED;
|| player.getPlaybackState() == Player.STATE_ENDED
|| (playIfSuppressed
&& player.getPlaybackSuppressionReason() != Player.PLAYBACK_SUPPRESSION_REASON_NONE);
}

/**
* Updates the player to handle an interaction with a play button.
*
* <p>This method assumes the play button is enabled if {@link #shouldShowPlayButton} returns
* true.
* {@code true}.
*
* @param player The {@link Player}. May be null.
* @param player The {@link Player}. May be {@code null}.
* @return Whether a player method was triggered to handle this action.
*/
public static boolean handlePlayButtonAction(@Nullable Player player) {
Expand Down Expand Up @@ -3215,9 +3234,9 @@ public static boolean handlePlayButtonAction(@Nullable Player player) {
* Updates the player to handle an interaction with a pause button.
*
* <p>This method assumes the pause button is enabled if {@link #shouldShowPlayButton} returns
* false.
* {@code false}.
*
* @param player The {@link Player}. May be null.
* @param player The {@link Player}. May be {@code null}.
* @return Whether a player method was triggered to handle this action.
*/
public static boolean handlePauseButtonAction(@Nullable Player player) {
Expand All @@ -3232,13 +3251,30 @@ public static boolean handlePauseButtonAction(@Nullable Player player) {
* Updates the player to handle an interaction with a play or pause button.
*
* <p>This method assumes that the UI element enables a play button if {@link
* #shouldShowPlayButton} returns true and a pause button otherwise.
* #shouldShowPlayButton} returns {@code true} and a pause button otherwise.
*
* @param player The {@link Player}. May be null.
* @param player The {@link Player}. May be {@code null}.
* @return Whether a player method was triggered to handle this action.
*/
public static boolean handlePlayPauseButtonAction(@Nullable Player player) {
if (shouldShowPlayButton(player)) {
return handlePlayPauseButtonAction(player, /* playIfSuppressed= */ true);
}

/**
* Updates the player to handle an interaction with a play or pause button.
*
* <p>This method assumes that the UI element enables a play button if {@link
* #shouldShowPlayButton(Player, boolean)} returns {@code true} and a pause button otherwise.
*
* @param player The {@link Player}. May be {@code null}.
* @param playIfSuppressed Whether to trigger a play action if playback is {@linkplain
* Player#getPlaybackSuppressionReason() suppressed}.
* @return Whether a player method was triggered to handle this action.
*/
@UnstableApi
public static boolean handlePlayPauseButtonAction(
@Nullable Player player, boolean playIfSuppressed) {
if (shouldShowPlayButton(player, playIfSuppressed)) {
return handlePlayButtonAction(player);
} else {
return handlePauseButtonAction(player);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS;
import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
import static androidx.media3.common.Player.COMMAND_STOP;
import static androidx.media3.common.Player.STATE_ENDED;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Assertions.checkStateNotNull;

Expand Down Expand Up @@ -320,8 +319,8 @@ public final MediaNotification createNotification(
mediaSession,
player.getAvailableCommands(),
customLayoutWithEnabledCommandButtonsOnly.build(),
/* showPauseButton= */ player.getPlayWhenReady()
&& player.getPlaybackState() != STATE_ENDED),
!Util.shouldShowPlayButton(
player, mediaSession.getShowPlayButtonIfPlaybackIsSuppressed())),
builder,
actionFactory);
mediaStyle.setShowActionsInCompactView(compactViewIndices);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,22 @@ public Builder setCustomLayout(List<CommandButton> customLayout) {
return super.setCustomLayout(customLayout);
}

/**
* Sets whether a play button is shown if playback is {@linkplain
* Player#getPlaybackSuppressionReason() suppressed}.
*
* <p>The default is {@code true}.
*
* @param showPlayButtonIfPlaybackIsSuppressed Whether to show a play button if playback is
* {@linkplain Player#getPlaybackSuppressionReason() suppressed}.
*/
@UnstableApi
@Override
public Builder setShowPlayButtonIfPlaybackIsSuppressed(
boolean showPlayButtonIfPlaybackIsSuppressed) {
return super.setShowPlayButtonIfPlaybackIsSuppressed(showPlayButtonIfPlaybackIsSuppressed);
}

/**
* Builds a {@link MediaLibrarySession}.
*
Expand All @@ -481,7 +497,8 @@ public MediaLibrarySession build() {
customLayout,
callback,
extras,
checkNotNull(bitmapLoader));
checkNotNull(bitmapLoader),
playIfSuppressed);
}
}

Expand All @@ -493,9 +510,18 @@ public MediaLibrarySession build() {
ImmutableList<CommandButton> customLayout,
MediaSession.Callback callback,
Bundle tokenExtras,
BitmapLoader bitmapLoader) {
BitmapLoader bitmapLoader,
boolean playIfSuppressed) {
super(
context, id, player, sessionActivity, customLayout, callback, tokenExtras, bitmapLoader);
context,
id,
player,
sessionActivity,
customLayout,
callback,
tokenExtras,
bitmapLoader,
playIfSuppressed);
}

@Override
Expand All @@ -507,7 +533,8 @@ public MediaLibrarySession build() {
ImmutableList<CommandButton> customLayout,
MediaSession.Callback callback,
Bundle tokenExtras,
BitmapLoader bitmapLoader) {
BitmapLoader bitmapLoader,
boolean playIfSuppressed) {
return new MediaLibrarySessionImpl(
this,
context,
Expand All @@ -517,7 +544,8 @@ public MediaLibrarySession build() {
customLayout,
(Callback) callback,
tokenExtras,
bitmapLoader);
bitmapLoader,
playIfSuppressed);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ public MediaLibrarySessionImpl(
ImmutableList<CommandButton> customLayout,
MediaLibrarySession.Callback callback,
Bundle tokenExtras,
BitmapLoader bitmapLoader) {
BitmapLoader bitmapLoader,
boolean playIfSuppressed) {
super(
instance,
context,
Expand All @@ -88,7 +89,8 @@ public MediaLibrarySessionImpl(
customLayout,
callback,
tokenExtras,
bitmapLoader);
bitmapLoader,
playIfSuppressed);
this.instance = instance;
this.callback = callback;
subscriptions = new ArrayMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,22 @@ public Builder setCustomLayout(List<CommandButton> customLayout) {
return super.setCustomLayout(customLayout);
}

/**
* Sets whether a play button is shown if playback is {@linkplain
* Player#getPlaybackSuppressionReason() suppressed}.
*
* <p>The default is {@code true}.
*
* @param showPlayButtonIfPlaybackIsSuppressed Whether to show a play button if playback is
* {@linkplain Player#getPlaybackSuppressionReason() suppressed}.
*/
@UnstableApi
@Override
public Builder setShowPlayButtonIfPlaybackIsSuppressed(
boolean showPlayButtonIfPlaybackIsSuppressed) {
return super.setShowPlayButtonIfPlaybackIsSuppressed(showPlayButtonIfPlaybackIsSuppressed);
}

/**
* Builds a {@link MediaSession}.
*
Expand All @@ -397,7 +413,8 @@ public MediaSession build() {
customLayout,
callback,
extras,
checkNotNull(bitmapLoader));
checkNotNull(bitmapLoader),
playIfSuppressed);
}
}

Expand Down Expand Up @@ -589,7 +606,8 @@ public static ControllerInfo createTestOnlyControllerInfo(
ImmutableList<CommandButton> customLayout,
Callback callback,
Bundle tokenExtras,
BitmapLoader bitmapLoader) {
BitmapLoader bitmapLoader,
boolean playIfSuppressed) {
synchronized (STATIC_LOCK) {
if (SESSION_ID_TO_SESSION_MAP.containsKey(id)) {
throw new IllegalStateException("Session ID must be unique. ID=" + id);
Expand All @@ -605,7 +623,8 @@ public static ControllerInfo createTestOnlyControllerInfo(
customLayout,
callback,
tokenExtras,
bitmapLoader);
bitmapLoader,
playIfSuppressed);
}

/* package */ MediaSessionImpl createImpl(
Expand All @@ -616,7 +635,8 @@ public static ControllerInfo createTestOnlyControllerInfo(
ImmutableList<CommandButton> customLayout,
Callback callback,
Bundle tokenExtras,
BitmapLoader bitmapLoader) {
BitmapLoader bitmapLoader,
boolean playIfSuppressed) {
return new MediaSessionImpl(
this,
context,
Expand All @@ -626,7 +646,8 @@ public static ControllerInfo createTestOnlyControllerInfo(
customLayout,
callback,
tokenExtras,
bitmapLoader);
bitmapLoader,
playIfSuppressed);
}

/* package */ MediaSessionImpl getImpl() {
Expand Down Expand Up @@ -935,6 +956,15 @@ public final BitmapLoader getBitmapLoader() {
return impl.getBitmapLoader();
}

/**
* Returns whether a play button is shown if playback is {@linkplain
* Player#getPlaybackSuppressionReason() suppressed}.
*/
@UnstableApi
public final boolean getShowPlayButtonIfPlaybackIsSuppressed() {
return impl.shouldPlayIfSuppressed();
}

/**
* Sends a custom command to a specific controller.
*
Expand Down Expand Up @@ -1740,6 +1770,7 @@ default void onRenderedFirstFrame(int seq) throws RemoteException {}
/* package */ @Nullable PendingIntent sessionActivity;
/* package */ Bundle extras;
/* package */ @MonotonicNonNull BitmapLoader bitmapLoader;
/* package */ boolean playIfSuppressed;

/* package */ ImmutableList<CommandButton> customLayout;

Expand All @@ -1751,6 +1782,7 @@ public BuilderBase(Context context, Player player, CallbackT callback) {
this.callback = callback;
extras = Bundle.EMPTY;
customLayout = ImmutableList.of();
playIfSuppressed = true;
}

@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -1789,6 +1821,13 @@ public BuilderT setCustomLayout(List<CommandButton> customLayout) {
return (BuilderT) this;
}

@SuppressWarnings("unchecked")
public BuilderT setShowPlayButtonIfPlaybackIsSuppressed(
boolean showPlayButtonIfPlaybackIsSuppressed) {
this.playIfSuppressed = showPlayButtonIfPlaybackIsSuppressed;
return (BuilderT) this;
}

public abstract SessionT build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
private final BitmapLoader bitmapLoader;
private final Runnable periodicSessionPositionInfoUpdateRunnable;
private final Handler mainHandler;
private final boolean playIfSuppressed;

private PlayerInfo playerInfo;
private PlayerWrapper playerWrapper;
Expand Down Expand Up @@ -140,7 +141,8 @@ public MediaSessionImpl(
ImmutableList<CommandButton> customLayout,
MediaSession.Callback callback,
Bundle tokenExtras,
BitmapLoader bitmapLoader) {
BitmapLoader bitmapLoader,
boolean playIfSuppressed) {
this.context = context;
this.instance = instance;

Expand All @@ -156,6 +158,7 @@ public MediaSessionImpl(
applicationHandler = new Handler(player.getApplicationLooper());
this.callback = callback;
this.bitmapLoader = bitmapLoader;
this.playIfSuppressed = playIfSuppressed;

playerInfo = PlayerInfo.DEFAULT;
onPlayerInfoChangedHandler = new PlayerInfoChangedHandler(player.getApplicationLooper());
Expand Down Expand Up @@ -189,7 +192,7 @@ public MediaSessionImpl(
sessionLegacyStub =
new MediaSessionLegacyStub(/* session= */ thisRef, sessionUri, applicationHandler);

PlayerWrapper playerWrapper = new PlayerWrapper(player);
PlayerWrapper playerWrapper = new PlayerWrapper(player, playIfSuppressed);
this.playerWrapper = playerWrapper;
this.playerWrapper.setCustomLayout(customLayout);
postOrRun(
Expand All @@ -208,7 +211,8 @@ public void setPlayer(Player player) {
if (player == playerWrapper.getWrappedPlayer()) {
return;
}
setPlayerInternal(/* oldPlayerWrapper= */ playerWrapper, new PlayerWrapper(player));
setPlayerInternal(
/* oldPlayerWrapper= */ playerWrapper, new PlayerWrapper(player, playIfSuppressed));
}

private void setPlayerInternal(
Expand Down Expand Up @@ -397,6 +401,10 @@ public BitmapLoader getBitmapLoader() {
return bitmapLoader;
}

public boolean shouldPlayIfSuppressed() {
return playIfSuppressed;
}

public void setAvailableCommands(
ControllerInfo controller, SessionCommands sessionCommands, Player.Commands playerCommands) {
if (sessionStub.getConnectedControllersManager().isConnected(controller)) {
Expand Down
Loading

0 comments on commit 77101f0

Please sign in to comment.