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