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