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