【问题标题】:Camera on Xamarin WebViewXamarin WebView 上的相机
【发布时间】:2018-11-06 06:58:48
【问题描述】:

我有一个简单的 Xamarin 页面,其中包含一个调用 WebRTC 测试页面的 WebView:

        _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 } };

https://test.webrtc.org/ 页面在同一 Android 模拟器上的 Chrome 上运行良好,但在显示“NotAllowedError”的 WebView 上无法正常运行。

应用程序具有所需的权限。以下代码(使用 Plugin.Permissions)返回 true:

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

怎么了?

谢谢

【问题讨论】:

    标签: android xamarin webview webrtc android-permissions


    【解决方案1】:

    关于NotAllowedError,来自here

    用户已指定不允许当前浏览实例访问设备;或者用户拒绝访问当前会话;或用户已全局拒绝对用户媒体设备的所有访问。


    您需要自定义一个WebView 来覆盖WebChromeClientOnPermissionRequest 方法。

    MyWebView PCL 中的类:

    public class MyWebView: WebView
    {
    }
    

    MyWebViewRendererMyWebClient 类:

    [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,我提供了一个demo供你测试。相机应该适合你。

    【讨论】:

    • 哇!精彩的回应!谢谢!
    • 截至 2020 年 10 月,您的解决方案不适用于我的 Pixel 3。我想知道 Android 发生了什么变化? test.webrtc.org 声称无法获取麦克风或摄像头。
    【解决方案2】:

    Android 的 WebChromeClient 默认为文件选择器提供 Intent。此默认选择器意图提供的内容因 Android 操作系统版本而异。在 Android 6 和 7 上,当您选择图库时,有一个打开相机的选项,但在更高版本的 Android 操作系统上,没有图库,也没有可用的相机选项。

    根据提供给WebChromeClient 的 OnShowFileChooser 方法的 FileChooserParams 上的 Android 文档,CreateIntent() 方法:

    创建将启动文件选择器以进行文件选择的意图。 Intent 支持从设备上可用的简单文件源中选择文件。某些高级来源(例如,实时媒体捕获)可能不受支持,希望支持这些来源或更高级文件操作的应用程序应构建自己的 Intent。

    因此,尽管您的应用需要其他答案中提到的权限(读取和写入外部存储以及相机权限),但如果您想提供相机作为选项,您必须自己构建它。

    对于 Xamarin.Forms,您可以利用 Xamarin.Essentials.MediaPicker API 来避免直接处理 Android Intent、设置 ContentProvider 等。

    这是一个可以添加到自定义 Xamarin.Forms WebViewRenderer 的解决方案(使用来自 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();
            }
        }
    }
    

    还有 CameraFormsWebChromeClient 类:

        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 };
            }
        }
    

    我只是使用原生 Android 对话框进行选择或拍照选择。当然,您可以创建自己的 UI。主要的是您必须从 OnShowFileChooser 方法返回“true”,让 WebChromeClient 知道您将调用 filePathCallback 并提供结果。如果你返回false,你会得到一个本地异常,因为 WebChromeClient 被告知这个方法不会提供结果,所以它提供了它自己的空结果,我们得到一个“重复的 showFileChooser 结果”错误。您还需要保存拍摄的图片,然后将 Android.Net.Uri[] 提供给 filePathCallback.OnReceiveValue 方法。并且如果OnShowFileChooser返回true必须调用这个回调,所以如果用户取消,需要调用filePathCallback.OnReceiveValue(null);

    【讨论】:

      猜你喜欢
      • 2018-07-14
      • 2020-05-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-13
      • 1970-01-01
      • 2020-11-30
      • 1970-01-01
      相关资源
      最近更新 更多