ContentVideoView.java revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
1// Copyright 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.content.browser; 6 7import android.app.Activity; 8import android.app.AlertDialog; 9import android.content.Context; 10import android.content.DialogInterface; 11import android.util.Log; 12import android.view.Gravity; 13import android.view.KeyEvent; 14import android.view.Surface; 15import android.view.SurfaceHolder; 16import android.view.SurfaceView; 17import android.view.View; 18import android.view.ViewGroup; 19import android.widget.FrameLayout; 20import android.widget.LinearLayout; 21import android.widget.ProgressBar; 22import android.widget.TextView; 23 24import org.chromium.base.CalledByNative; 25import org.chromium.base.JNINamespace; 26import org.chromium.base.ThreadUtils; 27import org.chromium.ui.base.ViewAndroid; 28import org.chromium.ui.base.ViewAndroidDelegate; 29import org.chromium.ui.base.WindowAndroid; 30 31/** 32 * This class implements accelerated fullscreen video playback using surface view. 33 */ 34@JNINamespace("content") 35public class ContentVideoView extends FrameLayout 36 implements SurfaceHolder.Callback, ViewAndroidDelegate { 37 38 private static final String TAG = "ContentVideoView"; 39 40 /* Do not change these values without updating their counterparts 41 * in include/media/mediaplayer.h! 42 */ 43 private static final int MEDIA_NOP = 0; // interface test message 44 private static final int MEDIA_PREPARED = 1; 45 private static final int MEDIA_PLAYBACK_COMPLETE = 2; 46 private static final int MEDIA_BUFFERING_UPDATE = 3; 47 private static final int MEDIA_SEEK_COMPLETE = 4; 48 private static final int MEDIA_SET_VIDEO_SIZE = 5; 49 private static final int MEDIA_ERROR = 100; 50 private static final int MEDIA_INFO = 200; 51 52 /** 53 * Keep these error codes in sync with the code we defined in 54 * MediaPlayerListener.java. 55 */ 56 public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 2; 57 public static final int MEDIA_ERROR_INVALID_CODE = 3; 58 59 // all possible internal states 60 private static final int STATE_ERROR = -1; 61 private static final int STATE_IDLE = 0; 62 private static final int STATE_PLAYING = 1; 63 private static final int STATE_PAUSED = 2; 64 private static final int STATE_PLAYBACK_COMPLETED = 3; 65 66 private SurfaceHolder mSurfaceHolder; 67 private int mVideoWidth; 68 private int mVideoHeight; 69 private int mDuration; 70 71 // Native pointer to C++ ContentVideoView object. 72 private long mNativeContentVideoView; 73 74 // webkit should have prepared the media 75 private int mCurrentState = STATE_IDLE; 76 77 // Strings for displaying media player errors 78 private String mPlaybackErrorText; 79 private String mUnknownErrorText; 80 private String mErrorButton; 81 private String mErrorTitle; 82 private String mVideoLoadingText; 83 84 // This view will contain the video. 85 private VideoSurfaceView mVideoSurfaceView; 86 87 // Progress view when the video is loading. 88 private View mProgressView; 89 90 // The ViewAndroid is used to keep screen on during video playback. 91 private ViewAndroid mViewAndroid; 92 93 private final ContentVideoViewClient mClient; 94 95 private class VideoSurfaceView extends SurfaceView { 96 97 public VideoSurfaceView(Context context) { 98 super(context); 99 } 100 101 @Override 102 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 103 // set the default surface view size to (1, 1) so that it won't block 104 // the infobar. (0, 0) is not a valid size for surface view. 105 int width = 1; 106 int height = 1; 107 if (mVideoWidth > 0 && mVideoHeight > 0) { 108 width = getDefaultSize(mVideoWidth, widthMeasureSpec); 109 height = getDefaultSize(mVideoHeight, heightMeasureSpec); 110 if (mVideoWidth * height > width * mVideoHeight) { 111 height = width * mVideoHeight / mVideoWidth; 112 } else if (mVideoWidth * height < width * mVideoHeight) { 113 width = height * mVideoWidth / mVideoHeight; 114 } 115 } 116 setMeasuredDimension(width, height); 117 } 118 } 119 120 private static class ProgressView extends LinearLayout { 121 122 private final ProgressBar mProgressBar; 123 private final TextView mTextView; 124 125 public ProgressView(Context context, String videoLoadingText) { 126 super(context); 127 setOrientation(LinearLayout.VERTICAL); 128 setLayoutParams(new LinearLayout.LayoutParams( 129 LinearLayout.LayoutParams.WRAP_CONTENT, 130 LinearLayout.LayoutParams.WRAP_CONTENT)); 131 mProgressBar = new ProgressBar(context, null, android.R.attr.progressBarStyleLarge); 132 mTextView = new TextView(context); 133 mTextView.setText(videoLoadingText); 134 addView(mProgressBar); 135 addView(mTextView); 136 } 137 } 138 139 private final Runnable mExitFullscreenRunnable = new Runnable() { 140 @Override 141 public void run() { 142 exitFullscreen(true); 143 } 144 }; 145 146 protected ContentVideoView(Context context, long nativeContentVideoView, 147 ContentVideoViewClient client) { 148 super(context); 149 mNativeContentVideoView = nativeContentVideoView; 150 mViewAndroid = new ViewAndroid(new WindowAndroid(context.getApplicationContext()), this); 151 mClient = client; 152 initResources(context); 153 mVideoSurfaceView = new VideoSurfaceView(context); 154 showContentVideoView(); 155 setVisibility(View.VISIBLE); 156 } 157 158 protected ContentVideoViewClient getContentVideoViewClient() { 159 return mClient; 160 } 161 162 private void initResources(Context context) { 163 if (mPlaybackErrorText != null) return; 164 mPlaybackErrorText = context.getString( 165 org.chromium.content.R.string.media_player_error_text_invalid_progressive_playback); 166 mUnknownErrorText = context.getString( 167 org.chromium.content.R.string.media_player_error_text_unknown); 168 mErrorButton = context.getString( 169 org.chromium.content.R.string.media_player_error_button); 170 mErrorTitle = context.getString( 171 org.chromium.content.R.string.media_player_error_title); 172 mVideoLoadingText = context.getString( 173 org.chromium.content.R.string.media_player_loading_video); 174 } 175 176 protected void showContentVideoView() { 177 mVideoSurfaceView.getHolder().addCallback(this); 178 this.addView(mVideoSurfaceView, new FrameLayout.LayoutParams( 179 ViewGroup.LayoutParams.WRAP_CONTENT, 180 ViewGroup.LayoutParams.WRAP_CONTENT, 181 Gravity.CENTER)); 182 183 mProgressView = mClient.getVideoLoadingProgressView(); 184 if (mProgressView == null) { 185 mProgressView = new ProgressView(getContext(), mVideoLoadingText); 186 } 187 this.addView(mProgressView, new FrameLayout.LayoutParams( 188 ViewGroup.LayoutParams.WRAP_CONTENT, 189 ViewGroup.LayoutParams.WRAP_CONTENT, 190 Gravity.CENTER)); 191 } 192 193 protected SurfaceView getSurfaceView() { 194 return mVideoSurfaceView; 195 } 196 197 @CalledByNative 198 public void onMediaPlayerError(int errorType) { 199 Log.d(TAG, "OnMediaPlayerError: " + errorType); 200 if (mCurrentState == STATE_ERROR || mCurrentState == STATE_PLAYBACK_COMPLETED) { 201 return; 202 } 203 204 // Ignore some invalid error codes. 205 if (errorType == MEDIA_ERROR_INVALID_CODE) { 206 return; 207 } 208 209 mCurrentState = STATE_ERROR; 210 211 /* Pop up an error dialog so the user knows that 212 * something bad has happened. Only try and pop up the dialog 213 * if we're attached to a window. When we're going away and no 214 * longer have a window, don't bother showing the user an error. 215 * 216 * TODO(qinmin): We need to review whether this Dialog is OK with 217 * the rest of the browser UI elements. 218 */ 219 if (getWindowToken() != null) { 220 String message; 221 222 if (errorType == MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { 223 message = mPlaybackErrorText; 224 } else { 225 message = mUnknownErrorText; 226 } 227 228 try { 229 new AlertDialog.Builder(getContext()) 230 .setTitle(mErrorTitle) 231 .setMessage(message) 232 .setPositiveButton(mErrorButton, 233 new DialogInterface.OnClickListener() { 234 @Override 235 public void onClick(DialogInterface dialog, int whichButton) { 236 /* Inform that the video is over. 237 */ 238 onCompletion(); 239 } 240 }) 241 .setCancelable(false) 242 .show(); 243 } catch (RuntimeException e) { 244 Log.e(TAG, "Cannot show the alert dialog, error message: " + message, e); 245 } 246 } 247 } 248 249 @CalledByNative 250 private void onVideoSizeChanged(int width, int height) { 251 mVideoWidth = width; 252 mVideoHeight = height; 253 // This will trigger the SurfaceView.onMeasure() call. 254 mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight); 255 } 256 257 @CalledByNative 258 protected void onBufferingUpdate(int percent) { 259 } 260 261 @CalledByNative 262 private void onPlaybackComplete() { 263 onCompletion(); 264 } 265 266 @CalledByNative 267 protected void onUpdateMediaMetadata( 268 int videoWidth, 269 int videoHeight, 270 int duration, 271 boolean canPause, 272 boolean canSeekBack, 273 boolean canSeekForward) { 274 mDuration = duration; 275 mProgressView.setVisibility(View.GONE); 276 mCurrentState = isPlaying() ? STATE_PLAYING : STATE_PAUSED; 277 onVideoSizeChanged(videoWidth, videoHeight); 278 } 279 280 @Override 281 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 282 } 283 284 @Override 285 public void surfaceCreated(SurfaceHolder holder) { 286 mSurfaceHolder = holder; 287 openVideo(); 288 } 289 290 @Override 291 public void surfaceDestroyed(SurfaceHolder holder) { 292 if (mNativeContentVideoView != 0) { 293 nativeSetSurface(mNativeContentVideoView, null); 294 } 295 mSurfaceHolder = null; 296 post(mExitFullscreenRunnable); 297 } 298 299 @CalledByNative 300 protected void openVideo() { 301 if (mSurfaceHolder != null) { 302 mCurrentState = STATE_IDLE; 303 if (mNativeContentVideoView != 0) { 304 nativeRequestMediaMetadata(mNativeContentVideoView); 305 nativeSetSurface(mNativeContentVideoView, 306 mSurfaceHolder.getSurface()); 307 } 308 } 309 } 310 311 protected void onCompletion() { 312 mCurrentState = STATE_PLAYBACK_COMPLETED; 313 } 314 315 316 protected boolean isInPlaybackState() { 317 return (mCurrentState != STATE_ERROR && mCurrentState != STATE_IDLE); 318 } 319 320 protected void start() { 321 if (isInPlaybackState()) { 322 if (mNativeContentVideoView != 0) { 323 nativePlay(mNativeContentVideoView); 324 } 325 mCurrentState = STATE_PLAYING; 326 } 327 } 328 329 protected void pause() { 330 if (isInPlaybackState()) { 331 if (isPlaying()) { 332 if (mNativeContentVideoView != 0) { 333 nativePause(mNativeContentVideoView); 334 } 335 mCurrentState = STATE_PAUSED; 336 } 337 } 338 } 339 340 // cache duration as mDuration for faster access 341 protected int getDuration() { 342 if (isInPlaybackState()) { 343 if (mDuration > 0) { 344 return mDuration; 345 } 346 if (mNativeContentVideoView != 0) { 347 mDuration = nativeGetDurationInMilliSeconds(mNativeContentVideoView); 348 } else { 349 mDuration = 0; 350 } 351 return mDuration; 352 } 353 mDuration = -1; 354 return mDuration; 355 } 356 357 protected int getCurrentPosition() { 358 if (isInPlaybackState() && mNativeContentVideoView != 0) { 359 return nativeGetCurrentPosition(mNativeContentVideoView); 360 } 361 return 0; 362 } 363 364 protected void seekTo(int msec) { 365 if (mNativeContentVideoView != 0) { 366 nativeSeekTo(mNativeContentVideoView, msec); 367 } 368 } 369 370 public boolean isPlaying() { 371 return mNativeContentVideoView != 0 && nativeIsPlaying(mNativeContentVideoView); 372 } 373 374 @CalledByNative 375 private static ContentVideoView createContentVideoView( 376 Context context, long nativeContentVideoView, ContentVideoViewClient client, 377 boolean legacy) { 378 ThreadUtils.assertOnUiThread(); 379 // The context needs be Activity to create the ContentVideoView correctly. 380 if (!(context instanceof Activity)) { 381 Log.w(TAG, "Wrong type of context, can't create fullscreen video"); 382 return null; 383 } 384 ContentVideoView videoView = null; 385 if (legacy) { 386 videoView = new ContentVideoViewLegacy(context, nativeContentVideoView, client); 387 } else { 388 videoView = new ContentVideoView(context, nativeContentVideoView, client); 389 } 390 391 if (videoView.getContentVideoViewClient().onShowCustomView(videoView)) { 392 return videoView; 393 } 394 return null; 395 } 396 397 public void removeSurfaceView() { 398 removeView(mVideoSurfaceView); 399 removeView(mProgressView); 400 mVideoSurfaceView = null; 401 mProgressView = null; 402 } 403 404 public void exitFullscreen(boolean relaseMediaPlayer) { 405 destroyContentVideoView(false); 406 if (mNativeContentVideoView != 0) { 407 nativeExitFullscreen(mNativeContentVideoView, relaseMediaPlayer); 408 mNativeContentVideoView = 0; 409 } 410 } 411 412 @CalledByNative 413 private void onExitFullscreen() { 414 exitFullscreen(false); 415 } 416 417 /** 418 * This method shall only be called by native and exitFullscreen, 419 * To exit fullscreen, use exitFullscreen in Java. 420 */ 421 @CalledByNative 422 protected void destroyContentVideoView(boolean nativeViewDestroyed) { 423 if (mVideoSurfaceView != null) { 424 removeSurfaceView(); 425 setVisibility(View.GONE); 426 427 // To prevent re-entrance, call this after removeSurfaceView. 428 mClient.onDestroyContentVideoView(); 429 } 430 if (nativeViewDestroyed) { 431 mNativeContentVideoView = 0; 432 } 433 } 434 435 public static ContentVideoView getContentVideoView() { 436 return nativeGetSingletonJavaContentVideoView(); 437 } 438 439 @Override 440 public boolean onKeyUp(int keyCode, KeyEvent event) { 441 if (keyCode == KeyEvent.KEYCODE_BACK) { 442 exitFullscreen(false); 443 return true; 444 } 445 return super.onKeyUp(keyCode, event); 446 } 447 448 @Override 449 public View acquireAnchorView() { 450 View anchorView = new View(getContext()); 451 addView(anchorView); 452 return anchorView; 453 } 454 455 @Override 456 public void setAnchorViewPosition(View view, float x, float y, float width, float height) { 457 Log.e(TAG, "setAnchorViewPosition isn't implemented"); 458 } 459 460 @Override 461 public void releaseAnchorView(View anchorView) { 462 removeView(anchorView); 463 } 464 465 @CalledByNative 466 private long getNativeViewAndroid() { 467 return mViewAndroid.getNativePointer(); 468 } 469 470 private static native ContentVideoView nativeGetSingletonJavaContentVideoView(); 471 private native void nativeExitFullscreen(long nativeContentVideoView, 472 boolean relaseMediaPlayer); 473 private native int nativeGetCurrentPosition(long nativeContentVideoView); 474 private native int nativeGetDurationInMilliSeconds(long nativeContentVideoView); 475 private native void nativeRequestMediaMetadata(long nativeContentVideoView); 476 private native int nativeGetVideoWidth(long nativeContentVideoView); 477 private native int nativeGetVideoHeight(long nativeContentVideoView); 478 private native boolean nativeIsPlaying(long nativeContentVideoView); 479 private native void nativePause(long nativeContentVideoView); 480 private native void nativePlay(long nativeContentVideoView); 481 private native void nativeSeekTo(long nativeContentVideoView, int msec); 482 private native void nativeSetSurface(long nativeContentVideoView, Surface surface); 483} 484