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 17 package com.example.android.supportv7.media; 18 19 import android.app.Activity; 20 import android.app.Presentation; 21 import android.content.Context; 22 import android.content.DialogInterface; 23 import android.content.res.Resources; 24 import android.graphics.Bitmap; 25 import android.media.MediaPlayer; 26 import android.net.Uri; 27 import android.os.Build; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.os.SystemClock; 31 import android.support.v4.media.session.MediaSessionCompat; 32 import android.support.v7.media.MediaRouter.RouteInfo; 33 import android.support.v7.media.MediaItemStatus; 34 import android.util.Log; 35 import android.view.Display; 36 import android.view.Gravity; 37 import android.view.Surface; 38 import android.view.SurfaceHolder; 39 import android.view.SurfaceView; 40 import android.view.View; 41 import android.view.ViewGroup; 42 import android.view.WindowManager; 43 import android.widget.FrameLayout; 44 45 import com.example.android.supportv7.R; 46 47 import java.io.IOException; 48 49 /** 50 * Handles playback of a single media item using MediaPlayer. 51 */ 52 public abstract class LocalPlayer extends Player implements 53 MediaPlayer.OnPreparedListener, 54 MediaPlayer.OnCompletionListener, 55 MediaPlayer.OnErrorListener, 56 MediaPlayer.OnSeekCompleteListener { 57 private static final String TAG = "LocalPlayer"; 58 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 59 60 private final Context mContext; 61 private final Handler mHandler = new Handler(); 62 private MediaPlayer mMediaPlayer; 63 private int mState = STATE_IDLE; 64 private int mSeekToPos; 65 private int mVideoWidth; 66 private int mVideoHeight; 67 private Surface mSurface; 68 private SurfaceHolder mSurfaceHolder; 69 70 public LocalPlayer(Context context) { 71 mContext = context; 72 73 // reset media player 74 reset(); 75 } 76 77 @Override 78 public boolean isRemotePlayback() { 79 return false; 80 } 81 82 @Override 83 public boolean isQueuingSupported() { 84 return false; 85 } 86 87 @Override 88 public void connect(RouteInfo route) { 89 if (DEBUG) { 90 Log.d(TAG, "connecting to: " + route); 91 } 92 } 93 94 @Override 95 public void release() { 96 if (DEBUG) { 97 Log.d(TAG, "releasing"); 98 } 99 // release media player 100 if (mMediaPlayer != null) { 101 mMediaPlayer.stop(); 102 mMediaPlayer.release(); 103 mMediaPlayer = null; 104 } 105 } 106 107 @Override 108 public MediaSessionCompat getMediaSession() { 109 return mMediaSession; 110 } 111 112 // Player 113 @Override 114 public void play(final PlaylistItem item) { 115 if (DEBUG) { 116 Log.d(TAG, "play: item=" + item); 117 } 118 reset(); 119 mSeekToPos = (int)item.getPosition(); 120 try { 121 mMediaPlayer.setDataSource(mContext, item.getUri()); 122 mMediaPlayer.prepareAsync(); 123 } catch (IllegalStateException e) { 124 Log.e(TAG, "MediaPlayer throws IllegalStateException, uri=" + item.getUri()); 125 } catch (IOException e) { 126 Log.e(TAG, "MediaPlayer throws IOException, uri=" + item.getUri()); 127 } catch (IllegalArgumentException e) { 128 Log.e(TAG, "MediaPlayer throws IllegalArgumentException, uri=" + item.getUri()); 129 } catch (SecurityException e) { 130 Log.e(TAG, "MediaPlayer throws SecurityException, uri=" + item.getUri()); 131 } 132 if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) { 133 resume(); 134 } else { 135 pause(); 136 } 137 } 138 139 @Override 140 public void seek(final PlaylistItem item) { 141 if (DEBUG) { 142 Log.d(TAG, "seek: item=" + item); 143 } 144 int pos = (int)item.getPosition(); 145 if (mState == STATE_PLAYING || mState == STATE_PAUSED) { 146 mMediaPlayer.seekTo(pos); 147 mSeekToPos = pos; 148 } else if (mState == STATE_IDLE || mState == STATE_PLAY_PENDING) { 149 // Seek before onPrepared() arrives, 150 // need to performed delayed seek in onPrepared() 151 mSeekToPos = pos; 152 } 153 } 154 155 @Override 156 public void getStatus(final PlaylistItem item, final boolean update) { 157 if (mState == STATE_PLAYING || mState == STATE_PAUSED) { 158 // use mSeekToPos if we're currently seeking (mSeekToPos is reset 159 // when seeking is completed) 160 item.setDuration(mMediaPlayer.getDuration()); 161 item.setPosition(mSeekToPos > 0 ? 162 mSeekToPos : mMediaPlayer.getCurrentPosition()); 163 item.setTimestamp(SystemClock.elapsedRealtime()); 164 } 165 if (update && mCallback != null) { 166 mCallback.onPlaylistReady(); 167 } 168 } 169 170 @Override 171 public void pause() { 172 if (DEBUG) { 173 Log.d(TAG, "pause"); 174 } 175 if (mState == STATE_PLAYING) { 176 mMediaPlayer.pause(); 177 mState = STATE_PAUSED; 178 } 179 } 180 181 @Override 182 public void resume() { 183 if (DEBUG) { 184 Log.d(TAG, "resume"); 185 } 186 if (mState == STATE_READY || mState == STATE_PAUSED) { 187 mMediaPlayer.start(); 188 mState = STATE_PLAYING; 189 } else if (mState == STATE_IDLE){ 190 mState = STATE_PLAY_PENDING; 191 } 192 } 193 194 @Override 195 public void stop() { 196 if (DEBUG) { 197 Log.d(TAG, "stop"); 198 } 199 if (mState == STATE_PLAYING || mState == STATE_PAUSED) { 200 mMediaPlayer.stop(); 201 mState = STATE_IDLE; 202 } 203 } 204 205 @Override 206 public void enqueue(final PlaylistItem item) { 207 throw new UnsupportedOperationException("LocalPlayer doesn't support enqueue!"); 208 } 209 210 @Override 211 public PlaylistItem remove(String iid) { 212 throw new UnsupportedOperationException("LocalPlayer doesn't support remove!"); 213 } 214 215 //MediaPlayer Listeners 216 @Override 217 public void onPrepared(MediaPlayer mp) { 218 if (DEBUG) { 219 Log.d(TAG, "onPrepared"); 220 } 221 mHandler.post(new Runnable() { 222 @Override 223 public void run() { 224 if (mState == STATE_IDLE) { 225 mState = STATE_READY; 226 updateVideoRect(); 227 } else if (mState == STATE_PLAY_PENDING) { 228 mState = STATE_PLAYING; 229 updateVideoRect(); 230 if (mSeekToPos > 0) { 231 if (DEBUG) { 232 Log.d(TAG, "seek to initial pos: " + mSeekToPos); 233 } 234 mMediaPlayer.seekTo(mSeekToPos); 235 } 236 mMediaPlayer.start(); 237 } 238 if (mCallback != null) { 239 mCallback.onPlaylistChanged(); 240 } 241 } 242 }); 243 } 244 245 @Override 246 public void onCompletion(MediaPlayer mp) { 247 if (DEBUG) { 248 Log.d(TAG, "onCompletion"); 249 } 250 mHandler.post(new Runnable() { 251 @Override 252 public void run() { 253 if (mCallback != null) { 254 mCallback.onCompletion(); 255 } 256 } 257 }); 258 } 259 260 @Override 261 public boolean onError(MediaPlayer mp, int what, int extra) { 262 if (DEBUG) { 263 Log.d(TAG, "onError"); 264 } 265 mHandler.post(new Runnable() { 266 @Override 267 public void run() { 268 if (mCallback != null) { 269 mCallback.onError(); 270 } 271 } 272 }); 273 // return true so that onCompletion is not called 274 return true; 275 } 276 277 @Override 278 public void onSeekComplete(MediaPlayer mp) { 279 if (DEBUG) { 280 Log.d(TAG, "onSeekComplete"); 281 } 282 mHandler.post(new Runnable() { 283 @Override 284 public void run() { 285 mSeekToPos = 0; 286 if (mCallback != null) { 287 mCallback.onPlaylistChanged(); 288 } 289 } 290 }); 291 } 292 293 protected Context getContext() { return mContext; } 294 protected MediaPlayer getMediaPlayer() { return mMediaPlayer; } 295 protected int getVideoWidth() { return mVideoWidth; } 296 protected int getVideoHeight() { return mVideoHeight; } 297 protected void setSurface(Surface surface) { 298 mSurface = surface; 299 mSurfaceHolder = null; 300 updateSurface(); 301 } 302 303 protected void setSurface(SurfaceHolder surfaceHolder) { 304 mSurface = null; 305 mSurfaceHolder = surfaceHolder; 306 updateSurface(); 307 } 308 309 protected void removeSurface(SurfaceHolder surfaceHolder) { 310 if (surfaceHolder == mSurfaceHolder) { 311 setSurface((SurfaceHolder)null); 312 } 313 } 314 315 protected void updateSurface() { 316 if (mMediaPlayer == null) { 317 // just return if media player is already gone 318 return; 319 } 320 if (mSurface != null) { 321 // The setSurface API does not exist until V14+. 322 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 323 ICSMediaPlayer.setSurface(mMediaPlayer, mSurface); 324 } else { 325 throw new UnsupportedOperationException("MediaPlayer does not support " 326 + "setSurface() on this version of the platform."); 327 } 328 } else if (mSurfaceHolder != null) { 329 mMediaPlayer.setDisplay(mSurfaceHolder); 330 } else { 331 mMediaPlayer.setDisplay(null); 332 } 333 } 334 335 protected abstract void updateSize(); 336 337 private void reset() { 338 if (mMediaPlayer != null) { 339 mMediaPlayer.stop(); 340 mMediaPlayer.release(); 341 mMediaPlayer = null; 342 } 343 mMediaPlayer = new MediaPlayer(); 344 mMediaPlayer.setOnPreparedListener(this); 345 mMediaPlayer.setOnCompletionListener(this); 346 mMediaPlayer.setOnErrorListener(this); 347 mMediaPlayer.setOnSeekCompleteListener(this); 348 updateSurface(); 349 mState = STATE_IDLE; 350 mSeekToPos = 0; 351 } 352 353 private void updateVideoRect() { 354 if (mState != STATE_IDLE && mState != STATE_PLAY_PENDING) { 355 int width = mMediaPlayer.getVideoWidth(); 356 int height = mMediaPlayer.getVideoHeight(); 357 if (width > 0 && height > 0) { 358 mVideoWidth = width; 359 mVideoHeight = height; 360 updateSize(); 361 } else { 362 Log.e(TAG, "video rect is 0x0!"); 363 mVideoWidth = mVideoHeight = 0; 364 } 365 } 366 } 367 368 private static final class ICSMediaPlayer { 369 public static final void setSurface(MediaPlayer player, Surface surface) { 370 player.setSurface(surface); 371 } 372 } 373 374 /** 375 * Handles playback of a single media item using MediaPlayer in SurfaceView 376 */ 377 public static class SurfaceViewPlayer extends LocalPlayer implements 378 SurfaceHolder.Callback { 379 private static final String TAG = "SurfaceViewPlayer"; 380 private RouteInfo mRoute; 381 private final SurfaceView mSurfaceView; 382 private final FrameLayout mLayout; 383 private DemoPresentation mPresentation; 384 385 public SurfaceViewPlayer(Context context) { 386 super(context); 387 388 mLayout = (FrameLayout)((Activity)context).findViewById(R.id.player); 389 mSurfaceView = (SurfaceView)((Activity)context).findViewById(R.id.surface_view); 390 391 // add surface holder callback 392 SurfaceHolder holder = mSurfaceView.getHolder(); 393 holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 394 holder.addCallback(this); 395 } 396 397 @Override 398 public void connect(RouteInfo route) { 399 super.connect(route); 400 mRoute = route; 401 } 402 403 @Override 404 public void release() { 405 super.release(); 406 407 // dismiss presentation display 408 if (mPresentation != null) { 409 Log.i(TAG, "Dismissing presentation because the activity is no longer visible."); 410 mPresentation.dismiss(); 411 mPresentation = null; 412 } 413 414 // remove surface holder callback 415 SurfaceHolder holder = mSurfaceView.getHolder(); 416 holder.removeCallback(this); 417 418 // hide the surface view when SurfaceViewPlayer is destroyed 419 mSurfaceView.setVisibility(View.GONE); 420 mLayout.setVisibility(View.GONE); 421 } 422 423 @Override 424 public void updatePresentation() { 425 // Get the current route and its presentation display. 426 Display presentationDisplay = mRoute != null ? mRoute.getPresentationDisplay() : null; 427 428 // Dismiss the current presentation if the display has changed. 429 if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) { 430 Log.i(TAG, "Dismissing presentation because the current route no longer " 431 + "has a presentation display."); 432 mPresentation.dismiss(); 433 mPresentation = null; 434 } 435 436 // Show a new presentation if needed. 437 if (mPresentation == null && presentationDisplay != null) { 438 Log.i(TAG, "Showing presentation on display: " + presentationDisplay); 439 mPresentation = new DemoPresentation(getContext(), presentationDisplay); 440 mPresentation.setOnDismissListener(mOnDismissListener); 441 try { 442 mPresentation.show(); 443 } catch (WindowManager.InvalidDisplayException ex) { 444 Log.w(TAG, "Couldn't show presentation! Display was removed in " 445 + "the meantime.", ex); 446 mPresentation = null; 447 } 448 } 449 450 updateContents(); 451 } 452 453 // SurfaceHolder.Callback 454 @Override 455 public void surfaceChanged(SurfaceHolder holder, int format, 456 int width, int height) { 457 if (DEBUG) { 458 Log.d(TAG, "surfaceChanged: " + width + "x" + height); 459 } 460 setSurface(holder); 461 } 462 463 @Override 464 public void surfaceCreated(SurfaceHolder holder) { 465 if (DEBUG) { 466 Log.d(TAG, "surfaceCreated"); 467 } 468 setSurface(holder); 469 updateSize(); 470 } 471 472 @Override 473 public void surfaceDestroyed(SurfaceHolder holder) { 474 if (DEBUG) { 475 Log.d(TAG, "surfaceDestroyed"); 476 } 477 removeSurface(holder); 478 } 479 480 @Override 481 protected void updateSize() { 482 int width = getVideoWidth(); 483 int height = getVideoHeight(); 484 if (width > 0 && height > 0) { 485 if (mPresentation == null) { 486 int surfaceWidth = mLayout.getWidth(); 487 int surfaceHeight = mLayout.getHeight(); 488 489 // Calculate the new size of mSurfaceView, so that video is centered 490 // inside the framelayout with proper letterboxing/pillarboxing 491 ViewGroup.LayoutParams lp = mSurfaceView.getLayoutParams(); 492 if (surfaceWidth * height < surfaceHeight * width) { 493 // Black bars on top&bottom, mSurfaceView has full layout width, 494 // while height is derived from video's aspect ratio 495 lp.width = surfaceWidth; 496 lp.height = surfaceWidth * height / width; 497 } else { 498 // Black bars on left&right, mSurfaceView has full layout height, 499 // while width is derived from video's aspect ratio 500 lp.width = surfaceHeight * width / height; 501 lp.height = surfaceHeight; 502 } 503 Log.i(TAG, "video rect is " + lp.width + "x" + lp.height); 504 mSurfaceView.setLayoutParams(lp); 505 } else { 506 mPresentation.updateSize(width, height); 507 } 508 } 509 } 510 511 private void updateContents() { 512 // Show either the content in the main activity or the content in the presentation 513 if (mPresentation != null) { 514 mLayout.setVisibility(View.GONE); 515 mSurfaceView.setVisibility(View.GONE); 516 } else { 517 mLayout.setVisibility(View.VISIBLE); 518 mSurfaceView.setVisibility(View.VISIBLE); 519 } 520 } 521 522 // Listens for when presentations are dismissed. 523 private final DialogInterface.OnDismissListener mOnDismissListener = 524 new DialogInterface.OnDismissListener() { 525 @Override 526 public void onDismiss(DialogInterface dialog) { 527 if (dialog == mPresentation) { 528 Log.i(TAG, "Presentation dismissed."); 529 mPresentation = null; 530 updateContents(); 531 } 532 } 533 }; 534 535 // Presentation 536 private final class DemoPresentation extends Presentation { 537 private SurfaceView mPresentationSurfaceView; 538 539 public DemoPresentation(Context context, Display display) { 540 super(context, display); 541 } 542 543 @Override 544 protected void onCreate(Bundle savedInstanceState) { 545 // Be sure to call the super class. 546 super.onCreate(savedInstanceState); 547 548 // Inflate the layout. 549 setContentView(R.layout.sample_media_router_presentation); 550 551 // Set up the surface view. 552 mPresentationSurfaceView = (SurfaceView)findViewById(R.id.surface_view); 553 SurfaceHolder holder = mPresentationSurfaceView.getHolder(); 554 holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 555 holder.addCallback(SurfaceViewPlayer.this); 556 Log.i(TAG, "Presentation created"); 557 } 558 559 public void updateSize(int width, int height) { 560 int surfaceHeight = getWindow().getDecorView().getHeight(); 561 int surfaceWidth = getWindow().getDecorView().getWidth(); 562 ViewGroup.LayoutParams lp = mPresentationSurfaceView.getLayoutParams(); 563 if (surfaceWidth * height < surfaceHeight * width) { 564 lp.width = surfaceWidth; 565 lp.height = surfaceWidth * height / width; 566 } else { 567 lp.width = surfaceHeight * width / height; 568 lp.height = surfaceHeight; 569 } 570 Log.i(TAG, "Presentation video rect is " + lp.width + "x" + lp.height); 571 mPresentationSurfaceView.setLayoutParams(lp); 572 } 573 } 574 } 575 576 /** 577 * Handles playback of a single media item using MediaPlayer in 578 * OverlayDisplayWindow. 579 */ 580 public static class OverlayPlayer extends LocalPlayer implements 581 OverlayDisplayWindow.OverlayWindowListener { 582 private static final String TAG = "OverlayPlayer"; 583 private final OverlayDisplayWindow mOverlay; 584 585 public OverlayPlayer(Context context) { 586 super(context); 587 588 mOverlay = OverlayDisplayWindow.create(getContext(), 589 getContext().getResources().getString( 590 R.string.sample_media_route_provider_remote), 591 1024, 768, Gravity.CENTER); 592 593 mOverlay.setOverlayWindowListener(this); 594 } 595 596 @Override 597 public void connect(RouteInfo route) { 598 super.connect(route); 599 mOverlay.show(); 600 } 601 602 @Override 603 public void release() { 604 super.release(); 605 mOverlay.dismiss(); 606 } 607 608 @Override 609 protected void updateSize() { 610 int width = getVideoWidth(); 611 int height = getVideoHeight(); 612 if (width > 0 && height > 0) { 613 mOverlay.updateAspectRatio(width, height); 614 } 615 } 616 617 // OverlayDisplayWindow.OverlayWindowListener 618 @Override 619 public void onWindowCreated(Surface surface) { 620 setSurface(surface); 621 } 622 623 @Override 624 public void onWindowCreated(SurfaceHolder surfaceHolder) { 625 setSurface(surfaceHolder); 626 } 627 628 @Override 629 public void onWindowDestroyed() { 630 setSurface((SurfaceHolder)null); 631 } 632 633 @Override 634 public Bitmap getSnapshot() { 635 return mOverlay.getSnapshot(); 636 } 637 } 638 } 639