From 661e8b1f21f03ea429ff34f8fdb58b68a5049092 Mon Sep 17 00:00:00 2001 From: Teng-Hui Zhu Date: Wed, 2 Mar 2011 15:09:34 -0800 Subject: Inline HTML5 Video support Use the HTML5VideoView to make inline HTML5 video possible. Full screen support will be the next step. The native side change is at 101310. bug:3506407, 2126902 Change-Id: I012f33a4d0c7b83d37b184fceb3923e1fb277b80 --- core/java/android/webkit/HTML5VideoViewProxy.java | 246 +++++++++++++--------- 1 file changed, 147 insertions(+), 99 deletions(-) (limited to 'core/java/android/webkit/HTML5VideoViewProxy.java') diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java index 85763da46e0d..b614d8f5467c 100644 --- a/core/java/android/webkit/HTML5VideoViewProxy.java +++ b/core/java/android/webkit/HTML5VideoViewProxy.java @@ -19,6 +19,7 @@ package android.webkit; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.SurfaceTexture; import android.media.MediaPlayer; import android.media.MediaPlayer.OnPreparedListener; import android.media.MediaPlayer.OnCompletionListener; @@ -59,7 +60,8 @@ import java.util.TimerTask; class HTML5VideoViewProxy extends Handler implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, - MediaPlayer.OnErrorListener { + MediaPlayer.OnErrorListener, + SurfaceTexture.OnFrameAvailableListener { // Logging tag. private static final String LOGTAG = "HTML5VideoViewProxy"; @@ -101,7 +103,7 @@ class HTML5VideoViewProxy extends Handler private static HTML5VideoViewProxy mCurrentProxy; // The VideoView instance. This is a singleton for now, at least until // http://b/issue?id=1973663 is fixed. - private static VideoView mVideoView; + private static HTML5VideoView mHTML5VideoView; // The progress view. private static View mProgressView; // The container for the progress view and video view @@ -122,131 +124,149 @@ class HTML5VideoViewProxy extends Handler } // The spec says the timer should fire every 250 ms or less. private static final int TIMEUPDATE_PERIOD = 250; // ms - static boolean isVideoSelfEnded = false; - - private static final WebChromeClient.CustomViewCallback mCallback = - new WebChromeClient.CustomViewCallback() { - public void onCustomViewHidden() { - // At this point the videoview is pretty much destroyed. - // It listens to SurfaceHolder.Callback.SurfaceDestroyed event - // which happens when the video view is detached from its parent - // view. This happens in the WebChromeClient before this method - // is invoked. - mTimer.cancel(); - mTimer = null; - if (mVideoView.isPlaying()) { - mVideoView.stopPlayback(); + private static boolean isVideoSelfEnded = false; + // By using the baseLayer and the current video Layer ID, we can + // identify the exact layer on the UI thread to use the SurfaceTexture. + private static int mBaseLayer = 0; + + // TODO: [FULL SCREEN SUPPORT] + + // Every time webView setBaseLayer, this will be called. + // When we found the Video layer, then we set the Surface Texture to it. + // Otherwise, we may want to delete the Surface Texture to save memory. + public static void setBaseLayer(int layer) { + if (mHTML5VideoView != null) { + mBaseLayer = layer; + SurfaceTexture surfTexture = mHTML5VideoView.getSurfaceTexture(); + int textureName = mHTML5VideoView.getTextureName(); + + int currentVideoLayerId = mHTML5VideoView.getVideoLayerId(); + if (layer != 0 && surfTexture != null && currentVideoLayerId != -1) { + boolean readyToUseSurfTex = + mHTML5VideoView.getReadyToUseSurfTex(); + boolean foundInTree = nativeSendSurfaceTexture(surfTexture, + layer, currentVideoLayerId, textureName, + readyToUseSurfTex); + if (readyToUseSurfTex && !foundInTree) { + mHTML5VideoView.pauseAndDispatch(mCurrentProxy); + mHTML5VideoView.deleteSurfaceTexture(); } - if (isVideoSelfEnded) - mCurrentProxy.dispatchOnEnded(); - else - mCurrentProxy.dispatchOnPaused(); - - // Re enable plugin views. - mCurrentProxy.getWebView().getViewManager().showAll(); - - isVideoSelfEnded = false; - mCurrentProxy = null; - mLayout.removeView(mVideoView); - mVideoView = null; - if (mProgressView != null) { - mLayout.removeView(mProgressView); - mProgressView = null; - } - mLayout = null; } - }; + } + } + + // When a WebView is paused, we also want to pause the video in it. + public static void pauseAndDispatch() { + if (mHTML5VideoView != null) { + mHTML5VideoView.pauseAndDispatch(mCurrentProxy); + // When switching out, clean the video content on the old page + // by telling the layer not readyToUseSurfTex. + setBaseLayer(mBaseLayer); + } + } + // This is on the UI thread. + // When native tell Java to play, we need to check whether or not it is + // still the same video by using videoLayerId and treat it differently. public static void play(String url, int time, HTML5VideoViewProxy proxy, - WebChromeClient client) { - if (mCurrentProxy == proxy) { - if (!mVideoView.isPlaying()) { - mVideoView.start(); + WebChromeClient client, int videoLayerId) { + int currentVideoLayerId = -1; + if (mHTML5VideoView != null) + currentVideoLayerId = mHTML5VideoView.getVideoLayerId(); + + if (currentVideoLayerId != videoLayerId + || mHTML5VideoView.getSurfaceTexture() == null) { + // Here, we handle the case when switching to a new video, + // either inside a WebView or across WebViews + // For switching videos within a WebView or across the WebView, + // we need to pause the old one and re-create a new media player + // inside the HTML5VideoView. + if (mHTML5VideoView != null) { + mHTML5VideoView.pauseAndDispatch(mCurrentProxy); + // release the media player to avoid finalize error + mHTML5VideoView.release(); } - return; - } + // HTML5VideoView is singleton, however, the internal state will + // be reset since we are switching from one video to another. + // Then we need to set up all the source/listener etc... + mHTML5VideoView = HTML5VideoView.getInstance(videoLayerId); - if (mCurrentProxy != null) { - // Some other video is already playing. Notify the caller that its playback ended. - proxy.dispatchOnEnded(); - return; - } + mCurrentProxy = proxy; - mCurrentProxy = proxy; - // Create a FrameLayout that will contain the VideoView and the - // progress view (if any). - mLayout = new FrameLayout(proxy.getContext()); - FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - Gravity.CENTER); - mVideoView = new VideoView(proxy.getContext()); - mVideoView.setWillNotDraw(false); - mVideoView.setMediaController(new MediaController(proxy.getContext())); - - boolean isPrivate = mCurrentProxy.getWebView().isPrivateBrowsingEnabled(); - String cookieValue = CookieManager.getInstance().getCookie(url, isPrivate); - Map headers = new HashMap(); - if (cookieValue != null) { - headers.put(COOKIE, cookieValue); - } - if (isPrivate) { - headers.put(HIDE_URL_LOGS, "true"); - } + // TODO: [FULL SCREEN SUPPORT] + + boolean isPrivate = mCurrentProxy.getWebView().isPrivateBrowsingEnabled(); + String cookieValue = CookieManager.getInstance().getCookie(url, isPrivate); + Map headers = new HashMap(); + if (cookieValue != null) { + headers.put(COOKIE, cookieValue); + } + if (isPrivate) { + headers.put(HIDE_URL_LOGS, "true"); + } + + mHTML5VideoView.setVideoURI(url, headers); + mHTML5VideoView.setOnCompletionListener(proxy); + mHTML5VideoView.setOnPreparedListener(proxy); + mHTML5VideoView.setOnErrorListener(proxy); + mHTML5VideoView.setFrameAvailableListener(proxy); - mVideoView.setVideoURI(Uri.parse(url), headers); - mVideoView.setOnCompletionListener(proxy); - mVideoView.setOnPreparedListener(proxy); - mVideoView.setOnErrorListener(proxy); - mVideoView.seekTo(time); - mLayout.addView(mVideoView, layoutParams); - mProgressView = client.getVideoLoadingProgressView(); - if (mProgressView != null) { - mLayout.addView(mProgressView, layoutParams); - mProgressView.setVisibility(View.VISIBLE); + mHTML5VideoView.seekTo(time); + + mTimer = new Timer(); + + } else if (mCurrentProxy == proxy) { + // Here, we handle the case when we keep playing with one video + if (!mHTML5VideoView.isPlaying()) { + mHTML5VideoView.seekTo(time); + mHTML5VideoView.start(); + } + } else if (mCurrentProxy != null) { + // Some other video is already playing. Notify the caller that its playback ended. + proxy.dispatchOnEnded(); } - mLayout.setVisibility(View.VISIBLE); - mTimer = new Timer(); - mVideoView.start(); - client.onShowCustomView(mLayout, mCallback); - // Plugins like Flash will draw over the video so hide - // them while we're playing. - mCurrentProxy.getWebView().getViewManager().hideAll(); } public static boolean isPlaying(HTML5VideoViewProxy proxy) { - return (mCurrentProxy == proxy && mVideoView != null && mVideoView.isPlaying()); + return (mCurrentProxy == proxy && mHTML5VideoView != null + && mHTML5VideoView.isPlaying()); } public static int getCurrentPosition() { int currentPosMs = 0; - if (mVideoView != null) { - currentPosMs = mVideoView.getCurrentPosition(); + if (mHTML5VideoView != null) { + currentPosMs = mHTML5VideoView.getCurrentPosition(); } return currentPosMs; } public static void seek(int time, HTML5VideoViewProxy proxy) { - if (mCurrentProxy == proxy && time >= 0 && mVideoView != null) { - mVideoView.seekTo(time); + if (mCurrentProxy == proxy && time >= 0 && mHTML5VideoView != null) { + mHTML5VideoView.seekTo(time); } } public static void pause(HTML5VideoViewProxy proxy) { - if (mCurrentProxy == proxy && mVideoView != null) { - mVideoView.pause(); + if (mCurrentProxy == proxy && mHTML5VideoView != null) { + mHTML5VideoView.pause(); mTimer.purge(); } } public static void onPrepared() { - if (mProgressView == null || mLayout == null) { - return; - } + mHTML5VideoView.start(); mTimer.schedule(new TimeupdateTask(mCurrentProxy), TIMEUPDATE_PERIOD, TIMEUPDATE_PERIOD); - mProgressView.setVisibility(View.GONE); - mLayout.removeView(mProgressView); - mProgressView = null; + // TODO: [FULL SCREEN SUPPORT] + } + + public static void end() { + if (mCurrentProxy != null) { + if (isVideoSelfEnded) + mCurrentProxy.dispatchOnEnded(); + else + mCurrentProxy.dispatchOnPaused(); + } + isVideoSelfEnded = false; } } @@ -292,6 +312,14 @@ class HTML5VideoViewProxy extends Handler sendMessage(obtainMessage(TIMEUPDATE)); } + // When there is a frame ready from surface texture, we should tell WebView + // to refresh. + @Override + public void onFrameAvailable(SurfaceTexture surfaceTexture) { + // TODO: This should support partial invalidation too. + mWebView.invalidate(); + } + // Handler for the messages from WebCore or Timer thread to the UI thread. @Override public void handleMessage(Message msg) { @@ -300,8 +328,9 @@ class HTML5VideoViewProxy extends Handler case PLAY: { String url = (String) msg.obj; WebChromeClient client = mWebView.getWebChromeClient(); + int videoLayerID = msg.arg1; if (client != null) { - VideoPlayer.play(url, mSeekPosition, this, client); + VideoPlayer.play(url, mSeekPosition, this, client, videoLayerID); } break; } @@ -318,6 +347,10 @@ class HTML5VideoViewProxy extends Handler case ENDED: if (msg.arg1 == 1) VideoPlayer.isVideoSelfEnded = true; + VideoPlayer.end(); + break; + // TODO: [FULL SCREEN SUPPORT] + // For full screen case, end may need hide the view. case ERROR: { WebChromeClient client = mWebView.getWebChromeClient(); if (client != null) { @@ -500,6 +533,10 @@ class HTML5VideoViewProxy extends Handler super(Looper.getMainLooper()); // Save the WebView object. mWebView = webView; + // Pass Proxy into webview, such that every time we have a setBaseLayer + // call, we tell this Proxy to call the native to update the layer tree + // for the Video Layer's surface texture info + mWebView.setHTML5VideoViewProxy(this); // Save the native ptr mNativePointer = nativePtr; // create the message handler for this thread @@ -565,7 +602,7 @@ class HTML5VideoViewProxy extends Handler * Play a video stream. * @param url is the URL of the video stream. */ - public void play(String url, int position) { + public void play(String url, int position, int videoLayerID) { if (url == null) { return; } @@ -573,8 +610,8 @@ class HTML5VideoViewProxy extends Handler if (position > 0) { seek(position); } - Message message = obtainMessage(PLAY); + message.arg1 = videoLayerID; message.obj = url; sendMessage(message); } @@ -628,6 +665,14 @@ class HTML5VideoViewProxy extends Handler mPosterDownloader.start(); } + // These two function are called from UI thread only by WebView. + public void setBaseLayer(int layer) { + VideoPlayer.setBaseLayer(layer); + } + + public void pauseAndDispatch() { + VideoPlayer.pauseAndDispatch(); + } /** * The factory for HTML5VideoViewProxy instances. * @param webViewCore is the WebViewCore that is requesting the proxy. @@ -647,4 +692,7 @@ class HTML5VideoViewProxy extends Handler private native void nativeOnPaused(int nativePointer); private native void nativeOnPosterFetched(Bitmap poster, int nativePointer); private native void nativeOnTimeupdate(int position, int nativePointer); + private native static boolean nativeSendSurfaceTexture(SurfaceTexture texture, + int baseLayer, int videoLayerId, int textureName, + boolean updateTexture); } -- cgit v1.2.3