ContentVideoView.java revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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.graphics.Color; 12import android.util.Log; 13import android.view.Gravity; 14import android.view.KeyEvent; 15import android.view.MotionEvent; 16import android.view.Surface; 17import android.view.SurfaceHolder; 18import android.view.SurfaceView; 19import android.view.View; 20import android.view.ViewGroup; 21import android.widget.FrameLayout; 22import android.widget.LinearLayout; 23import android.widget.MediaController; 24import android.widget.ProgressBar; 25import android.widget.TextView; 26 27import org.chromium.base.CalledByNative; 28import org.chromium.base.JNINamespace; 29import org.chromium.base.ThreadUtils; 30 31@JNINamespace("content") 32public class ContentVideoView extends FrameLayout implements MediaController.MediaPlayerControl, 33 SurfaceHolder.Callback, View.OnTouchListener, View.OnKeyListener { 34 35 private static final String TAG = "ContentVideoView"; 36 37 /* Do not change these values without updating their counterparts 38 * in include/media/mediaplayer.h! 39 */ 40 private static final int MEDIA_NOP = 0; // interface test message 41 private static final int MEDIA_PREPARED = 1; 42 private static final int MEDIA_PLAYBACK_COMPLETE = 2; 43 private static final int MEDIA_BUFFERING_UPDATE = 3; 44 private static final int MEDIA_SEEK_COMPLETE = 4; 45 private static final int MEDIA_SET_VIDEO_SIZE = 5; 46 private static final int MEDIA_ERROR = 100; 47 private static final int MEDIA_INFO = 200; 48 49 /** 50 * Keep these error codes in sync with the code we defined in 51 * MediaPlayerListener.java. 52 */ 53 public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 2; 54 public static final int MEDIA_ERROR_INVALID_CODE = 3; 55 56 // all possible internal states 57 private static final int STATE_ERROR = -1; 58 private static final int STATE_IDLE = 0; 59 private static final int STATE_PLAYING = 1; 60 private static final int STATE_PAUSED = 2; 61 private static final int STATE_PLAYBACK_COMPLETED = 3; 62 63 private SurfaceHolder mSurfaceHolder; 64 private int mVideoWidth; 65 private int mVideoHeight; 66 private int mCurrentBufferPercentage; 67 private int mDuration; 68 private MediaController mMediaController; 69 private boolean mCanPause; 70 private boolean mCanSeekBack; 71 private boolean mCanSeekForward; 72 73 // Native pointer to C++ ContentVideoView object. 74 private long mNativeContentVideoView; 75 76 // webkit should have prepared the media 77 private int mCurrentState = STATE_IDLE; 78 79 // Strings for displaying media player errors 80 private String mPlaybackErrorText; 81 private String mUnknownErrorText; 82 private String mErrorButton; 83 private String mErrorTitle; 84 private String mVideoLoadingText; 85 86 // This view will contain the video. 87 private VideoSurfaceView mVideoSurfaceView; 88 89 // Progress view when the video is loading. 90 private View mProgressView; 91 92 private final ContentVideoViewClient mClient; 93 94 private class VideoSurfaceView extends SurfaceView { 95 96 public VideoSurfaceView(Context context) { 97 super(context); 98 } 99 100 @Override 101 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 102 int width = getDefaultSize(mVideoWidth, widthMeasureSpec); 103 int height = getDefaultSize(mVideoHeight, heightMeasureSpec); 104 if (mVideoWidth > 0 && mVideoHeight > 0) { 105 if (mVideoWidth * height > width * mVideoHeight) { 106 height = width * mVideoHeight / mVideoWidth; 107 } else if (mVideoWidth * height < width * mVideoHeight) { 108 width = height * mVideoWidth / mVideoHeight; 109 } 110 } 111 setMeasuredDimension(width, height); 112 } 113 } 114 115 private static class ProgressView extends LinearLayout { 116 117 private final ProgressBar mProgressBar; 118 private final TextView mTextView; 119 120 public ProgressView(Context context, String videoLoadingText) { 121 super(context); 122 setOrientation(LinearLayout.VERTICAL); 123 setLayoutParams(new LinearLayout.LayoutParams( 124 LinearLayout.LayoutParams.WRAP_CONTENT, 125 LinearLayout.LayoutParams.WRAP_CONTENT)); 126 mProgressBar = new ProgressBar(context, null, android.R.attr.progressBarStyleLarge); 127 mTextView = new TextView(context); 128 mTextView.setText(videoLoadingText); 129 addView(mProgressBar); 130 addView(mTextView); 131 } 132 } 133 134 private static class FullScreenMediaController extends MediaController { 135 136 View mVideoView; 137 138 public FullScreenMediaController(Context context, View video) { 139 super(context); 140 mVideoView = video; 141 } 142 143 @Override 144 public void show() { 145 super.show(); 146 if (mVideoView != null) { 147 mVideoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); 148 } 149 } 150 151 @Override 152 public void hide() { 153 if (mVideoView != null) { 154 mVideoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); 155 } 156 super.hide(); 157 } 158 } 159 160 private final Runnable mExitFullscreenRunnable = new Runnable() { 161 @Override 162 public void run() { 163 exitFullscreen(true); 164 } 165 }; 166 167 private ContentVideoView(Context context, long nativeContentVideoView, 168 ContentVideoViewClient client) { 169 super(context); 170 mNativeContentVideoView = nativeContentVideoView; 171 mClient = client; 172 initResources(context); 173 mCurrentBufferPercentage = 0; 174 mVideoSurfaceView = new VideoSurfaceView(context); 175 setBackgroundColor(Color.BLACK); 176 showContentVideoView(); 177 setVisibility(View.VISIBLE); 178 mClient.onShowCustomView(this); 179 } 180 181 private void initResources(Context context) { 182 if (mPlaybackErrorText != null) return; 183 mPlaybackErrorText = context.getString( 184 org.chromium.content.R.string.media_player_error_text_invalid_progressive_playback); 185 mUnknownErrorText = context.getString( 186 org.chromium.content.R.string.media_player_error_text_unknown); 187 mErrorButton = context.getString( 188 org.chromium.content.R.string.media_player_error_button); 189 mErrorTitle = context.getString( 190 org.chromium.content.R.string.media_player_error_title); 191 mVideoLoadingText = context.getString( 192 org.chromium.content.R.string.media_player_loading_video); 193 } 194 195 private void showContentVideoView() { 196 FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( 197 ViewGroup.LayoutParams.MATCH_PARENT, 198 ViewGroup.LayoutParams.MATCH_PARENT, 199 Gravity.CENTER); 200 this.addView(mVideoSurfaceView, layoutParams); 201 View progressView = mClient.getVideoLoadingProgressView(); 202 if (progressView != null) { 203 mProgressView = progressView; 204 } else { 205 mProgressView = new ProgressView(getContext(), mVideoLoadingText); 206 } 207 this.addView(mProgressView, new FrameLayout.LayoutParams( 208 ViewGroup.LayoutParams.WRAP_CONTENT, 209 ViewGroup.LayoutParams.WRAP_CONTENT, 210 Gravity.CENTER)); 211 mVideoSurfaceView.setZOrderOnTop(true); 212 mVideoSurfaceView.setOnKeyListener(this); 213 mVideoSurfaceView.setOnTouchListener(this); 214 mVideoSurfaceView.getHolder().addCallback(this); 215 mVideoSurfaceView.setFocusable(true); 216 mVideoSurfaceView.setFocusableInTouchMode(true); 217 mVideoSurfaceView.requestFocus(); 218 } 219 220 @CalledByNative 221 public void onMediaPlayerError(int errorType) { 222 Log.d(TAG, "OnMediaPlayerError: " + errorType); 223 if (mCurrentState == STATE_ERROR || mCurrentState == STATE_PLAYBACK_COMPLETED) { 224 return; 225 } 226 227 // Ignore some invalid error codes. 228 if (errorType == MEDIA_ERROR_INVALID_CODE) { 229 return; 230 } 231 232 mCurrentState = STATE_ERROR; 233 if (mMediaController != null) { 234 mMediaController.hide(); 235 } 236 237 /* Pop up an error dialog so the user knows that 238 * something bad has happened. Only try and pop up the dialog 239 * if we're attached to a window. When we're going away and no 240 * longer have a window, don't bother showing the user an error. 241 * 242 * TODO(qinmin): We need to review whether this Dialog is OK with 243 * the rest of the browser UI elements. 244 */ 245 if (getWindowToken() != null) { 246 String message; 247 248 if (errorType == MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { 249 message = mPlaybackErrorText; 250 } else { 251 message = mUnknownErrorText; 252 } 253 254 try { 255 new AlertDialog.Builder(getContext()) 256 .setTitle(mErrorTitle) 257 .setMessage(message) 258 .setPositiveButton(mErrorButton, 259 new DialogInterface.OnClickListener() { 260 @Override 261 public void onClick(DialogInterface dialog, int whichButton) { 262 /* Inform that the video is over. 263 */ 264 onCompletion(); 265 } 266 }) 267 .setCancelable(false) 268 .show(); 269 } catch (RuntimeException e) { 270 Log.e(TAG, "Cannot show the alert dialog, error message: " + message, e); 271 } 272 } 273 } 274 275 @CalledByNative 276 private void onVideoSizeChanged(int width, int height) { 277 mVideoWidth = width; 278 mVideoHeight = height; 279 // This will trigger the SurfaceView.onMeasure() call. 280 mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight); 281 } 282 283 @CalledByNative 284 private void onBufferingUpdate(int percent) { 285 mCurrentBufferPercentage = percent; 286 } 287 288 @CalledByNative 289 private void onPlaybackComplete() { 290 onCompletion(); 291 } 292 293 @CalledByNative 294 private void onUpdateMediaMetadata( 295 int videoWidth, 296 int videoHeight, 297 int duration, 298 boolean canPause, 299 boolean canSeekBack, 300 boolean canSeekForward) { 301 mProgressView.setVisibility(View.GONE); 302 mDuration = duration; 303 mCanPause = canPause; 304 mCanSeekBack = canSeekBack; 305 mCanSeekForward = canSeekForward; 306 mCurrentState = isPlaying() ? STATE_PLAYING : STATE_PAUSED; 307 if (mMediaController != null) { 308 mMediaController.setEnabled(true); 309 // If paused , should show the controller for ever. 310 if (isPlaying()) 311 mMediaController.show(); 312 else 313 mMediaController.show(0); 314 } 315 316 onVideoSizeChanged(videoWidth, videoHeight); 317 } 318 319 @Override 320 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 321 mVideoSurfaceView.setFocusable(true); 322 mVideoSurfaceView.setFocusableInTouchMode(true); 323 if (isInPlaybackState() && mMediaController != null) { 324 mMediaController.show(); 325 } 326 } 327 328 @Override 329 public void surfaceCreated(SurfaceHolder holder) { 330 mSurfaceHolder = holder; 331 openVideo(); 332 } 333 334 @Override 335 public void surfaceDestroyed(SurfaceHolder holder) { 336 if (mNativeContentVideoView != 0) { 337 nativeSetSurface(mNativeContentVideoView, null); 338 } 339 mSurfaceHolder = null; 340 post(mExitFullscreenRunnable); 341 } 342 343 private void setMediaController(MediaController controller) { 344 if (mMediaController != null) { 345 mMediaController.hide(); 346 } 347 mMediaController = controller; 348 attachMediaController(); 349 } 350 351 private void attachMediaController() { 352 if (mMediaController != null) { 353 mMediaController.setMediaPlayer(this); 354 mMediaController.setAnchorView(mVideoSurfaceView); 355 mMediaController.setEnabled(false); 356 } 357 } 358 359 @CalledByNative 360 private void openVideo() { 361 if (mSurfaceHolder != null) { 362 mCurrentState = STATE_IDLE; 363 mCurrentBufferPercentage = 0; 364 setMediaController(new FullScreenMediaController(getContext(), this)); 365 if (mNativeContentVideoView != 0) { 366 nativeUpdateMediaMetadata(mNativeContentVideoView); 367 nativeSetSurface(mNativeContentVideoView, 368 mSurfaceHolder.getSurface()); 369 } 370 } 371 } 372 373 private void onCompletion() { 374 mCurrentState = STATE_PLAYBACK_COMPLETED; 375 if (mMediaController != null) { 376 mMediaController.hide(); 377 } 378 } 379 380 @Override 381 public boolean onTouch(View v, MotionEvent event) { 382 if (isInPlaybackState() && mMediaController != null && 383 event.getAction() == MotionEvent.ACTION_DOWN) { 384 toggleMediaControlsVisiblity(); 385 } 386 return true; 387 } 388 389 @Override 390 public boolean onTrackballEvent(MotionEvent ev) { 391 if (isInPlaybackState() && mMediaController != null) { 392 toggleMediaControlsVisiblity(); 393 } 394 return false; 395 } 396 397 @Override 398 public boolean onKey(View v, int keyCode, KeyEvent event) { 399 boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK && 400 keyCode != KeyEvent.KEYCODE_VOLUME_UP && 401 keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && 402 keyCode != KeyEvent.KEYCODE_VOLUME_MUTE && 403 keyCode != KeyEvent.KEYCODE_CALL && 404 keyCode != KeyEvent.KEYCODE_MENU && 405 keyCode != KeyEvent.KEYCODE_SEARCH && 406 keyCode != KeyEvent.KEYCODE_ENDCALL; 407 if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) { 408 if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || 409 keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) { 410 if (isPlaying()) { 411 pause(); 412 mMediaController.show(); 413 } else { 414 start(); 415 mMediaController.hide(); 416 } 417 return true; 418 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) { 419 if (!isPlaying()) { 420 start(); 421 mMediaController.hide(); 422 } 423 return true; 424 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP 425 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) { 426 if (isPlaying()) { 427 pause(); 428 mMediaController.show(); 429 } 430 return true; 431 } else { 432 toggleMediaControlsVisiblity(); 433 } 434 } else if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) { 435 exitFullscreen(false); 436 return true; 437 } else if (keyCode == KeyEvent.KEYCODE_MENU || keyCode == KeyEvent.KEYCODE_SEARCH) { 438 return true; 439 } 440 return super.onKeyDown(keyCode, event); 441 } 442 443 private void toggleMediaControlsVisiblity() { 444 if (mMediaController.isShowing()) { 445 mMediaController.hide(); 446 } else { 447 mMediaController.show(); 448 } 449 } 450 451 private boolean isInPlaybackState() { 452 return (mCurrentState != STATE_ERROR && mCurrentState != STATE_IDLE); 453 } 454 455 @Override 456 public void start() { 457 if (isInPlaybackState()) { 458 if (mNativeContentVideoView != 0) { 459 nativePlay(mNativeContentVideoView); 460 } 461 mCurrentState = STATE_PLAYING; 462 } 463 } 464 465 @Override 466 public void pause() { 467 if (isInPlaybackState()) { 468 if (isPlaying()) { 469 if (mNativeContentVideoView != 0) { 470 nativePause(mNativeContentVideoView); 471 } 472 mCurrentState = STATE_PAUSED; 473 } 474 } 475 } 476 477 // cache duration as mDuration for faster access 478 @Override 479 public int getDuration() { 480 if (isInPlaybackState()) { 481 if (mDuration > 0) { 482 return mDuration; 483 } 484 if (mNativeContentVideoView != 0) { 485 mDuration = nativeGetDurationInMilliSeconds(mNativeContentVideoView); 486 } else { 487 mDuration = 0; 488 } 489 return mDuration; 490 } 491 mDuration = -1; 492 return mDuration; 493 } 494 495 @Override 496 public int getCurrentPosition() { 497 if (isInPlaybackState() && mNativeContentVideoView != 0) { 498 return nativeGetCurrentPosition(mNativeContentVideoView); 499 } 500 return 0; 501 } 502 503 @Override 504 public void seekTo(int msec) { 505 if (mNativeContentVideoView != 0) { 506 nativeSeekTo(mNativeContentVideoView, msec); 507 } 508 } 509 510 @Override 511 public boolean isPlaying() { 512 return mNativeContentVideoView != 0 && nativeIsPlaying(mNativeContentVideoView); 513 } 514 515 @Override 516 public int getBufferPercentage() { 517 return mCurrentBufferPercentage; 518 } 519 520 @Override 521 public boolean canPause() { 522 return mCanPause; 523 } 524 525 @Override 526 public boolean canSeekBackward() { 527 return mCanSeekBack; 528 } 529 530 @Override 531 public boolean canSeekForward() { 532 return mCanSeekForward; 533 } 534 535 @Override 536 public int getAudioSessionId() { 537 return 0; 538 } 539 540 @CalledByNative 541 private static ContentVideoView createContentVideoView( 542 Context context, long nativeContentVideoView, ContentVideoViewClient client) { 543 ThreadUtils.assertOnUiThread(); 544 // The context needs be Activity to create the ContentVideoView correctly. 545 if (!(context instanceof Activity)) { 546 Log.w(TAG, "Wrong type of context, can't create fullscreen video"); 547 return null; 548 } 549 return new ContentVideoView(context, nativeContentVideoView, client); 550 } 551 552 private void removeMediaController() { 553 if (mMediaController != null) { 554 mMediaController.setEnabled(false); 555 mMediaController.hide(); 556 mMediaController = null; 557 } 558 } 559 560 public void removeSurfaceView() { 561 removeView(mVideoSurfaceView); 562 removeView(mProgressView); 563 mVideoSurfaceView = null; 564 mProgressView = null; 565 } 566 567 public void exitFullscreen(boolean relaseMediaPlayer) { 568 destroyContentVideoView(false); 569 if (mNativeContentVideoView != 0) { 570 nativeExitFullscreen(mNativeContentVideoView, relaseMediaPlayer); 571 mNativeContentVideoView = 0; 572 } 573 } 574 575 /** 576 * This method shall only be called by native and exitFullscreen, 577 * To exit fullscreen, use exitFullscreen in Java. 578 */ 579 @CalledByNative 580 private void destroyContentVideoView(boolean nativeViewDestroyed) { 581 if (mVideoSurfaceView != null) { 582 removeMediaController(); 583 removeSurfaceView(); 584 setVisibility(View.GONE); 585 586 // To prevent re-entrance, call this after removeSurfaceView. 587 mClient.onDestroyContentVideoView(); 588 } 589 if (nativeViewDestroyed) { 590 mNativeContentVideoView = 0; 591 } 592 } 593 594 public static ContentVideoView getContentVideoView() { 595 return nativeGetSingletonJavaContentVideoView(); 596 } 597 598 @Override 599 public boolean onTouchEvent(MotionEvent ev) { 600 return true; 601 } 602 603 @Override 604 public boolean onKeyDown(int keyCode, KeyEvent event) { 605 if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) { 606 exitFullscreen(false); 607 return true; 608 } 609 return super.onKeyDown(keyCode, event); 610 } 611 612 private static native ContentVideoView nativeGetSingletonJavaContentVideoView(); 613 private native void nativeExitFullscreen(long nativeContentVideoView, 614 boolean relaseMediaPlayer); 615 private native int nativeGetCurrentPosition(long nativeContentVideoView); 616 private native int nativeGetDurationInMilliSeconds(long nativeContentVideoView); 617 private native void nativeUpdateMediaMetadata(long nativeContentVideoView); 618 private native int nativeGetVideoWidth(long nativeContentVideoView); 619 private native int nativeGetVideoHeight(long nativeContentVideoView); 620 private native boolean nativeIsPlaying(long nativeContentVideoView); 621 private native void nativePause(long nativeContentVideoView); 622 private native void nativePlay(long nativeContentVideoView); 623 private native void nativeSeekTo(long nativeContentVideoView, int msec); 624 private native void nativeSetSurface(long nativeContentVideoView, Surface surface); 625} 626