• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
VideoSurfaceView(Context context)98         public VideoSurfaceView(Context context) {
99             super(context);
100         }
101 
102         @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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 
ProgressView(Context context, String videoLoadingText)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 
ContentVideoView(Context context, long nativeContentVideoView, ContentVideoViewClient client)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 
getContentVideoViewClient()159     protected ContentVideoViewClient getContentVideoViewClient() {
160         return mClient;
161     }
162 
initResources(Context context)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 
showContentVideoView()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 
getSurfaceView()194     protected SurfaceView getSurfaceView() {
195         return mVideoSurfaceView;
196     }
197 
198     @CalledByNative
onMediaPlayerError(int errorType)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
onVideoSizeChanged(int width, int height)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
onBufferingUpdate(int percent)259     protected void onBufferingUpdate(int percent) {
260     }
261 
262     @CalledByNative
onPlaybackComplete()263     private void onPlaybackComplete() {
264         onCompletion();
265     }
266 
267     @CalledByNative
onUpdateMediaMetadata( int videoWidth, int videoHeight, int duration, boolean canPause, boolean canSeekBack, boolean canSeekForward)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
surfaceChanged(SurfaceHolder holder, int format, int width, int height)282     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
283     }
284 
285     @Override
surfaceCreated(SurfaceHolder holder)286     public void surfaceCreated(SurfaceHolder holder) {
287         mSurfaceHolder = holder;
288         openVideo();
289     }
290 
291     @Override
surfaceDestroyed(SurfaceHolder holder)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
openVideo()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 
onCompletion()312     protected void onCompletion() {
313         mCurrentState = STATE_PLAYBACK_COMPLETED;
314     }
315 
316 
isInPlaybackState()317     protected boolean isInPlaybackState() {
318         return (mCurrentState != STATE_ERROR && mCurrentState != STATE_IDLE);
319     }
320 
start()321     protected void start() {
322         if (isInPlaybackState()) {
323             if (mNativeContentVideoView != 0) {
324                 nativePlay(mNativeContentVideoView);
325             }
326             mCurrentState = STATE_PLAYING;
327         }
328     }
329 
pause()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
getDuration()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 
getCurrentPosition()358     protected int getCurrentPosition() {
359         if (isInPlaybackState() && mNativeContentVideoView != 0) {
360             return nativeGetCurrentPosition(mNativeContentVideoView);
361         }
362         return 0;
363     }
364 
seekTo(int msec)365     protected void seekTo(int msec) {
366         if (mNativeContentVideoView != 0) {
367             nativeSeekTo(mNativeContentVideoView, msec);
368         }
369     }
370 
isPlaying()371     public boolean isPlaying() {
372         return mNativeContentVideoView != 0 && nativeIsPlaying(mNativeContentVideoView);
373     }
374 
375     @CalledByNative
createContentVideoView( Context context, long nativeContentVideoView, ContentVideoViewClient client, boolean legacy)376     private static ContentVideoView createContentVideoView(
377             Context context, long nativeContentVideoView, ContentVideoViewClient client,
378             boolean legacy) {
379         ThreadUtils.assertOnUiThread();
380         // The context needs be Activity to create the ContentVideoView correctly.
381         if (!isActivityContext(context)) {
382             Log.e(TAG, "Wrong type of context, can't create fullscreen video");
383             return null;
384         }
385         ContentVideoView videoView = null;
386         if (legacy) {
387             videoView = new ContentVideoViewLegacy(context, nativeContentVideoView, client);
388         } else {
389             videoView = new ContentVideoView(context, nativeContentVideoView, client);
390         }
391 
392         if (videoView.getContentVideoViewClient().onShowCustomView(videoView)) {
393             return videoView;
394         }
395         return null;
396     }
397 
isActivityContext(Context context)398     private static boolean isActivityContext(Context context) {
399         // Only retrieve the base context if the supplied context is a ContextWrapper but not
400         // an Activity, given that Activity is already a subclass of ContextWrapper.
401         if (context instanceof ContextWrapper && !(context instanceof Activity)) {
402             context = ((ContextWrapper) context).getBaseContext();
403         }
404         return context instanceof Activity;
405     }
406 
removeSurfaceView()407     public void removeSurfaceView() {
408         removeView(mVideoSurfaceView);
409         removeView(mProgressView);
410         mVideoSurfaceView = null;
411         mProgressView = null;
412     }
413 
exitFullscreen(boolean relaseMediaPlayer)414     public void exitFullscreen(boolean relaseMediaPlayer) {
415         destroyContentVideoView(false);
416         if (mNativeContentVideoView != 0) {
417             nativeExitFullscreen(mNativeContentVideoView, relaseMediaPlayer);
418             mNativeContentVideoView = 0;
419         }
420     }
421 
422     @CalledByNative
onExitFullscreen()423     private void onExitFullscreen() {
424         exitFullscreen(false);
425     }
426 
427     /**
428      * This method shall only be called by native and exitFullscreen,
429      * To exit fullscreen, use exitFullscreen in Java.
430      */
431     @CalledByNative
destroyContentVideoView(boolean nativeViewDestroyed)432     protected void destroyContentVideoView(boolean nativeViewDestroyed) {
433         if (mVideoSurfaceView != null) {
434             removeSurfaceView();
435             setVisibility(View.GONE);
436 
437             // To prevent re-entrance, call this after removeSurfaceView.
438             mClient.onDestroyContentVideoView();
439         }
440         if (nativeViewDestroyed) {
441             mNativeContentVideoView = 0;
442         }
443     }
444 
getContentVideoView()445     public static ContentVideoView getContentVideoView() {
446         return nativeGetSingletonJavaContentVideoView();
447     }
448 
449     @Override
onKeyUp(int keyCode, KeyEvent event)450     public boolean onKeyUp(int keyCode, KeyEvent event) {
451         if (keyCode == KeyEvent.KEYCODE_BACK) {
452             exitFullscreen(false);
453             return true;
454         }
455         return super.onKeyUp(keyCode, event);
456     }
457 
458     @Override
acquireAnchorView()459     public View acquireAnchorView() {
460         View anchorView = new View(getContext());
461         addView(anchorView);
462         return anchorView;
463     }
464 
465     @Override
setAnchorViewPosition(View view, float x, float y, float width, float height)466     public void setAnchorViewPosition(View view, float x, float y, float width, float height) {
467         Log.e(TAG, "setAnchorViewPosition isn't implemented");
468     }
469 
470     @Override
releaseAnchorView(View anchorView)471     public void releaseAnchorView(View anchorView) {
472         removeView(anchorView);
473     }
474 
475     @CalledByNative
getNativeViewAndroid()476     private long getNativeViewAndroid() {
477         return mViewAndroid.getNativePointer();
478     }
479 
nativeGetSingletonJavaContentVideoView()480     private static native ContentVideoView nativeGetSingletonJavaContentVideoView();
nativeExitFullscreen(long nativeContentVideoView, boolean relaseMediaPlayer)481     private native void nativeExitFullscreen(long nativeContentVideoView,
482             boolean relaseMediaPlayer);
nativeGetCurrentPosition(long nativeContentVideoView)483     private native int nativeGetCurrentPosition(long nativeContentVideoView);
nativeGetDurationInMilliSeconds(long nativeContentVideoView)484     private native int nativeGetDurationInMilliSeconds(long nativeContentVideoView);
nativeRequestMediaMetadata(long nativeContentVideoView)485     private native void nativeRequestMediaMetadata(long nativeContentVideoView);
nativeGetVideoWidth(long nativeContentVideoView)486     private native int nativeGetVideoWidth(long nativeContentVideoView);
nativeGetVideoHeight(long nativeContentVideoView)487     private native int nativeGetVideoHeight(long nativeContentVideoView);
nativeIsPlaying(long nativeContentVideoView)488     private native boolean nativeIsPlaying(long nativeContentVideoView);
nativePause(long nativeContentVideoView)489     private native void nativePause(long nativeContentVideoView);
nativePlay(long nativeContentVideoView)490     private native void nativePlay(long nativeContentVideoView);
nativeSeekTo(long nativeContentVideoView, int msec)491     private native void nativeSeekTo(long nativeContentVideoView, int msec);
nativeSetSurface(long nativeContentVideoView, Surface surface)492     private native void nativeSetSurface(long nativeContentVideoView, Surface surface);
493 }
494