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