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