简体   繁体   中英

Camera on Xamarin WebView

I have a simple Xamarin Page with a WebView that calls a WebRTC test page:

        _webView = new WebView
        {
            Source = "https://test.webrtc.org/",
            WidthRequest = 1000,
            HeightRequest = 1000
        };

        var stackLayout = new StackLayout()
        {
            Orientation = StackOrientation.Vertical,
            Padding = new Thickness(5, 20, 5, 10),
            Children = { _webView }
        };

        Content = new StackLayout { Children = { stackLayout } };

The https://test.webrtc.org/ page works fine on Chrome on the same Android Emulator, but don't work on WebView saying "NotAllowedError".

The application have the required permissions. The following code (that use Plugin.Permissions) returns true:

var statusCamera = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Camera);
var statusMicrophone = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Microphone);
return statusCamera == PermissionStatus.Granted && statusMicrophone == PermissionStatus.Granted;

What's wrong?

Thanks

About NotAllowedError , from here :

The user has specified that the current browsing instance is not permitted access to the device; or the user has denied access for the current session; or the user has denied all access to user media devices globally.


You need custom a WebView to override the WebChromeClient 's OnPermissionRequest method.

MyWebView class in PCL:

public class MyWebView: WebView
{
}

MyWebViewRenderer and MyWebClient class:

[assembly: ExportRenderer(typeof(App45.MyWebView), typeof(MyWebViewRenderer))]
namespace App45.Droid
{
    public class MyWebViewRenderer : WebViewRenderer
    {
        Activity mContext;
        public MyWebViewRenderer(Context context) : base(context)
        {
            this.mContext = context as Activity;
        }
            protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
            base.OnElementChanged(e);
            Control.Settings.JavaScriptEnabled = true;
            Control.ClearCache(true);
            Control.SetWebChromeClient(new MyWebClient(mContext));
        }
        public class MyWebClient : WebChromeClient
        {
            Activity mContext;
            public MyWebClient(Activity context) {
                this.mContext = context;
            }
            [TargetApi(Value = 21)]
            public override void OnPermissionRequest(PermissionRequest request)
            {
                mContext.RunOnUiThread(() => {
                        request.Grant(request.GetResources());

                        });

            }
        }

    }

}

Here , I have provided a demo for you to test. The camera should work for you.

Android's WebChromeClient provides an Intent for a file chooser by default. What this default chooser intent offers varies depending on Android OS version. On Android 6 and 7, when you select Gallery, there is an option to open the camera, but on later Android OS versions, there is no Gallery and there is no Camera option available.

As per Android docs on the FileChooserParams, which is provided to the OnShowFileChooser method of the WebChromeClient , the CreateIntent() method:

Creates an intent that would start a file picker for file selection. The Intent supports choosing files from simple file sources available on the device. Some advanced sources ( for example, live media capture ) may not be supported and applications wishing to support these sources or more advanced file operations should build their own Intent.

So although your app will need the permissions noted in other answers (read and write to external storage, and the Camera permission), if you want to offer the camera as an options, you have to build it yourself.

For Xamarin.Forms, you can leverage the Xamarin.Essentials.MediaPicker API to avoid having to deal with Android Intents directly, setting up ContentProviders, etc.

Here is a solution that you can add to a custom Xamarin.Forms WebViewRenderer (using code from MediaPicker docs ):

[assembly: ExportRenderer(typeof(WebView), typeof(MyWebViewRenderer))]
namespace YourAppNameSpace.Droid 
{
    public class MyWebViewRenderer: WebViewRenderer
    {

        public MyWebViewRenderer(Context context) : base(context) { }

        protected override FormsWebChromeClient GetFormsWebChromeClient()
        {
            return new CameraFormsWebChromeClient();
        }
    }
}

And the CameraFormsWebChromeClient class:

    public class CameraFormsWebChromeClient : FormsWebChromeClient
    {
        string _photoPath;
        public override bool OnShowFileChooser(Android.Webkit.WebView webView, Android.Webkit.IValueCallback filePathCallback, FileChooserParams fileChooserParams)
        {

            AlertDialog.Builder alertDialog = new AlertDialog.Builder(MainActivity.Instance);
            alertDialog.SetTitle("Take picture or choose a file");
            alertDialog.SetNeutralButton("Take picture", async (sender, alertArgs) =>
            {
                try
                {
                    var photo = await MediaPicker.CapturePhotoAsync();
                    var uri = await LoadPhotoAsync(photo);
                    filePathCallback.OnReceiveValue(uri);
                }
                catch (System.Exception ex)
                {
                    System.Console.WriteLine($"CapturePhotoAsync THREW: {ex.Message}");
                }
            });
            alertDialog.SetNegativeButton("Choose picture", async (sender, alertArgs) =>
            {
                try
                {
                    var photo = await MediaPicker.PickPhotoAsync();
                    var uri = await LoadPhotoAsync(photo);
                    filePathCallback.OnReceiveValue(uri);
                }
                catch (System.Exception ex)
                {
                    System.Console.WriteLine($"PickPhotoAsync THREW: {ex.Message}");
                }
            });
            alertDialog.SetPositiveButton("Cancel", (sender, alertArgs) =>
            {
                filePathCallback.OnReceiveValue(null);
            });
            Dialog dialog = alertDialog.Create();
            dialog.Show();
            return true;
        }

        async Task<Android.Net.Uri[]> LoadPhotoAsync(FileResult photo)
        {
            // cancelled
            if (photo == null)
            {
                _photoPath = null;
                return null;
            }
            // save the file into local storage
            var newFile = Path.Combine(FileSystem.CacheDirectory, photo.FileName);
            using (var stream = await photo.OpenReadAsync())
            using (var newStream = System.IO.File.OpenWrite(newFile))
                await stream.CopyToAsync(newStream);
            _photoPath = newFile;
            Android.Net.Uri uri = Android.Net.Uri.FromFile(new Java.IO.File(_photoPath));
            return new Android.Net.Uri[] { uri };
        }
    }

I just used the native Android Dialog for the choose or take picture selection. You can create your own UI, of course. The main thing is that you have to return 'true' from the OnShowFileChooser method to let the WebChromeClient know that you will be invoking the filePathCallback and providing a result. If you return false , you will get a native exception since the WebChromeClient was told that this method is not going to provide a result, so it provides it's own null result and we get a "Duplicate showFileChooser result" error. Also you need to save the taken picture, and then provide a Android.Net.Uri[] to the filePathCallback.OnReceiveValue method. And you must call this callback if true is returned from OnShowFileChooser, so if the user cancels, you need to call filePathCallback.OnReceiveValue(null);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM