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 17 package androidx.media; 18 19 import static androidx.media.MediaPlayerInterface.BUFFERING_STATE_UNKNOWN; 20 import static androidx.media.MediaSession2.ControllerCb; 21 import static androidx.media.MediaSession2.ControllerInfo; 22 import static androidx.media.MediaSession2.ErrorCode; 23 import static androidx.media.MediaSession2.OnDataSourceMissingHelper; 24 import static androidx.media.MediaSession2.SessionCallback; 25 import static androidx.media.SessionToken2.TYPE_LIBRARY_SERVICE; 26 import static androidx.media.SessionToken2.TYPE_SESSION; 27 import static androidx.media.SessionToken2.TYPE_SESSION_SERVICE; 28 29 import android.annotation.TargetApi; 30 import android.app.PendingIntent; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.pm.PackageManager; 34 import android.content.pm.ResolveInfo; 35 import android.media.AudioFocusRequest; 36 import android.media.AudioManager; 37 import android.os.Build; 38 import android.os.Bundle; 39 import android.os.DeadObjectException; 40 import android.os.Handler; 41 import android.os.HandlerThread; 42 import android.os.Process; 43 import android.os.RemoteException; 44 import android.os.ResultReceiver; 45 import android.support.v4.media.session.MediaSessionCompat; 46 import android.support.v4.media.session.PlaybackStateCompat; 47 import android.text.TextUtils; 48 import android.util.Log; 49 50 import androidx.annotation.GuardedBy; 51 import androidx.annotation.NonNull; 52 import androidx.annotation.Nullable; 53 import androidx.core.util.ObjectsCompat; 54 import androidx.media.MediaController2.PlaybackInfo; 55 import androidx.media.MediaPlayerInterface.PlayerEventCallback; 56 import androidx.media.MediaPlaylistAgent.PlaylistEventCallback; 57 58 import java.lang.ref.WeakReference; 59 import java.util.List; 60 import java.util.NoSuchElementException; 61 import java.util.concurrent.Executor; 62 import java.util.concurrent.RejectedExecutionException; 63 64 @TargetApi(Build.VERSION_CODES.KITKAT) 65 class MediaSession2ImplBase extends MediaSession2.SupportLibraryImpl { 66 static final String TAG = "MS2ImplBase"; 67 static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 68 69 private final Object mLock = new Object(); 70 71 private final Context mContext; 72 private final HandlerThread mHandlerThread; 73 private final Handler mHandler; 74 private final MediaSessionCompat mSessionCompat; 75 private final MediaSession2Stub mSession2Stub; 76 private final MediaSessionLegacyStub mSessionLegacyStub; 77 private final String mId; 78 private final Executor mCallbackExecutor; 79 private final SessionCallback mCallback; 80 private final SessionToken2 mSessionToken; 81 private final AudioManager mAudioManager; 82 private final MediaPlayerInterface.PlayerEventCallback mPlayerEventCallback; 83 private final MediaPlaylistAgent.PlaylistEventCallback mPlaylistEventCallback; 84 private final MediaSession2 mInstance; 85 86 @GuardedBy("mLock") 87 private MediaPlayerInterface mPlayer; 88 @GuardedBy("mLock") 89 private MediaPlaylistAgent mPlaylistAgent; 90 @GuardedBy("mLock") 91 private SessionPlaylistAgentImplBase mSessionPlaylistAgent; 92 @GuardedBy("mLock") 93 private VolumeProviderCompat mVolumeProvider; 94 @GuardedBy("mLock") 95 private OnDataSourceMissingHelper mDsmHelper; 96 @GuardedBy("mLock") 97 private PlaybackInfo mPlaybackInfo; 98 MediaSession2ImplBase(Context context, MediaSessionCompat sessionCompat, String id, MediaPlayerInterface player, MediaPlaylistAgent playlistAgent, VolumeProviderCompat volumeProvider, PendingIntent sessionActivity, Executor callbackExecutor, SessionCallback callback)99 MediaSession2ImplBase(Context context, MediaSessionCompat sessionCompat, String id, 100 MediaPlayerInterface player, MediaPlaylistAgent playlistAgent, 101 VolumeProviderCompat volumeProvider, PendingIntent sessionActivity, 102 Executor callbackExecutor, SessionCallback callback) { 103 mContext = context; 104 mInstance = createInstance(); 105 mHandlerThread = new HandlerThread("MediaController2_Thread"); 106 mHandlerThread.start(); 107 mHandler = new Handler(mHandlerThread.getLooper()); 108 109 mSessionCompat = sessionCompat; 110 mSession2Stub = new MediaSession2Stub(this); 111 mSessionLegacyStub = new MediaSessionLegacyStub(this); 112 mSessionCompat.setCallback(mSession2Stub, mHandler); 113 mSessionCompat.setSessionActivity(sessionActivity); 114 115 mId = id; 116 mCallback = callback; 117 mCallbackExecutor = callbackExecutor; 118 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 119 120 mPlayerEventCallback = new MyPlayerEventCallback(this); 121 mPlaylistEventCallback = new MyPlaylistEventCallback(this); 122 123 // Infer type from the id and package name. 124 String libraryService = getServiceName(context, MediaLibraryService2.SERVICE_INTERFACE, id); 125 String sessionService = getServiceName(context, MediaSessionService2.SERVICE_INTERFACE, id); 126 if (sessionService != null && libraryService != null) { 127 throw new IllegalArgumentException("Ambiguous session type. Multiple" 128 + " session services define the same id=" + id); 129 } else if (libraryService != null) { 130 mSessionToken = new SessionToken2(Process.myUid(), TYPE_LIBRARY_SERVICE, 131 context.getPackageName(), libraryService, id, mSessionCompat.getSessionToken()); 132 } else if (sessionService != null) { 133 mSessionToken = new SessionToken2(Process.myUid(), TYPE_SESSION_SERVICE, 134 context.getPackageName(), sessionService, id, mSessionCompat.getSessionToken()); 135 } else { 136 mSessionToken = new SessionToken2(Process.myUid(), TYPE_SESSION, 137 context.getPackageName(), null, id, mSessionCompat.getSessionToken()); 138 } 139 updatePlayer(player, playlistAgent, volumeProvider); 140 } 141 142 @Override updatePlayer(@onNull MediaPlayerInterface player, @Nullable MediaPlaylistAgent playlistAgent, @Nullable VolumeProviderCompat volumeProvider)143 public void updatePlayer(@NonNull MediaPlayerInterface player, 144 @Nullable MediaPlaylistAgent playlistAgent, 145 @Nullable VolumeProviderCompat volumeProvider) { 146 if (player == null) { 147 throw new IllegalArgumentException("player shouldn't be null"); 148 } 149 final boolean hasPlayerChanged; 150 final boolean hasAgentChanged; 151 final boolean hasPlaybackInfoChanged; 152 final MediaPlayerInterface oldPlayer; 153 final MediaPlaylistAgent oldAgent; 154 final PlaybackInfo info = createPlaybackInfo(volumeProvider, player.getAudioAttributes()); 155 synchronized (mLock) { 156 hasPlayerChanged = (mPlayer != player); 157 hasAgentChanged = (mPlaylistAgent != playlistAgent); 158 hasPlaybackInfoChanged = (mPlaybackInfo != info); 159 oldPlayer = mPlayer; 160 oldAgent = mPlaylistAgent; 161 mPlayer = player; 162 if (playlistAgent == null) { 163 mSessionPlaylistAgent = new SessionPlaylistAgentImplBase(this, mPlayer); 164 if (mDsmHelper != null) { 165 mSessionPlaylistAgent.setOnDataSourceMissingHelper(mDsmHelper); 166 } 167 playlistAgent = mSessionPlaylistAgent; 168 } 169 mPlaylistAgent = playlistAgent; 170 mVolumeProvider = volumeProvider; 171 mPlaybackInfo = info; 172 } 173 if (volumeProvider == null) { 174 int stream = getLegacyStreamType(player.getAudioAttributes()); 175 mSessionCompat.setPlaybackToLocal(stream); 176 } 177 if (player != oldPlayer) { 178 player.registerPlayerEventCallback(mCallbackExecutor, mPlayerEventCallback); 179 if (oldPlayer != null) { 180 // Warning: Poorly implement player may ignore this 181 oldPlayer.unregisterPlayerEventCallback(mPlayerEventCallback); 182 } 183 } 184 if (playlistAgent != oldAgent) { 185 playlistAgent.registerPlaylistEventCallback(mCallbackExecutor, mPlaylistEventCallback); 186 if (oldAgent != null) { 187 // Warning: Poorly implement agent may ignore this 188 oldAgent.unregisterPlaylistEventCallback(mPlaylistEventCallback); 189 } 190 } 191 192 if (oldPlayer != null) { 193 // If it's not the first updatePlayer(), tell changes in the player, agent, and playback 194 // info. 195 if (hasAgentChanged) { 196 // Update agent first. Otherwise current position may be changed off the current 197 // media item's duration, and controller may consider it as a bug. 198 notifyAgentUpdatedNotLocked(oldAgent); 199 } 200 if (hasPlayerChanged) { 201 notifyPlayerUpdatedNotLocked(oldPlayer); 202 } 203 if (hasPlaybackInfoChanged) { 204 // Currently hasPlaybackInfo is always true, but check this in case that we're 205 // adding PlaybackInfo#equals(). 206 notifyToAllControllers(new NotifyRunnable() { 207 @Override 208 public void run(ControllerCb callback) throws RemoteException { 209 callback.onPlaybackInfoChanged(info); 210 } 211 }); 212 } 213 } 214 } 215 createPlaybackInfo(VolumeProviderCompat volumeProvider, AudioAttributesCompat attrs)216 private PlaybackInfo createPlaybackInfo(VolumeProviderCompat volumeProvider, 217 AudioAttributesCompat attrs) { 218 PlaybackInfo info; 219 if (volumeProvider == null) { 220 int stream = getLegacyStreamType(attrs); 221 int controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE; 222 if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) { 223 controlType = VolumeProviderCompat.VOLUME_CONTROL_FIXED; 224 } 225 info = PlaybackInfo.createPlaybackInfo( 226 PlaybackInfo.PLAYBACK_TYPE_LOCAL, 227 attrs, 228 controlType, 229 mAudioManager.getStreamMaxVolume(stream), 230 mAudioManager.getStreamVolume(stream)); 231 } else { 232 info = PlaybackInfo.createPlaybackInfo( 233 PlaybackInfo.PLAYBACK_TYPE_REMOTE, 234 attrs, 235 volumeProvider.getVolumeControl(), 236 volumeProvider.getMaxVolume(), 237 volumeProvider.getCurrentVolume()); 238 } 239 return info; 240 } 241 getLegacyStreamType(@ullable AudioAttributesCompat attrs)242 private int getLegacyStreamType(@Nullable AudioAttributesCompat attrs) { 243 int stream; 244 if (attrs == null) { 245 stream = AudioManager.STREAM_MUSIC; 246 } else { 247 stream = attrs.getLegacyStreamType(); 248 if (stream == AudioManager.USE_DEFAULT_STREAM_TYPE) { 249 // Usually, AudioAttributesCompat#getLegacyStreamType() does not return 250 // USE_DEFAULT_STREAM_TYPE unless the developer sets it with 251 // AudioAttributesCompat.Builder#setLegacyStreamType(). 252 // But for safety, let's convert USE_DEFAULT_STREAM_TYPE to STREAM_MUSIC here. 253 stream = AudioManager.STREAM_MUSIC; 254 } 255 } 256 return stream; 257 } 258 259 @Override close()260 public void close() { 261 synchronized (mLock) { 262 if (mPlayer == null) { 263 return; 264 } 265 mPlayer.unregisterPlayerEventCallback(mPlayerEventCallback); 266 mPlayer = null; 267 mSessionCompat.release(); 268 mHandler.removeCallbacksAndMessages(null); 269 if (mHandlerThread.isAlive()) { 270 if (Build.VERSION.SDK_INT >= 18) { 271 mHandlerThread.quitSafely(); 272 } else { 273 mHandlerThread.quit(); 274 } 275 } 276 } 277 } 278 279 @Override getPlayer()280 public @NonNull MediaPlayerInterface getPlayer() { 281 synchronized (mLock) { 282 return mPlayer; 283 } 284 } 285 286 @Override getPlaylistAgent()287 public @NonNull MediaPlaylistAgent getPlaylistAgent() { 288 synchronized (mLock) { 289 return mPlaylistAgent; 290 } 291 } 292 293 @Override getVolumeProvider()294 public @Nullable VolumeProviderCompat getVolumeProvider() { 295 synchronized (mLock) { 296 return mVolumeProvider; 297 } 298 } 299 300 @Override getToken()301 public @NonNull SessionToken2 getToken() { 302 return mSessionToken; 303 } 304 305 @Override getConnectedControllers()306 public @NonNull List<ControllerInfo> getConnectedControllers() { 307 return mSession2Stub.getConnectedControllers(); 308 } 309 310 @Override setAudioFocusRequest(@ullable AudioFocusRequest afr)311 public void setAudioFocusRequest(@Nullable AudioFocusRequest afr) { 312 } 313 314 @Override setCustomLayout(@onNull ControllerInfo controller, @NonNull final List<MediaSession2.CommandButton> layout)315 public void setCustomLayout(@NonNull ControllerInfo controller, 316 @NonNull final List<MediaSession2.CommandButton> layout) { 317 if (controller == null) { 318 throw new IllegalArgumentException("controller shouldn't be null"); 319 } 320 if (layout == null) { 321 throw new IllegalArgumentException("layout shouldn't be null"); 322 } 323 notifyToController(controller, new NotifyRunnable() { 324 @Override 325 public void run(ControllerCb callback) throws RemoteException { 326 callback.onCustomLayoutChanged(layout); 327 } 328 }); 329 } 330 331 @Override setAllowedCommands(@onNull ControllerInfo controller, @NonNull final SessionCommandGroup2 commands)332 public void setAllowedCommands(@NonNull ControllerInfo controller, 333 @NonNull final SessionCommandGroup2 commands) { 334 if (controller == null) { 335 throw new IllegalArgumentException("controller shouldn't be null"); 336 } 337 if (commands == null) { 338 throw new IllegalArgumentException("commands shouldn't be null"); 339 } 340 mSession2Stub.setAllowedCommands(controller, commands); 341 notifyToController(controller, new NotifyRunnable() { 342 @Override 343 public void run(ControllerCb callback) throws RemoteException { 344 callback.onAllowedCommandsChanged(commands); 345 } 346 }); 347 } 348 349 @Override sendCustomCommand(@onNull final SessionCommand2 command, @Nullable final Bundle args)350 public void sendCustomCommand(@NonNull final SessionCommand2 command, 351 @Nullable final Bundle args) { 352 if (command == null) { 353 throw new IllegalArgumentException("command shouldn't be null"); 354 } 355 notifyToAllControllers(new NotifyRunnable() { 356 @Override 357 public void run(ControllerCb callback) throws RemoteException { 358 callback.onCustomCommand(command, args, null); 359 } 360 }); 361 } 362 363 @Override sendCustomCommand(@onNull ControllerInfo controller, @NonNull final SessionCommand2 command, @Nullable final Bundle args, @Nullable final ResultReceiver receiver)364 public void sendCustomCommand(@NonNull ControllerInfo controller, 365 @NonNull final SessionCommand2 command, @Nullable final Bundle args, 366 @Nullable final ResultReceiver receiver) { 367 if (controller == null) { 368 throw new IllegalArgumentException("controller shouldn't be null"); 369 } 370 if (command == null) { 371 throw new IllegalArgumentException("command shouldn't be null"); 372 } 373 notifyToController(controller, new NotifyRunnable() { 374 @Override 375 public void run(ControllerCb callback) throws RemoteException { 376 callback.onCustomCommand(command, args, receiver); 377 } 378 }); 379 } 380 381 @Override play()382 public void play() { 383 MediaPlayerInterface player; 384 synchronized (mLock) { 385 player = mPlayer; 386 } 387 if (player != null) { 388 player.play(); 389 } else if (DEBUG) { 390 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 391 } 392 } 393 394 @Override pause()395 public void pause() { 396 MediaPlayerInterface player; 397 synchronized (mLock) { 398 player = mPlayer; 399 } 400 if (player != null) { 401 player.pause(); 402 } else if (DEBUG) { 403 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 404 } 405 } 406 407 @Override reset()408 public void reset() { 409 MediaPlayerInterface player; 410 synchronized (mLock) { 411 player = mPlayer; 412 } 413 if (player != null) { 414 player.reset(); 415 } else if (DEBUG) { 416 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 417 } 418 } 419 420 @Override prepare()421 public void prepare() { 422 MediaPlayerInterface player; 423 synchronized (mLock) { 424 player = mPlayer; 425 } 426 if (player != null) { 427 player.prepare(); 428 } else if (DEBUG) { 429 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 430 } 431 } 432 433 @Override seekTo(long pos)434 public void seekTo(long pos) { 435 MediaPlayerInterface player; 436 synchronized (mLock) { 437 player = mPlayer; 438 } 439 if (player != null) { 440 player.seekTo(pos); 441 } else if (DEBUG) { 442 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 443 } 444 } 445 446 @Override skipForward()447 public void skipForward() { 448 // To match with KEYCODE_MEDIA_SKIP_FORWARD 449 } 450 451 @Override skipBackward()452 public void skipBackward() { 453 // To match with KEYCODE_MEDIA_SKIP_BACKWARD 454 } 455 456 @Override notifyError(@rrorCode final int errorCode, @Nullable final Bundle extras)457 public void notifyError(@ErrorCode final int errorCode, @Nullable final Bundle extras) { 458 notifyToAllControllers(new NotifyRunnable() { 459 @Override 460 public void run(ControllerCb callback) throws RemoteException { 461 callback.onError(errorCode, extras); 462 } 463 }); 464 } 465 466 @Override notifyRoutesInfoChanged(@onNull ControllerInfo controller, @Nullable final List<Bundle> routes)467 public void notifyRoutesInfoChanged(@NonNull ControllerInfo controller, 468 @Nullable final List<Bundle> routes) { 469 notifyToController(controller, new NotifyRunnable() { 470 @Override 471 public void run(ControllerCb callback) throws RemoteException { 472 callback.onRoutesInfoChanged(routes); 473 } 474 }); 475 } 476 477 @Override getPlayerState()478 public @MediaPlayerInterface.PlayerState int getPlayerState() { 479 MediaPlayerInterface player; 480 synchronized (mLock) { 481 player = mPlayer; 482 } 483 if (player != null) { 484 return player.getPlayerState(); 485 } else if (DEBUG) { 486 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 487 } 488 return MediaPlayerInterface.PLAYER_STATE_ERROR; 489 } 490 491 @Override getCurrentPosition()492 public long getCurrentPosition() { 493 MediaPlayerInterface player; 494 synchronized (mLock) { 495 player = mPlayer; 496 } 497 if (player != null) { 498 return player.getCurrentPosition(); 499 } else if (DEBUG) { 500 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 501 } 502 return MediaPlayerInterface.UNKNOWN_TIME; 503 } 504 505 @Override getDuration()506 public long getDuration() { 507 MediaPlayerInterface player; 508 synchronized (mLock) { 509 player = mPlayer; 510 } 511 if (player != null) { 512 // Note: This should be the same as 513 // getCurrentMediaItem().getMetadata().getLong(METADATA_KEY_DURATION) 514 return player.getDuration(); 515 } else if (DEBUG) { 516 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 517 } 518 return MediaPlayerInterface.UNKNOWN_TIME; 519 } 520 521 @Override getBufferedPosition()522 public long getBufferedPosition() { 523 MediaPlayerInterface player; 524 synchronized (mLock) { 525 player = mPlayer; 526 } 527 if (player != null) { 528 return player.getBufferedPosition(); 529 } else if (DEBUG) { 530 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 531 } 532 return MediaPlayerInterface.UNKNOWN_TIME; 533 } 534 535 @Override getBufferingState()536 public @MediaPlayerInterface.BuffState int getBufferingState() { 537 MediaPlayerInterface player; 538 synchronized (mLock) { 539 player = mPlayer; 540 } 541 if (player != null) { 542 return player.getBufferingState(); 543 } else if (DEBUG) { 544 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 545 } 546 return BUFFERING_STATE_UNKNOWN; 547 } 548 549 @Override getPlaybackSpeed()550 public float getPlaybackSpeed() { 551 MediaPlayerInterface player; 552 synchronized (mLock) { 553 player = mPlayer; 554 } 555 if (player != null) { 556 return player.getPlaybackSpeed(); 557 } else if (DEBUG) { 558 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 559 } 560 return 1.0f; 561 } 562 563 @Override setPlaybackSpeed(float speed)564 public void setPlaybackSpeed(float speed) { 565 MediaPlayerInterface player; 566 synchronized (mLock) { 567 player = mPlayer; 568 } 569 if (player != null) { 570 player.setPlaybackSpeed(speed); 571 } else if (DEBUG) { 572 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 573 } 574 } 575 576 @Override setOnDataSourceMissingHelper( @onNull OnDataSourceMissingHelper helper)577 public void setOnDataSourceMissingHelper( 578 @NonNull OnDataSourceMissingHelper helper) { 579 if (helper == null) { 580 throw new IllegalArgumentException("helper shouldn't be null"); 581 } 582 synchronized (mLock) { 583 mDsmHelper = helper; 584 if (mSessionPlaylistAgent != null) { 585 mSessionPlaylistAgent.setOnDataSourceMissingHelper(helper); 586 } 587 } 588 } 589 590 @Override clearOnDataSourceMissingHelper()591 public void clearOnDataSourceMissingHelper() { 592 synchronized (mLock) { 593 mDsmHelper = null; 594 if (mSessionPlaylistAgent != null) { 595 mSessionPlaylistAgent.clearOnDataSourceMissingHelper(); 596 } 597 } 598 } 599 600 @Override getPlaylist()601 public List<MediaItem2> getPlaylist() { 602 MediaPlaylistAgent agent; 603 synchronized (mLock) { 604 agent = mPlaylistAgent; 605 } 606 if (agent != null) { 607 return agent.getPlaylist(); 608 } else if (DEBUG) { 609 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 610 } 611 return null; 612 } 613 614 @Override setPlaylist(@onNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata)615 public void setPlaylist(@NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) { 616 if (list == null) { 617 throw new IllegalArgumentException("list shouldn't be null"); 618 } 619 MediaPlaylistAgent agent; 620 synchronized (mLock) { 621 agent = mPlaylistAgent; 622 } 623 if (agent != null) { 624 agent.setPlaylist(list, metadata); 625 } else if (DEBUG) { 626 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 627 } 628 } 629 630 @Override skipToPlaylistItem(@onNull MediaItem2 item)631 public void skipToPlaylistItem(@NonNull MediaItem2 item) { 632 if (item == null) { 633 throw new IllegalArgumentException("item shouldn't be null"); 634 } 635 MediaPlaylistAgent agent; 636 synchronized (mLock) { 637 agent = mPlaylistAgent; 638 } 639 if (agent != null) { 640 agent.skipToPlaylistItem(item); 641 } else if (DEBUG) { 642 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 643 } 644 } 645 646 @Override skipToPreviousItem()647 public void skipToPreviousItem() { 648 MediaPlaylistAgent agent; 649 synchronized (mLock) { 650 agent = mPlaylistAgent; 651 } 652 if (agent != null) { 653 agent.skipToPreviousItem(); 654 } else if (DEBUG) { 655 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 656 } 657 } 658 659 @Override skipToNextItem()660 public void skipToNextItem() { 661 MediaPlaylistAgent agent; 662 synchronized (mLock) { 663 agent = mPlaylistAgent; 664 } 665 if (agent != null) { 666 agent.skipToNextItem(); 667 } else if (DEBUG) { 668 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 669 } 670 } 671 672 @Override getPlaylistMetadata()673 public MediaMetadata2 getPlaylistMetadata() { 674 MediaPlaylistAgent agent; 675 synchronized (mLock) { 676 agent = mPlaylistAgent; 677 } 678 if (agent != null) { 679 return agent.getPlaylistMetadata(); 680 } else if (DEBUG) { 681 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 682 } 683 return null; 684 } 685 686 @Override addPlaylistItem(int index, @NonNull MediaItem2 item)687 public void addPlaylistItem(int index, @NonNull MediaItem2 item) { 688 if (index < 0) { 689 throw new IllegalArgumentException("index shouldn't be negative"); 690 } 691 if (item == null) { 692 throw new IllegalArgumentException("item shouldn't be null"); 693 } 694 MediaPlaylistAgent agent; 695 synchronized (mLock) { 696 agent = mPlaylistAgent; 697 } 698 if (agent != null) { 699 agent.addPlaylistItem(index, item); 700 } else if (DEBUG) { 701 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 702 } 703 } 704 705 @Override removePlaylistItem(@onNull MediaItem2 item)706 public void removePlaylistItem(@NonNull MediaItem2 item) { 707 if (item == null) { 708 throw new IllegalArgumentException("item shouldn't be null"); 709 } 710 MediaPlaylistAgent agent; 711 synchronized (mLock) { 712 agent = mPlaylistAgent; 713 } 714 if (agent != null) { 715 agent.removePlaylistItem(item); 716 } else if (DEBUG) { 717 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 718 } 719 } 720 721 @Override replacePlaylistItem(int index, @NonNull MediaItem2 item)722 public void replacePlaylistItem(int index, @NonNull MediaItem2 item) { 723 if (index < 0) { 724 throw new IllegalArgumentException("index shouldn't be negative"); 725 } 726 if (item == null) { 727 throw new IllegalArgumentException("item shouldn't be null"); 728 } 729 MediaPlaylistAgent agent; 730 synchronized (mLock) { 731 agent = mPlaylistAgent; 732 } 733 if (agent != null) { 734 agent.replacePlaylistItem(index, item); 735 } else if (DEBUG) { 736 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 737 } 738 } 739 740 @Override getCurrentMediaItem()741 public MediaItem2 getCurrentMediaItem() { 742 MediaPlaylistAgent agent; 743 synchronized (mLock) { 744 agent = mPlaylistAgent; 745 } 746 if (agent != null) { 747 return agent.getCurrentMediaItem(); 748 } else if (DEBUG) { 749 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 750 } 751 return null; 752 } 753 754 @Override updatePlaylistMetadata(@ullable MediaMetadata2 metadata)755 public void updatePlaylistMetadata(@Nullable MediaMetadata2 metadata) { 756 MediaPlaylistAgent agent; 757 synchronized (mLock) { 758 agent = mPlaylistAgent; 759 } 760 if (agent != null) { 761 agent.updatePlaylistMetadata(metadata); 762 } else if (DEBUG) { 763 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 764 } 765 } 766 767 @Override getRepeatMode()768 public @MediaPlaylistAgent.RepeatMode int getRepeatMode() { 769 MediaPlaylistAgent agent; 770 synchronized (mLock) { 771 agent = mPlaylistAgent; 772 } 773 if (agent != null) { 774 return agent.getRepeatMode(); 775 } else if (DEBUG) { 776 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 777 } 778 return MediaPlaylistAgent.REPEAT_MODE_NONE; 779 } 780 781 @Override setRepeatMode(@ediaPlaylistAgent.RepeatMode int repeatMode)782 public void setRepeatMode(@MediaPlaylistAgent.RepeatMode int repeatMode) { 783 MediaPlaylistAgent agent; 784 synchronized (mLock) { 785 agent = mPlaylistAgent; 786 } 787 if (agent != null) { 788 agent.setRepeatMode(repeatMode); 789 } else if (DEBUG) { 790 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 791 } 792 } 793 794 @Override getShuffleMode()795 public @MediaPlaylistAgent.ShuffleMode int getShuffleMode() { 796 MediaPlaylistAgent agent; 797 synchronized (mLock) { 798 agent = mPlaylistAgent; 799 } 800 if (agent != null) { 801 return agent.getShuffleMode(); 802 } else if (DEBUG) { 803 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 804 } 805 return MediaPlaylistAgent.SHUFFLE_MODE_NONE; 806 } 807 808 @Override setShuffleMode(int shuffleMode)809 public void setShuffleMode(int shuffleMode) { 810 MediaPlaylistAgent agent; 811 synchronized (mLock) { 812 agent = mPlaylistAgent; 813 } 814 if (agent != null) { 815 agent.setShuffleMode(shuffleMode); 816 } else if (DEBUG) { 817 Log.d(TAG, "API calls after the close()", new IllegalStateException()); 818 } 819 } 820 821 /////////////////////////////////////////////////// 822 // LibrarySession Methods 823 /////////////////////////////////////////////////// 824 825 @Override notifyChildrenChanged(ControllerInfo controller, final String parentId, final int itemCount, final Bundle extras, List<MediaSessionManager.RemoteUserInfo> subscribingBrowsers)826 void notifyChildrenChanged(ControllerInfo controller, final String parentId, 827 final int itemCount, final Bundle extras, 828 List<MediaSessionManager.RemoteUserInfo> subscribingBrowsers) { 829 if (controller == null) { 830 throw new IllegalArgumentException("controller shouldn't be null"); 831 } 832 if (TextUtils.isEmpty(parentId)) { 833 throw new IllegalArgumentException("query shouldn't be empty"); 834 } 835 836 // Notify controller only if it has subscribed the parentId. 837 for (MediaSessionManager.RemoteUserInfo info : subscribingBrowsers) { 838 if (info.getPackageName().equals(controller.getPackageName()) 839 && info.getUid() == controller.getUid()) { 840 notifyToController(controller, new NotifyRunnable() { 841 @Override 842 public void run(ControllerCb callback) throws RemoteException { 843 callback.onChildrenChanged(parentId, itemCount, extras); 844 } 845 }); 846 return; 847 } 848 } 849 } 850 851 @Override notifySearchResultChanged(ControllerInfo controller, final String query, final int itemCount, final Bundle extras)852 void notifySearchResultChanged(ControllerInfo controller, final String query, 853 final int itemCount, final Bundle extras) { 854 if (controller == null) { 855 throw new IllegalArgumentException("controller shouldn't be null"); 856 } 857 if (TextUtils.isEmpty(query)) { 858 throw new IllegalArgumentException("query shouldn't be empty"); 859 } 860 notifyToController(controller, new NotifyRunnable() { 861 @Override 862 public void run(ControllerCb callback) throws RemoteException { 863 callback.onSearchResultChanged(query, itemCount, extras); 864 } 865 }); 866 } 867 868 /////////////////////////////////////////////////// 869 // package private and private methods 870 /////////////////////////////////////////////////// 871 @Override createInstance()872 MediaSession2 createInstance() { 873 return new MediaSession2(this); 874 } 875 876 @Override getInstance()877 @NonNull MediaSession2 getInstance() { 878 return mInstance; 879 } 880 881 @Override getContext()882 Context getContext() { 883 return mContext; 884 } 885 886 @Override getCallbackExecutor()887 Executor getCallbackExecutor() { 888 return mCallbackExecutor; 889 } 890 891 @Override getCallback()892 SessionCallback getCallback() { 893 return mCallback; 894 } 895 896 @Override getSessionCompat()897 MediaSessionCompat getSessionCompat() { 898 return mSessionCompat; 899 } 900 901 @Override isClosed()902 boolean isClosed() { 903 return !mHandlerThread.isAlive(); 904 } 905 906 @Override getPlaybackStateCompat()907 PlaybackStateCompat getPlaybackStateCompat() { 908 synchronized (mLock) { 909 int state = MediaUtils2.createPlaybackStateCompatState(getPlayerState(), 910 getBufferingState()); 911 long allActions = PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_PAUSE 912 | PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_REWIND 913 | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS 914 | PlaybackStateCompat.ACTION_SKIP_TO_NEXT 915 | PlaybackStateCompat.ACTION_FAST_FORWARD 916 | PlaybackStateCompat.ACTION_SET_RATING 917 | PlaybackStateCompat.ACTION_SEEK_TO | PlaybackStateCompat.ACTION_PLAY_PAUSE 918 | PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID 919 | PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH 920 | PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM 921 | PlaybackStateCompat.ACTION_PLAY_FROM_URI | PlaybackStateCompat.ACTION_PREPARE 922 | PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID 923 | PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH 924 | PlaybackStateCompat.ACTION_PREPARE_FROM_URI 925 | PlaybackStateCompat.ACTION_SET_REPEAT_MODE 926 | PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE 927 | PlaybackStateCompat.ACTION_SET_CAPTIONING_ENABLED; 928 return new PlaybackStateCompat.Builder() 929 .setState(state, getCurrentPosition(), getPlaybackSpeed()) 930 .setActions(allActions) 931 .setBufferedPosition(getBufferedPosition()) 932 .build(); 933 } 934 } 935 936 @Override getPlaybackInfo()937 PlaybackInfo getPlaybackInfo() { 938 synchronized (mLock) { 939 return mPlaybackInfo; 940 } 941 } getServiceName(Context context, String serviceAction, String id)942 private static String getServiceName(Context context, String serviceAction, String id) { 943 PackageManager manager = context.getPackageManager(); 944 Intent serviceIntent = new Intent(serviceAction); 945 serviceIntent.setPackage(context.getPackageName()); 946 List<ResolveInfo> services = manager.queryIntentServices(serviceIntent, 947 PackageManager.GET_META_DATA); 948 String serviceName = null; 949 if (services != null) { 950 for (int i = 0; i < services.size(); i++) { 951 String serviceId = SessionToken2.getSessionId(services.get(i)); 952 if (serviceId != null && TextUtils.equals(id, serviceId)) { 953 if (services.get(i).serviceInfo == null) { 954 continue; 955 } 956 if (serviceName != null) { 957 throw new IllegalArgumentException("Ambiguous session type. Multiple" 958 + " session services define the same id=" + id); 959 } 960 serviceName = services.get(i).serviceInfo.name; 961 } 962 } 963 } 964 return serviceName; 965 } 966 notifyAgentUpdatedNotLocked(MediaPlaylistAgent oldAgent)967 private void notifyAgentUpdatedNotLocked(MediaPlaylistAgent oldAgent) { 968 // Tells the playlist change first, to current item can change be notified with an item 969 // within the playlist. 970 List<MediaItem2> oldPlaylist = oldAgent.getPlaylist(); 971 final List<MediaItem2> newPlaylist = getPlaylist(); 972 if (!ObjectsCompat.equals(oldPlaylist, newPlaylist)) { 973 notifyToAllControllers(new NotifyRunnable() { 974 @Override 975 public void run(ControllerCb callback) throws RemoteException { 976 callback.onPlaylistChanged( 977 newPlaylist, getPlaylistMetadata()); 978 } 979 }); 980 } else { 981 MediaMetadata2 oldMetadata = oldAgent.getPlaylistMetadata(); 982 final MediaMetadata2 newMetadata = getPlaylistMetadata(); 983 if (!ObjectsCompat.equals(oldMetadata, newMetadata)) { 984 notifyToAllControllers(new NotifyRunnable() { 985 @Override 986 public void run(ControllerCb callback) throws RemoteException { 987 callback.onPlaylistMetadataChanged(newMetadata); 988 } 989 }); 990 } 991 } 992 MediaItem2 oldCurrentItem = oldAgent.getCurrentMediaItem(); 993 final MediaItem2 newCurrentItem = getCurrentMediaItem(); 994 if (!ObjectsCompat.equals(oldCurrentItem, newCurrentItem)) { 995 notifyToAllControllers(new NotifyRunnable() { 996 @Override 997 public void run(ControllerCb callback) throws RemoteException { 998 callback.onCurrentMediaItemChanged(newCurrentItem); 999 } 1000 }); 1001 } 1002 final int repeatMode = getRepeatMode(); 1003 if (oldAgent.getRepeatMode() != repeatMode) { 1004 notifyToAllControllers(new NotifyRunnable() { 1005 @Override 1006 public void run(ControllerCb callback) throws RemoteException { 1007 callback.onRepeatModeChanged(repeatMode); 1008 } 1009 }); 1010 } 1011 final int shuffleMode = getShuffleMode(); 1012 if (oldAgent.getShuffleMode() != shuffleMode) { 1013 notifyToAllControllers(new NotifyRunnable() { 1014 @Override 1015 public void run(ControllerCb callback) throws RemoteException { 1016 callback.onShuffleModeChanged(shuffleMode); 1017 } 1018 }); 1019 } 1020 } 1021 notifyPlayerUpdatedNotLocked(MediaPlayerInterface oldPlayer)1022 private void notifyPlayerUpdatedNotLocked(MediaPlayerInterface oldPlayer) { 1023 // Always forcefully send the player state and buffered state to send the current position 1024 // and buffered position. 1025 final int playerState = getPlayerState(); 1026 notifyToAllControllers(new NotifyRunnable() { 1027 @Override 1028 public void run(ControllerCb callback) throws RemoteException { 1029 callback.onPlayerStateChanged(playerState); 1030 } 1031 }); 1032 final MediaItem2 item = getCurrentMediaItem(); 1033 if (item != null) { 1034 final int bufferingState = getBufferingState(); 1035 notifyToAllControllers(new NotifyRunnable() { 1036 @Override 1037 public void run(ControllerCb callback) throws RemoteException { 1038 callback.onBufferingStateChanged(item, bufferingState); 1039 } 1040 }); 1041 } 1042 final float speed = getPlaybackSpeed(); 1043 if (speed != oldPlayer.getPlaybackSpeed()) { 1044 notifyToAllControllers(new NotifyRunnable() { 1045 @Override 1046 public void run(ControllerCb callback) throws RemoteException { 1047 callback.onPlaybackSpeedChanged(speed); 1048 } 1049 }); 1050 } 1051 // Note: AudioInfo is updated outside of this API. 1052 } 1053 notifyPlaylistChangedOnExecutor(MediaPlaylistAgent playlistAgent, final List<MediaItem2> list, final MediaMetadata2 metadata)1054 private void notifyPlaylistChangedOnExecutor(MediaPlaylistAgent playlistAgent, 1055 final List<MediaItem2> list, final MediaMetadata2 metadata) { 1056 synchronized (mLock) { 1057 if (playlistAgent != mPlaylistAgent) { 1058 // Ignore calls from the old agent. 1059 return; 1060 } 1061 } 1062 mCallback.onPlaylistChanged(mInstance, playlistAgent, list, metadata); 1063 notifyToAllControllers(new NotifyRunnable() { 1064 @Override 1065 public void run(ControllerCb callback) throws RemoteException { 1066 callback.onPlaylistChanged(list, metadata); 1067 } 1068 }); 1069 } 1070 notifyPlaylistMetadataChangedOnExecutor(MediaPlaylistAgent playlistAgent, final MediaMetadata2 metadata)1071 private void notifyPlaylistMetadataChangedOnExecutor(MediaPlaylistAgent playlistAgent, 1072 final MediaMetadata2 metadata) { 1073 synchronized (mLock) { 1074 if (playlistAgent != mPlaylistAgent) { 1075 // Ignore calls from the old agent. 1076 return; 1077 } 1078 } 1079 mCallback.onPlaylistMetadataChanged(mInstance, playlistAgent, metadata); 1080 notifyToAllControllers(new NotifyRunnable() { 1081 @Override 1082 public void run(ControllerCb callback) throws RemoteException { 1083 callback.onPlaylistMetadataChanged(metadata); 1084 } 1085 }); 1086 } 1087 notifyRepeatModeChangedOnExecutor(MediaPlaylistAgent playlistAgent, final int repeatMode)1088 private void notifyRepeatModeChangedOnExecutor(MediaPlaylistAgent playlistAgent, 1089 final int repeatMode) { 1090 synchronized (mLock) { 1091 if (playlistAgent != mPlaylistAgent) { 1092 // Ignore calls from the old agent. 1093 return; 1094 } 1095 } 1096 mCallback.onRepeatModeChanged(mInstance, playlistAgent, repeatMode); 1097 notifyToAllControllers(new NotifyRunnable() { 1098 @Override 1099 public void run(ControllerCb callback) throws RemoteException { 1100 callback.onRepeatModeChanged(repeatMode); 1101 } 1102 }); 1103 } 1104 notifyShuffleModeChangedOnExecutor(MediaPlaylistAgent playlistAgent, final int shuffleMode)1105 private void notifyShuffleModeChangedOnExecutor(MediaPlaylistAgent playlistAgent, 1106 final int shuffleMode) { 1107 synchronized (mLock) { 1108 if (playlistAgent != mPlaylistAgent) { 1109 // Ignore calls from the old agent. 1110 return; 1111 } 1112 } 1113 mCallback.onShuffleModeChanged(mInstance, playlistAgent, shuffleMode); 1114 notifyToAllControllers(new NotifyRunnable() { 1115 @Override 1116 public void run(ControllerCb callback) throws RemoteException { 1117 callback.onShuffleModeChanged(shuffleMode); 1118 } 1119 }); 1120 } 1121 notifyToController(@onNull final ControllerInfo controller, @NonNull NotifyRunnable runnable)1122 private void notifyToController(@NonNull final ControllerInfo controller, 1123 @NonNull NotifyRunnable runnable) { 1124 if (controller == null) { 1125 return; 1126 } 1127 try { 1128 runnable.run(controller.getControllerCb()); 1129 } catch (DeadObjectException e) { 1130 if (DEBUG) { 1131 Log.d(TAG, controller.toString() + " is gone", e); 1132 } 1133 mSession2Stub.removeControllerInfo(controller); 1134 mCallbackExecutor.execute(new Runnable() { 1135 @Override 1136 public void run() { 1137 mCallback.onDisconnected(MediaSession2ImplBase.this.getInstance(), controller); 1138 } 1139 }); 1140 } catch (RemoteException e) { 1141 // Currently it's TransactionTooLargeException or DeadSystemException. 1142 // We'd better to leave log for those cases because 1143 // - TransactionTooLargeException means that we may need to fix our code. 1144 // (e.g. add pagination or special way to deliver Bitmap) 1145 // - DeadSystemException means that errors around it can be ignored. 1146 Log.w(TAG, "Exception in " + controller.toString(), e); 1147 } 1148 } 1149 notifyToAllControllers(@onNull NotifyRunnable runnable)1150 private void notifyToAllControllers(@NonNull NotifyRunnable runnable) { 1151 List<ControllerInfo> controllers = getConnectedControllers(); 1152 for (int i = 0; i < controllers.size(); i++) { 1153 notifyToController(controllers.get(i), runnable); 1154 } 1155 } 1156 1157 /////////////////////////////////////////////////// 1158 // Inner classes 1159 /////////////////////////////////////////////////// 1160 @FunctionalInterface 1161 private interface NotifyRunnable { run(ControllerCb callback)1162 void run(ControllerCb callback) throws RemoteException; 1163 } 1164 1165 private static class MyPlayerEventCallback extends PlayerEventCallback { 1166 private final WeakReference<MediaSession2ImplBase> mSession; 1167 MyPlayerEventCallback(MediaSession2ImplBase session)1168 private MyPlayerEventCallback(MediaSession2ImplBase session) { 1169 mSession = new WeakReference<>(session); 1170 } 1171 1172 @Override onCurrentDataSourceChanged(final MediaPlayerInterface player, final DataSourceDesc dsd)1173 public void onCurrentDataSourceChanged(final MediaPlayerInterface player, 1174 final DataSourceDesc dsd) { 1175 final MediaSession2ImplBase session = getSession(); 1176 if (session == null) { 1177 return; 1178 } 1179 session.getCallbackExecutor().execute(new Runnable() { 1180 @Override 1181 public void run() { 1182 final MediaItem2 item; 1183 if (dsd == null) { 1184 // This is OK because onCurrentDataSourceChanged() can be called with the 1185 // null dsd, so onCurrentMediaItemChanged() can be as well. 1186 item = null; 1187 } else { 1188 item = MyPlayerEventCallback.this.getMediaItem(session, dsd); 1189 if (item == null) { 1190 Log.w(TAG, "Cannot obtain media item from the dsd=" + dsd); 1191 return; 1192 } 1193 } 1194 session.getCallback().onCurrentMediaItemChanged(session.getInstance(), player, 1195 item); 1196 session.notifyToAllControllers(new NotifyRunnable() { 1197 @Override 1198 public void run(ControllerCb callback) throws RemoteException { 1199 callback.onCurrentMediaItemChanged(item); 1200 } 1201 }); 1202 } 1203 }); 1204 } 1205 1206 @Override onMediaPrepared(final MediaPlayerInterface mpb, final DataSourceDesc dsd)1207 public void onMediaPrepared(final MediaPlayerInterface mpb, final DataSourceDesc dsd) { 1208 final MediaSession2ImplBase session = getSession(); 1209 if (session == null || dsd == null) { 1210 return; 1211 } 1212 session.getCallbackExecutor().execute(new Runnable() { 1213 @Override 1214 public void run() { 1215 MediaItem2 item = MyPlayerEventCallback.this.getMediaItem(session, dsd); 1216 if (item == null) { 1217 return; 1218 } 1219 if (item.equals(session.getCurrentMediaItem())) { 1220 long duration = session.getDuration(); 1221 if (duration < 0) { 1222 return; 1223 } 1224 MediaMetadata2 metadata = item.getMetadata(); 1225 if (metadata != null) { 1226 if (!metadata.containsKey(MediaMetadata2.METADATA_KEY_DURATION)) { 1227 metadata = new MediaMetadata2.Builder(metadata).putLong( 1228 MediaMetadata2.METADATA_KEY_DURATION, duration).build(); 1229 } else { 1230 long durationFromMetadata = 1231 metadata.getLong(MediaMetadata2.METADATA_KEY_DURATION); 1232 if (duration != durationFromMetadata) { 1233 // Warns developers about the mismatch. Don't log media item 1234 // here to keep metadata secure. 1235 Log.w(TAG, "duration mismatch for an item." 1236 + " duration from player=" + duration 1237 + " duration from metadata=" + durationFromMetadata 1238 + ". May be a timing issue?"); 1239 } 1240 // Trust duration in the metadata set by developer. 1241 // In theory, duration may differ if the current item has been 1242 // changed before the getDuration(). So it's better not touch 1243 // duration set by developer. 1244 metadata = null; 1245 } 1246 } else { 1247 metadata = new MediaMetadata2.Builder() 1248 .putLong(MediaMetadata2.METADATA_KEY_DURATION, duration) 1249 .putString(MediaMetadata2.METADATA_KEY_MEDIA_ID, 1250 item.getMediaId()) 1251 .build(); 1252 } 1253 if (metadata != null) { 1254 item.setMetadata(metadata); 1255 session.notifyToAllControllers(new NotifyRunnable() { 1256 @Override 1257 public void run(ControllerCb callback) throws RemoteException { 1258 callback.onPlaylistChanged( 1259 session.getPlaylist(), session.getPlaylistMetadata()); 1260 } 1261 }); 1262 } 1263 } 1264 session.getCallback().onMediaPrepared(session.getInstance(), mpb, item); 1265 } 1266 }); 1267 } 1268 1269 @Override onPlayerStateChanged(final MediaPlayerInterface player, final int state)1270 public void onPlayerStateChanged(final MediaPlayerInterface player, final int state) { 1271 final MediaSession2ImplBase session = getSession(); 1272 if (session == null) { 1273 return; 1274 } 1275 session.getCallbackExecutor().execute(new Runnable() { 1276 @Override 1277 public void run() { 1278 session.getCallback().onPlayerStateChanged( 1279 session.getInstance(), player, state); 1280 session.notifyToAllControllers(new NotifyRunnable() { 1281 @Override 1282 public void run(ControllerCb callback) throws RemoteException { 1283 callback.onPlayerStateChanged(state); 1284 } 1285 }); 1286 } 1287 }); 1288 } 1289 1290 @Override onBufferingStateChanged(final MediaPlayerInterface mpb, final DataSourceDesc dsd, final int state)1291 public void onBufferingStateChanged(final MediaPlayerInterface mpb, 1292 final DataSourceDesc dsd, final int state) { 1293 final MediaSession2ImplBase session = getSession(); 1294 if (session == null || dsd == null) { 1295 return; 1296 } 1297 session.getCallbackExecutor().execute(new Runnable() { 1298 @Override 1299 public void run() { 1300 final MediaItem2 item = MyPlayerEventCallback.this.getMediaItem(session, dsd); 1301 if (item == null) { 1302 return; 1303 } 1304 session.getCallback().onBufferingStateChanged( 1305 session.getInstance(), mpb, item, state); 1306 session.notifyToAllControllers(new NotifyRunnable() { 1307 @Override 1308 public void run(ControllerCb callback) throws RemoteException { 1309 callback.onBufferingStateChanged(item, state); 1310 } 1311 }); 1312 } 1313 }); 1314 } 1315 1316 @Override onPlaybackSpeedChanged(final MediaPlayerInterface mpb, final float speed)1317 public void onPlaybackSpeedChanged(final MediaPlayerInterface mpb, final float speed) { 1318 final MediaSession2ImplBase session = getSession(); 1319 if (session == null) { 1320 return; 1321 } 1322 session.getCallbackExecutor().execute(new Runnable() { 1323 @Override 1324 public void run() { 1325 session.getCallback().onPlaybackSpeedChanged(session.getInstance(), mpb, speed); 1326 session.notifyToAllControllers(new NotifyRunnable() { 1327 @Override 1328 public void run(ControllerCb callback) throws RemoteException { 1329 callback.onPlaybackSpeedChanged(speed); 1330 } 1331 }); 1332 } 1333 }); 1334 } 1335 1336 @Override onSeekCompleted(final MediaPlayerInterface mpb, final long position)1337 public void onSeekCompleted(final MediaPlayerInterface mpb, final long position) { 1338 final MediaSession2ImplBase session = getSession(); 1339 if (session == null) { 1340 return; 1341 } 1342 session.getCallbackExecutor().execute(new Runnable() { 1343 @Override 1344 public void run() { 1345 session.getCallback().onSeekCompleted(session.getInstance(), mpb, position); 1346 session.notifyToAllControllers(new NotifyRunnable() { 1347 @Override 1348 public void run(ControllerCb callback) throws RemoteException { 1349 callback.onSeekCompleted(position); 1350 } 1351 }); 1352 } 1353 }); 1354 } 1355 getSession()1356 private MediaSession2ImplBase getSession() { 1357 final MediaSession2ImplBase session = mSession.get(); 1358 if (session == null && DEBUG) { 1359 Log.d(TAG, "Session is closed", new IllegalStateException()); 1360 } 1361 return session; 1362 } 1363 getMediaItem(MediaSession2ImplBase session, DataSourceDesc dsd)1364 private MediaItem2 getMediaItem(MediaSession2ImplBase session, DataSourceDesc dsd) { 1365 MediaPlaylistAgent agent = session.getPlaylistAgent(); 1366 if (agent == null) { 1367 if (DEBUG) { 1368 Log.d(TAG, "Session is closed", new IllegalStateException()); 1369 } 1370 return null; 1371 } 1372 MediaItem2 item = agent.getMediaItem(dsd); 1373 if (item == null) { 1374 if (DEBUG) { 1375 Log.d(TAG, "Could not find matching item for dsd=" + dsd, 1376 new NoSuchElementException()); 1377 } 1378 } 1379 return item; 1380 } 1381 } 1382 1383 private static class MyPlaylistEventCallback extends PlaylistEventCallback { 1384 private final WeakReference<MediaSession2ImplBase> mSession; 1385 MyPlaylistEventCallback(MediaSession2ImplBase session)1386 private MyPlaylistEventCallback(MediaSession2ImplBase session) { 1387 mSession = new WeakReference<>(session); 1388 } 1389 1390 @Override onPlaylistChanged(MediaPlaylistAgent playlistAgent, List<MediaItem2> list, MediaMetadata2 metadata)1391 public void onPlaylistChanged(MediaPlaylistAgent playlistAgent, List<MediaItem2> list, 1392 MediaMetadata2 metadata) { 1393 final MediaSession2ImplBase session = mSession.get(); 1394 if (session == null) { 1395 return; 1396 } 1397 session.notifyPlaylistChangedOnExecutor(playlistAgent, list, metadata); 1398 } 1399 1400 @Override onPlaylistMetadataChanged(MediaPlaylistAgent playlistAgent, MediaMetadata2 metadata)1401 public void onPlaylistMetadataChanged(MediaPlaylistAgent playlistAgent, 1402 MediaMetadata2 metadata) { 1403 final MediaSession2ImplBase session = mSession.get(); 1404 if (session == null) { 1405 return; 1406 } 1407 session.notifyPlaylistMetadataChangedOnExecutor(playlistAgent, metadata); 1408 } 1409 1410 @Override onRepeatModeChanged(MediaPlaylistAgent playlistAgent, int repeatMode)1411 public void onRepeatModeChanged(MediaPlaylistAgent playlistAgent, int repeatMode) { 1412 final MediaSession2ImplBase session = mSession.get(); 1413 if (session == null) { 1414 return; 1415 } 1416 session.notifyRepeatModeChangedOnExecutor(playlistAgent, repeatMode); 1417 } 1418 1419 @Override onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode)1420 public void onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode) { 1421 final MediaSession2ImplBase session = mSession.get(); 1422 if (session == null) { 1423 return; 1424 } 1425 session.notifyShuffleModeChangedOnExecutor(playlistAgent, shuffleMode); 1426 } 1427 } 1428 1429 abstract static class BuilderBase 1430 <T extends MediaSession2, C extends SessionCallback> { 1431 final Context mContext; 1432 MediaPlayerInterface mPlayer; 1433 String mId; 1434 Executor mCallbackExecutor; 1435 C mCallback; 1436 MediaPlaylistAgent mPlaylistAgent; 1437 VolumeProviderCompat mVolumeProvider; 1438 PendingIntent mSessionActivity; 1439 BuilderBase(Context context)1440 BuilderBase(Context context) { 1441 if (context == null) { 1442 throw new IllegalArgumentException("context shouldn't be null"); 1443 } 1444 mContext = context; 1445 // Ensure MediaSessionCompat non-null or empty 1446 mId = TAG; 1447 } 1448 setPlayer(@onNull MediaPlayerInterface player)1449 void setPlayer(@NonNull MediaPlayerInterface player) { 1450 if (player == null) { 1451 throw new IllegalArgumentException("player shouldn't be null"); 1452 } 1453 mPlayer = player; 1454 } 1455 setPlaylistAgent(@onNull MediaPlaylistAgent playlistAgent)1456 void setPlaylistAgent(@NonNull MediaPlaylistAgent playlistAgent) { 1457 if (playlistAgent == null) { 1458 throw new IllegalArgumentException("playlistAgent shouldn't be null"); 1459 } 1460 mPlaylistAgent = playlistAgent; 1461 } 1462 setVolumeProvider(@ullable VolumeProviderCompat volumeProvider)1463 void setVolumeProvider(@Nullable VolumeProviderCompat volumeProvider) { 1464 mVolumeProvider = volumeProvider; 1465 } 1466 setSessionActivity(@ullable PendingIntent pi)1467 void setSessionActivity(@Nullable PendingIntent pi) { 1468 mSessionActivity = pi; 1469 } 1470 setId(@onNull String id)1471 void setId(@NonNull String id) { 1472 if (id == null) { 1473 throw new IllegalArgumentException("id shouldn't be null"); 1474 } 1475 mId = id; 1476 } 1477 setSessionCallback(@onNull Executor executor, @NonNull C callback)1478 void setSessionCallback(@NonNull Executor executor, @NonNull C callback) { 1479 if (executor == null) { 1480 throw new IllegalArgumentException("executor shouldn't be null"); 1481 } 1482 if (callback == null) { 1483 throw new IllegalArgumentException("callback shouldn't be null"); 1484 } 1485 mCallbackExecutor = executor; 1486 mCallback = callback; 1487 } 1488 build()1489 abstract @NonNull T build(); 1490 } 1491 1492 static final class Builder extends 1493 BuilderBase<MediaSession2, MediaSession2.SessionCallback> { Builder(Context context)1494 Builder(Context context) { 1495 super(context); 1496 } 1497 1498 @Override build()1499 public @NonNull MediaSession2 build() { 1500 if (mCallbackExecutor == null) { 1501 mCallbackExecutor = new MainHandlerExecutor(mContext); 1502 } 1503 if (mCallback == null) { 1504 mCallback = new SessionCallback() {}; 1505 } 1506 return new MediaSession2(new MediaSession2ImplBase(mContext, 1507 new MediaSessionCompat(mContext, mId), mId, mPlayer, mPlaylistAgent, 1508 mVolumeProvider, mSessionActivity, mCallbackExecutor, mCallback)); 1509 } 1510 } 1511 1512 static class MainHandlerExecutor implements Executor { 1513 private final Handler mHandler; 1514 MainHandlerExecutor(Context context)1515 MainHandlerExecutor(Context context) { 1516 mHandler = new Handler(context.getMainLooper()); 1517 } 1518 1519 @Override execute(Runnable command)1520 public void execute(Runnable command) { 1521 if (!mHandler.post(command)) { 1522 throw new RejectedExecutionException(mHandler + " is shutting down"); 1523 } 1524 } 1525 } 1526 } 1527