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