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 androidx.media.widget; 18 19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21 import android.content.Context; 22 import android.content.pm.ActivityInfo; 23 import android.content.res.Resources; 24 import android.graphics.Bitmap; 25 import android.graphics.BitmapFactory; 26 import android.graphics.Point; 27 import android.graphics.drawable.BitmapDrawable; 28 import android.graphics.drawable.Drawable; 29 import android.media.AudioAttributes; 30 import android.media.AudioFocusRequest; 31 import android.media.AudioManager; 32 import android.media.MediaMetadataRetriever; 33 import android.media.MediaPlayer; 34 import android.media.PlaybackParams; 35 import android.net.Uri; 36 import android.os.Bundle; 37 import android.os.ResultReceiver; 38 import android.support.v4.media.MediaMetadataCompat; 39 import android.support.v4.media.session.MediaControllerCompat; 40 import android.support.v4.media.session.MediaControllerCompat.PlaybackInfo; 41 import android.support.v4.media.session.MediaSessionCompat; 42 import android.support.v4.media.session.PlaybackStateCompat; 43 import android.util.AttributeSet; 44 import android.util.DisplayMetrics; 45 import android.util.Log; 46 import android.util.Pair; 47 import android.view.LayoutInflater; 48 import android.view.MotionEvent; 49 import android.view.View; 50 import android.view.ViewGroup.LayoutParams; 51 import android.view.WindowManager; 52 import android.view.accessibility.AccessibilityManager; 53 import android.widget.ImageView; 54 import android.widget.TextView; 55 56 import androidx.annotation.NonNull; 57 import androidx.annotation.Nullable; 58 import androidx.annotation.RequiresApi; 59 import androidx.annotation.RestrictTo; 60 import androidx.media.AudioAttributesCompat; 61 import androidx.media.DataSourceDesc; 62 import androidx.media.MediaItem2; 63 import androidx.media.MediaMetadata2; 64 import androidx.media.SessionToken2; 65 import androidx.mediarouter.media.MediaControlIntent; 66 import androidx.mediarouter.media.MediaItemStatus; 67 import androidx.mediarouter.media.MediaRouteSelector; 68 import androidx.mediarouter.media.MediaRouter; 69 import androidx.palette.graphics.Palette; 70 71 import java.io.IOException; 72 import java.util.ArrayList; 73 import java.util.List; 74 import java.util.Map; 75 import java.util.concurrent.Executor; 76 77 /** 78 * Base implementation of VideoView2. 79 */ 80 @RequiresApi(21) // TODO correct minSdk API use incompatibilities and remove before release. 81 class VideoView2ImplBaseWithMp1 82 implements VideoView2Impl, VideoViewInterfaceWithMp1.SurfaceListener { 83 private static final String TAG = "VideoView2ImplBase_1"; 84 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 85 private static final long DEFAULT_SHOW_CONTROLLER_INTERVAL_MS = 2000; 86 87 private static final int STATE_ERROR = -1; 88 private static final int STATE_IDLE = 0; 89 private static final int STATE_PREPARING = 1; 90 private static final int STATE_PREPARED = 2; 91 private static final int STATE_PLAYING = 3; 92 private static final int STATE_PAUSED = 4; 93 private static final int STATE_PLAYBACK_COMPLETED = 5; 94 95 private static final int INVALID_TRACK_INDEX = -1; 96 private static final float INVALID_SPEED = 0f; 97 98 private static final int SIZE_TYPE_EMBEDDED = 0; 99 private static final int SIZE_TYPE_FULL = 1; 100 private static final int SIZE_TYPE_MINIMAL = 2; 101 102 private AccessibilityManager mAccessibilityManager; 103 private AudioManager mAudioManager; 104 private AudioAttributes mAudioAttributes; 105 private int mAudioFocusType = AudioManager.AUDIOFOCUS_GAIN; // legacy focus gain 106 private boolean mAudioFocused = false; 107 108 private Pair<Executor, VideoView2.OnCustomActionListener> mCustomActionListenerRecord; 109 private VideoView2.OnViewTypeChangedListener mViewTypeChangedListener; 110 111 private VideoViewInterfaceWithMp1 mCurrentView; 112 private VideoTextureViewWithMp1 mTextureView; 113 private VideoSurfaceViewWithMp1 mSurfaceView; 114 115 protected MediaPlayer mMediaPlayer; 116 private DataSourceDesc mDsd; 117 private Uri mUri; 118 private Map<String, String> mHeaders; 119 private MediaControlView2 mMediaControlView; 120 protected MediaSessionCompat mMediaSession; 121 private MediaControllerCompat mMediaController; 122 private MediaMetadata2 mMediaMetadata; 123 private MediaMetadataRetriever mRetriever; 124 private boolean mNeedUpdateMediaType; 125 private Bundle mMediaTypeData; 126 private String mTitle; 127 128 private WindowManager mManager; 129 private Resources mResources; 130 private View mMusicView; 131 private Drawable mMusicAlbumDrawable; 132 private String mMusicTitleText; 133 private String mMusicArtistText; 134 private int mPrevWidth; 135 private int mPrevHeight; 136 private int mDominantColor; 137 private int mSizeType; 138 139 private PlaybackStateCompat.Builder mStateBuilder; 140 private List<PlaybackStateCompat.CustomAction> mCustomActionList; 141 142 private int mTargetState = STATE_IDLE; 143 private int mCurrentState = STATE_IDLE; 144 private int mCurrentBufferPercentage; 145 private long mSeekWhenPrepared; // recording the seek position while preparing 146 147 private int mVideoWidth; 148 private int mVideoHeight; 149 150 protected ArrayList<Integer> mVideoTrackIndices; 151 protected ArrayList<Integer> mAudioTrackIndices; 152 153 // selected video/audio/subtitle track index as MediaPlayer returns 154 protected int mSelectedVideoTrackIndex; 155 protected int mSelectedAudioTrackIndex; 156 157 private float mSpeed; 158 private float mFallbackSpeed; // keep the original speed before 'pause' is called. 159 private float mVolumeLevelFloat; 160 private int mVolumeLevel; 161 protected VideoView2 mInstance; 162 163 private long mShowControllerIntervalMs; 164 165 private MediaRouter mMediaRouter; 166 private MediaRouteSelector mRouteSelector; 167 private MediaRouter.RouteInfo mRoute; 168 private RoutePlayer mRoutePlayer; 169 170 private final MediaRouter.Callback mRouterCallback = new MediaRouter.Callback() { 171 @Override 172 public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) { 173 if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) { 174 // Stop local playback (if necessary) 175 resetPlayer(); 176 mRoute = route; 177 mRoutePlayer = new RoutePlayer(mInstance.getContext(), route); 178 mRoutePlayer.setPlayerEventCallback(new RoutePlayer.PlayerEventCallback() { 179 @Override 180 public void onPlayerStateChanged(MediaItemStatus itemStatus) { 181 PlaybackStateCompat.Builder psBuilder = new PlaybackStateCompat.Builder(); 182 psBuilder.setActions(RoutePlayer.PLAYBACK_ACTIONS); 183 long position = itemStatus.getContentPosition(); 184 switch (itemStatus.getPlaybackState()) { 185 case MediaItemStatus.PLAYBACK_STATE_PENDING: 186 psBuilder.setState(PlaybackStateCompat.STATE_NONE, position, 0); 187 mCurrentState = STATE_IDLE; 188 break; 189 case MediaItemStatus.PLAYBACK_STATE_PLAYING: 190 psBuilder.setState(PlaybackStateCompat.STATE_PLAYING, position, 1); 191 mCurrentState = STATE_PLAYING; 192 break; 193 case MediaItemStatus.PLAYBACK_STATE_PAUSED: 194 psBuilder.setState(PlaybackStateCompat.STATE_PAUSED, position, 0); 195 mCurrentState = STATE_PAUSED; 196 break; 197 case MediaItemStatus.PLAYBACK_STATE_BUFFERING: 198 psBuilder.setState( 199 PlaybackStateCompat.STATE_BUFFERING, position, 0); 200 mCurrentState = STATE_PAUSED; 201 break; 202 case MediaItemStatus.PLAYBACK_STATE_FINISHED: 203 psBuilder.setState(PlaybackStateCompat.STATE_STOPPED, position, 0); 204 mCurrentState = STATE_PLAYBACK_COMPLETED; 205 break; 206 } 207 208 PlaybackStateCompat pbState = psBuilder.build(); 209 mMediaSession.setPlaybackState(pbState); 210 211 MediaMetadataCompat.Builder mmBuilder = new MediaMetadataCompat.Builder(); 212 mmBuilder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, 213 itemStatus.getContentDuration()); 214 mMediaSession.setMetadata(mmBuilder.build()); 215 } 216 }); 217 // Start remote playback (if necessary) 218 // TODO: b/77556429 219 mRoutePlayer.openVideo(mUri); 220 } 221 } 222 223 @Override 224 public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route, int reason) { 225 if (mRoute != null && mRoutePlayer != null) { 226 mRoutePlayer.release(); 227 mRoutePlayer = null; 228 } 229 if (mRoute == route) { 230 mRoute = null; 231 } 232 if (reason != MediaRouter.UNSELECT_REASON_ROUTE_CHANGED) { 233 // TODO: Resume local playback (if necessary) 234 // TODO: b/77556429 235 openVideo(mUri, mHeaders); 236 } 237 } 238 }; 239 240 @Override 241 public void initialize( 242 VideoView2 instance, Context context, 243 @Nullable AttributeSet attrs, int defStyleAttr) { 244 mInstance = instance; 245 246 mVideoWidth = 0; 247 mVideoHeight = 0; 248 mSpeed = 1.0f; 249 mFallbackSpeed = mSpeed; 250 mShowControllerIntervalMs = DEFAULT_SHOW_CONTROLLER_INTERVAL_MS; 251 252 mAccessibilityManager = (AccessibilityManager) context.getSystemService( 253 Context.ACCESSIBILITY_SERVICE); 254 255 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 256 mAudioAttributes = new AudioAttributes.Builder() 257 .setUsage(AudioAttributes.USAGE_MEDIA) 258 .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build(); 259 260 mInstance.setFocusable(true); 261 mInstance.setFocusableInTouchMode(true); 262 mInstance.requestFocus(); 263 264 mTextureView = new VideoTextureViewWithMp1(context); 265 mSurfaceView = new VideoSurfaceViewWithMp1(context); 266 LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, 267 LayoutParams.MATCH_PARENT); 268 mTextureView.setLayoutParams(params); 269 mSurfaceView.setLayoutParams(params); 270 mTextureView.setSurfaceListener(this); 271 mSurfaceView.setSurfaceListener(this); 272 273 mInstance.addView(mTextureView); 274 mInstance.addView(mSurfaceView); 275 276 boolean enableControlView = (attrs == null) || attrs.getAttributeBooleanValue( 277 "http://schemas.android.com/apk/res/android", 278 "enableControlView", true); 279 if (enableControlView) { 280 mMediaControlView = new MediaControlView2(context); 281 } 282 283 // Choose surface view by default 284 int viewType = (attrs == null) ? VideoView2.VIEW_TYPE_SURFACEVIEW 285 : attrs.getAttributeIntValue( 286 "http://schemas.android.com/apk/res/android", 287 "viewType", VideoView2.VIEW_TYPE_SURFACEVIEW); 288 if (viewType == VideoView2.VIEW_TYPE_SURFACEVIEW) { 289 Log.d(TAG, "viewType attribute is surfaceView."); 290 mTextureView.setVisibility(View.GONE); 291 mSurfaceView.setVisibility(View.VISIBLE); 292 mCurrentView = mSurfaceView; 293 } else if (viewType == VideoView2.VIEW_TYPE_TEXTUREVIEW) { 294 Log.d(TAG, "viewType attribute is textureView."); 295 mTextureView.setVisibility(View.VISIBLE); 296 mSurfaceView.setVisibility(View.GONE); 297 mCurrentView = mTextureView; 298 } 299 300 MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder(); 301 builder.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); 302 builder.addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO); 303 builder.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO); 304 mRouteSelector = builder.build(); 305 } 306 307 /** 308 * Sets MediaControlView2 instance. It will replace the previously assigned MediaControlView2 309 * instance if any. 310 * 311 * @param mediaControlView a media control view2 instance. 312 * @param intervalMs a time interval in milliseconds until VideoView2 hides MediaControlView2. 313 */ 314 @Override 315 public void setMediaControlView2(MediaControlView2 mediaControlView, long intervalMs) { 316 mMediaControlView = mediaControlView; 317 mShowControllerIntervalMs = intervalMs; 318 mMediaControlView.setRouteSelector(mRouteSelector); 319 320 if (mInstance.isAttachedToWindow()) { 321 attachMediaControlView(); 322 } 323 } 324 325 /** 326 * Returns MediaControlView2 instance which is currently attached to VideoView2 by default or by 327 * {@link #setMediaControlView2} method. 328 */ 329 @Override 330 public MediaControlView2 getMediaControlView2() { 331 return mMediaControlView; 332 } 333 334 /** 335 * Sets MediaMetadata2 instance. It will replace the previously assigned MediaMetadata2 instance 336 * if any. 337 * 338 * @param metadata a MediaMetadata2 instance. 339 * @hide 340 */ 341 @RestrictTo(LIBRARY_GROUP) 342 @Override 343 public void setMediaMetadata(MediaMetadata2 metadata) { 344 //mProvider.setMediaMetadata_impl(metadata); 345 } 346 347 /** 348 * Returns MediaMetadata2 instance which is retrieved from MediaPlayer inside VideoView2 by 349 * default or by {@link #setMediaMetadata} method. 350 * @hide 351 */ 352 @RestrictTo(LIBRARY_GROUP) 353 @Override 354 public MediaMetadata2 getMediaMetadata() { 355 return mMediaMetadata; 356 } 357 358 /** 359 * Returns MediaController instance which is connected with MediaSession that VideoView2 is 360 * using. This method should be called when VideoView2 is attached to window, or it throws 361 * IllegalStateException, since internal MediaSession instance is not available until 362 * this view is attached to window. Please check {@link View#isAttachedToWindow} 363 * before calling this method. 364 * 365 * @throws IllegalStateException if interal MediaSession is not created yet. 366 * @hide TODO: remove 367 */ 368 @RestrictTo(LIBRARY_GROUP) 369 @Override 370 public MediaControllerCompat getMediaController() { 371 if (mMediaSession == null) { 372 throw new IllegalStateException("MediaSession instance is not available."); 373 } 374 return mMediaController; 375 } 376 377 /** 378 * Returns {@link SessionToken2} so that developers create their own 379 * {@link androidx.media.MediaController2} instance. This method should be called when 380 * VideoView2 is attached to window, or it throws IllegalStateException. 381 * 382 * @throws IllegalStateException if interal MediaSession is not created yet. 383 * @hide 384 */ 385 @RestrictTo(LIBRARY_GROUP) 386 @Override 387 public SessionToken2 getMediaSessionToken() { 388 //return mProvider.getMediaSessionToken_impl(); 389 return null; 390 } 391 392 /** 393 * Shows or hides closed caption or subtitles if there is any. 394 * The first subtitle track will be chosen if there multiple subtitle tracks exist. 395 * Default behavior of VideoView2 is not showing subtitle. 396 * @param enable shows closed caption or subtitles if this value is true, or hides. 397 */ 398 @Override 399 public void setSubtitleEnabled(boolean enable) { 400 // No-op on API < 28 401 } 402 403 /** 404 * Returns true if showing subtitle feature is enabled or returns false. 405 * Although there is no subtitle track or closed caption, it can return true, if the feature 406 * has been enabled by {@link #setSubtitleEnabled}. 407 */ 408 @Override 409 public boolean isSubtitleEnabled() { 410 // Not supported on API < 28 411 return false; 412 } 413 414 /** 415 * Sets playback speed. 416 * 417 * It is expressed as a multiplicative factor, where normal speed is 1.0f. If it is less than 418 * or equal to zero, it will be just ignored and nothing will be changed. If it exceeds the 419 * maximum speed that internal engine supports, system will determine best handling or it will 420 * be reset to the normal speed 1.0f. 421 * @param speed the playback speed. It should be positive. 422 */ 423 @Override 424 public void setSpeed(float speed) { 425 if (speed <= 0.0f) { 426 Log.e(TAG, "Unsupported speed (" + speed + ") is ignored."); 427 return; 428 } 429 mSpeed = speed; 430 if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { 431 applySpeed(); 432 } 433 updatePlaybackState(); 434 } 435 436 /** 437 * Returns playback speed. 438 * 439 * It returns the same value that has been set by {@link #setSpeed}, if it was available value. 440 * If {@link #setSpeed} has not been called before, then the normal speed 1.0f will be returned. 441 */ 442 @Override 443 public float getSpeed() { 444 return mSpeed; 445 } 446 447 /** 448 * Sets which type of audio focus will be requested during the playback, or configures playback 449 * to not request audio focus. Valid values for focus requests are 450 * {@link AudioManager#AUDIOFOCUS_GAIN}, {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT}, 451 * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, and 452 * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. Or use 453 * {@link AudioManager#AUDIOFOCUS_NONE} to express that audio focus should not be 454 * requested when playback starts. You can for instance use this when playing a silent animation 455 * through this class, and you don't want to affect other audio applications playing in the 456 * background. 457 * 458 * @param focusGain the type of audio focus gain that will be requested, or 459 * {@link AudioManager#AUDIOFOCUS_NONE} to disable the use audio focus during 460 * playback. 461 */ 462 @Override 463 public void setAudioFocusRequest(int focusGain) { 464 if (focusGain != AudioManager.AUDIOFOCUS_NONE 465 && focusGain != AudioManager.AUDIOFOCUS_GAIN 466 && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT 467 && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 468 && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) { 469 throw new IllegalArgumentException("Illegal audio focus type " + focusGain); 470 } 471 mAudioFocusType = focusGain; 472 } 473 474 /** 475 * Sets the {@link AudioAttributesCompat} to be used during the playback of the video. 476 * 477 * @param attributes non-null <code>AudioAttributesCompat</code>. 478 */ 479 @Override 480 public void setAudioAttributes(@NonNull AudioAttributesCompat attributes) { 481 if (attributes == null) { 482 throw new IllegalArgumentException("Illegal null AudioAttributesCompat"); 483 } 484 mAudioAttributes = (AudioAttributes) attributes.unwrap(); 485 } 486 487 /** 488 * Sets video path. 489 * 490 * @param path the path of the video. 491 * 492 * @hide 493 */ 494 @RestrictTo(LIBRARY_GROUP) 495 @Override 496 public void setVideoPath(String path) { 497 setVideoUri(Uri.parse(path)); 498 } 499 500 /** 501 * Sets video URI. 502 * 503 * @param uri the URI of the video. 504 * 505 * @hide 506 */ 507 @RestrictTo(LIBRARY_GROUP) 508 @Override 509 public void setVideoUri(Uri uri) { 510 setVideoUri(uri, null); 511 } 512 513 /** 514 * Sets video URI using specific headers. 515 * 516 * @param uri the URI of the video. 517 * @param headers the headers for the URI request. 518 * Note that the cross domain redirection is allowed by default, but that can be 519 * changed with key/value pairs through the headers parameter with 520 * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value 521 * to disallow or allow cross domain redirection. 522 */ 523 @Override 524 public void setVideoUri(Uri uri, @Nullable Map<String, String> headers) { 525 mSeekWhenPrepared = 0; 526 openVideo(uri, headers); 527 } 528 529 /** 530 * Sets {@link MediaItem2} object to render using VideoView2. Alternative way to set media 531 * object to VideoView2 is {@link #setDataSource}. 532 * @param mediaItem the MediaItem2 to play 533 * @see #setDataSource 534 * 535 * @hide 536 */ 537 @RestrictTo(LIBRARY_GROUP) 538 @Override 539 public void setMediaItem(@NonNull MediaItem2 mediaItem) { 540 } 541 542 /** 543 * Sets {@link DataSourceDesc} object to render using VideoView2. 544 * @param dataSource the {@link DataSourceDesc} object to play. 545 * @see #setMediaItem 546 * @hide 547 */ 548 @RestrictTo(LIBRARY_GROUP) 549 @Override 550 public void setDataSource(@NonNull DataSourceDesc dataSource) { 551 } 552 553 /** 554 * Selects which view will be used to render video between SurfacView and TextureView. 555 * 556 * @param viewType the view type to render video 557 * <ul> 558 * <li>{@link #VideoView2.VIEW_TYPE_SURFACEVIEW} 559 * <li>{@link #VideoView2.VIEW_TYPE_TEXTUREVIEW} 560 * </ul> 561 */ 562 @Override 563 public void setViewType(@VideoView2.ViewType int viewType) { 564 if (viewType == mCurrentView.getViewType()) { 565 return; 566 } 567 VideoViewInterfaceWithMp1 targetView; 568 if (viewType == VideoView2.VIEW_TYPE_TEXTUREVIEW) { 569 Log.d(TAG, "switching to TextureView"); 570 targetView = mTextureView; 571 } else if (viewType == VideoView2.VIEW_TYPE_SURFACEVIEW) { 572 Log.d(TAG, "switching to SurfaceView"); 573 targetView = mSurfaceView; 574 } else { 575 throw new IllegalArgumentException("Unknown view type: " + viewType); 576 } 577 ((View) targetView).setVisibility(View.VISIBLE); 578 targetView.takeOver(mCurrentView); 579 mInstance.requestLayout(); 580 } 581 582 /** 583 * Returns view type. 584 * 585 * @return view type. See {@see setViewType}. 586 */ 587 @Override 588 @VideoView2.ViewType 589 public int getViewType() { 590 return mCurrentView.getViewType(); 591 } 592 593 /** 594 * Sets custom actions which will be shown as custom buttons in {@link MediaControlView2}. 595 * 596 * @param actionList A list of {@link PlaybackStateCompat.CustomAction}. The return value of 597 * {@link PlaybackStateCompat.CustomAction#getIcon()} will be used to draw 598 * buttons in {@link MediaControlView2}. 599 * @param executor executor to run callbacks on. 600 * @param listener A listener to be called when a custom button is clicked. 601 * @hide 602 */ 603 @RestrictTo(LIBRARY_GROUP) 604 @Override 605 public void setCustomActions(List<PlaybackStateCompat.CustomAction> actionList, 606 Executor executor, VideoView2.OnCustomActionListener listener) { 607 mCustomActionList = actionList; 608 mCustomActionListenerRecord = new Pair<>(executor, listener); 609 610 // Create a new playback builder in order to clear existing the custom actions. 611 mStateBuilder = null; 612 updatePlaybackState(); 613 } 614 615 /** 616 * Registers a callback to be invoked when a view type change is done. 617 * {@see #setViewType(int)} 618 * @param l The callback that will be run 619 * @hide 620 */ 621 @RestrictTo(LIBRARY_GROUP) 622 @Override 623 public void setOnViewTypeChangedListener(VideoView2.OnViewTypeChangedListener l) { 624 mViewTypeChangedListener = l; 625 } 626 627 @Override 628 public void onAttachedToWindowImpl() { 629 // Create MediaSession 630 mMediaSession = new MediaSessionCompat(mInstance.getContext(), "VideoView2MediaSession"); 631 mMediaSession.setCallback(new MediaSessionCallback()); 632 mMediaSession.setActive(true); 633 mMediaController = mMediaSession.getController(); 634 attachMediaControlView(); 635 if (mCurrentState == STATE_PREPARED) { 636 extractTracks(); 637 extractMetadata(); 638 extractAudioMetadata(); 639 if (mNeedUpdateMediaType) { 640 mMediaSession.sendSessionEvent( 641 MediaControlView2.EVENT_UPDATE_MEDIA_TYPE_STATUS, 642 mMediaTypeData); 643 mNeedUpdateMediaType = false; 644 } 645 } 646 647 mMediaRouter = MediaRouter.getInstance(mInstance.getContext()); 648 mMediaRouter.setMediaSessionCompat(mMediaSession); 649 mMediaRouter.addCallback(mRouteSelector, mRouterCallback, 650 MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); 651 } 652 653 @Override 654 public void onDetachedFromWindowImpl() { 655 mMediaSession.release(); 656 mMediaSession = null; 657 mMediaController = null; 658 } 659 660 @Override 661 public void onTouchEventImpl(MotionEvent ev) { 662 if (DEBUG) { 663 Log.d(TAG, "onTouchEvent(). mCurrentState=" + mCurrentState 664 + ", mTargetState=" + mTargetState); 665 } 666 if (ev.getAction() == MotionEvent.ACTION_UP && mMediaControlView != null) { 667 if (!isMusicMediaType() || mSizeType != SIZE_TYPE_FULL) { 668 toggleMediaControlViewVisibility(); 669 } 670 } 671 } 672 673 @Override 674 public void onTrackballEventImpl(MotionEvent ev) { 675 if (ev.getAction() == MotionEvent.ACTION_UP && mMediaControlView != null) { 676 if (!isMusicMediaType() || mSizeType != SIZE_TYPE_FULL) { 677 toggleMediaControlViewVisibility(); 678 } 679 } 680 } 681 682 @Override 683 public void onMeasureImpl(int widthMeasureSpec, int heightMeasureSpec) { 684 if (isMusicMediaType()) { 685 int currWidth = mInstance.getMeasuredWidth(); 686 int currHeight = mInstance.getMeasuredHeight(); 687 if (mPrevWidth != currWidth || mPrevHeight != currHeight) { 688 Point screenSize = new Point(); 689 mManager.getDefaultDisplay().getSize(screenSize); 690 int screenWidth = screenSize.x; 691 int screenHeight = screenSize.y; 692 693 if (currWidth == screenWidth && currHeight == screenHeight) { 694 int orientation = retrieveOrientation(); 695 if (orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { 696 inflateMusicView(R.layout.full_landscape_music); 697 } else { 698 inflateMusicView(R.layout.full_portrait_music); 699 } 700 701 if (mSizeType != SIZE_TYPE_FULL) { 702 mSizeType = SIZE_TYPE_FULL; 703 // Remove existing mFadeOut callback 704 mMediaControlView.removeCallbacks(mFadeOut); 705 mMediaControlView.setVisibility(View.VISIBLE); 706 } 707 } else { 708 if (mSizeType != SIZE_TYPE_EMBEDDED) { 709 mSizeType = SIZE_TYPE_EMBEDDED; 710 inflateMusicView(R.layout.embedded_music); 711 // Add new mFadeOut callback 712 mMediaControlView.postDelayed(mFadeOut, mShowControllerIntervalMs); 713 } 714 } 715 mPrevWidth = currWidth; 716 mPrevHeight = currHeight; 717 } 718 } 719 } 720 721 /////////////////////////////////////////////////// 722 // Implements VideoViewInterfaceWithMp1.SurfaceListener 723 /////////////////////////////////////////////////// 724 725 @Override 726 public void onSurfaceCreated(View view, int width, int height) { 727 if (DEBUG) { 728 Log.d(TAG, "onSurfaceCreated(). mCurrentState=" + mCurrentState 729 + ", mTargetState=" + mTargetState + ", width/height: " + width + "/" + height 730 + ", " + view.toString()); 731 } 732 if (needToStart()) { 733 mMediaController.getTransportControls().play(); 734 } 735 } 736 737 @Override 738 public void onSurfaceDestroyed(View view) { 739 if (DEBUG) { 740 Log.d(TAG, "onSurfaceDestroyed(). mCurrentState=" + mCurrentState 741 + ", mTargetState=" + mTargetState + ", " + view.toString()); 742 } 743 } 744 745 @Override 746 public void onSurfaceChanged(View view, int width, int height) { 747 if (DEBUG) { 748 Log.d(TAG, "onSurfaceChanged(). width/height: " + width + "/" + height 749 + ", " + view.toString()); 750 } 751 } 752 753 @Override 754 public void onSurfaceTakeOverDone(VideoViewInterfaceWithMp1 view) { 755 if (DEBUG) { 756 Log.d(TAG, "onSurfaceTakeOverDone(). Now current view is: " + view); 757 } 758 mCurrentView = view; 759 if (mViewTypeChangedListener != null) { 760 mViewTypeChangedListener.onViewTypeChanged(mInstance, view.getViewType()); 761 } 762 if (needToStart()) { 763 mMediaController.getTransportControls().play(); 764 } 765 } 766 767 /////////////////////////////////////////////////// 768 // Protected or private methods 769 /////////////////////////////////////////////////// 770 771 private void attachMediaControlView() { 772 // Get MediaController from MediaSession and set it inside MediaControlView 773 mMediaControlView.setController(mMediaSession.getController()); 774 775 LayoutParams params = 776 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 777 mInstance.addView(mMediaControlView, params); 778 } 779 780 protected boolean isInPlaybackState() { 781 return (mMediaPlayer != null || mRoutePlayer != null) 782 && mCurrentState != STATE_ERROR 783 && mCurrentState != STATE_IDLE 784 && mCurrentState != STATE_PREPARING; 785 } 786 787 private boolean needToStart() { 788 return (mMediaPlayer != null || mRoutePlayer != null) 789 && isAudioGranted() 790 && isWaitingPlayback(); 791 } 792 793 private boolean isMusicMediaType() { 794 return mVideoTrackIndices != null && mVideoTrackIndices.size() == 0; 795 } 796 797 private boolean isWaitingPlayback() { 798 return mCurrentState != STATE_PLAYING && mTargetState == STATE_PLAYING; 799 } 800 801 private boolean isAudioGranted() { 802 return mAudioFocused || mAudioFocusType == AudioManager.AUDIOFOCUS_NONE; 803 } 804 805 private AudioManager.OnAudioFocusChangeListener mAudioFocusListener = 806 new AudioManager.OnAudioFocusChangeListener() { 807 @Override 808 public void onAudioFocusChange(int focusChange) { 809 switch (focusChange) { 810 case AudioManager.AUDIOFOCUS_GAIN: 811 mAudioFocused = true; 812 if (needToStart()) { 813 mMediaController.getTransportControls().play(); 814 } 815 break; 816 case AudioManager.AUDIOFOCUS_LOSS: 817 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 818 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 819 mAudioFocused = false; 820 if (isInPlaybackState() && mMediaPlayer.isPlaying()) { 821 mMediaController.getTransportControls().pause(); 822 } else { 823 mTargetState = STATE_PAUSED; 824 } 825 } 826 } 827 }; 828 829 @SuppressWarnings("deprecation") 830 private void requestAudioFocus(int focusType) { 831 int result; 832 if (android.os.Build.VERSION.SDK_INT >= 26) { 833 AudioFocusRequest focusRequest; 834 focusRequest = new AudioFocusRequest.Builder(focusType) 835 .setAudioAttributes(mAudioAttributes) 836 .setOnAudioFocusChangeListener(mAudioFocusListener) 837 .build(); 838 result = mAudioManager.requestAudioFocus(focusRequest); 839 } else { 840 result = mAudioManager.requestAudioFocus(mAudioFocusListener, 841 AudioManager.STREAM_MUSIC, 842 focusType); 843 } 844 if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) { 845 mAudioFocused = false; 846 } else if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 847 mAudioFocused = true; 848 } else if (result == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) { 849 mAudioFocused = false; 850 } 851 } 852 853 // Creates a MediaPlayer instance and prepare playback. 854 private void openVideo(Uri uri, Map<String, String> headers) { 855 resetPlayer(); 856 mUri = uri; 857 if (isRemotePlayback()) { 858 // TODO: b/77556429 859 mRoutePlayer.openVideo(uri); 860 return; 861 } 862 863 try { 864 Log.d(TAG, "openVideo(): creating new MediaPlayer instance."); 865 mMediaPlayer = new MediaPlayer(); 866 final Context context = mInstance.getContext(); 867 setupMediaPlayer(context, uri, headers); 868 869 // we don't set the target state here either, but preserve the 870 // target state that was there before. 871 mCurrentState = STATE_PREPARING; 872 mMediaPlayer.prepareAsync(); 873 874 // Save file name as title since the file may not have a title Metadata. 875 mTitle = uri.getPath(); 876 String scheme = uri.getScheme(); 877 if (scheme != null && scheme.equals("file")) { 878 mTitle = uri.getLastPathSegment(); 879 mRetriever = new MediaMetadataRetriever(); 880 mRetriever.setDataSource(context, uri); 881 } 882 883 if (DEBUG) { 884 Log.d(TAG, "openVideo(). mCurrentState=" + mCurrentState 885 + ", mTargetState=" + mTargetState); 886 } 887 } catch (IOException | IllegalArgumentException ex) { 888 Log.w(TAG, "Unable to open content: " + uri, ex); 889 mCurrentState = STATE_ERROR; 890 mTargetState = STATE_ERROR; 891 mErrorListener.onError(mMediaPlayer, 892 MediaPlayer.MEDIA_ERROR_UNKNOWN, MediaPlayer.MEDIA_ERROR_IO); 893 } 894 } 895 896 /** 897 * Used in openVideo(). Setup MediaPlayer and related objects before calling prepare. 898 */ 899 protected void setupMediaPlayer(Context context, Uri uri, Map<String, String> headers) 900 throws IOException { 901 mSurfaceView.setMediaPlayer(mMediaPlayer); 902 mTextureView.setMediaPlayer(mMediaPlayer); 903 mCurrentView.assignSurfaceToMediaPlayer(mMediaPlayer); 904 905 mMediaPlayer.setOnPreparedListener(mPreparedListener); 906 mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); 907 mMediaPlayer.setOnCompletionListener(mCompletionListener); 908 mMediaPlayer.setOnSeekCompleteListener(mSeekCompleteListener); 909 mMediaPlayer.setOnErrorListener(mErrorListener); 910 mMediaPlayer.setOnInfoListener(mInfoListener); 911 mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); 912 913 mCurrentBufferPercentage = -1; 914 mMediaPlayer.setDataSource(context, uri, headers); 915 mMediaPlayer.setAudioAttributes(mAudioAttributes); 916 } 917 918 /* 919 * Reset the media player in any state 920 */ 921 @SuppressWarnings("deprecation") 922 private void resetPlayer() { 923 if (mMediaPlayer != null) { 924 mMediaPlayer.reset(); 925 mMediaPlayer.release(); 926 mMediaPlayer = null; 927 mTextureView.setMediaPlayer(null); 928 mSurfaceView.setMediaPlayer(null); 929 mCurrentState = STATE_IDLE; 930 mTargetState = STATE_IDLE; 931 if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) { 932 mAudioManager.abandonAudioFocus(null); 933 } 934 } 935 mVideoWidth = 0; 936 mVideoHeight = 0; 937 } 938 939 private void updatePlaybackState() { 940 if (mStateBuilder == null) { 941 long playbackActions = PlaybackStateCompat.ACTION_PLAY 942 | PlaybackStateCompat.ACTION_PAUSE 943 | PlaybackStateCompat.ACTION_REWIND | PlaybackStateCompat.ACTION_FAST_FORWARD 944 | PlaybackStateCompat.ACTION_SEEK_TO; 945 mStateBuilder = new PlaybackStateCompat.Builder(); 946 mStateBuilder.setActions(playbackActions); 947 948 if (mCustomActionList != null) { 949 for (PlaybackStateCompat.CustomAction action : mCustomActionList) { 950 mStateBuilder.addCustomAction(action); 951 } 952 } 953 } 954 mStateBuilder.setState(getCorrespondingPlaybackState(), 955 mMediaPlayer.getCurrentPosition(), mSpeed); 956 if (mCurrentState != STATE_ERROR 957 && mCurrentState != STATE_IDLE 958 && mCurrentState != STATE_PREPARING) { 959 if (mCurrentBufferPercentage == -1) { 960 mStateBuilder.setBufferedPosition(-1); 961 } else { 962 mStateBuilder.setBufferedPosition( 963 (long) (mCurrentBufferPercentage / 100.0 * mMediaPlayer.getDuration())); 964 } 965 } 966 967 // Set PlaybackState for MediaSession 968 if (mMediaSession != null) { 969 PlaybackStateCompat state = mStateBuilder.build(); 970 mMediaSession.setPlaybackState(state); 971 } 972 } 973 974 private int getCorrespondingPlaybackState() { 975 switch (mCurrentState) { 976 case STATE_ERROR: 977 return PlaybackStateCompat.STATE_ERROR; 978 case STATE_IDLE: 979 return PlaybackStateCompat.STATE_NONE; 980 case STATE_PREPARING: 981 return PlaybackStateCompat.STATE_CONNECTING; 982 case STATE_PREPARED: 983 return PlaybackStateCompat.STATE_PAUSED; 984 case STATE_PLAYING: 985 return PlaybackStateCompat.STATE_PLAYING; 986 case STATE_PAUSED: 987 return PlaybackStateCompat.STATE_PAUSED; 988 case STATE_PLAYBACK_COMPLETED: 989 return PlaybackStateCompat.STATE_STOPPED; 990 default: 991 return -1; 992 } 993 } 994 995 private final Runnable mFadeOut = new Runnable() { 996 @Override 997 public void run() { 998 if (mCurrentState == STATE_PLAYING) { 999 mMediaControlView.setVisibility(View.GONE); 1000 } 1001 } 1002 }; 1003 1004 private void showController() { 1005 if (mMediaControlView == null || !isInPlaybackState() 1006 || (isMusicMediaType() && mSizeType == SIZE_TYPE_FULL)) { 1007 return; 1008 } 1009 mMediaControlView.removeCallbacks(mFadeOut); 1010 mMediaControlView.setVisibility(View.VISIBLE); 1011 if (mShowControllerIntervalMs != 0 1012 && !mAccessibilityManager.isTouchExplorationEnabled()) { 1013 mMediaControlView.postDelayed(mFadeOut, mShowControllerIntervalMs); 1014 } 1015 } 1016 1017 private void toggleMediaControlViewVisibility() { 1018 if (mMediaControlView.getVisibility() == View.VISIBLE) { 1019 mMediaControlView.removeCallbacks(mFadeOut); 1020 mMediaControlView.setVisibility(View.GONE); 1021 } else { 1022 showController(); 1023 } 1024 } 1025 1026 private void applySpeed() { 1027 if (android.os.Build.VERSION.SDK_INT < 23) { 1028 return; 1029 } 1030 PlaybackParams params = mMediaPlayer.getPlaybackParams().allowDefaults(); 1031 if (mSpeed != params.getSpeed()) { 1032 try { 1033 params.setSpeed(mSpeed); 1034 mMediaPlayer.setPlaybackParams(params); 1035 mFallbackSpeed = mSpeed; 1036 } catch (IllegalArgumentException e) { 1037 Log.e(TAG, "PlaybackParams has unsupported value: " + e); 1038 float fallbackSpeed = mMediaPlayer.getPlaybackParams().allowDefaults().getSpeed(); 1039 if (fallbackSpeed > 0.0f) { 1040 mFallbackSpeed = fallbackSpeed; 1041 } 1042 mSpeed = mFallbackSpeed; 1043 } 1044 } 1045 } 1046 1047 private boolean isRemotePlayback() { 1048 if (mMediaController == null) { 1049 return false; 1050 } 1051 PlaybackInfo playbackInfo = mMediaController.getPlaybackInfo(); 1052 return playbackInfo != null 1053 && playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE; 1054 } 1055 1056 protected void extractTracks() { 1057 MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo(); 1058 mVideoTrackIndices = new ArrayList<>(); 1059 mAudioTrackIndices = new ArrayList<>(); 1060 for (int i = 0; i < trackInfos.length; ++i) { 1061 int trackType = trackInfos[i].getTrackType(); 1062 if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO) { 1063 mVideoTrackIndices.add(i); 1064 } else if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO) { 1065 mAudioTrackIndices.add(i); 1066 } 1067 } 1068 // Select first tracks as default 1069 if (mVideoTrackIndices.size() > 0) { 1070 mSelectedVideoTrackIndex = 0; 1071 } 1072 if (mAudioTrackIndices.size() > 0) { 1073 mSelectedAudioTrackIndex = 0; 1074 } 1075 1076 Bundle data = new Bundle(); 1077 data.putInt(MediaControlView2.KEY_VIDEO_TRACK_COUNT, mVideoTrackIndices.size()); 1078 data.putInt(MediaControlView2.KEY_AUDIO_TRACK_COUNT, mAudioTrackIndices.size()); 1079 mMediaSession.sendSessionEvent(MediaControlView2.EVENT_UPDATE_TRACK_STATUS, data); 1080 } 1081 1082 protected void doShowSubtitleCommand(Bundle args) { 1083 // No-op 1084 } 1085 1086 protected void doHideSubtitleCommand() { 1087 // No-op 1088 } 1089 1090 private void extractMetadata() { 1091 if (mRetriever == null) { 1092 return; 1093 } 1094 // Get and set duration and title values as MediaMetadata for MediaControlView2 1095 MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); 1096 String title = mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE); 1097 if (title != null) { 1098 mTitle = title; 1099 } 1100 builder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, mTitle); 1101 builder.putLong( 1102 MediaMetadataCompat.METADATA_KEY_DURATION, mMediaPlayer.getDuration()); 1103 1104 if (mMediaSession != null) { 1105 mMediaSession.setMetadata(builder.build()); 1106 } 1107 } 1108 1109 @SuppressWarnings("deprecation") 1110 private void extractAudioMetadata() { 1111 if (mRetriever == null || !isMusicMediaType()) { 1112 return; 1113 } 1114 1115 mResources = mInstance.getResources(); 1116 mManager = (WindowManager) mInstance.getContext().getApplicationContext() 1117 .getSystemService(Context.WINDOW_SERVICE); 1118 1119 byte[] album = mRetriever.getEmbeddedPicture(); 1120 if (album != null) { 1121 Bitmap bitmap = BitmapFactory.decodeByteArray(album, 0, album.length); 1122 mMusicAlbumDrawable = new BitmapDrawable(bitmap); 1123 1124 Palette.Builder builder = Palette.from(bitmap); 1125 builder.generate(new Palette.PaletteAsyncListener() { 1126 @Override 1127 public void onGenerated(Palette palette) { 1128 mDominantColor = palette.getDominantColor(0); 1129 if (mMusicView != null) { 1130 mMusicView.setBackgroundColor(mDominantColor); 1131 } 1132 } 1133 }); 1134 } else { 1135 mMusicAlbumDrawable = mResources.getDrawable(R.drawable.ic_default_album_image); 1136 } 1137 1138 String title = mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE); 1139 if (title != null) { 1140 mMusicTitleText = title; 1141 } else { 1142 mMusicTitleText = mResources.getString(R.string.mcv2_music_title_unknown_text); 1143 } 1144 1145 String artist = mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST); 1146 if (artist != null) { 1147 mMusicArtistText = artist; 1148 } else { 1149 mMusicArtistText = mResources.getString(R.string.mcv2_music_artist_unknown_text); 1150 } 1151 1152 // Send title and artist string to MediaControlView2 1153 MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); 1154 builder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, mMusicTitleText); 1155 builder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, mMusicArtistText); 1156 mMediaSession.setMetadata(builder.build()); 1157 1158 // Display Embedded mode as default 1159 mInstance.removeView(mSurfaceView); 1160 mInstance.removeView(mTextureView); 1161 inflateMusicView(R.layout.embedded_music); 1162 } 1163 1164 private int retrieveOrientation() { 1165 DisplayMetrics dm = Resources.getSystem().getDisplayMetrics(); 1166 int width = dm.widthPixels; 1167 int height = dm.heightPixels; 1168 1169 return (height > width) 1170 ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT 1171 : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 1172 } 1173 1174 private void inflateMusicView(int layoutId) { 1175 mInstance.removeView(mMusicView); 1176 1177 LayoutInflater inflater = (LayoutInflater) mInstance.getContext() 1178 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 1179 View v = inflater.inflate(layoutId, null); 1180 v.setBackgroundColor(mDominantColor); 1181 1182 ImageView albumView = v.findViewById(R.id.album); 1183 if (albumView != null) { 1184 albumView.setImageDrawable(mMusicAlbumDrawable); 1185 } 1186 1187 TextView titleView = v.findViewById(R.id.title); 1188 if (titleView != null) { 1189 titleView.setText(mMusicTitleText); 1190 } 1191 1192 TextView artistView = v.findViewById(R.id.artist); 1193 if (artistView != null) { 1194 artistView.setText(mMusicArtistText); 1195 } 1196 1197 mMusicView = v; 1198 mInstance.addView(mMusicView, 0); 1199 } 1200 1201 private MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener = 1202 new MediaPlayer.OnVideoSizeChangedListener() { 1203 @Override 1204 public void onVideoSizeChanged( 1205 MediaPlayer mp, int width, int height) { 1206 if (DEBUG) { 1207 Log.d(TAG, "onVideoSizeChanged(): size: " + width + "/" + height); 1208 } 1209 mVideoWidth = mp.getVideoWidth(); 1210 mVideoHeight = mp.getVideoHeight(); 1211 if (DEBUG) { 1212 Log.d(TAG, "onVideoSizeChanged(): mVideoSize:" + mVideoWidth + "/" 1213 + mVideoHeight); 1214 } 1215 if (mVideoWidth != 0 && mVideoHeight != 0) { 1216 mInstance.requestLayout(); 1217 } 1218 } 1219 }; 1220 1221 private MediaPlayer.OnPreparedListener mPreparedListener = 1222 new MediaPlayer.OnPreparedListener() { 1223 @Override 1224 public void onPrepared(MediaPlayer mp) { 1225 if (DEBUG) { 1226 Log.d(TAG, "OnPreparedListener(). mCurrentState=" + mCurrentState 1227 + ", mTargetState=" + mTargetState); 1228 } 1229 mCurrentState = STATE_PREPARED; 1230 // Create and set playback state for MediaControlView2 1231 updatePlaybackState(); 1232 1233 if (mMediaSession != null) { 1234 extractTracks(); 1235 extractMetadata(); 1236 extractAudioMetadata(); 1237 } 1238 1239 if (mMediaControlView != null) { 1240 mMediaControlView.setEnabled(true); 1241 } 1242 int videoWidth = mp.getVideoWidth(); 1243 int videoHeight = mp.getVideoHeight(); 1244 1245 // mSeekWhenPrepared may be changed after seekTo() call 1246 long seekToPosition = mSeekWhenPrepared; 1247 if (seekToPosition != 0) { 1248 mMediaController.getTransportControls().seekTo(seekToPosition); 1249 } 1250 1251 if (videoWidth != 0 && videoHeight != 0) { 1252 if (videoWidth != mVideoWidth || videoHeight != mVideoHeight) { 1253 mVideoWidth = videoWidth; 1254 mVideoHeight = videoHeight; 1255 mInstance.requestLayout(); 1256 } 1257 1258 if (needToStart()) { 1259 mMediaController.getTransportControls().play(); 1260 } 1261 } else { 1262 // We don't know the video size yet, but should start anyway. 1263 // The video size might be reported to us later. 1264 if (needToStart()) { 1265 mMediaController.getTransportControls().play(); 1266 } 1267 } 1268 // Get and set duration and title values as MediaMetadata for MediaControlView2 1269 MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); 1270 1271 builder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, mTitle); 1272 builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, mMediaPlayer.getDuration()); 1273 1274 if (mMediaSession != null) { 1275 mMediaSession.setMetadata(builder.build()); 1276 1277 if (mNeedUpdateMediaType) { 1278 mMediaSession.sendSessionEvent( 1279 MediaControlView2.EVENT_UPDATE_MEDIA_TYPE_STATUS, mMediaTypeData); 1280 mNeedUpdateMediaType = false; 1281 } 1282 } 1283 } 1284 }; 1285 1286 private MediaPlayer.OnSeekCompleteListener mSeekCompleteListener = 1287 new MediaPlayer.OnSeekCompleteListener() { 1288 @Override 1289 public void onSeekComplete(MediaPlayer mp) { 1290 updatePlaybackState(); 1291 } 1292 }; 1293 1294 private MediaPlayer.OnCompletionListener mCompletionListener = 1295 new MediaPlayer.OnCompletionListener() { 1296 @Override 1297 @SuppressWarnings("deprecation") 1298 public void onCompletion(MediaPlayer mp) { 1299 mCurrentState = STATE_PLAYBACK_COMPLETED; 1300 mTargetState = STATE_PLAYBACK_COMPLETED; 1301 updatePlaybackState(); 1302 if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) { 1303 mAudioManager.abandonAudioFocus(null); 1304 } 1305 } 1306 }; 1307 1308 private MediaPlayer.OnInfoListener mInfoListener = new MediaPlayer.OnInfoListener() { 1309 @Override 1310 public boolean onInfo(MediaPlayer mp, int what, int extra) { 1311 if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) { 1312 extractTracks(); 1313 } 1314 return true; 1315 } 1316 }; 1317 1318 private MediaPlayer.OnErrorListener mErrorListener = new MediaPlayer.OnErrorListener() { 1319 @Override 1320 public boolean onError(MediaPlayer mp, int frameworkErr, int implErr) { 1321 if (DEBUG) { 1322 Log.d(TAG, "Error: " + frameworkErr + "," + implErr); 1323 } 1324 mCurrentState = STATE_ERROR; 1325 mTargetState = STATE_ERROR; 1326 updatePlaybackState(); 1327 1328 if (mMediaControlView != null) { 1329 mMediaControlView.setVisibility(View.GONE); 1330 } 1331 return true; 1332 } 1333 }; 1334 1335 private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = 1336 new MediaPlayer.OnBufferingUpdateListener() { 1337 @Override 1338 public void onBufferingUpdate(MediaPlayer mp, int percent) { 1339 mCurrentBufferPercentage = percent; 1340 updatePlaybackState(); 1341 } 1342 }; 1343 1344 private class MediaSessionCallback extends MediaSessionCompat.Callback { 1345 @Override 1346 public void onCommand(String command, Bundle args, ResultReceiver receiver) { 1347 if (isRemotePlayback()) { 1348 mRoutePlayer.onCommand(command, args, receiver); 1349 } else { 1350 switch (command) { 1351 case MediaControlView2.COMMAND_SHOW_SUBTITLE: 1352 doShowSubtitleCommand(args); 1353 break; 1354 case MediaControlView2.COMMAND_HIDE_SUBTITLE: 1355 doHideSubtitleCommand(); 1356 break; 1357 case MediaControlView2.COMMAND_SELECT_AUDIO_TRACK: 1358 int audioIndex = args.getInt(MediaControlView2.KEY_SELECTED_AUDIO_INDEX, 1359 INVALID_TRACK_INDEX); 1360 if (audioIndex != INVALID_TRACK_INDEX) { 1361 int audioTrackIndex = mAudioTrackIndices.get(audioIndex); 1362 if (audioTrackIndex != mSelectedAudioTrackIndex) { 1363 mSelectedAudioTrackIndex = audioTrackIndex; 1364 mMediaPlayer.selectTrack(mSelectedAudioTrackIndex); 1365 } 1366 } 1367 break; 1368 case MediaControlView2.COMMAND_SET_PLAYBACK_SPEED: 1369 float speed = args.getFloat( 1370 MediaControlView2.KEY_PLAYBACK_SPEED, INVALID_SPEED); 1371 if (speed != INVALID_SPEED && speed != mSpeed) { 1372 setSpeed(speed); 1373 mSpeed = speed; 1374 } 1375 break; 1376 case MediaControlView2.COMMAND_MUTE: 1377 mVolumeLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 1378 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0); 1379 break; 1380 case MediaControlView2.COMMAND_UNMUTE: 1381 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mVolumeLevel, 0); 1382 break; 1383 } 1384 } 1385 showController(); 1386 } 1387 1388 @Override 1389 public void onCustomAction(final String action, final Bundle extras) { 1390 mCustomActionListenerRecord.first.execute(new Runnable() { 1391 @Override 1392 public void run() { 1393 mCustomActionListenerRecord.second.onCustomAction(action, extras); 1394 } 1395 }); 1396 showController(); 1397 } 1398 1399 @Override 1400 public void onPlay() { 1401 if (!isAudioGranted()) { 1402 requestAudioFocus(mAudioFocusType); 1403 } 1404 1405 if ((isInPlaybackState() && mCurrentView.hasAvailableSurface()) || isMusicMediaType()) { 1406 if (isRemotePlayback()) { 1407 mRoutePlayer.onPlay(); 1408 } else { 1409 applySpeed(); 1410 mMediaPlayer.start(); 1411 mCurrentState = STATE_PLAYING; 1412 updatePlaybackState(); 1413 } 1414 mCurrentState = STATE_PLAYING; 1415 } 1416 mTargetState = STATE_PLAYING; 1417 if (DEBUG) { 1418 Log.d(TAG, "onPlay(). mCurrentState=" + mCurrentState 1419 + ", mTargetState=" + mTargetState); 1420 } 1421 showController(); 1422 } 1423 1424 @Override 1425 public void onPause() { 1426 if (isInPlaybackState()) { 1427 if (isRemotePlayback()) { 1428 mRoutePlayer.onPlay(); 1429 mCurrentState = STATE_PAUSED; 1430 } else if (mMediaPlayer.isPlaying()) { 1431 mMediaPlayer.pause(); 1432 mCurrentState = STATE_PAUSED; 1433 updatePlaybackState(); 1434 } 1435 } 1436 mTargetState = STATE_PAUSED; 1437 if (DEBUG) { 1438 Log.d(TAG, "onPause(). mCurrentState=" + mCurrentState 1439 + ", mTargetState=" + mTargetState); 1440 } 1441 showController(); 1442 } 1443 1444 @Override 1445 public void onSeekTo(long pos) { 1446 if (isInPlaybackState()) { 1447 if (isRemotePlayback()) { 1448 mRoutePlayer.onPlay(); 1449 } else { 1450 if (android.os.Build.VERSION.SDK_INT < 26) { 1451 mMediaPlayer.seekTo((int) pos); 1452 } else { 1453 mMediaPlayer.seekTo(pos, MediaPlayer.SEEK_PREVIOUS_SYNC); 1454 } 1455 mSeekWhenPrepared = 0; 1456 } 1457 } else { 1458 mSeekWhenPrepared = pos; 1459 } 1460 showController(); 1461 } 1462 1463 @Override 1464 public void onStop() { 1465 if (isRemotePlayback()) { 1466 mRoutePlayer.onPlay(); 1467 } else { 1468 resetPlayer(); 1469 } 1470 showController(); 1471 } 1472 } 1473 } 1474