blob: 07838e8042d007620e1417e6e4ca216d0b43c991 [file] [log] [blame]
/*
* 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 androidx.media;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.util.Log;
import androidx.annotation.GuardedBy;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.collection.SimpleArrayMap;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.concurrent.Executor;
/**
* MediaPlaylistAgent is the abstract class an application needs to derive from to pass an object
* to a MediaSession2 that will override default playlist handling behaviors. It contains a set of
* notify methods to signal MediaSession2 that playlist-related state has changed.
* <p>
* Playlists are composed of one or multiple {@link MediaItem2} instances, which combine metadata
* and data sources (as {@link DataSourceDesc})
* Used by {@link MediaSession2} and {@link MediaController2}.
*/
// This class only includes methods that contain {@link MediaItem2}.
public abstract class MediaPlaylistAgent {
private static final String TAG = "MediaPlaylistAgent";
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
@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. An example of a group is the playlist.
*/
public static final int REPEAT_MODE_GROUP = 3;
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
@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. An example of a group is the playlist.
*/
public static final int SHUFFLE_MODE_GROUP = 2;
private final Object mLock = new Object();
@GuardedBy("mLock")
private final SimpleArrayMap<PlaylistEventCallback, Executor> mCallbacks =
new SimpleArrayMap<>();
/**
* Register {@link PlaylistEventCallback} to listen changes in the underlying
* {@link MediaPlaylistAgent}.
*
* @param executor a callback Executor
* @param callback a PlaylistEventCallback
* @throws IllegalArgumentException if executor or callback is {@code null}.
*/
public final void registerPlaylistEventCallback(
@NonNull /*@CallbackExecutor*/ Executor executor,
@NonNull PlaylistEventCallback callback) {
if (executor == null) {
throw new IllegalArgumentException("executor shouldn't be null");
}
if (callback == null) {
throw new IllegalArgumentException("callback shouldn't be null");
}
synchronized (mLock) {
if (mCallbacks.get(callback) != null) {
Log.w(TAG, "callback is already added. Ignoring.");
return;
}
mCallbacks.put(callback, executor);
}
}
/**
* Unregister the previously registered {@link PlaylistEventCallback}.
*
* @param callback the callback to be removed
* @throws IllegalArgumentException if the callback is {@code null}.
*/
public final void unregisterPlaylistEventCallback(@NonNull PlaylistEventCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback shouldn't be null");
}
synchronized (mLock) {
mCallbacks.remove(callback);
}
}
/**
* Notifies the current playlist and playlist metadata. Call this API when the playlist is
* changed.
* <p>
* Registered {@link PlaylistEventCallback} would receive this event through the
* {@link PlaylistEventCallback#onPlaylistChanged(MediaPlaylistAgent, List, MediaMetadata2)}.
*/
public final void notifyPlaylistChanged() {
SimpleArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
final List<MediaItem2> playlist = getPlaylist();
final MediaMetadata2 metadata = getPlaylistMetadata();
for (int i = 0; i < callbacks.size(); i++) {
final PlaylistEventCallback callback = callbacks.keyAt(i);
final Executor executor = callbacks.valueAt(i);
executor.execute(new Runnable() {
@Override
public void run() {
callback.onPlaylistChanged(
MediaPlaylistAgent.this, playlist, metadata);
}
});
}
}
/**
* Notifies the current playlist metadata. Call this API when the playlist metadata is changed.
* <p>
* Registered {@link PlaylistEventCallback} would receive this event through the
* {@link PlaylistEventCallback#onPlaylistMetadataChanged(MediaPlaylistAgent, MediaMetadata2)}.
*/
public final void notifyPlaylistMetadataChanged() {
SimpleArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
for (int i = 0; i < callbacks.size(); i++) {
final PlaylistEventCallback callback = callbacks.keyAt(i);
final Executor executor = callbacks.valueAt(i);
executor.execute(new Runnable() {
@Override
public void run() {
callback.onPlaylistMetadataChanged(
MediaPlaylistAgent.this, MediaPlaylistAgent.this.getPlaylistMetadata());
}
});
}
}
/**
* Notifies the current shuffle mode. Call this API when the shuffle mode is changed.
* <p>
* Registered {@link PlaylistEventCallback} would receive this event through the
* {@link PlaylistEventCallback#onShuffleModeChanged(MediaPlaylistAgent, int)}.
*/
public final void notifyShuffleModeChanged() {
SimpleArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
for (int i = 0; i < callbacks.size(); i++) {
final PlaylistEventCallback callback = callbacks.keyAt(i);
final Executor executor = callbacks.valueAt(i);
executor.execute(new Runnable() {
@Override
public void run() {
callback.onShuffleModeChanged(
MediaPlaylistAgent.this, MediaPlaylistAgent.this.getShuffleMode());
}
});
}
}
/**
* Notifies the current repeat mode. Call this API when the repeat mode is changed.
* <p>
* Registered {@link PlaylistEventCallback} would receive this event through the
* {@link PlaylistEventCallback#onRepeatModeChanged(MediaPlaylistAgent, int)}.
*/
public final void notifyRepeatModeChanged() {
SimpleArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
for (int i = 0; i < callbacks.size(); i++) {
final PlaylistEventCallback callback = callbacks.keyAt(i);
final Executor executor = callbacks.valueAt(i);
executor.execute(new Runnable() {
@Override
public void run() {
callback.onRepeatModeChanged(
MediaPlaylistAgent.this, MediaPlaylistAgent.this.getRepeatMode());
}
});
}
}
/**
* Returns the playlist
*
* @return playlist, or null if none is set.
*/
public abstract @Nullable List<MediaItem2> getPlaylist();
/**
* Sets the playlist with the metadata.
* <p>
* When the playlist is changed, call {@link #notifyPlaylistChanged()} to notify changes to the
* registered callbacks.
*
* @param list playlist
* @param metadata metadata of the playlist
* @see #notifyPlaylistChanged()
*/
public abstract void setPlaylist(@NonNull List<MediaItem2> list,
@Nullable MediaMetadata2 metadata);
/**
* Returns the playlist metadata
*
* @return metadata metadata of the playlist, or null if none is set
*/
public abstract @Nullable MediaMetadata2 getPlaylistMetadata();
/**
* Updates the playlist metadata.
* <p>
* When the playlist metadata is changed, call {@link #notifyPlaylistMetadataChanged()} to
* notify changes to the registered callbacks.
*
* @param metadata metadata of the playlist
* @see #notifyPlaylistMetadataChanged()
*/
public abstract void updatePlaylistMetadata(@Nullable MediaMetadata2 metadata);
/**
* Returns currently playing media item.
*/
public abstract MediaItem2 getCurrentMediaItem();
/**
* Adds the media item to the playlist at position index. Index equals or greater than
* the current playlist size (e.g. {@link Integer#MAX_VALUE}) will add the item at the end of
* the playlist.
* <p>
* 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 item the media item you want to add
*/
public abstract void addPlaylistItem(int index, @NonNull MediaItem2 item);
/**
* Removes the media item from the playlist
*
* @param item media item to remove
*/
public abstract void removePlaylistItem(@NonNull MediaItem2 item);
/**
* Replace the media item at index in the playlist. This can be also used to update metadata of
* an item.
*
* @param index the index of the item to replace
* @param item the new item
*/
public abstract void replacePlaylistItem(int index, @NonNull MediaItem2 item);
/**
* Skips to the the media item, and plays from it.
*
* @param item media item to start playing from
*/
public abstract void skipToPlaylistItem(@NonNull MediaItem2 item);
/**
* Skips to the previous item in the playlist.
*/
public abstract void skipToPreviousItem();
/**
* Skips to the next item in the playlist.
*/
public abstract void skipToNextItem();
/**
* Gets the repeat mode
*
* @return repeat mode
* @see #REPEAT_MODE_NONE
* @see #REPEAT_MODE_ONE
* @see #REPEAT_MODE_ALL
* @see #REPEAT_MODE_GROUP
*/
public abstract @RepeatMode int getRepeatMode();
/**
* Sets the repeat mode.
* <p>
* When the repeat mode is changed, call {@link #notifyRepeatModeChanged()} to notify changes
* to the registered callbacks.
*
* @param repeatMode repeat mode
* @see #REPEAT_MODE_NONE
* @see #REPEAT_MODE_ONE
* @see #REPEAT_MODE_ALL
* @see #REPEAT_MODE_GROUP
* @see #notifyRepeatModeChanged()
*/
public abstract void setRepeatMode(@RepeatMode int repeatMode);
/**
* Gets the shuffle mode
*
* @return The shuffle mode
* @see #SHUFFLE_MODE_NONE
* @see #SHUFFLE_MODE_ALL
* @see #SHUFFLE_MODE_GROUP
*/
public abstract @ShuffleMode int getShuffleMode();
/**
* Sets the shuffle mode.
* <p>
* When the shuffle mode is changed, call {@link #notifyShuffleModeChanged()} to notify changes
* to the registered callbacks.
*
* @param shuffleMode The shuffle mode
* @see #SHUFFLE_MODE_NONE
* @see #SHUFFLE_MODE_ALL
* @see #SHUFFLE_MODE_GROUP
* @see #notifyShuffleModeChanged()
*/
public abstract void setShuffleMode(@ShuffleMode int shuffleMode);
/**
* Called by {@link MediaSession2} when it wants to translate {@link DataSourceDesc} from the
* {@link MediaPlayerBase.PlayerEventCallback} to the {@link MediaItem2}. Override this method
* if you want to create {@link DataSourceDesc}s dynamically, instead of specifying them with
* {@link #setPlaylist(List, MediaMetadata2)}.
* <p>
* Session would throw an exception if this returns {@code null} for the dsd from the
* {@link MediaPlayerBase.PlayerEventCallback}.
* <p>
* Default implementation calls the {@link #getPlaylist()} and searches the {@link MediaItem2}
* with the {@param dsd}.
*
* @param dsd The dsd to query
* @return A {@link MediaItem2} object in the playlist that matches given {@code dsd}.
* @throws IllegalArgumentException if {@code dsd} is null
*/
public @Nullable MediaItem2 getMediaItem(@NonNull DataSourceDesc dsd) {
if (dsd == null) {
throw new IllegalArgumentException("dsd shouldn't be null");
}
List<MediaItem2> itemList = getPlaylist();
if (itemList == null) {
return null;
}
for (int i = 0; i < itemList.size(); i++) {
MediaItem2 item = itemList.get(i);
if (item != null && item.getDataSourceDesc().equals(dsd)) {
return item;
}
}
return null;
}
private SimpleArrayMap<PlaylistEventCallback, Executor> getCallbacks() {
SimpleArrayMap<PlaylistEventCallback, Executor> callbacks = new SimpleArrayMap<>();
synchronized (mLock) {
callbacks.putAll(mCallbacks);
}
return callbacks;
}
/**
* A callback class to receive notifications for events on the media player. See
* {@link MediaPlaylistAgent#registerPlaylistEventCallback(Executor, PlaylistEventCallback)}
* to register this callback.
*/
public abstract static class PlaylistEventCallback {
/**
* Called when a playlist is changed.
*
* @param playlistAgent playlist agent for this event
* @param list new playlist
* @param metadata new metadata
*/
public void onPlaylistChanged(@NonNull MediaPlaylistAgent playlistAgent,
@NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) { }
/**
* Called when a playlist metadata is changed.
*
* @param playlistAgent playlist agent for this event
* @param metadata new metadata
*/
public void onPlaylistMetadataChanged(@NonNull MediaPlaylistAgent playlistAgent,
@Nullable MediaMetadata2 metadata) { }
/**
* Called when the shuffle mode is changed.
*
* @param playlistAgent playlist agent for this event
* @param shuffleMode repeat mode
* @see #SHUFFLE_MODE_NONE
* @see #SHUFFLE_MODE_ALL
* @see #SHUFFLE_MODE_GROUP
*/
public void onShuffleModeChanged(@NonNull MediaPlaylistAgent playlistAgent,
@ShuffleMode int shuffleMode) { }
/**
* Called when the repeat mode is changed.
*
* @param playlistAgent playlist agent for this event
* @param repeatMode repeat mode
* @see #REPEAT_MODE_NONE
* @see #REPEAT_MODE_ONE
* @see #REPEAT_MODE_ALL
* @see #REPEAT_MODE_GROUP
*/
public void onRepeatModeChanged(@NonNull MediaPlaylistAgent playlistAgent,
@RepeatMode int repeatMode) { }
}
}