ContentVideoView.java revision 868fa2fe829687343ffae624259930155e16dbd8
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.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 226 mVideoSurfaceView.setFocusable(true); 227 mVideoSurfaceView.setFocusableInTouchMode(true); 228 mVideoSurfaceView.requestFocus(); 229 } 230 231 @CalledByNative 232 public void onMediaPlayerError(int errorType) { 233 Log.d(TAG, "OnMediaPlayerError: " + errorType); 234 if (mCurrentState == STATE_ERROR || mCurrentState == STATE_PLAYBACK_COMPLETED) { 235 return; 236 } 237 238 mCurrentState = STATE_ERROR; 239 if (mMediaController != null) { 240 mMediaController.hide(); 241 } 242 243 /* Pop up an error dialog so the user knows that 244 * something bad has happened. Only try and pop up the dialog 245 * if we're attached to a window. When we're going away and no 246 * longer have a window, don't bother showing the user an error. 247 * 248 * TODO(qinmin): We need to review whether this Dialog is OK with 249 * the rest of the browser UI elements. 250 */ 251 if (getWindowToken() != null) { 252 String message; 253 254 if (errorType == MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { 255 message = mPlaybackErrorText; 256 } else { 257 message = mUnknownErrorText; 258 } 259 260 new AlertDialog.Builder(getContext()) 261 .setTitle(mErrorTitle) 262 .setMessage(message) 263 .setPositiveButton(mErrorButton, 264 new DialogInterface.OnClickListener() { 265 public void onClick(DialogInterface dialog, int whichButton) { 266 /* Inform that the video is over. 267 */ 268 onCompletion(); 269 } 270 }) 271 .setCancelable(false) 272 .show(); 273 } 274 } 275 276 @CalledByNative 277 private void onVideoSizeChanged(int width, int height) { 278 mVideoWidth = width; 279 mVideoHeight = height; 280 if (mVideoWidth != 0 && mVideoHeight != 0) { 281 mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight); 282 } 283 } 284 285 @CalledByNative 286 private void onBufferingUpdate(int percent) { 287 mCurrentBufferPercentage = percent; 288 } 289 290 @CalledByNative 291 private void onPlaybackComplete() { 292 onCompletion(); 293 } 294 295 @CalledByNative 296 private void onUpdateMediaMetadata( 297 int videoWidth, 298 int videoHeight, 299 int duration, 300 boolean canPause, 301 boolean canSeekBack, 302 boolean canSeekForward) { 303 mProgressView.setVisibility(View.GONE); 304 mDuration = duration; 305 mCanPause = canPause; 306 mCanSeekBack = canSeekBack; 307 mCanSeekForward = canSeekForward; 308 mCurrentState = isPlaying() ? STATE_PLAYING : STATE_PAUSED; 309 if (mMediaController != null) { 310 mMediaController.setEnabled(true); 311 // If paused , should show the controller for ever. 312 if (isPlaying()) 313 mMediaController.show(); 314 else 315 mMediaController.show(0); 316 } 317 318 onVideoSizeChanged(videoWidth, videoHeight); 319 } 320 321 @Override 322 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 323 mVideoSurfaceView.setFocusable(true); 324 mVideoSurfaceView.setFocusableInTouchMode(true); 325 if (isInPlaybackState() && mMediaController != null) { 326 mMediaController.show(); 327 } 328 } 329 330 @Override 331 public void surfaceCreated(SurfaceHolder holder) { 332 mSurfaceHolder = holder; 333 openVideo(); 334 } 335 336 @Override 337 public void surfaceDestroyed(SurfaceHolder holder) { 338 if (mNativeContentVideoView != 0) { 339 nativeSetSurface(mNativeContentVideoView, null); 340 } 341 mSurfaceHolder = null; 342 post(mExitFullscreenRunnable); 343 } 344 345 private void setMediaController(MediaController controller) { 346 if (mMediaController != null) { 347 mMediaController.hide(); 348 } 349 mMediaController = controller; 350 attachMediaController(); 351 } 352 353 private void attachMediaController() { 354 if (mMediaController != null) { 355 mMediaController.setMediaPlayer(this); 356 mMediaController.setAnchorView(mVideoSurfaceView); 357 mMediaController.setEnabled(false); 358 } 359 } 360 361 @CalledByNative 362 private void openVideo() { 363 if (mSurfaceHolder != null) { 364 mCurrentState = STATE_IDLE; 365 mCurrentBufferPercentage = 0; 366 setMediaController(new FullScreenMediaController(getContext(), this)); 367 if (mNativeContentVideoView != 0) { 368 nativeUpdateMediaMetadata(mNativeContentVideoView); 369 nativeSetSurface(mNativeContentVideoView, 370 mSurfaceHolder.getSurface()); 371 } 372 } 373 } 374 375 private void onCompletion() { 376 mCurrentState = STATE_PLAYBACK_COMPLETED; 377 if (mMediaController != null) { 378 mMediaController.hide(); 379 } 380 } 381 382 @Override 383 public boolean onTouch(View v, MotionEvent event) { 384 if (isInPlaybackState() && mMediaController != null && 385 event.getAction() == MotionEvent.ACTION_DOWN) { 386 toggleMediaControlsVisiblity(); 387 } 388 return true; 389 } 390 391 @Override 392 public boolean onTrackballEvent(MotionEvent ev) { 393 if (isInPlaybackState() && mMediaController != null) { 394 toggleMediaControlsVisiblity(); 395 } 396 return false; 397 } 398 399 @Override 400 public boolean onKey(View v, int keyCode, KeyEvent event) { 401 boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK && 402 keyCode != KeyEvent.KEYCODE_VOLUME_UP && 403 keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && 404 keyCode != KeyEvent.KEYCODE_VOLUME_MUTE && 405 keyCode != KeyEvent.KEYCODE_CALL && 406 keyCode != KeyEvent.KEYCODE_MENU && 407 keyCode != KeyEvent.KEYCODE_SEARCH && 408 keyCode != KeyEvent.KEYCODE_ENDCALL; 409 if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) { 410 if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || 411 keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) { 412 if (isPlaying()) { 413 pause(); 414 mMediaController.show(); 415 } else { 416 start(); 417 mMediaController.hide(); 418 } 419 return true; 420 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) { 421 if (!isPlaying()) { 422 start(); 423 mMediaController.hide(); 424 } 425 return true; 426 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP 427 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) { 428 if (isPlaying()) { 429 pause(); 430 mMediaController.show(); 431 } 432 return true; 433 } else { 434 toggleMediaControlsVisiblity(); 435 } 436 } else if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) { 437 exitFullscreen(false); 438 return true; 439 } else if (keyCode == KeyEvent.KEYCODE_MENU || keyCode == KeyEvent.KEYCODE_SEARCH) { 440 return true; 441 } 442 return super.onKeyDown(keyCode, event); 443 } 444 445 private void toggleMediaControlsVisiblity() { 446 if (mMediaController.isShowing()) { 447 mMediaController.hide(); 448 } else { 449 mMediaController.show(); 450 } 451 } 452 453 private boolean isInPlaybackState() { 454 return (mCurrentState != STATE_ERROR && mCurrentState != STATE_IDLE); 455 } 456 457 @Override 458 public void start() { 459 if (isInPlaybackState()) { 460 if (mNativeContentVideoView != 0) { 461 nativePlay(mNativeContentVideoView); 462 } 463 mCurrentState = STATE_PLAYING; 464 } 465 } 466 467 @Override 468 public void pause() { 469 if (isInPlaybackState()) { 470 if (isPlaying()) { 471 if (mNativeContentVideoView != 0) { 472 nativePause(mNativeContentVideoView); 473 } 474 mCurrentState = STATE_PAUSED; 475 } 476 } 477 } 478 479 // cache duration as mDuration for faster access 480 @Override 481 public int getDuration() { 482 if (isInPlaybackState()) { 483 if (mDuration > 0) { 484 return mDuration; 485 } 486 if (mNativeContentVideoView != 0) { 487 mDuration = nativeGetDurationInMilliSeconds(mNativeContentVideoView); 488 } else { 489 mDuration = 0; 490 } 491 return mDuration; 492 } 493 mDuration = -1; 494 return mDuration; 495 } 496 497 @Override 498 public int getCurrentPosition() { 499 if (isInPlaybackState() && mNativeContentVideoView != 0) { 500 return nativeGetCurrentPosition(mNativeContentVideoView); 501 } 502 return 0; 503 } 504 505 @Override 506 public void seekTo(int msec) { 507 if (mNativeContentVideoView != 0) { 508 nativeSeekTo(mNativeContentVideoView, msec); 509 } 510 } 511 512 @Override 513 public boolean isPlaying() { 514 return mNativeContentVideoView != 0 && nativeIsPlaying(mNativeContentVideoView); 515 } 516 517 @Override 518 public int getBufferPercentage() { 519 return mCurrentBufferPercentage; 520 } 521 522 @Override 523 public boolean canPause() { 524 return mCanPause; 525 } 526 527 @Override 528 public boolean canSeekBackward() { 529 return mCanSeekBack; 530 } 531 532 @Override 533 public boolean canSeekForward() { 534 return mCanSeekForward; 535 } 536 537 public int getAudioSessionId() { 538 return 0; 539 } 540 541 @CalledByNative 542 private static ContentVideoView createContentVideoView( 543 Context context, int nativeContentVideoView, ContentVideoViewClient client) { 544 ThreadUtils.assertOnUiThread(); 545 // The context needs be Activity to create the ContentVideoView correctly. 546 if (!(context instanceof Activity)) { 547 Log.w(TAG, "Wrong type of context, can't create fullscreen video"); 548 return null; 549 } 550 return new ContentVideoView(context, nativeContentVideoView, client); 551 } 552 553 private void removeMediaController() { 554 if (mMediaController != null) { 555 mMediaController.setEnabled(false); 556 mMediaController.hide(); 557 mMediaController = null; 558 } 559 } 560 561 public void removeSurfaceView() { 562 removeView(mVideoSurfaceView); 563 removeView(mProgressView); 564 mVideoSurfaceView = null; 565 mProgressView = null; 566 } 567 568 public void exitFullscreen(boolean relaseMediaPlayer) { 569 destroyContentVideoView(); 570 if (mNativeContentVideoView != 0) { 571 nativeExitFullscreen(mNativeContentVideoView, relaseMediaPlayer); 572 mNativeContentVideoView = 0; 573 } 574 } 575 576 @CalledByNative 577 public static void keepScreenOnContentVideoView(boolean screenOn) { 578 ContentVideoView content_video_view = getContentVideoView(); 579 if ( content_video_view != null) { 580 content_video_view.mClient.keepScreenOn(screenOn); 581 } 582 } 583 584 /** 585 * This method shall only be called by native and exitFullscreen, 586 * To exit fullscreen, use exitFullscreen in Java. 587 */ 588 @CalledByNative 589 private void destroyContentVideoView() { 590 if (mVideoSurfaceView != null) { 591 mClient.onDestroyContentVideoView(); 592 removeMediaController(); 593 removeSurfaceView(); 594 setVisibility(View.GONE); 595 } 596 } 597 598 public static ContentVideoView getContentVideoView() { 599 return nativeGetSingletonJavaContentVideoView(); 600 } 601 602 @Override 603 public boolean onTouchEvent(MotionEvent ev) { 604 return true; 605 } 606 607 @Override 608 public boolean onKeyDown(int keyCode, KeyEvent event) { 609 if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) { 610 exitFullscreen(false); 611 return true; 612 } 613 return super.onKeyDown(keyCode, event); 614 } 615 616 private static native ContentVideoView nativeGetSingletonJavaContentVideoView(); 617 private native void nativeExitFullscreen(int nativeContentVideoView, boolean relaseMediaPlayer); 618 private native int nativeGetCurrentPosition(int nativeContentVideoView); 619 private native int nativeGetDurationInMilliSeconds(int nativeContentVideoView); 620 private native void nativeUpdateMediaMetadata(int nativeContentVideoView); 621 private native int nativeGetVideoWidth(int nativeContentVideoView); 622 private native int nativeGetVideoHeight(int nativeContentVideoView); 623 private native boolean nativeIsPlaying(int nativeContentVideoView); 624 private native void nativePause(int nativeContentVideoView); 625 private native void nativePlay(int nativeContentVideoView); 626 private native void nativeSeekTo(int nativeContentVideoView, int msec); 627 private native void nativeSetSurface(int nativeContentVideoView, Surface surface); 628} 629