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