diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 2baa657f2fa..6adcd72bc20 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -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
diff --git a/libraries/common/src/main/java/androidx/media3/common/util/Util.java b/libraries/common/src/main/java/androidx/media3/common/util/Util.java
index dcd7b2e8ad5..08dd27056ee 100644
--- a/libraries/common/src/main/java/androidx/media3/common/util/Util.java
+++ b/libraries/common/src/main/java/androidx/media3/common/util/Util.java
@@ -3171,23 +3171,42 @@ public static String intToStringMaxRadix(int i) {
*
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.
+ *
+ *
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.
*
*
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) {
@@ -3215,9 +3234,9 @@ public static boolean handlePlayButtonAction(@Nullable Player player) {
* Updates the player to handle an interaction with a pause button.
*
*
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) {
@@ -3232,13 +3251,30 @@ public static boolean handlePauseButtonAction(@Nullable Player player) {
* Updates the player to handle an interaction with a play or pause button.
*
*
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.
+ *
+ *
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);
diff --git a/libraries/session/src/main/java/androidx/media3/session/DefaultMediaNotificationProvider.java b/libraries/session/src/main/java/androidx/media3/session/DefaultMediaNotificationProvider.java
index 8fa6c236126..b9f7b3debb7 100644
--- a/libraries/session/src/main/java/androidx/media3/session/DefaultMediaNotificationProvider.java
+++ b/libraries/session/src/main/java/androidx/media3/session/DefaultMediaNotificationProvider.java
@@ -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;
@@ -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);
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaLibraryService.java b/libraries/session/src/main/java/androidx/media3/session/MediaLibraryService.java
index e393330ec67..2f6d536cdca 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaLibraryService.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaLibraryService.java
@@ -461,6 +461,22 @@ public Builder setCustomLayout(List customLayout) {
return super.setCustomLayout(customLayout);
}
+ /**
+ * Sets whether a play button is shown if playback is {@linkplain
+ * Player#getPlaybackSuppressionReason() suppressed}.
+ *
+ * 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}.
*
@@ -481,7 +497,8 @@ public MediaLibrarySession build() {
customLayout,
callback,
extras,
- checkNotNull(bitmapLoader));
+ checkNotNull(bitmapLoader),
+ playIfSuppressed);
}
}
@@ -493,9 +510,18 @@ public MediaLibrarySession build() {
ImmutableList 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
@@ -507,7 +533,8 @@ public MediaLibrarySession build() {
ImmutableList customLayout,
MediaSession.Callback callback,
Bundle tokenExtras,
- BitmapLoader bitmapLoader) {
+ BitmapLoader bitmapLoader,
+ boolean playIfSuppressed) {
return new MediaLibrarySessionImpl(
this,
context,
@@ -517,7 +544,8 @@ public MediaLibrarySession build() {
customLayout,
(Callback) callback,
tokenExtras,
- bitmapLoader);
+ bitmapLoader,
+ playIfSuppressed);
}
@Override
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaLibrarySessionImpl.java b/libraries/session/src/main/java/androidx/media3/session/MediaLibrarySessionImpl.java
index c31bc3737ff..5dc6271b354 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaLibrarySessionImpl.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaLibrarySessionImpl.java
@@ -78,7 +78,8 @@ public MediaLibrarySessionImpl(
ImmutableList customLayout,
MediaLibrarySession.Callback callback,
Bundle tokenExtras,
- BitmapLoader bitmapLoader) {
+ BitmapLoader bitmapLoader,
+ boolean playIfSuppressed) {
super(
instance,
context,
@@ -88,7 +89,8 @@ public MediaLibrarySessionImpl(
customLayout,
callback,
tokenExtras,
- bitmapLoader);
+ bitmapLoader,
+ playIfSuppressed);
this.instance = instance;
this.callback = callback;
subscriptions = new ArrayMap<>();
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSession.java b/libraries/session/src/main/java/androidx/media3/session/MediaSession.java
index c1f0a7507bc..ad5ad4e8bec 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaSession.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaSession.java
@@ -377,6 +377,22 @@ public Builder setCustomLayout(List customLayout) {
return super.setCustomLayout(customLayout);
}
+ /**
+ * Sets whether a play button is shown if playback is {@linkplain
+ * Player#getPlaybackSuppressionReason() suppressed}.
+ *
+ * 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}.
*
@@ -397,7 +413,8 @@ public MediaSession build() {
customLayout,
callback,
extras,
- checkNotNull(bitmapLoader));
+ checkNotNull(bitmapLoader),
+ playIfSuppressed);
}
}
@@ -589,7 +606,8 @@ public static ControllerInfo createTestOnlyControllerInfo(
ImmutableList 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);
@@ -605,7 +623,8 @@ public static ControllerInfo createTestOnlyControllerInfo(
customLayout,
callback,
tokenExtras,
- bitmapLoader);
+ bitmapLoader,
+ playIfSuppressed);
}
/* package */ MediaSessionImpl createImpl(
@@ -616,7 +635,8 @@ public static ControllerInfo createTestOnlyControllerInfo(
ImmutableList customLayout,
Callback callback,
Bundle tokenExtras,
- BitmapLoader bitmapLoader) {
+ BitmapLoader bitmapLoader,
+ boolean playIfSuppressed) {
return new MediaSessionImpl(
this,
context,
@@ -626,7 +646,8 @@ public static ControllerInfo createTestOnlyControllerInfo(
customLayout,
callback,
tokenExtras,
- bitmapLoader);
+ bitmapLoader,
+ playIfSuppressed);
}
/* package */ MediaSessionImpl getImpl() {
@@ -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.
*
@@ -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 customLayout;
@@ -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")
@@ -1789,6 +1821,13 @@ public BuilderT setCustomLayout(List customLayout) {
return (BuilderT) this;
}
+ @SuppressWarnings("unchecked")
+ public BuilderT setShowPlayButtonIfPlaybackIsSuppressed(
+ boolean showPlayButtonIfPlaybackIsSuppressed) {
+ this.playIfSuppressed = showPlayButtonIfPlaybackIsSuppressed;
+ return (BuilderT) this;
+ }
+
public abstract SessionT build();
}
}
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java
index b100682b24e..41fb3126afb 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java
@@ -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;
@@ -140,7 +141,8 @@ public MediaSessionImpl(
ImmutableList customLayout,
MediaSession.Callback callback,
Bundle tokenExtras,
- BitmapLoader bitmapLoader) {
+ BitmapLoader bitmapLoader,
+ boolean playIfSuppressed) {
this.context = context;
this.instance = instance;
@@ -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());
@@ -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(
@@ -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(
@@ -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)) {
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java
index 8a56dc6b0d5..887b36715b7 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java
@@ -346,7 +346,9 @@ private void handleMediaPlayPauseOnHandler(RemoteUserInfo remoteUserInfo) {
mediaPlayPauseKeyHandler.clearPendingMediaPlayPauseKey();
dispatchSessionTaskWithPlayerCommand(
COMMAND_PLAY_PAUSE,
- controller -> Util.handlePlayPauseButtonAction(sessionImpl.getPlayerWrapper()),
+ controller ->
+ Util.handlePlayPauseButtonAction(
+ sessionImpl.getPlayerWrapper(), sessionImpl.shouldPlayIfSuppressed()),
remoteUserInfo);
}
diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java b/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java
index 8cb439a0c4d..3959820cbcf 100644
--- a/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java
+++ b/libraries/session/src/main/java/androidx/media3/session/MediaUtils.java
@@ -759,24 +759,25 @@ public static RatingCompat convertToRatingCompat(@Nullable Rating rating) {
/** Converts {@link Player}' states to state of {@link PlaybackStateCompat}. */
@PlaybackStateCompat.State
- public static int convertToPlaybackStateCompatState(
- @Nullable PlaybackException playerError,
- @Player.State int playbackState,
- boolean playWhenReady) {
- if (playerError != null) {
+ public static int convertToPlaybackStateCompatState(Player player, boolean playIfSuppressed) {
+ if (player.getPlayerError() != null) {
return PlaybackStateCompat.STATE_ERROR;
}
+ @Player.State int playbackState = player.getPlaybackState();
+ boolean shouldShowPlayButton = Util.shouldShowPlayButton(player, playIfSuppressed);
switch (playbackState) {
case Player.STATE_IDLE:
return PlaybackStateCompat.STATE_NONE;
case Player.STATE_READY:
- return playWhenReady ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED;
+ return shouldShowPlayButton
+ ? PlaybackStateCompat.STATE_PAUSED
+ : PlaybackStateCompat.STATE_PLAYING;
case Player.STATE_ENDED:
return PlaybackStateCompat.STATE_STOPPED;
case Player.STATE_BUFFERING:
- return playWhenReady
- ? PlaybackStateCompat.STATE_BUFFERING
- : PlaybackStateCompat.STATE_PAUSED;
+ return shouldShowPlayButton
+ ? PlaybackStateCompat.STATE_PAUSED
+ : PlaybackStateCompat.STATE_BUFFERING;
default:
throw new IllegalArgumentException("Unrecognized State: " + playbackState);
}
diff --git a/libraries/session/src/main/java/androidx/media3/session/PlayerWrapper.java b/libraries/session/src/main/java/androidx/media3/session/PlayerWrapper.java
index 24c7cb019c6..a2b40c48591 100644
--- a/libraries/session/src/main/java/androidx/media3/session/PlayerWrapper.java
+++ b/libraries/session/src/main/java/androidx/media3/session/PlayerWrapper.java
@@ -64,13 +64,16 @@
private static final int STATUS_CODE_SUCCESS_COMPAT = -1;
+ private final boolean playIfSuppressed;
+
private int legacyStatusCode;
@Nullable private String legacyErrorMessage;
@Nullable private Bundle legacyErrorExtras;
private ImmutableList customLayout;
- public PlayerWrapper(Player player) {
+ public PlayerWrapper(Player player, boolean playIfSuppressed) {
super(player);
+ this.playIfSuppressed = playIfSuppressed;
legacyStatusCode = STATUS_CODE_SUCCESS_COMPAT;
customLayout = ImmutableList.of();
}
@@ -968,9 +971,7 @@ public PlaybackStateCompat createPlaybackStateCompat() {
.build();
}
@Nullable PlaybackException playerError = getPlayerError();
- int state =
- MediaUtils.convertToPlaybackStateCompatState(
- playerError, getPlaybackState(), getPlayWhenReady());
+ int state = MediaUtils.convertToPlaybackStateCompatState(/* player= */ this, playIfSuppressed);
// Always advertise ACTION_SET_RATING.
long actions = PlaybackStateCompat.ACTION_SET_RATING;
Commands availableCommands = getAvailableCommands();
diff --git a/libraries/session/src/test/java/androidx/media3/session/DefaultMediaNotificationProviderTest.java b/libraries/session/src/test/java/androidx/media3/session/DefaultMediaNotificationProviderTest.java
index e89365818aa..8b0a6177d1a 100644
--- a/libraries/session/src/test/java/androidx/media3/session/DefaultMediaNotificationProviderTest.java
+++ b/libraries/session/src/test/java/androidx/media3/session/DefaultMediaNotificationProviderTest.java
@@ -43,11 +43,14 @@
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.Player;
import androidx.media3.common.Player.Commands;
+import androidx.media3.common.SimpleBasePlayer;
import androidx.media3.common.util.BitmapLoader;
import androidx.media3.test.utils.TestExoPlayerBuilder;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import java.util.ArrayList;
import java.util.List;
@@ -663,6 +666,320 @@ protected ImmutableList getMediaButtons(
mediaSession.release();
}
+ @Test
+ public void
+ createNotification_withStateReadyAndPlayWhenReadyTrueAndNoSuppression_showsPauseButton() {
+ Player player =
+ createPlayerWithFixedState(
+ Player.STATE_READY, /* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
+ DefaultActionFactory defaultActionFactory =
+ new DefaultActionFactory(Robolectric.setupService(TestService.class));
+ MediaSession mediaSession = new MediaSession.Builder(context, player).build();
+ DefaultMediaNotificationProvider defaultMediaNotificationProvider =
+ new DefaultMediaNotificationProvider.Builder(ApplicationProvider.getApplicationContext())
+ .build();
+
+ MediaNotification mediaNotification =
+ defaultMediaNotificationProvider.createNotification(
+ mediaSession,
+ /* customLayout= */ ImmutableList.of(),
+ defaultActionFactory,
+ notification -> {});
+ mediaSession.release();
+
+ assertThat(mediaNotification.notification.actions[0].title.toString())
+ .isEqualTo(context.getString(R.string.media3_controls_pause_description));
+ }
+
+ @Test
+ public void
+ createNotification_withStateReadyAndPlayWhenReadyTrueAndPlaybackSuppression_showsPlayButton() {
+ Player player =
+ createPlayerWithFixedState(
+ Player.STATE_READY,
+ /* playWhenReady= */ true,
+ Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
+ DefaultActionFactory defaultActionFactory =
+ new DefaultActionFactory(Robolectric.setupService(TestService.class));
+ MediaSession mediaSession = new MediaSession.Builder(context, player).build();
+ DefaultMediaNotificationProvider defaultMediaNotificationProvider =
+ new DefaultMediaNotificationProvider.Builder(ApplicationProvider.getApplicationContext())
+ .build();
+
+ MediaNotification mediaNotification =
+ defaultMediaNotificationProvider.createNotification(
+ mediaSession,
+ /* customLayout= */ ImmutableList.of(),
+ defaultActionFactory,
+ notification -> {});
+ mediaSession.release();
+
+ assertThat(mediaNotification.notification.actions[0].title.toString())
+ .isEqualTo(context.getString(R.string.media3_controls_play_description));
+ }
+
+ @Test
+ public void
+ createNotification_withStateReadyAndPlayWhenReadyTrueAndPlaybackSuppressionWithoutShowPauseIfSuppressed_showsPauseButton() {
+ Player player =
+ createPlayerWithFixedState(
+ Player.STATE_READY,
+ /* playWhenReady= */ true,
+ Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
+ DefaultActionFactory defaultActionFactory =
+ new DefaultActionFactory(Robolectric.setupService(TestService.class));
+ MediaSession mediaSession =
+ new MediaSession.Builder(context, player)
+ .setShowPlayButtonIfPlaybackIsSuppressed(false)
+ .build();
+ DefaultMediaNotificationProvider defaultMediaNotificationProvider =
+ new DefaultMediaNotificationProvider.Builder(ApplicationProvider.getApplicationContext())
+ .build();
+
+ MediaNotification mediaNotification =
+ defaultMediaNotificationProvider.createNotification(
+ mediaSession,
+ /* customLayout= */ ImmutableList.of(),
+ defaultActionFactory,
+ notification -> {});
+ mediaSession.release();
+
+ assertThat(mediaNotification.notification.actions[0].title.toString())
+ .isEqualTo(context.getString(R.string.media3_controls_pause_description));
+ }
+
+ @Test
+ public void
+ createNotification_withStateBufferingAndPlayWhenReadyTrueAndNoSuppression_showsPauseButton() {
+ Player player =
+ createPlayerWithFixedState(
+ Player.STATE_BUFFERING,
+ /* playWhenReady= */ true,
+ Player.PLAYBACK_SUPPRESSION_REASON_NONE);
+ DefaultActionFactory defaultActionFactory =
+ new DefaultActionFactory(Robolectric.setupService(TestService.class));
+ MediaSession mediaSession = new MediaSession.Builder(context, player).build();
+ DefaultMediaNotificationProvider defaultMediaNotificationProvider =
+ new DefaultMediaNotificationProvider.Builder(ApplicationProvider.getApplicationContext())
+ .build();
+
+ MediaNotification mediaNotification =
+ defaultMediaNotificationProvider.createNotification(
+ mediaSession,
+ /* customLayout= */ ImmutableList.of(),
+ defaultActionFactory,
+ notification -> {});
+ mediaSession.release();
+
+ assertThat(mediaNotification.notification.actions[0].title.toString())
+ .isEqualTo(context.getString(R.string.media3_controls_pause_description));
+ }
+
+ @Test
+ public void
+ createNotification_withStateBufferingAndPlayWhenReadyTrueAndPlaybackSuppression_showsPlayButton() {
+ Player player =
+ createPlayerWithFixedState(
+ Player.STATE_BUFFERING,
+ /* playWhenReady= */ true,
+ Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
+ DefaultActionFactory defaultActionFactory =
+ new DefaultActionFactory(Robolectric.setupService(TestService.class));
+ MediaSession mediaSession = new MediaSession.Builder(context, player).build();
+ DefaultMediaNotificationProvider defaultMediaNotificationProvider =
+ new DefaultMediaNotificationProvider.Builder(ApplicationProvider.getApplicationContext())
+ .build();
+
+ MediaNotification mediaNotification =
+ defaultMediaNotificationProvider.createNotification(
+ mediaSession,
+ /* customLayout= */ ImmutableList.of(),
+ defaultActionFactory,
+ notification -> {});
+ mediaSession.release();
+
+ assertThat(mediaNotification.notification.actions[0].title.toString())
+ .isEqualTo(context.getString(R.string.media3_controls_play_description));
+ }
+
+ @Test
+ public void
+ createNotification_withStateBufferingAndPlayWhenReadyTrueAndPlaybackSuppressionWithoutShowPauseIfSuppressed_showsPauseButton() {
+ Player player =
+ createPlayerWithFixedState(
+ Player.STATE_BUFFERING,
+ /* playWhenReady= */ true,
+ Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
+ DefaultActionFactory defaultActionFactory =
+ new DefaultActionFactory(Robolectric.setupService(TestService.class));
+ MediaSession mediaSession =
+ new MediaSession.Builder(context, player)
+ .setShowPlayButtonIfPlaybackIsSuppressed(false)
+ .build();
+ DefaultMediaNotificationProvider defaultMediaNotificationProvider =
+ new DefaultMediaNotificationProvider.Builder(ApplicationProvider.getApplicationContext())
+ .build();
+
+ MediaNotification mediaNotification =
+ defaultMediaNotificationProvider.createNotification(
+ mediaSession,
+ /* customLayout= */ ImmutableList.of(),
+ defaultActionFactory,
+ notification -> {});
+ mediaSession.release();
+
+ assertThat(mediaNotification.notification.actions[0].title.toString())
+ .isEqualTo(context.getString(R.string.media3_controls_pause_description));
+ }
+
+ @Test
+ public void createNotification_withStateReadyAndPlayWhenReadyFalse_showsPlayButton() {
+ Player player =
+ createPlayerWithFixedState(
+ Player.STATE_READY,
+ /* playWhenReady= */ false,
+ Player.PLAYBACK_SUPPRESSION_REASON_NONE);
+ DefaultActionFactory defaultActionFactory =
+ new DefaultActionFactory(Robolectric.setupService(TestService.class));
+ MediaSession mediaSession = new MediaSession.Builder(context, player).build();
+ DefaultMediaNotificationProvider defaultMediaNotificationProvider =
+ new DefaultMediaNotificationProvider.Builder(ApplicationProvider.getApplicationContext())
+ .build();
+
+ MediaNotification mediaNotification =
+ defaultMediaNotificationProvider.createNotification(
+ mediaSession,
+ /* customLayout= */ ImmutableList.of(),
+ defaultActionFactory,
+ notification -> {});
+ mediaSession.release();
+
+ assertThat(mediaNotification.notification.actions[0].title.toString())
+ .isEqualTo(context.getString(R.string.media3_controls_play_description));
+ }
+
+ @Test
+ public void createNotification_withStateBufferingAndPlayWhenReadyFalse_showsPlayButton() {
+ Player player =
+ createPlayerWithFixedState(
+ Player.STATE_BUFFERING,
+ /* playWhenReady= */ false,
+ Player.PLAYBACK_SUPPRESSION_REASON_NONE);
+ DefaultActionFactory defaultActionFactory =
+ new DefaultActionFactory(Robolectric.setupService(TestService.class));
+ MediaSession mediaSession = new MediaSession.Builder(context, player).build();
+ DefaultMediaNotificationProvider defaultMediaNotificationProvider =
+ new DefaultMediaNotificationProvider.Builder(ApplicationProvider.getApplicationContext())
+ .build();
+
+ MediaNotification mediaNotification =
+ defaultMediaNotificationProvider.createNotification(
+ mediaSession,
+ /* customLayout= */ ImmutableList.of(),
+ defaultActionFactory,
+ notification -> {});
+ mediaSession.release();
+
+ assertThat(mediaNotification.notification.actions[0].title.toString())
+ .isEqualTo(context.getString(R.string.media3_controls_play_description));
+ }
+
+ @Test
+ public void createNotification_withStateEndedAndPlayWhenReadyTrue_showsPlayButton() {
+ Player player =
+ createPlayerWithFixedState(
+ Player.STATE_ENDED, /* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
+ DefaultActionFactory defaultActionFactory =
+ new DefaultActionFactory(Robolectric.setupService(TestService.class));
+ MediaSession mediaSession = new MediaSession.Builder(context, player).build();
+ DefaultMediaNotificationProvider defaultMediaNotificationProvider =
+ new DefaultMediaNotificationProvider.Builder(ApplicationProvider.getApplicationContext())
+ .build();
+
+ MediaNotification mediaNotification =
+ defaultMediaNotificationProvider.createNotification(
+ mediaSession,
+ /* customLayout= */ ImmutableList.of(),
+ defaultActionFactory,
+ notification -> {});
+ mediaSession.release();
+
+ assertThat(mediaNotification.notification.actions[0].title.toString())
+ .isEqualTo(context.getString(R.string.media3_controls_play_description));
+ }
+
+ @Test
+ public void createNotification_withStateEndedAndPlayWhenReadyFalse_showsPlayButton() {
+ Player player =
+ createPlayerWithFixedState(
+ Player.STATE_ENDED, /* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
+ DefaultActionFactory defaultActionFactory =
+ new DefaultActionFactory(Robolectric.setupService(TestService.class));
+ MediaSession mediaSession = new MediaSession.Builder(context, player).build();
+ DefaultMediaNotificationProvider defaultMediaNotificationProvider =
+ new DefaultMediaNotificationProvider.Builder(ApplicationProvider.getApplicationContext())
+ .build();
+
+ MediaNotification mediaNotification =
+ defaultMediaNotificationProvider.createNotification(
+ mediaSession,
+ /* customLayout= */ ImmutableList.of(),
+ defaultActionFactory,
+ notification -> {});
+ mediaSession.release();
+
+ assertThat(mediaNotification.notification.actions[0].title.toString())
+ .isEqualTo(context.getString(R.string.media3_controls_play_description));
+ }
+
+ @Test
+ public void createNotification_withStateIdleAndPlayWhenReadyTrue_showsPlayButton() {
+ Player player =
+ createPlayerWithFixedState(
+ Player.STATE_IDLE, /* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
+ DefaultActionFactory defaultActionFactory =
+ new DefaultActionFactory(Robolectric.setupService(TestService.class));
+ MediaSession mediaSession = new MediaSession.Builder(context, player).build();
+ DefaultMediaNotificationProvider defaultMediaNotificationProvider =
+ new DefaultMediaNotificationProvider.Builder(ApplicationProvider.getApplicationContext())
+ .build();
+
+ MediaNotification mediaNotification =
+ defaultMediaNotificationProvider.createNotification(
+ mediaSession,
+ /* customLayout= */ ImmutableList.of(),
+ defaultActionFactory,
+ notification -> {});
+ mediaSession.release();
+
+ assertThat(mediaNotification.notification.actions[0].title.toString())
+ .isEqualTo(context.getString(R.string.media3_controls_play_description));
+ }
+
+ @Test
+ public void createNotification_withStateIdleAndPlayWhenReadyFalse_showsPlayButton() {
+ Player player =
+ createPlayerWithFixedState(
+ Player.STATE_IDLE, /* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
+ DefaultActionFactory defaultActionFactory =
+ new DefaultActionFactory(Robolectric.setupService(TestService.class));
+ MediaSession mediaSession = new MediaSession.Builder(context, player).build();
+ DefaultMediaNotificationProvider defaultMediaNotificationProvider =
+ new DefaultMediaNotificationProvider.Builder(ApplicationProvider.getApplicationContext())
+ .build();
+
+ MediaNotification mediaNotification =
+ defaultMediaNotificationProvider.createNotification(
+ mediaSession,
+ /* customLayout= */ ImmutableList.of(),
+ defaultActionFactory,
+ notification -> {});
+ mediaSession.release();
+
+ assertThat(mediaNotification.notification.actions[0].title.toString())
+ .isEqualTo(context.getString(R.string.media3_controls_play_description));
+ }
+
@Test
public void provider_idsNotSpecified_usesDefaultIds() {
Context context = ApplicationProvider.getApplicationContext();
@@ -1010,6 +1327,31 @@ public MediaMetadata getMediaMetadata() {
};
}
+ private static Player createPlayerWithFixedState(
+ @Player.State int playbackState,
+ boolean playWhenReady,
+ @Player.PlaybackSuppressionReason int suppressionReason) {
+ return new SimpleBasePlayer(Looper.getMainLooper()) {
+ @Override
+ protected State getState() {
+ return new State.Builder()
+ .setAvailableCommands(new Commands.Builder().add(Player.COMMAND_PLAY_PAUSE).build())
+ .setPlaylist(
+ ImmutableList.of(new MediaItemData.Builder(/* uid= */ new Object()).build()))
+ .setPlaybackState(playbackState)
+ .setPlayWhenReady(playWhenReady, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST)
+ .setPlaybackSuppressionReason(suppressionReason)
+ .build();
+ }
+
+ @Override
+ protected ListenableFuture> handleSetPlayWhenReady(boolean playWhenReady) {
+ // Do nothing.
+ return Futures.immediateVoidFuture();
+ }
+ };
+ }
+
/** A test service for unit tests. */
private static final class TestService extends MediaLibraryService {
@Nullable
diff --git a/libraries/session/src/test/java/androidx/media3/session/PlayerWrapperTest.java b/libraries/session/src/test/java/androidx/media3/session/PlayerWrapperTest.java
index 430e14c61cc..fad36651e68 100644
--- a/libraries/session/src/test/java/androidx/media3/session/PlayerWrapperTest.java
+++ b/libraries/session/src/test/java/androidx/media3/session/PlayerWrapperTest.java
@@ -42,7 +42,7 @@ public class PlayerWrapperTest {
@Before
public void setUp() {
- playerWrapper = new PlayerWrapper(player);
+ playerWrapper = new PlayerWrapper(player, /* playIfSuppressed= */ true);
when(player.isCommandAvailable(anyInt())).thenReturn(true);
when(player.getApplicationLooper()).thenReturn(Looper.myLooper());
}
diff --git a/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaSessionConstants.java b/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaSessionConstants.java
index aa9f9b3eaea..b0c2601a320 100644
--- a/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaSessionConstants.java
+++ b/libraries/test_session_common/src/main/java/androidx/media3/test/session/common/MediaSessionConstants.java
@@ -28,6 +28,8 @@ public class MediaSessionConstants {
public static final String TEST_ON_VIDEO_SIZE_CHANGED = "onVideoSizeChanged";
public static final String TEST_ON_TRACKS_CHANGED_VIDEO_TO_AUDIO_TRANSITION =
"onTracksChanged_videoToAudioTransition";
+ public static final String TEST_SET_SHOW_PLAY_BUTTON_IF_SUPPRESSED_TO_FALSE =
+ "testSetShowPlayButtonIfSuppressedToFalse";
// Bundle keys
public static final String KEY_AVAILABLE_SESSION_COMMANDS = "availableSessionCommands";
diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatCallbackWithMediaSessionTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatCallbackWithMediaSessionTest.java
index d2a08c7465d..a7c54412f56 100644
--- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatCallbackWithMediaSessionTest.java
+++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerCompatCallbackWithMediaSessionTest.java
@@ -20,6 +20,7 @@
import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_USER_RATING;
import static androidx.media3.common.Player.STATE_ENDED;
import static androidx.media3.common.Player.STATE_READY;
+import static androidx.media3.test.session.common.MediaSessionConstants.TEST_SET_SHOW_PLAY_BUTTON_IF_SUPPRESSED_TO_FALSE;
import static androidx.media3.test.session.common.TestUtils.LONG_TIMEOUT_MS;
import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
@@ -662,12 +663,6 @@ public void onPlaybackStateChanged(PlaybackStateCompat playbackStateCompat) {
assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
assertThat(playbackStateCompatRef.get().getState()).isEqualTo(PlaybackStateCompat.STATE_PAUSED);
assertThat(playbackStateCompatRef.get().getPlaybackSpeed()).isEqualTo(0f);
- assertThat(
- playbackStateCompatRef
- .get()
- .getExtras()
- .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT))
- .isEqualTo(1f);
assertThat(
playbackStateCompatRef
.get()
@@ -713,12 +708,6 @@ public void onPlaybackStateChanged(PlaybackStateCompat playbackStateCompat) {
assertThat(playbackStateCompatRef.get().getState())
.isEqualTo(PlaybackStateCompat.STATE_BUFFERING);
assertThat(playbackStateCompatRef.get().getPlaybackSpeed()).isEqualTo(0f);
- assertThat(
- playbackStateCompatRef
- .get()
- .getExtras()
- .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT))
- .isEqualTo(1f);
assertThat(
playbackStateCompatRef
.get()
@@ -766,6 +755,44 @@ public void onPlaybackStateChanged(PlaybackStateCompat playbackStateCompat) {
.getExtras()
.getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT))
.isEqualTo(1f);
+ assertThat(controllerCompat.getPlaybackState().getState())
+ .isEqualTo(PlaybackStateCompat.STATE_STOPPED);
+ assertThat(controllerCompat.getPlaybackState().getPlaybackSpeed()).isEqualTo(0f);
+ assertThat(
+ controllerCompat
+ .getPlaybackState()
+ .getExtras()
+ .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT))
+ .isEqualTo(1f);
+ }
+
+ @Test
+ public void playbackStateChange_withPlaybackSuppression_notifiesPaused() throws Exception {
+ session.getMockPlayer().setPlaybackState(Player.STATE_READY);
+ session
+ .getMockPlayer()
+ .setPlayWhenReady(/* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
+ AtomicReference playbackStateCompatRef = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaControllerCompat.Callback callback =
+ new MediaControllerCompat.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackStateCompat playbackStateCompat) {
+ playbackStateCompatRef.set(playbackStateCompat);
+ latch.countDown();
+ }
+ };
+ controllerCompat.registerCallback(callback, handler);
+
+ session
+ .getMockPlayer()
+ .notifyPlayWhenReadyChanged(
+ /* playWhenReady= */ true,
+ Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS);
+
+ assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue();
+ assertThat(playbackStateCompatRef.get().getState()).isEqualTo(PlaybackStateCompat.STATE_PAUSED);
+ assertThat(playbackStateCompatRef.get().getPlaybackSpeed()).isEqualTo(0f);
assertThat(
playbackStateCompatRef
.get()
@@ -773,7 +800,7 @@ public void onPlaybackStateChanged(PlaybackStateCompat playbackStateCompat) {
.getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT))
.isEqualTo(1f);
assertThat(controllerCompat.getPlaybackState().getState())
- .isEqualTo(PlaybackStateCompat.STATE_STOPPED);
+ .isEqualTo(PlaybackStateCompat.STATE_PAUSED);
assertThat(controllerCompat.getPlaybackState().getPlaybackSpeed()).isEqualTo(0f);
assertThat(
controllerCompat
@@ -784,8 +811,13 @@ public void onPlaybackStateChanged(PlaybackStateCompat playbackStateCompat) {
}
@Test
- public void playbackStateChange_withPlaybackSuppression_notifiesPlayingWithSpeedZero()
- throws Exception {
+ public void
+ playbackStateChange_withPlaybackSuppressionWithoutShowPauseIfSuppressed_notifiesPlayingWithSpeedZero()
+ throws Exception {
+ RemoteMediaSession session =
+ new RemoteMediaSession(TEST_SET_SHOW_PLAY_BUTTON_IF_SUPPRESSED_TO_FALSE, context, null);
+ MediaControllerCompat controllerCompat =
+ new MediaControllerCompat(context, session.getCompatToken());
session.getMockPlayer().setPlaybackState(Player.STATE_READY);
session
.getMockPlayer()
@@ -812,12 +844,6 @@ public void onPlaybackStateChanged(PlaybackStateCompat playbackStateCompat) {
assertThat(playbackStateCompatRef.get().getState())
.isEqualTo(PlaybackStateCompat.STATE_PLAYING);
assertThat(playbackStateCompatRef.get().getPlaybackSpeed()).isEqualTo(0f);
- assertThat(
- playbackStateCompatRef
- .get()
- .getExtras()
- .getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT))
- .isEqualTo(1f);
assertThat(
playbackStateCompatRef
.get()
@@ -833,6 +859,7 @@ public void onPlaybackStateChanged(PlaybackStateCompat playbackStateCompat) {
.getExtras()
.getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT))
.isEqualTo(1f);
+ session.release();
}
@Test
diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java
index 38cd4d04e3e..49850464f31 100644
--- a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java
+++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java
@@ -65,6 +65,7 @@
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_IS_SESSION_COMMAND_AVAILABLE;
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_ON_TRACKS_CHANGED_VIDEO_TO_AUDIO_TRANSITION;
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_ON_VIDEO_SIZE_CHANGED;
+import static androidx.media3.test.session.common.MediaSessionConstants.TEST_SET_SHOW_PLAY_BUTTON_IF_SUPPRESSED_TO_FALSE;
import static androidx.media3.test.session.common.MediaSessionConstants.TEST_WITH_CUSTOM_COMMANDS;
import android.app.PendingIntent;
@@ -284,6 +285,11 @@ public MediaSession.ConnectionResult onConnect(
mockPlayer.currentTracks = MediaTestUtils.createDefaultVideoTracks();
break;
}
+ case TEST_SET_SHOW_PLAY_BUTTON_IF_SUPPRESSED_TO_FALSE:
+ {
+ builder.setShowPlayButtonIfPlaybackIsSuppressed(false);
+ break;
+ }
default: // fall out
}
diff --git a/libraries/ui/src/main/java/androidx/media3/ui/LegacyPlayerControlView.java b/libraries/ui/src/main/java/androidx/media3/ui/LegacyPlayerControlView.java
index 1e14fa5afa6..ab954343cc8 100644
--- a/libraries/ui/src/main/java/androidx/media3/ui/LegacyPlayerControlView.java
+++ b/libraries/ui/src/main/java/androidx/media3/ui/LegacyPlayerControlView.java
@@ -330,6 +330,7 @@ public interface ProgressUpdateListener {
private boolean isAttachedToWindow;
private boolean showMultiWindowTimeBar;
+ private boolean showPlayButtonIfSuppressed;
private boolean multiWindowTimeBar;
private boolean scrubbing;
private int showTimeoutMs;
@@ -373,6 +374,7 @@ public LegacyPlayerControlView(
@Nullable AttributeSet playbackAttrs) {
super(context, attrs, defStyleAttr);
int controllerLayoutId = R.layout.exo_legacy_player_control_view;
+ showPlayButtonIfSuppressed = true;
showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS;
repeatToggleModes = DEFAULT_REPEAT_TOGGLE_MODES;
timeBarMinUpdateIntervalMs = DEFAULT_TIME_BAR_MIN_UPDATE_INTERVAL_MS;
@@ -571,6 +573,20 @@ public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) {
updateTimeline();
}
+ /**
+ * Sets whether a play button is shown if playback is {@linkplain
+ * Player#getPlaybackSuppressionReason() suppressed}.
+ *
+ * The default is {@code true}.
+ *
+ * @param showPlayButtonIfSuppressed Whether to show a play button if playback is {@linkplain
+ * Player#getPlaybackSuppressionReason() suppressed}.
+ */
+ public void setShowPlayButtonIfPlaybackIsSuppressed(boolean showPlayButtonIfSuppressed) {
+ this.showPlayButtonIfSuppressed = showPlayButtonIfSuppressed;
+ updatePlayPauseButton();
+ }
+
/**
* Sets the millisecond positions of extra ad markers relative to the start of the window (or
* timeline, if in multi-window mode) and whether each extra ad has been played or not. The
@@ -842,7 +858,7 @@ private void updatePlayPauseButton() {
}
boolean requestPlayPauseFocus = false;
boolean requestPlayPauseAccessibilityFocus = false;
- boolean shouldShowPlayButton = Util.shouldShowPlayButton(player);
+ boolean shouldShowPlayButton = Util.shouldShowPlayButton(player, showPlayButtonIfSuppressed);
if (playButton != null) {
requestPlayPauseFocus |= !shouldShowPlayButton && playButton.isFocused();
requestPlayPauseAccessibilityFocus |=
@@ -1083,7 +1099,7 @@ private void updateProgress() {
}
private void requestPlayPauseFocus() {
- boolean shouldShowPlayButton = Util.shouldShowPlayButton(player);
+ boolean shouldShowPlayButton = Util.shouldShowPlayButton(player, showPlayButtonIfSuppressed);
if (shouldShowPlayButton && playButton != null) {
playButton.requestFocus();
} else if (!shouldShowPlayButton && pauseButton != null) {
@@ -1092,7 +1108,7 @@ private void requestPlayPauseFocus() {
}
private void requestPlayPauseAccessibilityFocus() {
- boolean shouldShowPlayButton = Util.shouldShowPlayButton(player);
+ boolean shouldShowPlayButton = Util.shouldShowPlayButton(player, showPlayButtonIfSuppressed);
if (shouldShowPlayButton && playButton != null) {
playButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
} else if (!shouldShowPlayButton && pauseButton != null) {
@@ -1202,7 +1218,7 @@ public boolean dispatchMediaKeyEvent(KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_HEADSETHOOK:
- Util.handlePlayPauseButtonAction(player);
+ Util.handlePlayPauseButtonAction(player, showPlayButtonIfSuppressed);
break;
case KeyEvent.KEYCODE_MEDIA_PLAY:
Util.handlePlayButtonAction(player);
diff --git a/libraries/ui/src/main/java/androidx/media3/ui/PlayerControlView.java b/libraries/ui/src/main/java/androidx/media3/ui/PlayerControlView.java
index eb13af7cf6e..c8424ca82e6 100644
--- a/libraries/ui/src/main/java/androidx/media3/ui/PlayerControlView.java
+++ b/libraries/ui/src/main/java/androidx/media3/ui/PlayerControlView.java
@@ -335,6 +335,7 @@ public interface OnFullScreenModeChangedListener {
private boolean isFullScreen;
private boolean isAttachedToWindow;
private boolean showMultiWindowTimeBar;
+ private boolean showPlayButtonIfSuppressed;
private boolean multiWindowTimeBar;
private boolean scrubbing;
private int showTimeoutMs;
@@ -373,6 +374,7 @@ public PlayerControlView(
@Nullable AttributeSet playbackAttrs) {
super(context, attrs, defStyleAttr);
int controllerLayoutId = R.layout.exo_player_control_view;
+ showPlayButtonIfSuppressed = true;
showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS;
repeatToggleModes = DEFAULT_REPEAT_TOGGLE_MODES;
timeBarMinUpdateIntervalMs = DEFAULT_TIME_BAR_MIN_UPDATE_INTERVAL_MS;
@@ -673,6 +675,20 @@ public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) {
updateTimeline();
}
+ /**
+ * Sets whether a play button is shown if playback is {@linkplain
+ * Player#getPlaybackSuppressionReason() suppressed}.
+ *
+ *
The default is {@code true}.
+ *
+ * @param showPlayButtonIfSuppressed Whether to show a play button if playback is {@linkplain
+ * Player#getPlaybackSuppressionReason() suppressed}.
+ */
+ public void setShowPlayButtonIfPlaybackIsSuppressed(boolean showPlayButtonIfSuppressed) {
+ this.showPlayButtonIfSuppressed = showPlayButtonIfSuppressed;
+ updatePlayPauseButton();
+ }
+
/**
* Sets the millisecond positions of extra ad markers relative to the start of the window (or
* timeline, if in multi-window mode) and whether each extra ad has been played or not. The
@@ -980,7 +996,7 @@ private void updatePlayPauseButton() {
return;
}
if (playPauseButton != null) {
- boolean shouldShowPlayButton = Util.shouldShowPlayButton(player);
+ boolean shouldShowPlayButton = Util.shouldShowPlayButton(player, showPlayButtonIfSuppressed);
@DrawableRes
int drawableRes =
shouldShowPlayButton
@@ -1479,7 +1495,7 @@ public boolean dispatchMediaKeyEvent(KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_HEADSETHOOK:
- Util.handlePlayPauseButtonAction(player);
+ Util.handlePlayPauseButtonAction(player, showPlayButtonIfSuppressed);
break;
case KeyEvent.KEYCODE_MEDIA_PLAY:
Util.handlePlayButtonAction(player);
@@ -1710,7 +1726,7 @@ public void onClick(View view) {
player.seekBack();
}
} else if (playPauseButton == view) {
- Util.handlePlayPauseButtonAction(player);
+ Util.handlePlayPauseButtonAction(player, showPlayButtonIfSuppressed);
} else if (repeatToggleButton == view) {
if (player.isCommandAvailable(COMMAND_SET_REPEAT_MODE)) {
player.setRepeatMode(
diff --git a/libraries/ui/src/main/java/androidx/media3/ui/PlayerNotificationManager.java b/libraries/ui/src/main/java/androidx/media3/ui/PlayerNotificationManager.java
index fb99b3d88bf..973320e2804 100644
--- a/libraries/ui/src/main/java/androidx/media3/ui/PlayerNotificationManager.java
+++ b/libraries/ui/src/main/java/androidx/media3/ui/PlayerNotificationManager.java
@@ -712,6 +712,7 @@ public void onBitmap(final Bitmap bitmap) {
private boolean useRewindActionInCompactView;
private boolean useFastForwardActionInCompactView;
private boolean usePlayPauseActions;
+ private boolean showPlayButtonIfSuppressed;
private boolean useStopAction;
private int badgeIconType;
private boolean colorized;
@@ -762,6 +763,7 @@ protected PlayerNotificationManager(
usePreviousAction = true;
useNextAction = true;
usePlayPauseActions = true;
+ showPlayButtonIfSuppressed = true;
useRewindAction = true;
useFastForwardAction = true;
colorized = true;
@@ -971,6 +973,22 @@ public final void setUsePlayPauseActions(boolean usePlayPauseActions) {
}
}
+ /**
+ * Sets whether a play button is shown if playback is {@linkplain
+ * Player#getPlaybackSuppressionReason() suppressed}.
+ *
+ *
The default is {@code true}.
+ *
+ * @param showPlayButtonIfSuppressed Whether to show a play button if playback is {@linkplain
+ * Player#getPlaybackSuppressionReason() suppressed}.
+ */
+ public void setShowPlayButtonIfPlaybackIsSuppressed(boolean showPlayButtonIfSuppressed) {
+ if (this.showPlayButtonIfSuppressed != showPlayButtonIfSuppressed) {
+ this.showPlayButtonIfSuppressed = showPlayButtonIfSuppressed;
+ invalidate();
+ }
+ }
+
/**
* Sets whether the stop action should be used.
*
@@ -1339,7 +1357,7 @@ protected List getActions(Player player) {
stringActions.add(ACTION_REWIND);
}
if (usePlayPauseActions) {
- if (Util.shouldShowPlayButton(player)) {
+ if (Util.shouldShowPlayButton(player, showPlayButtonIfSuppressed)) {
stringActions.add(ACTION_PLAY);
} else {
stringActions.add(ACTION_PAUSE);
@@ -1387,7 +1405,7 @@ protected int[] getActionIndicesForCompactView(List actionNames, Player
if (leftSideActionIndex != -1) {
actionIndices[actionCounter++] = leftSideActionIndex;
}
- boolean shouldShowPlayButton = Util.shouldShowPlayButton(player);
+ boolean shouldShowPlayButton = Util.shouldShowPlayButton(player, showPlayButtonIfSuppressed);
if (pauseActionIndex != -1 && !shouldShowPlayButton) {
actionIndices[actionCounter++] = pauseActionIndex;
} else if (playActionIndex != -1 && shouldShowPlayButton) {
diff --git a/libraries/ui/src/main/java/androidx/media3/ui/PlayerView.java b/libraries/ui/src/main/java/androidx/media3/ui/PlayerView.java
index 198a2c1fe34..0c726e4eaaf 100644
--- a/libraries/ui/src/main/java/androidx/media3/ui/PlayerView.java
+++ b/libraries/ui/src/main/java/androidx/media3/ui/PlayerView.java
@@ -1123,6 +1123,21 @@ public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) {
controller.setShowMultiWindowTimeBar(showMultiWindowTimeBar);
}
+ /**
+ * Sets whether a play button is shown if playback is {@linkplain
+ * Player#getPlaybackSuppressionReason() suppressed}.
+ *
+ * The default is {@code true}.
+ *
+ * @param showPlayButtonIfSuppressed Whether to show a play button if playback is {@linkplain
+ * Player#getPlaybackSuppressionReason() suppressed}.
+ */
+ @UnstableApi
+ public void setShowPlayButtonIfPlaybackIsSuppressed(boolean showPlayButtonIfSuppressed) {
+ Assertions.checkStateNotNull(controller);
+ controller.setShowPlayButtonIfPlaybackIsSuppressed(showPlayButtonIfSuppressed);
+ }
+
/**
* Sets the millisecond positions of extra ad markers relative to the start of the window (or
* timeline, if in multi-window mode) and whether each extra ad has been played or not. The