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 com.android.widget;
18
19import android.content.Context;
20import android.graphics.SurfaceTexture;
21import android.media.MediaPlayer2;
22import android.support.annotation.NonNull;
23import android.support.annotation.RequiresApi;
24import android.util.AttributeSet;
25import android.util.Log;
26import android.view.Surface;
27import android.view.TextureView;
28import android.view.View;
29
30import static android.widget.VideoView2.VIEW_TYPE_TEXTUREVIEW;
31
32@RequiresApi(26)
33class VideoTextureView extends TextureView
34        implements VideoViewInterface, TextureView.SurfaceTextureListener {
35    private static final String TAG = "VideoTextureView";
36    private static final boolean DEBUG = true; // STOPSHIP: Log.isLoggable(TAG, Log.DEBUG);
37
38    private SurfaceTexture mSurfaceTexture;
39    private Surface mSurface;
40    private SurfaceListener mSurfaceListener;
41    private MediaPlayer2 mMediaPlayer;
42    // A flag to indicate taking over other view should be proceed.
43    private boolean mIsTakingOverOldView;
44    private VideoViewInterface mOldView;
45
46    public VideoTextureView(Context context) {
47        this(context, null);
48    }
49
50    public VideoTextureView(Context context, AttributeSet attrs) {
51        this(context, attrs, 0);
52    }
53
54    public VideoTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
55        this(context, attrs, defStyleAttr, 0);
56    }
57
58    public VideoTextureView(
59            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
60        super(context, attrs, defStyleAttr, defStyleRes);
61        setSurfaceTextureListener(this);
62    }
63
64    ////////////////////////////////////////////////////
65    // implements VideoViewInterface
66    ////////////////////////////////////////////////////
67
68    @Override
69    public boolean assignSurfaceToMediaPlayer(MediaPlayer2 mp) {
70        Log.d(TAG, "assignSurfaceToMediaPlayer(): mSurfaceTexture: " + mSurfaceTexture);
71        if (mp == null || !hasAvailableSurface()) {
72            // Surface is not ready.
73            return false;
74        }
75        mp.setSurface(mSurface);
76        return true;
77    }
78
79    @Override
80    public void setSurfaceListener(SurfaceListener l) {
81        mSurfaceListener = l;
82    }
83
84    @Override
85    public int getViewType() {
86        return VIEW_TYPE_TEXTUREVIEW;
87    }
88
89    @Override
90    public void setMediaPlayer(MediaPlayer2 mp) {
91        mMediaPlayer = mp;
92        if (mIsTakingOverOldView) {
93            takeOver(mOldView);
94        }
95    }
96
97    @Override
98    public void takeOver(@NonNull VideoViewInterface oldView) {
99        if (assignSurfaceToMediaPlayer(mMediaPlayer)) {
100            ((View) oldView).setVisibility(GONE);
101            mIsTakingOverOldView = false;
102            mOldView = null;
103            if (mSurfaceListener != null) {
104                mSurfaceListener.onSurfaceTakeOverDone(this);
105            }
106        } else {
107            mIsTakingOverOldView = true;
108            mOldView = oldView;
109        }
110    }
111
112    @Override
113    public boolean hasAvailableSurface() {
114        return (mSurfaceTexture != null && !mSurfaceTexture.isReleased() && mSurface != null);
115    }
116
117    ////////////////////////////////////////////////////
118    // implements TextureView.SurfaceTextureListener
119    ////////////////////////////////////////////////////
120
121    @Override
122    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
123        Log.d(TAG, "onSurfaceTextureAvailable: mSurfaceTexture: " + mSurfaceTexture
124                + ", new surface: " + surfaceTexture);
125        mSurfaceTexture = surfaceTexture;
126        mSurface = new Surface(mSurfaceTexture);
127        if (mIsTakingOverOldView) {
128            takeOver(mOldView);
129        } else {
130            assignSurfaceToMediaPlayer(mMediaPlayer);
131        }
132        if (mSurfaceListener != null) {
133            mSurfaceListener.onSurfaceCreated(this, width, height);
134        }
135    }
136
137    @Override
138    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
139        if (mSurfaceListener != null) {
140            mSurfaceListener.onSurfaceChanged(this, width, height);
141        }
142        // requestLayout();  // TODO: figure out if it should be called here?
143    }
144
145    @Override
146    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
147        // no-op
148    }
149
150    @Override
151    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
152        if (mSurfaceListener != null) {
153            mSurfaceListener.onSurfaceDestroyed(this);
154        }
155        mSurfaceTexture = null;
156        mSurface = null;
157        return true;
158    }
159
160    @Override
161    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
162        int videoWidth = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoWidth();
163        int videoHeight = (mMediaPlayer == null) ? 0 : mMediaPlayer.getVideoHeight();
164        if (DEBUG) {
165            Log.d(TAG, "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
166                    + MeasureSpec.toString(heightMeasureSpec) + ")");
167            Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
168            Log.i(TAG, " viewSize: " + getWidth() + "/" + getHeight());
169            Log.i(TAG, " mVideoWidth/height: " + videoWidth + ", " + videoHeight);
170        }
171
172        int width = getDefaultSize(videoWidth, widthMeasureSpec);
173        int height = getDefaultSize(videoHeight, heightMeasureSpec);
174
175        if (videoWidth > 0 && videoHeight > 0) {
176            int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
177            int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
178
179            width = widthSpecSize;
180            height = heightSpecSize;
181
182            // for compatibility, we adjust size based on aspect ratio
183            if (videoWidth * height < width * videoHeight) {
184                width = height * videoWidth / videoHeight;
185                if (DEBUG) {
186                    Log.d(TAG, "image too wide, correcting. width: " + width);
187                }
188            } else if (videoWidth * height > width * videoHeight) {
189                height = width * videoHeight / videoWidth;
190                if (DEBUG) {
191                    Log.d(TAG, "image too tall, correcting. height: " + height);
192                }
193            }
194        } else {
195            // no size yet, just adopt the given spec sizes
196        }
197        setMeasuredDimension(width, height);
198        if (DEBUG) {
199            Log.i(TAG, "end of onMeasure()");
200            Log.i(TAG, " measuredSize: " + getMeasuredWidth() + "/" + getMeasuredHeight());
201        }
202    }
203
204    @Override
205    public String toString() {
206        return "ViewType: TextureView / Visibility: " + getVisibility()
207                + " / surfaceTexture: " + mSurfaceTexture;
208
209    }
210}
211