Simplify MediaSessionConnector

- Make MediaSessionConnector use a ControlDispatcher and add setControlDispatcher,
  setFastForwardIncrementMs and setRewindIncrementMs methods. This brings it in line
  with our other UI components, including PlayerControlView and
  PlayerNotificationManager.
- Collapsed DefaultPlaybackController into MediaSessionConnector, since I'm not
  sure there's a legitimate alternative implementation (note ControlDispatcher does
  provide some equivalent functionality e.g. to modify calls being made on the
  player).
- Pass the Player and ControlDispatcher to command receivers and custom actions,
  so they don't need their own references.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=220822082
This commit is contained in:
olly 2018-11-09 10:14:58 -08:00 committed by Oliver Woodman
parent 8ec757ad2b
commit 063f5704cd
5 changed files with 283 additions and 327 deletions

View File

@ -1,158 +0,0 @@
/*
* 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.os.Bundle;
import android.os.ResultReceiver;
import android.support.v4.media.session.PlaybackStateCompat;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
/**
* A default implementation of {@link MediaSessionConnector.PlaybackController}.
* <p>
* Methods can be safely overridden by subclasses to intercept calls for given actions.
*/
public class DefaultPlaybackController implements MediaSessionConnector.PlaybackController {
/**
* The default fast forward increment, in milliseconds.
*/
public static final int DEFAULT_FAST_FORWARD_MS = 15000;
/**
* The default rewind increment, in milliseconds.
*/
public static final int DEFAULT_REWIND_MS = 5000;
private static final long BASE_ACTIONS = PlaybackStateCompat.ACTION_PLAY_PAUSE
| PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE
| PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE
| PlaybackStateCompat.ACTION_SET_REPEAT_MODE;
protected final long rewindIncrementMs;
protected final long fastForwardIncrementMs;
/**
* Creates a new instance.
*
* <p>Equivalent to {@code DefaultPlaybackController(DefaultPlaybackController.DEFAULT_REWIND_MS,
* DefaultPlaybackController.DEFAULT_FAST_FORWARD_MS)}.
*/
public DefaultPlaybackController() {
this(DEFAULT_REWIND_MS, DEFAULT_FAST_FORWARD_MS);
}
/**
* Creates a new instance with the given fast forward and rewind increments.
*
* @param rewindIncrementMs The rewind increment in milliseconds. A zero or negative value will
* cause the rewind action to be disabled.
* @param fastForwardIncrementMs The fast forward increment in milliseconds. A zero or negative
*/
public DefaultPlaybackController(long rewindIncrementMs, long fastForwardIncrementMs) {
this.rewindIncrementMs = rewindIncrementMs;
this.fastForwardIncrementMs = fastForwardIncrementMs;
}
@Override
public long getSupportedPlaybackActions(Player player) {
if (player == null || player.getCurrentTimeline().isEmpty()) {
return 0;
} else if (!player.isCurrentWindowSeekable()) {
return BASE_ACTIONS;
}
long actions = BASE_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 positionMs) {
long duration = player.getDuration();
if (duration != C.TIME_UNSET) {
positionMs = Math.min(positionMs, duration);
}
player.seekTo(Math.max(positionMs, 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(true);
}
@Override
public void onSetShuffleMode(Player player, int shuffleMode) {
player.setShuffleModeEnabled(shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_ALL
|| shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP);
}
@Override
public void onSetRepeatMode(Player player, int mediaSessionRepeatMode) {
int repeatMode;
switch (mediaSessionRepeatMode) {
case PlaybackStateCompat.REPEAT_MODE_ALL:
case PlaybackStateCompat.REPEAT_MODE_GROUP:
repeatMode = Player.REPEAT_MODE_ALL;
break;
case PlaybackStateCompat.REPEAT_MODE_ONE:
repeatMode = Player.REPEAT_MODE_ONE;
break;
default:
repeatMode = Player.REPEAT_MODE_OFF;
break;
}
player.setRepeatMode(repeatMode);
}
// CommandReceiver implementation.
@Override
public boolean onCommand(Player player, String command, Bundle extras, ResultReceiver cb) {
return false;
}
}

View File

@ -22,6 +22,7 @@ import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.ResultReceiver; import android.os.ResultReceiver;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.LongDef;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.media.MediaDescriptionCompat; import android.support.v4.media.MediaDescriptionCompat;
@ -32,6 +33,8 @@ import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat; import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Pair; import android.util.Pair;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ControlDispatcher;
import com.google.android.exoplayer2.DefaultControlDispatcher;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
@ -39,7 +42,10 @@ import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ErrorMessageProvider; import com.google.android.exoplayer2.util.ErrorMessageProvider;
import com.google.android.exoplayer2.util.RepeatModeUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -76,7 +82,52 @@ public final class MediaSessionConnector {
ExoPlayerLibraryInfo.registerModule("goog.exo.mediasession"); ExoPlayerLibraryInfo.registerModule("goog.exo.mediasession");
} }
/** Playback actions supported by the connector. */
@LongDef(
flag = true,
value = {
PlaybackStateCompat.ACTION_PLAY_PAUSE,
PlaybackStateCompat.ACTION_PLAY,
PlaybackStateCompat.ACTION_PAUSE,
PlaybackStateCompat.ACTION_SEEK_TO,
PlaybackStateCompat.ACTION_FAST_FORWARD,
PlaybackStateCompat.ACTION_REWIND,
PlaybackStateCompat.ACTION_STOP,
PlaybackStateCompat.ACTION_SET_REPEAT_MODE,
PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE
})
@Retention(RetentionPolicy.SOURCE)
public @interface PlaybackActions {}
@PlaybackActions
public static final long ALL_PLAYBACK_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
| PlaybackStateCompat.ACTION_SET_REPEAT_MODE
| PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE;
/** The default playback actions. */
@PlaybackActions public static final long DEFAULT_PLAYBACK_ACTIONS = ALL_PLAYBACK_ACTIONS;
/** The default fast forward increment, in milliseconds. */
public static final int DEFAULT_FAST_FORWARD_MS = 15000;
/** The default rewind increment, in milliseconds. */
public static final int DEFAULT_REWIND_MS = 5000;
public static final String EXTRAS_PITCH = "EXO_PITCH"; public static final String EXTRAS_PITCH = "EXO_PITCH";
private static final long BASE_PLAYBACK_ACTIONS =
PlaybackStateCompat.ACTION_PLAY_PAUSE
| PlaybackStateCompat.ACTION_PLAY
| PlaybackStateCompat.ACTION_PAUSE
| PlaybackStateCompat.ACTION_STOP
| PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE
| PlaybackStateCompat.ACTION_SET_REPEAT_MODE;
private static final int BASE_MEDIA_SESSION_FLAGS = private static final int BASE_MEDIA_SESSION_FLAGS =
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
| MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS; | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS;
@ -86,11 +137,24 @@ public final class MediaSessionConnector {
/** Receiver of media commands sent by a media controller. */ /** Receiver of media commands sent by a media controller. */
public interface CommandReceiver { public interface CommandReceiver {
/** /**
* See {@link MediaSessionCompat.Callback#onCommand(String, Bundle, ResultReceiver)}. * See {@link MediaSessionCompat.Callback#onCommand(String, Bundle, ResultReceiver)}. The
* receiver may handle the command, but is not required to do so. Changes to the player should
* be made via the {@link ControlDispatcher}.
* *
* @return Whether the command was handled by the receiver. * @param player The player connected to the media session.
* @param controlDispatcher A {@link ControlDispatcher} that should be used for dispatching
* changes to the player.
* @param command The command name.
* @param extras Optional parameters for the command, may be null.
* @param cb A result receiver to which a result may be sent by the command, may be null.
* @return Whether the receiver handled the command.
*/ */
boolean onCommand(Player player, String command, Bundle extras, ResultReceiver cb); boolean onCommand(
Player player,
ControlDispatcher controlDispatcher,
String command,
Bundle extras,
ResultReceiver cb);
} }
/** Interface to which playback preparation actions are delegated. */ /** Interface to which playback preparation actions are delegated. */
@ -128,51 +192,6 @@ public final class MediaSessionConnector {
void onPrepareFromUri(Uri uri, Bundle extras); void onPrepareFromUri(Uri uri, Bundle extras);
} }
/** Interface to which playback actions are delegated. */
public interface PlaybackController extends CommandReceiver {
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
| PlaybackStateCompat.ACTION_SET_REPEAT_MODE
| PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE;
/**
* 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}, {@link PlaybackStateCompat#ACTION_STOP}, {@link
* PlaybackStateCompat#ACTION_SET_REPEAT_MODE} and {@link
* PlaybackStateCompat#ACTION_SET_SHUFFLE_MODE}.
*
* @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 positionMs);
/** 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);
/** See {@link MediaSessionCompat.Callback#onSetShuffleMode(int)}. */
void onSetShuffleMode(Player player, int shuffleMode);
/** See {@link MediaSessionCompat.Callback#onSetRepeatMode(int)}. */
void onSetRepeatMode(Player player, int repeatMode);
}
/** /**
* Handles queue navigation actions, and updates the media session queue by calling {@code * Handles queue navigation actions, and updates the media session queue by calling {@code
* MediaSessionCompat.setQueue()}. * MediaSessionCompat.setQueue()}.
@ -190,20 +209,20 @@ public final class MediaSessionConnector {
* PlaybackStateCompat#ACTION_SKIP_TO_NEXT}, {@link * PlaybackStateCompat#ACTION_SKIP_TO_NEXT}, {@link
* PlaybackStateCompat#ACTION_SKIP_TO_PREVIOUS}. * PlaybackStateCompat#ACTION_SKIP_TO_PREVIOUS}.
* *
* @param player The {@link Player}. * @param player The player connected to the media session.
* @return The bitmask of the supported media actions. * @return The bitmask of the supported media actions.
*/ */
long getSupportedQueueNavigatorActions(@Nullable Player player); long getSupportedQueueNavigatorActions(@Nullable Player player);
/** /**
* Called when the timeline of the player has changed. * Called when the timeline of the player has changed.
* *
* @param player The player of which the timeline has changed. * @param player The player connected to the media session.
*/ */
void onTimelineChanged(Player player); void onTimelineChanged(Player player);
/** /**
* Called when the current window index changed. * Called when the current window index changed.
* *
* @param player The player of which the current window index of the timeline has changed. * @param player The player connected to the media session.
*/ */
void onCurrentWindowIndexChanged(Player player); void onCurrentWindowIndexChanged(Player player);
/** /**
@ -218,12 +237,30 @@ public final class MediaSessionConnector {
* @return The id of the active queue item. * @return The id of the active queue item.
*/ */
long getActiveQueueItemId(@Nullable Player player); long getActiveQueueItemId(@Nullable Player player);
/** See {@link MediaSessionCompat.Callback#onSkipToPrevious()}. */ /**
void onSkipToPrevious(Player player); * See {@link MediaSessionCompat.Callback#onSkipToPrevious()}.
/** See {@link MediaSessionCompat.Callback#onSkipToQueueItem(long)}. */ *
void onSkipToQueueItem(Player player, long id); * @param player The player connected to the media session.
/** See {@link MediaSessionCompat.Callback#onSkipToNext()}. */ * @param controlDispatcher A {@link ControlDispatcher} that should be used for dispatching
void onSkipToNext(Player player); * changes to the player.
*/
void onSkipToPrevious(Player player, ControlDispatcher controlDispatcher);
/**
* See {@link MediaSessionCompat.Callback#onSkipToQueueItem(long)}.
*
* @param player The player connected to the media session.
* @param controlDispatcher A {@link ControlDispatcher} that should be used for dispatching
* changes to the player.
*/
void onSkipToQueueItem(Player player, ControlDispatcher controlDispatcher, long id);
/**
* See {@link MediaSessionCompat.Callback#onSkipToNext()}.
*
* @param player The player connected to the media session.
* @param controlDispatcher A {@link ControlDispatcher} that should be used for dispatching
* changes to the player.
*/
void onSkipToNext(Player player, ControlDispatcher controlDispatcher);
} }
/** Handles media session queue edits. */ /** Handles media session queue edits. */
@ -263,19 +300,24 @@ public final class MediaSessionConnector {
/** /**
* Called when a custom action provided by this provider is sent to the media session. * Called when a custom action provided by this provider is sent to the media session.
* *
* @param player The player connected to the media session.
* @param controlDispatcher A {@link ControlDispatcher} that should be used for dispatching
* changes to the player.
* @param action The name of the action which was sent by a media controller. * @param action The name of the action which was sent by a media controller.
* @param extras Optional extras sent by a media controller. * @param extras Optional extras sent by a media controller.
*/ */
void onCustomAction(String action, Bundle extras); void onCustomAction(
Player player, ControlDispatcher controlDispatcher, String action, Bundle extras);
/** /**
* Returns a {@link PlaybackStateCompat.CustomAction} which will be published to the media * Returns a {@link PlaybackStateCompat.CustomAction} which will be published to the media
* session by the connector or {@code null} if this action should not be published at the given * session by the connector or {@code null} if this action should not be published at the given
* player state. * player state.
* *
* @param player The player connected to the media session.
* @return The custom action to be included in the session playback state or {@code null}. * @return The custom action to be included in the session playback state or {@code null}.
*/ */
PlaybackStateCompat.CustomAction getCustomAction(); PlaybackStateCompat.CustomAction getCustomAction(Player player);
} }
/** Provides a {@link MediaMetadataCompat} for a given player state. */ /** Provides a {@link MediaMetadataCompat} for a given player state. */
@ -283,7 +325,7 @@ public final class MediaSessionConnector {
/** /**
* Gets the {@link MediaMetadataCompat} to be published to the session. * Gets the {@link MediaMetadataCompat} to be published to the session.
* *
* @param player The player for which to provide metadata. * @param player The player connected to the media session.
* @return The {@link MediaMetadataCompat} to be published to the session. * @return The {@link MediaMetadataCompat} to be published to the session.
*/ */
MediaMetadataCompat getMetadata(Player player); MediaMetadataCompat getMetadata(Player player);
@ -292,47 +334,37 @@ public final class MediaSessionConnector {
/** The wrapped {@link MediaSessionCompat}. */ /** The wrapped {@link MediaSessionCompat}. */
public final MediaSessionCompat mediaSession; public final MediaSessionCompat mediaSession;
private @Nullable final MediaMetadataProvider mediaMetadataProvider; @Nullable private final MediaMetadataProvider mediaMetadataProvider;
private final ExoPlayerEventListener exoPlayerEventListener; private final ExoPlayerEventListener exoPlayerEventListener;
private final MediaSessionCallback mediaSessionCallback; private final MediaSessionCallback mediaSessionCallback;
private final PlaybackController playbackController;
private final ArrayList<CommandReceiver> commandReceivers; private final ArrayList<CommandReceiver> commandReceivers;
private Player player; private Player player;
private ControlDispatcher controlDispatcher;
private CustomActionProvider[] customActionProviders; private CustomActionProvider[] customActionProviders;
private Map<String, CustomActionProvider> customActionMap; private Map<String, CustomActionProvider> customActionMap;
private @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider; @Nullable private ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
private @Nullable Pair<Integer, CharSequence> customError; @Nullable private Pair<Integer, CharSequence> customError;
private PlaybackPreparer playbackPreparer; private PlaybackPreparer playbackPreparer;
private QueueNavigator queueNavigator; private QueueNavigator queueNavigator;
private QueueEditor queueEditor; private QueueEditor queueEditor;
private RatingCallback ratingCallback; private RatingCallback ratingCallback;
/** private long enabledPlaybackActions;
* Creates an instance. private int rewindMs;
* private int fastForwardMs;
* <p>Equivalent to {@code MediaSessionConnector(mediaSession, new DefaultPlaybackController())}.
*
* @param mediaSession The {@link MediaSessionCompat} to connect to.
*/
public MediaSessionConnector(MediaSessionCompat mediaSession) {
this(mediaSession, null);
}
/** /**
* Creates an instance. * Creates an instance.
* *
* <p>Equivalent to {@code MediaSessionConnector(mediaSession, playbackController, new * <p>Equivalent to {@code MediaSessionConnector(mediaSession, new
* DefaultMediaMetadataProvider(mediaSession.getController(), null))}. * DefaultMediaMetadataProvider(mediaSession.getController(), null))}.
* *
* @param mediaSession The {@link MediaSessionCompat} to connect to. * @param mediaSession The {@link MediaSessionCompat} to connect to.
* @param playbackController A {@link PlaybackController} for handling playback actions.
*/ */
public MediaSessionConnector( public MediaSessionConnector(MediaSessionCompat mediaSession) {
MediaSessionCompat mediaSession, PlaybackController playbackController) {
this( this(
mediaSession, mediaSession,
playbackController,
new DefaultMediaMetadataProvider(mediaSession.getController(), null)); new DefaultMediaMetadataProvider(mediaSession.getController(), null));
} }
@ -340,52 +372,24 @@ public final class MediaSessionConnector {
* Creates an instance. * Creates an instance.
* *
* @param mediaSession The {@link MediaSessionCompat} to connect to. * @param mediaSession The {@link MediaSessionCompat} to connect to.
* @param playbackController A {@link PlaybackController} for handling playback actions, or {@code
* null} if the connector should handle playback actions directly.
* @param doMaintainMetadata Whether the connector should maintain the metadata of the session.
* @param metadataExtrasPrefix A string to prefix extra keys which are propagated from the active
* queue item to the session metadata.
* @deprecated Use {@link MediaSessionConnector#MediaSessionConnector(MediaSessionCompat,
* PlaybackController, MediaMetadataProvider)}.
*/
@Deprecated
public MediaSessionConnector(
MediaSessionCompat mediaSession,
@Nullable PlaybackController playbackController,
boolean doMaintainMetadata,
@Nullable String metadataExtrasPrefix) {
this(
mediaSession,
playbackController,
doMaintainMetadata
? new DefaultMediaMetadataProvider(mediaSession.getController(), metadataExtrasPrefix)
: null);
}
/**
* Creates an instance.
*
* @param mediaSession The {@link MediaSessionCompat} to connect to.
* @param playbackController A {@link PlaybackController} for handling playback actions, or {@code
* null} if the connector should handle playback actions directly.
* @param mediaMetadataProvider A {@link MediaMetadataProvider} for providing a custom metadata * @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 * object to be published to the media session, or {@code null} if metadata shouldn't be
* published. * published.
*/ */
public MediaSessionConnector( public MediaSessionConnector(
MediaSessionCompat mediaSession, MediaSessionCompat mediaSession,
@Nullable PlaybackController playbackController,
@Nullable MediaMetadataProvider mediaMetadataProvider) { @Nullable MediaMetadataProvider mediaMetadataProvider) {
this.mediaSession = mediaSession; this.mediaSession = mediaSession;
this.playbackController =
playbackController != null ? playbackController : new DefaultPlaybackController();
this.mediaMetadataProvider = mediaMetadataProvider; this.mediaMetadataProvider = mediaMetadataProvider;
mediaSession.setFlags(BASE_MEDIA_SESSION_FLAGS); mediaSession.setFlags(BASE_MEDIA_SESSION_FLAGS);
mediaSessionCallback = new MediaSessionCallback(); mediaSessionCallback = new MediaSessionCallback();
exoPlayerEventListener = new ExoPlayerEventListener(); exoPlayerEventListener = new ExoPlayerEventListener();
controlDispatcher = new DefaultControlDispatcher();
customActionMap = Collections.emptyMap(); customActionMap = Collections.emptyMap();
commandReceivers = new ArrayList<>(); commandReceivers = new ArrayList<>();
registerCommandReceiver(playbackController); enabledPlaybackActions = DEFAULT_PLAYBACK_ACTIONS;
rewindMs = DEFAULT_REWIND_MS;
fastForwardMs = DEFAULT_FAST_FORWARD_MS;
} }
/** /**
@ -410,8 +414,8 @@ public final class MediaSessionConnector {
this.player.removeListener(exoPlayerEventListener); this.player.removeListener(exoPlayerEventListener);
mediaSession.setCallback(null); mediaSession.setCallback(null);
} }
unregisterCommandReceiver(this.playbackPreparer);
unregisterCommandReceiver(this.playbackPreparer);
this.player = player; this.player = player;
this.playbackPreparer = playbackPreparer; this.playbackPreparer = playbackPreparer;
registerCommandReceiver(playbackPreparer); registerCommandReceiver(playbackPreparer);
@ -429,6 +433,58 @@ public final class MediaSessionConnector {
invalidateMediaSessionMetadata(); invalidateMediaSessionMetadata();
} }
/**
* Sets the {@link ControlDispatcher}.
*
* @param controlDispatcher The {@link ControlDispatcher}, or null to use {@link
* DefaultControlDispatcher}.
*/
public void setControlDispatcher(@Nullable ControlDispatcher controlDispatcher) {
if (this.controlDispatcher != controlDispatcher) {
this.controlDispatcher =
controlDispatcher == null ? new DefaultControlDispatcher() : controlDispatcher;
}
}
/**
* Sets the enabled playback actions.
*
* @param enabledPlaybackActions The enabled playback actions.
*/
public void setEnabledPlaybackActions(@PlaybackActions long enabledPlaybackActions) {
enabledPlaybackActions &= ALL_PLAYBACK_ACTIONS;
if (this.enabledPlaybackActions != enabledPlaybackActions) {
this.enabledPlaybackActions = enabledPlaybackActions;
invalidateMediaSessionPlaybackState();
}
}
/**
* Sets the rewind increment in milliseconds.
*
* @param rewindMs The rewind increment in milliseconds. A non-positive value will cause the
* rewind button to be disabled.
*/
public void setRewindIncrementMs(int rewindMs) {
if (this.rewindMs != rewindMs) {
this.rewindMs = rewindMs;
invalidateMediaSessionPlaybackState();
}
}
/**
* Sets the fast forward increment in milliseconds.
*
* @param fastForwardMs The fast forward increment in milliseconds. A non-positive value will
* cause the fast forward button to be disabled.
*/
public void setFastForwardIncrementMs(int fastForwardMs) {
if (this.fastForwardMs != fastForwardMs) {
this.fastForwardMs = fastForwardMs;
invalidateMediaSessionPlaybackState();
}
}
/** /**
* Sets the optional {@link ErrorMessageProvider}. * Sets the optional {@link ErrorMessageProvider}.
* *
@ -537,7 +593,7 @@ public final class MediaSessionConnector {
Map<String, CustomActionProvider> currentActions = new HashMap<>(); Map<String, CustomActionProvider> currentActions = new HashMap<>();
for (CustomActionProvider customActionProvider : customActionProviders) { for (CustomActionProvider customActionProvider : customActionProviders) {
PlaybackStateCompat.CustomAction customAction = customActionProvider.getCustomAction(); PlaybackStateCompat.CustomAction customAction = customActionProvider.getCustomAction(player);
if (customAction != null) { if (customAction != null) {
currentActions.put(customAction.getAction(), customActionProvider); currentActions.put(customAction.getAction(), customActionProvider);
builder.addCustomAction(customAction); builder.addCustomAction(customAction);
@ -602,8 +658,20 @@ public final class MediaSessionConnector {
} }
private long buildPlaybackActions() { private long buildPlaybackActions() {
long actions = long actions = 0;
(PlaybackController.ACTIONS & playbackController.getSupportedPlaybackActions(player)); if (player != null && !player.getCurrentTimeline().isEmpty()) {
long playbackActions = BASE_PLAYBACK_ACTIONS;
if (player.isCurrentWindowSeekable()) {
playbackActions |= PlaybackStateCompat.ACTION_SEEK_TO;
if (fastForwardMs > 0) {
playbackActions |= PlaybackStateCompat.ACTION_FAST_FORWARD;
}
if (rewindMs > 0) {
playbackActions |= PlaybackStateCompat.ACTION_REWIND;
}
}
actions |= (playbackActions & enabledPlaybackActions);
}
if (playbackPreparer != null) { if (playbackPreparer != null) {
actions |= (PlaybackPreparer.ACTIONS & playbackPreparer.getSupportedPrepareActions()); actions |= (PlaybackPreparer.ACTIONS & playbackPreparer.getSupportedPrepareActions());
} }
@ -630,18 +698,15 @@ public final class MediaSessionConnector {
} }
} }
private boolean canDispatchPlaybackAction(long action) {
return (enabledPlaybackActions & action) != 0;
}
private boolean canDispatchToPlaybackPreparer(long action) { private boolean canDispatchToPlaybackPreparer(long action) {
return playbackPreparer != null return playbackPreparer != null
&& (playbackPreparer.getSupportedPrepareActions() & PlaybackPreparer.ACTIONS & action) != 0; && (playbackPreparer.getSupportedPrepareActions() & PlaybackPreparer.ACTIONS & action) != 0;
} }
private boolean canDispatchToPlaybackController(long action) {
return (playbackController.getSupportedPlaybackActions(player)
& PlaybackController.ACTIONS
& action)
!= 0;
}
private boolean canDispatchToQueueNavigator(long action) { private boolean canDispatchToQueueNavigator(long action) {
return queueNavigator != null return queueNavigator != null
&& (queueNavigator.getSupportedQueueNavigatorActions(player) && (queueNavigator.getSupportedQueueNavigatorActions(player)
@ -650,6 +715,31 @@ public final class MediaSessionConnector {
!= 0; != 0;
} }
private void rewind() {
if (rewindMs > 0) {
seekTo(player.getCurrentPosition() - rewindMs);
}
}
private void fastForward() {
if (fastForwardMs > 0) {
seekTo(player.getCurrentPosition() + fastForwardMs);
}
}
private void seekTo(long positionMs) {
seekTo(player.getCurrentWindowIndex(), positionMs);
}
private void seekTo(int windowIndex, long positionMs) {
long durationMs = player.getDuration();
if (durationMs != C.TIME_UNSET) {
positionMs = Math.min(positionMs, durationMs);
}
positionMs = Math.max(positionMs, 0);
controlDispatcher.dispatchSeekTo(player, windowIndex, positionMs);
}
/** /**
* Provides a default {@link MediaMetadataCompat} with properties and extras propagated from the * Provides a default {@link MediaMetadataCompat} with properties and extras propagated from the
* active queue item to the session metadata. * active queue item to the session metadata.
@ -825,85 +915,101 @@ public final class MediaSessionConnector {
@Override @Override
public void onPlay() { public void onPlay() {
if (canDispatchToPlaybackController(PlaybackStateCompat.ACTION_PLAY)) { if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_PLAY)) {
playbackController.onPlay(player); controlDispatcher.dispatchSetPlayWhenReady(player, /* playWhenReady= */ true);
} }
} }
@Override @Override
public void onPause() { public void onPause() {
if (canDispatchToPlaybackController(PlaybackStateCompat.ACTION_PAUSE)) { if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_PAUSE)) {
playbackController.onPause(player); controlDispatcher.dispatchSetPlayWhenReady(player, /* playWhenReady= */ false);
} }
} }
@Override @Override
public void onSeekTo(long positionMs) { public void onSeekTo(long positionMs) {
if (canDispatchToPlaybackController(PlaybackStateCompat.ACTION_SEEK_TO)) { if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_SEEK_TO)) {
playbackController.onSeekTo(player, positionMs); seekTo(positionMs);
} }
} }
@Override @Override
public void onFastForward() { public void onFastForward() {
if (canDispatchToPlaybackController(PlaybackStateCompat.ACTION_FAST_FORWARD)) { if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_FAST_FORWARD)) {
playbackController.onFastForward(player); fastForward();
} }
} }
@Override @Override
public void onRewind() { public void onRewind() {
if (canDispatchToPlaybackController(PlaybackStateCompat.ACTION_REWIND)) { if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_REWIND)) {
playbackController.onRewind(player); rewind();
} }
} }
@Override @Override
public void onStop() { public void onStop() {
if (canDispatchToPlaybackController(PlaybackStateCompat.ACTION_STOP)) { if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_STOP)) {
playbackController.onStop(player); controlDispatcher.dispatchStop(player, /* reset= */ true);
} }
} }
@Override @Override
public void onSetShuffleMode(int shuffleMode) { public void onSetShuffleMode(int shuffleMode) {
if (canDispatchToPlaybackController(PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE)) { if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE)) {
playbackController.onSetShuffleMode(player, shuffleMode); boolean shuffleModeEnabled =
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_ALL
|| shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP;
controlDispatcher.dispatchSetShuffleModeEnabled(player, shuffleModeEnabled);
} }
} }
@Override @Override
public void onSetRepeatMode(int repeatMode) { public void onSetRepeatMode(int mediaSessionRepeatMode) {
if (canDispatchToPlaybackController(PlaybackStateCompat.ACTION_SET_REPEAT_MODE)) { if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_SET_REPEAT_MODE)) {
playbackController.onSetRepeatMode(player, repeatMode); @RepeatModeUtil.RepeatToggleModes int repeatMode;
switch (mediaSessionRepeatMode) {
case PlaybackStateCompat.REPEAT_MODE_ALL:
case PlaybackStateCompat.REPEAT_MODE_GROUP:
repeatMode = Player.REPEAT_MODE_ALL;
break;
case PlaybackStateCompat.REPEAT_MODE_ONE:
repeatMode = Player.REPEAT_MODE_ONE;
break;
default:
repeatMode = Player.REPEAT_MODE_OFF;
break;
}
controlDispatcher.dispatchSetRepeatMode(player, repeatMode);
} }
} }
@Override @Override
public void onSkipToNext() { public void onSkipToNext() {
if (canDispatchToQueueNavigator(PlaybackStateCompat.ACTION_SKIP_TO_NEXT)) { if (canDispatchToQueueNavigator(PlaybackStateCompat.ACTION_SKIP_TO_NEXT)) {
queueNavigator.onSkipToNext(player); queueNavigator.onSkipToNext(player, controlDispatcher);
} }
} }
@Override @Override
public void onSkipToPrevious() { public void onSkipToPrevious() {
if (canDispatchToQueueNavigator(PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)) { if (canDispatchToQueueNavigator(PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)) {
queueNavigator.onSkipToPrevious(player); queueNavigator.onSkipToPrevious(player, controlDispatcher);
} }
} }
@Override @Override
public void onSkipToQueueItem(long id) { public void onSkipToQueueItem(long id) {
if (canDispatchToQueueNavigator(PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM)) { if (canDispatchToQueueNavigator(PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM)) {
queueNavigator.onSkipToQueueItem(player, id); queueNavigator.onSkipToQueueItem(player, controlDispatcher, id);
} }
} }
@Override @Override
public void onCustomAction(@NonNull String action, @Nullable Bundle extras) { public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
if (customActionMap.containsKey(action)) { if (customActionMap.containsKey(action)) {
customActionMap.get(action).onCustomAction(action, extras); customActionMap.get(action).onCustomAction(player, controlDispatcher, action, extras);
invalidateMediaSessionPlaybackState(); invalidateMediaSessionPlaybackState();
} }
} }
@ -911,7 +1017,7 @@ public final class MediaSessionConnector {
@Override @Override
public void onCommand(String command, Bundle extras, ResultReceiver cb) { public void onCommand(String command, Bundle extras, ResultReceiver cb) {
for (int i = 0; i < commandReceivers.size(); i++) { for (int i = 0; i < commandReceivers.size(); i++) {
if (commandReceivers.get(i).onCommand(player, command, extras, cb)) { if (commandReceivers.get(i).onCommand(player, controlDispatcher, command, extras, cb)) {
return; return;
} }
} }

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.ext.mediasession;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.media.session.PlaybackStateCompat; import android.support.v4.media.session.PlaybackStateCompat;
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;
@ -33,7 +34,6 @@ public final class RepeatModeActionProvider implements MediaSessionConnector.Cus
private static final String ACTION_REPEAT_MODE = "ACTION_EXO_REPEAT_MODE"; private static final String ACTION_REPEAT_MODE = "ACTION_EXO_REPEAT_MODE";
private final Player player;
@RepeatModeUtil.RepeatToggleModes @RepeatModeUtil.RepeatToggleModes
private final int repeatToggleModes; private final int repeatToggleModes;
private final CharSequence repeatAllDescription; private final CharSequence repeatAllDescription;
@ -42,27 +42,23 @@ public final class RepeatModeActionProvider implements MediaSessionConnector.Cus
/** /**
* Creates a new instance. * Creates a new instance.
* <p> *
* Equivalent to {@code RepeatModeActionProvider(context, player, * <p>Equivalent to {@code RepeatModeActionProvider(context, DEFAULT_REPEAT_TOGGLE_MODES)}.
* MediaSessionConnector.DEFAULT_REPEAT_TOGGLE_MODES)}.
* *
* @param context The context. * @param context The context.
* @param player The player on which to toggle the repeat mode.
*/ */
public RepeatModeActionProvider(Context context, Player player) { public RepeatModeActionProvider(Context context) {
this(context, player, DEFAULT_REPEAT_TOGGLE_MODES); this(context, DEFAULT_REPEAT_TOGGLE_MODES);
} }
/** /**
* Creates a new instance enabling the given repeat toggle modes. * Creates a new instance enabling the given repeat toggle modes.
* *
* @param context The context. * @param context The context.
* @param player The player on which to toggle the repeat mode.
* @param repeatToggleModes The toggle modes to enable. * @param repeatToggleModes The toggle modes to enable.
*/ */
public RepeatModeActionProvider(Context context, Player player, public RepeatModeActionProvider(
@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) { Context context, @RepeatModeUtil.RepeatToggleModes int repeatToggleModes) {
this.player = player;
this.repeatToggleModes = repeatToggleModes; this.repeatToggleModes = repeatToggleModes;
repeatAllDescription = context.getString(R.string.exo_media_action_repeat_all_description); repeatAllDescription = context.getString(R.string.exo_media_action_repeat_all_description);
repeatOneDescription = context.getString(R.string.exo_media_action_repeat_one_description); repeatOneDescription = context.getString(R.string.exo_media_action_repeat_one_description);
@ -70,16 +66,17 @@ public final class RepeatModeActionProvider implements MediaSessionConnector.Cus
} }
@Override @Override
public void onCustomAction(String action, Bundle extras) { public void onCustomAction(
Player player, ControlDispatcher controlDispatcher, String action, Bundle extras) {
int mode = player.getRepeatMode(); int mode = player.getRepeatMode();
int proposedMode = RepeatModeUtil.getNextRepeatMode(mode, repeatToggleModes); int proposedMode = RepeatModeUtil.getNextRepeatMode(mode, repeatToggleModes);
if (mode != proposedMode) { if (mode != proposedMode) {
player.setRepeatMode(proposedMode); controlDispatcher.dispatchSetRepeatMode(player, proposedMode);
} }
} }
@Override @Override
public PlaybackStateCompat.CustomAction getCustomAction() { public PlaybackStateCompat.CustomAction getCustomAction(Player player) {
CharSequence actionLabel; CharSequence actionLabel;
int iconResourceId; int iconResourceId;
switch (player.getRepeatMode()) { switch (player.getRepeatMode()) {

View File

@ -23,6 +23,7 @@ import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.session.MediaControllerCompat; import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.MediaSessionCompat;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ControlDispatcher;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
@ -194,7 +195,12 @@ public final class TimelineQueueEditor
// CommandReceiver implementation. // CommandReceiver implementation.
@Override @Override
public boolean onCommand(Player player, String command, Bundle extras, ResultReceiver cb) { public boolean onCommand(
Player player,
ControlDispatcher controlDispatcher,
String command,
Bundle extras,
ResultReceiver cb) {
if (!COMMAND_MOVE_QUEUE_ITEM.equals(command)) { if (!COMMAND_MOVE_QUEUE_ITEM.equals(command)) {
return false; return false;
} }

View File

@ -22,6 +22,7 @@ import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat; import android.support.v4.media.session.PlaybackStateCompat;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ControlDispatcher;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -124,7 +125,7 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
} }
@Override @Override
public void onSkipToPrevious(Player player) { public void onSkipToPrevious(Player player, ControlDispatcher controlDispatcher) {
Timeline timeline = player.getCurrentTimeline(); Timeline timeline = player.getCurrentTimeline();
if (timeline.isEmpty() || player.isPlayingAd()) { if (timeline.isEmpty() || player.isPlayingAd()) {
return; return;
@ -135,26 +136,26 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
if (previousWindowIndex != C.INDEX_UNSET if (previousWindowIndex != C.INDEX_UNSET
&& (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS
|| (window.isDynamic && !window.isSeekable))) { || (window.isDynamic && !window.isSeekable))) {
player.seekTo(previousWindowIndex, C.TIME_UNSET); controlDispatcher.dispatchSeekTo(player, previousWindowIndex, C.TIME_UNSET);
} else { } else {
player.seekTo(0); controlDispatcher.dispatchSeekTo(player, windowIndex, 0);
} }
} }
@Override @Override
public void onSkipToQueueItem(Player player, long id) { public void onSkipToQueueItem(Player player, ControlDispatcher controlDispatcher, long id) {
Timeline timeline = player.getCurrentTimeline(); Timeline timeline = player.getCurrentTimeline();
if (timeline.isEmpty() || player.isPlayingAd()) { if (timeline.isEmpty() || player.isPlayingAd()) {
return; return;
} }
int windowIndex = (int) id; int windowIndex = (int) id;
if (0 <= windowIndex && windowIndex < timeline.getWindowCount()) { if (0 <= windowIndex && windowIndex < timeline.getWindowCount()) {
player.seekTo(windowIndex, C.TIME_UNSET); controlDispatcher.dispatchSeekTo(player, windowIndex, C.TIME_UNSET);
} }
} }
@Override @Override
public void onSkipToNext(Player player) { public void onSkipToNext(Player player, ControlDispatcher controlDispatcher) {
Timeline timeline = player.getCurrentTimeline(); Timeline timeline = player.getCurrentTimeline();
if (timeline.isEmpty() || player.isPlayingAd()) { if (timeline.isEmpty() || player.isPlayingAd()) {
return; return;
@ -162,16 +163,21 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
int windowIndex = player.getCurrentWindowIndex(); int windowIndex = player.getCurrentWindowIndex();
int nextWindowIndex = player.getNextWindowIndex(); int nextWindowIndex = player.getNextWindowIndex();
if (nextWindowIndex != C.INDEX_UNSET) { if (nextWindowIndex != C.INDEX_UNSET) {
player.seekTo(nextWindowIndex, C.TIME_UNSET); controlDispatcher.dispatchSeekTo(player, nextWindowIndex, C.TIME_UNSET);
} else if (timeline.getWindow(windowIndex, window).isDynamic) { } else if (timeline.getWindow(windowIndex, window).isDynamic) {
player.seekTo(windowIndex, C.TIME_UNSET); controlDispatcher.dispatchSeekTo(player, windowIndex, C.TIME_UNSET);
} }
} }
// CommandReceiver implementation. // CommandReceiver implementation.
@Override @Override
public boolean onCommand(Player player, String command, Bundle extras, ResultReceiver cb) { public boolean onCommand(
Player player,
ControlDispatcher controlDispatcher,
String command,
Bundle extras,
ResultReceiver cb) {
return false; return false;
} }
@ -197,4 +203,3 @@ public abstract class TimelineQueueNavigator implements MediaSessionConnector.Qu
} }
} }