Xamarin WebView 上的摄像头
Camera on Xamarin WebView
我有一个简单的 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 上工作正常,但在 WebView 上不工作说 "NotAllowedError".
应用程序具有所需的权限。以下代码(即使用 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;
怎么了?
谢谢
关于NotAllowedError
,来自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.
您需要自定义一个 WebView
来覆盖 WebChromeClient
的 OnPermissionRequest
方法。
MyWebView
class 在 PCL:
public class MyWebView: WebView
{
}
MyWebViewRenderer
和 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,我提供了一个demo给大家测试一下。相机应该适合你。
Android 的 WebChromeClient 默认为文件选择器提供 Intent。此默认选择器意图提供的内容因 Android OS 版本而异。在Android6和7,当你select图库时,有打开相机的选项,但在AndroidOS之后的版本,没有图库,有没有可用的相机选项。
根据 FileChooserParams 上的 Android 文档,它提供给 WebChromeClient 的 OnShowFileChooser 方法,CreateIntent() 方法:
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.
因此,尽管您的应用需要其他答案中提到的权限(读取和写入外部存储,以及摄像头权限),但如果您想提供摄像头作为选项,您必须自己构建它。
对于 Xamarin.Forms,您可以利用 Xamarin.Essentials.MediaPicker API 来避免必须直接处理 Android Intents,设置 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 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 };
}
}
我只是使用本机 Android 对话框来选择或拍照 selection。当然,您可以创建自己的 UI。最主要的是您必须从 OnShowFileChooser 方法 return 'true' 让 WebChromeClient 知道您将调用 filePathCallback 并提供结果。如果你 return false
,你会得到一个本地异常,因为 WebChromeClient 被告知这个方法不会提供结果,所以它提供它自己的空结果,我们得到一个“重复的 showFileChooser 结果“ 错误。另外你需要保存拍摄的照片,然后提供一个Android.Net.Uri[]到filePathCallback.OnReceiveValue方法。如果 true
是从 OnShowFileChooser return 编辑的,则必须调用此回调,因此如果用户取消,则需要调用 filePathCallback.OnReceiveValue(null);
我有一个简单的 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 上工作正常,但在 WebView 上不工作说 "NotAllowedError".
应用程序具有所需的权限。以下代码(即使用 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;
怎么了?
谢谢
关于NotAllowedError
,来自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.
您需要自定义一个 WebView
来覆盖 WebChromeClient
的 OnPermissionRequest
方法。
MyWebView
class 在 PCL:
public class MyWebView: WebView
{
}
MyWebViewRenderer
和 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,我提供了一个demo给大家测试一下。相机应该适合你。
Android 的 WebChromeClient 默认为文件选择器提供 Intent。此默认选择器意图提供的内容因 Android OS 版本而异。在Android6和7,当你select图库时,有打开相机的选项,但在AndroidOS之后的版本,没有图库,有没有可用的相机选项。
根据 FileChooserParams 上的 Android 文档,它提供给 WebChromeClient 的 OnShowFileChooser 方法,CreateIntent() 方法:
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.
因此,尽管您的应用需要其他答案中提到的权限(读取和写入外部存储,以及摄像头权限),但如果您想提供摄像头作为选项,您必须自己构建它。
对于 Xamarin.Forms,您可以利用 Xamarin.Essentials.MediaPicker API 来避免必须直接处理 Android Intents,设置 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 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 };
}
}
我只是使用本机 Android 对话框来选择或拍照 selection。当然,您可以创建自己的 UI。最主要的是您必须从 OnShowFileChooser 方法 return 'true' 让 WebChromeClient 知道您将调用 filePathCallback 并提供结果。如果你 return false
,你会得到一个本地异常,因为 WebChromeClient 被告知这个方法不会提供结果,所以它提供它自己的空结果,我们得到一个“重复的 showFileChooser 结果“ 错误。另外你需要保存拍摄的照片,然后提供一个Android.Net.Uri[]到filePathCallback.OnReceiveValue方法。如果 true
是从 OnShowFileChooser return 编辑的,则必须调用此回调,因此如果用户取消,则需要调用 filePathCallback.OnReceiveValue(null);