1
2package android.webkit;
3
4import android.content.Context;
5import android.media.MediaPlayer;
6import android.media.Metadata;
7import android.view.Gravity;
8import android.view.MotionEvent;
9import android.view.SurfaceHolder;
10import android.view.SurfaceView;
11import android.view.View;
12import android.view.ViewGroup;
13import android.widget.FrameLayout;
14import android.widget.MediaController;
15import android.widget.MediaController.MediaPlayerControl;
16
17
18/**
19 * @hide This is only used by the browser
20 */
21public class HTML5VideoFullScreen extends HTML5VideoView
22    implements MediaPlayerControl, MediaPlayer.OnPreparedListener,
23    View.OnTouchListener {
24
25    // Add this sub-class to handle the resizing when rotating screen.
26    private class VideoSurfaceView extends SurfaceView {
27
28        public VideoSurfaceView(Context context) {
29            super(context);
30        }
31
32        @Override
33        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
34            int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
35            int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
36            if (mVideoWidth > 0 && mVideoHeight > 0) {
37                if ( mVideoWidth * height  > width * mVideoHeight ) {
38                    height = width * mVideoHeight / mVideoWidth;
39                } else if ( mVideoWidth * height  < width * mVideoHeight ) {
40                    width = height * mVideoWidth / mVideoHeight;
41                }
42            }
43            setMeasuredDimension(width, height);
44        }
45    }
46
47    // This view will contain the video.
48    private VideoSurfaceView mVideoSurfaceView;
49
50    // We need the full screen state to decide which surface to render to and
51    // when to create the MediaPlayer accordingly.
52    static final int FULLSCREEN_OFF               = 0;
53    static final int FULLSCREEN_SURFACECREATING   = 1;
54    static final int FULLSCREEN_SURFACECREATED    = 2;
55
56    private int mFullScreenMode;
57    // The Media Controller only used for full screen mode
58    private MediaController mMediaController;
59
60    // SurfaceHolder for full screen
61    private SurfaceHolder mSurfaceHolder = null;
62
63    // Data only for MediaController
64    private boolean mCanSeekBack;
65    private boolean mCanSeekForward;
66    private boolean mCanPause;
67    private int mCurrentBufferPercentage;
68
69    // The progress view.
70    private static View mProgressView;
71    // The container for the progress view and video view
72    private static FrameLayout mLayout;
73
74    // The video size will be ready when prepared. Used to make sure the aspect
75    // ratio is correct.
76    private int mVideoWidth;
77    private int mVideoHeight;
78
79    SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
80    {
81        public void surfaceChanged(SurfaceHolder holder, int format,
82                                    int w, int h)
83        {
84            if (mPlayer != null && mMediaController != null
85                    && mCurrentState == STATE_PREPARED) {
86                if (mMediaController.isShowing()) {
87                    // ensure the controller will get repositioned later
88                    mMediaController.hide();
89                }
90                mMediaController.show();
91            }
92        }
93
94        public void surfaceCreated(SurfaceHolder holder)
95        {
96            mSurfaceHolder = holder;
97            mFullScreenMode = FULLSCREEN_SURFACECREATED;
98
99            prepareForFullScreen();
100        }
101
102        public void surfaceDestroyed(SurfaceHolder holder)
103        {
104            // After we return from this we can't use the surface any more.
105            // The current Video View will be destroy when we play a new video.
106            pauseAndDispatch(mProxy);
107            // TODO: handle full screen->inline mode transition without a reload.
108            mPlayer.release();
109            mPlayer = null;
110            mSurfaceHolder = null;
111            if (mMediaController != null) {
112                mMediaController.hide();
113            }
114        }
115    };
116
117    MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
118        new MediaPlayer.OnVideoSizeChangedListener() {
119            @Override
120            public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
121                mVideoWidth = mp.getVideoWidth();
122                mVideoHeight = mp.getVideoHeight();
123                if (mVideoWidth != 0 && mVideoHeight != 0) {
124                    mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
125                }
126            }
127    };
128
129    private SurfaceView getSurfaceView() {
130        return mVideoSurfaceView;
131    }
132
133    HTML5VideoFullScreen(Context context, int videoLayerId, int position, boolean skipPrepare) {
134        mVideoSurfaceView = new VideoSurfaceView(context);
135        mFullScreenMode = FULLSCREEN_OFF;
136        mVideoWidth = 0;
137        mVideoHeight = 0;
138        init(videoLayerId, position, skipPrepare);
139    }
140
141    private void setMediaController(MediaController m) {
142        mMediaController  = m;
143        attachMediaController();
144    }
145
146    private void attachMediaController() {
147        if (mPlayer != null && mMediaController != null) {
148            mMediaController.setMediaPlayer(this);
149            mMediaController.setAnchorView(mVideoSurfaceView);
150            //Will be enabled when prepared
151            mMediaController.setEnabled(false);
152        }
153    }
154
155    @Override
156    public void decideDisplayMode() {
157        mPlayer.setDisplay(mSurfaceHolder);
158    }
159
160    private void prepareForFullScreen() {
161        MediaController mc = new FullScreenMediaController(mProxy.getContext(), mLayout);
162        mc.setSystemUiVisibility(mLayout.getSystemUiVisibility());
163        setMediaController(mc);
164        mPlayer.setScreenOnWhilePlaying(true);
165        mPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
166        prepareDataAndDisplayMode(mProxy);
167    }
168
169
170    private void toggleMediaControlsVisiblity() {
171        if (mMediaController.isShowing()) {
172            mMediaController.hide();
173        } else {
174            mMediaController.show();
175        }
176    }
177
178    @Override
179    public void onPrepared(MediaPlayer mp) {
180        super.onPrepared(mp);
181
182        mVideoSurfaceView.setOnTouchListener(this);
183        // Get the capabilities of the player for this stream
184        Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,
185                MediaPlayer.BYPASS_METADATA_FILTER);
186        if (data != null) {
187            mCanPause = !data.has(Metadata.PAUSE_AVAILABLE)
188                    || data.getBoolean(Metadata.PAUSE_AVAILABLE);
189            mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
190                    || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);
191            mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)
192                    || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);
193        } else {
194            mCanPause = mCanSeekBack = mCanSeekForward = true;
195        }
196
197        if (mProgressView != null) {
198            mProgressView.setVisibility(View.GONE);
199        }
200
201        mVideoWidth = mp.getVideoWidth();
202        mVideoHeight = mp.getVideoHeight();
203        // This will trigger the onMeasure to get the display size right.
204        mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
205        // Call into the native to ask for the state, if still in play mode,
206        // this will trigger the video to play.
207        mProxy.dispatchOnRestoreState();
208
209        if (getStartWhenPrepared()) {
210            mPlayer.start();
211            // Clear the flag.
212            setStartWhenPrepared(false);
213        }
214
215        // mMediaController status depends on the Metadata result, so put it
216        // after reading the MetaData.
217        // And make sure mPlayer state is updated before showing the controller.
218        if (mMediaController != null) {
219            mMediaController.setEnabled(true);
220            mMediaController.show();
221        }
222    }
223
224    public boolean fullScreenExited() {
225        return (mLayout == null);
226    }
227
228    private final WebChromeClient.CustomViewCallback mCallback =
229        new WebChromeClient.CustomViewCallback() {
230            public void onCustomViewHidden() {
231                // It listens to SurfaceHolder.Callback.SurfaceDestroyed event
232                // which happens when the video view is detached from its parent
233                // view. This happens in the WebChromeClient before this method
234                // is invoked.
235                mProxy.dispatchOnStopFullScreen();
236                mLayout.removeView(getSurfaceView());
237
238                if (mProgressView != null) {
239                    mLayout.removeView(mProgressView);
240                    mProgressView = null;
241                }
242                mLayout = null;
243                // Re enable plugin views.
244                mProxy.getWebView().getViewManager().showAll();
245
246                mProxy = null;
247
248                // Don't show the controller after exiting the full screen.
249                mMediaController = null;
250                mCurrentState = STATE_RESETTED;
251            }
252        };
253
254    @Override
255    public void enterFullScreenVideoState(int layerId,
256            HTML5VideoViewProxy proxy, WebViewClassic webView) {
257        mFullScreenMode = FULLSCREEN_SURFACECREATING;
258        mCurrentBufferPercentage = 0;
259        mPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
260        mProxy = proxy;
261
262        mVideoSurfaceView.getHolder().addCallback(mSHCallback);
263        mVideoSurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
264        mVideoSurfaceView.setFocusable(true);
265        mVideoSurfaceView.setFocusableInTouchMode(true);
266        mVideoSurfaceView.requestFocus();
267
268        // Create a FrameLayout that will contain the VideoView and the
269        // progress view (if any).
270        mLayout = new FrameLayout(mProxy.getContext());
271        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
272                            ViewGroup.LayoutParams.WRAP_CONTENT,
273                            ViewGroup.LayoutParams.WRAP_CONTENT,
274                            Gravity.CENTER);
275
276        mLayout.addView(getSurfaceView(), layoutParams);
277
278        mLayout.setVisibility(View.VISIBLE);
279        WebChromeClient client = webView.getWebChromeClient();
280        if (client != null) {
281            client.onShowCustomView(mLayout, mCallback);
282            // Plugins like Flash will draw over the video so hide
283            // them while we're playing.
284            if (webView.getViewManager() != null)
285                webView.getViewManager().hideAll();
286
287            mProgressView = client.getVideoLoadingProgressView();
288            if (mProgressView != null) {
289                mLayout.addView(mProgressView, layoutParams);
290                mProgressView.setVisibility(View.VISIBLE);
291            }
292        }
293    }
294
295    /**
296     * @return true when we are in full screen mode, even the surface not fully
297     * created.
298     */
299    public boolean isFullScreenMode() {
300        return true;
301    }
302
303    // MediaController FUNCTIONS:
304    @Override
305    public boolean canPause() {
306        return mCanPause;
307    }
308
309    @Override
310    public boolean canSeekBackward() {
311        return mCanSeekBack;
312    }
313
314    @Override
315    public boolean canSeekForward() {
316        return mCanSeekForward;
317    }
318
319    @Override
320    public int getBufferPercentage() {
321        if (mPlayer != null) {
322            return mCurrentBufferPercentage;
323        }
324    return 0;
325    }
326
327    @Override
328    public void showControllerInFullScreen() {
329        if (mMediaController != null) {
330            mMediaController.show(0);
331        }
332    }
333
334    // Other listeners functions:
335    private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
336        new MediaPlayer.OnBufferingUpdateListener() {
337        public void onBufferingUpdate(MediaPlayer mp, int percent) {
338            mCurrentBufferPercentage = percent;
339        }
340    };
341
342    @Override
343    public boolean onTouch(View v, MotionEvent event) {
344        if (mFullScreenMode >= FULLSCREEN_SURFACECREATED
345                && mMediaController != null) {
346            toggleMediaControlsVisiblity();
347        }
348        return false;
349    }
350
351    @Override
352    protected void switchProgressView(boolean playerBuffering) {
353        if (mProgressView != null) {
354            if (playerBuffering) {
355                mProgressView.setVisibility(View.VISIBLE);
356            } else {
357                mProgressView.setVisibility(View.GONE);
358            }
359        }
360        return;
361    }
362
363    static class FullScreenMediaController extends MediaController {
364
365        View mVideoView;
366
367        public FullScreenMediaController(Context context, View video) {
368            super(context);
369            mVideoView = video;
370        }
371
372        @Override
373        public void show() {
374            super.show();
375            if (mVideoView != null) {
376                mVideoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
377            }
378        }
379
380        @Override
381        public void hide() {
382            if (mVideoView != null) {
383                mVideoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
384                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
385            }
386            super.hide();
387        }
388
389    }
390
391}
392