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.
5 package org.chromium.content.browser;
7 import android.app.Activity;
8 import android.app.AlertDialog;
9 import android.content.Context;
10 import android.content.ContextWrapper;
11 import android.content.DialogInterface;
12 import android.util.Log;
13 import android.view.Gravity;
14 import android.view.KeyEvent;
15 import android.view.Surface;
16 import android.view.SurfaceHolder;
17 import android.view.SurfaceView;
18 import android.view.View;
19 import android.view.ViewGroup;
20 import android.widget.FrameLayout;
21 import android.widget.LinearLayout;
22 import android.widget.ProgressBar;
23 import android.widget.TextView;
25 import org.chromium.base.CalledByNative;
26 import org.chromium.base.JNINamespace;
27 import org.chromium.base.ThreadUtils;
28 import org.chromium.ui.base.ViewAndroid;
29 import org.chromium.ui.base.ViewAndroidDelegate;
30 import org.chromium.ui.base.WindowAndroid;
33 * This class implements accelerated fullscreen video playback using surface view.
35 @JNINamespace("content")
36 public class ContentVideoView extends FrameLayout
37 implements SurfaceHolder.Callback, ViewAndroidDelegate {
39 private static final String TAG = "ContentVideoView";
41 /* Do not change these values without updating their counterparts
42 * in include/media/mediaplayer.h!
44 private static final int MEDIA_NOP = 0; // interface test message
45 private static final int MEDIA_PREPARED = 1;
46 private static final int MEDIA_PLAYBACK_COMPLETE = 2;
47 private static final int MEDIA_BUFFERING_UPDATE = 3;
48 private static final int MEDIA_SEEK_COMPLETE = 4;
49 private static final int MEDIA_SET_VIDEO_SIZE = 5;
50 private static final int MEDIA_ERROR = 100;
51 private static final int MEDIA_INFO = 200;
54 * Keep these error codes in sync with the code we defined in
55 * MediaPlayerListener.java.
57 public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 2;
58 public static final int MEDIA_ERROR_INVALID_CODE = 3;
60 // all possible internal states
61 private static final int STATE_ERROR = -1;
62 private static final int STATE_IDLE = 0;
63 private static final int STATE_PLAYING = 1;
64 private static final int STATE_PAUSED = 2;
65 private static final int STATE_PLAYBACK_COMPLETED = 3;
67 private SurfaceHolder mSurfaceHolder;
68 private int mVideoWidth;
69 private int mVideoHeight;
70 private int mDuration;
72 // Native pointer to C++ ContentVideoView object.
73 private long mNativeContentVideoView;
75 // webkit should have prepared the media
76 private int mCurrentState = STATE_IDLE;
78 // Strings for displaying media player errors
79 private String mPlaybackErrorText;
80 private String mUnknownErrorText;
81 private String mErrorButton;
82 private String mErrorTitle;
83 private String mVideoLoadingText;
85 // This view will contain the video.
86 private VideoSurfaceView mVideoSurfaceView;
88 // Progress view when the video is loading.
89 private View mProgressView;
91 // The ViewAndroid is used to keep screen on during video playback.
92 private ViewAndroid mViewAndroid;
94 private final ContentVideoViewClient mClient;
96 private class VideoSurfaceView extends SurfaceView {
98 public VideoSurfaceView(Context context) {
103 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
104 // set the default surface view size to (1, 1) so that it won't block
105 // the infobar. (0, 0) is not a valid size for surface view.
108 if (mVideoWidth > 0 && mVideoHeight > 0) {
109 width = getDefaultSize(mVideoWidth, widthMeasureSpec);
110 height = getDefaultSize(mVideoHeight, heightMeasureSpec);
111 if (mVideoWidth * height > width * mVideoHeight) {
112 height = width * mVideoHeight / mVideoWidth;
113 } else if (mVideoWidth * height < width * mVideoHeight) {
114 width = height * mVideoWidth / mVideoHeight;
117 setMeasuredDimension(width, height);
121 private static class ProgressView extends LinearLayout {
123 private final ProgressBar mProgressBar;
124 private final TextView mTextView;
126 public ProgressView(Context context, String videoLoadingText) {
128 setOrientation(LinearLayout.VERTICAL);
129 setLayoutParams(new LinearLayout.LayoutParams(
130 LinearLayout.LayoutParams.WRAP_CONTENT,
131 LinearLayout.LayoutParams.WRAP_CONTENT));
132 mProgressBar = new ProgressBar(context, null, android.R.attr.progressBarStyleLarge);
133 mTextView = new TextView(context);
134 mTextView.setText(videoLoadingText);
135 addView(mProgressBar);
140 private final Runnable mExitFullscreenRunnable = new Runnable() {
143 exitFullscreen(true);
147 protected ContentVideoView(Context context, long nativeContentVideoView,
148 ContentVideoViewClient client) {
150 mNativeContentVideoView = nativeContentVideoView;
151 mViewAndroid = new ViewAndroid(new WindowAndroid(context.getApplicationContext()), this);
153 initResources(context);
154 mVideoSurfaceView = new VideoSurfaceView(context);
155 showContentVideoView();
156 setVisibility(View.VISIBLE);
159 protected ContentVideoViewClient getContentVideoViewClient() {
163 private void initResources(Context context) {
164 if (mPlaybackErrorText != null) return;
165 mPlaybackErrorText = context.getString(
166 org.chromium.content.R.string.media_player_error_text_invalid_progressive_playback);
167 mUnknownErrorText = context.getString(
168 org.chromium.content.R.string.media_player_error_text_unknown);
169 mErrorButton = context.getString(
170 org.chromium.content.R.string.media_player_error_button);
171 mErrorTitle = context.getString(
172 org.chromium.content.R.string.media_player_error_title);
173 mVideoLoadingText = context.getString(
174 org.chromium.content.R.string.media_player_loading_video);
177 protected void showContentVideoView() {
178 mVideoSurfaceView.getHolder().addCallback(this);
179 this.addView(mVideoSurfaceView, new FrameLayout.LayoutParams(
180 ViewGroup.LayoutParams.WRAP_CONTENT,
181 ViewGroup.LayoutParams.WRAP_CONTENT,
184 mProgressView = mClient.getVideoLoadingProgressView();
185 if (mProgressView == null) {
186 mProgressView = new ProgressView(getContext(), mVideoLoadingText);
188 this.addView(mProgressView, new FrameLayout.LayoutParams(
189 ViewGroup.LayoutParams.WRAP_CONTENT,
190 ViewGroup.LayoutParams.WRAP_CONTENT,
194 protected SurfaceView getSurfaceView() {
195 return mVideoSurfaceView;
199 public void onMediaPlayerError(int errorType) {
200 Log.d(TAG, "OnMediaPlayerError: " + errorType);
201 if (mCurrentState == STATE_ERROR || mCurrentState == STATE_PLAYBACK_COMPLETED) {
205 // Ignore some invalid error codes.
206 if (errorType == MEDIA_ERROR_INVALID_CODE) {
210 mCurrentState = STATE_ERROR;
212 /* Pop up an error dialog so the user knows that
213 * something bad has happened. Only try and pop up the dialog
214 * if we're attached to a window. When we're going away and no
215 * longer have a window, don't bother showing the user an error.
217 * TODO(qinmin): We need to review whether this Dialog is OK with
218 * the rest of the browser UI elements.
220 if (getWindowToken() != null) {
223 if (errorType == MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
224 message = mPlaybackErrorText;
226 message = mUnknownErrorText;
230 new AlertDialog.Builder(getContext())
231 .setTitle(mErrorTitle)
233 .setPositiveButton(mErrorButton,
234 new DialogInterface.OnClickListener() {
236 public void onClick(DialogInterface dialog, int whichButton) {
237 /* Inform that the video is over.
242 .setCancelable(false)
244 } catch (RuntimeException e) {
245 Log.e(TAG, "Cannot show the alert dialog, error message: " + message, e);
251 private void onVideoSizeChanged(int width, int height) {
253 mVideoHeight = height;
254 // This will trigger the SurfaceView.onMeasure() call.
255 mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
259 protected void onBufferingUpdate(int percent) {
263 private void onPlaybackComplete() {
268 protected void onUpdateMediaMetadata(
274 boolean canSeekForward) {
275 mDuration = duration;
276 mProgressView.setVisibility(View.GONE);
277 mCurrentState = isPlaying() ? STATE_PLAYING : STATE_PAUSED;
278 onVideoSizeChanged(videoWidth, videoHeight);
282 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
286 public void surfaceCreated(SurfaceHolder holder) {
287 mSurfaceHolder = holder;
292 public void surfaceDestroyed(SurfaceHolder holder) {
293 if (mNativeContentVideoView != 0) {
294 nativeSetSurface(mNativeContentVideoView, null);
296 mSurfaceHolder = null;
297 post(mExitFullscreenRunnable);
301 protected void openVideo() {
302 if (mSurfaceHolder != null) {
303 mCurrentState = STATE_IDLE;
304 if (mNativeContentVideoView != 0) {
305 nativeRequestMediaMetadata(mNativeContentVideoView);
306 nativeSetSurface(mNativeContentVideoView,
307 mSurfaceHolder.getSurface());
312 protected void onCompletion() {
313 mCurrentState = STATE_PLAYBACK_COMPLETED;
317 protected boolean isInPlaybackState() {
318 return (mCurrentState != STATE_ERROR && mCurrentState != STATE_IDLE);
321 protected void start() {
322 if (isInPlaybackState()) {
323 if (mNativeContentVideoView != 0) {
324 nativePlay(mNativeContentVideoView);
326 mCurrentState = STATE_PLAYING;
330 protected void pause() {
331 if (isInPlaybackState()) {
333 if (mNativeContentVideoView != 0) {
334 nativePause(mNativeContentVideoView);
336 mCurrentState = STATE_PAUSED;
341 // cache duration as mDuration for faster access
342 protected int getDuration() {
343 if (isInPlaybackState()) {
347 if (mNativeContentVideoView != 0) {
348 mDuration = nativeGetDurationInMilliSeconds(mNativeContentVideoView);
358 protected int getCurrentPosition() {
359 if (isInPlaybackState() && mNativeContentVideoView != 0) {
360 return nativeGetCurrentPosition(mNativeContentVideoView);
365 protected void seekTo(int msec) {
366 if (mNativeContentVideoView != 0) {
367 nativeSeekTo(mNativeContentVideoView, msec);
371 public boolean isPlaying() {
372 return mNativeContentVideoView != 0 && nativeIsPlaying(mNativeContentVideoView);
376 private static ContentVideoView createContentVideoView(
377 Context context, long nativeContentVideoView, ContentVideoViewClient client,
379 ThreadUtils.assertOnUiThread();
380 ContentVideoView videoView = null;
382 videoView = new ContentVideoViewLegacy(context, nativeContentVideoView, client);
384 videoView = new ContentVideoView(context, nativeContentVideoView, client);
387 if (videoView.getContentVideoViewClient().onShowCustomView(videoView)) {
393 private static boolean isActivityContext(Context context) {
394 // Only retrieve the base context if the supplied context is a ContextWrapper but not
395 // an Activity, given that Activity is already a subclass of ContextWrapper.
396 if (context instanceof ContextWrapper && !(context instanceof Activity)) {
397 context = ((ContextWrapper) context).getBaseContext();
399 return context instanceof Activity;
402 public void removeSurfaceView() {
403 removeView(mVideoSurfaceView);
404 removeView(mProgressView);
405 mVideoSurfaceView = null;
406 mProgressView = null;
409 public void exitFullscreen(boolean relaseMediaPlayer) {
410 destroyContentVideoView(false);
411 if (mNativeContentVideoView != 0) {
412 nativeExitFullscreen(mNativeContentVideoView, relaseMediaPlayer);
413 mNativeContentVideoView = 0;
418 private void onExitFullscreen() {
419 exitFullscreen(false);
423 * This method shall only be called by native and exitFullscreen,
424 * To exit fullscreen, use exitFullscreen in Java.
427 protected void destroyContentVideoView(boolean nativeViewDestroyed) {
428 if (mVideoSurfaceView != null) {
430 setVisibility(View.GONE);
432 // To prevent re-entrance, call this after removeSurfaceView.
433 mClient.onDestroyContentVideoView();
435 if (nativeViewDestroyed) {
436 mNativeContentVideoView = 0;
440 public static ContentVideoView getContentVideoView() {
441 return nativeGetSingletonJavaContentVideoView();
445 public boolean onKeyUp(int keyCode, KeyEvent event) {
446 if (keyCode == KeyEvent.KEYCODE_BACK) {
447 exitFullscreen(false);
450 return super.onKeyUp(keyCode, event);
454 public View acquireAnchorView() {
455 View anchorView = new View(getContext());
461 public void setAnchorViewPosition(View view, float x, float y, float width, float height) {
462 Log.e(TAG, "setAnchorViewPosition isn't implemented");
466 public void releaseAnchorView(View anchorView) {
467 removeView(anchorView);
471 private long getNativeViewAndroid() {
472 return mViewAndroid.getNativePointer();
475 private static native ContentVideoView nativeGetSingletonJavaContentVideoView();
476 private native void nativeExitFullscreen(long nativeContentVideoView,
477 boolean relaseMediaPlayer);
478 private native int nativeGetCurrentPosition(long nativeContentVideoView);
479 private native int nativeGetDurationInMilliSeconds(long nativeContentVideoView);
480 private native void nativeRequestMediaMetadata(long nativeContentVideoView);
481 private native int nativeGetVideoWidth(long nativeContentVideoView);
482 private native int nativeGetVideoHeight(long nativeContentVideoView);
483 private native boolean nativeIsPlaying(long nativeContentVideoView);
484 private native void nativePause(long nativeContentVideoView);
485 private native void nativePlay(long nativeContentVideoView);
486 private native void nativeSeekTo(long nativeContentVideoView, int msec);
487 private native void nativeSetSurface(long nativeContentVideoView, Surface surface);