1 2package android.webkit; 3 4import android.content.Context; 5import android.media.MediaPlayer; 6import android.media.Metadata; 7import android.view.Gravity; 8import android.view.MotionEvent; 9import android.view.SurfaceHolder; 10import android.view.SurfaceView; 11import android.view.View; 12import android.view.ViewGroup; 13import android.widget.FrameLayout; 14import android.widget.MediaController; 15import android.widget.MediaController.MediaPlayerControl; 16 17 18/** 19 * @hide This is only used by the browser 20 */ 21public class HTML5VideoFullScreen extends HTML5VideoView 22 implements MediaPlayerControl, MediaPlayer.OnPreparedListener, 23 View.OnTouchListener { 24 25 // Add this sub-class to handle the resizing when rotating screen. 26 private class VideoSurfaceView extends SurfaceView { 27 28 public VideoSurfaceView(Context context) { 29 super(context); 30 } 31 32 @Override 33 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 34 int width = getDefaultSize(mVideoWidth, widthMeasureSpec); 35 int height = getDefaultSize(mVideoHeight, heightMeasureSpec); 36 if (mVideoWidth > 0 && mVideoHeight > 0) { 37 if ( mVideoWidth * height > width * mVideoHeight ) { 38 height = width * mVideoHeight / mVideoWidth; 39 } else if ( mVideoWidth * height < width * mVideoHeight ) { 40 width = height * mVideoWidth / mVideoHeight; 41 } 42 } 43 setMeasuredDimension(width, height); 44 } 45 } 46 47 // This view will contain the video. 48 private VideoSurfaceView mVideoSurfaceView; 49 50 // We need the full screen state to decide which surface to render to and 51 // when to create the MediaPlayer accordingly. 52 static final int FULLSCREEN_OFF = 0; 53 static final int FULLSCREEN_SURFACECREATING = 1; 54 static final int FULLSCREEN_SURFACECREATED = 2; 55 56 private int mFullScreenMode; 57 // The Media Controller only used for full screen mode 58 private MediaController mMediaController; 59 60 // SurfaceHolder for full screen 61 private SurfaceHolder mSurfaceHolder = null; 62 63 // Data only for MediaController 64 private boolean mCanSeekBack; 65 private boolean mCanSeekForward; 66 private boolean mCanPause; 67 private int mCurrentBufferPercentage; 68 69 // The progress view. 70 private static View mProgressView; 71 // The container for the progress view and video view 72 private static FrameLayout mLayout; 73 74 // The video size will be ready when prepared. Used to make sure the aspect 75 // ratio is correct. 76 private int mVideoWidth; 77 private int mVideoHeight; 78 79 SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback() 80 { 81 public void surfaceChanged(SurfaceHolder holder, int format, 82 int w, int h) 83 { 84 if (mPlayer != null && mMediaController != null 85 && mCurrentState == STATE_PREPARED) { 86 if (mMediaController.isShowing()) { 87 // ensure the controller will get repositioned later 88 mMediaController.hide(); 89 } 90 mMediaController.show(); 91 } 92 } 93 94 public void surfaceCreated(SurfaceHolder holder) 95 { 96 mSurfaceHolder = holder; 97 mFullScreenMode = FULLSCREEN_SURFACECREATED; 98 99 prepareForFullScreen(); 100 } 101 102 public void surfaceDestroyed(SurfaceHolder holder) 103 { 104 // After we return from this we can't use the surface any more. 105 // The current Video View will be destroy when we play a new video. 106 pauseAndDispatch(mProxy); 107 // TODO: handle full screen->inline mode transition without a reload. 108 mPlayer.release(); 109 mPlayer = null; 110 mSurfaceHolder = null; 111 if (mMediaController != null) { 112 mMediaController.hide(); 113 } 114 } 115 }; 116 117 MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener = 118 new MediaPlayer.OnVideoSizeChangedListener() { 119 @Override 120 public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { 121 mVideoWidth = mp.getVideoWidth(); 122 mVideoHeight = mp.getVideoHeight(); 123 if (mVideoWidth != 0 && mVideoHeight != 0) { 124 mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight); 125 } 126 } 127 }; 128 129 private SurfaceView getSurfaceView() { 130 return mVideoSurfaceView; 131 } 132 133 HTML5VideoFullScreen(Context context, int videoLayerId, int position, boolean skipPrepare) { 134 mVideoSurfaceView = new VideoSurfaceView(context); 135 mFullScreenMode = FULLSCREEN_OFF; 136 mVideoWidth = 0; 137 mVideoHeight = 0; 138 init(videoLayerId, position, skipPrepare); 139 } 140 141 private void setMediaController(MediaController m) { 142 mMediaController = m; 143 attachMediaController(); 144 } 145 146 private void attachMediaController() { 147 if (mPlayer != null && mMediaController != null) { 148 mMediaController.setMediaPlayer(this); 149 mMediaController.setAnchorView(mVideoSurfaceView); 150 //Will be enabled when prepared 151 mMediaController.setEnabled(false); 152 } 153 } 154 155 @Override 156 public void decideDisplayMode() { 157 mPlayer.setDisplay(mSurfaceHolder); 158 } 159 160 private void prepareForFullScreen() { 161 MediaController mc = new FullScreenMediaController(mProxy.getContext(), mLayout); 162 mc.setSystemUiVisibility(mLayout.getSystemUiVisibility()); 163 setMediaController(mc); 164 mPlayer.setScreenOnWhilePlaying(true); 165 mPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); 166 prepareDataAndDisplayMode(mProxy); 167 } 168 169 170 private void toggleMediaControlsVisiblity() { 171 if (mMediaController.isShowing()) { 172 mMediaController.hide(); 173 } else { 174 mMediaController.show(); 175 } 176 } 177 178 @Override 179 public void onPrepared(MediaPlayer mp) { 180 super.onPrepared(mp); 181 182 mVideoSurfaceView.setOnTouchListener(this); 183 // Get the capabilities of the player for this stream 184 Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL, 185 MediaPlayer.BYPASS_METADATA_FILTER); 186 if (data != null) { 187 mCanPause = !data.has(Metadata.PAUSE_AVAILABLE) 188 || data.getBoolean(Metadata.PAUSE_AVAILABLE); 189 mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE) 190 || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE); 191 mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE) 192 || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE); 193 } else { 194 mCanPause = mCanSeekBack = mCanSeekForward = true; 195 } 196 197 if (mProgressView != null) { 198 mProgressView.setVisibility(View.GONE); 199 } 200 201 mVideoWidth = mp.getVideoWidth(); 202 mVideoHeight = mp.getVideoHeight(); 203 // This will trigger the onMeasure to get the display size right. 204 mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight); 205 // Call into the native to ask for the state, if still in play mode, 206 // this will trigger the video to play. 207 mProxy.dispatchOnRestoreState(); 208 209 if (getStartWhenPrepared()) { 210 mPlayer.start(); 211 // Clear the flag. 212 setStartWhenPrepared(false); 213 } 214 215 // mMediaController status depends on the Metadata result, so put it 216 // after reading the MetaData. 217 // And make sure mPlayer state is updated before showing the controller. 218 if (mMediaController != null) { 219 mMediaController.setEnabled(true); 220 mMediaController.show(); 221 } 222 } 223 224 public boolean fullScreenExited() { 225 return (mLayout == null); 226 } 227 228 private final WebChromeClient.CustomViewCallback mCallback = 229 new WebChromeClient.CustomViewCallback() { 230 public void onCustomViewHidden() { 231 // It listens to SurfaceHolder.Callback.SurfaceDestroyed event 232 // which happens when the video view is detached from its parent 233 // view. This happens in the WebChromeClient before this method 234 // is invoked. 235 mProxy.dispatchOnStopFullScreen(); 236 mLayout.removeView(getSurfaceView()); 237 238 if (mProgressView != null) { 239 mLayout.removeView(mProgressView); 240 mProgressView = null; 241 } 242 mLayout = null; 243 // Re enable plugin views. 244 mProxy.getWebView().getViewManager().showAll(); 245 246 mProxy = null; 247 248 // Don't show the controller after exiting the full screen. 249 mMediaController = null; 250 mCurrentState = STATE_RESETTED; 251 } 252 }; 253 254 @Override 255 public void enterFullScreenVideoState(int layerId, 256 HTML5VideoViewProxy proxy, WebViewClassic webView) { 257 mFullScreenMode = FULLSCREEN_SURFACECREATING; 258 mCurrentBufferPercentage = 0; 259 mPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); 260 mProxy = proxy; 261 262 mVideoSurfaceView.getHolder().addCallback(mSHCallback); 263 mVideoSurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 264 mVideoSurfaceView.setFocusable(true); 265 mVideoSurfaceView.setFocusableInTouchMode(true); 266 mVideoSurfaceView.requestFocus(); 267 268 // Create a FrameLayout that will contain the VideoView and the 269 // progress view (if any). 270 mLayout = new FrameLayout(mProxy.getContext()); 271 FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( 272 ViewGroup.LayoutParams.WRAP_CONTENT, 273 ViewGroup.LayoutParams.WRAP_CONTENT, 274 Gravity.CENTER); 275 276 mLayout.addView(getSurfaceView(), layoutParams); 277 278 mLayout.setVisibility(View.VISIBLE); 279 WebChromeClient client = webView.getWebChromeClient(); 280 if (client != null) { 281 client.onShowCustomView(mLayout, mCallback); 282 // Plugins like Flash will draw over the video so hide 283 // them while we're playing. 284 if (webView.getViewManager() != null) 285 webView.getViewManager().hideAll(); 286 287 mProgressView = client.getVideoLoadingProgressView(); 288 if (mProgressView != null) { 289 mLayout.addView(mProgressView, layoutParams); 290 mProgressView.setVisibility(View.VISIBLE); 291 } 292 } 293 } 294 295 /** 296 * @return true when we are in full screen mode, even the surface not fully 297 * created. 298 */ 299 public boolean isFullScreenMode() { 300 return true; 301 } 302 303 // MediaController FUNCTIONS: 304 @Override 305 public boolean canPause() { 306 return mCanPause; 307 } 308 309 @Override 310 public boolean canSeekBackward() { 311 return mCanSeekBack; 312 } 313 314 @Override 315 public boolean canSeekForward() { 316 return mCanSeekForward; 317 } 318 319 @Override 320 public int getBufferPercentage() { 321 if (mPlayer != null) { 322 return mCurrentBufferPercentage; 323 } 324 return 0; 325 } 326 327 @Override 328 public void showControllerInFullScreen() { 329 if (mMediaController != null) { 330 mMediaController.show(0); 331 } 332 } 333 334 // Other listeners functions: 335 private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = 336 new MediaPlayer.OnBufferingUpdateListener() { 337 public void onBufferingUpdate(MediaPlayer mp, int percent) { 338 mCurrentBufferPercentage = percent; 339 } 340 }; 341 342 @Override 343 public boolean onTouch(View v, MotionEvent event) { 344 if (mFullScreenMode >= FULLSCREEN_SURFACECREATED 345 && mMediaController != null) { 346 toggleMediaControlsVisiblity(); 347 } 348 return false; 349 } 350 351 @Override 352 protected void switchProgressView(boolean playerBuffering) { 353 if (mProgressView != null) { 354 if (playerBuffering) { 355 mProgressView.setVisibility(View.VISIBLE); 356 } else { 357 mProgressView.setVisibility(View.GONE); 358 } 359 } 360 return; 361 } 362 363 static class FullScreenMediaController extends MediaController { 364 365 View mVideoView; 366 367 public FullScreenMediaController(Context context, View video) { 368 super(context); 369 mVideoView = video; 370 } 371 372 @Override 373 public void show() { 374 super.show(); 375 if (mVideoView != null) { 376 mVideoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); 377 } 378 } 379 380 @Override 381 public void hide() { 382 if (mVideoView != null) { 383 mVideoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE 384 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); 385 } 386 super.hide(); 387 } 388 389 } 390 391} 392