diff --git a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java index 849b7ee..e785fb0 100644 --- a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java +++ b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java @@ -6,12 +6,17 @@ import android.app.DownloadManager; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; +import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.os.Environment; +import android.support.annotation.RequiresApi; import android.text.TextUtils; +import android.view.Gravity; import android.view.View; +import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; +import android.view.WindowManager; import android.webkit.ConsoleMessage; import android.webkit.CookieManager; import android.webkit.DownloadListener; @@ -24,6 +29,7 @@ import android.webkit.WebResourceRequest; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; +import android.widget.FrameLayout; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.LifecycleEventListener; @@ -106,6 +112,9 @@ public class RNCWebViewManager extends SimpleViewManager { protected static final String BLANK_URL = "about:blank"; protected WebViewConfig mWebViewConfig; + protected RNCWebChromeClient mWebChromeClient = null; + protected boolean mAllowsFullscreenVideo = false; + public RNCWebViewManager() { mWebViewConfig = new WebViewConfig() { public void configWebView(WebView webView) { @@ -137,59 +146,7 @@ public class RNCWebViewManager extends SimpleViewManager { @TargetApi(Build.VERSION_CODES.LOLLIPOP) protected WebView createViewInstance(ThemedReactContext reactContext) { RNCWebView webView = createRNCWebViewInstance(reactContext); - webView.setWebChromeClient(new WebChromeClient() { - @Override - public boolean onConsoleMessage(ConsoleMessage message) { - if (ReactBuildConfig.DEBUG) { - return super.onConsoleMessage(message); - } - // Ignore console logs in non debug builds. - return true; - } - - - @Override - public void onProgressChanged(WebView webView, int newProgress) { - super.onProgressChanged(webView, newProgress); - WritableMap event = Arguments.createMap(); - event.putDouble("target", webView.getId()); - event.putString("title", webView.getTitle()); - event.putBoolean("canGoBack", webView.canGoBack()); - event.putBoolean("canGoForward", webView.canGoForward()); - event.putDouble("progress", (float) newProgress / 100); - dispatchEvent( - webView, - new TopLoadingProgressEvent( - webView.getId(), - event)); - } - - @Override - public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { - callback.invoke(origin, true, false); - } - - protected void openFileChooser(ValueCallback filePathCallback, String acceptType) { - getModule(reactContext).startPhotoPickerIntent(filePathCallback, acceptType); - } - - protected void openFileChooser(ValueCallback filePathCallback) { - getModule(reactContext).startPhotoPickerIntent(filePathCallback, ""); - } - - protected void openFileChooser(ValueCallback filePathCallback, String acceptType, String capture) { - getModule(reactContext).startPhotoPickerIntent(filePathCallback, acceptType); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - @Override - public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { - String[] acceptTypes = fileChooserParams.getAcceptTypes(); - boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE; - Intent intent = fileChooserParams.createIntent(); - return getModule(reactContext).startPhotoPickerIntent(filePathCallback, intent, acceptTypes, allowMultiple); - } - }); + setupWebChromeClient(reactContext, webView); reactContext.addLifecycleEventListener(webView); mWebViewConfig.configWebView(webView); WebSettings settings = webView.getSettings(); @@ -454,6 +411,14 @@ public class RNCWebViewManager extends SimpleViewManager { } } + @ReactProp(name = "allowsFullscreenVideo") + public void setAllowsFullscreenVideo( + WebView view, + @Nullable Boolean allowsFullscreenVideo) { + mAllowsFullscreenVideo = allowsFullscreenVideo != null && allowsFullscreenVideo; + setupWebChromeClient((ReactContext)view.getContext(), view); + } + @ReactProp(name = "allowFileAccess") public void setAllowFileAccess( WebView view, @@ -554,10 +519,67 @@ public class RNCWebViewManager extends SimpleViewManager { ((RNCWebView) webView).cleanupCallbacksAndDestroy(); } - public RNCWebViewModule getModule(ReactContext reactContext) { + public static RNCWebViewModule getModule(ReactContext reactContext) { return reactContext.getNativeModule(RNCWebViewModule.class); } + protected void setupWebChromeClient(ReactContext reactContext, WebView webView) { + if (mAllowsFullscreenVideo) { + mWebChromeClient = new RNCWebChromeClient(reactContext, webView) { + @Override + public void onShowCustomView(View view, CustomViewCallback callback) { + if (mVideoView != null) { + callback.onCustomViewHidden(); + return; + } + + mVideoView = view; + mCustomViewCallback = callback; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY); + mReactContext.getCurrentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + } + + mVideoView.setBackgroundColor(Color.BLACK); + getRootView().addView(mVideoView, FULLSCREEN_LAYOUT_PARAMS); + mWebView.setVisibility(View.GONE); + + mReactContext.addLifecycleEventListener(this); + } + + @Override + public void onHideCustomView() { + if (mVideoView == null) { + return; + } + + mVideoView.setVisibility(View.GONE); + getRootView().removeView(mVideoView); + mCustomViewCallback.onCustomViewHidden(); + + mVideoView = null; + mCustomViewCallback = null; + + mWebView.setVisibility(View.VISIBLE); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + mReactContext.getCurrentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + } + + mReactContext.removeLifecycleEventListener(this); + } + }; + webView.setWebChromeClient(mWebChromeClient); + } else { + if (mWebChromeClient != null) { + mWebChromeClient.onHideCustomView(); + } + mWebChromeClient = new RNCWebChromeClient(reactContext, webView); + webView.setWebChromeClient(mWebChromeClient); + } + } + protected static class RNCWebViewClient extends WebViewClient { protected boolean mLastLoadFailed = false; @@ -655,6 +677,99 @@ public class RNCWebViewManager extends SimpleViewManager { } } + protected static class RNCWebChromeClient extends WebChromeClient implements LifecycleEventListener { + protected static final FrameLayout.LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER); + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + protected static final int FULLSCREEN_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_FULLSCREEN | + View.SYSTEM_UI_FLAG_IMMERSIVE | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + + protected ReactContext mReactContext; + protected View mWebView; + + protected View mVideoView; + protected WebChromeClient.CustomViewCallback mCustomViewCallback; + + public RNCWebChromeClient(ReactContext reactContext, WebView webView) { + this.mReactContext = reactContext; + this.mWebView = webView; + } + + @Override + public boolean onConsoleMessage(ConsoleMessage message) { + if (ReactBuildConfig.DEBUG) { + return super.onConsoleMessage(message); + } + // Ignore console logs in non debug builds. + return true; + } + + @Override + public void onProgressChanged(WebView webView, int newProgress) { + super.onProgressChanged(webView, newProgress); + WritableMap event = Arguments.createMap(); + event.putDouble("target", webView.getId()); + event.putString("title", webView.getTitle()); + event.putBoolean("canGoBack", webView.canGoBack()); + event.putBoolean("canGoForward", webView.canGoForward()); + event.putDouble("progress", (float) newProgress / 100); + dispatchEvent( + webView, + new TopLoadingProgressEvent( + webView.getId(), + event)); + } + + @Override + public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { + callback.invoke(origin, true, false); + } + + protected void openFileChooser(ValueCallback filePathCallback, String acceptType) { + getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType); + } + + protected void openFileChooser(ValueCallback filePathCallback) { + getModule(mReactContext).startPhotoPickerIntent(filePathCallback, ""); + } + + protected void openFileChooser(ValueCallback filePathCallback, String acceptType, String capture) { + getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { + String[] acceptTypes = fileChooserParams.getAcceptTypes(); + boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE; + Intent intent = fileChooserParams.createIntent(); + return getModule(mReactContext).startPhotoPickerIntent(filePathCallback, intent, acceptTypes, allowMultiple); + } + + @Override + public void onHostResume() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mVideoView != null && mVideoView.getSystemUiVisibility() != FULLSCREEN_SYSTEM_UI_VISIBILITY) { + mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY); + } + } + + @Override + public void onHostPause() { } + + @Override + public void onHostDestroy() { } + + protected ViewGroup getRootView() { + return (ViewGroup) mReactContext.getCurrentActivity().findViewById(android.R.id.content); + } + } + /** * Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order * to call {@link WebView#destroy} on activity destroy event and also to clear the client diff --git a/docs/Reference.md b/docs/Reference.md index 284b8ee..0d426fb 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -30,6 +30,7 @@ This document lays out the current public properties and methods for the React N - [`mixedContentMode`](Reference.md#mixedcontentmode) - [`thirdPartyCookiesEnabled`](Reference.md#thirdpartycookiesenabled) - [`userAgent`](Reference.md#useragent) +- [`allowsFullscreenVideo`](Reference.md#allowsfullscreenvideo) - [`allowsInlineMediaPlayback`](Reference.md#allowsinlinemediaplayback) - [`bounces`](Reference.md#bounces) - [`overScrollMode`](Reference.md#overscrollmode) @@ -591,6 +592,16 @@ Sets the user-agent for the `WebView`. This will only work for iOS if you are us --- +### `allowsFullscreenVideo` + +Boolean that determines whether videos are allowed to be played in fullscreen. The default value is `false`. + +| Type | Required | Platform | +| ---- | -------- | -------- | +| bool | No | Android | + +--- + ### `allowsInlineMediaPlayback` Boolean that determines whether HTML5 videos play inline or use the native full-screen controller. The default value is `false`. diff --git a/src/WebView.android.tsx b/src/WebView.android.tsx index 412bcba..36cf7e2 100644 --- a/src/WebView.android.tsx +++ b/src/WebView.android.tsx @@ -48,6 +48,7 @@ class WebView extends React.Component { javaScriptEnabled: true, thirdPartyCookiesEnabled: true, scalesPageToFit: true, + allowsFullscreenVideo: false, allowFileAccess: false, saveFormDataDisabled: false, cacheEnabled: true,