HTML5VideoViewProxy.java revision c3a2858de909145a382e7932d5fb044e1388c0b3
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; 34 35import java.io.ByteArrayOutputStream; 36import java.io.IOException; 37import java.net.MalformedURLException; 38import java.net.URL; 39import java.util.HashMap; 40import java.util.Map; 41 42/** 43 * <p>Proxy for HTML5 video views. 44 */ 45class 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 private static final int ENTER_FULLSCREEN = 107; 63 64 // Message Ids to be handled on the WebCore thread 65 private static final int PREPARED = 200; 66 private static final int ENDED = 201; 67 private static final int POSTER_FETCHED = 202; 68 private static final int PAUSED = 203; 69 private static final int STOPFULLSCREEN = 204; 70 private static final int RESTORESTATE = 205; 71 72 // Timer thread -> UI thread 73 private static final int TIMEUPDATE = 300; 74 75 // The C++ MediaPlayerPrivateAndroid object. 76 int mNativePointer; 77 // The handler for WebCore thread messages; 78 private Handler mWebCoreHandler; 79 // The WebViewClassic instance that created this view. 80 private WebViewClassic mWebView; 81 // The poster image to be shown when the video is not playing. 82 // This ref prevents the bitmap from being GC'ed. 83 private Bitmap mPoster; 84 // The poster downloader. 85 private PosterDownloader mPosterDownloader; 86 // The seek position. 87 private int mSeekPosition; 88 // A helper class to control the playback. This executes on the UI thread! 89 private static final class VideoPlayer { 90 // The proxy that is currently playing (if any). 91 private static HTML5VideoViewProxy mCurrentProxy; 92 // The VideoView instance. This is a singleton for now, at least until 93 // http://b/issue?id=1973663 is fixed. 94 private static HTML5VideoView mHTML5VideoView; 95 96 private static boolean isVideoSelfEnded = false; 97 // By using the baseLayer and the current video Layer ID, we can 98 // identify the exact layer on the UI thread to use the SurfaceTexture. 99 private static int mBaseLayer = 0; 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 mBaseLayer = layer; 110 // Don't do this for full screen mode. 111 if (mHTML5VideoView != null 112 && !mHTML5VideoView.isFullScreenMode()) { 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 && currentVideoLayerId == videoLayerId) { 218 skipPrepare = true; 219 createInlineView = true; 220 } else if(backFromFullScreenMode 221 || currentVideoLayerId != videoLayerId 222 || HTML5VideoInline.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 createInlineView = true; 235 } 236 if (createInlineView) { 237 mCurrentProxy = proxy; 238 mHTML5VideoView = new HTML5VideoInline(videoLayerId, time, skipPrepare); 239 240 mHTML5VideoView.setVideoURI(url, mCurrentProxy); 241 mHTML5VideoView.prepareDataAndDisplayMode(proxy); 242 return; 243 } 244 245 if (mCurrentProxy == proxy) { 246 // Here, we handle the case when we keep playing with one video 247 if (!mHTML5VideoView.isPlaying()) { 248 mHTML5VideoView.seekTo(time); 249 mHTML5VideoView.start(); 250 } 251 } else if (mCurrentProxy != null) { 252 // Some other video is already playing. Notify the caller that 253 // its playback ended. 254 proxy.dispatchOnEnded(); 255 } 256 } 257 258 public static boolean isPlaying(HTML5VideoViewProxy proxy) { 259 return (mCurrentProxy == proxy && mHTML5VideoView != null 260 && mHTML5VideoView.isPlaying()); 261 } 262 263 public static int getCurrentPosition() { 264 int currentPosMs = 0; 265 if (mHTML5VideoView != null) { 266 currentPosMs = mHTML5VideoView.getCurrentPosition(); 267 } 268 return currentPosMs; 269 } 270 271 public static void seek(int time, HTML5VideoViewProxy proxy) { 272 if (mCurrentProxy == proxy && time >= 0 && mHTML5VideoView != null) { 273 mHTML5VideoView.seekTo(time); 274 } 275 } 276 277 public static void pause(HTML5VideoViewProxy proxy) { 278 if (mCurrentProxy == proxy && mHTML5VideoView != null) { 279 mHTML5VideoView.pause(); 280 } 281 } 282 283 public static void onPrepared() { 284 if (!mHTML5VideoView.isFullScreenMode()) { 285 mHTML5VideoView.start(); 286 } 287 } 288 289 public static void end() { 290 mHTML5VideoView.showControllerInFullScreen(); 291 if (mCurrentProxy != null) { 292 if (isVideoSelfEnded) 293 mCurrentProxy.dispatchOnEnded(); 294 else 295 mCurrentProxy.dispatchOnPaused(); 296 } 297 isVideoSelfEnded = false; 298 } 299 } 300 301 // A bunch event listeners for our VideoView 302 // MediaPlayer.OnPreparedListener 303 public void onPrepared(MediaPlayer mp) { 304 VideoPlayer.onPrepared(); 305 Message msg = Message.obtain(mWebCoreHandler, PREPARED); 306 Map<String, Object> map = new HashMap<String, Object>(); 307 map.put("dur", new Integer(mp.getDuration())); 308 map.put("width", new Integer(mp.getVideoWidth())); 309 map.put("height", new Integer(mp.getVideoHeight())); 310 msg.obj = map; 311 mWebCoreHandler.sendMessage(msg); 312 } 313 314 // MediaPlayer.OnCompletionListener; 315 public void onCompletion(MediaPlayer mp) { 316 // The video ended by itself, so we need to 317 // send a message to the UI thread to dismiss 318 // the video view and to return to the WebView. 319 // arg1 == 1 means the video ends by itself. 320 sendMessage(obtainMessage(ENDED, 1, 0)); 321 } 322 323 // MediaPlayer.OnErrorListener 324 public boolean onError(MediaPlayer mp, int what, int extra) { 325 sendMessage(obtainMessage(ERROR)); 326 return false; 327 } 328 329 public void dispatchOnEnded() { 330 Message msg = Message.obtain(mWebCoreHandler, ENDED); 331 mWebCoreHandler.sendMessage(msg); 332 } 333 334 public void dispatchOnPaused() { 335 Message msg = Message.obtain(mWebCoreHandler, PAUSED); 336 mWebCoreHandler.sendMessage(msg); 337 } 338 339 public void dispatchOnStopFullScreen(boolean stillPlaying) { 340 Message msg = Message.obtain(mWebCoreHandler, STOPFULLSCREEN); 341 msg.arg1 = stillPlaying ? 1 : 0; 342 mWebCoreHandler.sendMessage(msg); 343 } 344 345 public void dispatchOnRestoreState() { 346 Message msg = Message.obtain(mWebCoreHandler, RESTORESTATE); 347 mWebCoreHandler.sendMessage(msg); 348 } 349 350 public void onTimeupdate() { 351 sendMessage(obtainMessage(TIMEUPDATE)); 352 } 353 354 // When there is a frame ready from surface texture, we should tell WebView 355 // to refresh. 356 @Override 357 public void onFrameAvailable(SurfaceTexture surfaceTexture) { 358 // TODO: This should support partial invalidation too. 359 mWebView.invalidate(); 360 } 361 362 // Handler for the messages from WebCore or Timer thread to the UI thread. 363 @Override 364 public void handleMessage(Message msg) { 365 // This executes on the UI thread. 366 switch (msg.what) { 367 case PLAY: { 368 String url = (String) msg.obj; 369 WebChromeClient client = mWebView.getWebChromeClient(); 370 int videoLayerID = msg.arg1; 371 if (client != null) { 372 VideoPlayer.play(url, mSeekPosition, this, client, videoLayerID); 373 } 374 break; 375 } 376 case ENTER_FULLSCREEN:{ 377 String url = (String) msg.obj; 378 WebChromeClient client = mWebView.getWebChromeClient(); 379 int videoLayerID = msg.arg1; 380 if (client != null) { 381 VideoPlayer.enterFullScreenVideo(videoLayerID, url, this, mWebView); 382 } 383 break; 384 } 385 case SEEK: { 386 Integer time = (Integer) msg.obj; 387 mSeekPosition = time; 388 VideoPlayer.seek(mSeekPosition, this); 389 break; 390 } 391 case PAUSE: { 392 VideoPlayer.pause(this); 393 break; 394 } 395 case ENDED: 396 if (msg.arg1 == 1) 397 VideoPlayer.isVideoSelfEnded = true; 398 VideoPlayer.end(); 399 break; 400 case ERROR: { 401 WebChromeClient client = mWebView.getWebChromeClient(); 402 if (client != null) { 403 client.onHideCustomView(); 404 } 405 break; 406 } 407 case LOAD_DEFAULT_POSTER: { 408 WebChromeClient client = mWebView.getWebChromeClient(); 409 if (client != null) { 410 doSetPoster(client.getDefaultVideoPoster()); 411 } 412 break; 413 } 414 case TIMEUPDATE: { 415 if (VideoPlayer.isPlaying(this)) { 416 sendTimeupdate(); 417 } 418 break; 419 } 420 case BUFFERING_START: { 421 VideoPlayer.setPlayerBuffering(true); 422 break; 423 } 424 case BUFFERING_END: { 425 VideoPlayer.setPlayerBuffering(false); 426 break; 427 } 428 } 429 } 430 431 // Everything below this comment executes on the WebCore thread, except for 432 // the EventHandler methods, which are called on the network thread. 433 434 // A helper class that knows how to download posters 435 private static final class PosterDownloader implements EventHandler { 436 // The request queue. This is static as we have one queue for all posters. 437 private static RequestQueue mRequestQueue; 438 private static int mQueueRefCount = 0; 439 // The poster URL 440 private URL mUrl; 441 // The proxy we're doing this for. 442 private final HTML5VideoViewProxy mProxy; 443 // The poster bytes. We only touch this on the network thread. 444 private ByteArrayOutputStream mPosterBytes; 445 // The request handle. We only touch this on the WebCore thread. 446 private RequestHandle mRequestHandle; 447 // The response status code. 448 private int mStatusCode; 449 // The response headers. 450 private Headers mHeaders; 451 // The handler to handle messages on the WebCore thread. 452 private Handler mHandler; 453 454 public PosterDownloader(String url, HTML5VideoViewProxy proxy) { 455 try { 456 mUrl = new URL(url); 457 } catch (MalformedURLException e) { 458 mUrl = null; 459 } 460 mProxy = proxy; 461 mHandler = new Handler(); 462 } 463 // Start the download. Called on WebCore thread. 464 public void start() { 465 retainQueue(); 466 467 if (mUrl == null) { 468 return; 469 } 470 471 // Only support downloading posters over http/https. 472 // FIXME: Add support for other schemes. WebKit seems able to load 473 // posters over other schemes e.g. file://, but gets the dimensions wrong. 474 String protocol = mUrl.getProtocol(); 475 if ("http".equals(protocol) || "https".equals(protocol)) { 476 mRequestHandle = mRequestQueue.queueRequest(mUrl.toString(), "GET", null, 477 this, null, 0); 478 } 479 } 480 // Cancel the download if active and release the queue. Called on WebCore thread. 481 public void cancelAndReleaseQueue() { 482 if (mRequestHandle != null) { 483 mRequestHandle.cancel(); 484 mRequestHandle = null; 485 } 486 releaseQueue(); 487 } 488 // EventHandler methods. Executed on the network thread. 489 public void status(int major_version, 490 int minor_version, 491 int code, 492 String reason_phrase) { 493 mStatusCode = code; 494 } 495 496 public void headers(Headers headers) { 497 mHeaders = headers; 498 } 499 500 public void data(byte[] data, int len) { 501 if (mPosterBytes == null) { 502 mPosterBytes = new ByteArrayOutputStream(); 503 } 504 mPosterBytes.write(data, 0, len); 505 } 506 507 public void endData() { 508 if (mStatusCode == 200) { 509 if (mPosterBytes.size() > 0) { 510 Bitmap poster = BitmapFactory.decodeByteArray( 511 mPosterBytes.toByteArray(), 0, mPosterBytes.size()); 512 mProxy.doSetPoster(poster); 513 } 514 cleanup(); 515 } else if (mStatusCode >= 300 && mStatusCode < 400) { 516 // We have a redirect. 517 try { 518 mUrl = new URL(mHeaders.getLocation()); 519 } catch (MalformedURLException e) { 520 mUrl = null; 521 } 522 if (mUrl != null) { 523 mHandler.post(new Runnable() { 524 public void run() { 525 if (mRequestHandle != null) { 526 mRequestHandle.setupRedirect(mUrl.toString(), mStatusCode, 527 new HashMap<String, String>()); 528 } 529 } 530 }); 531 } 532 } 533 } 534 535 public void certificate(SslCertificate certificate) { 536 // Don't care. 537 } 538 539 public void error(int id, String description) { 540 cleanup(); 541 } 542 543 public boolean handleSslErrorRequest(SslError error) { 544 // Don't care. If this happens, data() will never be called so 545 // mPosterBytes will never be created, so no need to call cleanup. 546 return false; 547 } 548 // Tears down the poster bytes stream. Called on network thread. 549 private void cleanup() { 550 if (mPosterBytes != null) { 551 try { 552 mPosterBytes.close(); 553 } catch (IOException ignored) { 554 // Ignored. 555 } finally { 556 mPosterBytes = null; 557 } 558 } 559 } 560 561 // Queue management methods. Called on WebCore thread. 562 private void retainQueue() { 563 if (mRequestQueue == null) { 564 mRequestQueue = new RequestQueue(mProxy.getContext()); 565 } 566 mQueueRefCount++; 567 } 568 569 private void releaseQueue() { 570 if (mQueueRefCount == 0) { 571 return; 572 } 573 if (--mQueueRefCount == 0) { 574 mRequestQueue.shutdown(); 575 mRequestQueue = null; 576 } 577 } 578 } 579 580 /** 581 * Private constructor. 582 * @param webView is the WebView that hosts the video. 583 * @param nativePtr is the C++ pointer to the MediaPlayerPrivate object. 584 */ 585 private HTML5VideoViewProxy(WebViewClassic webView, int nativePtr) { 586 // This handler is for the main (UI) thread. 587 super(Looper.getMainLooper()); 588 // Save the WebView object. 589 mWebView = webView; 590 // Pass Proxy into webview, such that every time we have a setBaseLayer 591 // call, we tell this Proxy to call the native to update the layer tree 592 // for the Video Layer's surface texture info 593 mWebView.setHTML5VideoViewProxy(this); 594 // Save the native ptr 595 mNativePointer = nativePtr; 596 // create the message handler for this thread 597 createWebCoreHandler(); 598 } 599 600 private void createWebCoreHandler() { 601 mWebCoreHandler = new Handler() { 602 @Override 603 public void handleMessage(Message msg) { 604 switch (msg.what) { 605 case PREPARED: { 606 Map<String, Object> map = (Map<String, Object>) msg.obj; 607 Integer duration = (Integer) map.get("dur"); 608 Integer width = (Integer) map.get("width"); 609 Integer height = (Integer) map.get("height"); 610 nativeOnPrepared(duration.intValue(), width.intValue(), 611 height.intValue(), mNativePointer); 612 break; 613 } 614 case ENDED: 615 mSeekPosition = 0; 616 nativeOnEnded(mNativePointer); 617 break; 618 case PAUSED: 619 nativeOnPaused(mNativePointer); 620 break; 621 case POSTER_FETCHED: 622 Bitmap poster = (Bitmap) msg.obj; 623 nativeOnPosterFetched(poster, mNativePointer); 624 break; 625 case TIMEUPDATE: 626 nativeOnTimeupdate(msg.arg1, mNativePointer); 627 break; 628 case STOPFULLSCREEN: 629 nativeOnStopFullscreen(msg.arg1, mNativePointer); 630 break; 631 case RESTORESTATE: 632 nativeOnRestoreState(mNativePointer); 633 break; 634 } 635 } 636 }; 637 } 638 639 private void doSetPoster(Bitmap poster) { 640 if (poster == null) { 641 return; 642 } 643 // Save a ref to the bitmap and send it over to the WebCore thread. 644 mPoster = poster; 645 Message msg = Message.obtain(mWebCoreHandler, POSTER_FETCHED); 646 msg.obj = poster; 647 mWebCoreHandler.sendMessage(msg); 648 } 649 650 private void sendTimeupdate() { 651 Message msg = Message.obtain(mWebCoreHandler, TIMEUPDATE); 652 msg.arg1 = VideoPlayer.getCurrentPosition(); 653 mWebCoreHandler.sendMessage(msg); 654 } 655 656 public Context getContext() { 657 return mWebView.getContext(); 658 } 659 660 // The public methods below are all called from WebKit only. 661 /** 662 * Play a video stream. 663 * @param url is the URL of the video stream. 664 */ 665 public void play(String url, int position, int videoLayerID) { 666 if (url == null) { 667 return; 668 } 669 670 if (position > 0) { 671 seek(position); 672 } 673 Message message = obtainMessage(PLAY); 674 message.arg1 = videoLayerID; 675 message.obj = url; 676 sendMessage(message); 677 } 678 679 /** 680 * Play a video stream in full screen mode. 681 * @param url is the URL of the video stream. 682 */ 683 public void enterFullscreenForVideoLayer(String url, int videoLayerID) { 684 if (url == null) { 685 return; 686 } 687 688 Message message = obtainMessage(ENTER_FULLSCREEN); 689 message.arg1 = videoLayerID; 690 message.obj = url; 691 sendMessage(message); 692 } 693 694 /** 695 * Seek into the video stream. 696 * @param time is the position in the video stream. 697 */ 698 public void seek(int time) { 699 Message message = obtainMessage(SEEK); 700 message.obj = new Integer(time); 701 sendMessage(message); 702 } 703 704 /** 705 * Pause the playback. 706 */ 707 public void pause() { 708 Message message = obtainMessage(PAUSE); 709 sendMessage(message); 710 } 711 712 /** 713 * Tear down this proxy object. 714 */ 715 public void teardown() { 716 // This is called by the C++ MediaPlayerPrivate dtor. 717 // Cancel any active poster download. 718 if (mPosterDownloader != null) { 719 mPosterDownloader.cancelAndReleaseQueue(); 720 } 721 mNativePointer = 0; 722 } 723 724 /** 725 * Load the poster image. 726 * @param url is the URL of the poster image. 727 */ 728 public void loadPoster(String url) { 729 if (url == null) { 730 Message message = obtainMessage(LOAD_DEFAULT_POSTER); 731 sendMessage(message); 732 return; 733 } 734 // Cancel any active poster download. 735 if (mPosterDownloader != null) { 736 mPosterDownloader.cancelAndReleaseQueue(); 737 } 738 // Load the poster asynchronously 739 mPosterDownloader = new PosterDownloader(url, this); 740 mPosterDownloader.start(); 741 } 742 743 // These three function are called from UI thread only by WebView. 744 public void setBaseLayer(int layer) { 745 VideoPlayer.setBaseLayer(layer); 746 } 747 748 public void pauseAndDispatch() { 749 VideoPlayer.pauseAndDispatch(); 750 } 751 752 public void enterFullScreenVideo(int layerId, String url) { 753 VideoPlayer.enterFullScreenVideo(layerId, url, this, mWebView); 754 } 755 756 public void exitFullScreenVideo() { 757 VideoPlayer.exitFullScreenVideo(this, mWebView); 758 } 759 760 /** 761 * The factory for HTML5VideoViewProxy instances. 762 * @param webViewCore is the WebViewCore that is requesting the proxy. 763 * 764 * @return a new HTML5VideoViewProxy object. 765 */ 766 public static HTML5VideoViewProxy getInstance(WebViewCore webViewCore, int nativePtr) { 767 return new HTML5VideoViewProxy(webViewCore.getWebViewClassic(), nativePtr); 768 } 769 770 /* package */ WebViewClassic getWebView() { 771 return mWebView; 772 } 773 774 private native void nativeOnPrepared(int duration, int width, int height, int nativePointer); 775 private native void nativeOnEnded(int nativePointer); 776 private native void nativeOnPaused(int nativePointer); 777 private native void nativeOnPosterFetched(Bitmap poster, int nativePointer); 778 private native void nativeOnTimeupdate(int position, int nativePointer); 779 private native void nativeOnStopFullscreen(int stillPlaying, int nativePointer); 780 private native void nativeOnRestoreState(int nativePointer); 781 private native static boolean nativeSendSurfaceTexture(SurfaceTexture texture, 782 int baseLayer, int videoLayerId, int textureName, 783 int playerState); 784 785 @Override 786 public boolean onInfo(MediaPlayer mp, int what, int extra) { 787 if (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) { 788 sendMessage(obtainMessage(BUFFERING_START, what, extra)); 789 } else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END) { 790 sendMessage(obtainMessage(BUFFERING_END, what, extra)); 791 } 792 return false; 793 } 794} 795