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