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