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