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