Xamarin WebView 上的相机

Posted

技术标签:

【中文标题】Xamarin WebView 上的相机【英文标题】:Camera on Xamarin 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;

怎么了?

谢谢

【问题讨论】:

【参考方案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);

【讨论】:

以上是关于Xamarin WebView 上的相机的主要内容,如果未能解决你的问题,请参考以下文章

Xamarin 在 Webview 中使用相机上传图片

Xamarin表单:ios平台上的webview中视频未完全填充

Android 上的 Xamarin WebView 显示多个实例

CSS Id 选择器阻止所有内容在 Android 10 上的 Xamarin.Forms WebView 中显示

如何在 VS 2019 上混淆 xamarin 应用程序

Visual Studio 2015 / Xamarin,调试不工作:执行 MTouch 时出错