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