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.example.androidx.media; 18 19 import android.annotation.SuppressLint; 20 import android.app.Activity; 21 import android.app.AlertDialog; 22 import android.content.Context; 23 import android.content.DialogInterface; 24 import android.content.Intent; 25 import android.content.res.Configuration; 26 import android.net.Uri; 27 import android.os.Bundle; 28 import android.support.v4.media.session.MediaControllerCompat; 29 import android.support.v4.media.session.PlaybackStateCompat; 30 import android.util.AttributeSet; 31 import android.util.Log; 32 import android.view.KeyEvent; 33 import android.view.MotionEvent; 34 import android.view.View; 35 import android.view.ViewGroup; 36 import android.view.Window; 37 import android.view.WindowManager; 38 import android.widget.Toast; 39 40 import androidx.fragment.app.FragmentActivity; 41 import androidx.media.widget.MediaControlView2; 42 import androidx.media.widget.VideoView2; 43 44 /** 45 * Test application for VideoView2/MediaControlView2 46 */ 47 @SuppressLint("NewApi") 48 public class VideoViewTest extends FragmentActivity { 49 public static final String LOOPING_EXTRA_NAME = 50 "com.example.androidx.media.VideoViewTest.IsLooping"; 51 public static final String USE_TEXTURE_VIEW_EXTRA_NAME = 52 "com.example.androidx.media.VideoViewTest.UseTextureView"; 53 public static final String MEDIA_TYPE_ADVERTISEMENT = 54 "com.example.androidx.media.VideoViewTest.MediaTypeAdvertisement"; 55 private static final String TAG = "VideoViewTest"; 56 57 private MyVideoView mVideoView = null; 58 private float mSpeed = 1.0f; 59 60 private MediaControlView2 mMediaControlView = null; 61 62 private boolean mUseTextureView = false; 63 private int mPrevWidth; 64 private int mPrevHeight; 65 66 @Override 67 public void onCreate(Bundle savedInstanceState) { 68 super.onCreate(savedInstanceState); 69 70 //Remove title bar 71 requestWindowFeature(Window.FEATURE_NO_TITLE); 72 73 setContentView(R.layout.video_activity); 74 75 mVideoView = findViewById(R.id.video_view); 76 mVideoView.setActivity(this); 77 78 String errorString = null; 79 Intent intent = getIntent(); 80 Uri contentUri; 81 if (intent == null || (contentUri = intent.getData()) == null || !contentUri.isAbsolute()) { 82 errorString = "Invalid intent"; 83 } else { 84 mUseTextureView = intent.getBooleanExtra(USE_TEXTURE_VIEW_EXTRA_NAME, false); 85 if (mUseTextureView) { 86 mVideoView.setViewType(VideoView2.VIEW_TYPE_TEXTUREVIEW); 87 } 88 mVideoView.setVideoUri(contentUri); 89 90 mMediaControlView = new MediaControlView2(this); 91 mVideoView.setMediaControlView2(mMediaControlView, 2000); 92 mMediaControlView.setOnFullScreenListener(new FullScreenListener()); 93 } 94 if (errorString != null) { 95 showErrorDialog(errorString); 96 } 97 } 98 99 @Override 100 protected void onResume() { 101 super.onResume(); 102 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 103 if (mVideoView.isAttachedToWindow()) { 104 mVideoView.getMediaController().getTransportControls().play(); 105 mVideoView.getMediaController().registerCallback(mMediaControllerCallback); 106 } else { 107 mVideoView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { 108 @Override 109 public void onViewAttachedToWindow(View v) { 110 mVideoView.getMediaController().getTransportControls().play(); 111 mVideoView.getMediaController().registerCallback(mMediaControllerCallback); 112 } 113 114 @Override 115 public void onViewDetachedFromWindow(View v) { 116 // No need to remove callback here since MediaSession has already been 117 // destroyed. 118 } 119 }); 120 } 121 setTitle(getViewTypeString(mVideoView)); 122 } 123 124 @Override 125 protected void onPause() { 126 mVideoView.getMediaController().getTransportControls().pause(); 127 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 128 super.onPause(); 129 } 130 131 @Override 132 protected void onDestroy() { 133 Log.d(TAG, "onDestroy"); 134 mVideoView.getMediaController().unregisterCallback(mMediaControllerCallback); 135 mVideoView.getMediaController().getTransportControls().stop(); 136 super.onDestroy(); 137 } 138 139 @Override 140 public boolean onTouchEvent(MotionEvent ev) { 141 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 142 int screenWidth = getResources().getDisplayMetrics().widthPixels; 143 if (ev.getRawX() < (screenWidth / 2.0f)) { 144 // TODO: getSpeed() not needed? 145 mSpeed -= 0.1f; 146 } else { 147 mSpeed += 0.1f; 148 } 149 mVideoView.setSpeed(mSpeed); 150 Toast.makeText(this, "speed rate: " + String.format("%.2f", mSpeed), Toast.LENGTH_SHORT) 151 .show(); 152 } 153 return super.onTouchEvent(ev); 154 } 155 156 private void showErrorDialog(String errorMessage) { 157 AlertDialog.Builder builder = new AlertDialog.Builder(this); 158 builder.setTitle("Playback error") 159 .setMessage(errorMessage) 160 .setCancelable(false) 161 .setPositiveButton("OK", 162 new DialogInterface.OnClickListener() { 163 @Override 164 public void onClick(DialogInterface dialogInterface, int i) { 165 finish(); 166 } 167 }).show(); 168 } 169 170 MediaControllerCompat.Callback mMediaControllerCallback = new MediaControllerCompat.Callback() { 171 @Override 172 public void onPlaybackStateChanged(PlaybackStateCompat state) { 173 switch (state.getState()) { 174 case PlaybackStateCompat.STATE_STOPPED: 175 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 176 break; 177 case PlaybackStateCompat.STATE_ERROR: 178 showErrorDialog("Error: (" + state.getErrorMessage() + ")"); 179 break; 180 } 181 } 182 }; 183 184 private class FullScreenListener 185 implements MediaControlView2.OnFullScreenListener { 186 @Override 187 public void onFullScreen(View view, boolean fullScreen) { 188 // TODO: Remove bottom controls after adding back button functionality. 189 if (mPrevHeight == 0 && mPrevWidth == 0) { 190 ViewGroup.LayoutParams params = mVideoView.getLayoutParams(); 191 mPrevWidth = params.width; 192 mPrevHeight = params.height; 193 } 194 195 if (fullScreen) { 196 // Remove notification bar 197 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 198 WindowManager.LayoutParams.FLAG_FULLSCREEN); 199 200 ViewGroup.LayoutParams params = mVideoView.getLayoutParams(); 201 params.width = ViewGroup.LayoutParams.MATCH_PARENT; 202 params.height = ViewGroup.LayoutParams.MATCH_PARENT; 203 mVideoView.setLayoutParams(params); 204 } else { 205 // Restore notification bar 206 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); 207 208 ViewGroup.LayoutParams params = mVideoView.getLayoutParams(); 209 params.width = mPrevWidth; 210 params.height = mPrevHeight; 211 mVideoView.setLayoutParams(params); 212 } 213 mVideoView.requestLayout(); 214 mVideoView.invalidate(); 215 } 216 } 217 218 /** 219 * Extension of the stock android video view used to hook and override 220 * keypress behavior. Mainly used to make sure that certain keystrokes 221 * don't automatically bring up the andriod MediaController widget (which 222 * then steals focus) 223 * 224 * @author johngro (at) google.com (John Grossman) 225 */ 226 public static class MyVideoView extends VideoView2 { 227 private float mDX; 228 private float mDY; 229 private Activity mActivity; 230 231 public MyVideoView(Context context) { 232 super(context); 233 } 234 235 public MyVideoView(Context context, AttributeSet attrs) { 236 super(context, attrs); 237 } 238 239 public MyVideoView(Context context, AttributeSet attrs, int defStyle) { 240 super(context, attrs, defStyle); 241 } 242 243 @Override 244 public boolean onTouchEvent(MotionEvent ev) { 245 switch (ev.getAction()) { 246 case MotionEvent.ACTION_DOWN: 247 mDX = ev.getRawX() - getX(); 248 mDY = ev.getRawY() - getY(); 249 super.onTouchEvent(ev); 250 return true; 251 case MotionEvent.ACTION_MOVE: 252 animate() 253 .x(ev.getRawX() - mDX) 254 .y(ev.getRawY() - mDY) 255 .setDuration(0) 256 .start(); 257 super.onTouchEvent(ev); 258 return true; 259 } 260 return super.onTouchEvent(ev); 261 } 262 263 @Override 264 public boolean onKeyDown(int keyCode, KeyEvent event) { 265 if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { 266 mActivity.finish(); 267 } 268 return true; 269 } 270 271 public void setActivity(Activity activity) { 272 mActivity = activity; 273 } 274 } 275 276 @Override 277 public void onConfigurationChanged(Configuration newConfig) { 278 super.onConfigurationChanged(newConfig); 279 if (mVideoView.getViewType() == VideoView2.VIEW_TYPE_SURFACEVIEW) { 280 mVideoView.setViewType(VideoView2.VIEW_TYPE_TEXTUREVIEW); 281 Toast.makeText(this, "switch to TextureView", Toast.LENGTH_SHORT).show(); 282 setTitle(getViewTypeString(mVideoView)); 283 } else if (mVideoView.getViewType() == VideoView2.VIEW_TYPE_TEXTUREVIEW) { 284 mVideoView.setViewType(VideoView2.VIEW_TYPE_SURFACEVIEW); 285 Toast.makeText(this, "switch to SurfaceView", Toast.LENGTH_SHORT).show(); 286 setTitle(getViewTypeString(mVideoView)); 287 } 288 } 289 290 private String getViewTypeString(VideoView2 videoView) { 291 if (videoView == null) { 292 return "Unknown"; 293 } 294 int type = videoView.getViewType(); 295 if (type == VideoView2.VIEW_TYPE_SURFACEVIEW) { 296 return "SurfaceView"; 297 } else if (type == VideoView2.VIEW_TYPE_TEXTUREVIEW) { 298 return "TextureView"; 299 } 300 return "Unknown"; 301 } 302 } 303