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