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.AlertDialog;
8 import android.content.Context;
9 import android.content.DialogInterface;
10 import android.util.Log;
11 import android.view.Gravity;
12 import android.view.KeyEvent;
13 import android.view.Surface;
14 import android.view.SurfaceHolder;
15 import android.view.SurfaceView;
16 import android.view.View;
17 import android.view.ViewGroup;
18 import android.widget.FrameLayout;
19 import android.widget.LinearLayout;
20 import android.widget.ProgressBar;
21 import android.widget.TextView;
23 import org.chromium.base.CalledByNative;
24 import org.chromium.base.JNINamespace;
25 import org.chromium.base.ThreadUtils;
26 import org.chromium.ui.base.ViewAndroid;
27 import org.chromium.ui.base.ViewAndroidDelegate;
28 import org.chromium.ui.base.WindowAndroid;
31 * This class implements accelerated fullscreen video playback using surface view.
33 @JNINamespace("content")
34 public class ContentVideoView extends FrameLayout
35 implements SurfaceHolder.Callback, ViewAndroidDelegate {
37 private static final String TAG = "ContentVideoView";
39 /* Do not change these values without updating their counterparts
40 * in include/media/mediaplayer.h!
42 private static final int MEDIA_NOP = 0; // interface test message
43 private static final int MEDIA_PREPARED = 1;
44 private static final int MEDIA_PLAYBACK_COMPLETE = 2;
45 private static final int MEDIA_BUFFERING_UPDATE = 3;
46 private static final int MEDIA_SEEK_COMPLETE = 4;
47 private static final int MEDIA_SET_VIDEO_SIZE = 5;
48 private static final int MEDIA_ERROR = 100;
49 private static final int MEDIA_INFO = 200;
52 * Keep these error codes in sync with the code we defined in
53 * MediaPlayerListener.java.
55 public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 2;
56 public static final int MEDIA_ERROR_INVALID_CODE = 3;
58 // all possible internal states
59 private static final int STATE_ERROR = -1;
60 private static final int STATE_IDLE = 0;
61 private static final int STATE_PLAYING = 1;
62 private static final int STATE_PAUSED = 2;
63 private static final int STATE_PLAYBACK_COMPLETED = 3;
65 private SurfaceHolder mSurfaceHolder;
66 private int mVideoWidth;
67 private int mVideoHeight;
68 private int mDuration;
70 // Native pointer to C++ ContentVideoView object.
71 private long mNativeContentVideoView;
73 // webkit should have prepared the media
74 private int mCurrentState = STATE_IDLE;
76 // Strings for displaying media player errors
77 private String mPlaybackErrorText;
78 private String mUnknownErrorText;
79 private String mErrorButton;
80 private String mErrorTitle;
81 private String mVideoLoadingText;
83 // This view will contain the video.
84 private VideoSurfaceView mVideoSurfaceView;
86 // Progress view when the video is loading.
87 private View mProgressView;
89 // The ViewAndroid is used to keep screen on during video playback.
90 private ViewAndroid mViewAndroid;
92 private final ContentVideoViewClient mClient;
94 private class VideoSurfaceView extends SurfaceView {
96 public VideoSurfaceView(Context context) {
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;
111 setMeasuredDimension(width, height);
115 private static class ProgressView extends LinearLayout {
117 private final ProgressBar mProgressBar;
118 private final TextView mTextView;
120 public ProgressView(Context context, String videoLoadingText) {
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);
134 private final Runnable mExitFullscreenRunnable = new Runnable() {
137 exitFullscreen(true);
141 protected ContentVideoView(Context context, long nativeContentVideoView,
142 ContentVideoViewClient client) {
144 mNativeContentVideoView = nativeContentVideoView;
145 mViewAndroid = new ViewAndroid(new WindowAndroid(context.getApplicationContext()), this);
147 initResources(context);
148 mVideoSurfaceView = new VideoSurfaceView(context);
149 showContentVideoView();
150 setVisibility(View.VISIBLE);
151 mClient.onShowCustomView(this);
154 private void initResources(Context context) {
155 if (mPlaybackErrorText != null) return;
156 mPlaybackErrorText = context.getString(
157 org.chromium.content.R.string.media_player_error_text_invalid_progressive_playback);
158 mUnknownErrorText = context.getString(
159 org.chromium.content.R.string.media_player_error_text_unknown);
160 mErrorButton = context.getString(
161 org.chromium.content.R.string.media_player_error_button);
162 mErrorTitle = context.getString(
163 org.chromium.content.R.string.media_player_error_title);
164 mVideoLoadingText = context.getString(
165 org.chromium.content.R.string.media_player_loading_video);
168 protected void showContentVideoView() {
169 mVideoSurfaceView.getHolder().addCallback(this);
170 this.addView(mVideoSurfaceView, new FrameLayout.LayoutParams(
171 ViewGroup.LayoutParams.MATCH_PARENT,
172 ViewGroup.LayoutParams.MATCH_PARENT,
175 mProgressView = mClient.getVideoLoadingProgressView();
176 if (mProgressView == null) {
177 mProgressView = new ProgressView(getContext(), mVideoLoadingText);
179 this.addView(mProgressView, new FrameLayout.LayoutParams(
180 ViewGroup.LayoutParams.WRAP_CONTENT,
181 ViewGroup.LayoutParams.WRAP_CONTENT,
185 protected SurfaceView getSurfaceView() {
186 return mVideoSurfaceView;
190 public void onMediaPlayerError(int errorType) {
191 Log.d(TAG, "OnMediaPlayerError: " + errorType);
192 if (mCurrentState == STATE_ERROR || mCurrentState == STATE_PLAYBACK_COMPLETED) {
196 // Ignore some invalid error codes.
197 if (errorType == MEDIA_ERROR_INVALID_CODE) {
201 mCurrentState = STATE_ERROR;
203 /* Pop up an error dialog so the user knows that
204 * something bad has happened. Only try and pop up the dialog
205 * if we're attached to a window. When we're going away and no
206 * longer have a window, don't bother showing the user an error.
208 * TODO(qinmin): We need to review whether this Dialog is OK with
209 * the rest of the browser UI elements.
211 if (getWindowToken() != null) {
214 if (errorType == MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
215 message = mPlaybackErrorText;
217 message = mUnknownErrorText;
221 new AlertDialog.Builder(getContext())
222 .setTitle(mErrorTitle)
224 .setPositiveButton(mErrorButton,
225 new DialogInterface.OnClickListener() {
227 public void onClick(DialogInterface dialog, int whichButton) {
228 /* Inform that the video is over.
233 .setCancelable(false)
235 } catch (RuntimeException e) {
236 Log.e(TAG, "Cannot show the alert dialog, error message: " + message, e);
242 private void onVideoSizeChanged(int width, int height) {
244 mVideoHeight = height;
245 // This will trigger the SurfaceView.onMeasure() call.
246 mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
250 protected void onBufferingUpdate(int percent) {
254 private void onPlaybackComplete() {
259 protected void onUpdateMediaMetadata(
265 boolean canSeekForward) {
266 mDuration = duration;
267 mProgressView.setVisibility(View.GONE);
268 mCurrentState = isPlaying() ? STATE_PLAYING : STATE_PAUSED;
269 onVideoSizeChanged(videoWidth, videoHeight);
273 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
277 public void surfaceCreated(SurfaceHolder holder) {
278 mSurfaceHolder = holder;
283 public void surfaceDestroyed(SurfaceHolder holder) {
284 if (mNativeContentVideoView != 0) {
285 nativeSetSurface(mNativeContentVideoView, null);
287 mSurfaceHolder = null;
288 post(mExitFullscreenRunnable);
292 protected void openVideo() {
293 if (mSurfaceHolder != null) {
294 mCurrentState = STATE_IDLE;
295 if (mNativeContentVideoView != 0) {
296 nativeRequestMediaMetadata(mNativeContentVideoView);
297 nativeSetSurface(mNativeContentVideoView,
298 mSurfaceHolder.getSurface());
303 protected void onCompletion() {
304 mCurrentState = STATE_PLAYBACK_COMPLETED;
308 protected boolean isInPlaybackState() {
309 return (mCurrentState != STATE_ERROR && mCurrentState != STATE_IDLE);
312 protected void start() {
313 if (isInPlaybackState()) {
314 if (mNativeContentVideoView != 0) {
315 nativePlay(mNativeContentVideoView);
317 mCurrentState = STATE_PLAYING;
321 protected void pause() {
322 if (isInPlaybackState()) {
324 if (mNativeContentVideoView != 0) {
325 nativePause(mNativeContentVideoView);
327 mCurrentState = STATE_PAUSED;
332 // cache duration as mDuration for faster access
333 protected int getDuration() {
334 if (isInPlaybackState()) {
338 if (mNativeContentVideoView != 0) {
339 mDuration = nativeGetDurationInMilliSeconds(mNativeContentVideoView);
349 protected int getCurrentPosition() {
350 if (isInPlaybackState() && mNativeContentVideoView != 0) {
351 return nativeGetCurrentPosition(mNativeContentVideoView);
356 protected void seekTo(int msec) {
357 if (mNativeContentVideoView != 0) {
358 nativeSeekTo(mNativeContentVideoView, msec);
362 protected boolean isPlaying() {
363 return mNativeContentVideoView != 0 && nativeIsPlaying(mNativeContentVideoView);
367 private static ContentVideoView createContentVideoView(
368 Context context, long nativeContentVideoView, ContentVideoViewClient client,
370 ThreadUtils.assertOnUiThread();
372 return new ContentVideoViewLegacy(context, nativeContentVideoView, client);
374 return new ContentVideoView(context, nativeContentVideoView, client);
378 public void removeSurfaceView() {
379 removeView(mVideoSurfaceView);
380 removeView(mProgressView);
381 mVideoSurfaceView = null;
382 mProgressView = null;
385 public void exitFullscreen(boolean relaseMediaPlayer) {
386 destroyContentVideoView(false);
387 if (mNativeContentVideoView != 0) {
388 nativeExitFullscreen(mNativeContentVideoView, relaseMediaPlayer);
389 mNativeContentVideoView = 0;
394 private void onExitFullscreen() {
395 exitFullscreen(false);
399 * This method shall only be called by native and exitFullscreen,
400 * To exit fullscreen, use exitFullscreen in Java.
403 protected void destroyContentVideoView(boolean nativeViewDestroyed) {
404 if (mVideoSurfaceView != null) {
406 setVisibility(View.GONE);
408 // To prevent re-entrance, call this after removeSurfaceView.
409 mClient.onDestroyContentVideoView();
411 if (nativeViewDestroyed) {
412 mNativeContentVideoView = 0;
416 public static ContentVideoView getContentVideoView() {
417 return nativeGetSingletonJavaContentVideoView();
421 public boolean onKeyUp(int keyCode, KeyEvent event) {
422 if (keyCode == KeyEvent.KEYCODE_BACK) {
423 exitFullscreen(false);
426 return super.onKeyUp(keyCode, event);
430 public View acquireAnchorView() {
431 View anchorView = new View(getContext());
437 public void setAnchorViewPosition(View view, float x, float y, float width, float height) {
438 Log.e(TAG, "setAnchorViewPosition isn't implemented");
442 public void releaseAnchorView(View anchorView) {
443 removeView(anchorView);
447 private long getNativeViewAndroid() {
448 return mViewAndroid.getNativePointer();
451 private static native ContentVideoView nativeGetSingletonJavaContentVideoView();
452 private native void nativeExitFullscreen(long nativeContentVideoView,
453 boolean relaseMediaPlayer);
454 private native int nativeGetCurrentPosition(long nativeContentVideoView);
455 private native int nativeGetDurationInMilliSeconds(long nativeContentVideoView);
456 private native void nativeRequestMediaMetadata(long nativeContentVideoView);
457 private native int nativeGetVideoWidth(long nativeContentVideoView);
458 private native int nativeGetVideoHeight(long nativeContentVideoView);
459 private native boolean nativeIsPlaying(long nativeContentVideoView);
460 private native void nativePause(long nativeContentVideoView);
461 private native void nativePlay(long nativeContentVideoView);
462 private native void nativeSeekTo(long nativeContentVideoView, int msec);
463 private native void nativeSetSurface(long nativeContentVideoView, Surface surface);