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