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