Set MediaSessionConnector components separately

Also support preparation without a player, in line with PlayerView.

PiperOrigin-RevId: 227735260
This commit is contained in:
olly 2019-01-03 21:01:12 +00:00 committed by Oliver Woodman
parent d30375c9a1
commit d834eeab6f
3 changed files with 101 additions and 93 deletions

View File

@ -67,10 +67,10 @@ import java.util.Map;
* *
* <ul> * <ul>
* <li>Actions to initiate media playback ({@code PlaybackStateCompat#ACTION_PREPARE_*} and {@code * <li>Actions to initiate media playback ({@code PlaybackStateCompat#ACTION_PREPARE_*} and {@code
* PlaybackStateCompat#ACTION_PLAY_*}) can be handled by a {@link PlaybackPreparer} passed * PlaybackStateCompat#ACTION_PLAY_*}) can be handled by a {@link PlaybackPreparer} passed to
* when calling {@link #setPlayer(Player, PlaybackPreparer, CustomActionProvider...)}. Custom * {@link #setPlaybackPreparer(PlaybackPreparer)}.
* actions can be handled by passing one or more {@link CustomActionProvider}s in a similar * <li>Custom actions can be handled by passing one or more {@link CustomActionProvider}s to
* way. * {@link #setCustomActionProviders(CustomActionProvider...)}.
* <li>To enable a media queue and navigation within it, you can set a {@link QueueNavigator} by * <li>To enable a media queue and navigation within it, you can set a {@link QueueNavigator} by
* calling {@link #setQueueNavigator(QueueNavigator)}. Use of {@link TimelineQueueNavigator} * calling {@link #setQueueNavigator(QueueNavigator)}. Use of {@link TimelineQueueNavigator}
* is recommended for most use cases. * is recommended for most use cases.
@ -339,21 +339,21 @@ public final class MediaSessionConnector {
/** The wrapped {@link MediaSessionCompat}. */ /** The wrapped {@link MediaSessionCompat}. */
public final MediaSessionCompat mediaSession; public final MediaSessionCompat mediaSession;
@Nullable private final MediaMetadataProvider mediaMetadataProvider; private final Looper looper;
private final ExoPlayerEventListener exoPlayerEventListener; private final ComponentListener componentListener;
private final MediaSessionCallback mediaSessionCallback;
private final ArrayList<CommandReceiver> commandReceivers; private final ArrayList<CommandReceiver> commandReceivers;
private Player player;
private ControlDispatcher controlDispatcher; private ControlDispatcher controlDispatcher;
private CustomActionProvider[] customActionProviders; private CustomActionProvider[] customActionProviders;
private Map<String, CustomActionProvider> customActionMap; private Map<String, CustomActionProvider> customActionMap;
@Nullable private MediaMetadataProvider mediaMetadataProvider;
@Nullable private Player player;
@Nullable private ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider; @Nullable private ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
@Nullable private Pair<Integer, CharSequence> customError; @Nullable private Pair<Integer, CharSequence> customError;
private PlaybackPreparer playbackPreparer; @Nullable private PlaybackPreparer playbackPreparer;
private QueueNavigator queueNavigator; @Nullable private QueueNavigator queueNavigator;
private QueueEditor queueEditor; @Nullable private QueueEditor queueEditor;
private RatingCallback ratingCallback; @Nullable private RatingCallback ratingCallback;
private long enabledPlaybackActions; private long enabledPlaybackActions;
private int rewindMs; private int rewindMs;
@ -362,82 +362,60 @@ public final class MediaSessionConnector {
/** /**
* Creates an instance. * Creates an instance.
* *
* <p>Equivalent to {@code MediaSessionConnector(mediaSession, new
* DefaultMediaMetadataProvider(mediaSession.getController(), null))}.
*
* @param mediaSession The {@link MediaSessionCompat} to connect to. * @param mediaSession The {@link MediaSessionCompat} to connect to.
*/ */
public MediaSessionConnector(MediaSessionCompat mediaSession) { public MediaSessionConnector(MediaSessionCompat mediaSession) {
this(
mediaSession,
new DefaultMediaMetadataProvider(mediaSession.getController(), null));
}
/**
* Creates an instance.
*
* @param mediaSession The {@link MediaSessionCompat} to connect to.
* @param mediaMetadataProvider A {@link MediaMetadataProvider} for providing a custom metadata
* object to be published to the media session, or {@code null} if metadata shouldn't be
* published.
*/
public MediaSessionConnector(
MediaSessionCompat mediaSession,
@Nullable MediaMetadataProvider mediaMetadataProvider) {
this.mediaSession = mediaSession; this.mediaSession = mediaSession;
this.mediaMetadataProvider = mediaMetadataProvider; looper = Util.getLooper();
mediaSession.setFlags(BASE_MEDIA_SESSION_FLAGS); componentListener = new ComponentListener();
mediaSessionCallback = new MediaSessionCallback();
exoPlayerEventListener = new ExoPlayerEventListener();
controlDispatcher = new DefaultControlDispatcher();
customActionMap = Collections.emptyMap();
commandReceivers = new ArrayList<>(); commandReceivers = new ArrayList<>();
controlDispatcher = new DefaultControlDispatcher();
customActionProviders = new CustomActionProvider[0];
customActionMap = Collections.emptyMap();
mediaMetadataProvider =
new DefaultMediaMetadataProvider(
mediaSession.getController(), /* metadataExtrasPrefix= */ null);
enabledPlaybackActions = DEFAULT_PLAYBACK_ACTIONS; enabledPlaybackActions = DEFAULT_PLAYBACK_ACTIONS;
rewindMs = DEFAULT_REWIND_MS; rewindMs = DEFAULT_REWIND_MS;
fastForwardMs = DEFAULT_FAST_FORWARD_MS; fastForwardMs = DEFAULT_FAST_FORWARD_MS;
mediaSession.setFlags(BASE_MEDIA_SESSION_FLAGS);
mediaSession.setCallback(componentListener, new Handler(looper));
} }
/** /**
* Sets the player to be connected to the media session. Must be called on the same thread that is * Sets the player to be connected to the media session. Must be called on the same thread that is
* used to access the player. * used to access the player.
* *
* <p>The order in which any {@link CustomActionProvider}s are passed determines the order of the
* actions published with the playback state of the session.
*
* @param player The player to be connected to the {@code MediaSession}, or {@code null} to * @param player The player to be connected to the {@code MediaSession}, or {@code null} to
* disconnect the current player. * disconnect the current player.
* @param playbackPreparer An optional {@link PlaybackPreparer} for preparing the player.
* @param customActionProviders Optional {@link CustomActionProvider}s to publish and handle
* custom actions.
*/ */
public void setPlayer( public void setPlayer(@Nullable Player player) {
@Nullable Player player, Assertions.checkArgument(player == null || player.getApplicationLooper() == looper);
@Nullable PlaybackPreparer playbackPreparer,
CustomActionProvider... customActionProviders) {
Assertions.checkArgument(player == null || player.getApplicationLooper() == Looper.myLooper());
if (this.player != null) { if (this.player != null) {
this.player.removeListener(exoPlayerEventListener); this.player.removeListener(componentListener);
mediaSession.setCallback(null);
} }
unregisterCommandReceiver(this.playbackPreparer);
this.player = player; this.player = player;
this.playbackPreparer = playbackPreparer;
registerCommandReceiver(playbackPreparer);
this.customActionProviders =
(player != null && customActionProviders != null)
? customActionProviders
: new CustomActionProvider[0];
if (player != null) { if (player != null) {
Handler handler = new Handler(Util.getLooper()); player.addListener(componentListener);
mediaSession.setCallback(mediaSessionCallback, handler);
player.addListener(exoPlayerEventListener);
} }
invalidateMediaSessionPlaybackState(); invalidateMediaSessionPlaybackState();
invalidateMediaSessionMetadata(); invalidateMediaSessionMetadata();
} }
/**
* Sets the {@link PlaybackPreparer}.
*
* @param playbackPreparer The {@link PlaybackPreparer}.
*/
public void setPlaybackPreparer(@Nullable PlaybackPreparer playbackPreparer) {
if (this.playbackPreparer != playbackPreparer) {
unregisterCommandReceiver(this.playbackPreparer);
this.playbackPreparer = playbackPreparer;
registerCommandReceiver(playbackPreparer);
invalidateMediaSessionPlaybackState();
}
}
/** /**
* Sets the {@link ControlDispatcher}. * Sets the {@link ControlDispatcher}.
* *
@ -570,6 +548,32 @@ public final class MediaSessionConnector {
invalidateMediaSessionPlaybackState(); invalidateMediaSessionPlaybackState();
} }
/**
* Sets custom action providers. The order of the {@link CustomActionProvider}s determines the
* order in which the actions are published.
*
* @param customActionProviders The custom action providers, or null to remove all existing custom
* action providers.
*/
public void setCustomActionProviders(@Nullable CustomActionProvider... customActionProviders) {
this.customActionProviders =
customActionProviders == null ? new CustomActionProvider[0] : customActionProviders;
invalidateMediaSessionPlaybackState();
}
/**
* Sets a provider of metadata to be published to the media session.
*
* @param mediaMetadataProvider The provider of metadata to publish, or {@code null} if no
* metadata should be published.
*/
public void setMediaMetadataProvider(@Nullable MediaMetadataProvider mediaMetadataProvider) {
if (this.mediaMetadataProvider != mediaMetadataProvider) {
this.mediaMetadataProvider = mediaMetadataProvider;
invalidateMediaSessionMetadata();
}
}
/** /**
* Updates the metadata of the media session. * Updates the metadata of the media session.
* *
@ -577,9 +581,11 @@ public final class MediaSessionConnector {
* changed and the metadata should be updated immediately. * changed and the metadata should be updated immediately.
*/ */
public final void invalidateMediaSessionMetadata() { public final void invalidateMediaSessionMetadata() {
if (mediaMetadataProvider != null && player != null) { MediaMetadataCompat metadata =
mediaSession.setMetadata(mediaMetadataProvider.getMetadata(player)); mediaMetadataProvider != null && player != null
} ? mediaMetadataProvider.getMetadata(player)
: null;
mediaSession.setMetadata(metadata);
} }
/** /**
@ -591,7 +597,7 @@ public final class MediaSessionConnector {
public final void invalidateMediaSessionPlaybackState() { public final void invalidateMediaSessionPlaybackState() {
PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder(); PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
if (player == null) { if (player == null) {
builder.setActions(/* capabilities= */ 0).setState(PlaybackStateCompat.STATE_NONE, 0, 0, 0); builder.setActions(buildPrepareActions()).setState(PlaybackStateCompat.STATE_NONE, 0, 0, 0);
mediaSession.setPlaybackState(builder.build()); mediaSession.setPlaybackState(builder.build());
return; return;
} }
@ -627,7 +633,7 @@ public final class MediaSessionConnector {
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putFloat(EXTRAS_PITCH, player.getPlaybackParameters().pitch); extras.putFloat(EXTRAS_PITCH, player.getPlaybackParameters().pitch);
builder builder
.setActions(buildPlaybackActions(player)) .setActions(buildPrepareActions() | buildPlaybackActions(player))
.setActiveQueueItemId(activeQueueItemId) .setActiveQueueItemId(activeQueueItemId)
.setBufferedPosition(player.getBufferedPosition()) .setBufferedPosition(player.getBufferedPosition())
.setState( .setState(
@ -662,6 +668,12 @@ public final class MediaSessionConnector {
commandReceivers.remove(commandReceiver); commandReceivers.remove(commandReceiver);
} }
private long buildPrepareActions() {
return playbackPreparer == null
? 0
: (PlaybackPreparer.ACTIONS & playbackPreparer.getSupportedPrepareActions());
}
private long buildPlaybackActions(Player player) { private long buildPlaybackActions(Player player) {
boolean enableSeeking = false; boolean enableSeeking = false;
boolean enableRewind = false; boolean enableRewind = false;
@ -688,9 +700,6 @@ public final class MediaSessionConnector {
playbackActions &= enabledPlaybackActions; playbackActions &= enabledPlaybackActions;
long actions = playbackActions; long actions = playbackActions;
if (playbackPreparer != null) {
actions |= (PlaybackPreparer.ACTIONS & playbackPreparer.getSupportedPrepareActions());
}
if (queueNavigator != null) { if (queueNavigator != null) {
actions |= actions |=
(QueueNavigator.ACTIONS & queueNavigator.getSupportedQueueNavigatorActions(player)); (QueueNavigator.ACTIONS & queueNavigator.getSupportedQueueNavigatorActions(player));
@ -719,8 +728,7 @@ public final class MediaSessionConnector {
} }
private boolean canDispatchToPlaybackPreparer(long action) { private boolean canDispatchToPlaybackPreparer(long action) {
return player != null return playbackPreparer != null
&& playbackPreparer != null
&& (playbackPreparer.getSupportedPrepareActions() & action) != 0; && (playbackPreparer.getSupportedPrepareActions() & action) != 0;
} }
@ -738,6 +746,13 @@ public final class MediaSessionConnector {
return player != null && queueEditor != null; return player != null && queueEditor != null;
} }
private void stopPlayerForPrepare(boolean playWhenReady) {
if (player != null) {
player.stop();
player.setPlayWhenReady(playWhenReady);
}
}
private void rewind(Player player) { private void rewind(Player player) {
if (player.isCurrentWindowSeekable() && rewindMs > 0) { if (player.isCurrentWindowSeekable() && rewindMs > 0) {
seekTo(player, player.getCurrentPosition() - rewindMs); seekTo(player, player.getCurrentPosition() - rewindMs);
@ -865,11 +880,14 @@ public final class MediaSessionConnector {
} }
} }
private class ExoPlayerEventListener implements Player.EventListener { private class ComponentListener extends MediaSessionCompat.Callback
implements Player.EventListener {
private int currentWindowIndex; private int currentWindowIndex;
private int currentWindowCount; private int currentWindowCount;
// Player.EventListener implementation.
@Override @Override
public void onTimelineChanged( public void onTimelineChanged(
Timeline timeline, @Nullable Object manifest, @Player.TimelineChangeReason int reason) { Timeline timeline, @Nullable Object manifest, @Player.TimelineChangeReason int reason) {
@ -932,9 +950,8 @@ public final class MediaSessionConnector {
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
invalidateMediaSessionPlaybackState(); invalidateMediaSessionPlaybackState();
} }
}
private class MediaSessionCallback extends MediaSessionCompat.Callback { // MediaSessionCompat.Callback implementation.
@Override @Override
public void onPlay() { public void onPlay() {
@ -1058,8 +1075,7 @@ public final class MediaSessionConnector {
@Override @Override
public void onPrepare() { public void onPrepare() {
if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PREPARE)) { if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PREPARE)) {
player.stop(); stopPlayerForPrepare(/* playWhenReady= */ false);
player.setPlayWhenReady(false);
playbackPreparer.onPrepare(); playbackPreparer.onPrepare();
} }
} }
@ -1067,8 +1083,7 @@ public final class MediaSessionConnector {
@Override @Override
public void onPrepareFromMediaId(String mediaId, Bundle extras) { public void onPrepareFromMediaId(String mediaId, Bundle extras) {
if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID)) { if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID)) {
player.stop(); stopPlayerForPrepare(/* playWhenReady= */ false);
player.setPlayWhenReady(false);
playbackPreparer.onPrepareFromMediaId(mediaId, extras); playbackPreparer.onPrepareFromMediaId(mediaId, extras);
} }
} }
@ -1076,8 +1091,7 @@ public final class MediaSessionConnector {
@Override @Override
public void onPrepareFromSearch(String query, Bundle extras) { public void onPrepareFromSearch(String query, Bundle extras) {
if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH)) { if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH)) {
player.stop(); stopPlayerForPrepare(/* playWhenReady= */ false);
player.setPlayWhenReady(false);
playbackPreparer.onPrepareFromSearch(query, extras); playbackPreparer.onPrepareFromSearch(query, extras);
} }
} }
@ -1085,8 +1099,7 @@ public final class MediaSessionConnector {
@Override @Override
public void onPrepareFromUri(Uri uri, Bundle extras) { public void onPrepareFromUri(Uri uri, Bundle extras) {
if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PREPARE_FROM_URI)) { if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PREPARE_FROM_URI)) {
player.stop(); stopPlayerForPrepare(/* playWhenReady= */ false);
player.setPlayWhenReady(false);
playbackPreparer.onPrepareFromUri(uri, extras); playbackPreparer.onPrepareFromUri(uri, extras);
} }
} }
@ -1094,8 +1107,7 @@ public final class MediaSessionConnector {
@Override @Override
public void onPlayFromMediaId(String mediaId, Bundle extras) { public void onPlayFromMediaId(String mediaId, Bundle extras) {
if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID)) { if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID)) {
player.stop(); stopPlayerForPrepare(/* playWhenReady= */ true);
player.setPlayWhenReady(true);
playbackPreparer.onPrepareFromMediaId(mediaId, extras); playbackPreparer.onPrepareFromMediaId(mediaId, extras);
} }
} }
@ -1103,8 +1115,7 @@ public final class MediaSessionConnector {
@Override @Override
public void onPlayFromSearch(String query, Bundle extras) { public void onPlayFromSearch(String query, Bundle extras) {
if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH)) { if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH)) {
player.stop(); stopPlayerForPrepare(/* playWhenReady= */ true);
player.setPlayWhenReady(true);
playbackPreparer.onPrepareFromSearch(query, extras); playbackPreparer.onPrepareFromSearch(query, extras);
} }
} }
@ -1112,8 +1123,7 @@ public final class MediaSessionConnector {
@Override @Override
public void onPlayFromUri(Uri uri, Bundle extras) { public void onPlayFromUri(Uri uri, Bundle extras) {
if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PLAY_FROM_URI)) { if (canDispatchToPlaybackPreparer(PlaybackStateCompat.ACTION_PLAY_FROM_URI)) {
player.stop(); stopPlayerForPrepare(/* playWhenReady= */ true);
player.setPlayWhenReady(true);
playbackPreparer.onPrepareFromUri(uri, extras); playbackPreparer.onPrepareFromUri(uri, extras);
} }
} }

View File

@ -22,9 +22,7 @@ import com.google.android.exoplayer2.ControlDispatcher;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.util.RepeatModeUtil; import com.google.android.exoplayer2.util.RepeatModeUtil;
/** /** Provides a custom action for toggling repeat modes. */
* Provides a custom action for toggling repeat modes.
*/
public final class RepeatModeActionProvider implements MediaSessionConnector.CustomActionProvider { public final class RepeatModeActionProvider implements MediaSessionConnector.CustomActionProvider {
/** The default repeat toggle modes. */ /** The default repeat toggle modes. */

View File

@ -41,7 +41,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
private final MediaSessionCompat mediaSession; private final MediaSessionCompat mediaSession;
private final Timeline.Window window; private final Timeline.Window window;
protected final int maxQueueSize; private final int maxQueueSize;
private long activeQueueItemId; private long activeQueueItemId;