Loading core/java/android/webkit/HTML5VideoViewProxy.java +140 −45 Original line number Original line Diff line number Diff line Loading @@ -17,49 +17,148 @@ package android.webkit; package android.webkit; import android.content.Context; import android.content.Context; import android.media.MediaPlayer; import android.media.MediaPlayer.OnPreparedListener; import android.net.Uri; import android.net.Uri; import android.os.Bundle; import android.os.Bundle; import android.os.Handler; import android.os.Handler; import android.os.Looper; import android.os.Looper; import android.os.Message; import android.os.Message; import android.util.Log; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.View; import android.view.ViewGroup; import android.webkit.ViewManager.ChildView; import android.webkit.ViewManager.ChildView; import android.widget.AbsoluteLayout; import android.widget.MediaController; import android.widget.MediaController; import android.widget.VideoView; import android.widget.VideoView; import java.util.HashMap; import java.util.HashMap; /** /** * <p>A View that displays Videos. Instances of this class * <p>Proxy for HTML5 video views. * are created on the WebCore thread. However, their code * executes on the UI thread. Right now there is only one * such view for fullscreen video rendering. * */ */ class HTML5VideoViewProxy extends Handler { class HTML5VideoViewProxy extends Handler { // Logging tag. // Logging tag. private static final String LOGTAG = "HTML5VideoViewProxy"; private static final String LOGTAG = "HTML5VideoViewProxy"; // Message Ids // Message Ids for WebCore thread -> UI thread communication. private static final int INIT = 100; private static final int INIT = 100; private static final int PLAY = 101; private static final int PLAY = 101; // The singleton instance. // The WebView instance that created this view. private static HTML5VideoViewProxy sInstance; private WebView mWebView; // The context object used to initialize the VideoView and the // The ChildView instance used by the ViewManager. // MediaController. private ChildView mChildView; private Context mContext; // The VideoView instance. Note that we could // also access this via mChildView.mView but it would // always require cast, so it is more convenient to store // it here as well. private HTML5VideoView mVideoView; // A VideoView subclass that responds to double-tap // events by going fullscreen. class HTML5VideoView extends VideoView { // Used to save the layout parameters if the view // is changed to fullscreen. private AbsoluteLayout.LayoutParams mEmbeddedLayoutParams; // Flag that denotes whether the view is fullscreen or not. private boolean mIsFullscreen; // Used to save the current playback position when // transitioning to/from fullscreen. private int mPlaybackPosition; // The callback object passed to the host application. This callback // is invoked when the host application dismisses our VideoView // (e.g. the user presses the back key). private WebChromeClient.CustomViewCallback mCallback = new WebChromeClient.CustomViewCallback() { public void onCustomViewHidden() { playEmbedded(); } }; // The OnPreparedListener, used to automatically resume // playback when transitioning to/from fullscreen. private MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() { public void onPrepared(MediaPlayer mp) { resumePlayback(); } }; HTML5VideoView(Context context) { super(context); } void savePlaybackPosition() { if (isPlaying()) { mPlaybackPosition = getCurrentPosition(); } } void resumePlayback() { seekTo(mPlaybackPosition); start(); setOnPreparedListener(null); } void playEmbedded() { // Attach to the WebView. mChildView.attachViewOnUIThread(mEmbeddedLayoutParams); // Make sure we're visible setVisibility(View.VISIBLE); // Set the onPrepared listener so we start // playing when the video view is reattached // and its surface is recreated. setOnPreparedListener(mPreparedListener); mIsFullscreen = false; } void playFullScreen() { WebChromeClient client = mWebView.getWebChromeClient(); if (client == null) { return; } // Save the current layout params. mEmbeddedLayoutParams = (AbsoluteLayout.LayoutParams) getLayoutParams(); // Detach from the WebView. mChildView.removeViewOnUIThread(); // Attach to the browser UI. client.onShowCustomView(this, mCallback); // Set the onPrepared listener so we start // playing when after the video view is reattached // and its surface is recreated. setOnPreparedListener(mPreparedListener); mIsFullscreen = true; } @Override public boolean onTouchEvent(MotionEvent ev) { // TODO: implement properly (i.e. detect double tap) if (mIsFullscreen || !isPlaying()) { return super.onTouchEvent(ev); } playFullScreen(); return true; } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); savePlaybackPosition(); } } /** /** * Private constructor. * Private constructor. * @param context is the application context. * @param context is the application context. */ */ private HTML5VideoViewProxy(Context context) { private HTML5VideoViewProxy(WebView webView) { // This handler is for the main (UI) thread. // This handler is for the main (UI) thread. super(Looper.getMainLooper()); super(Looper.getMainLooper()); // Save the context object. // Save the WebView object. mContext = context; mWebView = webView; } } @Override @Override Loading @@ -67,22 +166,23 @@ class HTML5VideoViewProxy extends Handler { // This executes on the UI thread. // This executes on the UI thread. switch (msg.what) { switch (msg.what) { case INIT: case INIT: ChildView child = (ChildView) msg.obj; // Create the video view and set a default controller. // Create the video view and set a default controller. VideoView v = new VideoView(mContext); mVideoView = new HTML5VideoView(mWebView.getContext()); // This is needed because otherwise there will be a black square // This is needed because otherwise there will be a black square // stuck on the screen. // stuck on the screen. v.setWillNotDraw(false); mVideoView.setWillNotDraw(false); v.setMediaController(new MediaController(mContext)); mVideoView.setMediaController(new MediaController(mWebView.getContext())); child.mView = v; mChildView.mView = mVideoView; break; break; case PLAY: case PLAY: if (mVideoView == null) { return; } HashMap<String, Object> map = HashMap<String, Object> map = (HashMap<String, Object>) msg.obj; (HashMap<String, Object>) msg.obj; String url = (String) map.get("url"); String url = (String) map.get("url"); VideoView view = (VideoView) map.get("view"); mVideoView.setVideoURI(Uri.parse(url)); view.setVideoURI(Uri.parse(url)); mVideoView.start(); view.start(); break; break; } } } } Loading @@ -92,46 +192,41 @@ class HTML5VideoViewProxy extends Handler { * @param url is the URL of the video stream. * @param url is the URL of the video stream. * @param webview is the WebViewCore that is requesting the playback. * @param webview is the WebViewCore that is requesting the playback. */ */ public void play(String url, ChildView child) { public void play(String url) { // We need to know the webview that is requesting the playback. // We need to know the webview that is requesting the playback. Message message = obtainMessage(PLAY); Message message = obtainMessage(PLAY); HashMap<String, Object> map = new HashMap(); HashMap<String, Object> map = new HashMap(); map.put("url", url); map.put("url", url); map.put("view", child.mView); message.obj = map; message.obj = map; sendMessage(message); sendMessage(message); } } public ChildView createView(WebViewCore core) { public void createView() { WebView w = core.getWebView(); mChildView = mWebView.mViewManager.createView(); if (w == null) { sendMessage(obtainMessage(INIT)); return null; } ChildView child = w.mViewManager.createView(); sendMessage(obtainMessage(INIT, child)); return child; } } public void attachView(ChildView child, int x, int y, int width, public void attachView(int x, int y, int width, int height) { int height) { if (mChildView == null) { child.attachView(x, y, width, height); return; } mChildView.attachView(x, y, width, height); } } public void removeView(ChildView child) { public void removeView() { child.removeView(); if (mChildView == null) { return; } mChildView.removeView(); } } /** /** * The factory for HTML5VideoViewProxy instances. Right now, * The factory for HTML5VideoViewProxy instances. * it only produces a singleton. * @param webViewCore is the WebViewCore that is requesting the proxy. * @param webViewCore is the WebViewCore that is requesting the proxy. * * * @return the HTML5VideoViewProxy singleton. * @return a new HTML5VideoViewProxy object. */ */ public static HTML5VideoViewProxy getInstance(WebViewCore webViewCore) { public static HTML5VideoViewProxy getInstance(WebViewCore webViewCore) { if (sInstance == null) { return new HTML5VideoViewProxy(webViewCore.getWebView()); sInstance = new HTML5VideoViewProxy(webViewCore.getWebView().getContext()); } return sInstance; } } } } core/java/android/webkit/ViewManager.java +12 −4 Original line number Original line Diff line number Diff line Loading @@ -61,24 +61,32 @@ class ViewManager { if (mView.getParent() != null) { if (mView.getParent() != null) { mView.setLayoutParams(lp); mView.setLayoutParams(lp); } else { } else { mWebView.addView(mView, lp); attachViewOnUIThread(lp); mChildren.add(ChildView.this); } } } } }); }); } } void attachViewOnUIThread(AbsoluteLayout.LayoutParams lp) { mWebView.addView(mView, lp); mChildren.add(this); } void removeView() { void removeView() { if (mView == null) { if (mView == null) { return; return; } } mWebView.mPrivateHandler.post(new Runnable() { mWebView.mPrivateHandler.post(new Runnable() { public void run() { public void run() { mWebView.removeView(mView); removeViewOnUIThread(); mChildren.remove(ChildView.this); } } }); }); } } void removeViewOnUIThread() { mWebView.removeView(mView); mChildren.remove(this); } } } ViewManager(WebView w) { ViewManager(WebView w) { Loading core/java/android/webkit/WebChromeClient.java +17 −1 Original line number Original line Diff line number Diff line Loading @@ -44,14 +44,30 @@ public class WebChromeClient { */ */ public void onReceivedIcon(WebView view, Bitmap icon) {} public void onReceivedIcon(WebView view, Bitmap icon) {} /** * A callback interface used by the host application to notify * the current page that its custom view has been dismissed. * * @hide pending council approval */ public interface CustomViewCallback { /** * Invoked when the host application dismisses the * custom view. */ public void onCustomViewHidden(); } /** /** * Notify the host application that the current page would * Notify the host application that the current page would * like to show a custom View. * like to show a custom View. * @param view is the View object to be shown. * @param view is the View object to be shown. * @param callback is the callback to be invoked if and when the view * is dismissed. * * * @hide pending council approval * @hide pending council approval */ */ public void onShowCustomView(View view) {} public void onShowCustomView(View view, CustomViewCallback callback) {}; /** /** * Notify the host application that the current page would * Notify the host application that the current page would Loading Loading
core/java/android/webkit/HTML5VideoViewProxy.java +140 −45 Original line number Original line Diff line number Diff line Loading @@ -17,49 +17,148 @@ package android.webkit; package android.webkit; import android.content.Context; import android.content.Context; import android.media.MediaPlayer; import android.media.MediaPlayer.OnPreparedListener; import android.net.Uri; import android.net.Uri; import android.os.Bundle; import android.os.Bundle; import android.os.Handler; import android.os.Handler; import android.os.Looper; import android.os.Looper; import android.os.Message; import android.os.Message; import android.util.Log; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.View; import android.view.ViewGroup; import android.webkit.ViewManager.ChildView; import android.webkit.ViewManager.ChildView; import android.widget.AbsoluteLayout; import android.widget.MediaController; import android.widget.MediaController; import android.widget.VideoView; import android.widget.VideoView; import java.util.HashMap; import java.util.HashMap; /** /** * <p>A View that displays Videos. Instances of this class * <p>Proxy for HTML5 video views. * are created on the WebCore thread. However, their code * executes on the UI thread. Right now there is only one * such view for fullscreen video rendering. * */ */ class HTML5VideoViewProxy extends Handler { class HTML5VideoViewProxy extends Handler { // Logging tag. // Logging tag. private static final String LOGTAG = "HTML5VideoViewProxy"; private static final String LOGTAG = "HTML5VideoViewProxy"; // Message Ids // Message Ids for WebCore thread -> UI thread communication. private static final int INIT = 100; private static final int INIT = 100; private static final int PLAY = 101; private static final int PLAY = 101; // The singleton instance. // The WebView instance that created this view. private static HTML5VideoViewProxy sInstance; private WebView mWebView; // The context object used to initialize the VideoView and the // The ChildView instance used by the ViewManager. // MediaController. private ChildView mChildView; private Context mContext; // The VideoView instance. Note that we could // also access this via mChildView.mView but it would // always require cast, so it is more convenient to store // it here as well. private HTML5VideoView mVideoView; // A VideoView subclass that responds to double-tap // events by going fullscreen. class HTML5VideoView extends VideoView { // Used to save the layout parameters if the view // is changed to fullscreen. private AbsoluteLayout.LayoutParams mEmbeddedLayoutParams; // Flag that denotes whether the view is fullscreen or not. private boolean mIsFullscreen; // Used to save the current playback position when // transitioning to/from fullscreen. private int mPlaybackPosition; // The callback object passed to the host application. This callback // is invoked when the host application dismisses our VideoView // (e.g. the user presses the back key). private WebChromeClient.CustomViewCallback mCallback = new WebChromeClient.CustomViewCallback() { public void onCustomViewHidden() { playEmbedded(); } }; // The OnPreparedListener, used to automatically resume // playback when transitioning to/from fullscreen. private MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() { public void onPrepared(MediaPlayer mp) { resumePlayback(); } }; HTML5VideoView(Context context) { super(context); } void savePlaybackPosition() { if (isPlaying()) { mPlaybackPosition = getCurrentPosition(); } } void resumePlayback() { seekTo(mPlaybackPosition); start(); setOnPreparedListener(null); } void playEmbedded() { // Attach to the WebView. mChildView.attachViewOnUIThread(mEmbeddedLayoutParams); // Make sure we're visible setVisibility(View.VISIBLE); // Set the onPrepared listener so we start // playing when the video view is reattached // and its surface is recreated. setOnPreparedListener(mPreparedListener); mIsFullscreen = false; } void playFullScreen() { WebChromeClient client = mWebView.getWebChromeClient(); if (client == null) { return; } // Save the current layout params. mEmbeddedLayoutParams = (AbsoluteLayout.LayoutParams) getLayoutParams(); // Detach from the WebView. mChildView.removeViewOnUIThread(); // Attach to the browser UI. client.onShowCustomView(this, mCallback); // Set the onPrepared listener so we start // playing when after the video view is reattached // and its surface is recreated. setOnPreparedListener(mPreparedListener); mIsFullscreen = true; } @Override public boolean onTouchEvent(MotionEvent ev) { // TODO: implement properly (i.e. detect double tap) if (mIsFullscreen || !isPlaying()) { return super.onTouchEvent(ev); } playFullScreen(); return true; } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); savePlaybackPosition(); } } /** /** * Private constructor. * Private constructor. * @param context is the application context. * @param context is the application context. */ */ private HTML5VideoViewProxy(Context context) { private HTML5VideoViewProxy(WebView webView) { // This handler is for the main (UI) thread. // This handler is for the main (UI) thread. super(Looper.getMainLooper()); super(Looper.getMainLooper()); // Save the context object. // Save the WebView object. mContext = context; mWebView = webView; } } @Override @Override Loading @@ -67,22 +166,23 @@ class HTML5VideoViewProxy extends Handler { // This executes on the UI thread. // This executes on the UI thread. switch (msg.what) { switch (msg.what) { case INIT: case INIT: ChildView child = (ChildView) msg.obj; // Create the video view and set a default controller. // Create the video view and set a default controller. VideoView v = new VideoView(mContext); mVideoView = new HTML5VideoView(mWebView.getContext()); // This is needed because otherwise there will be a black square // This is needed because otherwise there will be a black square // stuck on the screen. // stuck on the screen. v.setWillNotDraw(false); mVideoView.setWillNotDraw(false); v.setMediaController(new MediaController(mContext)); mVideoView.setMediaController(new MediaController(mWebView.getContext())); child.mView = v; mChildView.mView = mVideoView; break; break; case PLAY: case PLAY: if (mVideoView == null) { return; } HashMap<String, Object> map = HashMap<String, Object> map = (HashMap<String, Object>) msg.obj; (HashMap<String, Object>) msg.obj; String url = (String) map.get("url"); String url = (String) map.get("url"); VideoView view = (VideoView) map.get("view"); mVideoView.setVideoURI(Uri.parse(url)); view.setVideoURI(Uri.parse(url)); mVideoView.start(); view.start(); break; break; } } } } Loading @@ -92,46 +192,41 @@ class HTML5VideoViewProxy extends Handler { * @param url is the URL of the video stream. * @param url is the URL of the video stream. * @param webview is the WebViewCore that is requesting the playback. * @param webview is the WebViewCore that is requesting the playback. */ */ public void play(String url, ChildView child) { public void play(String url) { // We need to know the webview that is requesting the playback. // We need to know the webview that is requesting the playback. Message message = obtainMessage(PLAY); Message message = obtainMessage(PLAY); HashMap<String, Object> map = new HashMap(); HashMap<String, Object> map = new HashMap(); map.put("url", url); map.put("url", url); map.put("view", child.mView); message.obj = map; message.obj = map; sendMessage(message); sendMessage(message); } } public ChildView createView(WebViewCore core) { public void createView() { WebView w = core.getWebView(); mChildView = mWebView.mViewManager.createView(); if (w == null) { sendMessage(obtainMessage(INIT)); return null; } ChildView child = w.mViewManager.createView(); sendMessage(obtainMessage(INIT, child)); return child; } } public void attachView(ChildView child, int x, int y, int width, public void attachView(int x, int y, int width, int height) { int height) { if (mChildView == null) { child.attachView(x, y, width, height); return; } mChildView.attachView(x, y, width, height); } } public void removeView(ChildView child) { public void removeView() { child.removeView(); if (mChildView == null) { return; } mChildView.removeView(); } } /** /** * The factory for HTML5VideoViewProxy instances. Right now, * The factory for HTML5VideoViewProxy instances. * it only produces a singleton. * @param webViewCore is the WebViewCore that is requesting the proxy. * @param webViewCore is the WebViewCore that is requesting the proxy. * * * @return the HTML5VideoViewProxy singleton. * @return a new HTML5VideoViewProxy object. */ */ public static HTML5VideoViewProxy getInstance(WebViewCore webViewCore) { public static HTML5VideoViewProxy getInstance(WebViewCore webViewCore) { if (sInstance == null) { return new HTML5VideoViewProxy(webViewCore.getWebView()); sInstance = new HTML5VideoViewProxy(webViewCore.getWebView().getContext()); } return sInstance; } } } }
core/java/android/webkit/ViewManager.java +12 −4 Original line number Original line Diff line number Diff line Loading @@ -61,24 +61,32 @@ class ViewManager { if (mView.getParent() != null) { if (mView.getParent() != null) { mView.setLayoutParams(lp); mView.setLayoutParams(lp); } else { } else { mWebView.addView(mView, lp); attachViewOnUIThread(lp); mChildren.add(ChildView.this); } } } } }); }); } } void attachViewOnUIThread(AbsoluteLayout.LayoutParams lp) { mWebView.addView(mView, lp); mChildren.add(this); } void removeView() { void removeView() { if (mView == null) { if (mView == null) { return; return; } } mWebView.mPrivateHandler.post(new Runnable() { mWebView.mPrivateHandler.post(new Runnable() { public void run() { public void run() { mWebView.removeView(mView); removeViewOnUIThread(); mChildren.remove(ChildView.this); } } }); }); } } void removeViewOnUIThread() { mWebView.removeView(mView); mChildren.remove(this); } } } ViewManager(WebView w) { ViewManager(WebView w) { Loading
core/java/android/webkit/WebChromeClient.java +17 −1 Original line number Original line Diff line number Diff line Loading @@ -44,14 +44,30 @@ public class WebChromeClient { */ */ public void onReceivedIcon(WebView view, Bitmap icon) {} public void onReceivedIcon(WebView view, Bitmap icon) {} /** * A callback interface used by the host application to notify * the current page that its custom view has been dismissed. * * @hide pending council approval */ public interface CustomViewCallback { /** * Invoked when the host application dismisses the * custom view. */ public void onCustomViewHidden(); } /** /** * Notify the host application that the current page would * Notify the host application that the current page would * like to show a custom View. * like to show a custom View. * @param view is the View object to be shown. * @param view is the View object to be shown. * @param callback is the callback to be invoked if and when the view * is dismissed. * * * @hide pending council approval * @hide pending council approval */ */ public void onShowCustomView(View view) {} public void onShowCustomView(View view, CustomViewCallback callback) {}; /** /** * Notify the host application that the current page would * Notify the host application that the current page would Loading