Show play button during playback suppression by default

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
This commit is contained in:
tonihei 2023-08-15 15:40:36 +01:00 committed by oceanjules
parent 86b9fdae63
commit 597de8706d
19 changed files with 642 additions and 77 deletions

View File

@ -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

View File

@ -3171,23 +3171,42 @@ public final class Util {
* <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) {
@ -3215,9 +3234,9 @@ public final class Util {
* 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) {
@ -3232,13 +3251,30 @@ public final class Util {
* 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);

View File

@ -23,7 +23,6 @@ import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
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 class DefaultMediaNotificationProvider implements MediaNotification.Provi
mediaSession,
player.getAvailableCommands(),
customLayoutWithEnabledCommandButtonsOnly.build(),
/* showPauseButton= */ player.getPlayWhenReady()
&& player.getPlaybackState() != STATE_ENDED),
!Util.shouldShowPlayButton(
player, mediaSession.getShowPlayButtonIfPlaybackIsSuppressed())),
builder,
actionFactory);
mediaStyle.setShowActionsInCompactView(compactViewIndices);

View File

@ -461,6 +461,22 @@ public abstract class MediaLibraryService extends MediaSessionService {
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}.
*
@ -481,7 +497,8 @@ public abstract class MediaLibraryService extends MediaSessionService {
customLayout,
callback,
extras,
checkNotNull(bitmapLoader));
checkNotNull(bitmapLoader),
playIfSuppressed);
}
}
@ -493,9 +510,18 @@ public abstract class MediaLibraryService extends MediaSessionService {
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
@ -507,7 +533,8 @@ public abstract class MediaLibraryService extends MediaSessionService {
ImmutableList<CommandButton> customLayout,
MediaSession.Callback callback,
Bundle tokenExtras,
BitmapLoader bitmapLoader) {
BitmapLoader bitmapLoader,
boolean playIfSuppressed) {
return new MediaLibrarySessionImpl(
this,
context,
@ -517,7 +544,8 @@ public abstract class MediaLibraryService extends MediaSessionService {
customLayout,
(Callback) callback,
tokenExtras,
bitmapLoader);
bitmapLoader,
playIfSuppressed);
}
@Override

View File

@ -78,7 +78,8 @@ import java.util.concurrent.Future;
ImmutableList<CommandButton> customLayout,
MediaLibrarySession.Callback callback,
Bundle tokenExtras,
BitmapLoader bitmapLoader) {
BitmapLoader bitmapLoader,
boolean playIfSuppressed) {
super(
instance,
context,
@ -88,7 +89,8 @@ import java.util.concurrent.Future;
customLayout,
callback,
tokenExtras,
bitmapLoader);
bitmapLoader,
playIfSuppressed);
this.instance = instance;
this.callback = callback;
subscriptions = new ArrayMap<>();

View File

@ -377,6 +377,22 @@ public class MediaSession {
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}.
*
@ -397,7 +413,8 @@ public class MediaSession {
customLayout,
callback,
extras,
checkNotNull(bitmapLoader));
checkNotNull(bitmapLoader),
playIfSuppressed);
}
}
@ -589,7 +606,8 @@ public class MediaSession {
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);
@ -605,7 +623,8 @@ public class MediaSession {
customLayout,
callback,
tokenExtras,
bitmapLoader);
bitmapLoader,
playIfSuppressed);
}
/* package */ MediaSessionImpl createImpl(
@ -616,7 +635,8 @@ public class MediaSession {
ImmutableList<CommandButton> customLayout,
Callback callback,
Bundle tokenExtras,
BitmapLoader bitmapLoader) {
BitmapLoader bitmapLoader,
boolean playIfSuppressed) {
return new MediaSessionImpl(
this,
context,
@ -626,7 +646,8 @@ public class MediaSession {
customLayout,
callback,
tokenExtras,
bitmapLoader);
bitmapLoader,
playIfSuppressed);
}
/* package */ MediaSessionImpl getImpl() {
@ -935,6 +956,15 @@ public class MediaSession {
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 @@ public class MediaSession {
/* package */ @Nullable PendingIntent sessionActivity;
/* package */ Bundle extras;
/* package */ @MonotonicNonNull BitmapLoader bitmapLoader;
/* package */ boolean playIfSuppressed;
/* package */ ImmutableList<CommandButton> customLayout;
@ -1751,6 +1782,7 @@ public class MediaSession {
this.callback = callback;
extras = Bundle.EMPTY;
customLayout = ImmutableList.of();
playIfSuppressed = true;
}
@SuppressWarnings("unchecked")
@ -1789,6 +1821,13 @@ public class MediaSession {
return (BuilderT) this;
}
@SuppressWarnings("unchecked")
public BuilderT setShowPlayButtonIfPlaybackIsSuppressed(
boolean showPlayButtonIfPlaybackIsSuppressed) {
this.playIfSuppressed = showPlayButtonIfPlaybackIsSuppressed;
return (BuilderT) this;
}
public abstract SessionT build();
}
}

View File

@ -112,6 +112,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
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 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
ImmutableList<CommandButton> customLayout,
MediaSession.Callback callback,
Bundle tokenExtras,
BitmapLoader bitmapLoader) {
BitmapLoader bitmapLoader,
boolean playIfSuppressed) {
this.context = context;
this.instance = instance;
@ -156,6 +158,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
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 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
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 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
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 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return bitmapLoader;
}
public boolean shouldPlayIfSuppressed() {
return playIfSuppressed;
}
public void setAvailableCommands(
ControllerInfo controller, SessionCommands sessionCommands, Player.Commands playerCommands) {
if (sessionStub.getConnectedControllersManager().isConnected(controller)) {

View File

@ -346,7 +346,9 @@ import org.checkerframework.checker.initialization.qual.Initialized;
mediaPlayPauseKeyHandler.clearPendingMediaPlayPauseKey();
dispatchSessionTaskWithPlayerCommand(
COMMAND_PLAY_PAUSE,
controller -> Util.handlePlayPauseButtonAction(sessionImpl.getPlayerWrapper()),
controller ->
Util.handlePlayPauseButtonAction(
sessionImpl.getPlayerWrapper(), sessionImpl.shouldPlayIfSuppressed()),
remoteUserInfo);
}

View File

@ -759,24 +759,25 @@ import java.util.concurrent.TimeoutException;
/** 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);
}

View File

@ -64,13 +64,16 @@ import java.util.List;
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<CommandButton> 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 @@ import java.util.List;
.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();

View File

@ -43,11 +43,14 @@ import androidx.media3.common.ForwardingPlayer;
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 @@ public class DefaultMediaNotificationProviderTest {
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 class DefaultMediaNotificationProviderTest {
};
}
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

View File

@ -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());
}

View File

@ -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";

View File

@ -20,6 +20,7 @@ import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_MEDIA_ID
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 class MediaControllerCompatCallbackWithMediaSessionTest {
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 class MediaControllerCompatCallbackWithMediaSessionTest {
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()
@ -760,12 +749,6 @@ public class MediaControllerCompatCallbackWithMediaSessionTest {
assertThat(playbackStateCompatRef.get().getState())
.isEqualTo(PlaybackStateCompat.STATE_STOPPED);
assertThat(playbackStateCompatRef.get().getPlaybackSpeed()).isEqualTo(0f);
assertThat(
playbackStateCompatRef
.get()
.getExtras()
.getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT))
.isEqualTo(1f);
assertThat(
playbackStateCompatRef
.get()
@ -784,8 +767,57 @@ public class MediaControllerCompatCallbackWithMediaSessionTest {
}
@Test
public void playbackStateChange_withPlaybackSuppression_notifiesPlayingWithSpeedZero()
throws Exception {
public void playbackStateChange_withPlaybackSuppression_notifiesPaused() throws Exception {
session.getMockPlayer().setPlaybackState(Player.STATE_READY);
session
.getMockPlayer()
.setPlayWhenReady(/* playWhenReady= */ true, Player.PLAYBACK_SUPPRESSION_REASON_NONE);
AtomicReference<PlaybackStateCompat> 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()
.getExtras()
.getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT))
.isEqualTo(1f);
assertThat(controllerCompat.getPlaybackState().getState())
.isEqualTo(PlaybackStateCompat.STATE_PAUSED);
assertThat(controllerCompat.getPlaybackState().getPlaybackSpeed()).isEqualTo(0f);
assertThat(
controllerCompat
.getPlaybackState()
.getExtras()
.getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT))
.isEqualTo(1f);
}
@Test
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 class MediaControllerCompatCallbackWithMediaSessionTest {
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 class MediaControllerCompatCallbackWithMediaSessionTest {
.getExtras()
.getFloat(MediaConstants.EXTRAS_KEY_PLAYBACK_SPEED_COMPAT))
.isEqualTo(1f);
session.release();
}
@Test

View File

@ -65,6 +65,7 @@ import static androidx.media3.test.session.common.MediaSessionConstants.TEST_GET
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 class MediaSessionProviderService extends Service {
mockPlayer.currentTracks = MediaTestUtils.createDefaultVideoTracks();
break;
}
case TEST_SET_SHOW_PLAY_BUTTON_IF_SUPPRESSED_TO_FALSE:
{
builder.setShowPlayButtonIfPlaybackIsSuppressed(false);
break;
}
default: // fall out
}

View File

@ -330,6 +330,7 @@ public class LegacyPlayerControlView extends FrameLayout {
private boolean isAttachedToWindow;
private boolean showMultiWindowTimeBar;
private boolean showPlayButtonIfSuppressed;
private boolean multiWindowTimeBar;
private boolean scrubbing;
private int showTimeoutMs;
@ -373,6 +374,7 @@ public class LegacyPlayerControlView extends FrameLayout {
@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 class LegacyPlayerControlView extends FrameLayout {
updateTimeline();
}
/**
* Sets whether a play button is shown if playback is {@linkplain
* Player#getPlaybackSuppressionReason() suppressed}.
*
* <p>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 @@ public class LegacyPlayerControlView extends FrameLayout {
}
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 @@ public class LegacyPlayerControlView extends FrameLayout {
}
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 @@ public class LegacyPlayerControlView extends FrameLayout {
}
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 class LegacyPlayerControlView extends FrameLayout {
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);

View File

@ -335,6 +335,7 @@ public class PlayerControlView extends FrameLayout {
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 class PlayerControlView extends FrameLayout {
@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 class PlayerControlView extends FrameLayout {
updateTimeline();
}
/**
* Sets whether a play button is shown if playback is {@linkplain
* Player#getPlaybackSuppressionReason() suppressed}.
*
* <p>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 @@ public class PlayerControlView extends FrameLayout {
return;
}
if (playPauseButton != null) {
boolean shouldShowPlayButton = Util.shouldShowPlayButton(player);
boolean shouldShowPlayButton = Util.shouldShowPlayButton(player, showPlayButtonIfSuppressed);
@DrawableRes
int drawableRes =
shouldShowPlayButton
@ -1479,7 +1495,7 @@ public class PlayerControlView extends FrameLayout {
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 class PlayerControlView extends FrameLayout {
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(

View File

@ -712,6 +712,7 @@ public class PlayerNotificationManager {
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 @@ public class PlayerNotificationManager {
usePreviousAction = true;
useNextAction = true;
usePlayPauseActions = true;
showPlayButtonIfSuppressed = true;
useRewindAction = true;
useFastForwardAction = true;
colorized = true;
@ -971,6 +973,22 @@ public class PlayerNotificationManager {
}
}
/**
* Sets whether a play button is shown if playback is {@linkplain
* Player#getPlaybackSuppressionReason() suppressed}.
*
* <p>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 @@ public class PlayerNotificationManager {
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 @@ public class PlayerNotificationManager {
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) {

View File

@ -1123,6 +1123,21 @@ public class PlayerView extends FrameLayout implements AdViewProvider {
controller.setShowMultiWindowTimeBar(showMultiWindowTimeBar);
}
/**
* Sets whether a play button is shown if playback is {@linkplain
* Player#getPlaybackSuppressionReason() suppressed}.
*
* <p>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