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