• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
VideoView2Impl(VideoView2 instance, ViewGroupProvider superProvider, ViewGroupProvider privateProvider)252     public VideoView2Impl(VideoView2 instance,
253             ViewGroupProvider superProvider, ViewGroupProvider privateProvider) {
254         super(instance, superProvider, privateProvider);
255         mInstance = instance;
256     }
257 
258     @Override
initialize(@ullable AttributeSet attrs, int defStyleAttr, int defStyleRes)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
setMediaControlView2_impl(MediaControlView2 mediaControlView, long intervalMs)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
getMediaController_impl()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
getMediaSessionToken_impl()352     public SessionToken2 getMediaSessionToken_impl() {
353         // TODO: implement this
354         return null;
355     }
356 
357     @Override
getMediaControlView2_impl()358     public MediaControlView2 getMediaControlView2_impl() {
359         return mMediaControlView;
360     }
361 
362     @Override
getMediaMetadata_impl()363     public MediaMetadata2 getMediaMetadata_impl() {
364         return mMediaMetadata;
365     }
366 
367     @Override
setMediaMetadata_impl(MediaMetadata2 metadata)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
setSubtitleEnabled_impl(boolean enable)389     public void setSubtitleEnabled_impl(boolean enable) {
390         if (enable != mSubtitleEnabled) {
391             selectOrDeselectSubtitle(enable);
392         }
393         mSubtitleEnabled = enable;
394     }
395 
396     @Override
isSubtitleEnabled_impl()397     public boolean isSubtitleEnabled_impl() {
398         return mSubtitleEnabled;
399     }
400 
401     // TODO: remove setSpeed_impl once MediaController2 is ready.
402     @Override
setSpeed_impl(float speed)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
setAudioFocusRequest_impl(int focusGain)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
setAudioAttributes_impl(AudioAttributes attributes)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
setVideoPath_impl(String path)436     public void setVideoPath_impl(String path) {
437         mInstance.setVideoUri(Uri.parse(path));
438     }
439 
440     @Override
setVideoUri_impl(Uri uri)441     public void setVideoUri_impl(Uri uri) {
442         mInstance.setVideoUri(uri, null);
443     }
444 
445     @Override
setVideoUri_impl(Uri uri, Map<String, String> headers)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
setMediaItem_impl(MediaItem2 mediaItem)453     public void setMediaItem_impl(MediaItem2 mediaItem) {
454         // TODO: implement this
455     }
456 
457     @Override
setDataSource_impl(DataSourceDesc dsd)458     public void setDataSource_impl(DataSourceDesc dsd) {
459         mDsd = dsd;
460         mSeekWhenPrepared = 0;
461         openVideo(dsd);
462     }
463 
464     @Override
setViewType_impl(int viewType)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
getViewType_impl()485     public int getViewType_impl() {
486         return mCurrentView.getViewType();
487     }
488 
489     @Override
setCustomActions_impl( List<PlaybackState.CustomAction> actionList, Executor executor, VideoView2.OnCustomActionListener listener)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
setOnViewTypeChangedListener_impl(VideoView2.OnViewTypeChangedListener l)502     public void setOnViewTypeChangedListener_impl(VideoView2.OnViewTypeChangedListener l) {
503         mViewTypeChangedListener = l;
504     }
505 
506     @Override
setFullScreenRequestListener_impl(VideoView2.OnFullScreenRequestListener l)507     public void setFullScreenRequestListener_impl(VideoView2.OnFullScreenRequestListener l) {
508         mFullScreenRequestListener = l;
509     }
510 
511     @Override
onAttachedToWindow_impl()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
onDetachedFromWindow_impl()539     public void onDetachedFromWindow_impl() {
540         super.onDetachedFromWindow_impl();
541 
542         mMediaSession.release();
543         mMediaSession = null;
544         mMediaController = null;
545     }
546 
547     @Override
getAccessibilityClassName_impl()548     public CharSequence getAccessibilityClassName_impl() {
549         return VideoView2.class.getName();
550     }
551 
552     @Override
onTouchEvent_impl(MotionEvent ev)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
onTrackballEvent_impl(MotionEvent ev)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
dispatchTouchEvent_impl(MotionEvent ev)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
onMeasure_impl(int widthMeasureSpec, int heightMeasureSpec)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
onSurfaceCreated(View view, int width, int height)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
onSurfaceDestroyed(View view)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
onSurfaceChanged(View view, int width, int height)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
onSurfaceTakeOverDone(VideoViewInterface view)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 
attachMediaControlView()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 
isInPlaybackState()686     private boolean isInPlaybackState() {
687         return (mMediaPlayer != null || mRoutePlayer != null)
688                 && mCurrentState != STATE_ERROR
689                 && mCurrentState != STATE_IDLE
690                 && mCurrentState != STATE_PREPARING;
691     }
692 
needToStart()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.
openVideo(DataSourceDesc dsd)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      */
resetPlayer()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 
updatePlaybackState()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 
getCorrespondingPlaybackState()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 
showController()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 
toggleMediaControlViewVisibility()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 
applySpeed()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 
isRemotePlayback()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 
selectOrDeselectSubtitle(boolean select)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 
extractTracks()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 
extractMetadata()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 
extractAudioMetadata()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 
retrieveOrientation()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 
inflateMusicView(int layoutId)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
onCommand(String command, Bundle args, ResultReceiver receiver)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
onCustomAction(String action, Bundle extras)1333         public void onCustomAction(String action, Bundle extras) {
1334             mCustomActionListenerRecord.first.execute(() ->
1335                     mCustomActionListenerRecord.second.onCustomAction(action, extras));
1336             showController();
1337         }
1338 
1339         @Override
onPlay()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
onPause()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
onSeekTo(long pos)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
onStop()1396         public void onStop() {
1397             if (isRemotePlayback()) {
1398                 mRoutePlayer.onStop();
1399             } else {
1400                 resetPlayer();
1401             }
1402             showController();
1403         }
1404     }
1405 }
1406