SampleMediaRouterActivity.java revision 7b95dd38981772983f00c5ed91aca4b30b782fac
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.example.android.supportv7.media; 18 19import com.example.android.supportv7.R; 20 21import android.content.ComponentName; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.content.BroadcastReceiver; 26import android.content.res.Resources; 27import android.content.DialogInterface; 28import android.app.PendingIntent; 29import android.app.Presentation; 30import android.media.AudioManager; 31import android.media.AudioManager.OnAudioFocusChangeListener; 32import android.media.MediaMetadataRetriever; 33import android.media.RemoteControlClient; 34import android.net.Uri; 35import android.os.Build; 36import android.os.Handler; 37import android.os.Bundle; 38import android.os.SystemClock; 39import android.support.v4.app.FragmentManager; 40import android.support.v4.view.MenuItemCompat; 41import android.support.v7.app.ActionBarActivity; 42import android.support.v7.app.MediaRouteActionProvider; 43import android.support.v7.app.MediaRouteDiscoveryFragment; 44import android.support.v7.media.MediaControlIntent; 45import android.support.v7.media.MediaRouter; 46import android.support.v7.media.MediaRouter.Callback; 47import android.support.v7.media.MediaRouter.RouteInfo; 48import android.support.v7.media.MediaRouter.ProviderInfo; 49import android.support.v7.media.MediaRouteSelector; 50import android.support.v7.media.MediaItemStatus; 51import android.util.Log; 52import android.view.Gravity; 53import android.view.KeyEvent; 54import android.view.Menu; 55import android.view.MenuItem; 56import android.view.View; 57import android.view.View.OnClickListener; 58import android.view.ViewGroup; 59import android.view.Display; 60import android.view.SurfaceView; 61import android.view.SurfaceHolder; 62import android.view.WindowManager; 63import android.widget.AdapterView; 64import android.widget.AdapterView.OnItemClickListener; 65import android.widget.ArrayAdapter; 66import android.widget.ImageButton; 67import android.widget.ListView; 68import android.widget.TextView; 69import android.widget.Toast; 70import android.widget.FrameLayout; 71import android.widget.TabHost; 72import android.widget.TabHost.TabSpec; 73import android.widget.TabHost.OnTabChangeListener; 74import android.widget.SeekBar; 75import android.widget.SeekBar.OnSeekBarChangeListener; 76 77 78import java.io.File; 79 80/** 81 * <h3>Media Router Support Activity</h3> 82 * 83 * <p> 84 * This demonstrates how to use the {@link MediaRouter} API to build an 85 * application that allows the user to send content to various rendering 86 * targets. 87 * </p> 88 */ 89public class SampleMediaRouterActivity extends ActionBarActivity { 90 private static final String TAG = "MediaRouterSupport"; 91 private static final String DISCOVERY_FRAGMENT_TAG = "DiscoveryFragment"; 92 private static final String ACTION_STATUS_CHANGE = 93 "com.example.android.supportv7.media.ACTION_STATUS_CHANGE"; 94 95 private MediaRouter mMediaRouter; 96 private MediaRouteSelector mSelector; 97 private LibraryAdapter mLibraryItems; 98 private PlaylistAdapter mPlayListItems; 99 private TextView mInfoTextView; 100 private ListView mLibraryView; 101 private ListView mPlayListView; 102 private ImageButton mPauseResumeButton; 103 private ImageButton mStopButton; 104 private SeekBar mSeekBar; 105 private String mStatsInfo; 106 private boolean mPaused; 107 private boolean mNeedResume; 108 private boolean mSeeking; 109 private long mLastStatusTime; 110 private PlaylistAdapter mSavedPlaylist; 111 112 private final Handler mHandler = new Handler(); 113 private final Runnable mUpdateSeekRunnable = new Runnable() { 114 @Override 115 public void run() { 116 updateProgress(getCheckedMediaQueueItem()); 117 // update Ui every 1 second 118 mHandler.postDelayed(this, 1000); 119 } 120 }; 121 122 private final MediaPlayerWrapper mMediaPlayer = new MediaPlayerWrapper(this); 123 private final MediaPlayerWrapper.Callback mMediaPlayerCB = 124 new MediaPlayerWrapper.Callback() { 125 @Override 126 public void onError() { 127 mPlayer.onFinish(true); 128 } 129 130 @Override 131 public void onCompletion() { 132 mPlayer.onFinish(false); 133 } 134 135 @Override 136 public void onSizeChanged(int width, int height) { 137 mPlayer.updateSize(width, height); 138 } 139 140 @Override 141 public void onStatusChanged() { 142 if (!mSeeking) { 143 updateUi(); 144 } 145 } 146 }; 147 148 private final RemotePlayer mRemotePlayer = new RemotePlayer(); 149 private final LocalPlayer mLocalPlayer = new LocalPlayer(); 150 private Player mPlayer; 151 private MediaSessionManager.Callback mPlayerCB; 152 153 private final MediaRouter.Callback mMediaRouterCB = new MediaRouter.Callback() { 154 // Return a custom callback that will simply log all of the route events 155 // for demonstration purposes. 156 @Override 157 public void onRouteAdded(MediaRouter router, RouteInfo route) { 158 Log.d(TAG, "onRouteAdded: route=" + route); 159 } 160 161 @Override 162 public void onRouteChanged(MediaRouter router, RouteInfo route) { 163 Log.d(TAG, "onRouteChanged: route=" + route); 164 mPlayer.showStatistics(); 165 } 166 167 @Override 168 public void onRouteRemoved(MediaRouter router, RouteInfo route) { 169 Log.d(TAG, "onRouteRemoved: route=" + route); 170 } 171 172 @Override 173 public void onRouteSelected(MediaRouter router, RouteInfo route) { 174 Log.d(TAG, "onRouteSelected: route=" + route); 175 176 Player player = mPlayer; 177 MediaSessionManager.Callback playerCB = mPlayerCB; 178 179 if (route.supportsControlCategory( 180 MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) { 181 Intent enqueueIntent = new Intent(MediaControlIntent.ACTION_ENQUEUE); 182 enqueueIntent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); 183 enqueueIntent.setDataAndType(Uri.parse("http://"), "video/mp4"); 184 185 Intent removeIntent = new Intent(MediaControlIntent.ACTION_REMOVE); 186 removeIntent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); 187 188 // Remote Playback: 189 // If route supports remote queuing, let it manage the queue; 190 // otherwise, manage the queue locally and feed it one item at a time 191 if (route.supportsControlRequest(enqueueIntent) 192 && route.supportsControlRequest(removeIntent)) { 193 player = mRemotePlayer; 194 } else { 195 player = mLocalPlayer; 196 } 197 playerCB = mRemotePlayer; 198 mRemotePlayer.reset(); 199 200 } else { 201 // Local Playback: 202 // Use local player and feed media player one item at a time 203 player = mLocalPlayer; 204 playerCB = mMediaPlayer; 205 } 206 207 if (player != mPlayer || playerCB != mPlayerCB) { 208 // save current playlist 209 PlaylistAdapter playlist = new PlaylistAdapter(); 210 for (int i = 0; i < mPlayListItems.getCount(); i++) { 211 MediaQueueItem item = mPlayListItems.getItem(i); 212 if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING 213 || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) { 214 long position = item.getContentPosition(); 215 long timeDelta = mPaused ? 0 : 216 (SystemClock.elapsedRealtime() - mLastStatusTime); 217 item.setContentPosition(position + timeDelta); 218 } 219 playlist.add(item); 220 } 221 222 // switch players 223 mPlayer.stop(); 224 mPaused = false; 225 mLocalPlayer.setCallback(playerCB); 226 mPlayerCB = playerCB; 227 mPlayer = player; 228 mPlayer.showStatistics(); 229 mLocalPlayer.updatePresentation(); 230 231 // migrate playlist to new route 232 int count = playlist.getCount(); 233 if (isRemoteQueue()) { 234 // if queuing is managed remotely, only enqueue the first 235 // item, as we need to have the returned session id to 236 // enqueue the rest of the playlist items 237 mSavedPlaylist = playlist; 238 count = 1; 239 } 240 for (int i = 0; i < count; i++) { 241 final MediaQueueItem item = playlist.getItem(i); 242 mPlayer.enqueue(item.getUri(), item.getContentPosition()); 243 } 244 } 245 updateUi(); 246 } 247 248 @Override 249 public void onRouteUnselected(MediaRouter router, RouteInfo route) { 250 Log.d(TAG, "onRouteUnselected: route=" + route); 251 mPlayer.showStatistics(); 252 } 253 254 @Override 255 public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) { 256 Log.d(TAG, "onRouteVolumeChanged: route=" + route); 257 } 258 259 @Override 260 public void onRoutePresentationDisplayChanged( 261 MediaRouter router, RouteInfo route) { 262 Log.d(TAG, "onRoutePresentationDisplayChanged: route=" + route); 263 } 264 265 @Override 266 public void onProviderAdded(MediaRouter router, ProviderInfo provider) { 267 Log.d(TAG, "onRouteProviderAdded: provider=" + provider); 268 } 269 270 @Override 271 public void onProviderRemoved(MediaRouter router, ProviderInfo provider) { 272 Log.d(TAG, "onRouteProviderRemoved: provider=" + provider); 273 } 274 275 @Override 276 public void onProviderChanged(MediaRouter router, ProviderInfo provider) { 277 Log.d(TAG, "onRouteProviderChanged: provider=" + provider); 278 } 279 }; 280 281 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 282 @Override 283 public void onReceive(Context context, Intent intent) { 284 Log.d(TAG, "Received status update: " + intent); 285 if (intent.getAction().equals(ACTION_STATUS_CHANGE)) { 286 String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); 287 String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID); 288 MediaItemStatus status = MediaItemStatus.fromBundle( 289 intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_STATUS)); 290 291 if (status.getPlaybackState() == 292 MediaItemStatus.PLAYBACK_STATE_FINISHED) { 293 mPlayer.onFinish(false); 294 } else if (status.getPlaybackState() == 295 MediaItemStatus.PLAYBACK_STATE_ERROR) { 296 mPlayer.onFinish(true); 297 showToast("Error while playing item" + 298 ", sid " + sid + ", iid " + iid); 299 } else { 300 if (!mSeeking) { 301 updateUi(); 302 } 303 } 304 } 305 } 306 }; 307 308 private RemoteControlClient mRemoteControlClient; 309 private ComponentName mEventReceiver; 310 private AudioManager mAudioManager; 311 private PendingIntent mMediaPendingIntent; 312 private final OnAudioFocusChangeListener mAfChangeListener = 313 new OnAudioFocusChangeListener() { 314 @Override 315 public void onAudioFocusChange(int focusChange) { 316 if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) { 317 Log.d(TAG, "onAudioFocusChange: LOSS_TRANSIENT"); 318 } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { 319 Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_GAIN"); 320 } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { 321 Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_LOSS"); 322 } 323 } 324 }; 325 326 @Override 327 protected void onCreate(Bundle savedInstanceState) { 328 // Be sure to call the super class. 329 super.onCreate(savedInstanceState); 330 331 // Get the media router service. 332 mMediaRouter = MediaRouter.getInstance(this); 333 334 // Create a route selector for the type of routes that we care about. 335 mSelector = new MediaRouteSelector.Builder() 336 .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO) 337 .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO) 338 .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK) 339 .addControlCategory(SampleMediaRouteProvider.CATEGORY_SAMPLE_ROUTE) 340 .build(); 341 342 // Add a fragment to take care of media route discovery. 343 // This fragment automatically adds or removes a callback whenever the activity 344 // is started or stopped. 345 FragmentManager fm = getSupportFragmentManager(); 346 DiscoveryFragment fragment = (DiscoveryFragment)fm.findFragmentByTag( 347 DISCOVERY_FRAGMENT_TAG); 348 if (fragment == null) { 349 fragment = new DiscoveryFragment(mMediaRouterCB); 350 fragment.setRouteSelector(mSelector); 351 fm.beginTransaction() 352 .add(fragment, DISCOVERY_FRAGMENT_TAG) 353 .commit(); 354 } else { 355 fragment.setCallback(mMediaRouterCB); 356 fragment.setRouteSelector(mSelector); 357 } 358 359 // Populate an array adapter with streaming media items. 360 String[] mediaNames = getResources().getStringArray(R.array.media_names); 361 String[] mediaUris = getResources().getStringArray(R.array.media_uris); 362 mLibraryItems = new LibraryAdapter(); 363 for (int i = 0; i < mediaNames.length; i++) { 364 mLibraryItems.add(new MediaItem( 365 "[streaming] "+mediaNames[i], Uri.parse(mediaUris[i]))); 366 } 367 368 // Scan local /sdcard/ directory for media files. 369 String sdcard = "/sdcard/"; 370 File file = new File(sdcard); 371 File list[] = file.listFiles(); 372 for (int i = 0; i < list.length; i++) { 373 String filename = list[i].getName(); 374 if (filename.matches(".*\\.(m4v|mp4)")) { 375 mLibraryItems.add(new MediaItem( 376 "[local] "+filename, Uri.parse("file:///sdcard/" + filename))); 377 } 378 } 379 380 mPlayListItems = new PlaylistAdapter(); 381 382 // Initialize the layout. 383 setContentView(R.layout.sample_media_router); 384 385 TabHost tabHost=(TabHost)findViewById(R.id.tabHost); 386 tabHost.setup(); 387 String tabName = getResources().getString(R.string.library_tab_text); 388 TabSpec spec1=tabHost.newTabSpec(tabName); 389 spec1.setContent(R.id.tab1); 390 spec1.setIndicator(tabName); 391 392 tabName = getResources().getString(R.string.playlist_tab_text); 393 TabSpec spec2=tabHost.newTabSpec(tabName); 394 spec2.setIndicator(tabName); 395 spec2.setContent(R.id.tab2); 396 397 tabName = getResources().getString(R.string.statistics_tab_text); 398 TabSpec spec3=tabHost.newTabSpec(tabName); 399 spec3.setIndicator(tabName); 400 spec3.setContent(R.id.tab3); 401 402 tabHost.addTab(spec1); 403 tabHost.addTab(spec2); 404 tabHost.addTab(spec3); 405 tabHost.setOnTabChangedListener(new OnTabChangeListener() { 406 @Override 407 public void onTabChanged(String arg0) { 408 if (arg0.equals(getResources().getString( 409 R.string.statistics_tab_text))) { 410 mPlayer.showStatistics(); 411 } 412 updateUi(); 413 } 414 }); 415 416 mLibraryView = (ListView) findViewById(R.id.media); 417 mLibraryView.setAdapter(mLibraryItems); 418 mLibraryView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 419 mLibraryView.setOnItemClickListener(new OnItemClickListener() { 420 @Override 421 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 422 updateButtons(); 423 } 424 }); 425 426 mPlayListView = (ListView) findViewById(R.id.playlist); 427 mPlayListView.setAdapter(mPlayListItems); 428 mPlayListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 429 mPlayListView.setOnItemClickListener(new OnItemClickListener() { 430 @Override 431 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 432 updateButtons(); 433 } 434 }); 435 436 mInfoTextView = (TextView) findViewById(R.id.info); 437 438 mPauseResumeButton = (ImageButton)findViewById(R.id.pause_resume_button); 439 mPauseResumeButton.setOnClickListener(new OnClickListener() { 440 @Override 441 public void onClick(View v) { 442 if (!mPaused) { 443 mPlayer.pause(); 444 } else { 445 mPlayer.resume(); 446 } 447 } 448 }); 449 450 mStopButton = (ImageButton)findViewById(R.id.stop_button); 451 mStopButton.setOnClickListener(new OnClickListener() { 452 @Override 453 public void onClick(View v) { 454 mPlayer.stop(); 455 clearContent(); 456 } 457 }); 458 459 mSeekBar = (SeekBar) findViewById(R.id.seekbar); 460 mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { 461 @Override 462 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 463 MediaQueueItem item = getCheckedMediaQueueItem(); 464 if (fromUser && item != null && item.getContentDuration() > 0) { 465 long pos = progress * item.getContentDuration() / 100; 466 mPlayer.seek(item.getSessionId(), item.getItemId(), pos); 467 item.setContentPosition(pos); 468 mLastStatusTime = SystemClock.elapsedRealtime(); 469 } 470 } 471 @Override 472 public void onStartTrackingTouch(SeekBar seekBar) { 473 mSeeking = true; 474 } 475 @Override 476 public void onStopTrackingTouch(SeekBar seekBar) { 477 mSeeking = false; 478 updateUi(); 479 } 480 }); 481 482 // Schedule Ui update 483 mHandler.postDelayed(mUpdateSeekRunnable, 1000); 484 485 // Use local playback with media player by default 486 mLocalPlayer.onCreate(); 487 mMediaPlayer.setCallback(mMediaPlayerCB); 488 mLocalPlayer.setCallback(mMediaPlayer); 489 mPlayerCB = mMediaPlayer; 490 mPlayer = mLocalPlayer; 491 492 // Register broadcast receiver to receive status update from MRP 493 IntentFilter filter = new IntentFilter(); 494 filter.addAction(SampleMediaRouterActivity.ACTION_STATUS_CHANGE); 495 registerReceiver(mReceiver, filter); 496 497 // Build the PendingIntent for the remote control client 498 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 499 mEventReceiver = new ComponentName(getPackageName(), 500 SampleMediaButtonReceiver.class.getName()); 501 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); 502 mediaButtonIntent.setComponent(mEventReceiver); 503 mMediaPendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0); 504 505 // Create and register the remote control client 506 registerRCC(); 507 } 508 509 private void registerRCC() { 510 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 511 // Create the RCC and register with AudioManager and MediaRouter 512 mAudioManager.requestAudioFocus(mAfChangeListener, 513 AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); 514 mAudioManager.registerMediaButtonEventReceiver(mEventReceiver); 515 mRemoteControlClient = new RemoteControlClient(mMediaPendingIntent); 516 mAudioManager.registerRemoteControlClient(mRemoteControlClient); 517 mMediaRouter.addRemoteControlClient(mRemoteControlClient); 518 SampleMediaButtonReceiver.setActivity(SampleMediaRouterActivity.this); 519 mRemoteControlClient.setTransportControlFlags( 520 RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE); 521 mRemoteControlClient.setPlaybackState( 522 RemoteControlClient.PLAYSTATE_PLAYING); 523 } 524 } 525 526 private void unregisterRCC() { 527 // Unregister the RCC with AudioManager and MediaRouter 528 if (mRemoteControlClient != null) { 529 mRemoteControlClient.setTransportControlFlags(0); 530 mAudioManager.abandonAudioFocus(mAfChangeListener); 531 mAudioManager.unregisterMediaButtonEventReceiver(mEventReceiver); 532 mAudioManager.unregisterRemoteControlClient(mRemoteControlClient); 533 mMediaRouter.removeRemoteControlClient(mRemoteControlClient); 534 SampleMediaButtonReceiver.setActivity(null); 535 mRemoteControlClient = null; 536 } 537 } 538 539 public boolean handleMediaKey(KeyEvent event) { 540 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 541 switch (event.getKeyCode()) { 542 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 543 { 544 Log.d(TAG, "Received Play/Pause event from RemoteControlClient"); 545 if (!mPaused) { 546 mPlayer.pause(); 547 } else { 548 mPlayer.resume(); 549 } 550 return true; 551 } 552 case KeyEvent.KEYCODE_MEDIA_PLAY: 553 { 554 Log.d(TAG, "Received Play event from RemoteControlClient"); 555 if (mPaused) { 556 mPlayer.resume(); 557 } 558 return true; 559 } 560 case KeyEvent.KEYCODE_MEDIA_PAUSE: 561 { 562 Log.d(TAG, "Received Pause event from RemoteControlClient"); 563 if (!mPaused) { 564 mPlayer.pause(); 565 } 566 return true; 567 } 568 case KeyEvent.KEYCODE_MEDIA_STOP: 569 { 570 Log.d(TAG, "Received Stop event from RemoteControlClient"); 571 mPlayer.stop(); 572 clearContent(); 573 return true; 574 } 575 default: 576 break; 577 } 578 } 579 return false; 580 } 581 582 @Override 583 public boolean onKeyDown(int keyCode, KeyEvent event) { 584 return handleMediaKey(event) || super.onKeyDown(keyCode, event); 585 } 586 587 @Override 588 public boolean onKeyUp(int keyCode, KeyEvent event) { 589 return handleMediaKey(event) || super.onKeyUp(keyCode, event); 590 } 591 592 @Override 593 public void onStart() { 594 // Be sure to call the super class. 595 super.onStart(); 596 mPlayer.showStatistics(); 597 } 598 599 @Override 600 public void onPause() { 601 // pause media player for local playback case only 602 if (!isRemotePlayback() && !mPaused) { 603 mNeedResume = true; 604 mPlayer.pause(); 605 } 606 super.onPause(); 607 } 608 609 @Override 610 public void onResume() { 611 // resume media player for local playback case only 612 if (!isRemotePlayback() && mNeedResume) { 613 mPlayer.resume(); 614 mNeedResume = false; 615 } 616 super.onResume(); 617 } 618 619 @Override 620 public void onDestroy() { 621 // Unregister the remote control client 622 unregisterRCC(); 623 624 // Unregister broadcast receiver 625 unregisterReceiver(mReceiver); 626 mPlayer.stop(); 627 mMediaPlayer.release(); 628 629 super.onDestroy(); 630 } 631 632 @Override 633 public boolean onCreateOptionsMenu(Menu menu) { 634 // Be sure to call the super class. 635 super.onCreateOptionsMenu(menu); 636 637 // Inflate the menu and configure the media router action provider. 638 getMenuInflater().inflate(R.menu.sample_media_router_menu, menu); 639 640 MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item); 641 MediaRouteActionProvider mediaRouteActionProvider = 642 (MediaRouteActionProvider)MenuItemCompat.getActionProvider(mediaRouteMenuItem); 643 mediaRouteActionProvider.setRouteSelector(mSelector); 644 645 // Return true to show the menu. 646 return true; 647 } 648 649 private void updateRouteDescription() { 650 RouteInfo route = mMediaRouter.getSelectedRoute(); 651 mInfoTextView.setText("Currently selected route:" 652 + "\nName: " + route.getName() 653 + "\nProvider: " + route.getProvider().getPackageName() 654 + "\nDescription: " + route.getDescription() 655 + "\nStatistics: " + mStatsInfo); 656 updateButtons(); 657 mLocalPlayer.updatePresentation(); 658 } 659 660 private void clearContent() { 661 //TO-DO: clear surface view 662 } 663 664 private void updateButtons() { 665 MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); 666 // show pause or resume icon depending on current state 667 mPauseResumeButton.setImageResource(mPaused ? 668 R.drawable.ic_media_play : R.drawable.ic_media_pause); 669 // only enable seek bar when duration is known 670 MediaQueueItem item = getCheckedMediaQueueItem(); 671 mSeekBar.setEnabled(item != null && item.getContentDuration() > 0); 672 if (mRemoteControlClient != null) { 673 mRemoteControlClient.setPlaybackState(mPaused ? 674 RemoteControlClient.PLAYSTATE_PAUSED : RemoteControlClient.PLAYSTATE_PLAYING); 675 } 676 } 677 678 private void updateProgress(MediaQueueItem queueItem) { 679 // Estimate content position from last status time and elapsed time. 680 // (Note this might be slightly out of sync with remote side, however 681 // it avoids frequent polling the MRP.) 682 int progress = 0; 683 if (queueItem != null) { 684 int state = queueItem.getState(); 685 long duration = queueItem.getContentDuration(); 686 if (duration <= 0) { 687 if (state == MediaItemStatus.PLAYBACK_STATE_PLAYING 688 || state == MediaItemStatus.PLAYBACK_STATE_PAUSED) { 689 updateUi(); 690 } 691 } else { 692 long position = queueItem.getContentPosition(); 693 long timeDelta = mPaused ? 0 : 694 (SystemClock.elapsedRealtime() - mLastStatusTime); 695 progress = (int)(100.0 * (position + timeDelta) / duration); 696 } 697 } 698 mSeekBar.setProgress(progress); 699 } 700 701 private void updateUi() { 702 updatePlaylist(); 703 updateButtons(); 704 } 705 706 private void updatePlaylist() { 707 Log.d(TAG, "updatePlaylist"); 708 final PlaylistAdapter playlist = new PlaylistAdapter(); 709 // make a copy of current playlist 710 for (int i = 0; i < mPlayListItems.getCount(); i++) { 711 playlist.add(mPlayListItems.getItem(i)); 712 } 713 // clear mPlayListItems first, items will be added back when we get 714 // status back from provider. 715 mPlayListItems.clear(); 716 mPlayListView.invalidate(); 717 718 for (int i = 0; i < playlist.getCount(); i++) { 719 final MediaQueueItem item = playlist.getItem(i); 720 final boolean update = (i == playlist.getCount() - 1); 721 mPlayer.getStatus(item, update); 722 } 723 } 724 725 private MediaItem getCheckedMediaItem() { 726 int index = mLibraryView.getCheckedItemPosition(); 727 if (index >= 0 && index < mLibraryItems.getCount()) { 728 return mLibraryItems.getItem(index); 729 } 730 return null; 731 } 732 733 private MediaQueueItem getCheckedMediaQueueItem() { 734 int count = mPlayListView.getCount(); 735 int index = mPlayListView.getCheckedItemPosition(); 736 if (count > 0) { 737 if (index < 0 || index >= count) { 738 index = 0; 739 mPlayListView.setItemChecked(0, true); 740 } 741 return mPlayListItems.getItem(index); 742 } 743 return null; 744 } 745 746 private void enqueuePlaylist() { 747 if (mSavedPlaylist != null) { 748 final PlaylistAdapter playlist = mSavedPlaylist; 749 mSavedPlaylist = null; 750 // migrate playlist (except for the 1st item) to new route 751 for (int i = 1; i < playlist.getCount(); i++) { 752 final MediaQueueItem item = playlist.getItem(i); 753 mPlayer.enqueue(item.getUri(), item.getContentPosition()); 754 } 755 } 756 } 757 758 private boolean isRemoteQueue() { 759 return mPlayer == mRemotePlayer; 760 } 761 762 private boolean isRemotePlayback() { 763 return mPlayerCB == mRemotePlayer; 764 } 765 766 private void showToast(String msg) { 767 Toast toast = Toast.makeText(SampleMediaRouterActivity.this, 768 "[app] " + msg, Toast.LENGTH_LONG); 769 toast.setGravity(Gravity.TOP, 0, 100); 770 toast.show(); 771 } 772 773 private interface Player { 774 void enqueue(final Uri uri, long pos); 775 void remove(final MediaQueueItem item); 776 void seek(String sid, String iid, long pos); 777 void getStatus(final MediaQueueItem item, final boolean update); 778 void pause(); 779 void resume(); 780 void stop(); 781 void showStatistics(); 782 void onFinish(boolean error); 783 void updateSize(int width, int height); 784 } 785 786 private class LocalPlayer implements Player, SurfaceHolder.Callback { 787 private final MediaSessionManager mSessionManager = new MediaSessionManager(); 788 private String mSessionId; 789 // The presentation to show on the secondary display. 790 private DemoPresentation mPresentation; 791 private SurfaceView mSurfaceView; 792 private FrameLayout mLayout; 793 private int mVideoWidth; 794 private int mVideoHeight; 795 796 public void onCreate() { 797 mLayout = (FrameLayout)findViewById(R.id.player); 798 mSurfaceView = (SurfaceView)findViewById(R.id.surface_view); 799 SurfaceHolder holder = mSurfaceView.getHolder(); 800 holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 801 holder.addCallback(this); 802 } 803 804 public void setCallback(MediaSessionManager.Callback cb) { 805 mSessionManager.setCallback(cb); 806 } 807 808 @Override 809 public void enqueue(final Uri uri, long pos) { 810 Log.d(TAG, "LocalPlayer: enqueue, uri=" + uri + ", pos=" + pos); 811 MediaQueueItem playlistItem = mSessionManager.enqueue(mSessionId, uri, null); 812 mSessionId = playlistItem.getSessionId(); 813 // Set remote control client title 814 if (mPlayListItems.getCount() == 0 && mRemoteControlClient != null) { 815 RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true); 816 ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, 817 playlistItem.toString()); 818 ed.apply(); 819 } 820 mPlayListItems.add(playlistItem); 821 if (pos > 0) { 822 // Seek to initial position if needed 823 mPlayer.seek(mSessionId, playlistItem.getItemId(), pos); 824 } 825 updateUi(); 826 } 827 828 @Override 829 public void remove(final MediaQueueItem item) { 830 Log.d(TAG, "LocalPlayer: remove, item=" + item); 831 mSessionManager.remove(item.getSessionId(), item.getItemId()); 832 updateUi(); 833 } 834 835 @Override 836 public void seek(String sid, String iid, long pos) { 837 Log.d(TAG, "LocalPlayer: seek, sid=" + sid + ", iid=" + iid); 838 mSessionManager.seek(sid, iid, pos); 839 } 840 841 @Override 842 public void getStatus(final MediaQueueItem item, final boolean update) { 843 Log.d(TAG, "LocalPlayer: getStatus, item=" + item + ", update=" + update); 844 MediaQueueItem playlistItem = 845 mSessionManager.getStatus(item.getSessionId(), item.getItemId()); 846 if (playlistItem != null) { 847 mLastStatusTime = playlistItem.getStatus().getTimestamp(); 848 mPlayListItems.add(item); 849 mPlayListView.invalidate(); 850 } 851 if (update) { 852 clearContent(); 853 updateButtons(); 854 } 855 } 856 857 @Override 858 public void pause() { 859 Log.d(TAG, "LocalPlayer: pause"); 860 mSessionManager.pause(mSessionId); 861 mPaused = true; 862 updateUi(); 863 } 864 865 @Override 866 public void resume() { 867 Log.d(TAG, "LocalPlayer: resume"); 868 mSessionManager.resume(mSessionId); 869 mPaused = false; 870 updateUi(); 871 } 872 873 @Override 874 public void stop() { 875 Log.d(TAG, "LocalPlayer: stop"); 876 mSessionManager.stop(mSessionId); 877 mSessionId = null; 878 mPaused = false; 879 // For demo purpose, invalidate remote session when local session 880 // is stopped (note this is not necessary, remote player could reuse 881 // the same session) 882 mRemotePlayer.reset(); 883 updateUi(); 884 } 885 886 @Override 887 public void showStatistics() { 888 Log.d(TAG, "LocalPlayer: showStatistics"); 889 mStatsInfo = null; 890 if (isRemotePlayback()) { 891 mRemotePlayer.showStatistics(); 892 } 893 updateRouteDescription(); 894 } 895 896 @Override 897 public void onFinish(boolean error) { 898 MediaQueueItem item = mSessionManager.finish(error); 899 updateUi(); 900 if (error && item != null) { 901 showToast("Failed to play item " + item.getUri()); 902 } 903 } 904 905 // SurfaceHolder.Callback 906 @Override 907 public void surfaceChanged(SurfaceHolder holder, int format, 908 int width, int height) { 909 Log.d(TAG, "surfaceChanged "+width+"x"+height); 910 mMediaPlayer.setSurface(holder); 911 } 912 913 @Override 914 public void surfaceCreated(SurfaceHolder holder) { 915 Log.d(TAG, "surfaceCreated"); 916 mMediaPlayer.setSurface(holder); 917 updateSize(mVideoWidth, mVideoHeight); 918 } 919 920 @Override 921 public void surfaceDestroyed(SurfaceHolder holder) { 922 Log.d(TAG, "surfaceDestroyed"); 923 } 924 925 @Override 926 public void updateSize(int width, int height) { 927 if (width > 0 && height > 0) { 928 if (mPresentation == null) { 929 int surfaceWidth = mLayout.getWidth(); 930 int surfaceHeight = mLayout.getHeight(); 931 932 // Calculate the new size of mSurfaceView, so that video is centered 933 // inside the framelayout with proper letterboxing/pillarboxing 934 ViewGroup.LayoutParams lp = mSurfaceView.getLayoutParams(); 935 if (surfaceWidth * height < surfaceHeight * width) { 936 // Black bars on top&bottom, mSurfaceView has full layout width, 937 // while height is derived from video's aspect ratio 938 lp.width = surfaceWidth; 939 lp.height = surfaceWidth * height / width; 940 } else { 941 // Black bars on left&right, mSurfaceView has full layout height, 942 // while width is derived from video's aspect ratio 943 lp.width = surfaceHeight * width / height; 944 lp.height = surfaceHeight; 945 } 946 Log.d(TAG, "video rect is "+lp.width+"x"+lp.height); 947 mSurfaceView.setLayoutParams(lp); 948 } else { 949 mPresentation.updateSize(width, height); 950 } 951 mVideoWidth = width; 952 mVideoHeight = height; 953 } else { 954 mVideoWidth = mVideoHeight = 0; 955 } 956 } 957 958 private void updatePresentation() { 959 // Get the current route and its presentation display. 960 MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); 961 Display presentationDisplay = route != null ? route.getPresentationDisplay() : null; 962 963 // Dismiss the current presentation if the display has changed. 964 if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) { 965 Log.i(TAG, "Dismissing presentation because the current route no longer " 966 + "has a presentation display."); 967 mPresentation.dismiss(); 968 mPresentation = null; 969 } 970 971 // Show a new presentation if needed. 972 if (mPresentation == null && presentationDisplay != null) { 973 Log.i(TAG, "Showing presentation on display: " + presentationDisplay); 974 mPresentation = new DemoPresentation( 975 SampleMediaRouterActivity.this, presentationDisplay); 976 mPresentation.setOnDismissListener(mOnDismissListener); 977 try { 978 mPresentation.show(); 979 } catch (WindowManager.InvalidDisplayException ex) { 980 Log.w(TAG, "Couldn't show presentation! Display was removed in " 981 + "the meantime.", ex); 982 mPresentation = null; 983 } 984 } 985 986 if (mPresentation != null || route.supportsControlCategory( 987 MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) { 988 mMediaPlayer.setSurface((SurfaceHolder)null); 989 mMediaPlayer.reset(); 990 mSurfaceView.setVisibility(View.GONE); 991 mLayout.setVisibility(View.GONE); 992 } else { 993 mLayout.setVisibility(View.VISIBLE); 994 mSurfaceView.setVisibility(View.VISIBLE); 995 } 996 } 997 998 // Listens for when presentations are dismissed. 999 private final DialogInterface.OnDismissListener mOnDismissListener = 1000 new DialogInterface.OnDismissListener() { 1001 @Override 1002 public void onDismiss(DialogInterface dialog) { 1003 if (dialog == mPresentation) { 1004 Log.i(TAG, "Presentation was dismissed."); 1005 mPresentation = null; 1006 updatePresentation(); 1007 } 1008 } 1009 }; 1010 1011 private final class DemoPresentation extends Presentation { 1012 private SurfaceView mPresentationSurfaceView; 1013 1014 public DemoPresentation(Context context, Display display) { 1015 super(context, display); 1016 } 1017 1018 @Override 1019 protected void onCreate(Bundle savedInstanceState) { 1020 // Be sure to call the super class. 1021 super.onCreate(savedInstanceState); 1022 1023 // Get the resources for the context of the presentation. 1024 // Notice that we are getting the resources from the context 1025 // of the presentation. 1026 Resources r = getContext().getResources(); 1027 1028 // Inflate the layout. 1029 setContentView(R.layout.sample_media_router_presentation); 1030 1031 // Set up the surface view. 1032 mPresentationSurfaceView = (SurfaceView)findViewById(R.id.surface_view); 1033 SurfaceHolder holder = mPresentationSurfaceView.getHolder(); 1034 holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 1035 holder.addCallback(LocalPlayer.this); 1036 } 1037 1038 public void updateSize(int width, int height) { 1039 int surfaceHeight = getWindow().getDecorView().getHeight(); 1040 int surfaceWidth = getWindow().getDecorView().getWidth(); 1041 ViewGroup.LayoutParams lp = mPresentationSurfaceView.getLayoutParams(); 1042 if (surfaceWidth * height < surfaceHeight * width) { 1043 lp.width = surfaceWidth; 1044 lp.height = surfaceWidth * height / width; 1045 } else { 1046 lp.width = surfaceHeight * width / height; 1047 lp.height = surfaceHeight; 1048 } 1049 Log.d(TAG, "video rect is " + lp.width + "x" + lp.height); 1050 mPresentationSurfaceView.setLayoutParams(lp); 1051 } 1052 } 1053 } 1054 1055 private class RemotePlayer implements Player, MediaSessionManager.Callback { 1056 private MediaQueueItem mQueueItem; 1057 private MediaQueueItem mPlaylistItem; 1058 private String mSessionId; 1059 private String mItemId; 1060 private long mPosition; 1061 1062 public void reset() { 1063 mQueueItem = null; 1064 mPlaylistItem = null; 1065 mSessionId = null; 1066 mItemId = null; 1067 mPosition = 0; 1068 } 1069 1070 // MediaSessionManager.Callback 1071 @Override 1072 public void onStart() { 1073 resume(); 1074 } 1075 1076 @Override 1077 public void onPause() { 1078 pause(); 1079 } 1080 1081 @Override 1082 public void onStop() { 1083 stop(); 1084 } 1085 1086 @Override 1087 public void onSeek(long pos) { 1088 // If we're currently performing a Play/Enqueue, do not seek 1089 // until we get the result back (or we may not have valid session 1090 // and item ids); otherwise do the seek now 1091 if (mSessionId != null) { 1092 seek(mSessionId, mItemId, pos); 1093 } 1094 // Set current position to seek-to position, actual position will 1095 // be updated when next getStatus is completed. 1096 mPosition = pos; 1097 } 1098 1099 @Override 1100 public void onGetStatus(MediaQueueItem item) { 1101 if (mQueueItem != null) { 1102 mPlaylistItem = item; 1103 getStatus(mQueueItem, false); 1104 } 1105 } 1106 1107 @Override 1108 public void onNewItem(Uri uri) { 1109 mPosition = 0; 1110 play(uri, false, 0); 1111 } 1112 1113 // Player API 1114 @Override 1115 public void enqueue(final Uri uri, long pos) { 1116 play(uri, true, pos); 1117 } 1118 1119 @Override 1120 public void remove(final MediaQueueItem item) { 1121 MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); 1122 Intent intent = makeRemoveIntent(item); 1123 if (route.supportsControlRequest(intent)) { 1124 MediaRouter.ControlRequestCallback callback = 1125 new MediaRouter.ControlRequestCallback() { 1126 @Override 1127 public void onResult(Bundle data) { 1128 MediaItemStatus status = MediaItemStatus.fromBundle( 1129 data.getBundle(MediaControlIntent.EXTRA_ITEM_STATUS)); 1130 Log.d(TAG, "Remove request succeeded: status=" + status.toString()); 1131 updateUi(); 1132 } 1133 1134 @Override 1135 public void onError(String error, Bundle data) { 1136 Log.d(TAG, "Remove request failed: error=" + error + ", data=" + data); 1137 } 1138 }; 1139 1140 Log.d(TAG, "Sending remove request: intent=" + intent); 1141 route.sendControlRequest(intent, callback); 1142 } else { 1143 Log.d(TAG, "Remove request not supported!"); 1144 } 1145 } 1146 1147 @Override 1148 public void seek(String sid, String iid, long pos) { 1149 MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); 1150 Intent intent = makeSeekIntent(sid, iid, pos); 1151 if (route.supportsControlRequest(intent)) { 1152 MediaRouter.ControlRequestCallback callback = 1153 new MediaRouter.ControlRequestCallback() { 1154 @Override 1155 public void onResult(Bundle data) { 1156 MediaItemStatus status = MediaItemStatus.fromBundle( 1157 data.getBundle(MediaControlIntent.EXTRA_ITEM_STATUS)); 1158 Log.d(TAG, "Seek request succeeded: status=" + status.toString()); 1159 } 1160 1161 @Override 1162 public void onError(String error, Bundle data) { 1163 Log.d(TAG, "Seek request failed: error=" + error + ", data=" + data); 1164 } 1165 }; 1166 1167 Log.d(TAG, "Sending seek request: intent=" + intent); 1168 route.sendControlRequest(intent, callback); 1169 } else { 1170 Log.d(TAG, "Seek request not supported!"); 1171 } 1172 } 1173 1174 @Override 1175 public void getStatus(final MediaQueueItem item, final boolean update) { 1176 MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); 1177 Intent intent = makeGetStatusIntent(item); 1178 if (route.supportsControlRequest(intent)) { 1179 MediaRouter.ControlRequestCallback callback = 1180 new MediaRouter.ControlRequestCallback() { 1181 @Override 1182 public void onResult(Bundle data) { 1183 if (data != null) { 1184 String sid = data.getString(MediaControlIntent.EXTRA_SESSION_ID); 1185 String iid = data.getString(MediaControlIntent.EXTRA_ITEM_ID); 1186 MediaItemStatus status = MediaItemStatus.fromBundle( 1187 data.getBundle(MediaControlIntent.EXTRA_ITEM_STATUS)); 1188 Log.d(TAG, "GetStatus request succeeded: status=" + status.toString()); 1189 //showToast("GetStatus request succeeded " + item.mName); 1190 if (isRemoteQueue()) { 1191 int state = status.getPlaybackState(); 1192 if (state == MediaItemStatus.PLAYBACK_STATE_PLAYING 1193 || state == MediaItemStatus.PLAYBACK_STATE_PAUSED 1194 || state == MediaItemStatus.PLAYBACK_STATE_PENDING) { 1195 item.setState(state); 1196 item.setContentPosition(status.getContentPosition()); 1197 item.setContentDuration(status.getContentDuration()); 1198 mLastStatusTime = status.getTimestamp(); 1199 mPlayListItems.add(item); 1200 mPlayListView.invalidate(); 1201 // update buttons as the queue count might have changed 1202 if (update) { 1203 clearContent(); 1204 updateButtons(); 1205 } 1206 } 1207 } else { 1208 if (mPlaylistItem != null) { 1209 mPlaylistItem.setContentPosition(status.getContentPosition()); 1210 mPlaylistItem.setContentDuration(status.getContentDuration()); 1211 mPlaylistItem = null; 1212 updateButtons(); 1213 } 1214 } 1215 } 1216 } 1217 1218 @Override 1219 public void onError(String error, Bundle data) { 1220 Log.d(TAG, "GetStatus request failed: error=" + error + ", data=" + data); 1221 //showToast("Unable to get status "); 1222 if (isRemoteQueue()) { 1223 if (update) { 1224 clearContent(); 1225 updateButtons(); 1226 } 1227 } 1228 } 1229 }; 1230 1231 Log.d(TAG, "Sending GetStatus request: intent=" + intent); 1232 route.sendControlRequest(intent, callback); 1233 } else { 1234 Log.d(TAG, "GetStatus request not supported!"); 1235 } 1236 } 1237 1238 @Override 1239 public void pause() { 1240 Intent intent = makePauseIntent(); 1241 MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); 1242 if (route.supportsControlRequest(intent)) { 1243 MediaRouter.ControlRequestCallback callback = 1244 new MediaRouter.ControlRequestCallback() { 1245 @Override 1246 public void onResult(Bundle data) { 1247 Log.d(TAG, "Pause request succeeded"); 1248 if (isRemoteQueue()) { 1249 mPaused = true; 1250 updateUi(); 1251 } 1252 } 1253 1254 @Override 1255 public void onError(String error, Bundle data) { 1256 Log.d(TAG, "Pause request failed: error=" + error); 1257 } 1258 }; 1259 1260 Log.d(TAG, "Sending pause request"); 1261 route.sendControlRequest(intent, callback); 1262 } else { 1263 Log.d(TAG, "Pause request not supported!"); 1264 } 1265 } 1266 1267 @Override 1268 public void resume() { 1269 Intent intent = makeResumeIntent(); 1270 MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); 1271 if (route.supportsControlRequest(intent)) { 1272 MediaRouter.ControlRequestCallback callback = 1273 new MediaRouter.ControlRequestCallback() { 1274 @Override 1275 public void onResult(Bundle data) { 1276 Log.d(TAG, "Resume request succeeded"); 1277 if (isRemoteQueue()) { 1278 mPaused = false; 1279 updateUi(); 1280 } 1281 } 1282 1283 @Override 1284 public void onError(String error, Bundle data) { 1285 Log.d(TAG, "Resume request failed: error=" + error); 1286 } 1287 }; 1288 1289 Log.d(TAG, "Sending resume request"); 1290 route.sendControlRequest(intent, callback); 1291 } else { 1292 Log.d(TAG, "Resume request not supported!"); 1293 } 1294 } 1295 1296 @Override 1297 public void stop() { 1298 Intent intent = makeStopIntent(); 1299 MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); 1300 if (route.supportsControlRequest(intent)) { 1301 MediaRouter.ControlRequestCallback callback = 1302 new MediaRouter.ControlRequestCallback() { 1303 @Override 1304 public void onResult(Bundle data) { 1305 Log.d(TAG, "Stop request succeeded"); 1306 if (isRemoteQueue()) { 1307 // Reset mSessionId, so that next Play/Enqueue 1308 // starts a new session 1309 mQueueItem = null; 1310 mSessionId = null; 1311 mPaused = false; 1312 updateUi(); 1313 } 1314 } 1315 1316 @Override 1317 public void onError(String error, Bundle data) { 1318 Log.d(TAG, "Stop request failed: error=" + error); 1319 } 1320 }; 1321 1322 Log.d(TAG, "Sending stop request"); 1323 route.sendControlRequest(intent, callback); 1324 } else { 1325 Log.d(TAG, "Stop request not supported!"); 1326 } 1327 } 1328 1329 @Override 1330 public void showStatistics() { 1331 MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); 1332 Intent intent = makeStatisticsIntent(); 1333 if (route.supportsControlRequest(intent)) { 1334 MediaRouter.ControlRequestCallback callback = 1335 new MediaRouter.ControlRequestCallback() { 1336 @Override 1337 public void onResult(Bundle data) { 1338 Log.d(TAG, "Statistics request succeeded: data=" + data); 1339 if (data != null) { 1340 int playbackCount = data.getInt( 1341 SampleMediaRouteProvider.DATA_PLAYBACK_COUNT, -1); 1342 mStatsInfo = "Total playback count: " + playbackCount; 1343 } else { 1344 showToast("Statistics query did not return any data"); 1345 } 1346 updateRouteDescription(); 1347 } 1348 1349 @Override 1350 public void onError(String error, Bundle data) { 1351 Log.d(TAG, "Statistics request failed: error=" + error + ", data=" + data); 1352 showToast("Unable to query statistics, error: " + error); 1353 updateRouteDescription(); 1354 } 1355 }; 1356 1357 Log.d(TAG, "Sent statistics request: intent=" + intent); 1358 route.sendControlRequest(intent, callback); 1359 } else { 1360 Log.d(TAG, "Statistics request not supported!"); 1361 } 1362 1363 } 1364 1365 @Override 1366 public void onFinish(boolean error) { 1367 updateUi(); 1368 } 1369 1370 @Override 1371 public void updateSize(int width, int height) { 1372 // nothing to do 1373 } 1374 1375 private void play(final Uri uri, boolean enqueue, final long pos) { 1376 // save the initial seek position 1377 mPosition = pos; 1378 MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(); 1379 Intent intent = makePlayIntent(uri, enqueue); 1380 final String request = enqueue ? "Enqueue" : "Play"; 1381 if (route.supportsControlRequest(intent)) { 1382 MediaRouter.ControlRequestCallback callback = 1383 new MediaRouter.ControlRequestCallback() { 1384 @Override 1385 public void onResult(Bundle data) { 1386 if (data != null) { 1387 String sid = data.getString(MediaControlIntent.EXTRA_SESSION_ID); 1388 String iid = data.getString(MediaControlIntent.EXTRA_ITEM_ID); 1389 MediaItemStatus status = MediaItemStatus.fromBundle( 1390 data.getBundle(MediaControlIntent.EXTRA_ITEM_STATUS)); 1391 Log.d(TAG, request + " request succeeded: data=" + data + 1392 ", sid=" + sid + ", iid=" + iid); 1393 1394 // perform delayed initial seek 1395 if (mSessionId == null && mPosition > 0) { 1396 seek(sid, iid, mPosition); 1397 } 1398 1399 mSessionId = sid; 1400 mItemId = iid; 1401 mQueueItem = new MediaQueueItem(sid, iid, null, null); 1402 1403 if (isRemoteQueue()) { 1404 MediaQueueItem playlistItem = 1405 new MediaQueueItem(sid, iid, uri, null); 1406 playlistItem.setState(status.getPlaybackState()); 1407 mPlayListItems.add(playlistItem); 1408 updateUi(); 1409 enqueuePlaylist(); 1410 } 1411 } 1412 } 1413 1414 @Override 1415 public void onError(String error, Bundle data) { 1416 Log.d(TAG, request + " request failed: error=" + error + ", data=" + data); 1417 showToast("Unable to " + request + uri + ", error: " + error); 1418 } 1419 }; 1420 1421 Log.d(TAG, "Sending " + request + " request: intent=" + intent); 1422 route.sendControlRequest(intent, callback); 1423 } else { 1424 Log.d(TAG, request + " request not supported!"); 1425 } 1426 } 1427 1428 private Intent makePlayIntent(Uri uri, boolean enqueue) { 1429 Intent intent = new Intent( 1430 enqueue ? MediaControlIntent.ACTION_ENQUEUE 1431 : MediaControlIntent.ACTION_PLAY); 1432 intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); 1433 intent.setDataAndType(uri, "video/mp4"); 1434 1435 // Provide a valid session id, or none (which starts a new session) 1436 if (mSessionId != null) { 1437 intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId); 1438 } 1439 1440 // PendingIntent for receiving status update from MRP 1441 Intent statusIntent = new Intent(SampleMediaRouterActivity.ACTION_STATUS_CHANGE); 1442 intent.putExtra(MediaControlIntent.EXTRA_ITEM_STATUS_UPDATE_RECEIVER, 1443 PendingIntent.getBroadcast(SampleMediaRouterActivity.this, 1444 0, statusIntent, 0)); 1445 1446 return intent; 1447 } 1448 1449 private Intent makeRemoveIntent(MediaQueueItem item) { 1450 Intent intent = new Intent(MediaControlIntent.ACTION_REMOVE); 1451 intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); 1452 intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId()); 1453 intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId()); 1454 return intent; 1455 } 1456 1457 private Intent makeSeekIntent(String sid, String iid, long pos) { 1458 Intent intent = new Intent(MediaControlIntent.ACTION_SEEK); 1459 intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); 1460 intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, sid); 1461 intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, iid); 1462 intent.putExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, pos); 1463 return intent; 1464 } 1465 1466 private Intent makePauseIntent() { 1467 Intent intent = new Intent(MediaControlIntent.ACTION_PAUSE); 1468 intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); 1469 if (mSessionId != null) { 1470 intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId); 1471 } 1472 return intent; 1473 } 1474 1475 private Intent makeResumeIntent() { 1476 Intent intent = new Intent(MediaControlIntent.ACTION_RESUME); 1477 intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); 1478 if (mSessionId != null) { 1479 intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId); 1480 } 1481 return intent; 1482 } 1483 1484 private Intent makeStopIntent() { 1485 Intent intent = new Intent(MediaControlIntent.ACTION_STOP); 1486 intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); 1487 if (mSessionId != null) { 1488 intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId); 1489 } 1490 return intent; 1491 } 1492 1493 private Intent makeGetStatusIntent(MediaQueueItem item) { 1494 Intent intent = new Intent(MediaControlIntent.ACTION_GET_STATUS); 1495 intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); 1496 intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId()); 1497 intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId()); 1498 return intent; 1499 } 1500 1501 private Intent makeStatisticsIntent() { 1502 Intent intent = new Intent(SampleMediaRouteProvider.ACTION_GET_STATISTICS); 1503 intent.addCategory(SampleMediaRouteProvider.CATEGORY_SAMPLE_ROUTE); 1504 return intent; 1505 } 1506 } 1507 1508 public static final class DiscoveryFragment extends MediaRouteDiscoveryFragment { 1509 private static final String TAG = "DiscoveryFragment"; 1510 private Callback mCallback; 1511 1512 public DiscoveryFragment() { 1513 mCallback = null; 1514 } 1515 1516 public DiscoveryFragment(Callback cb) { 1517 mCallback = cb; 1518 } 1519 1520 public void setCallback(Callback cb) { 1521 mCallback = cb; 1522 } 1523 1524 @Override 1525 public Callback onCreateCallback() { 1526 return mCallback; 1527 } 1528 1529 @Override 1530 public int onPrepareCallbackFlags() { 1531 // Add the CALLBACK_FLAG_UNFILTERED_EVENTS flag to ensure that we will 1532 // observe and log all route events including those that are for routes 1533 // that do not match our selector. This is only for demonstration purposes 1534 // and should not be needed by most applications. 1535 return super.onPrepareCallbackFlags() 1536 | MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS; 1537 } 1538 } 1539 1540 private static final class MediaItem { 1541 public final String mName; 1542 public final Uri mUri; 1543 1544 public MediaItem(String name, Uri uri) { 1545 mName = name; 1546 mUri = uri; 1547 } 1548 1549 @Override 1550 public String toString() { 1551 return mName; 1552 } 1553 } 1554 1555 private final class LibraryAdapter extends ArrayAdapter<MediaItem> { 1556 public LibraryAdapter() { 1557 super(SampleMediaRouterActivity.this, R.layout.media_item); 1558 } 1559 1560 @Override 1561 public View getView(int position, View convertView, ViewGroup parent) { 1562 final View v; 1563 if (convertView == null) { 1564 v = getLayoutInflater().inflate(R.layout.media_item, null); 1565 } else { 1566 v = convertView; 1567 } 1568 1569 final MediaItem item = getItem(position); 1570 1571 TextView tv = (TextView)v.findViewById(R.id.item_text); 1572 tv.setText(item.mName); 1573 1574 ImageButton b = (ImageButton)v.findViewById(R.id.item_action); 1575 b.setImageResource(R.drawable.ic_menu_add); 1576 b.setTag(item); 1577 b.setOnClickListener(new OnClickListener() { 1578 @Override 1579 public void onClick(View v) { 1580 if (item != null) { 1581 mPlayer.enqueue(item.mUri, 0); 1582 } 1583 } 1584 }); 1585 1586 return v; 1587 } 1588 } 1589 1590 private final class PlaylistAdapter extends ArrayAdapter<MediaQueueItem> { 1591 public PlaylistAdapter() { 1592 super(SampleMediaRouterActivity.this, R.layout.media_item); 1593 } 1594 1595 @Override 1596 public View getView(int position, View convertView, ViewGroup parent) { 1597 final View v; 1598 if (convertView == null) { 1599 v = getLayoutInflater().inflate(R.layout.media_item, null); 1600 } else { 1601 v = convertView; 1602 } 1603 1604 final MediaQueueItem item = getItem(position); 1605 1606 TextView tv = (TextView)v.findViewById(R.id.item_text); 1607 tv.setText(item.toString()); 1608 1609 ImageButton b = (ImageButton)v.findViewById(R.id.item_action); 1610 b.setImageResource(R.drawable.ic_menu_delete); 1611 b.setTag(item); 1612 b.setOnClickListener(new OnClickListener() { 1613 @Override 1614 public void onClick(View v) { 1615 if (item != null) { 1616 mPlayer.remove(item); 1617 } 1618 } 1619 }); 1620 1621 return v; 1622 } 1623 } 1624 1625 /** 1626 * Trivial subclass of this activity used to provide another copy of the 1627 * same activity using a light theme instead of the dark theme. 1628 */ 1629 public static class Light extends SampleMediaRouterActivity { 1630 } 1631 1632 /** 1633 * Trivial subclass of this activity used to provide another copy of the 1634 * same activity using a light theme with dark action bar instead of the dark theme. 1635 */ 1636 public static class LightWithDarkActionBar extends SampleMediaRouterActivity { 1637 } 1638} 1639