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.graphics.SurfaceTexture;
21 import android.media.MediaPlayer2;
22 import android.support.annotation.NonNull;
23 import android.support.annotation.RequiresApi;
24 import android.util.AttributeSet;
25 import android.util.Log;
26 import android.view.Surface;
27 import android.view.TextureView;
28 import android.view.View;
29 
30 import static android.widget.VideoView2.VIEW_TYPE_TEXTUREVIEW;
31 
32 @RequiresApi(26)
33 class 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 
VideoTextureView(Context context)46     public VideoTextureView(Context context) {
47         this(context, null);
48     }
49 
VideoTextureView(Context context, AttributeSet attrs)50     public VideoTextureView(Context context, AttributeSet attrs) {
51         this(context, attrs, 0);
52     }
53 
VideoTextureView(Context context, AttributeSet attrs, int defStyleAttr)54     public VideoTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
55         this(context, attrs, defStyleAttr, 0);
56     }
57 
VideoTextureView( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)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
assignSurfaceToMediaPlayer(MediaPlayer2 mp)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
setSurfaceListener(SurfaceListener l)80     public void setSurfaceListener(SurfaceListener l) {
81         mSurfaceListener = l;
82     }
83 
84     @Override
getViewType()85     public int getViewType() {
86         return VIEW_TYPE_TEXTUREVIEW;
87     }
88 
89     @Override
setMediaPlayer(MediaPlayer2 mp)90     public void setMediaPlayer(MediaPlayer2 mp) {
91         mMediaPlayer = mp;
92         if (mIsTakingOverOldView) {
93             takeOver(mOldView);
94         }
95     }
96 
97     @Override
takeOver(@onNull VideoViewInterface oldView)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
hasAvailableSurface()113     public boolean hasAvailableSurface() {
114         return (mSurfaceTexture != null && !mSurfaceTexture.isReleased() && mSurface != null);
115     }
116 
117     ////////////////////////////////////////////////////
118     // implements TextureView.SurfaceTextureListener
119     ////////////////////////////////////////////////////
120 
121     @Override
onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height)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
onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height)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
onSurfaceTextureUpdated(SurfaceTexture surface)146     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
147         // no-op
148     }
149 
150     @Override
onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture)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
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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
toString()205     public String toString() {
206         return "ViewType: TextureView / Visibility: " + getVisibility()
207                 + " / surfaceTexture: " + mSurfaceTexture;
208 
209     }
210 }
211