Home | History | Annotate | Download | only in impl
      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