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.
5 package org.chromium.content.browser;
7 import android.app.AlertDialog;
8 import android.content.Context;
9 import android.content.DialogInterface;
10 import android.graphics.Color;
11 import android.os.Handler;
12 import android.os.Message;
13 import android.os.RemoteException;
14 import android.util.Log;
15 import android.view.Gravity;
16 import android.view.KeyEvent;
17 import android.view.MotionEvent;
18 import android.view.Surface;
19 import android.view.SurfaceHolder;
20 import android.view.SurfaceView;
21 import android.view.View;
22 import android.view.ViewGroup;
23 import android.widget.FrameLayout;
24 import android.widget.LinearLayout;
25 import android.widget.MediaController;
26 import android.widget.ProgressBar;
27 import android.widget.TextView;
29 import java.lang.ref.WeakReference;
31 import org.chromium.base.CalledByNative;
32 import org.chromium.base.JNINamespace;
33 import org.chromium.base.ThreadUtils;
34 import org.chromium.content.common.IChildProcessService;
35 import org.chromium.content.R;
37 @JNINamespace("content")
38 public class ContentVideoView
40 implements ContentVideoViewControls.Delegate,
41 SurfaceHolder.Callback, View.OnTouchListener, View.OnKeyListener {
43 private static final String TAG = "ContentVideoView";
45 /* Do not change these values without updating their counterparts
46 * in include/media/mediaplayer.h!
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;
58 * Keep these error codes in sync with the code we defined in
59 * MediaPlayerListener.java.
61 public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 2;
62 public static final int MEDIA_ERROR_INVALID_CODE = 3;
64 // all possible internal states
65 private static final int STATE_ERROR = -1;
66 private static final int STATE_IDLE = 0;
67 private static final int STATE_PLAYING = 1;
68 private static final int STATE_PAUSED = 2;
69 private static final int STATE_PLAYBACK_COMPLETED = 3;
71 private SurfaceHolder mSurfaceHolder;
72 private int mVideoWidth;
73 private int mVideoHeight;
74 private int mCurrentBufferPercentage;
75 private int mDuration;
76 private ContentVideoViewControls mControls;
77 private boolean mCanPause;
78 private boolean mCanSeekBack;
79 private boolean mCanSeekForward;
81 // Native pointer to C++ ContentVideoView object.
82 private int mNativeContentVideoView;
84 // webkit should have prepared the media
85 private int mCurrentState = STATE_IDLE;
87 // Strings for displaying media player errors
88 private String mPlaybackErrorText;
89 private String mUnknownErrorText;
90 private String mErrorButton;
91 private String mErrorTitle;
92 private String mVideoLoadingText;
94 // This view will contain the video.
95 private VideoSurfaceView mVideoSurfaceView;
97 // Progress view when the video is loading.
98 private View mProgressView;
100 private Surface mSurface;
102 private ContentVideoViewClient mClient;
104 private class VideoSurfaceView extends SurfaceView {
106 public VideoSurfaceView(Context context) {
111 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
112 int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
113 int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
114 if (mVideoWidth > 0 && mVideoHeight > 0) {
115 if ( mVideoWidth * height > width * mVideoHeight ) {
116 height = width * mVideoHeight / mVideoWidth;
117 } else if ( mVideoWidth * height < width * mVideoHeight ) {
118 width = height * mVideoWidth / mVideoHeight;
121 setMeasuredDimension(width, height);
125 private static class ProgressView extends LinearLayout {
127 private ProgressBar mProgressBar;
128 private TextView mTextView;
130 public ProgressView(Context context, String videoLoadingText) {
132 setOrientation(LinearLayout.VERTICAL);
133 setLayoutParams(new LinearLayout.LayoutParams(
134 LinearLayout.LayoutParams.WRAP_CONTENT,
135 LinearLayout.LayoutParams.WRAP_CONTENT));
136 mProgressBar = new ProgressBar(context, null, android.R.attr.progressBarStyleLarge);
137 mTextView = new TextView(context);
138 mTextView.setText(videoLoadingText);
139 addView(mProgressBar);
144 private static class FullScreenControls implements ContentVideoViewControls {
147 MediaController mMediaController;
149 public FullScreenControls(Context context, View video) {
150 mMediaController = new MediaController(context);
156 mMediaController.show();
157 if (mVideoView != null) {
158 mVideoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
163 public void show(int timeout_ms) {
164 mMediaController.show(timeout_ms);
169 if (mVideoView != null) {
170 mVideoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
172 mMediaController.hide();
176 public boolean isShowing() {
177 return mMediaController.isShowing();
181 public void setEnabled(boolean enabled) {
182 mMediaController.setEnabled(enabled);
186 public void setDelegate(Delegate delegate) {
187 mMediaController.setMediaPlayer(delegate);
191 public void setAnchorView(View view) {
192 mMediaController.setAnchorView(view);
196 private Runnable mExitFullscreenRunnable = new Runnable() {
199 exitFullscreen(true);
203 private ContentVideoView(Context context, int nativeContentVideoView,
204 ContentVideoViewClient client) {
206 mNativeContentVideoView = nativeContentVideoView;
208 initResources(context);
209 mCurrentBufferPercentage = 0;
210 mVideoSurfaceView = new VideoSurfaceView(context);
211 setBackgroundColor(Color.BLACK);
212 showContentVideoView();
213 setVisibility(View.VISIBLE);
214 mClient.onShowCustomView(this);
217 private void initResources(Context context) {
218 if (mPlaybackErrorText != null) return;
219 mPlaybackErrorText = context.getString(
220 org.chromium.content.R.string.media_player_error_text_invalid_progressive_playback);
221 mUnknownErrorText = context.getString(
222 org.chromium.content.R.string.media_player_error_text_unknown);
223 mErrorButton = context.getString(
224 org.chromium.content.R.string.media_player_error_button);
225 mErrorTitle = context.getString(
226 org.chromium.content.R.string.media_player_error_title);
227 mVideoLoadingText = context.getString(
228 org.chromium.content.R.string.media_player_loading_video);
231 private void showContentVideoView() {
232 FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
233 ViewGroup.LayoutParams.MATCH_PARENT,
234 ViewGroup.LayoutParams.MATCH_PARENT,
236 this.addView(mVideoSurfaceView, layoutParams);
237 View progressView = mClient.getVideoLoadingProgressView();
238 if (progressView != null) {
239 mProgressView = progressView;
241 mProgressView = new ProgressView(getContext(), mVideoLoadingText);
243 this.addView(mProgressView, new FrameLayout.LayoutParams(
244 ViewGroup.LayoutParams.WRAP_CONTENT,
245 ViewGroup.LayoutParams.WRAP_CONTENT,
247 mVideoSurfaceView.setZOrderOnTop(true);
248 mVideoSurfaceView.setOnKeyListener(this);
249 mVideoSurfaceView.setOnTouchListener(this);
250 mVideoSurfaceView.getHolder().addCallback(this);
251 mVideoSurfaceView.setFocusable(true);
252 mVideoSurfaceView.setFocusableInTouchMode(true);
253 mVideoSurfaceView.requestFocus();
257 public void onMediaPlayerError(int errorType) {
258 Log.d(TAG, "OnMediaPlayerError: " + errorType);
259 if (mCurrentState == STATE_ERROR || mCurrentState == STATE_PLAYBACK_COMPLETED) {
263 // Ignore some invalid error codes.
264 if (errorType == MEDIA_ERROR_INVALID_CODE) {
268 mCurrentState = STATE_ERROR;
269 if (mControls != null) {
273 /* Pop up an error dialog so the user knows that
274 * something bad has happened. Only try and pop up the dialog
275 * if we're attached to a window. When we're going away and no
276 * longer have a window, don't bother showing the user an error.
278 * TODO(qinmin): We need to review whether this Dialog is OK with
279 * the rest of the browser UI elements.
281 if (getWindowToken() != null) {
284 if (errorType == MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
285 message = mPlaybackErrorText;
287 message = mUnknownErrorText;
290 new AlertDialog.Builder(getContext())
291 .setTitle(mErrorTitle)
293 .setPositiveButton(mErrorButton,
294 new DialogInterface.OnClickListener() {
295 public void onClick(DialogInterface dialog, int whichButton) {
296 /* Inform that the video is over.
301 .setCancelable(false)
307 private void onVideoSizeChanged(int width, int height) {
309 mVideoHeight = height;
310 // This will trigger the SurfaceView.onMeasure() call.
311 mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
315 private void onBufferingUpdate(int percent) {
316 mCurrentBufferPercentage = percent;
320 private void onPlaybackComplete() {
325 private void onUpdateMediaMetadata(
331 boolean canSeekForward) {
332 mProgressView.setVisibility(View.GONE);
333 mDuration = duration;
334 mCanPause = canPause;
335 mCanSeekBack = canSeekBack;
336 mCanSeekForward = canSeekForward;
337 mCurrentState = isPlaying() ? STATE_PLAYING : STATE_PAUSED;
338 if (mControls != null) {
339 mControls.setEnabled(true);
340 // If paused , should show the controller for ever.
347 onVideoSizeChanged(videoWidth, videoHeight);
351 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
352 mVideoSurfaceView.setFocusable(true);
353 mVideoSurfaceView.setFocusableInTouchMode(true);
354 if (isInPlaybackState() && mControls != null) {
360 public void surfaceCreated(SurfaceHolder holder) {
361 mSurfaceHolder = holder;
366 public void surfaceDestroyed(SurfaceHolder holder) {
367 if (mNativeContentVideoView != 0) {
368 nativeSetSurface(mNativeContentVideoView, null);
370 mSurfaceHolder = null;
371 post(mExitFullscreenRunnable);
374 private void setControls(ContentVideoViewControls controls) {
375 if (mControls != null) {
378 mControls = controls;
382 private void attachControls() {
383 if (mControls != null) {
384 mControls.setDelegate(this);
385 mControls.setAnchorView(mVideoSurfaceView);
386 mControls.setEnabled(false);
391 private void openVideo() {
392 if (mSurfaceHolder != null) {
393 mCurrentState = STATE_IDLE;
394 mCurrentBufferPercentage = 0;
395 ContentVideoViewControls controls = mClient.createControls();
396 if (controls == null) {
397 controls = new FullScreenControls(getContext(), this);
399 setControls(controls);
400 if (mNativeContentVideoView != 0) {
401 nativeUpdateMediaMetadata(mNativeContentVideoView);
402 nativeSetSurface(mNativeContentVideoView,
403 mSurfaceHolder.getSurface());
408 private void onCompletion() {
409 mCurrentState = STATE_PLAYBACK_COMPLETED;
410 if (mControls != null) {
416 public boolean onTouch(View v, MotionEvent event) {
417 if (isInPlaybackState() && mControls != null &&
418 event.getAction() == MotionEvent.ACTION_DOWN) {
419 toggleMediaControlsVisiblity();
425 public boolean onTrackballEvent(MotionEvent ev) {
426 if (isInPlaybackState() && mControls != null) {
427 toggleMediaControlsVisiblity();
433 public boolean onKey(View v, int keyCode, KeyEvent event) {
434 boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&
435 keyCode != KeyEvent.KEYCODE_VOLUME_UP &&
436 keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&
437 keyCode != KeyEvent.KEYCODE_VOLUME_MUTE &&
438 keyCode != KeyEvent.KEYCODE_CALL &&
439 keyCode != KeyEvent.KEYCODE_MENU &&
440 keyCode != KeyEvent.KEYCODE_SEARCH &&
441 keyCode != KeyEvent.KEYCODE_ENDCALL;
442 if (isInPlaybackState() && isKeyCodeSupported && mControls != null) {
443 if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
444 keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
453 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
459 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
460 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
467 toggleMediaControlsVisiblity();
469 } else if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
470 exitFullscreen(false);
472 } else if (keyCode == KeyEvent.KEYCODE_MENU || keyCode == KeyEvent.KEYCODE_SEARCH) {
475 return super.onKeyDown(keyCode, event);
478 private void toggleMediaControlsVisiblity() {
479 if (mControls.isShowing()) {
486 private boolean isInPlaybackState() {
487 return (mCurrentState != STATE_ERROR && mCurrentState != STATE_IDLE);
491 public void start() {
492 if (isInPlaybackState()) {
493 if (mNativeContentVideoView != 0) {
494 nativePlay(mNativeContentVideoView);
496 mCurrentState = STATE_PLAYING;
501 public void pause() {
502 if (isInPlaybackState()) {
504 if (mNativeContentVideoView != 0) {
505 nativePause(mNativeContentVideoView);
507 mCurrentState = STATE_PAUSED;
512 // cache duration as mDuration for faster access
514 public int getDuration() {
515 if (isInPlaybackState()) {
519 if (mNativeContentVideoView != 0) {
520 mDuration = nativeGetDurationInMilliSeconds(mNativeContentVideoView);
531 public int getCurrentPosition() {
532 if (isInPlaybackState() && mNativeContentVideoView != 0) {
533 return nativeGetCurrentPosition(mNativeContentVideoView);
539 public void seekTo(int msec) {
540 if (mNativeContentVideoView != 0) {
541 nativeSeekTo(mNativeContentVideoView, msec);
546 public boolean isPlaying() {
547 return mNativeContentVideoView != 0 && nativeIsPlaying(mNativeContentVideoView);
551 public int getBufferPercentage() {
552 return mCurrentBufferPercentage;
556 public boolean canPause() {
561 public boolean canSeekBackward() {
566 public boolean canSeekForward() {
567 return mCanSeekForward;
570 public int getAudioSessionId() {
575 private static ContentVideoView createContentVideoView(
576 Context context, int nativeContentVideoView, ContentVideoViewClient client) {
577 ThreadUtils.assertOnUiThread();
578 return new ContentVideoView(context, nativeContentVideoView, client);
581 private void removeControls() {
582 if (mControls != null) {
583 mControls.setEnabled(false);
589 public void removeSurfaceView() {
590 removeView(mVideoSurfaceView);
591 removeView(mProgressView);
592 mVideoSurfaceView = null;
593 mProgressView = null;
596 public void exitFullscreen(boolean relaseMediaPlayer) {
597 destroyContentVideoView(false);
598 if (mNativeContentVideoView != 0) {
599 nativeExitFullscreen(mNativeContentVideoView, relaseMediaPlayer);
600 mNativeContentVideoView = 0;
605 * This method shall only be called by native and exitFullscreen,
606 * To exit fullscreen, use exitFullscreen in Java.
609 private void destroyContentVideoView(boolean nativeViewDestroyed) {
610 if (mVideoSurfaceView != null) {
613 setVisibility(View.GONE);
615 // To prevent re-entrance, call this after removeSurfaceView.
616 mClient.onDestroyContentVideoView();
618 if (nativeViewDestroyed) {
619 mNativeContentVideoView = 0;
623 public static ContentVideoView getContentVideoView() {
624 return nativeGetSingletonJavaContentVideoView();
628 public boolean onTouchEvent(MotionEvent ev) {
633 public boolean onKeyDown(int keyCode, KeyEvent event) {
634 if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
635 exitFullscreen(false);
638 return super.onKeyDown(keyCode, event);
641 private static native ContentVideoView nativeGetSingletonJavaContentVideoView();
642 private native void nativeExitFullscreen(int nativeContentVideoView, boolean relaseMediaPlayer);
643 private native int nativeGetCurrentPosition(int nativeContentVideoView);
644 private native int nativeGetDurationInMilliSeconds(int nativeContentVideoView);
645 private native void nativeUpdateMediaMetadata(int nativeContentVideoView);
646 private native int nativeGetVideoWidth(int nativeContentVideoView);
647 private native int nativeGetVideoHeight(int nativeContentVideoView);
648 private native boolean nativeIsPlaying(int nativeContentVideoView);
649 private native void nativePause(int nativeContentVideoView);
650 private native void nativePlay(int nativeContentVideoView);
651 private native void nativeSeekTo(int nativeContentVideoView, int msec);
652 private native void nativeSetSurface(int nativeContentVideoView, Surface surface);