ContentVideoView.java revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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 int width = getDefaultSize(mVideoWidth, widthMeasureSpec); 104 int height = getDefaultSize(mVideoHeight, heightMeasureSpec); 105 if (mVideoWidth > 0 && mVideoHeight > 0) { 106 if (mVideoWidth * height > width * mVideoHeight) { 107 height = width * mVideoHeight / mVideoWidth; 108 } else if (mVideoWidth * height < width * mVideoHeight) { 109 width = height * mVideoWidth / mVideoHeight; 110 } 111 } 112 setMeasuredDimension(width, height); 113 } 114 } 115 116 private static class ProgressView extends LinearLayout { 117 118 private final ProgressBar mProgressBar; 119 private final TextView mTextView; 120 121 public ProgressView(Context context, String videoLoadingText) { 122 super(context); 123 setOrientation(LinearLayout.VERTICAL); 124 setLayoutParams(new LinearLayout.LayoutParams( 125 LinearLayout.LayoutParams.WRAP_CONTENT, 126 LinearLayout.LayoutParams.WRAP_CONTENT)); 127 mProgressBar = new ProgressBar(context, null, android.R.attr.progressBarStyleLarge); 128 mTextView = new TextView(context); 129 mTextView.setText(videoLoadingText); 130 addView(mProgressBar); 131 addView(mTextView); 132 } 133 } 134 135 private final Runnable mExitFullscreenRunnable = new Runnable() { 136 @Override 137 public void run() { 138 exitFullscreen(true); 139 } 140 }; 141 142 protected ContentVideoView(Context context, long nativeContentVideoView, 143 ContentVideoViewClient client) { 144 super(context); 145 mNativeContentVideoView = nativeContentVideoView; 146 mViewAndroid = new ViewAndroid(new WindowAndroid(context.getApplicationContext()), this); 147 mClient = client; 148 initResources(context); 149 mVideoSurfaceView = new VideoSurfaceView(context); 150 showContentVideoView(); 151 setVisibility(View.VISIBLE); 152 mClient.onShowCustomView(this); 153 } 154 155 private void initResources(Context context) { 156 if (mPlaybackErrorText != null) return; 157 mPlaybackErrorText = context.getString( 158 org.chromium.content.R.string.media_player_error_text_invalid_progressive_playback); 159 mUnknownErrorText = context.getString( 160 org.chromium.content.R.string.media_player_error_text_unknown); 161 mErrorButton = context.getString( 162 org.chromium.content.R.string.media_player_error_button); 163 mErrorTitle = context.getString( 164 org.chromium.content.R.string.media_player_error_title); 165 mVideoLoadingText = context.getString( 166 org.chromium.content.R.string.media_player_loading_video); 167 } 168 169 protected void showContentVideoView() { 170 mVideoSurfaceView.getHolder().addCallback(this); 171 this.addView(mVideoSurfaceView, new FrameLayout.LayoutParams( 172 ViewGroup.LayoutParams.MATCH_PARENT, 173 ViewGroup.LayoutParams.MATCH_PARENT, 174 Gravity.CENTER)); 175 176 mProgressView = mClient.getVideoLoadingProgressView(); 177 if (mProgressView == null) { 178 mProgressView = new ProgressView(getContext(), mVideoLoadingText); 179 } 180 this.addView(mProgressView, new FrameLayout.LayoutParams( 181 ViewGroup.LayoutParams.WRAP_CONTENT, 182 ViewGroup.LayoutParams.WRAP_CONTENT, 183 Gravity.CENTER)); 184 } 185 186 protected SurfaceView getSurfaceView() { 187 return mVideoSurfaceView; 188 } 189 190 @CalledByNative 191 public void onMediaPlayerError(int errorType) { 192 Log.d(TAG, "OnMediaPlayerError: " + errorType); 193 if (mCurrentState == STATE_ERROR || mCurrentState == STATE_PLAYBACK_COMPLETED) { 194 return; 195 } 196 197 // Ignore some invalid error codes. 198 if (errorType == MEDIA_ERROR_INVALID_CODE) { 199 return; 200 } 201 202 mCurrentState = STATE_ERROR; 203 204 /* Pop up an error dialog so the user knows that 205 * something bad has happened. Only try and pop up the dialog 206 * if we're attached to a window. When we're going away and no 207 * longer have a window, don't bother showing the user an error. 208 * 209 * TODO(qinmin): We need to review whether this Dialog is OK with 210 * the rest of the browser UI elements. 211 */ 212 if (getWindowToken() != null) { 213 String message; 214 215 if (errorType == MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { 216 message = mPlaybackErrorText; 217 } else { 218 message = mUnknownErrorText; 219 } 220 221 try { 222 new AlertDialog.Builder(getContext()) 223 .setTitle(mErrorTitle) 224 .setMessage(message) 225 .setPositiveButton(mErrorButton, 226 new DialogInterface.OnClickListener() { 227 @Override 228 public void onClick(DialogInterface dialog, int whichButton) { 229 /* Inform that the video is over. 230 */ 231 onCompletion(); 232 } 233 }) 234 .setCancelable(false) 235 .show(); 236 } catch (RuntimeException e) { 237 Log.e(TAG, "Cannot show the alert dialog, error message: " + message, e); 238 } 239 } 240 } 241 242 @CalledByNative 243 private void onVideoSizeChanged(int width, int height) { 244 mVideoWidth = width; 245 mVideoHeight = height; 246 // This will trigger the SurfaceView.onMeasure() call. 247 mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight); 248 } 249 250 @CalledByNative 251 protected void onBufferingUpdate(int percent) { 252 } 253 254 @CalledByNative 255 private void onPlaybackComplete() { 256 onCompletion(); 257 } 258 259 @CalledByNative 260 protected void onUpdateMediaMetadata( 261 int videoWidth, 262 int videoHeight, 263 int duration, 264 boolean canPause, 265 boolean canSeekBack, 266 boolean canSeekForward) { 267 mDuration = duration; 268 mProgressView.setVisibility(View.GONE); 269 mCurrentState = isPlaying() ? STATE_PLAYING : STATE_PAUSED; 270 onVideoSizeChanged(videoWidth, videoHeight); 271 } 272 273 @Override 274 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 275 } 276 277 @Override 278 public void surfaceCreated(SurfaceHolder holder) { 279 mSurfaceHolder = holder; 280 openVideo(); 281 } 282 283 @Override 284 public void surfaceDestroyed(SurfaceHolder holder) { 285 if (mNativeContentVideoView != 0) { 286 nativeSetSurface(mNativeContentVideoView, null); 287 } 288 mSurfaceHolder = null; 289 post(mExitFullscreenRunnable); 290 } 291 292 @CalledByNative 293 protected void openVideo() { 294 if (mSurfaceHolder != null) { 295 mCurrentState = STATE_IDLE; 296 if (mNativeContentVideoView != 0) { 297 nativeRequestMediaMetadata(mNativeContentVideoView); 298 nativeSetSurface(mNativeContentVideoView, 299 mSurfaceHolder.getSurface()); 300 } 301 } 302 } 303 304 protected void onCompletion() { 305 mCurrentState = STATE_PLAYBACK_COMPLETED; 306 } 307 308 309 protected boolean isInPlaybackState() { 310 return (mCurrentState != STATE_ERROR && mCurrentState != STATE_IDLE); 311 } 312 313 protected void start() { 314 if (isInPlaybackState()) { 315 if (mNativeContentVideoView != 0) { 316 nativePlay(mNativeContentVideoView); 317 } 318 mCurrentState = STATE_PLAYING; 319 } 320 } 321 322 protected void pause() { 323 if (isInPlaybackState()) { 324 if (isPlaying()) { 325 if (mNativeContentVideoView != 0) { 326 nativePause(mNativeContentVideoView); 327 } 328 mCurrentState = STATE_PAUSED; 329 } 330 } 331 } 332 333 // cache duration as mDuration for faster access 334 protected int getDuration() { 335 if (isInPlaybackState()) { 336 if (mDuration > 0) { 337 return mDuration; 338 } 339 if (mNativeContentVideoView != 0) { 340 mDuration = nativeGetDurationInMilliSeconds(mNativeContentVideoView); 341 } else { 342 mDuration = 0; 343 } 344 return mDuration; 345 } 346 mDuration = -1; 347 return mDuration; 348 } 349 350 protected int getCurrentPosition() { 351 if (isInPlaybackState() && mNativeContentVideoView != 0) { 352 return nativeGetCurrentPosition(mNativeContentVideoView); 353 } 354 return 0; 355 } 356 357 protected void seekTo(int msec) { 358 if (mNativeContentVideoView != 0) { 359 nativeSeekTo(mNativeContentVideoView, msec); 360 } 361 } 362 363 protected boolean isPlaying() { 364 return mNativeContentVideoView != 0 && nativeIsPlaying(mNativeContentVideoView); 365 } 366 367 @CalledByNative 368 private static ContentVideoView createContentVideoView( 369 Context context, long nativeContentVideoView, ContentVideoViewClient client, 370 boolean legacy) { 371 ThreadUtils.assertOnUiThread(); 372 // The context needs be Activity to create the ContentVideoView correctly. 373 if (!(context instanceof Activity)) { 374 Log.w(TAG, "Wrong type of context, can't create fullscreen video"); 375 return null; 376 } 377 if (legacy) { 378 return new ContentVideoViewLegacy(context, nativeContentVideoView, client); 379 } else { 380 return new ContentVideoView(context, nativeContentVideoView, client); 381 } 382 } 383 384 public void removeSurfaceView() { 385 removeView(mVideoSurfaceView); 386 removeView(mProgressView); 387 mVideoSurfaceView = null; 388 mProgressView = null; 389 } 390 391 public void exitFullscreen(boolean relaseMediaPlayer) { 392 destroyContentVideoView(false); 393 if (mNativeContentVideoView != 0) { 394 nativeExitFullscreen(mNativeContentVideoView, relaseMediaPlayer); 395 mNativeContentVideoView = 0; 396 } 397 } 398 399 @CalledByNative 400 private void onExitFullscreen() { 401 exitFullscreen(false); 402 } 403 404 /** 405 * This method shall only be called by native and exitFullscreen, 406 * To exit fullscreen, use exitFullscreen in Java. 407 */ 408 @CalledByNative 409 protected void destroyContentVideoView(boolean nativeViewDestroyed) { 410 if (mVideoSurfaceView != null) { 411 removeSurfaceView(); 412 setVisibility(View.GONE); 413 414 // To prevent re-entrance, call this after removeSurfaceView. 415 mClient.onDestroyContentVideoView(); 416 } 417 if (nativeViewDestroyed) { 418 mNativeContentVideoView = 0; 419 } 420 } 421 422 public static ContentVideoView getContentVideoView() { 423 return nativeGetSingletonJavaContentVideoView(); 424 } 425 426 @Override 427 public boolean onKeyUp(int keyCode, KeyEvent event) { 428 if (keyCode == KeyEvent.KEYCODE_BACK) { 429 exitFullscreen(false); 430 return true; 431 } 432 return super.onKeyUp(keyCode, event); 433 } 434 435 @Override 436 public View acquireAnchorView() { 437 View anchorView = new View(getContext()); 438 addView(anchorView); 439 return anchorView; 440 } 441 442 @Override 443 public void setAnchorViewPosition(View view, float x, float y, float width, float height) { 444 Log.e(TAG, "setAnchorViewPosition isn't implemented"); 445 } 446 447 @Override 448 public void releaseAnchorView(View anchorView) { 449 removeView(anchorView); 450 } 451 452 @CalledByNative 453 private long getNativeViewAndroid() { 454 return mViewAndroid.getNativePointer(); 455 } 456 457 private static native ContentVideoView nativeGetSingletonJavaContentVideoView(); 458 private native void nativeExitFullscreen(long nativeContentVideoView, 459 boolean relaseMediaPlayer); 460 private native int nativeGetCurrentPosition(long nativeContentVideoView); 461 private native int nativeGetDurationInMilliSeconds(long nativeContentVideoView); 462 private native void nativeRequestMediaMetadata(long nativeContentVideoView); 463 private native int nativeGetVideoWidth(long nativeContentVideoView); 464 private native int nativeGetVideoHeight(long nativeContentVideoView); 465 private native boolean nativeIsPlaying(long nativeContentVideoView); 466 private native void nativePause(long nativeContentVideoView); 467 private native void nativePlay(long nativeContentVideoView); 468 private native void nativeSeekTo(long nativeContentVideoView, int msec); 469 private native void nativeSetSurface(long nativeContentVideoView, Surface surface); 470} 471