1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.webkit;
18
19import android.content.Context;
20import android.graphics.Bitmap;
21import android.graphics.BitmapFactory;
22import android.graphics.SurfaceTexture;
23import android.media.MediaPlayer;
24import android.net.http.EventHandler;
25import android.net.http.Headers;
26import android.net.http.RequestHandle;
27import android.net.http.RequestQueue;
28import android.net.http.SslCertificate;
29import android.net.http.SslError;
30import android.os.Handler;
31import android.os.Looper;
32import android.os.Message;
33import android.util.Log;
34import android.view.KeyEvent;
35import android.view.View;
36
37import java.io.ByteArrayOutputStream;
38import java.io.IOException;
39import java.net.MalformedURLException;
40import java.net.URL;
41import java.util.HashMap;
42import java.util.Map;
43
44/**
45 * <p>Proxy for HTML5 video views.
46 */
47class 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
101        private static void setPlayerBuffering(boolean playerBuffering) {
102            mHTML5VideoView.setPlayerBuffering(playerBuffering);
103        }
104
105        // Every time webView setBaseLayer, this will be called.
106        // When we found the Video layer, then we set the Surface Texture to it.
107        // Otherwise, we may want to delete the Surface Texture to save memory.
108        public static void setBaseLayer(int layer) {
109            // 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.
134        public static void pauseAndDispatch() {
135            if (mHTML5VideoView != null) {
136                mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
137            }
138        }
139
140        public static void enterFullScreenVideo(int layerId, String url,
141                HTML5VideoViewProxy proxy, WebViewClassic webView) {
142                // Save the inline video info and inherit it in the full screen
143                int savePosition = 0;
144                boolean canSkipPrepare = false;
145                boolean forceStart = false;
146                if (mHTML5VideoView != null) {
147                    // We don't allow enter full screen mode while the previous
148                    // full screen video hasn't finished yet.
149                    if (!mHTML5VideoView.fullScreenExited() && mHTML5VideoView.isFullScreenMode()) {
150                        Log.w(LOGTAG, "Try to reenter the full screen mode");
151                        return;
152                    }
153                    int playerState = mHTML5VideoView.getCurrentState();
154                    // If we are playing the same video, then it is better to
155                    // save the current position.
156                    if (layerId == mHTML5VideoView.getVideoLayerId()) {
157                        savePosition = mHTML5VideoView.getCurrentPosition();
158                        canSkipPrepare = (playerState == HTML5VideoView.STATE_PREPARING
159                                || playerState == HTML5VideoView.STATE_PREPARED
160                                || playerState == HTML5VideoView.STATE_PLAYING)
161                                && !mHTML5VideoView.isFullScreenMode();
162                    }
163                    if (!canSkipPrepare) {
164                        mHTML5VideoView.reset();
165                    } else {
166                        forceStart = playerState == HTML5VideoView.STATE_PREPARING
167                                || playerState == HTML5VideoView.STATE_PLAYING;
168                    }
169                }
170                mHTML5VideoView = new HTML5VideoFullScreen(proxy.getContext(),
171                        layerId, savePosition, canSkipPrepare);
172                mHTML5VideoView.setStartWhenPrepared(forceStart);
173                mCurrentProxy = proxy;
174                mHTML5VideoView.setVideoURI(url, mCurrentProxy);
175                mHTML5VideoView.enterFullScreenVideoState(layerId, proxy, webView);
176        }
177
178        public static void exitFullScreenVideo(HTML5VideoViewProxy proxy,
179                WebViewClassic webView) {
180            if (!mHTML5VideoView.fullScreenExited() && mHTML5VideoView.isFullScreenMode()) {
181                WebChromeClient client = webView.getWebChromeClient();
182                if (client != null) {
183                    client.onHideCustomView();
184                }
185            }
186        }
187
188        // This is on the UI thread.
189        // When native tell Java to play, we need to check whether or not it is
190        // still the same video by using videoLayerId and treat it differently.
191        public static void play(String url, int time, HTML5VideoViewProxy proxy,
192                WebChromeClient client, int videoLayerId) {
193            int currentVideoLayerId = -1;
194            boolean backFromFullScreenMode = false;
195            if (mHTML5VideoView != null) {
196                currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
197                backFromFullScreenMode = mHTML5VideoView.fullScreenExited();
198
199                // When playing video back to back in full screen mode,
200                // javascript will switch the src and call play.
201                // In this case, we can just reuse the same full screen view,
202                // and play the video after prepared.
203                if (mHTML5VideoView.isFullScreenMode()
204                    && !backFromFullScreenMode
205                    && currentVideoLayerId != videoLayerId
206                    && mCurrentProxy != proxy) {
207                    mCurrentProxy = proxy;
208                    mHTML5VideoView.setStartWhenPrepared(true);
209                    mHTML5VideoView.setVideoURI(url, proxy);
210                    mHTML5VideoView.reprepareData(proxy);
211                    return;
212                }
213            }
214
215            boolean skipPrepare = false;
216            boolean createInlineView = false;
217            if (backFromFullScreenMode
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
260        public static boolean isPlaying(HTML5VideoViewProxy proxy) {
261            return (mCurrentProxy == proxy && mHTML5VideoView != null
262                    && mHTML5VideoView.isPlaying());
263        }
264
265        public static int getCurrentPosition() {
266            int currentPosMs = 0;
267            if (mHTML5VideoView != null) {
268                currentPosMs = mHTML5VideoView.getCurrentPosition();
269            }
270            return currentPosMs;
271        }
272
273        public static void seek(int time, HTML5VideoViewProxy proxy) {
274            if (mCurrentProxy == proxy && time >= 0 && mHTML5VideoView != null) {
275                mHTML5VideoView.seekTo(time);
276            }
277        }
278
279        public static void pause(HTML5VideoViewProxy proxy) {
280            if (mCurrentProxy == proxy && mHTML5VideoView != null) {
281                mHTML5VideoView.pause();
282            }
283        }
284
285        public static void onPrepared() {
286            if (!mHTML5VideoView.isFullScreenMode()) {
287                mHTML5VideoView.start();
288            }
289        }
290
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
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
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
329    public boolean onError(MediaPlayer mp, int what, int extra) {
330        sendMessage(obtainMessage(ERROR));
331        return false;
332    }
333
334    public void dispatchOnEnded() {
335        Message msg = Message.obtain(mWebCoreHandler, ENDED);
336        mWebCoreHandler.sendMessage(msg);
337    }
338
339    public void dispatchOnPaused() {
340        Message msg = Message.obtain(mWebCoreHandler, PAUSED);
341        mWebCoreHandler.sendMessage(msg);
342    }
343
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
350    public void dispatchOnRestoreState() {
351        Message msg = Message.obtain(mWebCoreHandler, RESTORESTATE);
352        mWebCoreHandler.sendMessage(msg);
353    }
354
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
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
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
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.
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.
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
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
503        public void headers(Headers headers) {
504            mHeaders = headers;
505        }
506
507        @Override
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
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
546        public void certificate(SslCertificate certificate) {
547            // Don't care.
548        }
549
550        @Override
551        public void error(int id, String description) {
552            cleanup();
553        }
554
555        @Override
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.
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.
575        private void retainQueue() {
576            if (mRequestQueue == null) {
577                mRequestQueue = new RequestQueue(mProxy.getContext());
578            }
579            mQueueRefCount++;
580        }
581
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     */
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
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
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
663    private void sendTimeupdate() {
664        Message msg = Message.obtain(mWebCoreHandler, TIMEUPDATE);
665        msg.arg1 = VideoPlayer.getCurrentPosition();
666        mWebCoreHandler.sendMessage(msg);
667    }
668
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     */
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     */
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     */
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     */
720    public void pause() {
721        Message message = obtainMessage(PAUSE);
722        sendMessage(message);
723    }
724
725    /**
726     * Tear down this proxy object.
727     */
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     */
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.
757    public void setBaseLayer(int layer) {
758        VideoPlayer.setBaseLayer(layer);
759    }
760
761    public void pauseAndDispatch() {
762        VideoPlayer.pauseAndDispatch();
763    }
764
765    public void enterFullScreenVideo(int layerId, String url) {
766        VideoPlayer.enterFullScreenVideo(layerId, url, this, mWebView);
767    }
768
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     */
779    public static HTML5VideoViewProxy getInstance(WebViewCore webViewCore, int nativePtr) {
780        return new HTML5VideoViewProxy(webViewCore.getWebViewClassic(), nativePtr);
781    }
782
783    /* package */ WebViewClassic getWebView() {
784        return mWebView;
785    }
786
787    private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
788    private native void nativeOnEnded(int nativePointer);
789    private native void nativeOnPaused(int nativePointer);
790    private native void nativeOnPosterFetched(Bitmap poster, int nativePointer);
791    private native void nativeOnTimeupdate(int position, int nativePointer);
792    private native void nativeOnStopFullscreen(int stillPlaying, int nativePointer);
793    private native void nativeOnRestoreState(int nativePointer);
794    private native static boolean nativeSendSurfaceTexture(SurfaceTexture texture,
795            int baseLayer, int videoLayerId, int textureName,
796            int playerState);
797
798    @Override
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
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