Take care playback preparer and queue navigator can not register overlapping

playback actions.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=162736210
This commit is contained in:
bachinger 2017-07-21 06:56:03 -07:00 committed by Oliver Woodman
parent 3bc3900dba
commit 6f600a8fa5
3 changed files with 330 additions and 183 deletions

View File

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

View File

@ -47,11 +47,11 @@ import java.util.Map;
* Mediates between a {@link MediaSessionCompat} and an {@link Player} instance set with * Mediates between a {@link MediaSessionCompat} and an {@link Player} instance set with
* {@link #setPlayer(Player, PlaybackPreparer, CustomActionProvider...)}. * {@link #setPlayer(Player, PlaybackPreparer, CustomActionProvider...)}.
* <p> * <p>
* By default the {@code MediaSessionConnector} listens for {@link #DEFAULT_PLAYBACK_ACTIONS} sent * The {@code MediaSessionConnector} listens for media actions sent by a media controller and
* by a media controller and realizes these actions by calling appropriate ExoPlayer methods. * realizes these actions by calling appropriate ExoPlayer methods. Further, the state of ExoPlayer
* Further, the state of ExoPlayer will be synced automatically with the {@link PlaybackStateCompat} * will be synced automatically with the {@link PlaybackStateCompat} of the media session to
* of the media session to broadcast state transitions to clients. You can optionally extend this * broadcast state transitions to clients. You can optionally extend this behaviour by providing
* behaviour by providing various collaborators. * various collaborators.
* <p> * <p>
* Media actions to initiate media playback ({@code PlaybackStateCompat#ACTION_PREPARE_*} and * Media actions to initiate media playback ({@code PlaybackStateCompat#ACTION_PREPARE_*} and
* {@code PlaybackStateCompat#ACTION_PLAY_*} need to be handled by a {@link PlaybackPreparer} which * {@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"); 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 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 * Interface to which media controller commands regarding preparing playback for a given media
* clip are delegated to. * clip are delegated to.
@ -108,12 +84,30 @@ public final class MediaSessionConnector {
* Normally preparing playback includes preparing the player with a * Normally preparing playback includes preparing the player with a
* {@link com.google.android.exoplayer2.source.MediaSource} and setting up the media session queue * {@link com.google.android.exoplayer2.source.MediaSource} and setting up the media session queue
* with a corresponding list of queue items. * with a corresponding list of queue items.
* <p>
* 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()}. * 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. * {#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. * Called when the timeline of the player has changed.
* *
@ -186,7 +243,17 @@ public final class MediaSessionConnector {
/** /**
* Editor to manipulate the queue. * 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)}. * See {@link MediaSessionCompat.Callback#onAddQueueItem(MediaDescriptionCompat description)}.
*/ */
@ -254,13 +321,11 @@ public final class MediaSessionConnector {
private final boolean doMaintainMetadata; private final boolean doMaintainMetadata;
private final ExoPlayerEventListener exoPlayerEventListener; private final ExoPlayerEventListener exoPlayerEventListener;
private final MediaSessionCallback mediaSessionCallback; private final MediaSessionCallback mediaSessionCallback;
private final PlaybackController playbackController;
private Player player; private Player player;
private CustomActionProvider[] customActionProviders; private CustomActionProvider[] customActionProviders;
private int currentWindowIndex; private int currentWindowIndex;
private long playbackActions;
private long fastForwardIncrementMs;
private long rewindIncrementMs;
private Map<String, CustomActionProvider> customActionMap; private Map<String, CustomActionProvider> customActionMap;
private ErrorMessageProvider errorMessageProvider; private ErrorMessageProvider errorMessageProvider;
private PlaybackPreparer playbackPreparer; private PlaybackPreparer playbackPreparer;
@ -270,7 +335,7 @@ public final class MediaSessionConnector {
/** /**
* Creates a {@code MediaSessionConnector}. This is equivalent to calling * Creates a {@code MediaSessionConnector}. This is equivalent to calling
* {@code #MediaSessionConnector(mediaSession, true)}. * {@code #MediaSessionConnector(mediaSession, new DefaultPlaybackController)}.
* <p> * <p>
* Constructing the {@link MediaSessionConnector} needs to be done on the same thread as * Constructing the {@link MediaSessionConnector} needs to be done on the same thread as
* constructing the player instance. * constructing the player instance.
@ -278,7 +343,22 @@ public final class MediaSessionConnector {
* @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, true); this(mediaSession, new DefaultPlaybackController());
}
/**
* Creates a {@code MediaSessionConnector}. This is equivalent to calling
* {@code #MediaSessionConnector(mediaSession, playbackController, true)}.
* <p>
* 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. * constructing the player instance.
* *
* @param mediaSession The {@link MediaSessionCompat} to connect to. * @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 * @param doMaintainMetadata Sets whether the connector should maintain the metadata of the
* session. * session.
*/ */
public MediaSessionConnector(MediaSessionCompat mediaSession, boolean doMaintainMetadata) { public MediaSessionConnector(MediaSessionCompat mediaSession,
PlaybackController playbackController, boolean doMaintainMetadata) {
this.mediaSession = mediaSession; this.mediaSession = mediaSession;
this.playbackController = playbackController;
this.handler = new Handler(Looper.myLooper() != null ? Looper.myLooper() this.handler = new Handler(Looper.myLooper() != null ? Looper.myLooper()
: Looper.getMainLooper()); : Looper.getMainLooper());
this.doMaintainMetadata = doMaintainMetadata; this.doMaintainMetadata = doMaintainMetadata;
@ -305,10 +388,7 @@ public final class MediaSessionConnector {
mediaController = mediaSession.getController(); mediaController = mediaSession.getController();
mediaSessionCallback = new MediaSessionCallback(); mediaSessionCallback = new MediaSessionCallback();
exoPlayerEventListener = new ExoPlayerEventListener(); exoPlayerEventListener = new ExoPlayerEventListener();
playbackActions = DEFAULT_PLAYBACK_ACTIONS;
customActionMap = Collections.emptyMap(); customActionMap = Collections.emptyMap();
fastForwardIncrementMs = DEFAULT_FAST_FORWARD_MS;
rewindIncrementMs = DEFAULT_REWIND_MS;
} }
/** /**
@ -343,68 +423,6 @@ public final class MediaSessionConnector {
updateMediaSessionMetadata(); 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}. * 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 * Sets the {@link QueueNavigator} to handle queue navigation actions {@code ACTION_SKIP_TO_NEXT},
* {@code ACTION_SKIP_TO_NEXT}, {@code ACTION_SKIP_TO_PREVIOUS}, * {@code ACTION_SKIP_TO_PREVIOUS}, {@code ACTION_SKIP_TO_QUEUE_ITEM} and
* {@code ACTION_SKIP_TO_QUEUE_ITEM} and {@code ACTION_SET_SHUFFLE_MODE_ENABLED}. * {@code ACTION_SET_SHUFFLE_MODE_ENABLED}.
* *
* @param queueNavigator The navigator to handle queue navigation. * @param queueNavigator The navigator to handle queue navigation.
*/ */
public void setQueueNavigator(QueueNavigator queueNavigator) { public void setQueueNavigator(QueueNavigator queueNavigator) {
if (this.queueNavigator != null) {
removePlaybackActions(this.queueNavigator.getSupportedPlaybackActions());
}
this.queueNavigator = queueNavigator; 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. * @param queueEditor The editor to handle queue manipulation actions.
*/ */
public void setQueueEditor(QueueEditor queueEditor) { public void setQueueEditor(QueueEditor queueEditor) {
if (this.queueEditor != null) {
removePlaybackActions(this.queueEditor.getSupportedPlaybackActions());
}
this.queueEditor = queueEditor; this.queueEditor = queueEditor;
if (queueEditor != null) {
addPlaybackActions(queueEditor.getSupportedPlaybackActions());
}
} }
private void setPlaybackPreparer(PlaybackPreparer playbackPreparer) { private void setPlaybackPreparer(PlaybackPreparer playbackPreparer) {
if (this.playbackPreparer != null) {
removePlaybackActions(this.playbackPreparer.getSupportedPlaybackActions());
}
this.playbackPreparer = playbackPreparer; this.playbackPreparer = playbackPreparer;
if (playbackPreparer != null) {
addPlaybackActions(playbackPreparer.getSupportedPlaybackActions());
}
} }
private void updateMediaSessionPlaybackState() { private void updateMediaSessionPlaybackState() {
PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder(); PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
if (player == null) { 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()); mediaSession.setPlaybackState(builder.build());
return; return;
} }
@ -487,10 +487,9 @@ public final class MediaSessionConnector {
} }
long activeQueueItemId = queueNavigator != null ? queueNavigator.getActiveQueueItemId(player) long activeQueueItemId = queueNavigator != null ? queueNavigator.getActiveQueueItemId(player)
: MediaSessionCompat.QueueItem.UNKNOWN_ID; : MediaSessionCompat.QueueItem.UNKNOWN_ID;
updatePlaybackActions(activeQueueItemId);
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putFloat(EXTRAS_PITCH, player.getPlaybackParameters().pitch); extras.putFloat(EXTRAS_PITCH, player.getPlaybackParameters().pitch);
builder.setActions(playbackActions) builder.setActions(buildPlaybackActions())
.setActiveQueueItemId(activeQueueItemId) .setActiveQueueItemId(activeQueueItemId)
.setBufferedPosition(player.getBufferedPosition()) .setBufferedPosition(player.getBufferedPosition())
.setState(sessionPlaybackState, player.getCurrentPosition(), .setState(sessionPlaybackState, player.getCurrentPosition(),
@ -499,24 +498,23 @@ public final class MediaSessionConnector {
mediaSession.setPlaybackState(builder.build()); mediaSession.setPlaybackState(builder.build());
} }
private void updatePlaybackActions(long activeQueueItemId) { private long buildPlaybackActions() {
List<MediaSessionCompat.QueueItem> queue = mediaController.getQueue(); long actions = 0;
if (queue == null || queue.size() < 2) { if (playbackController != null) {
removePlaybackActions(PlaybackStateCompat.ACTION_SKIP_TO_NEXT actions |= (PlaybackController.ACTIONS & playbackController
| PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS); .getSupportedPlaybackActions(player));
} else if (player.getRepeatMode() != Player.REPEAT_MODE_OFF) {
addPlaybackActions(PlaybackStateCompat.ACTION_SKIP_TO_NEXT
| PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS);
} else if (activeQueueItemId == queue.get(0).getQueueId()) {
removePlaybackActions(PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS);
addPlaybackActions(PlaybackStateCompat.ACTION_SKIP_TO_NEXT);
} else if (activeQueueItemId == queue.get(queue.size() - 1).getQueueId()) {
removePlaybackActions(PlaybackStateCompat.ACTION_SKIP_TO_NEXT);
addPlaybackActions(PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS);
} else {
addPlaybackActions(PlaybackStateCompat.ACTION_SKIP_TO_NEXT
| PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS);
} }
if (playbackPreparer != null) {
actions |= (PlaybackPreparer.ACTIONS & playbackPreparer.getSupportedPrepareActions());
}
if (queueNavigator != null) {
actions |= (QueueNavigator.ACTIONS & queueNavigator.getSupportedQueueNavigatorActions(
player));
}
if (queueEditor != null) {
actions |= (QueueEditor.ACTIONS & queueEditor.getSupportedQueueEditorActions(player));
}
return actions;
} }
private void updateMediaSessionMetadata() { private void updateMediaSessionMetadata() {
@ -584,16 +582,24 @@ public final class MediaSessionConnector {
} }
} }
private boolean isActionPublished(long action) { private boolean canDispatchToPlaybackPreparer(long action) {
return (playbackActions & action) != 0; return playbackPreparer != null && (playbackPreparer.getSupportedPrepareActions()
& PlaybackPreparer.ACTIONS & action) != 0;
}
private boolean canDispatchToPlaybackController(long action) {
return playbackController != null && (playbackController.getSupportedPlaybackActions(player)
& PlaybackController.ACTIONS & action) != 0;
} }
private boolean canDispatchToQueueNavigator(long action) { private boolean canDispatchToQueueNavigator(long action) {
return queueNavigator != null && isActionPublished(action); return queueNavigator != null && (queueNavigator.getSupportedQueueNavigatorActions(player)
& QueueNavigator.ACTIONS & action) != 0;
} }
private boolean canDispatchToPlaybackPreparer(long action) { private boolean canDispatchToQueueEditor(long action) {
return playbackPreparer != null && isActionPublished(action); return queueEditor != null && (queueEditor.getSupportedQueueEditorActions(player)
& QueueEditor.ACTIONS & action) != 0;
} }
private class ExoPlayerEventListener implements Player.EventListener { private class ExoPlayerEventListener implements Player.EventListener {
@ -658,37 +664,44 @@ public final class MediaSessionConnector {
@Override @Override
public void onPlay() { public void onPlay() {
player.setPlayWhenReady(true); if (canDispatchToPlaybackController(PlaybackStateCompat.ACTION_PLAY)) {
playbackController.onPlay(player);
}
} }
@Override @Override
public void onPause() { public void onPause() {
player.setPlayWhenReady(false); if (canDispatchToPlaybackController(PlaybackStateCompat.ACTION_PAUSE)) {
playbackController.onPause(player);
}
} }
@Override @Override
public void onSeekTo(long position) { public void onSeekTo(long position) {
long duration = player.getDuration(); if (canDispatchToPlaybackController(PlaybackStateCompat.ACTION_SEEK_TO)) {
if (duration != C.TIME_UNSET) { playbackController.onSeekTo(player, position);
position = Math.min(position, duration);
} }
player.seekTo(Math.max(position, 0));
} }
@Override @Override
public void onFastForward() { public void onFastForward() {
if (fastForwardIncrementMs <= 0) { if (canDispatchToPlaybackController(PlaybackStateCompat.ACTION_FAST_FORWARD)) {
return; playbackController.onFastForward(player);
} }
onSeekTo(player.getCurrentPosition() + fastForwardIncrementMs);
} }
@Override @Override
public void onRewind() { public void onRewind() {
if (rewindIncrementMs <= 0) { if (canDispatchToPlaybackController(PlaybackStateCompat.ACTION_REWIND)) {
return; playbackController.onRewind(player);
}
}
@Override
public void onStop() {
if (canDispatchToPlaybackController(PlaybackStateCompat.ACTION_STOP)) {
playbackController.onStop(player);
} }
onSeekTo(player.getCurrentPosition() - rewindIncrementMs);
} }
@Override @Override
@ -712,13 +725,6 @@ public final class MediaSessionConnector {
} }
} }
@Override
public void onStop() {
if (isActionPublished(PlaybackStateCompat.ACTION_STOP)) {
player.stop();
}
}
@Override @Override
public void onSetRepeatMode(int repeatMode) { public void onSetRepeatMode(int repeatMode) {
// implemented as custom action // implemented as custom action
@ -840,7 +846,7 @@ public final class MediaSessionConnector {
@Override @Override
public void onSetRating(RatingCompat rating) { public void onSetRating(RatingCompat rating) {
if (queueEditor != null && isActionPublished(PlaybackStateCompat.ACTION_SET_RATING)) { if (canDispatchToQueueEditor(PlaybackStateCompat.ACTION_SET_RATING)) {
queueEditor.onSetRating(player, rating); queueEditor.onSetRating(player, rating);
} }
} }

View File

@ -39,7 +39,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
public static final int DEFAULT_MAX_QUEUE_SIZE = 10; public static final int DEFAULT_MAX_QUEUE_SIZE = 10;
private final MediaSessionCompat mediaSession; private final MediaSessionCompat mediaSession;
private final int maxQueueSize; protected final int maxQueueSize;
private long activeQueueItemId; private long activeQueueItemId;
@ -80,19 +80,42 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
*/ */
public abstract MediaDescriptionCompat getMediaDescription(int windowIndex); public abstract MediaDescriptionCompat getMediaDescription(int windowIndex);
/**
* Supports the following media actions: {@code PlaybackStateCompat.ACTION_SKIP_TO_NEXT |
* PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS | PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM}.
*
* @return The bit mask of the supported media actions.
*/
@Override @Override
public long getSupportedPlaybackActions() { public long getSupportedQueueNavigatorActions(Player player) {
return PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS if (player == null || player.getCurrentTimeline().getWindowCount() < 2) {
| PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; return 0;
}
if (player.getRepeatMode() != Player.REPEAT_MODE_OFF) {
return PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
| PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM;
}
int currentWindowIndex = player.getCurrentWindowIndex();
long actions;
if (currentWindowIndex == 0) {
actions = PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
} else if (currentWindowIndex == player.getCurrentTimeline().getWindowCount() - 1) {
actions = PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
} else {
actions = PlaybackStateCompat.ACTION_SKIP_TO_NEXT
| PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
}
return actions | PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM;
} }
@Override @Override
public void onTimelineChanged(Player player) { public final void onTimelineChanged(Player player) {
publishFloatingQueueWindow(player); publishFloatingQueueWindow(player);
} }
@Override @Override
public void onCurrentWindowIndexChanged(Player player) { public final void onCurrentWindowIndexChanged(Player player) {
if (activeQueueItemId == MediaSessionCompat.QueueItem.UNKNOWN_ID if (activeQueueItemId == MediaSessionCompat.QueueItem.UNKNOWN_ID
|| player.getCurrentTimeline().getWindowCount() > maxQueueSize) { || player.getCurrentTimeline().getWindowCount() > maxQueueSize) {
publishFloatingQueueWindow(player); publishFloatingQueueWindow(player);
@ -107,7 +130,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
} }
@Override @Override
public final void onSkipToPrevious(Player player) { public void onSkipToPrevious(Player player) {
Timeline timeline = player.getCurrentTimeline(); Timeline timeline = player.getCurrentTimeline();
if (timeline.isEmpty()) { if (timeline.isEmpty()) {
return; return;
@ -123,7 +146,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
} }
@Override @Override
public final void onSkipToQueueItem(Player player, long id) { public void onSkipToQueueItem(Player player, long id) {
Timeline timeline = player.getCurrentTimeline(); Timeline timeline = player.getCurrentTimeline();
if (timeline.isEmpty()) { if (timeline.isEmpty()) {
return; return;
@ -135,7 +158,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
} }
@Override @Override
public final void onSkipToNext(Player player) { public void onSkipToNext(Player player) {
Timeline timeline = player.getCurrentTimeline(); Timeline timeline = player.getCurrentTimeline();
if (timeline.isEmpty()) { if (timeline.isEmpty()) {
return; return;