1 /* 2 * Copyright 2018 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.android.widget; 18 19 import android.content.Context; 20 import android.content.pm.ActivityInfo; 21 import android.content.res.Resources; 22 import android.graphics.Bitmap; 23 import android.graphics.BitmapFactory; 24 import android.graphics.Point; 25 import android.graphics.drawable.BitmapDrawable; 26 import android.graphics.drawable.Drawable; 27 import android.media.AudioAttributes; 28 import android.media.AudioFocusRequest; 29 import android.media.AudioManager; 30 import android.media.DataSourceDesc; 31 import android.media.MediaMetadata; 32 import android.media.MediaPlayer2; 33 import android.media.MediaPlayer2.MediaPlayer2EventCallback; 34 import android.media.MediaPlayer2.OnSubtitleDataListener; 35 import android.media.MediaPlayer2Impl; 36 import android.media.SubtitleData; 37 import android.media.MediaItem2; 38 import android.media.MediaMetadata2; 39 import android.media.MediaMetadataRetriever; 40 import android.media.Metadata; 41 import android.media.PlaybackParams; 42 import android.media.TimedText; 43 import android.media.session.MediaController; 44 import android.media.session.MediaController.PlaybackInfo; 45 import android.media.session.MediaSession; 46 import android.media.session.PlaybackState; 47 import android.media.SessionToken2; 48 import android.media.update.VideoView2Provider; 49 import android.media.update.ViewGroupProvider; 50 import android.net.Uri; 51 import android.os.AsyncTask; 52 import android.os.Bundle; 53 import android.os.ResultReceiver; 54 import android.support.annotation.Nullable; 55 import android.util.AttributeSet; 56 import android.util.DisplayMetrics; 57 import android.util.Log; 58 import android.util.Pair; 59 import android.view.MotionEvent; 60 import android.view.View; 61 import android.view.ViewGroup.LayoutParams; 62 import android.view.WindowManager; 63 import android.view.accessibility.AccessibilityManager; 64 import android.widget.ImageView; 65 import android.widget.MediaControlView2; 66 import android.widget.TextView; 67 import android.widget.VideoView2; 68 69 import com.android.internal.graphics.palette.Palette; 70 import com.android.media.RoutePlayer; 71 import com.android.media.subtitle.ClosedCaptionRenderer; 72 import com.android.media.subtitle.SubtitleController; 73 import com.android.media.subtitle.SubtitleTrack; 74 import com.android.media.update.ApiHelper; 75 import com.android.media.update.R; 76 import com.android.support.mediarouter.media.MediaItemStatus; 77 import com.android.support.mediarouter.media.MediaControlIntent; 78 import com.android.support.mediarouter.media.MediaRouter; 79 import com.android.support.mediarouter.media.MediaRouteSelector; 80 81 import java.util.ArrayList; 82 import java.util.List; 83 import java.util.Map; 84 import java.util.concurrent.Executor; 85 86 public class VideoView2Impl extends BaseLayout 87 implements VideoView2Provider, VideoViewInterface.SurfaceListener { 88 private static final String TAG = "VideoView2"; 89 private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG); 90 private static final long DEFAULT_SHOW_CONTROLLER_INTERVAL_MS = 2000; 91 92 private final VideoView2 mInstance; 93 94 private static final int STATE_ERROR = -1; 95 private static final int STATE_IDLE = 0; 96 private static final int STATE_PREPARING = 1; 97 private static final int STATE_PREPARED = 2; 98 private static final int STATE_PLAYING = 3; 99 private static final int STATE_PAUSED = 4; 100 private static final int STATE_PLAYBACK_COMPLETED = 5; 101 102 private static final int INVALID_TRACK_INDEX = -1; 103 private static final float INVALID_SPEED = 0f; 104 105 private static final int SIZE_TYPE_EMBEDDED = 0; 106 private static final int SIZE_TYPE_FULL = 1; 107 // TODO: add support for Minimal size type. 108 private static final int SIZE_TYPE_MINIMAL = 2; 109 110 private AccessibilityManager mAccessibilityManager; 111 private AudioManager mAudioManager; 112 private AudioAttributes mAudioAttributes; 113 private int mAudioFocusType = AudioManager.AUDIOFOCUS_GAIN; // legacy focus gain 114 115 private Pair<Executor, VideoView2.OnCustomActionListener> mCustomActionListenerRecord; 116 private VideoView2.OnViewTypeChangedListener mViewTypeChangedListener; 117 private VideoView2.OnFullScreenRequestListener mFullScreenRequestListener; 118 119 private VideoViewInterface mCurrentView; 120 private VideoTextureView mTextureView; 121 private VideoSurfaceView mSurfaceView; 122 123 private MediaPlayer2 mMediaPlayer; 124 private DataSourceDesc mDsd; 125 private MediaControlView2 mMediaControlView; 126 private MediaSession mMediaSession; 127 private MediaController mMediaController; 128 private Metadata mMetadata; 129 private MediaMetadata2 mMediaMetadata; 130 private MediaMetadataRetriever mRetriever; 131 private boolean mNeedUpdateMediaType; 132 private Bundle mMediaTypeData; 133 private String mTitle; 134 135 // TODO: move music view inside SurfaceView/TextureView or implement VideoViewInterface. 136 private WindowManager mManager; 137 private Resources mResources; 138 private View mMusicView; 139 private Drawable mMusicAlbumDrawable; 140 private String mMusicTitleText; 141 private String mMusicArtistText; 142 private boolean mIsMusicMediaType; 143 private int mPrevWidth; 144 private int mPrevHeight; 145 private int mDominantColor; 146 private int mSizeType; 147 148 private PlaybackState.Builder mStateBuilder; 149 private List<PlaybackState.CustomAction> mCustomActionList; 150 private int mTargetState = STATE_IDLE; 151 private int mCurrentState = STATE_IDLE; 152 private int mCurrentBufferPercentage; 153 private long mSeekWhenPrepared; // recording the seek position while preparing 154 155 private int mVideoWidth; 156 private int mVideoHeight; 157 158 private ArrayList<Integer> mVideoTrackIndices; 159 private ArrayList<Integer> mAudioTrackIndices; 160 private ArrayList<Pair<Integer, SubtitleTrack>> mSubtitleTrackIndices; 161 private SubtitleController mSubtitleController; 162 163 // selected video/audio/subtitle track index as MediaPlayer2 returns 164 private int mSelectedVideoTrackIndex; 165 private int mSelectedAudioTrackIndex; 166 private int mSelectedSubtitleTrackIndex; 167 168 private SubtitleView mSubtitleView; 169 private boolean mSubtitleEnabled; 170 171 private float mSpeed; 172 // TODO: Remove mFallbackSpeed when integration with MediaPlayer2's new setPlaybackParams(). 173 // Refer: https://docs.google.com/document/d/1nzAfns6i2hJ3RkaUre3QMT6wsDedJ5ONLiA_OOBFFX8/edit 174 private float mFallbackSpeed; // keep the original speed before 'pause' is called. 175 private float mVolumeLevelFloat; 176 private int mVolumeLevel; 177 178 private long mShowControllerIntervalMs; 179 180 private MediaRouter mMediaRouter; 181 private MediaRouteSelector mRouteSelector; 182 private MediaRouter.RouteInfo mRoute; 183 private RoutePlayer mRoutePlayer; 184 185 private final MediaRouter.Callback mRouterCallback = new MediaRouter.Callback() { 186 @Override 187 public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) { 188 if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) { 189 // Stop local playback (if necessary) 190 resetPlayer(); 191 mRoute = route; 192 mRoutePlayer = new RoutePlayer(mInstance.getContext(), route); 193 mRoutePlayer.setPlayerEventCallback(new RoutePlayer.PlayerEventCallback() { 194 @Override 195 public void onPlayerStateChanged(MediaItemStatus itemStatus) { 196 PlaybackState.Builder psBuilder = new PlaybackState.Builder(); 197 psBuilder.setActions(RoutePlayer.PLAYBACK_ACTIONS); 198 long position = itemStatus.getContentPosition(); 199 switch (itemStatus.getPlaybackState()) { 200 case MediaItemStatus.PLAYBACK_STATE_PENDING: 201 psBuilder.setState(PlaybackState.STATE_NONE, position, 0); 202 mCurrentState = STATE_IDLE; 203 break; 204 case MediaItemStatus.PLAYBACK_STATE_PLAYING: 205 psBuilder.setState(PlaybackState.STATE_PLAYING, position, 1); 206 mCurrentState = STATE_PLAYING; 207 break; 208 case MediaItemStatus.PLAYBACK_STATE_PAUSED: 209 psBuilder.setState(PlaybackState.STATE_PAUSED, position, 0); 210 mCurrentState = STATE_PAUSED; 211 break; 212 case MediaItemStatus.PLAYBACK_STATE_BUFFERING: 213 psBuilder.setState(PlaybackState.STATE_BUFFERING, position, 0); 214 mCurrentState = STATE_PAUSED; 215 break; 216 case MediaItemStatus.PLAYBACK_STATE_FINISHED: 217 psBuilder.setState(PlaybackState.STATE_STOPPED, position, 0); 218 mCurrentState = STATE_PLAYBACK_COMPLETED; 219 break; 220 } 221 222 PlaybackState pbState = psBuilder.build(); 223 mMediaSession.setPlaybackState(pbState); 224 225 MediaMetadata.Builder mmBuilder = new MediaMetadata.Builder(); 226 mmBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION, 227 itemStatus.getContentDuration()); 228 mMediaSession.setMetadata(mmBuilder.build()); 229 } 230 }); 231 // Start remote playback (if necessary) 232 mRoutePlayer.openVideo(mDsd); 233 } 234 } 235 236 @Override 237 public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route, int reason) { 238 if (mRoute != null && mRoutePlayer != null) { 239 mRoutePlayer.release(); 240 mRoutePlayer = null; 241 } 242 if (mRoute == route) { 243 mRoute = null; 244 } 245 if (reason != MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) { 246 // TODO: Resume local playback (if necessary) 247 openVideo(mDsd); 248 } 249 } 250 }; 251 252 public VideoView2Impl(VideoView2 instance, 253 ViewGroupProvider superProvider, ViewGroupProvider privateProvider) { 254 super(instance, superProvider, privateProvider); 255 mInstance = instance; 256 } 257 258 @Override 259 public void initialize(@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 260 mVideoWidth = 0; 261 mVideoHeight = 0; 262 mSpeed = 1.0f; 263 mFallbackSpeed = mSpeed; 264 mSelectedSubtitleTrackIndex = INVALID_TRACK_INDEX; 265 // TODO: add attributes to get this value. 266 mShowControllerIntervalMs = DEFAULT_SHOW_CONTROLLER_INTERVAL_MS; 267 268 mAccessibilityManager = AccessibilityManager.getInstance(mInstance.getContext()); 269 270 mAudioManager = (AudioManager) mInstance.getContext() 271 .getSystemService(Context.AUDIO_SERVICE); 272 mAudioAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA) 273 .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build(); 274 mInstance.setFocusable(true); 275 mInstance.setFocusableInTouchMode(true); 276 mInstance.requestFocus(); 277 278 // TODO: try to keep a single child at a time rather than always having both. 279 mTextureView = new VideoTextureView(mInstance.getContext()); 280 mSurfaceView = new VideoSurfaceView(mInstance.getContext()); 281 LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, 282 LayoutParams.MATCH_PARENT); 283 mTextureView.setLayoutParams(params); 284 mSurfaceView.setLayoutParams(params); 285 mTextureView.setSurfaceListener(this); 286 mSurfaceView.setSurfaceListener(this); 287 mInstance.addView(mTextureView); 288 mInstance.addView(mSurfaceView); 289 290 mSubtitleView = new SubtitleView(mInstance.getContext()); 291 mSubtitleView.setLayoutParams(params); 292 mSubtitleView.setBackgroundColor(0); 293 mInstance.addView(mSubtitleView); 294 295 boolean enableControlView = (attrs == null) || attrs.getAttributeBooleanValue( 296 "http://schemas.android.com/apk/res/android", 297 "enableControlView", true); 298 if (enableControlView) { 299 mMediaControlView = new MediaControlView2(mInstance.getContext()); 300 } 301 302 mSubtitleEnabled = (attrs == null) || attrs.getAttributeBooleanValue( 303 "http://schemas.android.com/apk/res/android", 304 "enableSubtitle", false); 305 306 // TODO: Choose TextureView when SurfaceView cannot be created. 307 // Choose surface view by default 308 int viewType = (attrs == null) ? VideoView2.VIEW_TYPE_SURFACEVIEW 309 : attrs.getAttributeIntValue( 310 "http://schemas.android.com/apk/res/android", 311 "viewType", VideoView2.VIEW_TYPE_SURFACEVIEW); 312 if (viewType == VideoView2.VIEW_TYPE_SURFACEVIEW) { 313 Log.d(TAG, "viewType attribute is surfaceView."); 314 mTextureView.setVisibility(View.GONE); 315 mSurfaceView.setVisibility(View.VISIBLE); 316 mCurrentView = mSurfaceView; 317 } else if (viewType == VideoView2.VIEW_TYPE_TEXTUREVIEW) { 318 Log.d(TAG, "viewType attribute is textureView."); 319 mTextureView.setVisibility(View.VISIBLE); 320 mSurfaceView.setVisibility(View.GONE); 321 mCurrentView = mTextureView; 322 } 323 324 MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder(); 325 builder.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); 326 builder.addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO); 327 builder.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO); 328 mRouteSelector = builder.build(); 329 } 330 331 @Override 332 public void setMediaControlView2_impl(MediaControlView2 mediaControlView, long intervalMs) { 333 mMediaControlView = mediaControlView; 334 mShowControllerIntervalMs = intervalMs; 335 // TODO: Call MediaControlView2.setRouteSelector only when cast availalbe. 336 ((MediaControlView2Impl) mMediaControlView.getProvider()).setRouteSelector(mRouteSelector); 337 338 if (mInstance.isAttachedToWindow()) { 339 attachMediaControlView(); 340 } 341 } 342 343 @Override 344 public MediaController getMediaController_impl() { 345 if (mMediaSession == null) { 346 throw new IllegalStateException("MediaSession instance is not available."); 347 } 348 return mMediaController; 349 } 350 351 @Override 352 public SessionToken2 getMediaSessionToken_impl() { 353 // TODO: implement this 354 return null; 355 } 356 357 @Override 358 public MediaControlView2 getMediaControlView2_impl() { 359 return mMediaControlView; 360 } 361 362 @Override 363 public MediaMetadata2 getMediaMetadata_impl() { 364 return mMediaMetadata; 365 } 366 367 @Override 368 public void setMediaMetadata_impl(MediaMetadata2 metadata) { 369 // TODO: integrate this with MediaSession2#MediaItem2 370 mMediaMetadata = metadata; 371 372 // TODO: add support for handling website link 373 mMediaTypeData = new Bundle(); 374 boolean isAd = metadata == null ? 375 false : metadata.getLong(MediaMetadata2.METADATA_KEY_ADVERTISEMENT) != 0; 376 mMediaTypeData.putBoolean( 377 MediaControlView2Impl.KEY_STATE_IS_ADVERTISEMENT, isAd); 378 379 if (mMediaSession != null) { 380 mMediaSession.sendSessionEvent( 381 MediaControlView2Impl.EVENT_UPDATE_MEDIA_TYPE_STATUS, mMediaTypeData); 382 } else { 383 // Update later inside OnPreparedListener after MediaSession is initialized. 384 mNeedUpdateMediaType = true; 385 } 386 } 387 388 @Override 389 public void setSubtitleEnabled_impl(boolean enable) { 390 if (enable != mSubtitleEnabled) { 391 selectOrDeselectSubtitle(enable); 392 } 393 mSubtitleEnabled = enable; 394 } 395 396 @Override 397 public boolean isSubtitleEnabled_impl() { 398 return mSubtitleEnabled; 399 } 400 401 // TODO: remove setSpeed_impl once MediaController2 is ready. 402 @Override 403 public void setSpeed_impl(float speed) { 404 if (speed <= 0.0f) { 405 Log.e(TAG, "Unsupported speed (" + speed + ") is ignored."); 406 return; 407 } 408 mSpeed = speed; 409 if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { 410 applySpeed(); 411 } 412 updatePlaybackState(); 413 } 414 415 @Override 416 public void setAudioFocusRequest_impl(int focusGain) { 417 if (focusGain != AudioManager.AUDIOFOCUS_NONE 418 && focusGain != AudioManager.AUDIOFOCUS_GAIN 419 && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT 420 && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 421 && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) { 422 throw new IllegalArgumentException("Illegal audio focus type " + focusGain); 423 } 424 mAudioFocusType = focusGain; 425 } 426 427 @Override 428 public void setAudioAttributes_impl(AudioAttributes attributes) { 429 if (attributes == null) { 430 throw new IllegalArgumentException("Illegal null AudioAttributes"); 431 } 432 mAudioAttributes = attributes; 433 } 434 435 @Override 436 public void setVideoPath_impl(String path) { 437 mInstance.setVideoUri(Uri.parse(path)); 438 } 439 440 @Override 441 public void setVideoUri_impl(Uri uri) { 442 mInstance.setVideoUri(uri, null); 443 } 444 445 @Override 446 public void setVideoUri_impl(Uri uri, Map<String, String> headers) { 447 DataSourceDesc.Builder builder = new DataSourceDesc.Builder(); 448 builder.setDataSource(mInstance.getContext(), uri, headers, null); 449 mInstance.setDataSource(builder.build()); 450 } 451 452 @Override 453 public void setMediaItem_impl(MediaItem2 mediaItem) { 454 // TODO: implement this 455 } 456 457 @Override 458 public void setDataSource_impl(DataSourceDesc dsd) { 459 mDsd = dsd; 460 mSeekWhenPrepared = 0; 461 openVideo(dsd); 462 } 463 464 @Override 465 public void setViewType_impl(int viewType) { 466 if (viewType == mCurrentView.getViewType()) { 467 return; 468 } 469 VideoViewInterface targetView; 470 if (viewType == VideoView2.VIEW_TYPE_TEXTUREVIEW) { 471 Log.d(TAG, "switching to TextureView"); 472 targetView = mTextureView; 473 } else if (viewType == VideoView2.VIEW_TYPE_SURFACEVIEW) { 474 Log.d(TAG, "switching to SurfaceView"); 475 targetView = mSurfaceView; 476 } else { 477 throw new IllegalArgumentException("Unknown view type: " + viewType); 478 } 479 ((View) targetView).setVisibility(View.VISIBLE); 480 targetView.takeOver(mCurrentView); 481 mInstance.requestLayout(); 482 } 483 484 @Override 485 public int getViewType_impl() { 486 return mCurrentView.getViewType(); 487 } 488 489 @Override 490 public void setCustomActions_impl( 491 List<PlaybackState.CustomAction> actionList, 492 Executor executor, VideoView2.OnCustomActionListener listener) { 493 mCustomActionList = actionList; 494 mCustomActionListenerRecord = new Pair<>(executor, listener); 495 496 // Create a new playback builder in order to clear existing the custom actions. 497 mStateBuilder = null; 498 updatePlaybackState(); 499 } 500 501 @Override 502 public void setOnViewTypeChangedListener_impl(VideoView2.OnViewTypeChangedListener l) { 503 mViewTypeChangedListener = l; 504 } 505 506 @Override 507 public void setFullScreenRequestListener_impl(VideoView2.OnFullScreenRequestListener l) { 508 mFullScreenRequestListener = l; 509 } 510 511 @Override 512 public void onAttachedToWindow_impl() { 513 super.onAttachedToWindow_impl(); 514 515 // Create MediaSession 516 mMediaSession = new MediaSession(mInstance.getContext(), "VideoView2MediaSession"); 517 mMediaSession.setCallback(new MediaSessionCallback()); 518 mMediaSession.setActive(true); 519 mMediaController = mMediaSession.getController(); 520 mMediaRouter = MediaRouter.getInstance(mInstance.getContext()); 521 mMediaRouter.setMediaSession(mMediaSession); 522 mMediaRouter.addCallback(mRouteSelector, mRouterCallback); 523 attachMediaControlView(); 524 // TODO: remove this after moving MediaSession creating code inside initializing VideoView2 525 if (mCurrentState == STATE_PREPARED) { 526 extractTracks(); 527 extractMetadata(); 528 extractAudioMetadata(); 529 if (mNeedUpdateMediaType) { 530 mMediaSession.sendSessionEvent( 531 MediaControlView2Impl.EVENT_UPDATE_MEDIA_TYPE_STATUS, 532 mMediaTypeData); 533 mNeedUpdateMediaType = false; 534 } 535 } 536 } 537 538 @Override 539 public void onDetachedFromWindow_impl() { 540 super.onDetachedFromWindow_impl(); 541 542 mMediaSession.release(); 543 mMediaSession = null; 544 mMediaController = null; 545 } 546 547 @Override 548 public CharSequence getAccessibilityClassName_impl() { 549 return VideoView2.class.getName(); 550 } 551 552 @Override 553 public boolean onTouchEvent_impl(MotionEvent ev) { 554 if (DEBUG) { 555 Log.d(TAG, "onTouchEvent(). mCurrentState=" + mCurrentState 556 + ", mTargetState=" + mTargetState); 557 } 558 if (ev.getAction() == MotionEvent.ACTION_UP && mMediaControlView != null) { 559 if (!mIsMusicMediaType || mSizeType != SIZE_TYPE_FULL) { 560 toggleMediaControlViewVisibility(); 561 } 562 } 563 564 return super.onTouchEvent_impl(ev); 565 } 566 567 @Override 568 public boolean onTrackballEvent_impl(MotionEvent ev) { 569 if (ev.getAction() == MotionEvent.ACTION_UP && mMediaControlView != null) { 570 if (!mIsMusicMediaType || mSizeType != SIZE_TYPE_FULL) { 571 toggleMediaControlViewVisibility(); 572 } 573 } 574 575 return super.onTrackballEvent_impl(ev); 576 } 577 578 @Override 579 public boolean dispatchTouchEvent_impl(MotionEvent ev) { 580 // TODO: Test touch event handling logic thoroughly and simplify the logic. 581 return super.dispatchTouchEvent_impl(ev); 582 } 583 584 @Override 585 public void onMeasure_impl(int widthMeasureSpec, int heightMeasureSpec) { 586 super.onMeasure_impl(widthMeasureSpec, heightMeasureSpec); 587 588 if (mIsMusicMediaType) { 589 if (mPrevWidth != mInstance.getMeasuredWidth() 590 || mPrevHeight != mInstance.getMeasuredHeight()) { 591 int currWidth = mInstance.getMeasuredWidth(); 592 int currHeight = mInstance.getMeasuredHeight(); 593 Point screenSize = new Point(); 594 mManager.getDefaultDisplay().getSize(screenSize); 595 int screenWidth = screenSize.x; 596 int screenHeight = screenSize.y; 597 598 if (currWidth == screenWidth && currHeight == screenHeight) { 599 int orientation = retrieveOrientation(); 600 if (orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { 601 inflateMusicView(R.layout.full_landscape_music); 602 } else { 603 inflateMusicView(R.layout.full_portrait_music); 604 } 605 606 if (mSizeType != SIZE_TYPE_FULL) { 607 mSizeType = SIZE_TYPE_FULL; 608 // Remove existing mFadeOut callback 609 mMediaControlView.removeCallbacks(mFadeOut); 610 mMediaControlView.setVisibility(View.VISIBLE); 611 } 612 } else { 613 if (mSizeType != SIZE_TYPE_EMBEDDED) { 614 mSizeType = SIZE_TYPE_EMBEDDED; 615 inflateMusicView(R.layout.embedded_music); 616 // Add new mFadeOut callback 617 mMediaControlView.postDelayed(mFadeOut, mShowControllerIntervalMs); 618 } 619 } 620 mPrevWidth = currWidth; 621 mPrevHeight = currHeight; 622 } 623 } 624 } 625 626 /////////////////////////////////////////////////// 627 // Implements VideoViewInterface.SurfaceListener 628 /////////////////////////////////////////////////// 629 630 @Override 631 public void onSurfaceCreated(View view, int width, int height) { 632 if (DEBUG) { 633 Log.d(TAG, "onSurfaceCreated(). mCurrentState=" + mCurrentState 634 + ", mTargetState=" + mTargetState + ", width/height: " + width + "/" + height 635 + ", " + view.toString()); 636 } 637 if (needToStart()) { 638 mMediaController.getTransportControls().play(); 639 } 640 } 641 642 @Override 643 public void onSurfaceDestroyed(View view) { 644 if (DEBUG) { 645 Log.d(TAG, "onSurfaceDestroyed(). mCurrentState=" + mCurrentState 646 + ", mTargetState=" + mTargetState + ", " + view.toString()); 647 } 648 } 649 650 @Override 651 public void onSurfaceChanged(View view, int width, int height) { 652 // TODO: Do we need to call requestLayout here? 653 if (DEBUG) { 654 Log.d(TAG, "onSurfaceChanged(). width/height: " + width + "/" + height 655 + ", " + view.toString()); 656 } 657 } 658 659 @Override 660 public void onSurfaceTakeOverDone(VideoViewInterface view) { 661 if (DEBUG) { 662 Log.d(TAG, "onSurfaceTakeOverDone(). Now current view is: " + view); 663 } 664 mCurrentView = view; 665 if (mViewTypeChangedListener != null) { 666 mViewTypeChangedListener.onViewTypeChanged(mInstance, view.getViewType()); 667 } 668 if (needToStart()) { 669 mMediaController.getTransportControls().play(); 670 } 671 } 672 673 /////////////////////////////////////////////////// 674 // Protected or private methods 675 /////////////////////////////////////////////////// 676 677 private void attachMediaControlView() { 678 // Get MediaController from MediaSession and set it inside MediaControlView 679 mMediaControlView.setController(mMediaSession.getController()); 680 681 LayoutParams params = 682 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 683 mInstance.addView(mMediaControlView, params); 684 } 685 686 private boolean isInPlaybackState() { 687 return (mMediaPlayer != null || mRoutePlayer != null) 688 && mCurrentState != STATE_ERROR 689 && mCurrentState != STATE_IDLE 690 && mCurrentState != STATE_PREPARING; 691 } 692 693 private boolean needToStart() { 694 return (mMediaPlayer != null || mRoutePlayer != null) 695 && mCurrentState != STATE_PLAYING 696 && mTargetState == STATE_PLAYING; 697 } 698 699 // Creates a MediaPlayer2 instance and prepare playback. 700 private void openVideo(DataSourceDesc dsd) { 701 Uri uri = dsd.getUri(); 702 Map<String, String> headers = dsd.getUriHeaders(); 703 resetPlayer(); 704 if (isRemotePlayback()) { 705 mRoutePlayer.openVideo(dsd); 706 return; 707 } 708 if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) { 709 // TODO this should have a focus listener 710 AudioFocusRequest focusRequest; 711 focusRequest = new AudioFocusRequest.Builder(mAudioFocusType) 712 .setAudioAttributes(mAudioAttributes) 713 .build(); 714 mAudioManager.requestAudioFocus(focusRequest); 715 } 716 717 try { 718 Log.d(TAG, "openVideo(): creating new MediaPlayer2 instance."); 719 mMediaPlayer = new MediaPlayer2Impl(); 720 mSurfaceView.setMediaPlayer(mMediaPlayer); 721 mTextureView.setMediaPlayer(mMediaPlayer); 722 mCurrentView.assignSurfaceToMediaPlayer(mMediaPlayer); 723 724 final Context context = mInstance.getContext(); 725 // TODO: Add timely firing logic for more accurate sync between CC and video frame 726 mSubtitleController = new SubtitleController(context); 727 mSubtitleController.registerRenderer(new ClosedCaptionRenderer(context)); 728 mSubtitleController.setAnchor((SubtitleController.Anchor) mSubtitleView); 729 Executor executor = new Executor() { 730 @Override 731 public void execute(Runnable runnable) { 732 runnable.run(); 733 } 734 }; 735 mMediaPlayer.setMediaPlayer2EventCallback(executor, mMediaPlayer2Callback); 736 737 mCurrentBufferPercentage = -1; 738 mMediaPlayer.setDataSource(dsd); 739 mMediaPlayer.setAudioAttributes(mAudioAttributes); 740 mMediaPlayer.setOnSubtitleDataListener(mSubtitleListener); 741 // we don't set the target state here either, but preserve the 742 // target state that was there before. 743 mCurrentState = STATE_PREPARING; 744 mMediaPlayer.prepare(); 745 746 // Save file name as title since the file may not have a title Metadata. 747 mTitle = uri.getPath(); 748 String scheme = uri.getScheme(); 749 if (scheme != null && scheme.equals("file")) { 750 mTitle = uri.getLastPathSegment(); 751 } 752 mRetriever = new MediaMetadataRetriever(); 753 mRetriever.setDataSource(mInstance.getContext(), uri); 754 755 if (DEBUG) { 756 Log.d(TAG, "openVideo(). mCurrentState=" + mCurrentState 757 + ", mTargetState=" + mTargetState); 758 } 759 } catch (IllegalArgumentException ex) { 760 Log.w(TAG, "Unable to open content: " + uri, ex); 761 mCurrentState = STATE_ERROR; 762 mTargetState = STATE_ERROR; 763 mMediaPlayer2Callback.onError(mMediaPlayer, dsd, 764 MediaPlayer2.MEDIA_ERROR_UNKNOWN, MediaPlayer2.MEDIA_ERROR_IO); 765 } 766 } 767 768 /* 769 * Reset the media player in any state 770 */ 771 private void resetPlayer() { 772 if (mMediaPlayer != null) { 773 final MediaPlayer2 player = mMediaPlayer; 774 new AsyncTask<MediaPlayer2, Void, Void>() { 775 @Override 776 protected Void doInBackground(MediaPlayer2... players) { 777 // TODO: Fix NPE while MediaPlayer2.close() 778 //players[0].close(); 779 return null; 780 } 781 }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, player); 782 mMediaPlayer = null; 783 mTextureView.setMediaPlayer(null); 784 mSurfaceView.setMediaPlayer(null); 785 //mPendingSubtitleTracks.clear(); 786 mCurrentState = STATE_IDLE; 787 mTargetState = STATE_IDLE; 788 if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) { 789 mAudioManager.abandonAudioFocus(null); 790 } 791 } 792 mVideoWidth = 0; 793 mVideoHeight = 0; 794 } 795 796 private void updatePlaybackState() { 797 if (mStateBuilder == null) { 798 // Get the capabilities of the player for this stream 799 mMetadata = mMediaPlayer.getMetadata(MediaPlayer2.METADATA_ALL, 800 MediaPlayer2.BYPASS_METADATA_FILTER); 801 802 // Add Play action as default 803 long playbackActions = PlaybackState.ACTION_PLAY; 804 if (mMetadata != null) { 805 if (!mMetadata.has(Metadata.PAUSE_AVAILABLE) 806 || mMetadata.getBoolean(Metadata.PAUSE_AVAILABLE)) { 807 playbackActions |= PlaybackState.ACTION_PAUSE; 808 } 809 if (!mMetadata.has(Metadata.SEEK_BACKWARD_AVAILABLE) 810 || mMetadata.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE)) { 811 playbackActions |= PlaybackState.ACTION_REWIND; 812 } 813 if (!mMetadata.has(Metadata.SEEK_FORWARD_AVAILABLE) 814 || mMetadata.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE)) { 815 playbackActions |= PlaybackState.ACTION_FAST_FORWARD; 816 } 817 if (!mMetadata.has(Metadata.SEEK_AVAILABLE) 818 || mMetadata.getBoolean(Metadata.SEEK_AVAILABLE)) { 819 playbackActions |= PlaybackState.ACTION_SEEK_TO; 820 } 821 } else { 822 playbackActions |= (PlaybackState.ACTION_PAUSE | 823 PlaybackState.ACTION_REWIND | PlaybackState.ACTION_FAST_FORWARD | 824 PlaybackState.ACTION_SEEK_TO); 825 } 826 mStateBuilder = new PlaybackState.Builder(); 827 mStateBuilder.setActions(playbackActions); 828 829 if (mCustomActionList != null) { 830 for (PlaybackState.CustomAction action : mCustomActionList) { 831 mStateBuilder.addCustomAction(action); 832 } 833 } 834 } 835 mStateBuilder.setState(getCorrespondingPlaybackState(), 836 mMediaPlayer.getCurrentPosition(), mSpeed); 837 if (mCurrentState != STATE_ERROR 838 && mCurrentState != STATE_IDLE 839 && mCurrentState != STATE_PREPARING) { 840 // TODO: this should be replaced with MediaPlayer2.getBufferedPosition() once it is 841 // implemented. 842 if (mCurrentBufferPercentage == -1) { 843 mStateBuilder.setBufferedPosition(-1); 844 } else { 845 mStateBuilder.setBufferedPosition( 846 (long) (mCurrentBufferPercentage / 100.0 * mMediaPlayer.getDuration())); 847 } 848 } 849 850 // Set PlaybackState for MediaSession 851 if (mMediaSession != null) { 852 PlaybackState state = mStateBuilder.build(); 853 mMediaSession.setPlaybackState(state); 854 } 855 } 856 857 private int getCorrespondingPlaybackState() { 858 switch (mCurrentState) { 859 case STATE_ERROR: 860 return PlaybackState.STATE_ERROR; 861 case STATE_IDLE: 862 return PlaybackState.STATE_NONE; 863 case STATE_PREPARING: 864 return PlaybackState.STATE_CONNECTING; 865 case STATE_PREPARED: 866 return PlaybackState.STATE_PAUSED; 867 case STATE_PLAYING: 868 return PlaybackState.STATE_PLAYING; 869 case STATE_PAUSED: 870 return PlaybackState.STATE_PAUSED; 871 case STATE_PLAYBACK_COMPLETED: 872 return PlaybackState.STATE_STOPPED; 873 default: 874 return -1; 875 } 876 } 877 878 private final Runnable mFadeOut = new Runnable() { 879 @Override 880 public void run() { 881 if (mCurrentState == STATE_PLAYING) { 882 mMediaControlView.setVisibility(View.GONE); 883 } 884 } 885 }; 886 887 private void showController() { 888 // TODO: Decide what to show when the state is not in playback state 889 if (mMediaControlView == null || !isInPlaybackState() 890 || (mIsMusicMediaType && mSizeType == SIZE_TYPE_FULL)) { 891 return; 892 } 893 mMediaControlView.removeCallbacks(mFadeOut); 894 mMediaControlView.setVisibility(View.VISIBLE); 895 if (mShowControllerIntervalMs != 0 896 && !mAccessibilityManager.isTouchExplorationEnabled()) { 897 mMediaControlView.postDelayed(mFadeOut, mShowControllerIntervalMs); 898 } 899 } 900 901 private void toggleMediaControlViewVisibility() { 902 if (mMediaControlView.getVisibility() == View.VISIBLE) { 903 mMediaControlView.removeCallbacks(mFadeOut); 904 mMediaControlView.setVisibility(View.GONE); 905 } else { 906 showController(); 907 } 908 } 909 910 private void applySpeed() { 911 PlaybackParams params = mMediaPlayer.getPlaybackParams().allowDefaults(); 912 if (mSpeed != params.getSpeed()) { 913 try { 914 params.setSpeed(mSpeed); 915 mMediaPlayer.setPlaybackParams(params); 916 mFallbackSpeed = mSpeed; 917 } catch (IllegalArgumentException e) { 918 Log.e(TAG, "PlaybackParams has unsupported value: " + e); 919 // TODO: should revise this part after integrating with MP2. 920 // If mSpeed had an illegal value for speed rate, system will determine best 921 // handling (see PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT). 922 // Note: The pre-MP2 returns 0.0f when it is paused. In this case, VideoView2 will 923 // use mFallbackSpeed instead. 924 float fallbackSpeed = mMediaPlayer.getPlaybackParams().allowDefaults().getSpeed(); 925 if (fallbackSpeed > 0.0f) { 926 mFallbackSpeed = fallbackSpeed; 927 } 928 mSpeed = mFallbackSpeed; 929 } 930 } 931 } 932 933 private boolean isRemotePlayback() { 934 if (mMediaController == null) { 935 return false; 936 } 937 PlaybackInfo playbackInfo = mMediaController.getPlaybackInfo(); 938 return playbackInfo != null 939 && playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE; 940 } 941 942 private void selectOrDeselectSubtitle(boolean select) { 943 if (!isInPlaybackState()) { 944 return; 945 } 946 if (select) { 947 if (mSubtitleTrackIndices.size() > 0) { 948 // TODO: make this selection dynamic 949 mSelectedSubtitleTrackIndex = mSubtitleTrackIndices.get(0).first; 950 mSubtitleController.selectTrack(mSubtitleTrackIndices.get(0).second); 951 mMediaPlayer.selectTrack(mSelectedSubtitleTrackIndex); 952 mSubtitleView.setVisibility(View.VISIBLE); 953 } 954 } else { 955 if (mSelectedSubtitleTrackIndex != INVALID_TRACK_INDEX) { 956 mMediaPlayer.deselectTrack(mSelectedSubtitleTrackIndex); 957 mSelectedSubtitleTrackIndex = INVALID_TRACK_INDEX; 958 mSubtitleView.setVisibility(View.GONE); 959 } 960 } 961 } 962 963 private void extractTracks() { 964 List<MediaPlayer2.TrackInfo> trackInfos = mMediaPlayer.getTrackInfo(); 965 mVideoTrackIndices = new ArrayList<>(); 966 mAudioTrackIndices = new ArrayList<>(); 967 mSubtitleTrackIndices = new ArrayList<>(); 968 mSubtitleController.reset(); 969 for (int i = 0; i < trackInfos.size(); ++i) { 970 int trackType = trackInfos.get(i).getTrackType(); 971 if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_VIDEO) { 972 mVideoTrackIndices.add(i); 973 } else if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_AUDIO) { 974 mAudioTrackIndices.add(i); 975 } else if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE 976 || trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) { 977 SubtitleTrack track = mSubtitleController.addTrack(trackInfos.get(i).getFormat()); 978 if (track != null) { 979 mSubtitleTrackIndices.add(new Pair<>(i, track)); 980 } 981 } 982 } 983 // Select first tracks as default 984 if (mVideoTrackIndices.size() > 0) { 985 mSelectedVideoTrackIndex = 0; 986 } 987 if (mAudioTrackIndices.size() > 0) { 988 mSelectedAudioTrackIndex = 0; 989 } 990 if (mVideoTrackIndices.size() == 0 && mAudioTrackIndices.size() > 0) { 991 mIsMusicMediaType = true; 992 } 993 994 Bundle data = new Bundle(); 995 data.putInt(MediaControlView2Impl.KEY_VIDEO_TRACK_COUNT, mVideoTrackIndices.size()); 996 data.putInt(MediaControlView2Impl.KEY_AUDIO_TRACK_COUNT, mAudioTrackIndices.size()); 997 data.putInt(MediaControlView2Impl.KEY_SUBTITLE_TRACK_COUNT, mSubtitleTrackIndices.size()); 998 if (mSubtitleTrackIndices.size() > 0) { 999 selectOrDeselectSubtitle(mSubtitleEnabled); 1000 } 1001 mMediaSession.sendSessionEvent(MediaControlView2Impl.EVENT_UPDATE_TRACK_STATUS, data); 1002 } 1003 1004 private void extractMetadata() { 1005 // Get and set duration and title values as MediaMetadata for MediaControlView2 1006 MediaMetadata.Builder builder = new MediaMetadata.Builder(); 1007 if (mMetadata != null && mMetadata.has(Metadata.TITLE)) { 1008 mTitle = mMetadata.getString(Metadata.TITLE); 1009 } 1010 builder.putString(MediaMetadata.METADATA_KEY_TITLE, mTitle); 1011 builder.putLong( 1012 MediaMetadata.METADATA_KEY_DURATION, mMediaPlayer.getDuration()); 1013 1014 if (mMediaSession != null) { 1015 mMediaSession.setMetadata(builder.build()); 1016 } 1017 } 1018 1019 private void extractAudioMetadata() { 1020 if (!mIsMusicMediaType) { 1021 return; 1022 } 1023 1024 mResources = ApiHelper.getLibResources(mInstance.getContext()); 1025 mManager = (WindowManager) mInstance.getContext().getApplicationContext() 1026 .getSystemService(Context.WINDOW_SERVICE); 1027 1028 byte[] album = mRetriever.getEmbeddedPicture(); 1029 if (album != null) { 1030 Bitmap bitmap = BitmapFactory.decodeByteArray(album, 0, album.length); 1031 mMusicAlbumDrawable = new BitmapDrawable(bitmap); 1032 1033 // TODO: replace with visualizer 1034 Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() { 1035 public void onGenerated(Palette palette) { 1036 // TODO: add dominant color for default album image. 1037 mDominantColor = palette.getDominantColor(0); 1038 if (mMusicView != null) { 1039 mMusicView.setBackgroundColor(mDominantColor); 1040 } 1041 } 1042 }); 1043 } else { 1044 mMusicAlbumDrawable = mResources.getDrawable(R.drawable.ic_default_album_image); 1045 } 1046 1047 String title = mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE); 1048 if (title != null) { 1049 mMusicTitleText = title; 1050 } else { 1051 mMusicTitleText = mResources.getString(R.string.mcv2_music_title_unknown_text); 1052 } 1053 1054 String artist = mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST); 1055 if (artist != null) { 1056 mMusicArtistText = artist; 1057 } else { 1058 mMusicArtistText = mResources.getString(R.string.mcv2_music_artist_unknown_text); 1059 } 1060 1061 // Send title and artist string to MediaControlView2 1062 MediaMetadata.Builder builder = new MediaMetadata.Builder(); 1063 builder.putString(MediaMetadata.METADATA_KEY_TITLE, mMusicTitleText); 1064 builder.putString(MediaMetadata.METADATA_KEY_ARTIST, mMusicArtistText); 1065 mMediaSession.setMetadata(builder.build()); 1066 1067 // Display Embedded mode as default 1068 mInstance.removeView(mSurfaceView); 1069 mInstance.removeView(mTextureView); 1070 inflateMusicView(R.layout.embedded_music); 1071 } 1072 1073 private int retrieveOrientation() { 1074 DisplayMetrics dm = Resources.getSystem().getDisplayMetrics(); 1075 int width = dm.widthPixels; 1076 int height = dm.heightPixels; 1077 1078 return (height > width) ? 1079 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT : 1080 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 1081 } 1082 1083 private void inflateMusicView(int layoutId) { 1084 mInstance.removeView(mMusicView); 1085 1086 View v = ApiHelper.inflateLibLayout(mInstance.getContext(), layoutId); 1087 v.setBackgroundColor(mDominantColor); 1088 1089 ImageView albumView = v.findViewById(R.id.album); 1090 if (albumView != null) { 1091 albumView.setImageDrawable(mMusicAlbumDrawable); 1092 } 1093 1094 TextView titleView = v.findViewById(R.id.title); 1095 if (titleView != null) { 1096 titleView.setText(mMusicTitleText); 1097 } 1098 1099 TextView artistView = v.findViewById(R.id.artist); 1100 if (artistView != null) { 1101 artistView.setText(mMusicArtistText); 1102 } 1103 1104 mMusicView = v; 1105 mInstance.addView(mMusicView, 0); 1106 } 1107 1108 OnSubtitleDataListener mSubtitleListener = 1109 new OnSubtitleDataListener() { 1110 @Override 1111 public void onSubtitleData(MediaPlayer2 mp, SubtitleData data) { 1112 if (DEBUG) { 1113 Log.d(TAG, "onSubtitleData(): getTrackIndex: " + data.getTrackIndex() 1114 + ", getCurrentPosition: " + mp.getCurrentPosition() 1115 + ", getStartTimeUs(): " + data.getStartTimeUs() 1116 + ", diff: " 1117 + (data.getStartTimeUs()/1000 - mp.getCurrentPosition()) 1118 + "ms, getDurationUs(): " + data.getDurationUs() 1119 ); 1120 1121 } 1122 final int index = data.getTrackIndex(); 1123 if (index != mSelectedSubtitleTrackIndex) { 1124 Log.d(TAG, "onSubtitleData(): getTrackIndex: " + data.getTrackIndex() 1125 + ", selected track index: " + mSelectedSubtitleTrackIndex); 1126 return; 1127 } 1128 for (Pair<Integer, SubtitleTrack> p : mSubtitleTrackIndices) { 1129 if (p.first == index) { 1130 SubtitleTrack track = p.second; 1131 track.onData(data); 1132 } 1133 } 1134 } 1135 }; 1136 1137 MediaPlayer2EventCallback mMediaPlayer2Callback = 1138 new MediaPlayer2EventCallback() { 1139 @Override 1140 public void onVideoSizeChanged( 1141 MediaPlayer2 mp, DataSourceDesc dsd, int width, int height) { 1142 if (DEBUG) { 1143 Log.d(TAG, "onVideoSizeChanged(): size: " + width + "/" + height); 1144 } 1145 mVideoWidth = mp.getVideoWidth(); 1146 mVideoHeight = mp.getVideoHeight(); 1147 if (DEBUG) { 1148 Log.d(TAG, "onVideoSizeChanged(): mVideoSize:" + mVideoWidth + "/" 1149 + mVideoHeight); 1150 } 1151 if (mVideoWidth != 0 && mVideoHeight != 0) { 1152 mInstance.requestLayout(); 1153 } 1154 } 1155 1156 // TODO: Remove timed text related code later once relevant Renderer is defined. 1157 // This is just for debugging purpose. 1158 @Override 1159 public void onTimedText( 1160 MediaPlayer2 mp, DataSourceDesc dsd, TimedText text) { 1161 Log.d(TAG, "TimedText: " + text.getText()); 1162 } 1163 1164 @Override 1165 public void onInfo( 1166 MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) { 1167 if (what == MediaPlayer2.MEDIA_INFO_METADATA_UPDATE) { 1168 extractTracks(); 1169 } else if (what == MediaPlayer2.MEDIA_INFO_PREPARED) { 1170 this.onPrepared(mp, dsd); 1171 } else if (what == MediaPlayer2.MEDIA_INFO_PLAYBACK_COMPLETE) { 1172 this.onCompletion(mp, dsd); 1173 } else if (what == MediaPlayer2.MEDIA_INFO_BUFFERING_UPDATE) { 1174 this.onBufferingUpdate(mp, dsd, extra); 1175 } 1176 } 1177 1178 @Override 1179 public void onError( 1180 MediaPlayer2 mp, DataSourceDesc dsd, int frameworkErr, int implErr) { 1181 if (DEBUG) { 1182 Log.d(TAG, "Error: " + frameworkErr + "," + implErr); 1183 } 1184 mCurrentState = STATE_ERROR; 1185 mTargetState = STATE_ERROR; 1186 updatePlaybackState(); 1187 1188 if (mMediaControlView != null) { 1189 mMediaControlView.setVisibility(View.GONE); 1190 } 1191 } 1192 1193 @Override 1194 public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, int what, 1195 int status) { 1196 if (what == MediaPlayer2.CALL_COMPLETED_SEEK_TO && status == 0) { 1197 updatePlaybackState(); 1198 } 1199 } 1200 1201 private void onPrepared(MediaPlayer2 mp, DataSourceDesc dsd) { 1202 if (DEBUG) { 1203 Log.d(TAG, "OnPreparedListener(). mCurrentState=" + mCurrentState 1204 + ", mTargetState=" + mTargetState); 1205 } 1206 mCurrentState = STATE_PREPARED; 1207 // Create and set playback state for MediaControlView2 1208 updatePlaybackState(); 1209 1210 // TODO: change this to send TrackInfos to MediaControlView2 1211 // TODO: create MediaSession when initializing VideoView2 1212 if (mMediaSession != null) { 1213 extractTracks(); 1214 extractMetadata(); 1215 extractAudioMetadata(); 1216 } 1217 1218 if (mMediaControlView != null) { 1219 mMediaControlView.setEnabled(true); 1220 } 1221 int videoWidth = mp.getVideoWidth(); 1222 int videoHeight = mp.getVideoHeight(); 1223 1224 // mSeekWhenPrepared may be changed after seekTo() call 1225 long seekToPosition = mSeekWhenPrepared; 1226 if (seekToPosition != 0) { 1227 mMediaController.getTransportControls().seekTo(seekToPosition); 1228 } 1229 1230 if (videoWidth != 0 && videoHeight != 0) { 1231 if (videoWidth != mVideoWidth || videoHeight != mVideoHeight) { 1232 if (DEBUG) { 1233 Log.i(TAG, "OnPreparedListener() : "); 1234 Log.i(TAG, " video size: " + videoWidth + "/" + videoHeight); 1235 Log.i(TAG, " measuredSize: " + mInstance.getMeasuredWidth() + "/" 1236 + mInstance.getMeasuredHeight()); 1237 Log.i(TAG, " viewSize: " + mInstance.getWidth() + "/" 1238 + mInstance.getHeight()); 1239 } 1240 mVideoWidth = videoWidth; 1241 mVideoHeight = videoHeight; 1242 mInstance.requestLayout(); 1243 } 1244 1245 if (needToStart()) { 1246 mMediaController.getTransportControls().play(); 1247 } 1248 } else { 1249 // We don't know the video size yet, but should start anyway. 1250 // The video size might be reported to us later. 1251 if (needToStart()) { 1252 mMediaController.getTransportControls().play(); 1253 } 1254 } 1255 } 1256 1257 private void onCompletion(MediaPlayer2 mp, DataSourceDesc dsd) { 1258 mCurrentState = STATE_PLAYBACK_COMPLETED; 1259 mTargetState = STATE_PLAYBACK_COMPLETED; 1260 updatePlaybackState(); 1261 if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) { 1262 mAudioManager.abandonAudioFocus(null); 1263 } 1264 } 1265 1266 private void onBufferingUpdate(MediaPlayer2 mp, DataSourceDesc dsd, int percent) { 1267 mCurrentBufferPercentage = percent; 1268 updatePlaybackState(); 1269 } 1270 }; 1271 1272 private class MediaSessionCallback extends MediaSession.Callback { 1273 @Override 1274 public void onCommand(String command, Bundle args, ResultReceiver receiver) { 1275 if (isRemotePlayback()) { 1276 mRoutePlayer.onCommand(command, args, receiver); 1277 } else { 1278 switch (command) { 1279 case MediaControlView2Impl.COMMAND_SHOW_SUBTITLE: 1280 int subtitleIndex = args.getInt( 1281 MediaControlView2Impl.KEY_SELECTED_SUBTITLE_INDEX, 1282 INVALID_TRACK_INDEX); 1283 if (subtitleIndex != INVALID_TRACK_INDEX) { 1284 int subtitleTrackIndex = mSubtitleTrackIndices.get(subtitleIndex).first; 1285 if (subtitleTrackIndex != mSelectedSubtitleTrackIndex) { 1286 mSelectedSubtitleTrackIndex = subtitleTrackIndex; 1287 mInstance.setSubtitleEnabled(true); 1288 } 1289 } 1290 break; 1291 case MediaControlView2Impl.COMMAND_HIDE_SUBTITLE: 1292 mInstance.setSubtitleEnabled(false); 1293 break; 1294 case MediaControlView2Impl.COMMAND_SET_FULLSCREEN: 1295 if (mFullScreenRequestListener != null) { 1296 mFullScreenRequestListener.onFullScreenRequest( 1297 mInstance, 1298 args.getBoolean(MediaControlView2Impl.ARGUMENT_KEY_FULLSCREEN)); 1299 } 1300 break; 1301 case MediaControlView2Impl.COMMAND_SELECT_AUDIO_TRACK: 1302 int audioIndex = args.getInt(MediaControlView2Impl.KEY_SELECTED_AUDIO_INDEX, 1303 INVALID_TRACK_INDEX); 1304 if (audioIndex != INVALID_TRACK_INDEX) { 1305 int audioTrackIndex = mAudioTrackIndices.get(audioIndex); 1306 if (audioTrackIndex != mSelectedAudioTrackIndex) { 1307 mSelectedAudioTrackIndex = audioTrackIndex; 1308 mMediaPlayer.selectTrack(mSelectedAudioTrackIndex); 1309 } 1310 } 1311 break; 1312 case MediaControlView2Impl.COMMAND_SET_PLAYBACK_SPEED: 1313 float speed = args.getFloat( 1314 MediaControlView2Impl.KEY_PLAYBACK_SPEED, INVALID_SPEED); 1315 if (speed != INVALID_SPEED && speed != mSpeed) { 1316 mInstance.setSpeed(speed); 1317 mSpeed = speed; 1318 } 1319 break; 1320 case MediaControlView2Impl.COMMAND_MUTE: 1321 mVolumeLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 1322 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0); 1323 break; 1324 case MediaControlView2Impl.COMMAND_UNMUTE: 1325 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mVolumeLevel, 0); 1326 break; 1327 } 1328 } 1329 showController(); 1330 } 1331 1332 @Override 1333 public void onCustomAction(String action, Bundle extras) { 1334 mCustomActionListenerRecord.first.execute(() -> 1335 mCustomActionListenerRecord.second.onCustomAction(action, extras)); 1336 showController(); 1337 } 1338 1339 @Override 1340 public void onPlay() { 1341 if (isInPlaybackState() && (mCurrentView.hasAvailableSurface() || mIsMusicMediaType)) { 1342 if (isRemotePlayback()) { 1343 mRoutePlayer.onPlay(); 1344 } else { 1345 applySpeed(); 1346 mMediaPlayer.play(); 1347 mCurrentState = STATE_PLAYING; 1348 updatePlaybackState(); 1349 } 1350 mCurrentState = STATE_PLAYING; 1351 } 1352 mTargetState = STATE_PLAYING; 1353 if (DEBUG) { 1354 Log.d(TAG, "onPlay(). mCurrentState=" + mCurrentState 1355 + ", mTargetState=" + mTargetState); 1356 } 1357 showController(); 1358 } 1359 1360 @Override 1361 public void onPause() { 1362 if (isInPlaybackState()) { 1363 if (isRemotePlayback()) { 1364 mRoutePlayer.onPause(); 1365 mCurrentState = STATE_PAUSED; 1366 } else if (mMediaPlayer.isPlaying()) { 1367 mMediaPlayer.pause(); 1368 mCurrentState = STATE_PAUSED; 1369 updatePlaybackState(); 1370 } 1371 } 1372 mTargetState = STATE_PAUSED; 1373 if (DEBUG) { 1374 Log.d(TAG, "onPause(). mCurrentState=" + mCurrentState 1375 + ", mTargetState=" + mTargetState); 1376 } 1377 showController(); 1378 } 1379 1380 @Override 1381 public void onSeekTo(long pos) { 1382 if (isInPlaybackState()) { 1383 if (isRemotePlayback()) { 1384 mRoutePlayer.onSeekTo(pos); 1385 } else { 1386 mMediaPlayer.seekTo(pos, MediaPlayer2.SEEK_PREVIOUS_SYNC); 1387 mSeekWhenPrepared = 0; 1388 } 1389 } else { 1390 mSeekWhenPrepared = pos; 1391 } 1392 showController(); 1393 } 1394 1395 @Override 1396 public void onStop() { 1397 if (isRemotePlayback()) { 1398 mRoutePlayer.onStop(); 1399 } else { 1400 resetPlayer(); 1401 } 1402 showController(); 1403 } 1404 } 1405 } 1406