Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / content / public / android / java / src / org / chromium / content / browser / ContentVideoView.java
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.
4
5 package org.chromium.content.browser;
6
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;
24
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;
31
32 /**
33  * This class implements accelerated fullscreen video playback using surface view.
34  */
35 @JNINamespace("content")
36 public class ContentVideoView extends FrameLayout
37         implements SurfaceHolder.Callback, ViewAndroidDelegate {
38
39     private static final String TAG = "ContentVideoView";
40
41     /* Do not change these values without updating their counterparts
42      * in include/media/mediaplayer.h!
43      */
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;
52
53     /**
54      * Keep these error codes in sync with the code we defined in
55      * MediaPlayerListener.java.
56      */
57     public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 2;
58     public static final int MEDIA_ERROR_INVALID_CODE = 3;
59
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;
66
67     private SurfaceHolder mSurfaceHolder;
68     private int mVideoWidth;
69     private int mVideoHeight;
70     private int mDuration;
71
72     // Native pointer to C++ ContentVideoView object.
73     private long mNativeContentVideoView;
74
75     // webkit should have prepared the media
76     private int mCurrentState = STATE_IDLE;
77
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;
84
85     // This view will contain the video.
86     private VideoSurfaceView mVideoSurfaceView;
87
88     // Progress view when the video is loading.
89     private View mProgressView;
90
91     // The ViewAndroid is used to keep screen on during video playback.
92     private ViewAndroid mViewAndroid;
93
94     private final ContentVideoViewClient mClient;
95
96     private class VideoSurfaceView extends SurfaceView {
97
98         public VideoSurfaceView(Context context) {
99             super(context);
100         }
101
102         @Override
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.
106             int width = 1;
107             int height = 1;
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;
115                 }
116             }
117             setMeasuredDimension(width, height);
118         }
119     }
120
121     private static class ProgressView extends LinearLayout {
122
123         private final ProgressBar mProgressBar;
124         private final TextView mTextView;
125
126         public ProgressView(Context context, String videoLoadingText) {
127             super(context);
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);
136             addView(mTextView);
137         }
138     }
139
140     private final Runnable mExitFullscreenRunnable = new Runnable() {
141         @Override
142         public void run() {
143             exitFullscreen(true);
144         }
145     };
146
147     protected ContentVideoView(Context context, long nativeContentVideoView,
148             ContentVideoViewClient client) {
149         super(context);
150         mNativeContentVideoView = nativeContentVideoView;
151         mViewAndroid = new ViewAndroid(new WindowAndroid(context.getApplicationContext()), this);
152         mClient = client;
153         initResources(context);
154         mVideoSurfaceView = new VideoSurfaceView(context);
155         showContentVideoView();
156         setVisibility(View.VISIBLE);
157     }
158
159     protected ContentVideoViewClient getContentVideoViewClient() {
160         return mClient;
161     }
162
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);
175     }
176
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,
182                 Gravity.CENTER));
183
184         mProgressView = mClient.getVideoLoadingProgressView();
185         if (mProgressView == null) {
186             mProgressView = new ProgressView(getContext(), mVideoLoadingText);
187         }
188         this.addView(mProgressView, new FrameLayout.LayoutParams(
189                 ViewGroup.LayoutParams.WRAP_CONTENT,
190                 ViewGroup.LayoutParams.WRAP_CONTENT,
191                 Gravity.CENTER));
192     }
193
194     protected SurfaceView getSurfaceView() {
195         return mVideoSurfaceView;
196     }
197
198     @CalledByNative
199     public void onMediaPlayerError(int errorType) {
200         Log.d(TAG, "OnMediaPlayerError: " + errorType);
201         if (mCurrentState == STATE_ERROR || mCurrentState == STATE_PLAYBACK_COMPLETED) {
202             return;
203         }
204
205         // Ignore some invalid error codes.
206         if (errorType == MEDIA_ERROR_INVALID_CODE) {
207             return;
208         }
209
210         mCurrentState = STATE_ERROR;
211
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.
216          *
217          * TODO(qinmin): We need to review whether this Dialog is OK with
218          * the rest of the browser UI elements.
219          */
220         if (getWindowToken() != null) {
221             String message;
222
223             if (errorType == MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
224                 message = mPlaybackErrorText;
225             } else {
226                 message = mUnknownErrorText;
227             }
228
229             try {
230                 new AlertDialog.Builder(getContext())
231                     .setTitle(mErrorTitle)
232                     .setMessage(message)
233                     .setPositiveButton(mErrorButton,
234                             new DialogInterface.OnClickListener() {
235                         @Override
236                         public void onClick(DialogInterface dialog, int whichButton) {
237                             /* Inform that the video is over.
238                              */
239                             onCompletion();
240                         }
241                     })
242                     .setCancelable(false)
243                     .show();
244             } catch (RuntimeException e) {
245                 Log.e(TAG, "Cannot show the alert dialog, error message: " + message, e);
246             }
247         }
248     }
249
250     @CalledByNative
251     private void onVideoSizeChanged(int width, int height) {
252         mVideoWidth = width;
253         mVideoHeight = height;
254         // This will trigger the SurfaceView.onMeasure() call.
255         mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
256     }
257
258     @CalledByNative
259     protected void onBufferingUpdate(int percent) {
260     }
261
262     @CalledByNative
263     private void onPlaybackComplete() {
264         onCompletion();
265     }
266
267     @CalledByNative
268     protected void onUpdateMediaMetadata(
269             int videoWidth,
270             int videoHeight,
271             int duration,
272             boolean canPause,
273             boolean canSeekBack,
274             boolean canSeekForward) {
275         mDuration = duration;
276         mProgressView.setVisibility(View.GONE);
277         mCurrentState = isPlaying() ? STATE_PLAYING : STATE_PAUSED;
278         onVideoSizeChanged(videoWidth, videoHeight);
279     }
280
281     @Override
282     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
283     }
284
285     @Override
286     public void surfaceCreated(SurfaceHolder holder) {
287         mSurfaceHolder = holder;
288         openVideo();
289     }
290
291     @Override
292     public void surfaceDestroyed(SurfaceHolder holder) {
293         if (mNativeContentVideoView != 0) {
294             nativeSetSurface(mNativeContentVideoView, null);
295         }
296         mSurfaceHolder = null;
297         post(mExitFullscreenRunnable);
298     }
299
300     @CalledByNative
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());
308             }
309         }
310     }
311
312     protected void onCompletion() {
313         mCurrentState = STATE_PLAYBACK_COMPLETED;
314     }
315
316
317     protected boolean isInPlaybackState() {
318         return (mCurrentState != STATE_ERROR && mCurrentState != STATE_IDLE);
319     }
320
321     protected void start() {
322         if (isInPlaybackState()) {
323             if (mNativeContentVideoView != 0) {
324                 nativePlay(mNativeContentVideoView);
325             }
326             mCurrentState = STATE_PLAYING;
327         }
328     }
329
330     protected void pause() {
331         if (isInPlaybackState()) {
332             if (isPlaying()) {
333                 if (mNativeContentVideoView != 0) {
334                     nativePause(mNativeContentVideoView);
335                 }
336                 mCurrentState = STATE_PAUSED;
337             }
338         }
339     }
340
341     // cache duration as mDuration for faster access
342     protected int getDuration() {
343         if (isInPlaybackState()) {
344             if (mDuration > 0) {
345                 return mDuration;
346             }
347             if (mNativeContentVideoView != 0) {
348                 mDuration = nativeGetDurationInMilliSeconds(mNativeContentVideoView);
349             } else {
350                 mDuration = 0;
351             }
352             return mDuration;
353         }
354         mDuration = -1;
355         return mDuration;
356     }
357
358     protected int getCurrentPosition() {
359         if (isInPlaybackState() && mNativeContentVideoView != 0) {
360             return nativeGetCurrentPosition(mNativeContentVideoView);
361         }
362         return 0;
363     }
364
365     protected void seekTo(int msec) {
366         if (mNativeContentVideoView != 0) {
367             nativeSeekTo(mNativeContentVideoView, msec);
368         }
369     }
370
371     public boolean isPlaying() {
372         return mNativeContentVideoView != 0 && nativeIsPlaying(mNativeContentVideoView);
373     }
374
375     @CalledByNative
376     private static ContentVideoView createContentVideoView(
377             Context context, long nativeContentVideoView, ContentVideoViewClient client,
378             boolean legacy) {
379         ThreadUtils.assertOnUiThread();
380         ContentVideoView videoView = null;
381         if (legacy) {
382             videoView = new ContentVideoViewLegacy(context, nativeContentVideoView, client);
383         } else {
384             videoView = new ContentVideoView(context, nativeContentVideoView, client);
385         }
386
387         if (videoView.getContentVideoViewClient().onShowCustomView(videoView)) {
388             return videoView;
389         }
390         return null;
391     }
392
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();
398         }
399         return context instanceof Activity;
400     }
401
402     public void removeSurfaceView() {
403         removeView(mVideoSurfaceView);
404         removeView(mProgressView);
405         mVideoSurfaceView = null;
406         mProgressView = null;
407     }
408
409     public void exitFullscreen(boolean relaseMediaPlayer) {
410         destroyContentVideoView(false);
411         if (mNativeContentVideoView != 0) {
412             nativeExitFullscreen(mNativeContentVideoView, relaseMediaPlayer);
413             mNativeContentVideoView = 0;
414         }
415     }
416
417     @CalledByNative
418     private void onExitFullscreen() {
419         exitFullscreen(false);
420     }
421
422     /**
423      * This method shall only be called by native and exitFullscreen,
424      * To exit fullscreen, use exitFullscreen in Java.
425      */
426     @CalledByNative
427     protected void destroyContentVideoView(boolean nativeViewDestroyed) {
428         if (mVideoSurfaceView != null) {
429             removeSurfaceView();
430             setVisibility(View.GONE);
431
432             // To prevent re-entrance, call this after removeSurfaceView.
433             mClient.onDestroyContentVideoView();
434         }
435         if (nativeViewDestroyed) {
436             mNativeContentVideoView = 0;
437         }
438     }
439
440     public static ContentVideoView getContentVideoView() {
441         return nativeGetSingletonJavaContentVideoView();
442     }
443
444     @Override
445     public boolean onKeyUp(int keyCode, KeyEvent event) {
446         if (keyCode == KeyEvent.KEYCODE_BACK) {
447             exitFullscreen(false);
448             return true;
449         }
450         return super.onKeyUp(keyCode, event);
451     }
452
453     @Override
454     public View acquireAnchorView() {
455         View anchorView = new View(getContext());
456         addView(anchorView);
457         return anchorView;
458     }
459
460     @Override
461     public void setAnchorViewPosition(View view, float x, float y, float width, float height) {
462         Log.e(TAG, "setAnchorViewPosition isn't implemented");
463     }
464
465     @Override
466     public void releaseAnchorView(View anchorView) {
467         removeView(anchorView);
468     }
469
470     @CalledByNative
471     private long getNativeViewAndroid() {
472         return mViewAndroid.getNativePointer();
473     }
474
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);
488 }