• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.car.developeroptions.widget;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.content.res.TypedArray;
22 import android.graphics.SurfaceTexture;
23 import android.media.MediaPlayer;
24 import android.net.Uri;
25 import android.util.AttributeSet;
26 import android.util.Log;
27 import android.view.Surface;
28 import android.view.TextureView;
29 import android.view.View;
30 import android.widget.ImageView;
31 
32 import androidx.annotation.VisibleForTesting;
33 import androidx.preference.Preference;
34 import androidx.preference.PreferenceViewHolder;
35 
36 import com.android.car.developeroptions.R;
37 
38 /**
39  * A full width preference that hosts a MP4 video.
40  */
41 public class VideoPreference extends Preference {
42 
43     private static final String TAG = "VideoPreference";
44     private final Context mContext;
45 
46     private Uri mVideoPath;
47     @VisibleForTesting
48     MediaPlayer mMediaPlayer;
49     @VisibleForTesting
50     boolean mAnimationAvailable;
51     @VisibleForTesting
52     boolean mVideoReady;
53     private boolean mVideoPaused;
54     private float mAspectRadio = 1.0f;
55     private int mPreviewResource;
56     private boolean mViewVisible;
57     private Surface mSurface;
58     private int mAnimationId;
59 
VideoPreference(Context context)60     public VideoPreference(Context context) {
61         super(context);
62         mContext = context;
63         initialize(context, null);
64     }
65 
VideoPreference(Context context, AttributeSet attrs)66     public VideoPreference(Context context, AttributeSet attrs) {
67         super(context, attrs);
68         mContext = context;
69         initialize(context, attrs);
70     }
71 
initialize(Context context, AttributeSet attrs)72     private void initialize(Context context, AttributeSet attrs) {
73         TypedArray attributes = context.getTheme().obtainStyledAttributes(
74                 attrs,
75                 R.styleable.VideoPreference,
76                 0, 0);
77         try {
78             // if these are already set that means they were set dynamically and don't need
79             // to be loaded from xml
80             mAnimationId = mAnimationId == 0
81                 ? attributes.getResourceId(R.styleable.VideoPreference_animation, 0)
82                 : mAnimationId;
83             mVideoPath = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
84                     .authority(context.getPackageName())
85                     .appendPath(String.valueOf(mAnimationId))
86                     .build();
87             mPreviewResource = mPreviewResource == 0
88                 ? attributes.getResourceId(R.styleable.VideoPreference_preview, 0)
89                 : mPreviewResource;
90             if (mPreviewResource == 0 && mAnimationId == 0) {
91                 return;
92             }
93             initMediaPlayer();
94             if (mMediaPlayer != null && mMediaPlayer.getDuration() > 0) {
95                 setVisible(true);
96                 setLayoutResource(R.layout.video_preference);
97                 mAnimationAvailable = true;
98                 updateAspectRatio();
99             } else {
100                 setVisible(false);
101             }
102         } catch (Exception e) {
103             Log.w(TAG, "Animation resource not found. Will not show animation.");
104         } finally {
105             attributes.recycle();
106         }
107     }
108 
109     @Override
onBindViewHolder(PreferenceViewHolder holder)110     public void onBindViewHolder(PreferenceViewHolder holder) {
111         super.onBindViewHolder(holder);
112 
113         if (!mAnimationAvailable) {
114             return;
115         }
116 
117         final TextureView video = (TextureView) holder.findViewById(R.id.video_texture_view);
118         final ImageView imageView = (ImageView) holder.findViewById(R.id.video_preview_image);
119         final ImageView playButton = (ImageView) holder.findViewById(R.id.video_play_button);
120         final AspectRatioFrameLayout layout = (AspectRatioFrameLayout) holder.findViewById(
121                 R.id.video_container);
122 
123         imageView.setImageResource(mPreviewResource);
124         layout.setAspectRatio(mAspectRadio);
125         updateViewStates(imageView, playButton);
126 
127         video.setOnClickListener(v -> updateViewStates(imageView, playButton));
128 
129         video.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
130             @Override
131             public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width,
132                     int height) {
133                 if (mMediaPlayer != null) {
134                     mSurface = new Surface(surfaceTexture);
135                     mMediaPlayer.setSurface(mSurface);
136                 }
137             }
138 
139             @Override
140             public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width,
141                     int height) {
142             }
143 
144             @Override
145             public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
146                 imageView.setVisibility(View.VISIBLE);
147                 return false;
148             }
149 
150             @Override
151             public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
152                 if (!mViewVisible) {
153                     return;
154                 }
155                 if (mVideoReady) {
156                     if (imageView.getVisibility() == View.VISIBLE) {
157                         imageView.setVisibility(View.GONE);
158                     }
159                     if (!mVideoPaused && mMediaPlayer != null && !mMediaPlayer.isPlaying()) {
160                         mMediaPlayer.start();
161                         playButton.setVisibility(View.GONE);
162                     }
163                 }
164                 if (mMediaPlayer != null && !mMediaPlayer.isPlaying() &&
165                         playButton.getVisibility() != View.VISIBLE) {
166                     playButton.setVisibility(View.VISIBLE);
167                 }
168             }
169         });
170     }
171 
172     @VisibleForTesting
updateViewStates(ImageView imageView, ImageView playButton)173     void updateViewStates(ImageView imageView, ImageView playButton) {
174         if (mMediaPlayer != null) {
175             if (mMediaPlayer.isPlaying()) {
176                 mMediaPlayer.pause();
177                 playButton.setVisibility(View.VISIBLE);
178                 imageView.setVisibility(View.VISIBLE);
179                 mVideoPaused = true;
180             } else {
181                 imageView.setVisibility(View.GONE);
182                 playButton.setVisibility(View.GONE);
183                 mMediaPlayer.start();
184                 mVideoPaused = false;
185             }
186         }
187     }
188 
189     @Override
onDetached()190     public void onDetached() {
191         releaseMediaPlayer();
192         super.onDetached();
193     }
194 
onViewVisible(boolean videoPaused)195     public void onViewVisible(boolean videoPaused) {
196         mViewVisible = true;
197         mVideoPaused = videoPaused;
198         initMediaPlayer();
199     }
200 
onViewInvisible()201     public void onViewInvisible() {
202         mViewVisible = false;
203         releaseMediaPlayer();
204     }
205 
206     /**
207      * Sets the video for this preference. If a previous video was set this one will override it
208      * and properly release any resources and re-initialize the preference to play the new video.
209      *
210      * @param videoId The raw res id of the video
211      * @param previewId The drawable res id of the preview image to use if the video fails to load.
212      */
setVideo(int videoId, int previewId)213     public void setVideo(int videoId, int previewId) {
214         mAnimationId = videoId;
215         mPreviewResource = previewId;
216         releaseMediaPlayer();
217         initialize(mContext, null);
218     }
219 
initMediaPlayer()220     private void initMediaPlayer() {
221         if (mMediaPlayer == null) {
222             mMediaPlayer = MediaPlayer.create(mContext, mVideoPath);
223             // when the playback res is invalid or others, MediaPlayer create may fail
224             // and return null, so need add the null judgement.
225             if (mMediaPlayer != null) {
226                 mMediaPlayer.seekTo(0);
227                 mMediaPlayer.setOnSeekCompleteListener(mp -> mVideoReady = true);
228                 mMediaPlayer.setOnPreparedListener(mediaPlayer -> mediaPlayer.setLooping(true));
229                 if (mSurface != null) {
230                     mMediaPlayer.setSurface(mSurface);
231                 }
232             }
233         }
234     }
235 
releaseMediaPlayer()236     private void releaseMediaPlayer() {
237         if (mMediaPlayer != null) {
238             mMediaPlayer.stop();
239             mMediaPlayer.reset();
240             mMediaPlayer.release();
241             mMediaPlayer = null;
242             mVideoReady = false;
243         }
244     }
245 
isVideoPaused()246     public boolean isVideoPaused() {
247         return mVideoPaused;
248     }
249 
250     @VisibleForTesting
updateAspectRatio()251     void updateAspectRatio() {
252         mAspectRadio = mMediaPlayer.getVideoWidth() / (float) mMediaPlayer.getVideoHeight();
253     }
254 }
255