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 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