Test: Run all MediaComponents tests once Change-Id: I29a7aa9d649ea212ad4728ebabff40ec0d47ecb1
1326 lines
48 KiB
Java
1326 lines
48 KiB
Java
/*
|
|
* Copyright 2018 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 android.media;
|
|
|
|
import android.annotation.CallbackExecutor;
|
|
import android.annotation.IntDef;
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.annotation.SystemApi;
|
|
import android.app.PendingIntent;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.media.MediaPlayerInterface.PlaybackListener;
|
|
import android.media.session.MediaSession;
|
|
import android.media.session.MediaSession.Callback;
|
|
import android.media.session.PlaybackState;
|
|
import android.media.update.ApiLoader;
|
|
import android.media.update.MediaSession2Provider;
|
|
import android.media.update.MediaSession2Provider.ControllerInfoProvider;
|
|
import android.net.Uri;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.IInterface;
|
|
import android.os.Parcelable;
|
|
import android.os.ResultReceiver;
|
|
import android.text.TextUtils;
|
|
import android.util.ArraySet;
|
|
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.concurrent.Executor;
|
|
|
|
/**
|
|
* Allows a media app to expose its transport controls and playback information in a process to
|
|
* other processes including the Android framework and other apps. Common use cases are as follows.
|
|
* <ul>
|
|
* <li>Bluetooth/wired headset key events support</li>
|
|
* <li>Android Auto/Wearable support</li>
|
|
* <li>Separating UI process and playback process</li>
|
|
* </ul>
|
|
* <p>
|
|
* A MediaSession2 should be created when an app wants to publish media playback information or
|
|
* handle media keys. In general an app only needs one session for all playback, though multiple
|
|
* sessions can be created to provide finer grain controls of media.
|
|
* <p>
|
|
* If you want to support background playback, {@link MediaSessionService2} is preferred
|
|
* instead. With it, your playback can be revived even after you've finished playback. See
|
|
* {@link MediaSessionService2} for details.
|
|
* <p>
|
|
* A session can be obtained by {@link Builder}. The owner of the session may pass its session token
|
|
* to other processes to allow them to create a {@link MediaController2} to interact with the
|
|
* session.
|
|
* <p>
|
|
* When a session receive transport control commands, the session sends the commands directly to
|
|
* the the underlying media player set by {@link Builder} or
|
|
* {@link #setPlayer(MediaPlayerInterface)}.
|
|
* <p>
|
|
* When an app is finished performing playback it must call {@link #close()} to clean up the session
|
|
* and notify any controllers.
|
|
* <p>
|
|
* {@link MediaSession2} objects should be used on the thread on the looper.
|
|
*
|
|
* @see MediaSessionService2
|
|
* @hide
|
|
*/
|
|
public class MediaSession2 implements AutoCloseable {
|
|
private final MediaSession2Provider mProvider;
|
|
|
|
// Note: Do not define IntDef because subclass can add more command code on top of these.
|
|
// TODO(jaewan): Shouldn't we pull out?
|
|
// TODO(jaewan): Should we also protect getPlaybackState()?
|
|
public static final int COMMAND_CODE_CUSTOM = 0;
|
|
public static final int COMMAND_CODE_PLAYBACK_START = 1;
|
|
public static final int COMMAND_CODE_PLAYBACK_PAUSE = 2;
|
|
public static final int COMMAND_CODE_PLAYBACK_STOP = 3;
|
|
public static final int COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM = 4;
|
|
public static final int COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM = 5;
|
|
public static final int COMMAND_CODE_PLAYBACK_PREPARE = 6;
|
|
public static final int COMMAND_CODE_PLAYBACK_FAST_FORWARD = 7;
|
|
public static final int COMMAND_CODE_PLAYBACK_REWIND = 8;
|
|
public static final int COMMAND_CODE_PLAYBACK_SEEK_TO = 9;
|
|
public static final int COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM = 10;
|
|
|
|
public static final int COMMAND_CODE_PLAYLIST_GET = 11;
|
|
public static final int COMMAND_CODE_PLAYLIST_ADD = 12;
|
|
public static final int COMMAND_CODE_PLAYLIST_REMOVE = 13;
|
|
|
|
public static final int COMMAND_CODE_PLAY_FROM_MEDIA_ID = 14;
|
|
public static final int COMMAND_CODE_PLAY_FROM_URI = 15;
|
|
public static final int COMMAND_CODE_PLAY_FROM_SEARCH = 16;
|
|
|
|
public static final int COMMAND_CODE_PREPARE_FROM_MEDIA_ID = 17;
|
|
public static final int COMMAND_CODE_PREPARE_FROM_URI = 18;
|
|
public static final int COMMAND_CODE_PREPARE_FROM_SEARCH = 19;
|
|
|
|
public static final int COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS = 20;
|
|
|
|
/**
|
|
* Define a command that a {@link MediaController2} can send to a {@link MediaSession2}.
|
|
* <p>
|
|
* If {@link #getCommandCode()} isn't {@link #COMMAND_CODE_CUSTOM}), it's predefined command.
|
|
* If {@link #getCommandCode()} is {@link #COMMAND_CODE_CUSTOM}), it's custom command and
|
|
* {@link #getCustomCommand()} shouldn't be {@code null}.
|
|
*/
|
|
// TODO(jaewan): Move this into the updatable.
|
|
public static final class Command {
|
|
private static final String KEY_COMMAND_CODE
|
|
= "android.media.media_session2.command.command_code";
|
|
private static final String KEY_COMMAND_CUSTOM_COMMAND
|
|
= "android.media.media_session2.command.custom_command";
|
|
private static final String KEY_COMMAND_EXTRA
|
|
= "android.media.media_session2.command.extra";
|
|
|
|
private final int mCommandCode;
|
|
// Nonnull if it's custom command
|
|
private final String mCustomCommand;
|
|
private final Bundle mExtra;
|
|
|
|
public Command(int commandCode) {
|
|
mCommandCode = commandCode;
|
|
mCustomCommand = null;
|
|
mExtra = null;
|
|
}
|
|
|
|
public Command(@NonNull String action, @Nullable Bundle extra) {
|
|
if (action == null) {
|
|
throw new IllegalArgumentException("action shouldn't be null");
|
|
}
|
|
mCommandCode = COMMAND_CODE_CUSTOM;
|
|
mCustomCommand = action;
|
|
mExtra = extra;
|
|
}
|
|
|
|
public int getCommandCode() {
|
|
return mCommandCode;
|
|
}
|
|
|
|
public @Nullable String getCustomCommand() {
|
|
return mCustomCommand;
|
|
}
|
|
|
|
public @Nullable Bundle getExtra() {
|
|
return mExtra;
|
|
}
|
|
|
|
/**
|
|
* @return a new Bundle instance from the Command
|
|
* @hide
|
|
*/
|
|
public Bundle toBundle() {
|
|
Bundle bundle = new Bundle();
|
|
bundle.putInt(KEY_COMMAND_CODE, mCommandCode);
|
|
bundle.putString(KEY_COMMAND_CUSTOM_COMMAND, mCustomCommand);
|
|
bundle.putBundle(KEY_COMMAND_EXTRA, mExtra);
|
|
return bundle;
|
|
}
|
|
|
|
/**
|
|
* @return a new Command instance from the Bundle
|
|
* @hide
|
|
*/
|
|
public static Command fromBundle(Bundle command) {
|
|
int code = command.getInt(KEY_COMMAND_CODE);
|
|
if (code != COMMAND_CODE_CUSTOM) {
|
|
return new Command(code);
|
|
} else {
|
|
String customCommand = command.getString(KEY_COMMAND_CUSTOM_COMMAND);
|
|
if (customCommand == null) {
|
|
return null;
|
|
}
|
|
return new Command(customCommand, command.getBundle(KEY_COMMAND_EXTRA));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (!(obj instanceof Command)) {
|
|
return false;
|
|
}
|
|
Command other = (Command) obj;
|
|
// TODO(jaewan): Should we also compare contents in bundle?
|
|
// It may not be possible if the bundle contains private class.
|
|
return mCommandCode == other.mCommandCode
|
|
&& TextUtils.equals(mCustomCommand, other.mCustomCommand);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
final int prime = 31;
|
|
return ((mCustomCommand != null)
|
|
? mCustomCommand.hashCode() : 0) * prime + mCommandCode;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represent set of {@link Command}.
|
|
*/
|
|
// TODO(jaewan): Move this to updatable
|
|
public static class CommandGroup {
|
|
private static final String KEY_COMMANDS =
|
|
"android.media.mediasession2.commandgroup.commands";
|
|
private ArraySet<Command> mCommands = new ArraySet<>();
|
|
|
|
public CommandGroup() {
|
|
}
|
|
|
|
public CommandGroup(CommandGroup others) {
|
|
mCommands.addAll(others.mCommands);
|
|
}
|
|
|
|
public void addCommand(Command command) {
|
|
mCommands.add(command);
|
|
}
|
|
|
|
public void addAllPredefinedCommands() {
|
|
// TODO(jaewan): Is there any better way than this?
|
|
mCommands.add(new Command(COMMAND_CODE_PLAYBACK_START));
|
|
mCommands.add(new Command(COMMAND_CODE_PLAYBACK_PAUSE));
|
|
mCommands.add(new Command(COMMAND_CODE_PLAYBACK_STOP));
|
|
mCommands.add(new Command(COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM));
|
|
mCommands.add(new Command(COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM));
|
|
mCommands.add(new Command(COMMAND_CODE_PLAYBACK_PREPARE));
|
|
mCommands.add(new Command(COMMAND_CODE_PLAYBACK_FAST_FORWARD));
|
|
mCommands.add(new Command(COMMAND_CODE_PLAYBACK_REWIND));
|
|
mCommands.add(new Command(COMMAND_CODE_PLAYBACK_SEEK_TO));
|
|
mCommands.add(new Command(COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM));
|
|
mCommands.add(new Command(COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS));
|
|
}
|
|
|
|
public void removeCommand(Command command) {
|
|
mCommands.remove(command);
|
|
}
|
|
|
|
public boolean hasCommand(Command command) {
|
|
return mCommands.contains(command);
|
|
}
|
|
|
|
public boolean hasCommand(int code) {
|
|
if (code == COMMAND_CODE_CUSTOM) {
|
|
throw new IllegalArgumentException("Use hasCommand(Command) for custom command");
|
|
}
|
|
for (int i = 0; i < mCommands.size(); i++) {
|
|
if (mCommands.valueAt(i).getCommandCode() == code) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @return new bundle from the CommandGroup
|
|
* @hide
|
|
*/
|
|
public Bundle toBundle() {
|
|
ArrayList<Bundle> list = new ArrayList<>();
|
|
for (int i = 0; i < mCommands.size(); i++) {
|
|
list.add(mCommands.valueAt(i).toBundle());
|
|
}
|
|
Bundle bundle = new Bundle();
|
|
bundle.putParcelableArrayList(KEY_COMMANDS, list);
|
|
return bundle;
|
|
}
|
|
|
|
/**
|
|
* @return new instance of CommandGroup from the bundle
|
|
* @hide
|
|
*/
|
|
public static @Nullable CommandGroup fromBundle(Bundle commands) {
|
|
if (commands == null) {
|
|
return null;
|
|
}
|
|
List<Parcelable> list = commands.getParcelableArrayList(KEY_COMMANDS);
|
|
if (list == null) {
|
|
return null;
|
|
}
|
|
CommandGroup commandGroup = new CommandGroup();
|
|
for (int i = 0; i < list.size(); i++) {
|
|
Parcelable parcelable = list.get(i);
|
|
if (!(parcelable instanceof Bundle)) {
|
|
continue;
|
|
}
|
|
Bundle commandBundle = (Bundle) parcelable;
|
|
Command command = Command.fromBundle(commandBundle);
|
|
if (command != null) {
|
|
commandGroup.addCommand(command);
|
|
}
|
|
}
|
|
return commandGroup;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Callback to be called for all incoming commands from {@link MediaController2}s.
|
|
* <p>
|
|
* If it's not set, the session will accept all controllers and all incoming commands by
|
|
* default.
|
|
*/
|
|
// TODO(jaewan): Can we move this inside of the updatable for default implementation.
|
|
public static class SessionCallback {
|
|
/**
|
|
* Called when a controller is created for this session. Return allowed commands for
|
|
* controller. By default it allows all connection requests and commands.
|
|
* <p>
|
|
* You can reject the connection by return {@code null}. In that case, controller receives
|
|
* {@link MediaController2.ControllerCallback#onDisconnected()} and cannot be usable.
|
|
*
|
|
* @param controller controller information.
|
|
* @return allowed commands. Can be {@code null} to reject coonnection.
|
|
*/
|
|
// TODO(jaewan): Change return type. Once we do, null is for reject.
|
|
public @Nullable CommandGroup onConnect(@NonNull ControllerInfo controller) {
|
|
CommandGroup commands = new CommandGroup();
|
|
commands.addAllPredefinedCommands();
|
|
return commands;
|
|
}
|
|
|
|
/**
|
|
* Called when a controller is disconnected
|
|
*
|
|
* @param controller controller information
|
|
*/
|
|
public void onDisconnected(@NonNull ControllerInfo controller) { }
|
|
|
|
/**
|
|
* Called when a controller sent a command to the session, and the command will be sent to
|
|
* the player directly unless you reject the request by {@code false}.
|
|
*
|
|
* @param controller controller information.
|
|
* @param command a command. This method will be called for every single command.
|
|
* @return {@code true} if you want to accept incoming command. {@code false} otherwise.
|
|
*/
|
|
// TODO(jaewan): Add more documentations (or make it clear) which commands can be filtered
|
|
// with this.
|
|
public boolean onCommandRequest(@NonNull ControllerInfo controller,
|
|
@NonNull Command command) {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Called when a controller set rating on the currently playing contents.
|
|
*
|
|
* @param controller controller information
|
|
* @param rating new rating from the controller
|
|
*/
|
|
public void onSetRating(@NonNull ControllerInfo controller, @NonNull Rating2 rating) { }
|
|
|
|
/**
|
|
* Called when a controller sent a custom command.
|
|
*
|
|
* @param controller controller information
|
|
* @param customCommand custom command.
|
|
* @param args optional arguments
|
|
* @param cb optional result receiver
|
|
*/
|
|
public void onCustomCommand(@NonNull ControllerInfo controller,
|
|
@NonNull Command customCommand, @Nullable Bundle args,
|
|
@Nullable ResultReceiver cb) { }
|
|
|
|
/**
|
|
* Override to handle requests to prepare for playing a specific mediaId.
|
|
* During the preparation, a session should not hold audio focus in order to allow other
|
|
* sessions play seamlessly. The state of playback should be updated to
|
|
* {@link PlaybackState#STATE_PAUSED} after the preparation is done.
|
|
* <p>
|
|
* The playback of the prepared content should start in the later calls of
|
|
* {@link MediaSession2#play()}.
|
|
* <p>
|
|
* Override {@link #onPlayFromMediaId} to handle requests for starting
|
|
* playback without preparation.
|
|
*/
|
|
public void onPlayFromMediaId(@NonNull ControllerInfo controller,
|
|
@NonNull String mediaId, @Nullable Bundle extras) { }
|
|
|
|
/**
|
|
* Override to handle requests to prepare playback from a search query. An empty query
|
|
* indicates that the app may prepare any music. The implementation should attempt to make a
|
|
* smart choice about what to play. During the preparation, a session should not hold audio
|
|
* focus in order to allow other sessions play seamlessly. The state of playback should be
|
|
* updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done.
|
|
* <p>
|
|
* The playback of the prepared content should start in the later calls of
|
|
* {@link MediaSession2#play()}.
|
|
* <p>
|
|
* Override {@link #onPlayFromSearch} to handle requests for starting playback without
|
|
* preparation.
|
|
*/
|
|
public void onPlayFromSearch(@NonNull ControllerInfo controller,
|
|
@NonNull String query, @Nullable Bundle extras) { }
|
|
|
|
/**
|
|
* Override to handle requests to prepare a specific media item represented by a URI.
|
|
* During the preparation, a session should not hold audio focus in order to allow
|
|
* other sessions play seamlessly. The state of playback should be updated to
|
|
* {@link PlaybackState#STATE_PAUSED} after the preparation is done.
|
|
* <p>
|
|
* The playback of the prepared content should start in the later calls of
|
|
* {@link MediaSession2#play()}.
|
|
* <p>
|
|
* Override {@link #onPlayFromUri} to handle requests for starting playback without
|
|
* preparation.
|
|
*/
|
|
public void onPlayFromUri(@NonNull ControllerInfo controller,
|
|
@NonNull String uri, @Nullable Bundle extras) { }
|
|
|
|
/**
|
|
* Override to handle requests to play a specific mediaId.
|
|
*/
|
|
public void onPrepareFromMediaId(@NonNull ControllerInfo controller,
|
|
@NonNull String mediaId, @Nullable Bundle extras) { }
|
|
|
|
/**
|
|
* Override to handle requests to begin playback from a search query. An
|
|
* empty query indicates that the app may play any music. The
|
|
* implementation should attempt to make a smart choice about what to
|
|
* play.
|
|
*/
|
|
public void onPrepareFromSearch(@NonNull ControllerInfo controller,
|
|
@NonNull String query, @Nullable Bundle extras) { }
|
|
|
|
/**
|
|
* Override to handle requests to play a specific media item represented by a URI.
|
|
*/
|
|
public void onPrepareFromUri(@NonNull ControllerInfo controller,
|
|
@NonNull Uri uri, @Nullable Bundle extras) { }
|
|
|
|
/**
|
|
* Called when a controller wants to add a {@link MediaItem2} at the specified position
|
|
* in the play queue.
|
|
* <p>
|
|
* The item from the media controller wouldn't have valid data source descriptor because
|
|
* it would have been anonymized when it's sent to the remote process.
|
|
*
|
|
* @param item The media item to be inserted.
|
|
* @param index The index at which the item is to be inserted.
|
|
*/
|
|
public void onAddPlaylistItem(@NonNull ControllerInfo controller,
|
|
@NonNull MediaItem2 item, int index) { }
|
|
|
|
/**
|
|
* Called when a controller wants to remove the {@link MediaItem2}
|
|
*
|
|
* @param item
|
|
*/
|
|
// Can we do this automatically?
|
|
public void onRemovePlaylistItem(@NonNull MediaItem2 item) { }
|
|
};
|
|
|
|
/**
|
|
* Base builder class for MediaSession2 and its subclass.
|
|
*
|
|
* @hide
|
|
*/
|
|
static abstract class BuilderBase
|
|
<T extends MediaSession2.BuilderBase<T, C>, C extends SessionCallback> {
|
|
final Context mContext;
|
|
final MediaPlayerInterface mPlayer;
|
|
String mId;
|
|
Executor mCallbackExecutor;
|
|
C mCallback;
|
|
VolumeProvider mVolumeProvider;
|
|
int mRatingType;
|
|
PendingIntent mSessionActivity;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param context a context
|
|
* @param player a player to handle incoming command from any controller.
|
|
* @throws IllegalArgumentException if any parameter is null, or the player is a
|
|
* {@link MediaSession2} or {@link MediaController2}.
|
|
*/
|
|
// TODO(jaewan): Also need executor
|
|
public BuilderBase(@NonNull Context context, @NonNull MediaPlayerInterface player) {
|
|
if (context == null) {
|
|
throw new IllegalArgumentException("context shouldn't be null");
|
|
}
|
|
if (player == null) {
|
|
throw new IllegalArgumentException("player shouldn't be null");
|
|
}
|
|
mContext = context;
|
|
mPlayer = player;
|
|
// Ensure non-null
|
|
mId = "";
|
|
}
|
|
|
|
/**
|
|
* Set volume provider to configure this session to use remote volume handling.
|
|
* This must be called to receive volume button events, otherwise the system
|
|
* will adjust the appropriate stream volume for this session's player.
|
|
* <p>
|
|
* Set {@code null} to reset.
|
|
*
|
|
* @param volumeProvider The provider that will handle volume changes. Can be {@code null}
|
|
*/
|
|
public T setVolumeProvider(@Nullable VolumeProvider volumeProvider) {
|
|
mVolumeProvider = volumeProvider;
|
|
return (T) this;
|
|
}
|
|
|
|
/**
|
|
* Set the style of rating used by this session. Apps trying to set the
|
|
* rating should use this style. Must be one of the following:
|
|
* <ul>
|
|
* <li>{@link Rating2#RATING_NONE}</li>
|
|
* <li>{@link Rating2#RATING_3_STARS}</li>
|
|
* <li>{@link Rating2#RATING_4_STARS}</li>
|
|
* <li>{@link Rating2#RATING_5_STARS}</li>
|
|
* <li>{@link Rating2#RATING_HEART}</li>
|
|
* <li>{@link Rating2#RATING_PERCENTAGE}</li>
|
|
* <li>{@link Rating2#RATING_THUMB_UP_DOWN}</li>
|
|
* </ul>
|
|
*/
|
|
public T setRatingType(@Rating2.Style int type) {
|
|
mRatingType = type;
|
|
return (T) this;
|
|
}
|
|
|
|
/**
|
|
* Set an intent for launching UI for this Session. This can be used as a
|
|
* quick link to an ongoing media screen. The intent should be for an
|
|
* activity that may be started using {@link Context#startActivity(Intent)}.
|
|
*
|
|
* @param pi The intent to launch to show UI for this session.
|
|
*/
|
|
public T setSessionActivity(@Nullable PendingIntent pi) {
|
|
mSessionActivity = pi;
|
|
return (T) this;
|
|
}
|
|
|
|
/**
|
|
* Set ID of the session. If it's not set, an empty string with used to create a session.
|
|
* <p>
|
|
* Use this if and only if your app supports multiple playback at the same time and also
|
|
* wants to provide external apps to have finer controls of them.
|
|
*
|
|
* @param id id of the session. Must be unique per package.
|
|
* @throws IllegalArgumentException if id is {@code null}
|
|
* @return
|
|
*/
|
|
public T setId(@NonNull String id) {
|
|
if (id == null) {
|
|
throw new IllegalArgumentException("id shouldn't be null");
|
|
}
|
|
mId = id;
|
|
return (T) this;
|
|
}
|
|
|
|
/**
|
|
* Set callback for the session.
|
|
*
|
|
* @param executor callback executor
|
|
* @param callback session callback.
|
|
* @return
|
|
*/
|
|
public T setSessionCallback(@NonNull @CallbackExecutor Executor executor,
|
|
@NonNull C callback) {
|
|
if (executor == null) {
|
|
throw new IllegalArgumentException("executor shouldn't be null");
|
|
}
|
|
if (callback == null) {
|
|
throw new IllegalArgumentException("callback shouldn't be null");
|
|
}
|
|
mCallbackExecutor = executor;
|
|
mCallback = callback;
|
|
return (T) this;
|
|
}
|
|
|
|
/**
|
|
* Build {@link MediaSession2}.
|
|
*
|
|
* @return a new session
|
|
* @throws IllegalStateException if the session with the same id is already exists for the
|
|
* package.
|
|
*/
|
|
public abstract MediaSession2 build();
|
|
}
|
|
|
|
/**
|
|
* Builder for {@link MediaSession2}.
|
|
* <p>
|
|
* Any incoming event from the {@link MediaController2} will be handled on the thread
|
|
* that created session with the {@link Builder#build()}.
|
|
*/
|
|
// TODO(jaewan): Move this to updatable
|
|
// TODO(jaewan): Add setRatingType()
|
|
// TODO(jaewan): Add setSessionActivity()
|
|
public static final class Builder extends BuilderBase<Builder, SessionCallback> {
|
|
public Builder(Context context, @NonNull MediaPlayerInterface player) {
|
|
super(context, player);
|
|
}
|
|
|
|
@Override
|
|
public MediaSession2 build() {
|
|
if (mCallbackExecutor == null) {
|
|
mCallbackExecutor = mContext.getMainExecutor();
|
|
}
|
|
if (mCallback == null) {
|
|
mCallback = new SessionCallback();
|
|
}
|
|
return new MediaSession2(mContext, mPlayer, mId, mVolumeProvider, mRatingType,
|
|
mSessionActivity, mCallbackExecutor, mCallback);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Information of a controller.
|
|
*/
|
|
// TODO(jaewan): Move implementation to the updatable.
|
|
public static final class ControllerInfo {
|
|
private final ControllerInfoProvider mProvider;
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
// TODO(jaewan): SystemApi
|
|
// TODO(jaewan): Also accept componentName to check notificaiton listener.
|
|
public ControllerInfo(Context context, int uid, int pid, String packageName,
|
|
IInterface callback) {
|
|
mProvider = ApiLoader.getProvider(context)
|
|
.createMediaSession2ControllerInfoProvider(
|
|
context, this, uid, pid, packageName, callback);
|
|
}
|
|
|
|
/**
|
|
* @return package name of the controller
|
|
*/
|
|
public String getPackageName() {
|
|
return mProvider.getPackageName_impl();
|
|
}
|
|
|
|
/**
|
|
* @return uid of the controller
|
|
*/
|
|
public int getUid() {
|
|
return mProvider.getUid_impl();
|
|
}
|
|
|
|
/**
|
|
* Return if the controller has granted {@code android.permission.MEDIA_CONTENT_CONTROL} or
|
|
* has a enabled notification listener so can be trusted to accept connection and incoming
|
|
* command request.
|
|
*
|
|
* @return {@code true} if the controller is trusted.
|
|
*/
|
|
public boolean isTrusted() {
|
|
return mProvider.isTrusted_impl();
|
|
}
|
|
|
|
@SystemApi
|
|
public ControllerInfoProvider getProvider() {
|
|
return mProvider;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return mProvider.hashCode_impl();
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (!(obj instanceof ControllerInfo)) {
|
|
return false;
|
|
}
|
|
ControllerInfo other = (ControllerInfo) obj;
|
|
return mProvider.equals_impl(other.mProvider);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
// TODO(jaewan): Move this to updatable.
|
|
return "ControllerInfo {pkg=" + getPackageName() + ", uid=" + getUid() + ", trusted="
|
|
+ isTrusted() + "}";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Button for a {@link Command} that will be shown by the controller.
|
|
* <p>
|
|
* It's up to the controller's decision to respect or ignore this customization request.
|
|
*/
|
|
// TODO(jaewan): Move this to updatable.
|
|
public static class CommandButton {
|
|
private static final String KEY_COMMAND
|
|
= "android.media.media_session2.command_button.command";
|
|
private static final String KEY_ICON_RES_ID
|
|
= "android.media.media_session2.command_button.icon_res_id";
|
|
private static final String KEY_DISPLAY_NAME
|
|
= "android.media.media_session2.command_button.display_name";
|
|
private static final String KEY_EXTRA
|
|
= "android.media.media_session2.command_button.extra";
|
|
private static final String KEY_ENABLED
|
|
= "android.media.media_session2.command_button.enabled";
|
|
|
|
private Command mCommand;
|
|
private int mIconResId;
|
|
private String mDisplayName;
|
|
private Bundle mExtra;
|
|
private boolean mEnabled;
|
|
|
|
private CommandButton(@Nullable Command command, int iconResId,
|
|
@Nullable String displayName, Bundle extra, boolean enabled) {
|
|
mCommand = command;
|
|
mIconResId = iconResId;
|
|
mDisplayName = displayName;
|
|
mExtra = extra;
|
|
mEnabled = enabled;
|
|
}
|
|
|
|
/**
|
|
* Get command associated with this button. Can be {@code null} if the button isn't enabled
|
|
* and only providing placeholder.
|
|
*
|
|
* @return command or {@code null}
|
|
*/
|
|
public @Nullable Command getCommand() {
|
|
return mCommand;
|
|
}
|
|
|
|
/**
|
|
* Resource id of the button in this package. Can be {@code 0} if the command is predefined
|
|
* and custom icon isn't needed.
|
|
*
|
|
* @return resource id of the icon. Can be {@code 0}.
|
|
*/
|
|
public int getIconResId() {
|
|
return mIconResId;
|
|
}
|
|
|
|
/**
|
|
* Display name of the button. Can be {@code null} or empty if the command is predefined
|
|
* and custom name isn't needed.
|
|
*
|
|
* @return custom display name. Can be {@code null} or empty.
|
|
*/
|
|
public @Nullable String getDisplayName() {
|
|
return mDisplayName;
|
|
}
|
|
|
|
/**
|
|
* Extra information of the button. It's private information between session and controller.
|
|
*
|
|
* @return
|
|
*/
|
|
public @Nullable Bundle getExtra() {
|
|
return mExtra;
|
|
}
|
|
|
|
/**
|
|
* Return whether it's enabled
|
|
*
|
|
* @return {@code true} if enabled. {@code false} otherwise.
|
|
*/
|
|
public boolean isEnabled() {
|
|
return mEnabled;
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
// TODO(jaewan): @SystemApi
|
|
public @NonNull Bundle toBundle() {
|
|
Bundle bundle = new Bundle();
|
|
bundle.putBundle(KEY_COMMAND, mCommand.toBundle());
|
|
bundle.putInt(KEY_ICON_RES_ID, mIconResId);
|
|
bundle.putString(KEY_DISPLAY_NAME, mDisplayName);
|
|
bundle.putBundle(KEY_EXTRA, mExtra);
|
|
bundle.putBoolean(KEY_ENABLED, mEnabled);
|
|
return bundle;
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
// TODO(jaewan): @SystemApi
|
|
public static @Nullable CommandButton fromBundle(Bundle bundle) {
|
|
Builder builder = new Builder();
|
|
builder.setCommand(Command.fromBundle(bundle.getBundle(KEY_COMMAND)));
|
|
builder.setIconResId(bundle.getInt(KEY_ICON_RES_ID, 0));
|
|
builder.setDisplayName(bundle.getString(KEY_DISPLAY_NAME));
|
|
builder.setExtra(bundle.getBundle(KEY_EXTRA));
|
|
builder.setEnabled(bundle.getBoolean(KEY_ENABLED));
|
|
try {
|
|
return builder.build();
|
|
} catch (IllegalStateException e) {
|
|
// Malformed or version mismatch. Return null for now.
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builder for {@link CommandButton}.
|
|
*/
|
|
public static class Builder {
|
|
private Command mCommand;
|
|
private int mIconResId;
|
|
private String mDisplayName;
|
|
private Bundle mExtra;
|
|
private boolean mEnabled;
|
|
|
|
public Builder() {
|
|
mEnabled = true;
|
|
}
|
|
|
|
public Builder setCommand(Command command) {
|
|
mCommand = command;
|
|
return this;
|
|
}
|
|
|
|
public Builder setIconResId(int resId) {
|
|
mIconResId = resId;
|
|
return this;
|
|
}
|
|
|
|
public Builder setDisplayName(String displayName) {
|
|
mDisplayName = displayName;
|
|
return this;
|
|
}
|
|
|
|
public Builder setEnabled(boolean enabled) {
|
|
mEnabled = enabled;
|
|
return this;
|
|
}
|
|
|
|
public Builder setExtra(Bundle extra) {
|
|
mExtra = extra;
|
|
return this;
|
|
}
|
|
|
|
public CommandButton build() {
|
|
if (mEnabled && mCommand == null) {
|
|
throw new IllegalStateException("Enabled button needs Command"
|
|
+ " for controller to invoke the command");
|
|
}
|
|
if (mCommand != null && mCommand.getCommandCode() == COMMAND_CODE_CUSTOM
|
|
&& (mIconResId == 0 || TextUtils.isEmpty(mDisplayName))) {
|
|
throw new IllegalStateException("Custom commands needs icon and"
|
|
+ " and name to display");
|
|
}
|
|
return new CommandButton(mCommand, mIconResId, mDisplayName, mExtra, mEnabled);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parameter for the playlist.
|
|
*/
|
|
public static class PlaylistParams {
|
|
/**
|
|
* @hide
|
|
*/
|
|
@IntDef({REPEAT_MODE_NONE, REPEAT_MODE_ONE, REPEAT_MODE_ALL,
|
|
REPEAT_MODE_GROUP})
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
public @interface RepeatMode {}
|
|
|
|
/**
|
|
* Playback will be stopped at the end of the playing media list.
|
|
*/
|
|
public static final int REPEAT_MODE_NONE = 0;
|
|
|
|
/**
|
|
* Playback of the current playing media item will be repeated.
|
|
*/
|
|
public static final int REPEAT_MODE_ONE = 1;
|
|
|
|
/**
|
|
* Playing media list will be repeated.
|
|
*/
|
|
public static final int REPEAT_MODE_ALL = 2;
|
|
|
|
/**
|
|
* Playback of the playing media group will be repeated.
|
|
* A group is a logical block of media items which is specified in the section 5.7 of the
|
|
* Bluetooth AVRCP 1.6.
|
|
*/
|
|
public static final int REPEAT_MODE_GROUP = 3;
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@IntDef({SHUFFLE_MODE_NONE, SHUFFLE_MODE_ALL, SHUFFLE_MODE_GROUP})
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
public @interface ShuffleMode {}
|
|
|
|
/**
|
|
* Media list will be played in order.
|
|
*/
|
|
public static final int SHUFFLE_MODE_NONE = 0;
|
|
|
|
/**
|
|
* Media list will be played in shuffled order.
|
|
*/
|
|
public static final int SHUFFLE_MODE_ALL = 1;
|
|
|
|
/**
|
|
* Media group will be played in shuffled order.
|
|
* A group is a logical block of media items which is specified in the section 5.7 of the
|
|
* Bluetooth AVRCP 1.6.
|
|
*/
|
|
public static final int SHUFFLE_MODE_GROUP = 2;
|
|
|
|
/**
|
|
* Keys used for converting a PlaylistParams object to a bundle object and vice versa.
|
|
*/
|
|
private static final String KEY_REPEAT_MODE =
|
|
"android.media.session2.playlistparams2.repeat_mode";
|
|
private static final String KEY_SHUFFLE_MODE =
|
|
"android.media.session2.playlistparams2.shuffle_mode";
|
|
private static final String KEY_MEDIA_METADATA2_BUNDLE =
|
|
"android.media.session2.playlistparams2.metadata2_bundle";
|
|
|
|
private @RepeatMode int mRepeatMode;
|
|
private @ShuffleMode int mShuffleMode;
|
|
|
|
private MediaMetadata2 mPlaylistMetadata;
|
|
|
|
public PlaylistParams(@RepeatMode int repeatMode, @ShuffleMode int shuffleMode,
|
|
@Nullable MediaMetadata2 playlistMetadata) {
|
|
mRepeatMode = repeatMode;
|
|
mShuffleMode = shuffleMode;
|
|
mPlaylistMetadata = playlistMetadata;
|
|
}
|
|
|
|
public @RepeatMode int getRepeatMode() {
|
|
return mRepeatMode;
|
|
}
|
|
|
|
public @ShuffleMode int getShuffleMode() {
|
|
return mShuffleMode;
|
|
}
|
|
|
|
public MediaMetadata2 getPlaylistMetadata() {
|
|
return mPlaylistMetadata;
|
|
}
|
|
|
|
/**
|
|
* Returns this object as a bundle to share between processes.
|
|
*
|
|
* @hide
|
|
*/
|
|
public Bundle toBundle() {
|
|
Bundle bundle = new Bundle();
|
|
bundle.putInt(KEY_REPEAT_MODE, mRepeatMode);
|
|
bundle.putInt(KEY_SHUFFLE_MODE, mShuffleMode);
|
|
if (mPlaylistMetadata != null) {
|
|
bundle.putBundle(KEY_MEDIA_METADATA2_BUNDLE, mPlaylistMetadata.getBundle());
|
|
}
|
|
return bundle;
|
|
}
|
|
|
|
/**
|
|
* Creates an instance from a bundle which is previously created by {@link #toBundle()}.
|
|
*
|
|
* @param bundle A bundle created by {@link #toBundle()}.
|
|
* @return A new {@link PlaylistParams} instance. Returns {@code null} if the given
|
|
* {@param bundle} is null, or if the {@param bundle} has no playlist parameters.
|
|
* @hide
|
|
*/
|
|
public static PlaylistParams fromBundle(Bundle bundle) {
|
|
if (bundle == null) {
|
|
return null;
|
|
}
|
|
if (!bundle.containsKey(KEY_REPEAT_MODE) || !bundle.containsKey(KEY_SHUFFLE_MODE)) {
|
|
return null;
|
|
}
|
|
|
|
Bundle metadataBundle = bundle.getBundle(KEY_MEDIA_METADATA2_BUNDLE);
|
|
MediaMetadata2 metadata =
|
|
metadataBundle == null ? null : new MediaMetadata2(metadataBundle);
|
|
|
|
return new PlaylistParams(
|
|
bundle.getInt(KEY_REPEAT_MODE),
|
|
bundle.getInt(KEY_SHUFFLE_MODE),
|
|
metadata);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Constructor is hidden and apps can only instantiate indirectly through {@link Builder}.
|
|
* <p>
|
|
* This intended behavior and here's the reasons.
|
|
* 1. Prevent multiple sessions with the same tag in a media app.
|
|
* Whenever it happens only one session was properly setup and others were all dummies.
|
|
* Android framework couldn't find the right session to dispatch media key event.
|
|
* 2. Simplify session's lifecycle.
|
|
* {@link MediaSession} can be available after all of {@link MediaSession#setFlags(int)},
|
|
* {@link MediaSession#setCallback(Callback)}, and
|
|
* {@link MediaSession#setActive(boolean)}. It was common for an app to omit one, so
|
|
* framework had to add heuristics to figure out if an app is
|
|
* @hide
|
|
*/
|
|
MediaSession2(Context context, MediaPlayerInterface player, String id,
|
|
VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity,
|
|
Executor callbackExecutor, SessionCallback callback) {
|
|
super();
|
|
mProvider = createProvider(context, player, id, volumeProvider, ratingType, sessionActivity,
|
|
callbackExecutor, callback);
|
|
mProvider.initialize();
|
|
}
|
|
|
|
MediaSession2Provider createProvider(Context context, MediaPlayerInterface player, String id,
|
|
VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity,
|
|
Executor callbackExecutor, SessionCallback callback) {
|
|
return ApiLoader.getProvider(context)
|
|
.createMediaSession2(context, this, player, id, volumeProvider, ratingType,
|
|
sessionActivity, callbackExecutor, callback);
|
|
}
|
|
|
|
@SystemApi
|
|
public MediaSession2Provider getProvider() {
|
|
return mProvider;
|
|
}
|
|
|
|
/**
|
|
* Set the underlying {@link MediaPlayerInterface} for this session to dispatch incoming event
|
|
* to. Events from the {@link MediaController2} will be sent directly to the underlying
|
|
* player on the {@link Handler} where the session is created on.
|
|
* <p>
|
|
* If the new player is successfully set, {@link PlaybackListener}
|
|
* will be called to tell the current playback state of the new player.
|
|
* <p>
|
|
* You can also specify a volume provider. If so, playback in the player is considered as
|
|
* remote playback.
|
|
*
|
|
* @param player a {@link MediaPlayerInterface} that handles actual media playback in your app.
|
|
* @throws IllegalArgumentException if the player is {@code null}.
|
|
*/
|
|
public void setPlayer(@NonNull MediaPlayerInterface player) {
|
|
mProvider.setPlayer_impl(player);
|
|
}
|
|
|
|
/**
|
|
* Set the underlying {@link MediaPlayerInterface} with the volume provider for remote playback.
|
|
*
|
|
* @param player a {@link MediaPlayerInterface} that handles actual media playback in your app.
|
|
* @param volumeProvider a volume provider
|
|
* @see #setPlayer(MediaPlayerInterface)
|
|
* @see Builder#setVolumeProvider(VolumeProvider)
|
|
*/
|
|
public void setPlayer(@NonNull MediaPlayerInterface player,
|
|
@NonNull VolumeProvider volumeProvider) {
|
|
mProvider.setPlayer_impl(player, volumeProvider);
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
mProvider.close_impl();
|
|
}
|
|
|
|
/**
|
|
* @return player
|
|
*/
|
|
public @Nullable
|
|
MediaPlayerInterface getPlayer() {
|
|
return mProvider.getPlayer_impl();
|
|
}
|
|
|
|
/**
|
|
* Returns the {@link SessionToken2} for creating {@link MediaController2}.
|
|
*/
|
|
public @NonNull
|
|
SessionToken2 getToken() {
|
|
return mProvider.getToken_impl();
|
|
}
|
|
|
|
public @NonNull List<ControllerInfo> getConnectedControllers() {
|
|
return mProvider.getConnectedControllers_impl();
|
|
}
|
|
|
|
/**
|
|
* Sets which type of audio focus will be requested during the playback, or configures playback
|
|
* to not request audio focus. Valid values for focus requests are
|
|
* {@link AudioManager#AUDIOFOCUS_GAIN}, {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT},
|
|
* {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, and
|
|
* {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. Or use
|
|
* {@link AudioManager#AUDIOFOCUS_NONE} to express that audio focus should not be
|
|
* requested when playback starts. You can for instance use this when playing a silent animation
|
|
* through this class, and you don't want to affect other audio applications playing in the
|
|
* background.
|
|
*
|
|
* @param focusGain the type of audio focus gain that will be requested, or
|
|
* {@link AudioManager#AUDIOFOCUS_NONE} to disable the use audio focus during
|
|
* playback.
|
|
*/
|
|
public void setAudioFocusRequest(int focusGain) {
|
|
mProvider.setAudioFocusRequest_impl(focusGain);
|
|
}
|
|
|
|
/**
|
|
* Sets ordered list of {@link CommandButton} for controllers to build UI with it.
|
|
* <p>
|
|
* It's up to controller's decision how to represent the layout in its own UI.
|
|
* Here's the same way
|
|
* (layout[i] means a CommandButton at index i in the given list)
|
|
* For 5 icons row
|
|
* layout[3] layout[1] layout[0] layout[2] layout[4]
|
|
* For 3 icons row
|
|
* layout[1] layout[0] layout[2]
|
|
* For 5 icons row with overflow icon (can show +5 extra buttons with overflow button)
|
|
* expanded row: layout[5] layout[6] layout[7] layout[8] layout[9]
|
|
* main row: layout[3] layout[1] layout[0] layout[2] layout[4]
|
|
* <p>
|
|
* This API can be called in the {@link SessionCallback#onConnect(ControllerInfo)}.
|
|
*
|
|
* @param controller controller to specify layout.
|
|
* @param layout oredered list of layout.
|
|
*/
|
|
public void setCustomLayout(@NonNull ControllerInfo controller,
|
|
@NonNull List<CommandButton> layout) {
|
|
mProvider.setCustomLayout_impl(controller, layout);
|
|
}
|
|
|
|
/**
|
|
* Set the new allowed command group for the controller
|
|
*
|
|
* @param controller controller to change allowed commands
|
|
* @param commands new allowed commands
|
|
*/
|
|
public void setAllowedCommands(@NonNull ControllerInfo controller,
|
|
@NonNull CommandGroup commands) {
|
|
mProvider.setAllowedCommands_impl(controller, commands);
|
|
}
|
|
|
|
/**
|
|
* Notify changes in metadata of previously set playlist. Controller will get the whole set of
|
|
* playlist again.
|
|
*/
|
|
public void notifyMetadataChanged() {
|
|
mProvider.notifyMetadataChanged_impl();
|
|
}
|
|
|
|
/**
|
|
* Send custom command to all connected controllers.
|
|
*
|
|
* @param command a command
|
|
* @param args optional argument
|
|
*/
|
|
public void sendCustomCommand(@NonNull Command command, @Nullable Bundle args) {
|
|
mProvider.sendCustomCommand_impl(command, args);
|
|
}
|
|
|
|
/**
|
|
* Send custom command to a specific controller.
|
|
*
|
|
* @param command a command
|
|
* @param args optional argument
|
|
* @param receiver result receiver for the session
|
|
*/
|
|
public void sendCustomCommand(@NonNull ControllerInfo controller, @NonNull Command command,
|
|
@Nullable Bundle args, @Nullable ResultReceiver receiver) {
|
|
// Equivalent to the MediaController.sendCustomCommand(Action action, ResultReceiver r);
|
|
mProvider.sendCustomCommand_impl(controller, command, args, receiver);
|
|
}
|
|
|
|
/**
|
|
* Play playback
|
|
*/
|
|
public void play() {
|
|
mProvider.play_impl();
|
|
}
|
|
|
|
/**
|
|
* Pause playback
|
|
*/
|
|
public void pause() {
|
|
mProvider.pause_impl();
|
|
}
|
|
|
|
/**
|
|
* Stop playback
|
|
*/
|
|
public void stop() {
|
|
mProvider.stop_impl();
|
|
}
|
|
|
|
/**
|
|
* Rewind playback
|
|
*/
|
|
public void skipToPrevious() {
|
|
mProvider.skipToPrevious_impl();
|
|
}
|
|
|
|
/**
|
|
* Rewind playback
|
|
*/
|
|
public void skipToNext() {
|
|
mProvider.skipToNext_impl();
|
|
}
|
|
|
|
/**
|
|
* Request that the player prepare its playback. In other words, other sessions can continue
|
|
* to play during the preparation of this session. This method can be used to speed up the
|
|
* start of the playback. Once the preparation is done, the session will change its playback
|
|
* state to {@link PlaybackState#STATE_PAUSED}. Afterwards, {@link #play} can be called to
|
|
* start playback.
|
|
*/
|
|
public void prepare() {
|
|
mProvider.prepare_impl();
|
|
}
|
|
|
|
/**
|
|
* Start fast forwarding. If playback is already fast forwarding this may increase the rate.
|
|
*/
|
|
public void fastForward() {
|
|
mProvider.fastForward_impl();
|
|
}
|
|
|
|
/**
|
|
* Start rewinding. If playback is already rewinding this may increase the rate.
|
|
*/
|
|
public void rewind() {
|
|
mProvider.rewind_impl();
|
|
}
|
|
|
|
/**
|
|
* Move to a new location in the media stream.
|
|
*
|
|
* @param pos Position to move to, in milliseconds.
|
|
*/
|
|
public void seekTo(long pos) {
|
|
mProvider.seekTo_impl(pos);
|
|
}
|
|
|
|
/**
|
|
* Sets the index of current DataSourceDesc in the play list to be played.
|
|
*
|
|
* @param index the index of DataSourceDesc in the play list you want to play
|
|
* @throws IllegalArgumentException if the play list is null
|
|
* @throws NullPointerException if index is outside play list range
|
|
*/
|
|
public void setCurrentPlaylistItem(int index) {
|
|
mProvider.setCurrentPlaylistItem_impl(index);
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
public void skipForward() {
|
|
// To match with KEYCODE_MEDIA_SKIP_FORWARD
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
public void skipBackward() {
|
|
// To match with KEYCODE_MEDIA_SKIP_BACKWARD
|
|
}
|
|
|
|
public void setPlaylist(@NonNull List<MediaItem2> playlist, @NonNull PlaylistParams param) {
|
|
mProvider.setPlaylist_impl(playlist, param);
|
|
}
|
|
|
|
public List<MediaItem2> getPlaylist() {
|
|
return mProvider.getPlaylist_impl();
|
|
}
|
|
|
|
/**
|
|
* Sets the {@link PlaylistParams} for the current play list. Repeat/shuffle mode and metadata
|
|
* for the list can be set by calling this method.
|
|
*
|
|
* @param params A {@link PlaylistParams} object to set.
|
|
* @throws IllegalArgumentException if given {@param param} is null.
|
|
*/
|
|
public void setPlaylistParams(PlaylistParams params) {
|
|
mProvider.setPlaylistParams_impl(params);
|
|
}
|
|
|
|
/**
|
|
* Returns the {@link PlaylistParams} for the current play list.
|
|
* Returns {@code null} if not set.
|
|
*/
|
|
public PlaylistParams getPlaylistParams() {
|
|
return mProvider.getPlaylistParams_impl();
|
|
}
|
|
|
|
/*
|
|
* Add a {@link PlaybackListener} to listen changes in the underlying
|
|
* {@link MediaPlayerInterface}. Listener will be called immediately to tell the current value.
|
|
* <p>
|
|
* Added listeners will be also called when the underlying player is changed.
|
|
*
|
|
* @param executor the call listener
|
|
* @param listener the listener that will be run
|
|
* @throws IllegalArgumentException when either the listener or handler is {@code null}.
|
|
*/
|
|
public void addPlaybackListener(@NonNull @CallbackExecutor Executor executor,
|
|
@NonNull PlaybackListener listener) {
|
|
mProvider.addPlaybackListener_impl(executor, listener);
|
|
}
|
|
|
|
/**
|
|
* Remove previously added {@link PlaybackListener}.
|
|
*
|
|
* @param listener the listener to be removed
|
|
* @throws IllegalArgumentException if the listener is {@code null}.
|
|
*/
|
|
public void removePlaybackListener(@NonNull PlaybackListener listener) {
|
|
mProvider.removePlaybackListener_impl(listener);
|
|
}
|
|
|
|
/**
|
|
* Return the {@link PlaybackState2} from the player.
|
|
*
|
|
* @return playback state
|
|
*/
|
|
public PlaybackState2 getPlaybackState() {
|
|
return mProvider.getPlaybackState_impl();
|
|
}
|
|
}
|