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 android.support.v4.media.MediaMetadataCompat.METADATA_KEY_DURATION; 20 21 import static androidx.media.MediaConstants2.ARGUMENT_ALLOWED_COMMANDS; 22 import static androidx.media.MediaConstants2.ARGUMENT_ARGUMENTS; 23 import static androidx.media.MediaConstants2.ARGUMENT_BUFFERING_STATE; 24 import static androidx.media.MediaConstants2.ARGUMENT_COMMAND_BUTTONS; 25 import static androidx.media.MediaConstants2.ARGUMENT_COMMAND_CODE; 26 import static androidx.media.MediaConstants2.ARGUMENT_CUSTOM_COMMAND; 27 import static androidx.media.MediaConstants2.ARGUMENT_ERROR_CODE; 28 import static androidx.media.MediaConstants2.ARGUMENT_EXTRAS; 29 import static androidx.media.MediaConstants2.ARGUMENT_ICONTROLLER_CALLBACK; 30 import static androidx.media.MediaConstants2.ARGUMENT_ITEM_COUNT; 31 import static androidx.media.MediaConstants2.ARGUMENT_MEDIA_ID; 32 import static androidx.media.MediaConstants2.ARGUMENT_MEDIA_ITEM; 33 import static androidx.media.MediaConstants2.ARGUMENT_PACKAGE_NAME; 34 import static androidx.media.MediaConstants2.ARGUMENT_PID; 35 import static androidx.media.MediaConstants2.ARGUMENT_PLAYBACK_INFO; 36 import static androidx.media.MediaConstants2.ARGUMENT_PLAYBACK_SPEED; 37 import static androidx.media.MediaConstants2.ARGUMENT_PLAYBACK_STATE_COMPAT; 38 import static androidx.media.MediaConstants2.ARGUMENT_PLAYER_STATE; 39 import static androidx.media.MediaConstants2.ARGUMENT_PLAYLIST; 40 import static androidx.media.MediaConstants2.ARGUMENT_PLAYLIST_INDEX; 41 import static androidx.media.MediaConstants2.ARGUMENT_PLAYLIST_METADATA; 42 import static androidx.media.MediaConstants2.ARGUMENT_QUERY; 43 import static androidx.media.MediaConstants2.ARGUMENT_RATING; 44 import static androidx.media.MediaConstants2.ARGUMENT_REPEAT_MODE; 45 import static androidx.media.MediaConstants2.ARGUMENT_RESULT_RECEIVER; 46 import static androidx.media.MediaConstants2.ARGUMENT_ROUTE_BUNDLE; 47 import static androidx.media.MediaConstants2.ARGUMENT_SEEK_POSITION; 48 import static androidx.media.MediaConstants2.ARGUMENT_SHUFFLE_MODE; 49 import static androidx.media.MediaConstants2.ARGUMENT_UID; 50 import static androidx.media.MediaConstants2.ARGUMENT_URI; 51 import static androidx.media.MediaConstants2.ARGUMENT_VOLUME; 52 import static androidx.media.MediaConstants2.ARGUMENT_VOLUME_DIRECTION; 53 import static androidx.media.MediaConstants2.ARGUMENT_VOLUME_FLAGS; 54 import static androidx.media.MediaConstants2.CONNECT_RESULT_CONNECTED; 55 import static androidx.media.MediaConstants2.CONNECT_RESULT_DISCONNECTED; 56 import static androidx.media.MediaConstants2.CONTROLLER_COMMAND_BY_COMMAND_CODE; 57 import static androidx.media.MediaConstants2.CONTROLLER_COMMAND_BY_CUSTOM_COMMAND; 58 import static androidx.media.MediaConstants2.CONTROLLER_COMMAND_CONNECT; 59 import static androidx.media.MediaConstants2.CONTROLLER_COMMAND_DISCONNECT; 60 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_ALLOWED_COMMANDS_CHANGED; 61 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_BUFFERING_STATE_CHANGED; 62 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_CHILDREN_CHANGED; 63 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_CURRENT_MEDIA_ITEM_CHANGED; 64 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_ERROR; 65 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYBACK_INFO_CHANGED; 66 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYBACK_SPEED_CHANGED; 67 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYER_STATE_CHANGED; 68 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYLIST_CHANGED; 69 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYLIST_METADATA_CHANGED; 70 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_REPEAT_MODE_CHANGED; 71 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_ROUTES_INFO_CHANGED; 72 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_SEARCH_RESULT_CHANGED; 73 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_SEEK_COMPLETED; 74 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_SHUFFLE_MODE_CHANGED; 75 import static androidx.media.MediaConstants2.SESSION_EVENT_SEND_CUSTOM_COMMAND; 76 import static androidx.media.MediaConstants2.SESSION_EVENT_SET_CUSTOM_LAYOUT; 77 import static androidx.media.MediaPlayerInterface.BUFFERING_STATE_UNKNOWN; 78 import static androidx.media.MediaPlayerInterface.UNKNOWN_TIME; 79 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_PAUSE; 80 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_PLAY; 81 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_PREPARE; 82 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_RESET; 83 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_SEEK_TO; 84 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_SET_SPEED; 85 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_ADD_ITEM; 86 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM; 87 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM; 88 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SET_LIST; 89 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA; 90 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SET_REPEAT_MODE; 91 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SET_SHUFFLE_MODE; 92 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SKIP_TO_NEXT_ITEM; 93 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM; 94 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SKIP_TO_PREV_ITEM; 95 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_FAST_FORWARD; 96 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID; 97 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_SEARCH; 98 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_URI; 99 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID; 100 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH; 101 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_URI; 102 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_REWIND; 103 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_SELECT_ROUTE; 104 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_SET_RATING; 105 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_SUBSCRIBE_ROUTES_INFO; 106 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_UNSUBSCRIBE_ROUTES_INFO; 107 import static androidx.media.SessionCommand2.COMMAND_CODE_VOLUME_ADJUST_VOLUME; 108 import static androidx.media.SessionCommand2.COMMAND_CODE_VOLUME_SET_VOLUME; 109 110 import android.annotation.TargetApi; 111 import android.app.PendingIntent; 112 import android.content.Context; 113 import android.net.Uri; 114 import android.os.Build; 115 import android.os.Bundle; 116 import android.os.Handler; 117 import android.os.HandlerThread; 118 import android.os.IBinder; 119 import android.os.Process; 120 import android.os.RemoteException; 121 import android.os.ResultReceiver; 122 import android.os.SystemClock; 123 import android.support.v4.media.MediaBrowserCompat; 124 import android.support.v4.media.MediaMetadataCompat; 125 import android.support.v4.media.session.MediaControllerCompat; 126 import android.support.v4.media.session.MediaSessionCompat; 127 import android.support.v4.media.session.PlaybackStateCompat; 128 import android.util.Log; 129 130 import androidx.annotation.GuardedBy; 131 import androidx.annotation.NonNull; 132 import androidx.annotation.Nullable; 133 import androidx.core.app.BundleCompat; 134 import androidx.media.MediaController2.ControllerCallback; 135 import androidx.media.MediaController2.PlaybackInfo; 136 import androidx.media.MediaController2.VolumeDirection; 137 import androidx.media.MediaController2.VolumeFlags; 138 import androidx.media.MediaPlaylistAgent.RepeatMode; 139 import androidx.media.MediaPlaylistAgent.ShuffleMode; 140 import androidx.media.MediaSession2.CommandButton; 141 142 import java.util.List; 143 import java.util.concurrent.Executor; 144 145 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 146 class MediaController2ImplBase implements MediaController2.SupportLibraryImpl { 147 148 private static final String TAG = "MC2ImplBase"; 149 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 150 151 // Note: Using {@code null} doesn't helpful here because MediaBrowserServiceCompat always wraps 152 // the rootHints so it becomes non-null. 153 static final Bundle sDefaultRootExtras = new Bundle(); 154 static { 155 sDefaultRootExtras.putBoolean(MediaConstants2.ROOT_EXTRA_DEFAULT, true); 156 } 157 158 private final Context mContext; 159 private final Object mLock = new Object(); 160 161 private final SessionToken2 mToken; 162 private final ControllerCallback mCallback; 163 private final Executor mCallbackExecutor; 164 private final IBinder.DeathRecipient mDeathRecipient; 165 166 private final HandlerThread mHandlerThread; 167 private final Handler mHandler; 168 169 private MediaController2 mInstance; 170 171 @GuardedBy("mLock") 172 private MediaBrowserCompat mBrowserCompat; 173 @GuardedBy("mLock") 174 private boolean mIsReleased; 175 @GuardedBy("mLock") 176 private List<MediaItem2> mPlaylist; 177 @GuardedBy("mLock") 178 private MediaMetadata2 mPlaylistMetadata; 179 @GuardedBy("mLock") 180 private @RepeatMode int mRepeatMode; 181 @GuardedBy("mLock") 182 private @ShuffleMode int mShuffleMode; 183 @GuardedBy("mLock") 184 private int mPlayerState; 185 @GuardedBy("mLock") 186 private MediaItem2 mCurrentMediaItem; 187 @GuardedBy("mLock") 188 private int mBufferingState; 189 @GuardedBy("mLock") 190 private PlaybackInfo mPlaybackInfo; 191 @GuardedBy("mLock") 192 private SessionCommandGroup2 mAllowedCommands; 193 194 // Media 1.0 variables 195 @GuardedBy("mLock") 196 private MediaControllerCompat mControllerCompat; 197 @GuardedBy("mLock") 198 private ControllerCompatCallback mControllerCompatCallback; 199 @GuardedBy("mLock") 200 private PlaybackStateCompat mPlaybackStateCompat; 201 @GuardedBy("mLock") 202 private MediaMetadataCompat mMediaMetadataCompat; 203 204 // Assignment should be used with the lock hold, but should be used without a lock to prevent 205 // potential deadlock. 206 @GuardedBy("mLock") 207 private volatile boolean mConnected; 208 209 MediaController2ImplBase(@NonNull Context context, @NonNull SessionToken2 token, 210 @NonNull Executor executor, @NonNull ControllerCallback callback) { 211 super(); 212 if (context == null) { 213 throw new IllegalArgumentException("context shouldn't be null"); 214 } 215 if (token == null) { 216 throw new IllegalArgumentException("token shouldn't be null"); 217 } 218 if (callback == null) { 219 throw new IllegalArgumentException("callback shouldn't be null"); 220 } 221 if (executor == null) { 222 throw new IllegalArgumentException("executor shouldn't be null"); 223 } 224 mContext = context; 225 mHandlerThread = new HandlerThread("MediaController2_Thread"); 226 mHandlerThread.start(); 227 mHandler = new Handler(mHandlerThread.getLooper()); 228 mToken = token; 229 mCallback = callback; 230 mCallbackExecutor = executor; 231 mDeathRecipient = new IBinder.DeathRecipient() { 232 @Override 233 public void binderDied() { 234 MediaController2ImplBase.this.close(); 235 } 236 }; 237 238 initialize(); 239 } 240 241 @Override 242 public void setInstance(MediaController2 controller) { 243 mInstance = controller; 244 } 245 246 @Override 247 public void close() { 248 if (DEBUG) { 249 //Log.d(TAG, "release from " + mToken, new IllegalStateException()); 250 } 251 synchronized (mLock) { 252 if (mIsReleased) { 253 // Prevent re-enterance from the ControllerCallback.onDisconnected() 254 return; 255 } 256 mHandler.removeCallbacksAndMessages(null); 257 258 if (Build.VERSION.SDK_INT >= 18) { 259 mHandlerThread.quitSafely(); 260 } else { 261 mHandlerThread.quit(); 262 } 263 264 mIsReleased = true; 265 266 // Send command before the unregister callback to use mIControllerCallback in the 267 // callback. 268 sendCommand(CONTROLLER_COMMAND_DISCONNECT); 269 if (mControllerCompat != null) { 270 mControllerCompat.unregisterCallback(mControllerCompatCallback); 271 } 272 if (mBrowserCompat != null) { 273 mBrowserCompat.disconnect(); 274 mBrowserCompat = null; 275 } 276 if (mControllerCompat != null) { 277 mControllerCompat.unregisterCallback(mControllerCompatCallback); 278 mControllerCompat = null; 279 } 280 mConnected = false; 281 } 282 mCallbackExecutor.execute(new Runnable() { 283 @Override 284 public void run() { 285 mCallback.onDisconnected(mInstance); 286 } 287 }); 288 } 289 290 @Override 291 public @NonNull SessionToken2 getSessionToken() { 292 return mToken; 293 } 294 295 @Override 296 public boolean isConnected() { 297 synchronized (mLock) { 298 return mConnected; 299 } 300 } 301 302 @Override 303 public void play() { 304 synchronized (mLock) { 305 if (!mConnected) { 306 Log.w(TAG, "Session isn't active", new IllegalStateException()); 307 return; 308 } 309 sendCommand(COMMAND_CODE_PLAYBACK_PLAY); 310 } 311 } 312 313 @Override 314 public void pause() { 315 synchronized (mLock) { 316 if (!mConnected) { 317 Log.w(TAG, "Session isn't active", new IllegalStateException()); 318 return; 319 } 320 sendCommand(COMMAND_CODE_PLAYBACK_PAUSE); 321 } 322 } 323 324 @Override 325 public void reset() { 326 synchronized (mLock) { 327 if (!mConnected) { 328 Log.w(TAG, "Session isn't active", new IllegalStateException()); 329 return; 330 } 331 sendCommand(COMMAND_CODE_PLAYBACK_RESET); 332 } 333 } 334 335 @Override 336 public void prepare() { 337 synchronized (mLock) { 338 if (!mConnected) { 339 Log.w(TAG, "Session isn't active", new IllegalStateException()); 340 return; 341 } 342 sendCommand(COMMAND_CODE_PLAYBACK_PREPARE); 343 } 344 } 345 346 @Override 347 public void fastForward() { 348 synchronized (mLock) { 349 if (!mConnected) { 350 Log.w(TAG, "Session isn't active", new IllegalStateException()); 351 return; 352 } 353 sendCommand(COMMAND_CODE_SESSION_FAST_FORWARD); 354 } 355 } 356 357 @Override 358 public void rewind() { 359 synchronized (mLock) { 360 if (!mConnected) { 361 Log.w(TAG, "Session isn't active", new IllegalStateException()); 362 return; 363 } 364 sendCommand(COMMAND_CODE_SESSION_REWIND); 365 } 366 } 367 368 @Override 369 public void seekTo(long pos) { 370 synchronized (mLock) { 371 if (!mConnected) { 372 Log.w(TAG, "Session isn't active", new IllegalStateException()); 373 return; 374 } 375 Bundle args = new Bundle(); 376 args.putLong(ARGUMENT_SEEK_POSITION, pos); 377 sendCommand(COMMAND_CODE_PLAYBACK_SEEK_TO, args); 378 } 379 } 380 381 @Override 382 public void skipForward() { 383 // To match with KEYCODE_MEDIA_SKIP_FORWARD 384 } 385 386 @Override 387 public void skipBackward() { 388 // To match with KEYCODE_MEDIA_SKIP_BACKWARD 389 } 390 391 @Override 392 public void playFromMediaId(@NonNull String mediaId, @Nullable Bundle extras) { 393 synchronized (mLock) { 394 if (!mConnected) { 395 Log.w(TAG, "Session isn't active", new IllegalStateException()); 396 return; 397 } 398 Bundle args = new Bundle(); 399 args.putString(ARGUMENT_MEDIA_ID, mediaId); 400 args.putBundle(ARGUMENT_EXTRAS, extras); 401 sendCommand(COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID, args); 402 } 403 } 404 405 @Override 406 public void playFromSearch(@NonNull String query, @Nullable Bundle extras) { 407 synchronized (mLock) { 408 if (!mConnected) { 409 Log.w(TAG, "Session isn't active", new IllegalStateException()); 410 return; 411 } 412 Bundle args = new Bundle(); 413 args.putString(ARGUMENT_QUERY, query); 414 args.putBundle(ARGUMENT_EXTRAS, extras); 415 sendCommand(COMMAND_CODE_SESSION_PLAY_FROM_SEARCH, args); 416 } 417 } 418 419 @Override 420 public void playFromUri(@NonNull Uri uri, @Nullable Bundle extras) { 421 synchronized (mLock) { 422 if (!mConnected) { 423 Log.w(TAG, "Session isn't active", new IllegalStateException()); 424 return; 425 } 426 Bundle args = new Bundle(); 427 args.putParcelable(ARGUMENT_URI, uri); 428 args.putBundle(ARGUMENT_EXTRAS, extras); 429 sendCommand(COMMAND_CODE_SESSION_PLAY_FROM_URI, args); 430 } 431 } 432 433 @Override 434 public void prepareFromMediaId(@NonNull String mediaId, @Nullable Bundle extras) { 435 synchronized (mLock) { 436 if (!mConnected) { 437 Log.w(TAG, "Session isn't active", new IllegalStateException()); 438 return; 439 } 440 Bundle args = new Bundle(); 441 args.putString(ARGUMENT_MEDIA_ID, mediaId); 442 args.putBundle(ARGUMENT_EXTRAS, extras); 443 sendCommand(COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID, args); 444 } 445 } 446 447 @Override 448 public void prepareFromSearch(@NonNull String query, @Nullable Bundle extras) { 449 synchronized (mLock) { 450 if (!mConnected) { 451 Log.w(TAG, "Session isn't active", new IllegalStateException()); 452 return; 453 } 454 Bundle args = new Bundle(); 455 args.putString(ARGUMENT_QUERY, query); 456 args.putBundle(ARGUMENT_EXTRAS, extras); 457 sendCommand(COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH, args); 458 } 459 } 460 461 @Override 462 public void prepareFromUri(@NonNull Uri uri, @Nullable Bundle extras) { 463 synchronized (mLock) { 464 if (!mConnected) { 465 Log.w(TAG, "Session isn't active", new IllegalStateException()); 466 return; 467 } 468 Bundle args = new Bundle(); 469 args.putParcelable(ARGUMENT_URI, uri); 470 args.putBundle(ARGUMENT_EXTRAS, extras); 471 sendCommand(COMMAND_CODE_SESSION_PREPARE_FROM_URI, args); 472 } 473 } 474 475 @Override 476 public void setVolumeTo(int value, @VolumeFlags int flags) { 477 synchronized (mLock) { 478 if (!mConnected) { 479 Log.w(TAG, "Session isn't active", new IllegalStateException()); 480 return; 481 } 482 Bundle args = new Bundle(); 483 args.putInt(ARGUMENT_VOLUME, value); 484 args.putInt(ARGUMENT_VOLUME_FLAGS, flags); 485 sendCommand(COMMAND_CODE_VOLUME_SET_VOLUME, args); 486 } 487 } 488 489 @Override 490 public void adjustVolume(@VolumeDirection int direction, @VolumeFlags int flags) { 491 synchronized (mLock) { 492 if (!mConnected) { 493 Log.w(TAG, "Session isn't active", new IllegalStateException()); 494 return; 495 } 496 Bundle args = new Bundle(); 497 args.putInt(ARGUMENT_VOLUME_DIRECTION, direction); 498 args.putInt(ARGUMENT_VOLUME_FLAGS, flags); 499 sendCommand(COMMAND_CODE_VOLUME_ADJUST_VOLUME, args); 500 } 501 } 502 503 @Override 504 public @Nullable PendingIntent getSessionActivity() { 505 synchronized (mLock) { 506 if (!mConnected) { 507 Log.w(TAG, "Session isn't active", new IllegalStateException()); 508 return null; 509 } 510 return mControllerCompat.getSessionActivity(); 511 } 512 } 513 514 @Override 515 public int getPlayerState() { 516 synchronized (mLock) { 517 return mPlayerState; 518 } 519 } 520 521 @Override 522 public long getDuration() { 523 synchronized (mLock) { 524 if (mMediaMetadataCompat != null 525 && mMediaMetadataCompat.containsKey(METADATA_KEY_DURATION)) { 526 return mMediaMetadataCompat.getLong(METADATA_KEY_DURATION); 527 } 528 } 529 return MediaPlayerInterface.UNKNOWN_TIME; 530 } 531 532 @Override 533 public long getCurrentPosition() { 534 synchronized (mLock) { 535 if (!mConnected) { 536 Log.w(TAG, "Session isn't active", new IllegalStateException()); 537 return UNKNOWN_TIME; 538 } 539 if (mPlaybackStateCompat != null) { 540 long timeDiff = (mInstance.mTimeDiff != null) ? mInstance.mTimeDiff 541 : SystemClock.elapsedRealtime() 542 - mPlaybackStateCompat.getLastPositionUpdateTime(); 543 long expectedPosition = mPlaybackStateCompat.getPosition() 544 + (long) (mPlaybackStateCompat.getPlaybackSpeed() * timeDiff); 545 return Math.max(0, expectedPosition); 546 } 547 return UNKNOWN_TIME; 548 } 549 } 550 551 @Override 552 public float getPlaybackSpeed() { 553 synchronized (mLock) { 554 if (!mConnected) { 555 Log.w(TAG, "Session isn't active", new IllegalStateException()); 556 return 0f; 557 } 558 return (mPlaybackStateCompat == null) ? 0f : mPlaybackStateCompat.getPlaybackSpeed(); 559 } 560 } 561 562 @Override 563 public void setPlaybackSpeed(float speed) { 564 synchronized (mLock) { 565 if (!mConnected) { 566 Log.w(TAG, "Session isn't active", new IllegalStateException()); 567 return; 568 } 569 Bundle args = new Bundle(); 570 args.putFloat(ARGUMENT_PLAYBACK_SPEED, speed); 571 sendCommand(COMMAND_CODE_PLAYBACK_SET_SPEED, args); 572 } 573 } 574 575 @Override 576 public @MediaPlayerInterface.BuffState int getBufferingState() { 577 synchronized (mLock) { 578 if (!mConnected) { 579 Log.w(TAG, "Session isn't active", new IllegalStateException()); 580 return BUFFERING_STATE_UNKNOWN; 581 } 582 return mBufferingState; 583 } 584 } 585 586 @Override 587 public long getBufferedPosition() { 588 synchronized (mLock) { 589 if (!mConnected) { 590 Log.w(TAG, "Session isn't active", new IllegalStateException()); 591 return UNKNOWN_TIME; 592 } 593 return (mPlaybackStateCompat == null) ? UNKNOWN_TIME 594 : mPlaybackStateCompat.getBufferedPosition(); 595 } 596 } 597 598 @Override 599 public @Nullable PlaybackInfo getPlaybackInfo() { 600 synchronized (mLock) { 601 return mPlaybackInfo; 602 } 603 } 604 605 @Override 606 public void setRating(@NonNull String mediaId, @NonNull Rating2 rating) { 607 synchronized (mLock) { 608 if (!mConnected) { 609 Log.w(TAG, "Session isn't active", new IllegalStateException()); 610 return; 611 } 612 Bundle args = new Bundle(); 613 args.putString(ARGUMENT_MEDIA_ID, mediaId); 614 args.putBundle(ARGUMENT_RATING, rating.toBundle()); 615 sendCommand(COMMAND_CODE_SESSION_SET_RATING, args); 616 } 617 } 618 619 @Override 620 public void sendCustomCommand(@NonNull SessionCommand2 command, @Nullable Bundle args, 621 @Nullable ResultReceiver cb) { 622 synchronized (mLock) { 623 if (!mConnected) { 624 Log.w(TAG, "Session isn't active", new IllegalStateException()); 625 return; 626 } 627 Bundle bundle = new Bundle(); 628 bundle.putBundle(ARGUMENT_CUSTOM_COMMAND, command.toBundle()); 629 bundle.putBundle(ARGUMENT_ARGUMENTS, args); 630 sendCommand(CONTROLLER_COMMAND_BY_CUSTOM_COMMAND, bundle, cb); 631 } 632 } 633 634 @Override 635 public @Nullable List<MediaItem2> getPlaylist() { 636 synchronized (mLock) { 637 return mPlaylist; 638 } 639 } 640 641 @Override 642 public void setPlaylist(@NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) { 643 if (list == null) { 644 throw new IllegalArgumentException("list shouldn't be null"); 645 } 646 Bundle args = new Bundle(); 647 args.putParcelableArray(ARGUMENT_PLAYLIST, MediaUtils2.toMediaItem2ParcelableArray(list)); 648 args.putBundle(ARGUMENT_PLAYLIST_METADATA, metadata == null ? null : metadata.toBundle()); 649 sendCommand(COMMAND_CODE_PLAYLIST_SET_LIST, args); 650 } 651 652 @Override 653 public void updatePlaylistMetadata(@Nullable MediaMetadata2 metadata) { 654 Bundle args = new Bundle(); 655 args.putBundle(ARGUMENT_PLAYLIST_METADATA, metadata == null ? null : metadata.toBundle()); 656 sendCommand(COMMAND_CODE_PLAYLIST_SET_LIST_METADATA, args); 657 } 658 659 @Override 660 public @Nullable MediaMetadata2 getPlaylistMetadata() { 661 synchronized (mLock) { 662 return mPlaylistMetadata; 663 } 664 } 665 666 @Override 667 public void addPlaylistItem(int index, @NonNull MediaItem2 item) { 668 Bundle args = new Bundle(); 669 args.putInt(ARGUMENT_PLAYLIST_INDEX, index); 670 args.putBundle(ARGUMENT_MEDIA_ITEM, item.toBundle()); 671 sendCommand(COMMAND_CODE_PLAYLIST_ADD_ITEM, args); 672 } 673 674 @Override 675 public void removePlaylistItem(@NonNull MediaItem2 item) { 676 Bundle args = new Bundle(); 677 args.putBundle(ARGUMENT_MEDIA_ITEM, item.toBundle()); 678 sendCommand(COMMAND_CODE_PLAYLIST_REMOVE_ITEM, args); 679 } 680 681 @Override 682 public void replacePlaylistItem(int index, @NonNull MediaItem2 item) { 683 Bundle args = new Bundle(); 684 args.putInt(ARGUMENT_PLAYLIST_INDEX, index); 685 args.putBundle(ARGUMENT_MEDIA_ITEM, item.toBundle()); 686 sendCommand(COMMAND_CODE_PLAYLIST_REPLACE_ITEM, args); 687 } 688 689 @Override 690 public MediaItem2 getCurrentMediaItem() { 691 synchronized (mLock) { 692 return mCurrentMediaItem; 693 } 694 } 695 696 @Override 697 public void skipToPreviousItem() { 698 sendCommand(COMMAND_CODE_PLAYLIST_SKIP_TO_PREV_ITEM); 699 } 700 701 @Override 702 public void skipToNextItem() { 703 sendCommand(COMMAND_CODE_PLAYLIST_SKIP_TO_NEXT_ITEM); 704 } 705 706 @Override 707 public void skipToPlaylistItem(@NonNull MediaItem2 item) { 708 Bundle args = new Bundle(); 709 args.putBundle(ARGUMENT_MEDIA_ITEM, item.toBundle()); 710 sendCommand(COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM, args); 711 } 712 713 @Override 714 public @RepeatMode int getRepeatMode() { 715 synchronized (mLock) { 716 return mRepeatMode; 717 } 718 } 719 720 @Override 721 public void setRepeatMode(@RepeatMode int repeatMode) { 722 Bundle args = new Bundle(); 723 args.putInt(ARGUMENT_REPEAT_MODE, repeatMode); 724 sendCommand(COMMAND_CODE_PLAYLIST_SET_REPEAT_MODE, args); 725 } 726 727 @Override 728 public @ShuffleMode int getShuffleMode() { 729 synchronized (mLock) { 730 return mShuffleMode; 731 } 732 } 733 734 @Override 735 public void setShuffleMode(@ShuffleMode int shuffleMode) { 736 Bundle args = new Bundle(); 737 args.putInt(ARGUMENT_SHUFFLE_MODE, shuffleMode); 738 sendCommand(COMMAND_CODE_PLAYLIST_SET_SHUFFLE_MODE, args); 739 } 740 741 @Override 742 public void subscribeRoutesInfo() { 743 sendCommand(COMMAND_CODE_SESSION_SUBSCRIBE_ROUTES_INFO); 744 } 745 746 @Override 747 public void unsubscribeRoutesInfo() { 748 sendCommand(COMMAND_CODE_SESSION_UNSUBSCRIBE_ROUTES_INFO); 749 } 750 751 @Override 752 public void selectRoute(@NonNull Bundle route) { 753 if (route == null) { 754 throw new IllegalArgumentException("route shouldn't be null"); 755 } 756 Bundle args = new Bundle(); 757 args.putBundle(ARGUMENT_ROUTE_BUNDLE, route); 758 sendCommand(COMMAND_CODE_SESSION_SELECT_ROUTE, args); 759 } 760 761 @Override 762 public @NonNull Context getContext() { 763 return mContext; 764 } 765 766 @Override 767 public @NonNull ControllerCallback getCallback() { 768 return mCallback; 769 } 770 771 @Override 772 public @NonNull Executor getCallbackExecutor() { 773 return mCallbackExecutor; 774 } 775 776 @Override 777 public @Nullable MediaBrowserCompat getBrowserCompat() { 778 synchronized (mLock) { 779 return mBrowserCompat; 780 } 781 } 782 783 // Should be used without a lock to prevent potential deadlock. 784 void onConnectedNotLocked(Bundle data) { 785 data.setClassLoader(MediaSession2.class.getClassLoader()); 786 // is enough or should we pass it while connecting? 787 final SessionCommandGroup2 allowedCommands = SessionCommandGroup2.fromBundle( 788 data.getBundle(ARGUMENT_ALLOWED_COMMANDS)); 789 final int playerState = data.getInt(ARGUMENT_PLAYER_STATE); 790 final int bufferingState = data.getInt(ARGUMENT_BUFFERING_STATE); 791 final PlaybackStateCompat playbackStateCompat = data.getParcelable( 792 ARGUMENT_PLAYBACK_STATE_COMPAT); 793 final int repeatMode = data.getInt(ARGUMENT_REPEAT_MODE); 794 final int shuffleMode = data.getInt(ARGUMENT_SHUFFLE_MODE); 795 final List<MediaItem2> playlist = MediaUtils2.fromMediaItem2ParcelableArray( 796 data.getParcelableArray(ARGUMENT_PLAYLIST)); 797 final MediaItem2 currentMediaItem = MediaItem2.fromBundle( 798 data.getBundle(ARGUMENT_MEDIA_ITEM)); 799 final PlaybackInfo playbackInfo = 800 PlaybackInfo.fromBundle(data.getBundle(ARGUMENT_PLAYBACK_INFO)); 801 final MediaMetadata2 metadata = MediaMetadata2.fromBundle( 802 data.getBundle(ARGUMENT_PLAYLIST_METADATA)); 803 if (DEBUG) { 804 Log.d(TAG, "onConnectedNotLocked sessionCompatToken=" + mToken.getSessionCompatToken() 805 + ", allowedCommands=" + allowedCommands); 806 } 807 boolean close = false; 808 try { 809 synchronized (mLock) { 810 if (mIsReleased) { 811 return; 812 } 813 if (mConnected) { 814 Log.e(TAG, "Cannot be notified about the connection result many times." 815 + " Probably a bug or malicious app."); 816 close = true; 817 return; 818 } 819 mAllowedCommands = allowedCommands; 820 mPlayerState = playerState; 821 mBufferingState = bufferingState; 822 mPlaybackStateCompat = playbackStateCompat; 823 mRepeatMode = repeatMode; 824 mShuffleMode = shuffleMode; 825 mPlaylist = playlist; 826 mCurrentMediaItem = currentMediaItem; 827 mPlaylistMetadata = metadata; 828 mConnected = true; 829 mPlaybackInfo = playbackInfo; 830 } 831 mCallbackExecutor.execute(new Runnable() { 832 @Override 833 public void run() { 834 // Note: We may trigger ControllerCallbacks with the initial values 835 // But it's hard to define the order of the controller callbacks 836 // Only notify about the 837 mCallback.onConnected(mInstance, allowedCommands); 838 } 839 }); 840 } finally { 841 if (close) { 842 // Trick to call release() without holding the lock, to prevent potential deadlock 843 // with the developer's custom lock within the ControllerCallback.onDisconnected(). 844 close(); 845 } 846 } 847 } 848 849 private void initialize() { 850 if (mToken.getType() == SessionToken2.TYPE_SESSION) { 851 synchronized (mLock) { 852 mBrowserCompat = null; 853 } 854 connectToSession(mToken.getSessionCompatToken()); 855 } else { 856 connectToService(); 857 } 858 } 859 860 private void connectToSession(MediaSessionCompat.Token sessionCompatToken) { 861 MediaControllerCompat controllerCompat = null; 862 try { 863 controllerCompat = new MediaControllerCompat(mContext, sessionCompatToken); 864 } catch (RemoteException e) { 865 e.printStackTrace(); 866 } 867 synchronized (mLock) { 868 mControllerCompat = controllerCompat; 869 mControllerCompatCallback = new ControllerCompatCallback(); 870 mControllerCompat.registerCallback(mControllerCompatCallback, mHandler); 871 } 872 873 if (controllerCompat.isSessionReady()) { 874 sendCommand(CONTROLLER_COMMAND_CONNECT, new ResultReceiver(mHandler) { 875 @Override 876 protected void onReceiveResult(int resultCode, Bundle resultData) { 877 if (!mHandlerThread.isAlive()) { 878 return; 879 } 880 switch (resultCode) { 881 case CONNECT_RESULT_CONNECTED: 882 onConnectedNotLocked(resultData); 883 break; 884 case CONNECT_RESULT_DISCONNECTED: 885 mCallbackExecutor.execute(new Runnable() { 886 @Override 887 public void run() { 888 mCallback.onDisconnected(mInstance); 889 } 890 }); 891 close(); 892 break; 893 } 894 } 895 }); 896 } 897 } 898 899 private void connectToService() { 900 mCallbackExecutor.execute(new Runnable() { 901 @Override 902 public void run() { 903 synchronized (mLock) { 904 mBrowserCompat = new MediaBrowserCompat(mContext, mToken.getComponentName(), 905 new ConnectionCallback(), sDefaultRootExtras); 906 mBrowserCompat.connect(); 907 } 908 } 909 }); 910 } 911 912 private void sendCommand(int commandCode) { 913 sendCommand(commandCode, null); 914 } 915 916 private void sendCommand(int commandCode, Bundle args) { 917 if (args == null) { 918 args = new Bundle(); 919 } 920 args.putInt(ARGUMENT_COMMAND_CODE, commandCode); 921 sendCommand(CONTROLLER_COMMAND_BY_COMMAND_CODE, args, null); 922 } 923 924 private void sendCommand(String command) { 925 sendCommand(command, null, null); 926 } 927 928 private void sendCommand(String command, ResultReceiver receiver) { 929 sendCommand(command, null, receiver); 930 } 931 932 private void sendCommand(String command, Bundle args, ResultReceiver receiver) { 933 if (args == null) { 934 args = new Bundle(); 935 } 936 MediaControllerCompat controller; 937 ControllerCompatCallback callback; 938 synchronized (mLock) { 939 controller = mControllerCompat; 940 callback = mControllerCompatCallback; 941 } 942 BundleCompat.putBinder(args, ARGUMENT_ICONTROLLER_CALLBACK, 943 callback.getIControllerCallback().asBinder()); 944 args.putString(ARGUMENT_PACKAGE_NAME, mContext.getPackageName()); 945 args.putInt(ARGUMENT_UID, Process.myUid()); 946 args.putInt(ARGUMENT_PID, Process.myPid()); 947 controller.sendCommand(command, args, receiver); 948 } 949 950 private class ConnectionCallback extends MediaBrowserCompat.ConnectionCallback { 951 @Override 952 public void onConnected() { 953 MediaBrowserCompat browser = getBrowserCompat(); 954 if (browser != null) { 955 connectToSession(browser.getSessionToken()); 956 } else if (DEBUG) { 957 Log.d(TAG, "Controller is closed prematually", new IllegalStateException()); 958 } 959 } 960 961 @Override 962 public void onConnectionSuspended() { 963 close(); 964 } 965 966 @Override 967 public void onConnectionFailed() { 968 close(); 969 } 970 } 971 972 private final class ControllerCompatCallback extends MediaControllerCompat.Callback { 973 @Override 974 public void onSessionReady() { 975 sendCommand(CONTROLLER_COMMAND_CONNECT, new ResultReceiver(mHandler) { 976 @Override 977 protected void onReceiveResult(int resultCode, Bundle resultData) { 978 if (!mHandlerThread.isAlive()) { 979 return; 980 } 981 switch (resultCode) { 982 case CONNECT_RESULT_CONNECTED: 983 onConnectedNotLocked(resultData); 984 break; 985 case CONNECT_RESULT_DISCONNECTED: 986 mCallbackExecutor.execute(new Runnable() { 987 @Override 988 public void run() { 989 mCallback.onDisconnected(mInstance); 990 } 991 }); 992 close(); 993 break; 994 } 995 } 996 }); 997 } 998 999 @Override 1000 public void onSessionDestroyed() { 1001 close(); 1002 } 1003 1004 @Override 1005 public void onPlaybackStateChanged(PlaybackStateCompat state) { 1006 synchronized (mLock) { 1007 mPlaybackStateCompat = state; 1008 } 1009 } 1010 1011 @Override 1012 public void onMetadataChanged(MediaMetadataCompat metadata) { 1013 synchronized (mLock) { 1014 mMediaMetadataCompat = metadata; 1015 } 1016 } 1017 1018 @Override 1019 public void onSessionEvent(String event, Bundle extras) { 1020 if (extras != null) { 1021 extras.setClassLoader(MediaSession2.class.getClassLoader()); 1022 } 1023 switch (event) { 1024 case SESSION_EVENT_ON_ALLOWED_COMMANDS_CHANGED: { 1025 final SessionCommandGroup2 allowedCommands = SessionCommandGroup2.fromBundle( 1026 extras.getBundle(ARGUMENT_ALLOWED_COMMANDS)); 1027 synchronized (mLock) { 1028 mAllowedCommands = allowedCommands; 1029 } 1030 mCallbackExecutor.execute(new Runnable() { 1031 @Override 1032 public void run() { 1033 mCallback.onAllowedCommandsChanged(mInstance, allowedCommands); 1034 } 1035 }); 1036 break; 1037 } 1038 case SESSION_EVENT_ON_PLAYER_STATE_CHANGED: { 1039 final int playerState = extras.getInt(ARGUMENT_PLAYER_STATE); 1040 PlaybackStateCompat state = 1041 extras.getParcelable(ARGUMENT_PLAYBACK_STATE_COMPAT); 1042 if (state == null) { 1043 return; 1044 } 1045 synchronized (mLock) { 1046 mPlayerState = playerState; 1047 mPlaybackStateCompat = state; 1048 } 1049 mCallbackExecutor.execute(new Runnable() { 1050 @Override 1051 public void run() { 1052 mCallback.onPlayerStateChanged(mInstance, playerState); 1053 } 1054 }); 1055 break; 1056 } 1057 case SESSION_EVENT_ON_CURRENT_MEDIA_ITEM_CHANGED: { 1058 final MediaItem2 item = MediaItem2.fromBundle( 1059 extras.getBundle(ARGUMENT_MEDIA_ITEM)); 1060 synchronized (mLock) { 1061 mCurrentMediaItem = item; 1062 } 1063 mCallbackExecutor.execute(new Runnable() { 1064 @Override 1065 public void run() { 1066 mCallback.onCurrentMediaItemChanged(mInstance, item); 1067 } 1068 }); 1069 break; 1070 } 1071 case SESSION_EVENT_ON_ERROR: { 1072 final int errorCode = extras.getInt(ARGUMENT_ERROR_CODE); 1073 final Bundle errorExtras = extras.getBundle(ARGUMENT_EXTRAS); 1074 mCallbackExecutor.execute(new Runnable() { 1075 @Override 1076 public void run() { 1077 mCallback.onError(mInstance, errorCode, errorExtras); 1078 } 1079 }); 1080 break; 1081 } 1082 case SESSION_EVENT_ON_ROUTES_INFO_CHANGED: { 1083 final List<Bundle> routes = MediaUtils2.toBundleList( 1084 extras.getParcelableArray(ARGUMENT_ROUTE_BUNDLE)); 1085 mCallbackExecutor.execute(new Runnable() { 1086 @Override 1087 public void run() { 1088 mCallback.onRoutesInfoChanged(mInstance, routes); 1089 } 1090 }); 1091 break; 1092 } 1093 case SESSION_EVENT_ON_PLAYLIST_CHANGED: { 1094 final MediaMetadata2 playlistMetadata = MediaMetadata2.fromBundle( 1095 extras.getBundle(ARGUMENT_PLAYLIST_METADATA)); 1096 final List<MediaItem2> playlist = MediaUtils2.fromMediaItem2ParcelableArray( 1097 extras.getParcelableArray(ARGUMENT_PLAYLIST)); 1098 synchronized (mLock) { 1099 mPlaylist = playlist; 1100 mPlaylistMetadata = playlistMetadata; 1101 } 1102 mCallbackExecutor.execute(new Runnable() { 1103 @Override 1104 public void run() { 1105 mCallback.onPlaylistChanged(mInstance, playlist, playlistMetadata); 1106 } 1107 }); 1108 break; 1109 } 1110 case SESSION_EVENT_ON_PLAYLIST_METADATA_CHANGED: { 1111 final MediaMetadata2 playlistMetadata = MediaMetadata2.fromBundle( 1112 extras.getBundle(ARGUMENT_PLAYLIST_METADATA)); 1113 synchronized (mLock) { 1114 mPlaylistMetadata = playlistMetadata; 1115 } 1116 mCallbackExecutor.execute(new Runnable() { 1117 @Override 1118 public void run() { 1119 mCallback.onPlaylistMetadataChanged(mInstance, playlistMetadata); 1120 } 1121 }); 1122 break; 1123 } 1124 case SESSION_EVENT_ON_REPEAT_MODE_CHANGED: { 1125 final int repeatMode = extras.getInt(ARGUMENT_REPEAT_MODE); 1126 synchronized (mLock) { 1127 mRepeatMode = repeatMode; 1128 } 1129 mCallbackExecutor.execute(new Runnable() { 1130 @Override 1131 public void run() { 1132 mCallback.onRepeatModeChanged(mInstance, repeatMode); 1133 } 1134 }); 1135 break; 1136 } 1137 case SESSION_EVENT_ON_SHUFFLE_MODE_CHANGED: { 1138 final int shuffleMode = extras.getInt(ARGUMENT_SHUFFLE_MODE); 1139 synchronized (mLock) { 1140 mShuffleMode = shuffleMode; 1141 } 1142 mCallbackExecutor.execute(new Runnable() { 1143 @Override 1144 public void run() { 1145 mCallback.onShuffleModeChanged(mInstance, shuffleMode); 1146 } 1147 }); 1148 break; 1149 } 1150 case SESSION_EVENT_SEND_CUSTOM_COMMAND: { 1151 Bundle commandBundle = extras.getBundle(ARGUMENT_CUSTOM_COMMAND); 1152 if (commandBundle == null) { 1153 return; 1154 } 1155 final SessionCommand2 command = SessionCommand2.fromBundle(commandBundle); 1156 final Bundle args = extras.getBundle(ARGUMENT_ARGUMENTS); 1157 final ResultReceiver receiver = extras.getParcelable(ARGUMENT_RESULT_RECEIVER); 1158 mCallbackExecutor.execute(new Runnable() { 1159 @Override 1160 public void run() { 1161 mCallback.onCustomCommand(mInstance, command, args, receiver); 1162 } 1163 }); 1164 break; 1165 } 1166 case SESSION_EVENT_SET_CUSTOM_LAYOUT: { 1167 final List<CommandButton> layout = MediaUtils2.fromCommandButtonParcelableArray( 1168 extras.getParcelableArray(ARGUMENT_COMMAND_BUTTONS)); 1169 if (layout == null) { 1170 return; 1171 } 1172 mCallbackExecutor.execute(new Runnable() { 1173 @Override 1174 public void run() { 1175 mCallback.onCustomLayoutChanged(mInstance, layout); 1176 } 1177 }); 1178 break; 1179 } 1180 case SESSION_EVENT_ON_PLAYBACK_INFO_CHANGED: { 1181 final PlaybackInfo info = PlaybackInfo.fromBundle( 1182 extras.getBundle(ARGUMENT_PLAYBACK_INFO)); 1183 if (info == null) { 1184 return; 1185 } 1186 synchronized (mLock) { 1187 mPlaybackInfo = info; 1188 } 1189 mCallbackExecutor.execute(new Runnable() { 1190 @Override 1191 public void run() { 1192 mCallback.onPlaybackInfoChanged(mInstance, info); 1193 } 1194 }); 1195 break; 1196 } 1197 case SESSION_EVENT_ON_PLAYBACK_SPEED_CHANGED: { 1198 final PlaybackStateCompat state = 1199 extras.getParcelable(ARGUMENT_PLAYBACK_STATE_COMPAT); 1200 if (state == null) { 1201 return; 1202 } 1203 synchronized (mLock) { 1204 mPlaybackStateCompat = state; 1205 } 1206 mCallbackExecutor.execute(new Runnable() { 1207 @Override 1208 public void run() { 1209 mCallback.onPlaybackSpeedChanged(mInstance, state.getPlaybackSpeed()); 1210 } 1211 }); 1212 break; 1213 } 1214 case SESSION_EVENT_ON_BUFFERING_STATE_CHANGED: { 1215 final MediaItem2 item = MediaItem2.fromBundle( 1216 extras.getBundle(ARGUMENT_MEDIA_ITEM)); 1217 final int bufferingState = extras.getInt(ARGUMENT_BUFFERING_STATE); 1218 PlaybackStateCompat state = 1219 extras.getParcelable(ARGUMENT_PLAYBACK_STATE_COMPAT); 1220 if (item == null || state == null) { 1221 return; 1222 } 1223 synchronized (mLock) { 1224 mBufferingState = bufferingState; 1225 mPlaybackStateCompat = state; 1226 } 1227 mCallbackExecutor.execute(new Runnable() { 1228 @Override 1229 public void run() { 1230 mCallback.onBufferingStateChanged(mInstance, item, bufferingState); 1231 } 1232 }); 1233 break; 1234 } 1235 case SESSION_EVENT_ON_SEEK_COMPLETED: { 1236 final long position = extras.getLong(ARGUMENT_SEEK_POSITION); 1237 PlaybackStateCompat state = 1238 extras.getParcelable(ARGUMENT_PLAYBACK_STATE_COMPAT); 1239 if (state == null) { 1240 return; 1241 } 1242 synchronized (mLock) { 1243 mPlaybackStateCompat = state; 1244 } 1245 mCallbackExecutor.execute(new Runnable() { 1246 @Override 1247 public void run() { 1248 mCallback.onSeekCompleted(mInstance, position); 1249 } 1250 }); 1251 break; 1252 } 1253 case SESSION_EVENT_ON_CHILDREN_CHANGED: { 1254 String parentId = extras.getString(ARGUMENT_MEDIA_ID); 1255 if (parentId == null || !(mInstance instanceof MediaBrowser2)) { 1256 return; 1257 } 1258 int itemCount = extras.getInt(ARGUMENT_ITEM_COUNT, -1); 1259 Bundle childrenExtras = extras.getBundle(ARGUMENT_EXTRAS); 1260 ((MediaBrowser2.BrowserCallback) mCallback).onChildrenChanged( 1261 (MediaBrowser2) mInstance, parentId, itemCount, childrenExtras); 1262 break; 1263 } 1264 case SESSION_EVENT_ON_SEARCH_RESULT_CHANGED: { 1265 final String query = extras.getString(ARGUMENT_QUERY); 1266 if (query == null || !(mInstance instanceof MediaBrowser2)) { 1267 return; 1268 } 1269 final int itemCount = extras.getInt(ARGUMENT_ITEM_COUNT, -1); 1270 final Bundle searchExtras = extras.getBundle(ARGUMENT_EXTRAS); 1271 mCallbackExecutor.execute(new Runnable() { 1272 @Override 1273 public void run() { 1274 ((MediaBrowser2.BrowserCallback) mCallback).onSearchResultChanged( 1275 (MediaBrowser2) mInstance, query, itemCount, searchExtras); 1276 } 1277 }); 1278 break; 1279 } 1280 } 1281 } 1282 } 1283 } 1284