1/* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package androidx.media; 18 19import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21import android.util.Log; 22 23import androidx.annotation.GuardedBy; 24import androidx.annotation.IntDef; 25import androidx.annotation.NonNull; 26import androidx.annotation.Nullable; 27import androidx.annotation.RestrictTo; 28import androidx.collection.SimpleArrayMap; 29 30import java.lang.annotation.Retention; 31import java.lang.annotation.RetentionPolicy; 32import java.util.List; 33import java.util.concurrent.Executor; 34 35/** 36 * MediaPlaylistAgent is the abstract class an application needs to derive from to pass an object 37 * to a MediaSession2 that will override default playlist handling behaviors. It contains a set of 38 * notify methods to signal MediaSession2 that playlist-related state has changed. 39 * <p> 40 * Playlists are composed of one or multiple {@link MediaItem2} instances, which combine metadata 41 * and data sources (as {@link DataSourceDesc}) 42 * Used by {@link MediaSession2} and {@link MediaController2}. 43 */ 44// This class only includes methods that contain {@link MediaItem2}. 45public abstract class MediaPlaylistAgent { 46 private static final String TAG = "MediaPlaylistAgent"; 47 48 /** 49 * @hide 50 */ 51 @RestrictTo(LIBRARY_GROUP) 52 @IntDef({REPEAT_MODE_NONE, REPEAT_MODE_ONE, REPEAT_MODE_ALL, 53 REPEAT_MODE_GROUP}) 54 @Retention(RetentionPolicy.SOURCE) 55 public @interface RepeatMode {} 56 57 /** 58 * Playback will be stopped at the end of the playing media list. 59 */ 60 public static final int REPEAT_MODE_NONE = 0; 61 62 /** 63 * Playback of the current playing media item will be repeated. 64 */ 65 public static final int REPEAT_MODE_ONE = 1; 66 67 /** 68 * Playing media list will be repeated. 69 */ 70 public static final int REPEAT_MODE_ALL = 2; 71 72 /** 73 * Playback of the playing media group will be repeated. 74 * A group is a logical block of media items which is specified in the section 5.7 of the 75 * Bluetooth AVRCP 1.6. An example of a group is the playlist. 76 */ 77 public static final int REPEAT_MODE_GROUP = 3; 78 79 /** 80 * @hide 81 */ 82 @RestrictTo(LIBRARY_GROUP) 83 @IntDef({SHUFFLE_MODE_NONE, SHUFFLE_MODE_ALL, SHUFFLE_MODE_GROUP}) 84 @Retention(RetentionPolicy.SOURCE) 85 public @interface ShuffleMode {} 86 87 /** 88 * Media list will be played in order. 89 */ 90 public static final int SHUFFLE_MODE_NONE = 0; 91 92 /** 93 * Media list will be played in shuffled order. 94 */ 95 public static final int SHUFFLE_MODE_ALL = 1; 96 97 /** 98 * Media group will be played in shuffled order. 99 * A group is a logical block of media items which is specified in the section 5.7 of the 100 * Bluetooth AVRCP 1.6. An example of a group is the playlist. 101 */ 102 public static final int SHUFFLE_MODE_GROUP = 2; 103 104 private final Object mLock = new Object(); 105 @GuardedBy("mLock") 106 private final SimpleArrayMap<PlaylistEventCallback, Executor> mCallbacks = 107 new SimpleArrayMap<>(); 108 109 /** 110 * Register {@link PlaylistEventCallback} to listen changes in the underlying 111 * {@link MediaPlaylistAgent}. 112 * 113 * @param executor a callback Executor 114 * @param callback a PlaylistEventCallback 115 * @throws IllegalArgumentException if executor or callback is {@code null}. 116 */ 117 public final void registerPlaylistEventCallback( 118 @NonNull /*@CallbackExecutor*/ Executor executor, 119 @NonNull PlaylistEventCallback callback) { 120 if (executor == null) { 121 throw new IllegalArgumentException("executor shouldn't be null"); 122 } 123 if (callback == null) { 124 throw new IllegalArgumentException("callback shouldn't be null"); 125 } 126 127 synchronized (mLock) { 128 if (mCallbacks.get(callback) != null) { 129 Log.w(TAG, "callback is already added. Ignoring."); 130 return; 131 } 132 mCallbacks.put(callback, executor); 133 } 134 } 135 136 /** 137 * Unregister the previously registered {@link PlaylistEventCallback}. 138 * 139 * @param callback the callback to be removed 140 * @throws IllegalArgumentException if the callback is {@code null}. 141 */ 142 public final void unregisterPlaylistEventCallback(@NonNull PlaylistEventCallback callback) { 143 if (callback == null) { 144 throw new IllegalArgumentException("callback shouldn't be null"); 145 } 146 synchronized (mLock) { 147 mCallbacks.remove(callback); 148 } 149 } 150 151 /** 152 * Notifies the current playlist and playlist metadata. Call this API when the playlist is 153 * changed. 154 * <p> 155 * Registered {@link PlaylistEventCallback} would receive this event through the 156 * {@link PlaylistEventCallback#onPlaylistChanged(MediaPlaylistAgent, List, MediaMetadata2)}. 157 */ 158 public final void notifyPlaylistChanged() { 159 SimpleArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks(); 160 final List<MediaItem2> playlist = getPlaylist(); 161 final MediaMetadata2 metadata = getPlaylistMetadata(); 162 for (int i = 0; i < callbacks.size(); i++) { 163 final PlaylistEventCallback callback = callbacks.keyAt(i); 164 final Executor executor = callbacks.valueAt(i); 165 executor.execute(new Runnable() { 166 @Override 167 public void run() { 168 callback.onPlaylistChanged( 169 MediaPlaylistAgent.this, playlist, metadata); 170 } 171 }); 172 } 173 } 174 175 /** 176 * Notifies the current playlist metadata. Call this API when the playlist metadata is changed. 177 * <p> 178 * Registered {@link PlaylistEventCallback} would receive this event through the 179 * {@link PlaylistEventCallback#onPlaylistMetadataChanged(MediaPlaylistAgent, MediaMetadata2)}. 180 */ 181 public final void notifyPlaylistMetadataChanged() { 182 SimpleArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks(); 183 for (int i = 0; i < callbacks.size(); i++) { 184 final PlaylistEventCallback callback = callbacks.keyAt(i); 185 final Executor executor = callbacks.valueAt(i); 186 executor.execute(new Runnable() { 187 @Override 188 public void run() { 189 callback.onPlaylistMetadataChanged( 190 MediaPlaylistAgent.this, MediaPlaylistAgent.this.getPlaylistMetadata()); 191 } 192 }); 193 } 194 } 195 196 /** 197 * Notifies the current shuffle mode. Call this API when the shuffle mode is changed. 198 * <p> 199 * Registered {@link PlaylistEventCallback} would receive this event through the 200 * {@link PlaylistEventCallback#onShuffleModeChanged(MediaPlaylistAgent, int)}. 201 */ 202 public final void notifyShuffleModeChanged() { 203 SimpleArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks(); 204 for (int i = 0; i < callbacks.size(); i++) { 205 final PlaylistEventCallback callback = callbacks.keyAt(i); 206 final Executor executor = callbacks.valueAt(i); 207 executor.execute(new Runnable() { 208 @Override 209 public void run() { 210 callback.onShuffleModeChanged( 211 MediaPlaylistAgent.this, MediaPlaylistAgent.this.getShuffleMode()); 212 } 213 }); 214 } 215 } 216 217 /** 218 * Notifies the current repeat mode. Call this API when the repeat mode is changed. 219 * <p> 220 * Registered {@link PlaylistEventCallback} would receive this event through the 221 * {@link PlaylistEventCallback#onRepeatModeChanged(MediaPlaylistAgent, int)}. 222 */ 223 public final void notifyRepeatModeChanged() { 224 SimpleArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks(); 225 for (int i = 0; i < callbacks.size(); i++) { 226 final PlaylistEventCallback callback = callbacks.keyAt(i); 227 final Executor executor = callbacks.valueAt(i); 228 executor.execute(new Runnable() { 229 @Override 230 public void run() { 231 callback.onRepeatModeChanged( 232 MediaPlaylistAgent.this, MediaPlaylistAgent.this.getRepeatMode()); 233 } 234 }); 235 } 236 } 237 238 /** 239 * Returns the playlist 240 * 241 * @return playlist, or null if none is set. 242 */ 243 public abstract @Nullable List<MediaItem2> getPlaylist(); 244 245 /** 246 * Sets the playlist with the metadata. 247 * <p> 248 * When the playlist is changed, call {@link #notifyPlaylistChanged()} to notify changes to the 249 * registered callbacks. 250 * 251 * @param list playlist 252 * @param metadata metadata of the playlist 253 * @see #notifyPlaylistChanged() 254 */ 255 public abstract void setPlaylist(@NonNull List<MediaItem2> list, 256 @Nullable MediaMetadata2 metadata); 257 258 /** 259 * Returns the playlist metadata 260 * 261 * @return metadata metadata of the playlist, or null if none is set 262 */ 263 public abstract @Nullable MediaMetadata2 getPlaylistMetadata(); 264 265 /** 266 * Updates the playlist metadata. 267 * <p> 268 * When the playlist metadata is changed, call {@link #notifyPlaylistMetadataChanged()} to 269 * notify changes to the registered callbacks. 270 * 271 * @param metadata metadata of the playlist 272 * @see #notifyPlaylistMetadataChanged() 273 */ 274 public abstract void updatePlaylistMetadata(@Nullable MediaMetadata2 metadata); 275 276 /** 277 * Returns currently playing media item. 278 */ 279 public abstract MediaItem2 getCurrentMediaItem(); 280 281 /** 282 * Adds the media item to the playlist at position index. Index equals or greater than 283 * the current playlist size (e.g. {@link Integer#MAX_VALUE}) will add the item at the end of 284 * the playlist. 285 * <p> 286 * This will not change the currently playing media item. 287 * If index is less than or equal to the current index of the playlist, 288 * the current index of the playlist will be incremented correspondingly. 289 * 290 * @param index the index you want to add 291 * @param item the media item you want to add 292 */ 293 public abstract void addPlaylistItem(int index, @NonNull MediaItem2 item); 294 295 /** 296 * Removes the media item from the playlist 297 * 298 * @param item media item to remove 299 */ 300 public abstract void removePlaylistItem(@NonNull MediaItem2 item); 301 302 /** 303 * Replace the media item at index in the playlist. This can be also used to update metadata of 304 * an item. 305 * 306 * @param index the index of the item to replace 307 * @param item the new item 308 */ 309 public abstract void replacePlaylistItem(int index, @NonNull MediaItem2 item); 310 311 /** 312 * Skips to the the media item, and plays from it. 313 * 314 * @param item media item to start playing from 315 */ 316 public abstract void skipToPlaylistItem(@NonNull MediaItem2 item); 317 318 /** 319 * Skips to the previous item in the playlist. 320 */ 321 public abstract void skipToPreviousItem(); 322 323 /** 324 * Skips to the next item in the playlist. 325 */ 326 public abstract void skipToNextItem(); 327 328 /** 329 * Gets the repeat mode 330 * 331 * @return repeat mode 332 * @see #REPEAT_MODE_NONE 333 * @see #REPEAT_MODE_ONE 334 * @see #REPEAT_MODE_ALL 335 * @see #REPEAT_MODE_GROUP 336 */ 337 public abstract @RepeatMode int getRepeatMode(); 338 339 /** 340 * Sets the repeat mode. 341 * <p> 342 * When the repeat mode is changed, call {@link #notifyRepeatModeChanged()} to notify changes 343 * to the registered callbacks. 344 * 345 * @param repeatMode repeat mode 346 * @see #REPEAT_MODE_NONE 347 * @see #REPEAT_MODE_ONE 348 * @see #REPEAT_MODE_ALL 349 * @see #REPEAT_MODE_GROUP 350 * @see #notifyRepeatModeChanged() 351 */ 352 public abstract void setRepeatMode(@RepeatMode int repeatMode); 353 354 /** 355 * Gets the shuffle mode 356 * 357 * @return The shuffle mode 358 * @see #SHUFFLE_MODE_NONE 359 * @see #SHUFFLE_MODE_ALL 360 * @see #SHUFFLE_MODE_GROUP 361 */ 362 public abstract @ShuffleMode int getShuffleMode(); 363 364 /** 365 * Sets the shuffle mode. 366 * <p> 367 * When the shuffle mode is changed, call {@link #notifyShuffleModeChanged()} to notify changes 368 * to the registered callbacks. 369 * 370 * @param shuffleMode The shuffle mode 371 * @see #SHUFFLE_MODE_NONE 372 * @see #SHUFFLE_MODE_ALL 373 * @see #SHUFFLE_MODE_GROUP 374 * @see #notifyShuffleModeChanged() 375 */ 376 public abstract void setShuffleMode(@ShuffleMode int shuffleMode); 377 378 /** 379 * Called by {@link MediaSession2} when it wants to translate {@link DataSourceDesc} from the 380 * {@link MediaPlayerInterface.PlayerEventCallback} to the {@link MediaItem2}. Override this 381 * method if you want to create {@link DataSourceDesc}s dynamically, instead of specifying them 382 * with {@link #setPlaylist(List, MediaMetadata2)}. 383 * <p> 384 * Session would throw an exception if this returns {@code null} for the dsd from the 385 * {@link MediaPlayerInterface.PlayerEventCallback}. 386 * <p> 387 * Default implementation calls the {@link #getPlaylist()} and searches the {@link MediaItem2} 388 * with the {@param dsd}. 389 * 390 * @param dsd The dsd to query 391 * @return A {@link MediaItem2} object in the playlist that matches given {@code dsd}. 392 * @throws IllegalArgumentException if {@code dsd} is null 393 */ 394 public @Nullable MediaItem2 getMediaItem(@NonNull DataSourceDesc dsd) { 395 if (dsd == null) { 396 throw new IllegalArgumentException("dsd shouldn't be null"); 397 } 398 List<MediaItem2> itemList = getPlaylist(); 399 if (itemList == null) { 400 return null; 401 } 402 for (int i = 0; i < itemList.size(); i++) { 403 MediaItem2 item = itemList.get(i); 404 if (item != null && item.getDataSourceDesc().equals(dsd)) { 405 return item; 406 } 407 } 408 return null; 409 } 410 411 private SimpleArrayMap<PlaylistEventCallback, Executor> getCallbacks() { 412 SimpleArrayMap<PlaylistEventCallback, Executor> callbacks = new SimpleArrayMap<>(); 413 synchronized (mLock) { 414 callbacks.putAll(mCallbacks); 415 } 416 return callbacks; 417 } 418 419 /** 420 * A callback class to receive notifications for events on the media player. See 421 * {@link MediaPlaylistAgent#registerPlaylistEventCallback(Executor, PlaylistEventCallback)} 422 * to register this callback. 423 */ 424 public abstract static class PlaylistEventCallback { 425 /** 426 * Called when a playlist is changed. 427 * 428 * @param playlistAgent playlist agent for this event 429 * @param list new playlist 430 * @param metadata new metadata 431 */ 432 public void onPlaylistChanged(@NonNull MediaPlaylistAgent playlistAgent, 433 @NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) { } 434 435 /** 436 * Called when a playlist metadata is changed. 437 * 438 * @param playlistAgent playlist agent for this event 439 * @param metadata new metadata 440 */ 441 public void onPlaylistMetadataChanged(@NonNull MediaPlaylistAgent playlistAgent, 442 @Nullable MediaMetadata2 metadata) { } 443 444 /** 445 * Called when the shuffle mode is changed. 446 * 447 * @param playlistAgent playlist agent for this event 448 * @param shuffleMode repeat mode 449 * @see #SHUFFLE_MODE_NONE 450 * @see #SHUFFLE_MODE_ALL 451 * @see #SHUFFLE_MODE_GROUP 452 */ 453 public void onShuffleModeChanged(@NonNull MediaPlaylistAgent playlistAgent, 454 @ShuffleMode int shuffleMode) { } 455 456 /** 457 * Called when the repeat mode is changed. 458 * 459 * @param playlistAgent playlist agent for this event 460 * @param repeatMode repeat mode 461 * @see #REPEAT_MODE_NONE 462 * @see #REPEAT_MODE_ONE 463 * @see #REPEAT_MODE_ALL 464 * @see #REPEAT_MODE_GROUP 465 */ 466 public void onRepeatModeChanged(@NonNull MediaPlaylistAgent playlistAgent, 467 @RepeatMode int repeatMode) { } 468 } 469} 470