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