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