Loading core/java/android/webkit/HTML5VideoView.java 0 → 100644 +211 −0 Original line number Diff line number Diff line package android.webkit; import android.graphics.SurfaceTexture; import android.media.MediaPlayer; import android.util.Log; import android.webkit.HTML5VideoViewProxy; import android.widget.MediaController; import android.opengl.GLES20; import java.io.IOException; import java.util.Map; /** * @hide This is only used by the browser */ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{ // Due to the fact that SurfaceTexture consume a lot of memory, we make it // as static. m_textureNames is the texture bound with this SurfaceTexture. private static SurfaceTexture mSurfaceTexture = null; private static int[] mTextureNames; // Only when the video is prepared, we render using SurfaceTexture. // This in fact is used to avoid showing the obsolete content when // switching videos. private static boolean mReadyToUseSurfTex = false; // For handling the seekTo before prepared, we need to know whether or not // the video is prepared. Therefore, we differentiate the state between // prepared and not prepared. // When the video is not prepared, we will have to save the seekTo time, // and use it when prepared to play. private static final int STATE_NOTPREPARED = 0; private static final int STATE_PREPARED = 1; // We only need state for handling seekTo private int mCurrentState; // Basically for calling back the OnPrepared in the proxy private HTML5VideoViewProxy mProxy; // Save the seek time when not prepared. This can happen when switching // video besides initial load. private int mSaveSeekTime; // This is used to find the VideoLayer on the native side. private int mVideoLayerId; // Every video will have one MediaPlayer. Given the fact we only have one // SurfaceTexture, there is only one MediaPlayer in action. Every time we // switch videos, a new instance of MediaPlayer will be created in reset(). private MediaPlayer mPlayer; private static HTML5VideoView mInstance = new HTML5VideoView(); // Video control FUNCTIONS: public void start() { if (mCurrentState == STATE_PREPARED) { mPlayer.start(); mReadyToUseSurfTex = true; } } public void pause() { mPlayer.pause(); } public int getDuration() { return mPlayer.getDuration(); } public int getCurrentPosition() { return mPlayer.getCurrentPosition(); } public void seekTo(int pos) { if (mCurrentState == STATE_PREPARED) mPlayer.seekTo(pos); else mSaveSeekTime = pos; } public boolean isPlaying() { return mPlayer.isPlaying(); } public void release() { mPlayer.release(); } public void stopPlayback() { mPlayer.stop(); } private void reset(int videoLayerId) { mPlayer = new MediaPlayer(); mCurrentState = STATE_NOTPREPARED; mProxy = null; mVideoLayerId = videoLayerId; mReadyToUseSurfTex = false; } public static HTML5VideoView getInstance(int videoLayerId) { // Every time we switch between the videos, a new MediaPlayer will be // created. Make sure we call the m_player.release() when it is done. mInstance.reset(videoLayerId); return mInstance; } private HTML5VideoView() { // This is a singleton across WebViews (i.e. Tabs). // HTML5VideoViewProxy will reset the internal state every time a new // video start. } public void setMediaController(MediaController m) { this.setMediaController(m); } public void setVideoURI(String uri, Map<String, String> headers) { // When switching players, surface texture will be reused. mPlayer.setTexture(getSurfaceTextureInstance()); // When there is exception, we could just bail out silently. // No Video will be played though. Write the stack for debug try { mPlayer.setDataSource(uri, headers); mPlayer.prepareAsync(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } // TODO [FULL SCREEN SUPPORT] // Listeners setup FUNCTIONS: public void setOnCompletionListener(HTML5VideoViewProxy proxy) { mPlayer.setOnCompletionListener(proxy); } public void setOnErrorListener(HTML5VideoViewProxy proxy) { mPlayer.setOnErrorListener(proxy); } public void setOnPreparedListener(HTML5VideoViewProxy proxy) { mProxy = proxy; mPlayer.setOnPreparedListener(this); } // Inline Video specific FUNCTIONS: public SurfaceTexture getSurfaceTexture() { return mSurfaceTexture; } public void deleteSurfaceTexture() { mSurfaceTexture = null; return; } // SurfaceTexture is a singleton here , too private SurfaceTexture getSurfaceTextureInstance() { // Create the surface texture. if (mSurfaceTexture == null) { mTextureNames = new int[1]; GLES20.glGenTextures(1, mTextureNames, 0); mSurfaceTexture = new SurfaceTexture(mTextureNames[0]); } return mSurfaceTexture; } public int getTextureName() { return mTextureNames[0]; } public int getVideoLayerId() { return mVideoLayerId; } public boolean getReadyToUseSurfTex() { return mReadyToUseSurfTex; } public void setFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener l) { mSurfaceTexture.setOnFrameAvailableListener(l); } @Override public void onPrepared(MediaPlayer mp) { mCurrentState = STATE_PREPARED; seekTo(mSaveSeekTime); if (mProxy != null) mProxy.onPrepared(mp); } // Pause the play and update the play/pause button public void pauseAndDispatch(HTML5VideoViewProxy proxy) { if (isPlaying()) { pause(); if (proxy != null) { proxy.dispatchOnPaused(); } } mReadyToUseSurfTex = false; } } core/java/android/webkit/HTML5VideoViewProxy.java +147 −99 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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"; Loading Loading @@ -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 Loading @@ -122,67 +124,76 @@ 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(); } if (isVideoSelfEnded) mCurrentProxy.dispatchOnEnded(); else mCurrentProxy.dispatchOnPaused(); 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; // Re enable plugin views. mCurrentProxy.getWebView().getViewManager().showAll(); // TODO: [FULL SCREEN SUPPORT] isVideoSelfEnded = false; mCurrentProxy = null; mLayout.removeView(mVideoView); mVideoView = null; if (mProgressView != null) { mLayout.removeView(mProgressView); mProgressView = null; // 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(); } mLayout = null; } }; public static void play(String url, int time, HTML5VideoViewProxy proxy, WebChromeClient client) { if (mCurrentProxy == proxy) { if (!mVideoView.isPlaying()) { mVideoView.start(); } return; } if (mCurrentProxy != null) { // Some other video is already playing. Notify the caller that its playback ended. proxy.dispatchOnEnded(); return; // 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, 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(); } // 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); 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())); // TODO: [FULL SCREEN SUPPORT] boolean isPrivate = mCurrentProxy.getWebView().isPrivateBrowsingEnabled(); String cookieValue = CookieManager.getInstance().getCookie(url, isPrivate); Loading @@ -194,59 +205,68 @@ class HTML5VideoViewProxy extends Handler headers.put(HIDE_URL_LOGS, "true"); } 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); } mLayout.setVisibility(View.VISIBLE); mHTML5VideoView.setVideoURI(url, headers); mHTML5VideoView.setOnCompletionListener(proxy); mHTML5VideoView.setOnPreparedListener(proxy); mHTML5VideoView.setOnErrorListener(proxy); mHTML5VideoView.setFrameAvailableListener(proxy); mHTML5VideoView.seekTo(time); 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(); } 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(); } } 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; } } Loading Loading @@ -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) { Loading @@ -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; } Loading @@ -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) { Loading Loading @@ -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 Loading Loading @@ -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; } Loading @@ -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); } Loading Loading @@ -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. Loading @@ -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); } core/java/android/webkit/WebView.java +23 −0 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.graphics.SurfaceTexture; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.net.Proxy; Loading Loading @@ -608,6 +609,10 @@ public class WebView extends AbsoluteLayout private int mTouchHighlightX; private int mTouchHighlightY; // Basically this proxy is used to tell the Video to update layer tree at // SetBaseLayer time and to pause when WebView paused. private HTML5VideoViewProxy mHTML5VideoViewProxy; /* * Private message ids */ Loading Loading @@ -1184,6 +1189,7 @@ public class WebView extends AbsoluteLayout // Initially use a size of two, since the user is likely to only hold // down two keys at a time (shift + another key) mKeysPressed = new Vector<Integer>(2); mHTML5VideoViewProxy = null ; } /** Loading Loading @@ -2900,6 +2906,11 @@ public class WebView extends AbsoluteLayout if (!mIsPaused) { mIsPaused = true; mWebViewCore.sendMessage(EventHub.ON_PAUSE); // We want to pause the current playing video when switching out // from the current WebView/tab. if (mHTML5VideoViewProxy != null) { mHTML5VideoViewProxy.pauseAndDispatch(); } } } Loading Loading @@ -4034,6 +4045,9 @@ public class WebView extends AbsoluteLayout if (mNativeClass == 0) return; nativeSetBaseLayer(layer, invalRegion, showVisualIndicator); if (mHTML5VideoViewProxy != null) { mHTML5VideoViewProxy.setBaseLayer(layer); } } private void onZoomAnimationStart() { Loading Loading @@ -8512,6 +8526,15 @@ public class WebView extends AbsoluteLayout nativeDraw(canvas, 0, 0, false); } /** * Enable the communication b/t the webView and VideoViewProxy * * @hide only used by the Browser */ public void setHTML5VideoViewProxy(HTML5VideoViewProxy proxy) { mHTML5VideoViewProxy = proxy; } /** * Enable expanded tiles bound for smoother scrolling. * Loading Loading
core/java/android/webkit/HTML5VideoView.java 0 → 100644 +211 −0 Original line number Diff line number Diff line package android.webkit; import android.graphics.SurfaceTexture; import android.media.MediaPlayer; import android.util.Log; import android.webkit.HTML5VideoViewProxy; import android.widget.MediaController; import android.opengl.GLES20; import java.io.IOException; import java.util.Map; /** * @hide This is only used by the browser */ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{ // Due to the fact that SurfaceTexture consume a lot of memory, we make it // as static. m_textureNames is the texture bound with this SurfaceTexture. private static SurfaceTexture mSurfaceTexture = null; private static int[] mTextureNames; // Only when the video is prepared, we render using SurfaceTexture. // This in fact is used to avoid showing the obsolete content when // switching videos. private static boolean mReadyToUseSurfTex = false; // For handling the seekTo before prepared, we need to know whether or not // the video is prepared. Therefore, we differentiate the state between // prepared and not prepared. // When the video is not prepared, we will have to save the seekTo time, // and use it when prepared to play. private static final int STATE_NOTPREPARED = 0; private static final int STATE_PREPARED = 1; // We only need state for handling seekTo private int mCurrentState; // Basically for calling back the OnPrepared in the proxy private HTML5VideoViewProxy mProxy; // Save the seek time when not prepared. This can happen when switching // video besides initial load. private int mSaveSeekTime; // This is used to find the VideoLayer on the native side. private int mVideoLayerId; // Every video will have one MediaPlayer. Given the fact we only have one // SurfaceTexture, there is only one MediaPlayer in action. Every time we // switch videos, a new instance of MediaPlayer will be created in reset(). private MediaPlayer mPlayer; private static HTML5VideoView mInstance = new HTML5VideoView(); // Video control FUNCTIONS: public void start() { if (mCurrentState == STATE_PREPARED) { mPlayer.start(); mReadyToUseSurfTex = true; } } public void pause() { mPlayer.pause(); } public int getDuration() { return mPlayer.getDuration(); } public int getCurrentPosition() { return mPlayer.getCurrentPosition(); } public void seekTo(int pos) { if (mCurrentState == STATE_PREPARED) mPlayer.seekTo(pos); else mSaveSeekTime = pos; } public boolean isPlaying() { return mPlayer.isPlaying(); } public void release() { mPlayer.release(); } public void stopPlayback() { mPlayer.stop(); } private void reset(int videoLayerId) { mPlayer = new MediaPlayer(); mCurrentState = STATE_NOTPREPARED; mProxy = null; mVideoLayerId = videoLayerId; mReadyToUseSurfTex = false; } public static HTML5VideoView getInstance(int videoLayerId) { // Every time we switch between the videos, a new MediaPlayer will be // created. Make sure we call the m_player.release() when it is done. mInstance.reset(videoLayerId); return mInstance; } private HTML5VideoView() { // This is a singleton across WebViews (i.e. Tabs). // HTML5VideoViewProxy will reset the internal state every time a new // video start. } public void setMediaController(MediaController m) { this.setMediaController(m); } public void setVideoURI(String uri, Map<String, String> headers) { // When switching players, surface texture will be reused. mPlayer.setTexture(getSurfaceTextureInstance()); // When there is exception, we could just bail out silently. // No Video will be played though. Write the stack for debug try { mPlayer.setDataSource(uri, headers); mPlayer.prepareAsync(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } // TODO [FULL SCREEN SUPPORT] // Listeners setup FUNCTIONS: public void setOnCompletionListener(HTML5VideoViewProxy proxy) { mPlayer.setOnCompletionListener(proxy); } public void setOnErrorListener(HTML5VideoViewProxy proxy) { mPlayer.setOnErrorListener(proxy); } public void setOnPreparedListener(HTML5VideoViewProxy proxy) { mProxy = proxy; mPlayer.setOnPreparedListener(this); } // Inline Video specific FUNCTIONS: public SurfaceTexture getSurfaceTexture() { return mSurfaceTexture; } public void deleteSurfaceTexture() { mSurfaceTexture = null; return; } // SurfaceTexture is a singleton here , too private SurfaceTexture getSurfaceTextureInstance() { // Create the surface texture. if (mSurfaceTexture == null) { mTextureNames = new int[1]; GLES20.glGenTextures(1, mTextureNames, 0); mSurfaceTexture = new SurfaceTexture(mTextureNames[0]); } return mSurfaceTexture; } public int getTextureName() { return mTextureNames[0]; } public int getVideoLayerId() { return mVideoLayerId; } public boolean getReadyToUseSurfTex() { return mReadyToUseSurfTex; } public void setFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener l) { mSurfaceTexture.setOnFrameAvailableListener(l); } @Override public void onPrepared(MediaPlayer mp) { mCurrentState = STATE_PREPARED; seekTo(mSaveSeekTime); if (mProxy != null) mProxy.onPrepared(mp); } // Pause the play and update the play/pause button public void pauseAndDispatch(HTML5VideoViewProxy proxy) { if (isPlaying()) { pause(); if (proxy != null) { proxy.dispatchOnPaused(); } } mReadyToUseSurfTex = false; } }
core/java/android/webkit/HTML5VideoViewProxy.java +147 −99 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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"; Loading Loading @@ -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 Loading @@ -122,67 +124,76 @@ 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(); } if (isVideoSelfEnded) mCurrentProxy.dispatchOnEnded(); else mCurrentProxy.dispatchOnPaused(); 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; // Re enable plugin views. mCurrentProxy.getWebView().getViewManager().showAll(); // TODO: [FULL SCREEN SUPPORT] isVideoSelfEnded = false; mCurrentProxy = null; mLayout.removeView(mVideoView); mVideoView = null; if (mProgressView != null) { mLayout.removeView(mProgressView); mProgressView = null; // 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(); } mLayout = null; } }; public static void play(String url, int time, HTML5VideoViewProxy proxy, WebChromeClient client) { if (mCurrentProxy == proxy) { if (!mVideoView.isPlaying()) { mVideoView.start(); } return; } if (mCurrentProxy != null) { // Some other video is already playing. Notify the caller that its playback ended. proxy.dispatchOnEnded(); return; // 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, 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(); } // 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); 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())); // TODO: [FULL SCREEN SUPPORT] boolean isPrivate = mCurrentProxy.getWebView().isPrivateBrowsingEnabled(); String cookieValue = CookieManager.getInstance().getCookie(url, isPrivate); Loading @@ -194,59 +205,68 @@ class HTML5VideoViewProxy extends Handler headers.put(HIDE_URL_LOGS, "true"); } 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); } mLayout.setVisibility(View.VISIBLE); mHTML5VideoView.setVideoURI(url, headers); mHTML5VideoView.setOnCompletionListener(proxy); mHTML5VideoView.setOnPreparedListener(proxy); mHTML5VideoView.setOnErrorListener(proxy); mHTML5VideoView.setFrameAvailableListener(proxy); mHTML5VideoView.seekTo(time); 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(); } 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(); } } 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; } } Loading Loading @@ -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) { Loading @@ -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; } Loading @@ -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) { Loading Loading @@ -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 Loading Loading @@ -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; } Loading @@ -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); } Loading Loading @@ -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. Loading @@ -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); }
core/java/android/webkit/WebView.java +23 −0 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.graphics.SurfaceTexture; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.net.Proxy; Loading Loading @@ -608,6 +609,10 @@ public class WebView extends AbsoluteLayout private int mTouchHighlightX; private int mTouchHighlightY; // Basically this proxy is used to tell the Video to update layer tree at // SetBaseLayer time and to pause when WebView paused. private HTML5VideoViewProxy mHTML5VideoViewProxy; /* * Private message ids */ Loading Loading @@ -1184,6 +1189,7 @@ public class WebView extends AbsoluteLayout // Initially use a size of two, since the user is likely to only hold // down two keys at a time (shift + another key) mKeysPressed = new Vector<Integer>(2); mHTML5VideoViewProxy = null ; } /** Loading Loading @@ -2900,6 +2906,11 @@ public class WebView extends AbsoluteLayout if (!mIsPaused) { mIsPaused = true; mWebViewCore.sendMessage(EventHub.ON_PAUSE); // We want to pause the current playing video when switching out // from the current WebView/tab. if (mHTML5VideoViewProxy != null) { mHTML5VideoViewProxy.pauseAndDispatch(); } } } Loading Loading @@ -4034,6 +4045,9 @@ public class WebView extends AbsoluteLayout if (mNativeClass == 0) return; nativeSetBaseLayer(layer, invalRegion, showVisualIndicator); if (mHTML5VideoViewProxy != null) { mHTML5VideoViewProxy.setBaseLayer(layer); } } private void onZoomAnimationStart() { Loading Loading @@ -8512,6 +8526,15 @@ public class WebView extends AbsoluteLayout nativeDraw(canvas, 0, 0, false); } /** * Enable the communication b/t the webView and VideoViewProxy * * @hide only used by the Browser */ public void setHTML5VideoViewProxy(HTML5VideoViewProxy proxy) { mHTML5VideoViewProxy = proxy; } /** * Enable expanded tiles bound for smoother scrolling. * Loading