HTML5VideoViewProxy.java revision c3a2858de909145a382e7932d5fb044e1388c0b3
1/*
2 * Copyright (C) 2009 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.graphics.Bitmap;
21import android.graphics.BitmapFactory;
22import android.graphics.SurfaceTexture;
23import android.media.MediaPlayer;
24import android.net.http.EventHandler;
25import android.net.http.Headers;
26import android.net.http.RequestHandle;
27import android.net.http.RequestQueue;
28import android.net.http.SslCertificate;
29import android.net.http.SslError;
30import android.os.Handler;
31import android.os.Looper;
32import android.os.Message;
33import android.util.Log;
34
35import java.io.ByteArrayOutputStream;
36import java.io.IOException;
37import java.net.MalformedURLException;
38import java.net.URL;
39import java.util.HashMap;
40import java.util.Map;
41
42/**
43 * <p>Proxy for HTML5 video views.
44 */
45class HTML5VideoViewProxy extends Handler
46                          implements MediaPlayer.OnPreparedListener,
47                          MediaPlayer.OnCompletionListener,
48                          MediaPlayer.OnErrorListener,
49                          MediaPlayer.OnInfoListener,
50                          SurfaceTexture.OnFrameAvailableListener {
51    // Logging tag.
52    private static final String LOGTAG = "HTML5VideoViewProxy";
53
54    // Message Ids for WebCore thread -> UI thread communication.
55    private static final int PLAY                = 100;
56    private static final int SEEK                = 101;
57    private static final int PAUSE               = 102;
58    private static final int ERROR               = 103;
59    private static final int LOAD_DEFAULT_POSTER = 104;
60    private static final int BUFFERING_START     = 105;
61    private static final int BUFFERING_END       = 106;
62    private static final int ENTER_FULLSCREEN    = 107;
63
64    // Message Ids to be handled on the WebCore thread
65    private static final int PREPARED          = 200;
66    private static final int ENDED             = 201;
67    private static final int POSTER_FETCHED    = 202;
68    private static final int PAUSED            = 203;
69    private static final int STOPFULLSCREEN    = 204;
70    private static final int RESTORESTATE      = 205;
71
72    // Timer thread -> UI thread
73    private static final int TIMEUPDATE = 300;
74
75    // The C++ MediaPlayerPrivateAndroid object.
76    int mNativePointer;
77    // The handler for WebCore thread messages;
78    private Handler mWebCoreHandler;
79    // The WebViewClassic instance that created this view.
80    private WebViewClassic mWebView;
81    // The poster image to be shown when the video is not playing.
82    // This ref prevents the bitmap from being GC'ed.
83    private Bitmap mPoster;
84    // The poster downloader.
85    private PosterDownloader mPosterDownloader;
86    // The seek position.
87    private int mSeekPosition;
88    // A helper class to control the playback. This executes on the UI thread!
89    private static final class VideoPlayer {
90        // The proxy that is currently playing (if any).
91        private static HTML5VideoViewProxy mCurrentProxy;
92        // The VideoView instance. This is a singleton for now, at least until
93        // http://b/issue?id=1973663 is fixed.
94        private static HTML5VideoView mHTML5VideoView;
95
96        private static boolean isVideoSelfEnded = false;
97        // By using the baseLayer and the current video Layer ID, we can
98        // identify the exact layer on the UI thread to use the SurfaceTexture.
99        private static int mBaseLayer = 0;
100
101        private static void setPlayerBuffering(boolean playerBuffering) {
102            mHTML5VideoView.setPlayerBuffering(playerBuffering);
103        }
104
105        // Every time webView setBaseLayer, this will be called.
106        // When we found the Video layer, then we set the Surface Texture to it.
107        // Otherwise, we may want to delete the Surface Texture to save memory.
108        public static void setBaseLayer(int layer) {
109            mBaseLayer = layer;
110            // Don't do this for full screen mode.
111            if (mHTML5VideoView != null
112                && !mHTML5VideoView.isFullScreenMode()) {
113                int currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
114                SurfaceTexture surfTexture =
115                        HTML5VideoInline.getSurfaceTexture(currentVideoLayerId);
116                int textureName = mHTML5VideoView.getTextureName();
117
118                if (layer != 0 && surfTexture != null && currentVideoLayerId != -1) {
119                    int playerState = mHTML5VideoView.getCurrentState();
120                    if (mHTML5VideoView.getPlayerBuffering())
121                        playerState = HTML5VideoView.STATE_PREPARING;
122                    boolean foundInTree = nativeSendSurfaceTexture(surfTexture,
123                            layer, currentVideoLayerId, textureName,
124                            playerState);
125                    if (playerState >= HTML5VideoView.STATE_PREPARED
126                            && !foundInTree) {
127                        mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
128                    }
129                }
130            }
131        }
132
133        // When a WebView is paused, we also want to pause the video in it.
134        public static void pauseAndDispatch() {
135            if (mHTML5VideoView != null) {
136                mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
137            }
138        }
139
140        public static void enterFullScreenVideo(int layerId, String url,
141                HTML5VideoViewProxy proxy, WebViewClassic webView) {
142                // Save the inline video info and inherit it in the full screen
143                int savePosition = 0;
144                boolean canSkipPrepare = false;
145                boolean forceStart = false;
146                if (mHTML5VideoView != null) {
147                    // We don't allow enter full screen mode while the previous
148                    // full screen video hasn't finished yet.
149                    if (!mHTML5VideoView.fullScreenExited() && mHTML5VideoView.isFullScreenMode()) {
150                        Log.w(LOGTAG, "Try to reenter the full screen mode");
151                        return;
152                    }
153                    int playerState = mHTML5VideoView.getCurrentState();
154                    // If we are playing the same video, then it is better to
155                    // save the current position.
156                    if (layerId == mHTML5VideoView.getVideoLayerId()) {
157                        savePosition = mHTML5VideoView.getCurrentPosition();
158                        canSkipPrepare = (playerState == HTML5VideoView.STATE_PREPARING
159                                || playerState == HTML5VideoView.STATE_PREPARED
160                                || playerState == HTML5VideoView.STATE_PLAYING)
161                                && !mHTML5VideoView.isFullScreenMode();
162                    }
163                    if (!canSkipPrepare) {
164                        mHTML5VideoView.reset();
165                    } else {
166                        forceStart = playerState == HTML5VideoView.STATE_PREPARING
167                                || playerState == HTML5VideoView.STATE_PLAYING;
168                    }
169                }
170                mHTML5VideoView = new HTML5VideoFullScreen(proxy.getContext(),
171                        layerId, savePosition, canSkipPrepare);
172                mHTML5VideoView.setStartWhenPrepared(forceStart);
173                mCurrentProxy = proxy;
174                mHTML5VideoView.setVideoURI(url, mCurrentProxy);
175                mHTML5VideoView.enterFullScreenVideoState(layerId, proxy, webView);
176        }
177
178        public static void exitFullScreenVideo(HTML5VideoViewProxy proxy,
179                WebViewClassic webView) {
180            if (!mHTML5VideoView.fullScreenExited() && mHTML5VideoView.isFullScreenMode()) {
181                WebChromeClient client = webView.getWebChromeClient();
182                if (client != null) {
183                    client.onHideCustomView();
184                }
185            }
186        }
187
188        // This is on the UI thread.
189        // When native tell Java to play, we need to check whether or not it is
190        // still the same video by using videoLayerId and treat it differently.
191        public static void play(String url, int time, HTML5VideoViewProxy proxy,
192                WebChromeClient client, int videoLayerId) {
193            int currentVideoLayerId = -1;
194            boolean backFromFullScreenMode = false;
195            if (mHTML5VideoView != null) {
196                currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
197                backFromFullScreenMode = mHTML5VideoView.fullScreenExited();
198
199                // When playing video back to back in full screen mode,
200                // javascript will switch the src and call play.
201                // In this case, we can just reuse the same full screen view,
202                // and play the video after prepared.
203                if (mHTML5VideoView.isFullScreenMode()
204                    && !backFromFullScreenMode
205                    && currentVideoLayerId != videoLayerId
206                    && mCurrentProxy != proxy) {
207                    mCurrentProxy = proxy;
208                    mHTML5VideoView.setStartWhenPrepared(true);
209                    mHTML5VideoView.setVideoURI(url, proxy);
210                    mHTML5VideoView.reprepareData(proxy);
211                    return;
212                }
213            }
214
215            boolean skipPrepare = false;
216            boolean createInlineView = false;
217            if (backFromFullScreenMode && currentVideoLayerId == videoLayerId) {
218                skipPrepare = true;
219                createInlineView = true;
220            } else if(backFromFullScreenMode
221                || currentVideoLayerId != videoLayerId
222                || HTML5VideoInline.surfaceTextureDeleted()) {
223                // Here, we handle the case when switching to a new video,
224                // either inside a WebView or across WebViews
225                // For switching videos within a WebView or across the WebView,
226                // we need to pause the old one and re-create a new media player
227                // inside the HTML5VideoView.
228                if (mHTML5VideoView != null) {
229                    if (!backFromFullScreenMode) {
230                        mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
231                    }
232                    mHTML5VideoView.reset();
233                }
234                createInlineView = true;
235            }
236            if (createInlineView) {
237                mCurrentProxy = proxy;
238                mHTML5VideoView = new HTML5VideoInline(videoLayerId, time, skipPrepare);
239
240                mHTML5VideoView.setVideoURI(url, mCurrentProxy);
241                mHTML5VideoView.prepareDataAndDisplayMode(proxy);
242                return;
243            }
244
245            if (mCurrentProxy == proxy) {
246                // Here, we handle the case when we keep playing with one video
247                if (!mHTML5VideoView.isPlaying()) {
248                    mHTML5VideoView.seekTo(time);
249                    mHTML5VideoView.start();
250                }
251            } else if (mCurrentProxy != null) {
252                // Some other video is already playing. Notify the caller that
253                // its playback ended.
254                proxy.dispatchOnEnded();
255            }
256        }
257
258        public static boolean isPlaying(HTML5VideoViewProxy proxy) {
259            return (mCurrentProxy == proxy && mHTML5VideoView != null
260                    && mHTML5VideoView.isPlaying());
261        }
262
263        public static int getCurrentPosition() {
264            int currentPosMs = 0;
265            if (mHTML5VideoView != null) {
266                currentPosMs = mHTML5VideoView.getCurrentPosition();
267            }
268            return currentPosMs;
269        }
270
271        public static void seek(int time, HTML5VideoViewProxy proxy) {
272            if (mCurrentProxy == proxy && time >= 0 && mHTML5VideoView != null) {
273                mHTML5VideoView.seekTo(time);
274            }
275        }
276
277        public static void pause(HTML5VideoViewProxy proxy) {
278            if (mCurrentProxy == proxy && mHTML5VideoView != null) {
279                mHTML5VideoView.pause();
280            }
281        }
282
283        public static void onPrepared() {
284            if (!mHTML5VideoView.isFullScreenMode()) {
285                mHTML5VideoView.start();
286            }
287        }
288
289        public static void end() {
290            mHTML5VideoView.showControllerInFullScreen();
291            if (mCurrentProxy != null) {
292                if (isVideoSelfEnded)
293                    mCurrentProxy.dispatchOnEnded();
294                else
295                    mCurrentProxy.dispatchOnPaused();
296            }
297            isVideoSelfEnded = false;
298        }
299    }
300
301    // A bunch event listeners for our VideoView
302    // MediaPlayer.OnPreparedListener
303    public void onPrepared(MediaPlayer mp) {
304        VideoPlayer.onPrepared();
305        Message msg = Message.obtain(mWebCoreHandler, PREPARED);
306        Map<String, Object> map = new HashMap<String, Object>();
307        map.put("dur", new Integer(mp.getDuration()));
308        map.put("width", new Integer(mp.getVideoWidth()));
309        map.put("height", new Integer(mp.getVideoHeight()));
310        msg.obj = map;
311        mWebCoreHandler.sendMessage(msg);
312    }
313
314    // MediaPlayer.OnCompletionListener;
315    public void onCompletion(MediaPlayer mp) {
316        // The video ended by itself, so we need to
317        // send a message to the UI thread to dismiss
318        // the video view and to return to the WebView.
319        // arg1 == 1 means the video ends by itself.
320        sendMessage(obtainMessage(ENDED, 1, 0));
321    }
322
323    // MediaPlayer.OnErrorListener
324    public boolean onError(MediaPlayer mp, int what, int extra) {
325        sendMessage(obtainMessage(ERROR));
326        return false;
327    }
328
329    public void dispatchOnEnded() {
330        Message msg = Message.obtain(mWebCoreHandler, ENDED);
331        mWebCoreHandler.sendMessage(msg);
332    }
333
334    public void dispatchOnPaused() {
335        Message msg = Message.obtain(mWebCoreHandler, PAUSED);
336        mWebCoreHandler.sendMessage(msg);
337    }
338
339    public void dispatchOnStopFullScreen(boolean stillPlaying) {
340        Message msg = Message.obtain(mWebCoreHandler, STOPFULLSCREEN);
341        msg.arg1 = stillPlaying ? 1 : 0;
342        mWebCoreHandler.sendMessage(msg);
343    }
344
345    public void dispatchOnRestoreState() {
346        Message msg = Message.obtain(mWebCoreHandler, RESTORESTATE);
347        mWebCoreHandler.sendMessage(msg);
348    }
349
350    public void onTimeupdate() {
351        sendMessage(obtainMessage(TIMEUPDATE));
352    }
353
354    // When there is a frame ready from surface texture, we should tell WebView
355    // to refresh.
356    @Override
357    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
358        // TODO: This should support partial invalidation too.
359        mWebView.invalidate();
360    }
361
362    // Handler for the messages from WebCore or Timer thread to the UI thread.
363    @Override
364    public void handleMessage(Message msg) {
365        // This executes on the UI thread.
366        switch (msg.what) {
367            case PLAY: {
368                String url = (String) msg.obj;
369                WebChromeClient client = mWebView.getWebChromeClient();
370                int videoLayerID = msg.arg1;
371                if (client != null) {
372                    VideoPlayer.play(url, mSeekPosition, this, client, videoLayerID);
373                }
374                break;
375            }
376            case ENTER_FULLSCREEN:{
377                String url = (String) msg.obj;
378                WebChromeClient client = mWebView.getWebChromeClient();
379                int videoLayerID = msg.arg1;
380                if (client != null) {
381                    VideoPlayer.enterFullScreenVideo(videoLayerID, url, this, mWebView);
382                }
383                break;
384            }
385            case SEEK: {
386                Integer time = (Integer) msg.obj;
387                mSeekPosition = time;
388                VideoPlayer.seek(mSeekPosition, this);
389                break;
390            }
391            case PAUSE: {
392                VideoPlayer.pause(this);
393                break;
394            }
395            case ENDED:
396                if (msg.arg1 == 1)
397                    VideoPlayer.isVideoSelfEnded = true;
398                VideoPlayer.end();
399                break;
400            case ERROR: {
401                WebChromeClient client = mWebView.getWebChromeClient();
402                if (client != null) {
403                    client.onHideCustomView();
404                }
405                break;
406            }
407            case LOAD_DEFAULT_POSTER: {
408                WebChromeClient client = mWebView.getWebChromeClient();
409                if (client != null) {
410                    doSetPoster(client.getDefaultVideoPoster());
411                }
412                break;
413            }
414            case TIMEUPDATE: {
415                if (VideoPlayer.isPlaying(this)) {
416                    sendTimeupdate();
417                }
418                break;
419            }
420            case BUFFERING_START: {
421                VideoPlayer.setPlayerBuffering(true);
422                break;
423            }
424            case BUFFERING_END: {
425                VideoPlayer.setPlayerBuffering(false);
426                break;
427            }
428        }
429    }
430
431    // Everything below this comment executes on the WebCore thread, except for
432    // the EventHandler methods, which are called on the network thread.
433
434    // A helper class that knows how to download posters
435    private static final class PosterDownloader implements EventHandler {
436        // The request queue. This is static as we have one queue for all posters.
437        private static RequestQueue mRequestQueue;
438        private static int mQueueRefCount = 0;
439        // The poster URL
440        private URL mUrl;
441        // The proxy we're doing this for.
442        private final HTML5VideoViewProxy mProxy;
443        // The poster bytes. We only touch this on the network thread.
444        private ByteArrayOutputStream mPosterBytes;
445        // The request handle. We only touch this on the WebCore thread.
446        private RequestHandle mRequestHandle;
447        // The response status code.
448        private int mStatusCode;
449        // The response headers.
450        private Headers mHeaders;
451        // The handler to handle messages on the WebCore thread.
452        private Handler mHandler;
453
454        public PosterDownloader(String url, HTML5VideoViewProxy proxy) {
455            try {
456                mUrl = new URL(url);
457            } catch (MalformedURLException e) {
458                mUrl = null;
459            }
460            mProxy = proxy;
461            mHandler = new Handler();
462        }
463        // Start the download. Called on WebCore thread.
464        public void start() {
465            retainQueue();
466
467            if (mUrl == null) {
468                return;
469            }
470
471            // Only support downloading posters over http/https.
472            // FIXME: Add support for other schemes. WebKit seems able to load
473            // posters over other schemes e.g. file://, but gets the dimensions wrong.
474            String protocol = mUrl.getProtocol();
475            if ("http".equals(protocol) || "https".equals(protocol)) {
476                mRequestHandle = mRequestQueue.queueRequest(mUrl.toString(), "GET", null,
477                        this, null, 0);
478            }
479        }
480        // Cancel the download if active and release the queue. Called on WebCore thread.
481        public void cancelAndReleaseQueue() {
482            if (mRequestHandle != null) {
483                mRequestHandle.cancel();
484                mRequestHandle = null;
485            }
486            releaseQueue();
487        }
488        // EventHandler methods. Executed on the network thread.
489        public void status(int major_version,
490                int minor_version,
491                int code,
492                String reason_phrase) {
493            mStatusCode = code;
494        }
495
496        public void headers(Headers headers) {
497            mHeaders = headers;
498        }
499
500        public void data(byte[] data, int len) {
501            if (mPosterBytes == null) {
502                mPosterBytes = new ByteArrayOutputStream();
503            }
504            mPosterBytes.write(data, 0, len);
505        }
506
507        public void endData() {
508            if (mStatusCode == 200) {
509                if (mPosterBytes.size() > 0) {
510                    Bitmap poster = BitmapFactory.decodeByteArray(
511                            mPosterBytes.toByteArray(), 0, mPosterBytes.size());
512                    mProxy.doSetPoster(poster);
513                }
514                cleanup();
515            } else if (mStatusCode >= 300 && mStatusCode < 400) {
516                // We have a redirect.
517                try {
518                    mUrl = new URL(mHeaders.getLocation());
519                } catch (MalformedURLException e) {
520                    mUrl = null;
521                }
522                if (mUrl != null) {
523                    mHandler.post(new Runnable() {
524                       public void run() {
525                           if (mRequestHandle != null) {
526                               mRequestHandle.setupRedirect(mUrl.toString(), mStatusCode,
527                                       new HashMap<String, String>());
528                           }
529                       }
530                    });
531                }
532            }
533        }
534
535        public void certificate(SslCertificate certificate) {
536            // Don't care.
537        }
538
539        public void error(int id, String description) {
540            cleanup();
541        }
542
543        public boolean handleSslErrorRequest(SslError error) {
544            // Don't care. If this happens, data() will never be called so
545            // mPosterBytes will never be created, so no need to call cleanup.
546            return false;
547        }
548        // Tears down the poster bytes stream. Called on network thread.
549        private void cleanup() {
550            if (mPosterBytes != null) {
551                try {
552                    mPosterBytes.close();
553                } catch (IOException ignored) {
554                    // Ignored.
555                } finally {
556                    mPosterBytes = null;
557                }
558            }
559        }
560
561        // Queue management methods. Called on WebCore thread.
562        private void retainQueue() {
563            if (mRequestQueue == null) {
564                mRequestQueue = new RequestQueue(mProxy.getContext());
565            }
566            mQueueRefCount++;
567        }
568
569        private void releaseQueue() {
570            if (mQueueRefCount == 0) {
571                return;
572            }
573            if (--mQueueRefCount == 0) {
574                mRequestQueue.shutdown();
575                mRequestQueue = null;
576            }
577        }
578    }
579
580    /**
581     * Private constructor.
582     * @param webView is the WebView that hosts the video.
583     * @param nativePtr is the C++ pointer to the MediaPlayerPrivate object.
584     */
585    private HTML5VideoViewProxy(WebViewClassic webView, int nativePtr) {
586        // This handler is for the main (UI) thread.
587        super(Looper.getMainLooper());
588        // Save the WebView object.
589        mWebView = webView;
590        // Pass Proxy into webview, such that every time we have a setBaseLayer
591        // call, we tell this Proxy to call the native to update the layer tree
592        // for the Video Layer's surface texture info
593        mWebView.setHTML5VideoViewProxy(this);
594        // Save the native ptr
595        mNativePointer = nativePtr;
596        // create the message handler for this thread
597        createWebCoreHandler();
598    }
599
600    private void createWebCoreHandler() {
601        mWebCoreHandler = new Handler() {
602            @Override
603            public void handleMessage(Message msg) {
604                switch (msg.what) {
605                    case PREPARED: {
606                        Map<String, Object> map = (Map<String, Object>) msg.obj;
607                        Integer duration = (Integer) map.get("dur");
608                        Integer width = (Integer) map.get("width");
609                        Integer height = (Integer) map.get("height");
610                        nativeOnPrepared(duration.intValue(), width.intValue(),
611                                height.intValue(), mNativePointer);
612                        break;
613                    }
614                    case ENDED:
615                        mSeekPosition = 0;
616                        nativeOnEnded(mNativePointer);
617                        break;
618                    case PAUSED:
619                        nativeOnPaused(mNativePointer);
620                        break;
621                    case POSTER_FETCHED:
622                        Bitmap poster = (Bitmap) msg.obj;
623                        nativeOnPosterFetched(poster, mNativePointer);
624                        break;
625                    case TIMEUPDATE:
626                        nativeOnTimeupdate(msg.arg1, mNativePointer);
627                        break;
628                    case STOPFULLSCREEN:
629                        nativeOnStopFullscreen(msg.arg1, mNativePointer);
630                        break;
631                    case RESTORESTATE:
632                        nativeOnRestoreState(mNativePointer);
633                        break;
634                }
635            }
636        };
637    }
638
639    private void doSetPoster(Bitmap poster) {
640        if (poster == null) {
641            return;
642        }
643        // Save a ref to the bitmap and send it over to the WebCore thread.
644        mPoster = poster;
645        Message msg = Message.obtain(mWebCoreHandler, POSTER_FETCHED);
646        msg.obj = poster;
647        mWebCoreHandler.sendMessage(msg);
648    }
649
650    private void sendTimeupdate() {
651        Message msg = Message.obtain(mWebCoreHandler, TIMEUPDATE);
652        msg.arg1 = VideoPlayer.getCurrentPosition();
653        mWebCoreHandler.sendMessage(msg);
654    }
655
656    public Context getContext() {
657        return mWebView.getContext();
658    }
659
660    // The public methods below are all called from WebKit only.
661    /**
662     * Play a video stream.
663     * @param url is the URL of the video stream.
664     */
665    public void play(String url, int position, int videoLayerID) {
666        if (url == null) {
667            return;
668        }
669
670        if (position > 0) {
671            seek(position);
672        }
673        Message message = obtainMessage(PLAY);
674        message.arg1 = videoLayerID;
675        message.obj = url;
676        sendMessage(message);
677    }
678
679    /**
680     * Play a video stream in full screen mode.
681     * @param url is the URL of the video stream.
682     */
683    public void enterFullscreenForVideoLayer(String url, int videoLayerID) {
684        if (url == null) {
685            return;
686        }
687
688        Message message = obtainMessage(ENTER_FULLSCREEN);
689        message.arg1 = videoLayerID;
690        message.obj = url;
691        sendMessage(message);
692    }
693
694    /**
695     * Seek into the video stream.
696     * @param  time is the position in the video stream.
697     */
698    public void seek(int time) {
699        Message message = obtainMessage(SEEK);
700        message.obj = new Integer(time);
701        sendMessage(message);
702    }
703
704    /**
705     * Pause the playback.
706     */
707    public void pause() {
708        Message message = obtainMessage(PAUSE);
709        sendMessage(message);
710    }
711
712    /**
713     * Tear down this proxy object.
714     */
715    public void teardown() {
716        // This is called by the C++ MediaPlayerPrivate dtor.
717        // Cancel any active poster download.
718        if (mPosterDownloader != null) {
719            mPosterDownloader.cancelAndReleaseQueue();
720        }
721        mNativePointer = 0;
722    }
723
724    /**
725     * Load the poster image.
726     * @param url is the URL of the poster image.
727     */
728    public void loadPoster(String url) {
729        if (url == null) {
730            Message message = obtainMessage(LOAD_DEFAULT_POSTER);
731            sendMessage(message);
732            return;
733        }
734        // Cancel any active poster download.
735        if (mPosterDownloader != null) {
736            mPosterDownloader.cancelAndReleaseQueue();
737        }
738        // Load the poster asynchronously
739        mPosterDownloader = new PosterDownloader(url, this);
740        mPosterDownloader.start();
741    }
742
743    // These three function are called from UI thread only by WebView.
744    public void setBaseLayer(int layer) {
745        VideoPlayer.setBaseLayer(layer);
746    }
747
748    public void pauseAndDispatch() {
749        VideoPlayer.pauseAndDispatch();
750    }
751
752    public void enterFullScreenVideo(int layerId, String url) {
753        VideoPlayer.enterFullScreenVideo(layerId, url, this, mWebView);
754    }
755
756    public void exitFullScreenVideo() {
757        VideoPlayer.exitFullScreenVideo(this, mWebView);
758    }
759
760    /**
761     * The factory for HTML5VideoViewProxy instances.
762     * @param webViewCore is the WebViewCore that is requesting the proxy.
763     *
764     * @return a new HTML5VideoViewProxy object.
765     */
766    public static HTML5VideoViewProxy getInstance(WebViewCore webViewCore, int nativePtr) {
767        return new HTML5VideoViewProxy(webViewCore.getWebViewClassic(), nativePtr);
768    }
769
770    /* package */ WebViewClassic getWebView() {
771        return mWebView;
772    }
773
774    private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
775    private native void nativeOnEnded(int nativePointer);
776    private native void nativeOnPaused(int nativePointer);
777    private native void nativeOnPosterFetched(Bitmap poster, int nativePointer);
778    private native void nativeOnTimeupdate(int position, int nativePointer);
779    private native void nativeOnStopFullscreen(int stillPlaying, int nativePointer);
780    private native void nativeOnRestoreState(int nativePointer);
781    private native static boolean nativeSendSurfaceTexture(SurfaceTexture texture,
782            int baseLayer, int videoLayerId, int textureName,
783            int playerState);
784
785    @Override
786    public boolean onInfo(MediaPlayer mp, int what, int extra) {
787        if (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {
788            sendMessage(obtainMessage(BUFFERING_START, what, extra));
789        } else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {
790            sendMessage(obtainMessage(BUFFERING_END, what, extra));
791        }
792        return false;
793    }
794}
795