mirror of
https://github.com/androidx/media.git
synced 2025-05-11 09:39:52 +08:00
make PlayerNotificationListener better suited for foreground services
Issue: #5301 Issue: #4988 Issue: #4813 Issue: #5344 Issue: #5117 PiperOrigin-RevId: 229603354
This commit is contained in:
parent
ec77f737ee
commit
16a185de1d
@ -37,6 +37,8 @@
|
||||
* Add `Handler` parameter to `ConcatenatingMediaSource` methods which take a
|
||||
callback `Runnable`.
|
||||
* Remove `player` and `isTopLevelSource` parameters from `MediaSource.prepare`.
|
||||
* Change signature of `PlayerNotificationManager.NotificationListener` to better
|
||||
fit service requirements. Remove ability to set a custom stop action.
|
||||
|
||||
### 2.9.4 ###
|
||||
|
||||
|
@ -58,7 +58,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
* player state.
|
||||
*
|
||||
* <p>The notification is cancelled when {@code null} is passed to {@link #setPlayer(Player)} or
|
||||
* when an intent with action {@link #ACTION_STOP} is received.
|
||||
* when the notification is dismissed by the user.
|
||||
*
|
||||
* <p>If the player is released it must be removed from the manager by calling {@code
|
||||
* setPlayer(null)} which will cancel the notification.
|
||||
@ -72,11 +72,17 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
* are displayed.
|
||||
* <ul>
|
||||
* <li>Corresponding setter: {@link #setUseNavigationActions(boolean)}
|
||||
* <li>Default: {@code true}
|
||||
* </ul>
|
||||
* <li><b>{@code stopAction}</b> - Sets which stop action should be used. If set to null, the stop
|
||||
* action is not displayed.
|
||||
* <li><b>{@code usePlayPauseActions}</b> - Sets whether the play and pause actions are displayed.
|
||||
* <ul>
|
||||
* <li>Corresponding setter: {@link #setStopAction(String)}}
|
||||
* <li>Corresponding setter: {@link #setUsePlayPauseActions(boolean)}
|
||||
* <li>Default: {@code true}
|
||||
* </ul>
|
||||
* <li><b>{@code useStopAction}</b> - Sets whether the stop action is displayed.
|
||||
* <ul>
|
||||
* <li>Corresponding setter: {@link #setUseStopAction(boolean)}
|
||||
* <li>Default: {@code false}
|
||||
* </ul>
|
||||
* <li><b>{@code rewindIncrementMs}</b> - Sets the rewind increment. If set to zero the rewind
|
||||
* action is not displayed.
|
||||
@ -87,7 +93,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
* <li><b>{@code fastForwardIncrementMs}</b> - Sets the fast forward increment. If set to zero the
|
||||
* fast forward action is not included in the notification.
|
||||
* <ul>
|
||||
* <li>Corresponding setter: {@link #setFastForwardIncrementMs(long)}}
|
||||
* <li>Corresponding setter: {@link #setFastForwardIncrementMs(long)}
|
||||
* <li>Default: {@link #DEFAULT_FAST_FORWARD_MS} (5000)
|
||||
* </ul>
|
||||
* </ul>
|
||||
@ -195,7 +201,7 @@ public class PlayerNotificationManager {
|
||||
void onCustomAction(Player player, String action, Intent intent);
|
||||
}
|
||||
|
||||
/** A listener for start and cancellation of the notification. */
|
||||
/** A listener for changes to the notification. */
|
||||
public interface NotificationListener {
|
||||
|
||||
/**
|
||||
@ -203,15 +209,41 @@ public class PlayerNotificationManager {
|
||||
*
|
||||
* @param notificationId The id with which the notification has been posted.
|
||||
* @param notification The {@link Notification}.
|
||||
* @deprecated Use {@link #onNotificationPosted(int, Notification, boolean)} instead.
|
||||
*/
|
||||
void onNotificationStarted(int notificationId, Notification notification);
|
||||
@Deprecated
|
||||
default void onNotificationStarted(int notificationId, Notification notification) {}
|
||||
|
||||
/**
|
||||
* Called after the notification has been cancelled.
|
||||
*
|
||||
* @param notificationId The id of the notification which has been cancelled.
|
||||
* @deprecated Use {@link #onNotificationCancelled(int, boolean)}.
|
||||
*/
|
||||
void onNotificationCancelled(int notificationId);
|
||||
@Deprecated
|
||||
default void onNotificationCancelled(int notificationId) {}
|
||||
|
||||
/**
|
||||
* Called after the notification has been cancelled.
|
||||
*
|
||||
* @param notificationId The id of the notification which has been cancelled.
|
||||
* @param dismissedByUser {@code true} if the notification is cancelled because the user
|
||||
* dismissed the notification.
|
||||
*/
|
||||
default void onNotificationCancelled(int notificationId, boolean dismissedByUser) {}
|
||||
|
||||
/**
|
||||
* Called each time after the notification has been posted.
|
||||
*
|
||||
* <p>The {@code isPlayerActive} flag indicates whether a service in which the player may run
|
||||
* needs to be in the foreground.
|
||||
*
|
||||
* @param notificationId The id of the notification which has been posted.
|
||||
* @param notification The {@link Notification}.
|
||||
* @param isPlayerActive {@code true} if the player is active.
|
||||
*/
|
||||
default void onNotificationPosted(
|
||||
int notificationId, Notification notification, boolean isPlayerActive) {}
|
||||
}
|
||||
|
||||
/** Receives a {@link Bitmap}. */
|
||||
@ -235,7 +267,7 @@ public class PlayerNotificationManager {
|
||||
if (player != null
|
||||
&& notificationTag == currentNotificationTag
|
||||
&& isNotificationStarted) {
|
||||
updateNotification(bitmap);
|
||||
startOrUpdateNotification(bitmap);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -254,10 +286,15 @@ public class PlayerNotificationManager {
|
||||
public static final String ACTION_FAST_FORWARD = "com.google.android.exoplayer.ffwd";
|
||||
/** The action which rewinds. */
|
||||
public static final String ACTION_REWIND = "com.google.android.exoplayer.rewind";
|
||||
/** The action which cancels the notification and stops playback. */
|
||||
/** The action which stops playback. */
|
||||
public static final String ACTION_STOP = "com.google.android.exoplayer.stop";
|
||||
/** The extra key of the instance id of the player notification manager. */
|
||||
public static final String EXTRA_INSTANCE_ID = "INSTANCE_ID";
|
||||
/**
|
||||
* The action which is executed when the notification is dismissed. It cancels the notification
|
||||
* and calls {@link NotificationListener#onNotificationCancelled(int, boolean)}.
|
||||
*/
|
||||
private static final String ACTION_DISMISS = "com.google.android.exoplayer.dismiss";
|
||||
|
||||
/**
|
||||
* Visibility of notification on the lock screen. One of {@link
|
||||
@ -311,6 +348,7 @@ public class PlayerNotificationManager {
|
||||
private final NotificationBroadcastReceiver notificationBroadcastReceiver;
|
||||
private final Map<String, NotificationCompat.Action> playbackActions;
|
||||
private final Map<String, NotificationCompat.Action> customActions;
|
||||
private final PendingIntent dismissPendingIntent;
|
||||
private final int instanceId;
|
||||
private final Timeline.Window window;
|
||||
|
||||
@ -323,8 +361,7 @@ public class PlayerNotificationManager {
|
||||
private @Nullable MediaSessionCompat.Token mediaSessionToken;
|
||||
private boolean useNavigationActions;
|
||||
private boolean usePlayPauseActions;
|
||||
private @Nullable String stopAction;
|
||||
private @Nullable PendingIntent stopPendingIntent;
|
||||
private boolean useStopAction;
|
||||
private long fastForwardMs;
|
||||
private long rewindMs;
|
||||
private int badgeIconType;
|
||||
@ -519,7 +556,6 @@ public class PlayerNotificationManager {
|
||||
priority = NotificationCompat.PRIORITY_LOW;
|
||||
fastForwardMs = DEFAULT_FAST_FORWARD_MS;
|
||||
rewindMs = DEFAULT_REWIND_MS;
|
||||
stopAction = ACTION_STOP;
|
||||
badgeIconType = NotificationCompat.BADGE_ICON_SMALL;
|
||||
visibility = NotificationCompat.VISIBILITY_PUBLIC;
|
||||
|
||||
@ -535,7 +571,8 @@ public class PlayerNotificationManager {
|
||||
for (String action : customActions.keySet()) {
|
||||
intentFilter.addAction(action);
|
||||
}
|
||||
stopPendingIntent = Assertions.checkNotNull(playbackActions.get(ACTION_STOP)).actionIntent;
|
||||
dismissPendingIntent = createBroadcastIntent(ACTION_DISMISS, context, instanceId);
|
||||
intentFilter.addAction(ACTION_DISMISS);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -562,7 +599,7 @@ public class PlayerNotificationManager {
|
||||
if (this.player != null) {
|
||||
this.player.removeListener(playerListener);
|
||||
if (player == null) {
|
||||
stopNotification();
|
||||
stopNotification(/* dismissedByUser= */ false);
|
||||
}
|
||||
}
|
||||
this.player = player;
|
||||
@ -570,9 +607,7 @@ public class PlayerNotificationManager {
|
||||
wasPlayWhenReady = player.getPlayWhenReady();
|
||||
lastPlaybackState = player.getPlaybackState();
|
||||
player.addListener(playerListener);
|
||||
if (lastPlaybackState != Player.STATE_IDLE) {
|
||||
startOrUpdateNotification();
|
||||
}
|
||||
startOrUpdateNotification();
|
||||
}
|
||||
}
|
||||
|
||||
@ -664,24 +699,15 @@ public class PlayerNotificationManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the action to be used as stop action to cancel the notification. If {@code
|
||||
* null} is passed the stop action is not displayed.
|
||||
* Sets whether the stop action should be used.
|
||||
*
|
||||
* @param stopAction The name of the stop action which must be {@link #ACTION_STOP} or an action
|
||||
* provided by the {@link CustomActionReceiver}. {@code null} to omit the stop action.
|
||||
* @param useStopAction Whether to use the stop action.
|
||||
*/
|
||||
public final void setStopAction(@Nullable String stopAction) {
|
||||
if (Util.areEqual(stopAction, this.stopAction)) {
|
||||
public final void setUseStopAction(boolean useStopAction) {
|
||||
if (this.useStopAction == useStopAction) {
|
||||
return;
|
||||
}
|
||||
this.stopAction = stopAction;
|
||||
if (ACTION_STOP.equals(stopAction)) {
|
||||
stopPendingIntent = Assertions.checkNotNull(playbackActions.get(ACTION_STOP)).actionIntent;
|
||||
} else if (stopAction != null) {
|
||||
stopPendingIntent = Assertions.checkNotNull(customActions.get(stopAction)).actionIntent;
|
||||
} else {
|
||||
stopPendingIntent = null;
|
||||
}
|
||||
this.useStopAction = useStopAction;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@ -864,36 +890,50 @@ public class PlayerNotificationManager {
|
||||
/** Forces an update of the notification if already started. */
|
||||
public void invalidate() {
|
||||
if (isNotificationStarted && player != null) {
|
||||
updateNotification(null);
|
||||
startOrUpdateNotification();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Notification startOrUpdateNotification() {
|
||||
return player != null ? startOrUpdateNotification(/* bitmap= */ null) : null;
|
||||
}
|
||||
|
||||
@RequiresNonNull("player")
|
||||
private Notification updateNotification(@Nullable Bitmap bitmap) {
|
||||
@Nullable
|
||||
private Notification startOrUpdateNotification(@Nullable Bitmap bitmap) {
|
||||
Notification notification = createNotification(player, bitmap);
|
||||
if (notification == null) {
|
||||
stopNotification(/* dismissedByUser= */ false);
|
||||
return null;
|
||||
}
|
||||
notificationManager.notify(notificationId, notification);
|
||||
if (!isNotificationStarted) {
|
||||
isNotificationStarted = true;
|
||||
context.registerReceiver(notificationBroadcastReceiver, intentFilter);
|
||||
if (notificationListener != null) {
|
||||
notificationListener.onNotificationStarted(notificationId, notification);
|
||||
}
|
||||
}
|
||||
NotificationListener listener = notificationListener;
|
||||
Player player = this.player;
|
||||
if (listener != null && player != null) {
|
||||
boolean isPlayerActive =
|
||||
player.getPlayWhenReady() && player.getPlaybackState() != Player.STATE_IDLE;
|
||||
listener.onNotificationPosted(notificationId, notification, isPlayerActive);
|
||||
}
|
||||
return notification;
|
||||
}
|
||||
|
||||
private void startOrUpdateNotification() {
|
||||
if (player != null) {
|
||||
Notification notification = updateNotification(null);
|
||||
if (!isNotificationStarted) {
|
||||
isNotificationStarted = true;
|
||||
context.registerReceiver(notificationBroadcastReceiver, intentFilter);
|
||||
if (notificationListener != null) {
|
||||
notificationListener.onNotificationStarted(notificationId, notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void stopNotification() {
|
||||
private void stopNotification(boolean dismissedByUser) {
|
||||
if (isNotificationStarted) {
|
||||
notificationManager.cancel(notificationId);
|
||||
if (!dismissedByUser) {
|
||||
notificationManager.cancel(notificationId);
|
||||
}
|
||||
isNotificationStarted = false;
|
||||
context.unregisterReceiver(notificationBroadcastReceiver);
|
||||
if (notificationListener != null) {
|
||||
notificationListener.onNotificationCancelled(notificationId, dismissedByUser);
|
||||
notificationListener.onNotificationCancelled(notificationId);
|
||||
}
|
||||
}
|
||||
@ -904,9 +944,14 @@ public class PlayerNotificationManager {
|
||||
*
|
||||
* @param player The player for which state to build a notification.
|
||||
* @param largeIcon The large icon to be used.
|
||||
* @return The {@link Notification} which has been built.
|
||||
* @return The {@link Notification} which has been built, or {@code null} if no notification
|
||||
* should be displayed.
|
||||
*/
|
||||
@Nullable
|
||||
protected Notification createNotification(Player player, @Nullable Bitmap largeIcon) {
|
||||
if (player.getPlaybackState() == Player.STATE_IDLE) {
|
||||
return null;
|
||||
}
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId);
|
||||
List<String> actionNames = getActions(player);
|
||||
for (int i = 0; i < actionNames.size(); i++) {
|
||||
@ -925,14 +970,13 @@ public class PlayerNotificationManager {
|
||||
mediaStyle.setMediaSession(mediaSessionToken);
|
||||
}
|
||||
mediaStyle.setShowActionsInCompactView(getActionIndicesForCompactView(actionNames, player));
|
||||
// Configure stop action (eg. when user dismisses the notification when !isOngoing).
|
||||
boolean useStopAction = stopAction != null;
|
||||
mediaStyle.setShowCancelButton(useStopAction);
|
||||
if (useStopAction && stopPendingIntent != null) {
|
||||
builder.setDeleteIntent(stopPendingIntent);
|
||||
mediaStyle.setCancelButtonIntent(stopPendingIntent);
|
||||
}
|
||||
// Configure dismiss action prior to API 21 ('x' button).
|
||||
mediaStyle.setShowCancelButton(true);
|
||||
mediaStyle.setCancelButtonIntent(dismissPendingIntent);
|
||||
// Set intent which is sent if the user selects 'clear all'
|
||||
builder.setDeleteIntent(dismissPendingIntent);
|
||||
builder.setStyle(mediaStyle);
|
||||
|
||||
// Set notification properties from getters.
|
||||
builder
|
||||
.setBadgeIconType(badgeIconType)
|
||||
@ -1030,8 +1074,8 @@ public class PlayerNotificationManager {
|
||||
if (customActionReceiver != null) {
|
||||
stringActions.addAll(customActionReceiver.getCustomActions(player));
|
||||
}
|
||||
if (ACTION_STOP.equals(stopAction)) {
|
||||
stringActions.add(stopAction);
|
||||
if (useStopAction) {
|
||||
stringActions.add(ACTION_STOP);
|
||||
}
|
||||
return stringActions;
|
||||
}
|
||||
@ -1176,27 +1220,20 @@ public class PlayerNotificationManager {
|
||||
|
||||
@Override
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
if ((wasPlayWhenReady != playWhenReady && playbackState != Player.STATE_IDLE)
|
||||
|| lastPlaybackState != playbackState) {
|
||||
if (wasPlayWhenReady != playWhenReady || lastPlaybackState != playbackState) {
|
||||
startOrUpdateNotification();
|
||||
wasPlayWhenReady = playWhenReady;
|
||||
lastPlaybackState = playbackState;
|
||||
}
|
||||
wasPlayWhenReady = playWhenReady;
|
||||
lastPlaybackState = playbackState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, @Nullable Object manifest, int reason) {
|
||||
if (player == null || player.getPlaybackState() == Player.STATE_IDLE) {
|
||||
return;
|
||||
}
|
||||
startOrUpdateNotification();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||
if (player == null || player.getPlaybackState() == Player.STATE_IDLE) {
|
||||
return;
|
||||
}
|
||||
startOrUpdateNotification();
|
||||
}
|
||||
|
||||
@ -1207,9 +1244,6 @@ public class PlayerNotificationManager {
|
||||
|
||||
@Override
|
||||
public void onRepeatModeChanged(int repeatMode) {
|
||||
if (player == null || player.getPlaybackState() == Player.STATE_IDLE) {
|
||||
return;
|
||||
}
|
||||
startOrUpdateNotification();
|
||||
}
|
||||
}
|
||||
@ -1245,8 +1279,9 @@ public class PlayerNotificationManager {
|
||||
} else if (ACTION_NEXT.equals(action)) {
|
||||
next(player);
|
||||
} else if (ACTION_STOP.equals(action)) {
|
||||
controlDispatcher.dispatchStop(player, true);
|
||||
stopNotification();
|
||||
controlDispatcher.dispatchStop(player, /* reset= */ true);
|
||||
} else if (ACTION_DISMISS.equals(action)) {
|
||||
stopNotification(/* dismissedByUser= */ true);
|
||||
} else if (customActionReceiver != null && customActions.containsKey(action)) {
|
||||
customActionReceiver.onCustomAction(player, action, intent);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user