/* * Copyright 2019 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 androidx.media2.session; import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; import static androidx.media2.common.SessionPlayer.BUFFERING_STATE_UNKNOWN; import static androidx.media2.common.SessionPlayer.PLAYER_STATE_IDLE; import static androidx.media2.common.SessionPlayer.REPEAT_MODE_NONE; import static androidx.media2.common.SessionPlayer.SHUFFLE_MODE_NONE; import static androidx.media2.common.SessionPlayer.UNKNOWN_TIME; import android.app.PendingIntent; import android.content.Context; import android.media.AudioManager; import android.net.Uri; import android.os.Bundle; import android.os.ResultReceiver; import android.support.v4.media.MediaBrowserCompat; import android.support.v4.media.session.MediaSessionCompat; import android.text.TextUtils; import android.util.Log; import android.view.Surface; import androidx.annotation.GuardedBy; import androidx.annotation.IntDef; import androidx.annotation.IntRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; import androidx.annotation.VisibleForTesting; import androidx.core.util.ObjectsCompat; import androidx.core.util.Pair; import androidx.media.AudioAttributesCompat; import androidx.media.VolumeProviderCompat; import androidx.media2.common.MediaItem; import androidx.media2.common.MediaMetadata; import androidx.media2.common.Rating; import androidx.media2.common.SessionPlayer; import androidx.media2.common.SessionPlayer.RepeatMode; import androidx.media2.common.SessionPlayer.ShuffleMode; import androidx.media2.common.SessionPlayer.TrackInfo; import androidx.media2.common.SubtitleData; import androidx.media2.common.VideoSize; import androidx.media2.session.MediaSession.CommandButton; import androidx.versionedparcelable.ParcelField; import androidx.versionedparcelable.VersionedParcelable; import androidx.versionedparcelable.VersionedParcelize; import com.google.common.util.concurrent.ListenableFuture; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; /** * Allows an app to interact with an active {@link MediaSession} or a * {@link MediaSessionService} which would provide {@link MediaSession}. Media buttons and other * commands can be sent to the session. *
* MediaController objects are thread-safe. *
* Topic covered here: *
* ** When a controller is created with the {@link SessionToken} for a {@link MediaSession} (i.e. * session token type is {@link SessionToken#TYPE_SESSION}), the controller will connect to the * specific session. *
* When a controller is created with the {@link SessionToken} for a {@link MediaSessionService} * (i.e. session token type is {@link SessionToken#TYPE_SESSION_SERVICE} or * {@link SessionToken#TYPE_LIBRARY_SERVICE}), the controller binds to the service for connecting * to a {@link MediaSession} in it. {@link MediaSessionService} will provide a session to connect. *
* When a controller connects to a session, * {@link MediaSession.SessionCallback#onConnect(MediaSession, MediaSession.ControllerInfo)} * will be called to either accept or reject the connection. Wait * {@link ControllerCallback#onConnected(MediaController, SessionCommandGroup)} or * {@link ControllerCallback#onDisconnected(MediaController)} for the result. *
* When the connected session is closed, the controller will receive * {@link ControllerCallback#onDisconnected(MediaController)}. *
* When you're done, use {@link #close()} to clean up resources. This also helps session service
* to be destroyed when there's no controller associated with it.
*
* @see MediaSession
* @see MediaSessionService
*/
public class MediaController implements AutoCloseable {
private static final String TAG = "MediaController";
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@IntDef({AudioManager.ADJUST_LOWER, AudioManager.ADJUST_RAISE, AudioManager.ADJUST_SAME,
AudioManager.ADJUST_MUTE, AudioManager.ADJUST_UNMUTE, AudioManager.ADJUST_TOGGLE_MUTE})
@Retention(RetentionPolicy.SOURCE)
public @interface VolumeDirection {}
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@IntDef(value = {AudioManager.FLAG_SHOW_UI, AudioManager.FLAG_ALLOW_RINGER_MODES,
AudioManager.FLAG_PLAY_SOUND, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE,
AudioManager.FLAG_VIBRATE}, flag = true)
@Retention(RetentionPolicy.SOURCE)
public @interface VolumeFlags {}
final Object mLock = new Object();
@GuardedBy("mLock")
MediaControllerImpl mImpl;
@GuardedBy("mLock")
boolean mClosed;
final ControllerCallback mCallback;
final Executor mCallbackExecutor;
@GuardedBy("mLock")
private final List
* This may differ with the {@link SessionToken} from the constructor. For example, if the
* controller is created with the token for {@link MediaSessionService}, this would return
* token for the {@link MediaSession} in the service.
*
* @return SessionToken of the connected session, or {@code null} if not connected
*/
@Nullable
public SessionToken getConnectedToken() {
return isConnected() ? getImpl().getConnectedToken() : null;
}
/**
* Returns whether this class is connected to active {@link MediaSession} or not.
*/
public boolean isConnected() {
MediaControllerImpl impl = getImpl();
return impl != null && impl.isConnected();
}
/**
* Requests that the player starts or resumes playback.
*
* If the player state is {@link SessionPlayer#PLAYER_STATE_IDLE}, the session would also call
* {@link SessionPlayer#prepare} and then {@link SessionPlayer#play} to start playback. If you
* want to have finer grained control of the playback start call {@link #prepare} manually
* before this. Calling {@link #prepare} in advance would help this method to start playback
* faster and also help to take audio focus at the last moment.
*
* @see #prepare
*/
@NonNull
public ListenableFuture
* This would transfer the player state from {@link SessionPlayer#PLAYER_STATE_PLAYING} to
* {@link SessionPlayer#PLAYER_STATE_PAUSED}.
*/
@NonNull
public ListenableFuture
* This would transfer the player state from {@link SessionPlayer#PLAYER_STATE_IDLE} to
* {@link SessionPlayer#PLAYER_STATE_PAUSED}.
*
* Playback can be started without this. But this provides finer grained control of playback
* start. See {@link #play} for details.
*
* @see #play
*/
@NonNull
public ListenableFuture
* If the session is local playback, this changes the device's volume with the stream that
* session's player is using. Flags will be specified for the {@link AudioManager}.
*
* If the session is remote player (i.e. session has set volume provider), its volume provider
* will receive this request instead.
*
* @see #getPlaybackInfo()
* @param value The value to set it to, between 0 and the reported max.
* @param flags flags from {@link AudioManager} to include with the volume request for local
* playback
*/
@NonNull
public ListenableFuture
* The command will be ignored if the session does not support
* {@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE} or
* {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}.
*
* If the session is local playback, this changes the device's volume with the stream that
* session's player is using. Flags will be specified for the {@link AudioManager}.
*
* If the session is remote player (i.e. session has set volume provider), its volume provider
* will receive this request instead.
*
* @see #getPlaybackInfo()
* @param direction The direction to adjust the volume in.
* @param flags flags from {@link AudioManager} to include with the volume request for local
* playback
*/
@NonNull
public ListenableFuture
* This returns the calculated value of the position, based on the difference between the
* update time and current time.
*
* @return the current playback position in ms, or {@link SessionPlayer#UNKNOWN_TIME}
* if unknown or not connected
*/
public long getCurrentPosition() {
return isConnected() ? getImpl().getCurrentPosition() : UNKNOWN_TIME;
}
/**
* Get the lastly cached playback speed from
* {@link ControllerCallback#onPlaybackSpeedChanged(MediaController, float)}.
*
* @return speed the lastly cached playback speed, or 0f if unknown or not connected
*/
public float getPlaybackSpeed() {
return isConnected() ? getImpl().getPlaybackSpeed() : 0f;
}
/**
* Sets the playback speed. A value of {@code 1.0f} is the default playback value,
* and a negative value indicates reverse playback. {@code 0.0f} is not allowed.
*
* @throws IllegalArgumentException if the {@code speed} is equal to zero.
*/
@NonNull
public ListenableFuture
* If the user rating was {@code null}, the media item does not accept setting user rating.
*
* @param mediaId The non-empty media id
* @param rating The rating to set
*/
@NonNull
public ListenableFuture
* Interoperability: When connected to
* {@link android.support.v4.media.session.MediaSessionCompat},
* {@link SessionResult#getResultCode()} will return the custom result code from the
* {@link ResultReceiver#onReceiveResult(int, Bundle)} instead of the standard result codes
* defined in the {@link SessionResult}.
*
* A command is not accepted if it is not a custom command.
*
* @param command custom command
* @param args optional argument
*/
@NonNull
public ListenableFuture
* This list may differ with the list that was specified with
* {@link #setPlaylist(List, MediaMetadata)} depending on the {@link SessionPlayer}
* implementation. Use media items returned here for other playlist agent APIs such as
* {@link SessionPlayer#skipToPlaylistItem}.
*
* @return playlist, or {@code null} if the playlist hasn't been set, controller isn't
* connected, or it doesn't have enough permission.
* @see SessionCommand#COMMAND_CODE_PLAYER_GET_PLAYLIST
*/
@Nullable
public List
* All media IDs in the list shouldn't be an empty string.
*
* The {@link ControllerCallback#onPlaylistChanged} and
* {@link ControllerCallback#onCurrentMediaItemChanged} would be called when it's completed.
* The current item would be the first item in the playlist.
*
* @param list list of media id. Shouldn't contain an empty id.
* @param metadata metadata of the playlist
* @see #setMediaItem
* @see ControllerCallback#onCurrentMediaItemChanged
* @see ControllerCallback#onPlaylistChanged
* @see MediaMetadata#METADATA_KEY_MEDIA_ID
* @throws IllegalArgumentException if the list is {@code null} or contains any empty string.
*/
@NonNull
public ListenableFuture
* The {@link ControllerCallback#onPlaylistChanged} and
* {@link ControllerCallback#onCurrentMediaItemChanged} would be called when it's completed.
* The current item would be the item given here.
*
* @param mediaId The non-empty media id of the item to play
* @see #setPlaylist
* @see ControllerCallback#onCurrentMediaItemChanged
* @see ControllerCallback#onPlaylistChanged
* @see MediaMetadata#METADATA_KEY_MEDIA_ID
*/
@NonNull
public ListenableFuture
* This will not change the currently playing media item.
* If index is less than or equal to the current index of the playlist,
* the current index of the playlist will be incremented correspondingly.
*
* @param index the index you want to add
* @param mediaId The non-empty media id of the new item
* @see MediaMetadata#METADATA_KEY_MEDIA_ID
*/
@NonNull
public ListenableFuture
* If the item is the currently playing item of the playlist, current playback
* will be stopped and playback moves to next source in the list.
*
* @param index the media item you want to add
*/
@NonNull
public ListenableFuture
* Interoperability: When connected to
* {@link android.support.v4.media.session.MediaSessionCompat}, this will always return
* {@code -1}.
*
* @return the index of previous item in playlist, or -1 if previous media item does not exist
* or playlist hasn't been set.
*/
public int getPreviousMediaItemIndex() {
return isConnected() ? getImpl().getPreviousMediaItemIndex() : -1;
}
/**
* Gets the next item index in the playlist. The returned value can be outdated after
* {@link ControllerCallback#onCurrentMediaItemChanged(MediaController, MediaItem)} or
* {@link ControllerCallback#onPlaylistChanged(MediaController, List, MediaMetadata)} is called.
*
* Interoperability: When connected to
* {@link android.support.v4.media.session.MediaSessionCompat}, this will always return
* {@code -1}.
*
* @return the index of next item in playlist, or -1 if next media item does not exist or
* playlist hasn't been set.
*/
public int getNextMediaItemIndex() {
return isConnected() ? getImpl().getNextMediaItemIndex() : -1;
}
/**
* Skips to the previous item in the playlist.
*
* This calls {@link SessionPlayer#skipToPreviousPlaylistItem()}.
*/
@NonNull
public ListenableFuture
* This calls {@link SessionPlayer#skipToNextPlaylistItem()}.
*/
@NonNull
public ListenableFuture
* This calls {@link SessionPlayer#skipToPlaylistItem(int)}.
*
* @param index The item in the playlist you want to play
*/
@NonNull
public ListenableFuture
* This calls {@link SessionPlayer#setSurfaceInternal(Surface)}.
*
* @param surface The {@link Surface} to be used for the video portion of the media.
* @return a {@link ListenableFuture} which represents the pending completion of the command.
*
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
public ListenableFuture
* To set the token of the session for the controller to connect to, one of the
* {@link #setSessionToken(SessionToken)} or
* {@link #setSessionCompatToken(MediaSessionCompat.Token)} should be called.
* Otherwise, the {@link #build()} will throw an {@link IllegalArgumentException}.
*
* Any incoming event from the {@link MediaSession} will be handled on the callback
* executor.
*/
public static final class Builder extends BuilderBase
* It will throw an {@link IllegalArgumentException} if both {@link SessionToken} and
* {@link MediaSessionCompat.Token} are not set.
*
* @return a new controller
*/
@Override
@NonNull
public MediaController build() {
if (mToken == null && mCompatToken == null) {
throw new IllegalArgumentException("token and compat token shouldn't be both null");
}
if (mToken != null) {
return new MediaController(mContext, mToken, mConnectionHints,
mCallbackExecutor, mCallback);
} else {
return new MediaController(mContext, mCompatToken, mConnectionHints,
mCallbackExecutor, mCallback);
}
}
}
/**
* Base builder class for MediaController and its subclass. Any change in this class should be
* also applied to the subclasses {@link MediaController.Builder} and
* {@link MediaBrowser.Builder}.
*
* APIs here should be package private, but should have documentations for developers.
* Otherwise, javadoc will generate documentation with the generic types such as follows.
*
* This class is hidden to prevent from generating test stub, which fails with
* 'unexpected bound' because it tries to auto generate stub class as follows.
*
* When this method is called, the {@link MediaSessionCompat.Token} which was set by calling
* {@link #setSessionCompatToken} is removed.
*
* Detailed behavior of the {@link MediaController} differs according to the type of the
* token as follows.
*
*
* The controller connects to the specified session directly. It's recommended when you're
* sure which session to control, or a you've got token directly from the session app.
*
* This can be used only when the session for the token is running. Once the session is
* closed, the token becomes unusable.
*
* The controller connects to the session provided by the
* {@link MediaSessionService#onGetSession(ControllerInfo)}.
* It's up to the service's decision which session would be returned for the connection.
* Use the {@link #getConnectedSessionToken()} to know the connected session.
*
* This can be used regardless of the session app is running or not. The controller would
* bind to the service while connected to wake up and keep the service process running.
*
* When this method is called, the {@link SessionToken} which was set by calling
* {@link #setSessionToken(SessionToken)} is removed.
*
* @param compatToken token to connect to
* @return The Builder to allow chaining
*/
@NonNull
public U setSessionCompatToken(@NonNull MediaSessionCompat.Token compatToken) {
if (compatToken == null) {
throw new NullPointerException("compatToken shouldn't be null");
}
mCompatToken = compatToken;
mToken = null;
return (U) this;
}
/**
* Set the connection hints for the controller.
*
* {@code connectionHints} is a session-specific argument to send to the session when
* connecting. The contents of this bundle may affect the connection result.
*
* The hints specified here are only used when when connecting to the {@link MediaSession}.
* They will be ignored when connecting to {@link MediaSessionCompat}.
*
* @param connectionHints a bundle which contains the connection hints
* @return The Builder to allow chaining
*/
@NonNull
public U setConnectionHints(@NonNull Bundle connectionHints) {
if (connectionHints == null) {
throw new NullPointerException("connectionHints shouldn't be null");
}
mConnectionHints = new Bundle(connectionHints);
return (U) this;
}
/**
* Set callback for the controller and its executor.
*
* @param executor callback executor
* @param callback controller callback.
* @return The Builder to allow chaining
*/
@NonNull
public U setControllerCallback(@NonNull Executor executor, @NonNull C callback) {
if (executor == null) {
throw new NullPointerException("executor shouldn't be null");
}
if (callback == null) {
throw new NullPointerException("callback shouldn't be null");
}
mCallbackExecutor = executor;
mCallback = callback;
return (U) this;
}
@NonNull
abstract T build();
}
/**
* Interface for listening to change in activeness of the {@link MediaSession}. It's
* active if and only if it has set a player.
*/
public abstract static class ControllerCallback {
/**
* Called when the controller is successfully connected to the session. The controller
* becomes available afterwards.
*
* @param controller the controller for this event
* @param allowedCommands commands that's allowed by the session.
*/
public void onConnected(@NonNull MediaController controller,
@NonNull SessionCommandGroup allowedCommands) {}
/**
* Called when the session refuses the controller or the controller is disconnected from
* the session. The controller becomes unavailable afterwards and the callback wouldn't
* be called.
*
* It will be also called after the {@link #close()}, so you can put clean up code here.
* You don't need to call {@link #close()} after this.
*
* @param controller the controller for this event
*/
public void onDisconnected(@NonNull MediaController controller) {}
/**
* Called when the session set the custom layout through the
* {@link MediaSession#setCustomLayout(MediaSession.ControllerInfo, List)}.
*
* Can be called before {@link #onConnected(MediaController, SessionCommandGroup)}
* is called.
*
* Default implementation returns {@link SessionResult#RESULT_ERROR_NOT_SUPPORTED}.
*
* @param controller the controller for this event
* @param layout
*/
@SessionResult.ResultCode
public int onSetCustomLayout(
@NonNull MediaController controller, @NonNull List
* Interoperability: When connected to
* {@link android.support.v4.media.session.MediaSessionCompat}, this may be called when the
* session changes playback info by calling
* {@link android.support.v4.media.session.MediaSessionCompat#setPlaybackToLocal(int)} or
* {@link android.support.v4.media.session.MediaSessionCompat#setPlaybackToRemote(
* VolumeProviderCompat)}}. Specifically:
*
* Default implementation returns {@link SessionResult#RESULT_ERROR_NOT_SUPPORTED}.
*
* @param controller the controller for this event
* @param command
* @param args
* @return result of handling custom command
*/
@NonNull
public SessionResult onCustomCommand(@NonNull MediaController controller,
@NonNull SessionCommand command, @Nullable Bundle args) {
return new SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED);
}
/**
* Called when the player state is changed.
*
* @param controller the controller for this event
* @param state the new player state
*/
public void onPlayerStateChanged(@NonNull MediaController controller,
@SessionPlayer.PlayerState int state) {}
/**
* Called when playback speed is changed.
*
* @param controller the controller for this event
* @param speed speed
*/
public void onPlaybackSpeedChanged(@NonNull MediaController controller,
float speed) {}
/**
* Called to report buffering events for a media item.
*
* Use {@link #getBufferedPosition()} for current buffering position.
*
* @param controller the controller for this event
* @param item the media item for which buffering is happening.
* @param state the new buffering state.
*/
public void onBufferingStateChanged(@NonNull MediaController controller,
@NonNull MediaItem item, @SessionPlayer.BuffState int state) {}
/**
* Called to indicate that seeking is completed.
*
* @param controller the controller for this event.
* @param position the previous seeking request.
*/
public void onSeekCompleted(@NonNull MediaController controller, long position) {}
/**
* Called when the player's current item is changed. It's also called after
* {@link #setPlaylist} or {@link #setMediaItem}.
*
* When it's called, you should invalidate previous playback information and wait for later
* callbacks. Also, current, previous, and next media item indices may need to be updated.
*
* @param controller the controller for this event
* @param item new current media item
* @see #getPlaylist()
* @see #getPlaylistMetadata()
*/
public void onCurrentMediaItemChanged(@NonNull MediaController controller,
@Nullable MediaItem item) {}
/**
* Called when a playlist is changed. It's also called after {@link #setPlaylist} or
* {@link #setMediaItem}.
*
* When it's called, current, previous, and next media item indices may need to be updated.
*
* @param controller the controller for this event
* @param list new playlist
* @param metadata new metadata
* @see #getPlaylist()
* @see #getPlaylistMetadata()
*/
public void onPlaylistChanged(@NonNull MediaController controller,
@Nullable List
* When it's called, you should invalidate previous track information and use the new
* tracks to call {@link #selectTrack(TrackInfo)} or
* {@link #deselectTrack(TrackInfo)}.
*
* The types of tracks supported may vary based on player implementation.
*
* @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
* @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
* @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
* @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
*
* @param controller the controller for this event
* @param trackInfos the list of track infos
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
public void onTrackInfoChanged(@NonNull MediaController controller,
@NonNull List
* The types of tracks supported may vary based on player implementation, but generally
* one track will be selected for each track type.
*
* @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
* @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
* @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
* @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
*
* @param controller the controller for this event
* @param trackInfo the selected track
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
public void onTrackSelected(@NonNull MediaController controller,
@NonNull TrackInfo trackInfo) {}
/**
* Called when a track is deselected.
*
* The types of tracks supported may vary based on player implementation, but generally
* a track should already be selected in order to be deselected and audio and video tracks
* should not be deselected.
*
* @see TrackInfo#MEDIA_TRACK_TYPE_VIDEO
* @see TrackInfo#MEDIA_TRACK_TYPE_AUDIO
* @see TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE
* @see TrackInfo#MEDIA_TRACK_TYPE_METADATA
*
* @param controller the controller for this event
* @param trackInfo the deselected track
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
public void onTrackDeselected(@NonNull MediaController controller,
@NonNull TrackInfo trackInfo) {}
/**
* Called when the subtitle track has new subtitle data available.
* @param controller the controller for this event
* @param item the MediaItem of this media item
* @param track the track that has the subtitle data
* @param data the subtitle data
*
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
public void onSubtitleData(@NonNull MediaController controller, @NonNull MediaItem item,
@NonNull TrackInfo track, @NonNull SubtitleData data) {}
}
/**
* Holds information about the the way volume is handled for this session.
*/
// The same as MediaController.PlaybackInfo
@VersionedParcelize
public static final class PlaybackInfo implements VersionedParcelable {
@ParcelField(1)
int mPlaybackType;
@ParcelField(2)
int mControlType;
@ParcelField(3)
int mMaxVolume;
@ParcelField(4)
int mCurrentVolume;
@ParcelField(5)
AudioAttributesCompat mAudioAttrsCompat;
/**
* The session uses local playback.
*/
public static final int PLAYBACK_TYPE_LOCAL = 1;
/**
* The session uses remote playback.
*/
public static final int PLAYBACK_TYPE_REMOTE = 2;
/**
* Used for VersionedParcelable
*/
PlaybackInfo() {
}
PlaybackInfo(int playbackType, AudioAttributesCompat attrs, int controlType, int max,
int current) {
mPlaybackType = playbackType;
mAudioAttrsCompat = attrs;
mControlType = controlType;
mMaxVolume = max;
mCurrentVolume = current;
}
/**
* Get the type of playback which affects volume handling. One of:
*
* This is only meaningful when the playback type is {@link #PLAYBACK_TYPE_REMOTE}.
*
* @return The maximum allowed volume where this session is playing
*/
public int getMaxVolume() {
return mMaxVolume;
}
/**
* Get the current volume for this session.
*
* This is only meaningful when the playback type is {@link #PLAYBACK_TYPE_REMOTE}.
*
* @return The current volume where this session is playing
*/
public int getCurrentVolume() {
return mCurrentVolume;
}
@Override
public int hashCode() {
return ObjectsCompat.hash(
mPlaybackType, mControlType, mMaxVolume, mCurrentVolume, mAudioAttrsCompat);
}
@Override
public boolean equals(@Nullable Object obj) {
if (!(obj instanceof PlaybackInfo)) {
return false;
}
PlaybackInfo other = (PlaybackInfo) obj;
return mPlaybackType == other.mPlaybackType
&& mControlType == other.mControlType
&& mMaxVolume == other.mMaxVolume
&& mCurrentVolume == other.mCurrentVolume
&& ObjectsCompat.equals(mAudioAttrsCompat, other.mAudioAttrsCompat);
}
static PlaybackInfo createPlaybackInfo(int playbackType, AudioAttributesCompat attrs,
int controlType, int max, int current) {
return new PlaybackInfo(playbackType, attrs, controlType, max, current);
}
}
}
U extends BuilderBase
* abstract static class BuilderBase<
* T extends androidx.media2.MediaController,
* U extends androidx.media2.MediaController.BuilderBase<
* T, U, C extends androidx.media2.MediaController.ControllerCallback>, C>
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
abstract static class BuilderBase
*
*
* @param token token to connect to
* @return The Builder to allow chaining
* @see MediaSessionService#onGetSession(ControllerInfo)
* @see #getConnectedSessionToken()
* @see #setConnectionHints(Bundle)
*/
@NonNull
public U setSessionToken(@NonNull SessionToken token) {
if (token == null) {
throw new NullPointerException("token shouldn't be null");
}
mToken = token;
mCompatToken = null;
return (U) this;
}
/**
* Set the {@link MediaSessionCompat.Token} for the controller to connect to.
*
*
*
* @param controller the controller for this event
* @param info new playback info
*/
public void onPlaybackInfoChanged(@NonNull MediaController controller,
@NonNull PlaybackInfo info) {}
/**
* Called when the allowed commands are changed by session.
*
* @param controller the controller for this event
* @param commands newly allowed commands
*/
public void onAllowedCommandsChanged(@NonNull MediaController controller,
@NonNull SessionCommandGroup commands) {}
/**
* Called when the session sent a custom command. Returns a {@link SessionResult} for
* session to get notification back. If the {@code null} is returned,
* {@link SessionResult#RESULT_ERROR_UNKNOWN} will be returned.
*
*
*
* @return The type of playback this session is using
*/
public int getPlaybackType() {
return mPlaybackType;
}
/**
* Get the audio attributes for this session. The attributes will affect
* volume handling for the session. When the volume type is
* {@link #PLAYBACK_TYPE_REMOTE} these may be ignored by the
* remote volume handler.
*
* @return The attributes for this session
*/
@Nullable
public AudioAttributesCompat getAudioAttributes() {
return mAudioAttrsCompat;
}
/**
* Get the type of volume control that can be used. One of:
*
*
*
* @return The type of volume control that may be used with this session
*/
public int getControlType() {
return mControlType;
}
/**
* Get the maximum volume that may be set for this session.
*