HTML5VideoView.java revision 156f97b6c8580b790600b09b679b9dab8b271c5d
1
2package android.webkit;
3
4import android.graphics.SurfaceTexture;
5import android.media.MediaPlayer;
6import android.net.Uri;
7import android.util.Log;
8import android.view.SurfaceView;
9import android.webkit.HTML5VideoViewProxy;
10import java.io.IOException;
11import java.util.HashMap;
12import java.util.Map;
13import java.util.Timer;
14import java.util.TimerTask;
15
16/**
17 * @hide This is only used by the browser
18 */
19public class HTML5VideoView implements MediaPlayer.OnPreparedListener {
20
21    protected static final String LOGTAG = "HTML5VideoView";
22
23    protected static final String COOKIE = "Cookie";
24    protected static final String HIDE_URL_LOGS = "x-hide-urls-from-log";
25
26    // For handling the seekTo before prepared, we need to know whether or not
27    // the video is prepared. Therefore, we differentiate the state between
28    // prepared and not prepared.
29    // When the video is not prepared, we will have to save the seekTo time,
30    // and use it when prepared to play.
31    // NOTE: these values are in sync with VideoLayerAndroid.h in webkit side.
32    // Please keep them in sync when changed.
33    static final int STATE_INITIALIZED        = 0;
34    static final int STATE_PREPARING          = 1;
35    static final int STATE_PREPARED           = 2;
36    static final int STATE_PLAYING            = 3;
37    static final int STATE_RESETTED           = 4;
38    static final int STATE_RELEASED           = 5;
39
40    protected HTML5VideoViewProxy mProxy;
41
42    // Save the seek time when not prepared. This can happen when switching
43    // video besides initial load.
44    protected int mSaveSeekTime;
45
46    // This is used to find the VideoLayer on the native side.
47    protected int mVideoLayerId;
48
49    // Given the fact we only have one SurfaceTexture, we cannot support multiple
50    // player at the same time. We may recreate a new one and abandon the old
51    // one at transition time.
52    protected static MediaPlayer mPlayer = null;
53    protected static int mCurrentState = -1;
54
55    // We need to save such info.
56    protected Uri mUri;
57    protected Map<String, String> mHeaders;
58
59    // The timer for timeupate events.
60    // See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate
61    protected static Timer mTimer;
62
63    protected boolean mPauseDuringPreparing;
64
65    // The spec says the timer should fire every 250 ms or less.
66    private static final int TIMEUPDATE_PERIOD = 250;  // ms
67    private boolean mSkipPrepare = false;
68
69    // common Video control FUNCTIONS:
70    public void start() {
71        if (mCurrentState == STATE_PREPARED) {
72            // When replaying the same video, there is no onPrepared call.
73            // Therefore, the timer should be set up here.
74            if (mTimer == null)
75            {
76                mTimer = new Timer();
77                mTimer.schedule(new TimeupdateTask(mProxy), TIMEUPDATE_PERIOD,
78                        TIMEUPDATE_PERIOD);
79            }
80            mPlayer.start();
81            setPlayerBuffering(false);
82        }
83    }
84
85    public void pause() {
86        if (isPlaying()) {
87            mPlayer.pause();
88        } else if (mCurrentState == STATE_PREPARING) {
89            mPauseDuringPreparing = true;
90        }
91        // Delete the Timer to stop it since there is no stop call.
92        if (mTimer != null) {
93            mTimer.purge();
94            mTimer.cancel();
95            mTimer = null;
96        }
97    }
98
99    public int getDuration() {
100        if (mCurrentState == STATE_PREPARED) {
101            return mPlayer.getDuration();
102        } else {
103            return -1;
104        }
105    }
106
107    public int getCurrentPosition() {
108        if (mCurrentState == STATE_PREPARED) {
109            return mPlayer.getCurrentPosition();
110        }
111        return 0;
112    }
113
114    public void seekTo(int pos) {
115        if (mCurrentState == STATE_PREPARED)
116            mPlayer.seekTo(pos);
117        else
118            mSaveSeekTime = pos;
119    }
120
121    public boolean isPlaying() {
122        if (mCurrentState == STATE_PREPARED) {
123            return mPlayer.isPlaying();
124        } else {
125            return false;
126        }
127    }
128
129    public void reset() {
130        if (mCurrentState < STATE_RESETTED) {
131            mPlayer.reset();
132        }
133        mCurrentState = STATE_RESETTED;
134    }
135
136    public void stopPlayback() {
137        if (mCurrentState == STATE_PREPARED) {
138            mPlayer.stop();
139        }
140    }
141
142    public static void release() {
143        if (mPlayer != null && mCurrentState != STATE_RELEASED) {
144            mPlayer.release();
145            mPlayer = null;
146        }
147        mCurrentState = STATE_RELEASED;
148    }
149
150    public boolean isReleased() {
151        return mCurrentState == STATE_RELEASED;
152    }
153
154    public boolean getPauseDuringPreparing() {
155        return mPauseDuringPreparing;
156    }
157
158    // Every time we start a new Video, we create a VideoView and a MediaPlayer
159    public void init(int videoLayerId, int position, boolean skipPrepare) {
160        if (mPlayer == null) {
161            mPlayer = new MediaPlayer();
162            mCurrentState = STATE_INITIALIZED;
163        }
164        mSkipPrepare = skipPrepare;
165        // If we want to skip the prepare, then we keep the state.
166        if (!mSkipPrepare) {
167            mCurrentState = STATE_INITIALIZED;
168        }
169        mProxy = null;
170        mVideoLayerId = videoLayerId;
171        mSaveSeekTime = position;
172        mTimer = null;
173        mPauseDuringPreparing = false;
174    }
175
176    protected HTML5VideoView() {
177    }
178
179    protected static Map<String, String> generateHeaders(String url,
180            HTML5VideoViewProxy proxy) {
181        boolean isPrivate = proxy.getWebView().isPrivateBrowsingEnabled();
182        String cookieValue = CookieManager.getInstance().getCookie(url, isPrivate);
183        Map<String, String> headers = new HashMap<String, String>();
184        if (cookieValue != null) {
185            headers.put(COOKIE, cookieValue);
186        }
187        if (isPrivate) {
188            headers.put(HIDE_URL_LOGS, "true");
189        }
190
191        return headers;
192    }
193
194    public void setVideoURI(String uri, HTML5VideoViewProxy proxy) {
195        // When switching players, surface texture will be reused.
196        mUri = Uri.parse(uri);
197        mHeaders = generateHeaders(uri, proxy);
198    }
199
200    // Listeners setup FUNCTIONS:
201    public void setOnCompletionListener(HTML5VideoViewProxy proxy) {
202        mPlayer.setOnCompletionListener(proxy);
203    }
204
205    public void setOnErrorListener(HTML5VideoViewProxy proxy) {
206        mPlayer.setOnErrorListener(proxy);
207    }
208
209    public void setOnPreparedListener(HTML5VideoViewProxy proxy) {
210        mProxy = proxy;
211        mPlayer.setOnPreparedListener(this);
212    }
213
214    public void setOnInfoListener(HTML5VideoViewProxy proxy) {
215        mPlayer.setOnInfoListener(proxy);
216    }
217
218    public void prepareDataCommon(HTML5VideoViewProxy proxy) {
219        if (!mSkipPrepare) {
220            try {
221                mPlayer.reset();
222                mPlayer.setDataSource(proxy.getContext(), mUri, mHeaders);
223                mPlayer.prepareAsync();
224            } catch (IllegalArgumentException e) {
225                e.printStackTrace();
226            } catch (IllegalStateException e) {
227                e.printStackTrace();
228            } catch (IOException e) {
229                e.printStackTrace();
230            }
231            mCurrentState = STATE_PREPARING;
232        } else {
233            // If we skip prepare and the onPrepared happened in inline mode, we
234            // don't need to call prepare again, we just need to call onPrepared
235            // to refresh the state here.
236            if (mCurrentState >= STATE_PREPARED) {
237                onPrepared(mPlayer);
238            }
239            mSkipPrepare = false;
240        }
241    }
242
243    public void reprepareData(HTML5VideoViewProxy proxy) {
244        mPlayer.reset();
245        prepareDataCommon(proxy);
246    }
247
248    // Normally called immediately after setVideoURI. But for full screen,
249    // this should be after surface holder created
250    public void prepareDataAndDisplayMode(HTML5VideoViewProxy proxy) {
251        // SurfaceTexture will be created lazily here for inline mode
252        decideDisplayMode();
253
254        setOnCompletionListener(proxy);
255        setOnPreparedListener(proxy);
256        setOnErrorListener(proxy);
257        setOnInfoListener(proxy);
258
259        prepareDataCommon(proxy);
260    }
261
262
263    // Common code
264    public int getVideoLayerId() {
265        return mVideoLayerId;
266    }
267
268
269    public int getCurrentState() {
270        if (isPlaying()) {
271            return STATE_PLAYING;
272        } else {
273            return mCurrentState;
274        }
275    }
276
277    private static final class TimeupdateTask extends TimerTask {
278        private HTML5VideoViewProxy mProxy;
279
280        public TimeupdateTask(HTML5VideoViewProxy proxy) {
281            mProxy = proxy;
282        }
283
284        @Override
285        public void run() {
286            mProxy.onTimeupdate();
287        }
288    }
289
290    @Override
291    public void onPrepared(MediaPlayer mp) {
292        mCurrentState = STATE_PREPARED;
293        seekTo(mSaveSeekTime);
294        if (mProxy != null) {
295            mProxy.onPrepared(mp);
296        }
297        if (mPauseDuringPreparing) {
298            pauseAndDispatch(mProxy);
299            mPauseDuringPreparing = false;
300        }
301    }
302
303    // Pause the play and update the play/pause button
304    public void pauseAndDispatch(HTML5VideoViewProxy proxy) {
305        pause();
306        if (proxy != null) {
307            proxy.dispatchOnPaused();
308        }
309    }
310
311    // Below are functions that are different implementation on inline and full-
312    // screen mode. Some are specific to one type, but currently are called
313    // directly from the proxy.
314    public void enterFullScreenVideoState(int layerId,
315            HTML5VideoViewProxy proxy, WebViewClassic webView) {
316    }
317
318    public boolean isFullScreenMode() {
319        return false;
320    }
321
322    public void decideDisplayMode() {
323    }
324
325    public boolean getReadyToUseSurfTex() {
326        return false;
327    }
328
329    public void deleteSurfaceTexture() {
330    }
331
332    public int getTextureName() {
333        return 0;
334    }
335
336    // This is true only when the player is buffering and paused
337    public boolean mPlayerBuffering = false;
338
339    public boolean getPlayerBuffering() {
340        return mPlayerBuffering;
341    }
342
343    public void setPlayerBuffering(boolean playerBuffering) {
344        mPlayerBuffering = playerBuffering;
345        switchProgressView(playerBuffering);
346    }
347
348
349    protected void switchProgressView(boolean playerBuffering) {
350        // Only used in HTML5VideoFullScreen
351    }
352
353    public boolean fullScreenExited() {
354        // Only meaningful for HTML5VideoFullScreen
355        return false;
356    }
357
358    private boolean mStartWhenPrepared = false;
359
360    public void setStartWhenPrepared(boolean willPlay) {
361        mStartWhenPrepared  = willPlay;
362    }
363
364    public boolean getStartWhenPrepared() {
365        return mStartWhenPrepared;
366    }
367
368    public void showControllerInFullScreen() {
369    }
370
371}
372