diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/DefaultPlaybackController.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/DefaultPlaybackController.java new file mode 100644 index 0000000000..231c1f1ea5 --- /dev/null +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/DefaultPlaybackController.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ext.mediasession; + +import android.support.v4.media.session.PlaybackStateCompat; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Player; + +/** + * A default implementation of the {@link MediaSessionConnector.PlaybackController}. You can safely + * override any method for instance to intercept calls for a given action. + */ +public class DefaultPlaybackController implements MediaSessionConnector.PlaybackController { + + private static final long BASE_ACTIONS = PlaybackStateCompat.ACTION_PLAY_PAUSE + | PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE + | PlaybackStateCompat.ACTION_STOP; + + protected final long fastForwardIncrementMs; + protected final long rewindIncrementMs; + + /** + * Creates a new {@link DefaultPlaybackController}. This is equivalent to calling + * {@code DefaultPlaybackController(15000L, 5000L)}. + */ + public DefaultPlaybackController() { + this(15000L, 5000L); + } + + /** + * Creates a new {@link DefaultPlaybackController} and sets the fast forward and rewind increments + * in milliseconds. + * + * @param fastForwardIncrementMs A positive value will cause the + * {@link PlaybackStateCompat#ACTION_FAST_FORWARD} playback action to be added. A zero or a + * negative value will cause it to be removed. + * @param rewindIncrementMs A positive value will cause the + * {@link PlaybackStateCompat#ACTION_REWIND} playback action to be added. A zero or a + * negative value will cause it to be removed. + */ + public DefaultPlaybackController(long fastForwardIncrementMs, long rewindIncrementMs) { + this.fastForwardIncrementMs = fastForwardIncrementMs; + this.rewindIncrementMs = rewindIncrementMs; + } + + @Override + public long getSupportedPlaybackActions(Player player) { + if (player == null || player.getCurrentTimeline().isEmpty()) { + return 0; + } + long actions = BASE_ACTIONS; + if (player.isCurrentWindowSeekable()) { + actions |= PlaybackStateCompat.ACTION_SEEK_TO; + } + if (fastForwardIncrementMs > 0) { + actions |= PlaybackStateCompat.ACTION_FAST_FORWARD; + } + if (rewindIncrementMs > 0) { + actions |= PlaybackStateCompat.ACTION_REWIND; + } + return actions; + } + + @Override + public void onPlay(Player player) { + player.setPlayWhenReady(true); + } + + @Override + public void onPause(Player player) { + player.setPlayWhenReady(false); + } + + @Override + public void onSeekTo(Player player, long position) { + long duration = player.getDuration(); + if (duration != C.TIME_UNSET) { + position = Math.min(position, duration); + } + player.seekTo(Math.max(position, 0)); + } + + @Override + public void onFastForward(Player player) { + if (fastForwardIncrementMs <= 0) { + return; + } + onSeekTo(player, player.getCurrentPosition() + fastForwardIncrementMs); + } + + @Override + public void onRewind(Player player) { + if (rewindIncrementMs <= 0) { + return; + } + onSeekTo(player, player.getCurrentPosition() - rewindIncrementMs); + } + + @Override + public void onStop(Player player) { + player.stop(); + } + +} diff --git a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java index d70d1bcaa9..a300acfffa 100644 --- a/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java +++ b/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java @@ -47,11 +47,11 @@ import java.util.Map; * Mediates between a {@link MediaSessionCompat} and an {@link Player} instance set with * {@link #setPlayer(Player, PlaybackPreparer, CustomActionProvider...)}. *
- * By default the {@code MediaSessionConnector} listens for {@link #DEFAULT_PLAYBACK_ACTIONS} sent - * by a media controller and realizes these actions by calling appropriate ExoPlayer methods. - * Further, the state of ExoPlayer will be synced automatically with the {@link PlaybackStateCompat} - * of the media session to broadcast state transitions to clients. You can optionally extend this - * behaviour by providing various collaborators. + * The {@code MediaSessionConnector} listens for media actions sent by a media controller and + * realizes these actions by calling appropriate ExoPlayer methods. Further, the state of ExoPlayer + * will be synced automatically with the {@link PlaybackStateCompat} of the media session to + * broadcast state transitions to clients. You can optionally extend this behaviour by providing + * various collaborators. *
* Media actions to initiate media playback ({@code PlaybackStateCompat#ACTION_PREPARE_*} and * {@code PlaybackStateCompat#ACTION_PLAY_*} need to be handled by a {@link PlaybackPreparer} which @@ -75,32 +75,8 @@ public final class MediaSessionConnector { ExoPlayerLibraryInfo.registerModule("goog.exo.mediasession"); } - /** - * Actions that are published to the media session by default - * ({@code PlaybackStateCompat.ACTION_PLAY_PAUSE}, {@code PlaybackStateCompat.ACTION_PLAY}, - * {@code PlaybackStateCompat.ACTION_PAUSE}, {@code PlaybackStateCompat.ACTION_FAST_FORWARD}, - * {@code PlaybackStateCompat.ACTION_REWIND}). - */ - public static final long DEFAULT_PLAYBACK_ACTIONS = PlaybackStateCompat.ACTION_PLAY_PAUSE - | PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE - | PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND; - public static final String EXTRAS_PITCH = "EXO_PITCH"; - public static final long DEFAULT_FAST_FORWARD_MS = 15000; - public static final long DEFAULT_REWIND_MS = 5000; - - /** - * Interface of components taking responsibility of a set of media session playback actions - * ({@code PlaybackStateCompat#ACTION_*}). - */ - public interface PlaybackActionSupport { - /** - * Returns the bit mask of the playback actions supported by this component. - */ - long getSupportedPlaybackActions(); - } - /** * Interface to which media controller commands regarding preparing playback for a given media * clip are delegated to. @@ -108,12 +84,30 @@ public final class MediaSessionConnector { * Normally preparing playback includes preparing the player with a * {@link com.google.android.exoplayer2.source.MediaSource} and setting up the media session queue * with a corresponding list of queue items. - *
- * The {@link PlaybackPreparer} handles the media actions {@code ACTION_PREPARE},
- * {@code ACTION_PREPARE_FROM_MEDIA_ID}, {@code ACTION_PREPARE_FROM_URI} and
- * {@code ACTION_PREPARE_FROM_SEARCH}.
*/
- public interface PlaybackPreparer extends PlaybackActionSupport {
+ public interface PlaybackPreparer {
+
+ long ACTIONS = PlaybackStateCompat.ACTION_PREPARE
+ | PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID
+ | PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH
+ | PlaybackStateCompat.ACTION_PREPARE_FROM_URI
+ | PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID
+ | PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH
+ | PlaybackStateCompat.ACTION_PLAY_FROM_URI;
+
+ /**
+ * Returns the actions which are supported by the preparer. The supported actions must be a
+ * bitmask combined out of {@link PlaybackStateCompat#ACTION_PREPARE},
+ * {@link PlaybackStateCompat#ACTION_PREPARE_FROM_MEDIA_ID},
+ * {@link PlaybackStateCompat#ACTION_PREPARE_FROM_SEARCH},
+ * {@link PlaybackStateCompat#ACTION_PREPARE_FROM_URI},
+ * {@link PlaybackStateCompat#ACTION_PLAY_FROM_MEDIA_ID},
+ * {@link PlaybackStateCompat#ACTION_PLAY_FROM_SEARCH} and
+ * {@link PlaybackStateCompat#ACTION_PLAY_FROM_URI}.
+ *
+ * @return The bitmask of the supported media actions.
+ */
+ long getSupportedPrepareActions();
/**
* See {@link MediaSessionCompat.Callback#onPrepare()}.
*/
@@ -137,10 +131,73 @@ public final class MediaSessionConnector {
}
/**
- * Navigator to handle queue navigation commands and maintain the media session queue with
+ * Controller to handle playback actions.
+ */
+ public interface PlaybackController {
+
+ long ACTIONS = PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_PLAY
+ | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_SEEK_TO
+ | PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND
+ | PlaybackStateCompat.ACTION_STOP;
+
+ /**
+ * Returns the actions which are supported by the controller. The supported actions must be a
+ * bitmask combined out of {@link PlaybackStateCompat#ACTION_PLAY_PAUSE},
+ * {@link PlaybackStateCompat#ACTION_PLAY}, {@link PlaybackStateCompat#ACTION_PAUSE},
+ * {@link PlaybackStateCompat#ACTION_SEEK_TO}, {@link PlaybackStateCompat#ACTION_FAST_FORWARD},
+ * {@link PlaybackStateCompat#ACTION_REWIND} and {@link PlaybackStateCompat#ACTION_STOP}.
+ *
+ * @param player The player.
+ * @return The bitmask of the supported media actions.
+ */
+ long getSupportedPlaybackActions(@Nullable Player player);
+ /**
+ * See {@link MediaSessionCompat.Callback#onPlay()}.
+ */
+ void onPlay(Player player);
+ /**
+ * See {@link MediaSessionCompat.Callback#onPause()}.
+ */
+ void onPause(Player player);
+ /**
+ * See {@link MediaSessionCompat.Callback#onSeekTo(long)}.
+ */
+ void onSeekTo(Player player, long position);
+ /**
+ * See {@link MediaSessionCompat.Callback#onFastForward()}.
+ */
+ void onFastForward(Player player);
+ /**
+ * See {@link MediaSessionCompat.Callback#onRewind()}.
+ */
+ void onRewind(Player player);
+ /**
+ * See {@link MediaSessionCompat.Callback#onStop()}.
+ */
+ void onStop(Player player);
+ }
+
+ /**
+ * Navigator to handle queue navigation actions and maintain the media session queue with
* {#link MediaSessionCompat#setQueue(List)} to provide the active queue item to the connector.
*/
- public interface QueueNavigator extends PlaybackActionSupport {
+ public interface QueueNavigator {
+
+ long ACTIONS = PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM
+ | PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
+ | PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE_ENABLED;
+
+ /**
+ * Returns the actions which are supported by the navigator. The supported actions must be a
+ * bitmask combined out of {@link PlaybackStateCompat#ACTION_SKIP_TO_QUEUE_ITEM},
+ * {@link PlaybackStateCompat#ACTION_SKIP_TO_NEXT},
+ * {@link PlaybackStateCompat#ACTION_SKIP_TO_PREVIOUS},
+ * {@link PlaybackStateCompat#ACTION_SET_SHUFFLE_MODE_ENABLED}.
+ *
+ * @param player The {@link Player}.
+ * @return The bitmask of the supported media actions.
+ */
+ long getSupportedQueueNavigatorActions(@Nullable Player player);
/**
* Called when the timeline of the player has changed.
*
@@ -186,7 +243,17 @@ public final class MediaSessionConnector {
/**
* Editor to manipulate the queue.
*/
- public interface QueueEditor extends PlaybackActionSupport {
+ public interface QueueEditor {
+
+ long ACTIONS = PlaybackStateCompat.ACTION_SET_RATING;
+
+ /**
+ * Returns {@link PlaybackStateCompat#ACTION_SET_RATING} or {@code 0}. The Media API does
+ * not declare action constants for adding and removing queue items.
+ *
+ * @param player The {@link Player}.
+ */
+ long getSupportedQueueEditorActions(@Nullable Player player);
/**
* See {@link MediaSessionCompat.Callback#onAddQueueItem(MediaDescriptionCompat description)}.
*/
@@ -254,13 +321,11 @@ public final class MediaSessionConnector {
private final boolean doMaintainMetadata;
private final ExoPlayerEventListener exoPlayerEventListener;
private final MediaSessionCallback mediaSessionCallback;
+ private final PlaybackController playbackController;
private Player player;
private CustomActionProvider[] customActionProviders;
private int currentWindowIndex;
- private long playbackActions;
- private long fastForwardIncrementMs;
- private long rewindIncrementMs;
private Map
* Constructing the {@link MediaSessionConnector} needs to be done on the same thread as
* constructing the player instance.
@@ -278,7 +343,22 @@ public final class MediaSessionConnector {
* @param mediaSession The {@link MediaSessionCompat} to connect to.
*/
public MediaSessionConnector(MediaSessionCompat mediaSession) {
- this(mediaSession, true);
+ this(mediaSession, new DefaultPlaybackController());
+ }
+
+ /**
+ * Creates a {@code MediaSessionConnector}. This is equivalent to calling
+ * {@code #MediaSessionConnector(mediaSession, playbackController, true)}.
+ *
+ * Constructing the {@link MediaSessionConnector} needs to be done on the same thread as
+ * constructing the player instance.
+ *
+ * @param mediaSession The {@link MediaSessionCompat} to connect to.
+ * @param playbackController The {@link PlaybackController}.
+ */
+ public MediaSessionConnector(MediaSessionCompat mediaSession,
+ PlaybackController playbackController) {
+ this(mediaSession, playbackController, true);
}
/**
@@ -292,11 +372,14 @@ public final class MediaSessionConnector {
* constructing the player instance.
*
* @param mediaSession The {@link MediaSessionCompat} to connect to.
+ * @param playbackController The {@link PlaybackController}.
* @param doMaintainMetadata Sets whether the connector should maintain the metadata of the
* session.
*/
- public MediaSessionConnector(MediaSessionCompat mediaSession, boolean doMaintainMetadata) {
+ public MediaSessionConnector(MediaSessionCompat mediaSession,
+ PlaybackController playbackController, boolean doMaintainMetadata) {
this.mediaSession = mediaSession;
+ this.playbackController = playbackController;
this.handler = new Handler(Looper.myLooper() != null ? Looper.myLooper()
: Looper.getMainLooper());
this.doMaintainMetadata = doMaintainMetadata;
@@ -305,10 +388,7 @@ public final class MediaSessionConnector {
mediaController = mediaSession.getController();
mediaSessionCallback = new MediaSessionCallback();
exoPlayerEventListener = new ExoPlayerEventListener();
- playbackActions = DEFAULT_PLAYBACK_ACTIONS;
customActionMap = Collections.emptyMap();
- fastForwardIncrementMs = DEFAULT_FAST_FORWARD_MS;
- rewindIncrementMs = DEFAULT_REWIND_MS;
}
/**
@@ -343,68 +423,6 @@ public final class MediaSessionConnector {
updateMediaSessionMetadata();
}
- /**
- * Sets the fast forward increment in milliseconds. A positive value will cause the
- * {@link PlaybackStateCompat#ACTION_FAST_FORWARD} playback action to be added. A zero or a
- * negative value will cause it to be removed.
- *
- * @param fastForwardIncrementMs The fast forward increment in milliseconds.
- */
- public void setFastForwardIncrementMs(long fastForwardIncrementMs) {
- this.fastForwardIncrementMs = fastForwardIncrementMs;
- if (fastForwardIncrementMs > 0) {
- addPlaybackActions(PlaybackStateCompat.ACTION_FAST_FORWARD);
- } else {
- removePlaybackActions(PlaybackStateCompat.ACTION_FAST_FORWARD);
- }
- }
-
- /**
- * Sets the rewind increment in milliseconds. A positive value will cause the
- * {@link PlaybackStateCompat#ACTION_REWIND} playback action to be added. A zero or a
- * negative value will cause it to be removed.
- *
- * @param rewindIncrementMs The rewind increment in milliseconds.
- */
- public void setRewindIncrementMs(long rewindIncrementMs) {
- this.rewindIncrementMs = rewindIncrementMs;
- if (rewindIncrementMs > 0) {
- addPlaybackActions(PlaybackStateCompat.ACTION_REWIND);
- } else {
- removePlaybackActions(PlaybackStateCompat.ACTION_REWIND);
- }
- }
-
- /**
- * Adds playback actions. The playback actions that are enabled by default are those in
- * {@link MediaSessionConnector#DEFAULT_PLAYBACK_ACTIONS}. See {@link PlaybackStateCompat} for
- * available playback action constants.
- *
- * @param playbackActions The playback actions to add.
- */
- public void addPlaybackActions(long playbackActions) {
- this.playbackActions |= playbackActions;
- }
-
- /**
- * Removes playback actions. The playback actions that are enabled by default are those in
- * {@link MediaSessionConnector#DEFAULT_PLAYBACK_ACTIONS}.
- *
- * @param playbackActions The playback actions to remove.
- */
- public void removePlaybackActions(long playbackActions) {
- this.playbackActions &= ~playbackActions;
- }
-
- /**
- * Sets the playback actions. The playback actions that are enabled by default are overridden.
- *
- * @param playbackActions The playback actions to publish.
- */
- public void setPlaybackActions(long playbackActions) {
- this.playbackActions = playbackActions;
- }
-
/**
* Sets the optional {@link ErrorMessageProvider}.
*
@@ -415,20 +433,14 @@ public final class MediaSessionConnector {
}
/**
- * Sets the {@link QueueNavigator} to handle queue navigation for the media actions
- * {@code ACTION_SKIP_TO_NEXT}, {@code ACTION_SKIP_TO_PREVIOUS},
- * {@code ACTION_SKIP_TO_QUEUE_ITEM} and {@code ACTION_SET_SHUFFLE_MODE_ENABLED}.
+ * Sets the {@link QueueNavigator} to handle queue navigation actions {@code ACTION_SKIP_TO_NEXT},
+ * {@code ACTION_SKIP_TO_PREVIOUS}, {@code ACTION_SKIP_TO_QUEUE_ITEM} and
+ * {@code ACTION_SET_SHUFFLE_MODE_ENABLED}.
*
* @param queueNavigator The navigator to handle queue navigation.
*/
public void setQueueNavigator(QueueNavigator queueNavigator) {
- if (this.queueNavigator != null) {
- removePlaybackActions(this.queueNavigator.getSupportedPlaybackActions());
- }
this.queueNavigator = queueNavigator;
- if (queueNavigator != null) {
- addPlaybackActions(queueNavigator.getSupportedPlaybackActions());
- }
}
/**
@@ -437,29 +449,17 @@ public final class MediaSessionConnector {
* @param queueEditor The editor to handle queue manipulation actions.
*/
public void setQueueEditor(QueueEditor queueEditor) {
- if (this.queueEditor != null) {
- removePlaybackActions(this.queueEditor.getSupportedPlaybackActions());
- }
this.queueEditor = queueEditor;
- if (queueEditor != null) {
- addPlaybackActions(queueEditor.getSupportedPlaybackActions());
- }
}
private void setPlaybackPreparer(PlaybackPreparer playbackPreparer) {
- if (this.playbackPreparer != null) {
- removePlaybackActions(this.playbackPreparer.getSupportedPlaybackActions());
- }
this.playbackPreparer = playbackPreparer;
- if (playbackPreparer != null) {
- addPlaybackActions(playbackPreparer.getSupportedPlaybackActions());
- }
}
private void updateMediaSessionPlaybackState() {
PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
if (player == null) {
- builder.setActions(0).setState(PlaybackStateCompat.STATE_NONE, 0, 0, 0);
+ builder.setActions(buildPlaybackActions()).setState(PlaybackStateCompat.STATE_NONE, 0, 0, 0);
mediaSession.setPlaybackState(builder.build());
return;
}
@@ -487,10 +487,9 @@ public final class MediaSessionConnector {
}
long activeQueueItemId = queueNavigator != null ? queueNavigator.getActiveQueueItemId(player)
: MediaSessionCompat.QueueItem.UNKNOWN_ID;
- updatePlaybackActions(activeQueueItemId);
Bundle extras = new Bundle();
extras.putFloat(EXTRAS_PITCH, player.getPlaybackParameters().pitch);
- builder.setActions(playbackActions)
+ builder.setActions(buildPlaybackActions())
.setActiveQueueItemId(activeQueueItemId)
.setBufferedPosition(player.getBufferedPosition())
.setState(sessionPlaybackState, player.getCurrentPosition(),
@@ -499,24 +498,23 @@ public final class MediaSessionConnector {
mediaSession.setPlaybackState(builder.build());
}
- private void updatePlaybackActions(long activeQueueItemId) {
- List