前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >webview拉起拍照和录像的爬坑终结篇

webview拉起拍照和录像的爬坑终结篇

原创
作者头像
老码小张
修改2021-01-23 16:01:59
4K1
修改2021-01-23 16:01:59
举报
文章被收录于专栏:玩转全栈

对于iOS环境上,简单的两个配置就OK啦

即只需在配置里加上摄像头和麦克风的使用权限。具体做法是在App 的info.plist中加入:

代码语言:javascript
复制
.NSMicrophoneUsageDescription
.NSCameraUsageDescription

就完事了!

对于Android环境,就会比较复杂一点点:

step1、我们需要实现一个自己的 WebChromeClient,其主要目的就是为了拦截FileChooser这个选择文件的动作:

这里,用户在h5上点击文件,我们以下环节实现的WebChromeClient中,基于不同Android的api版本中的回调函数会被触发:

代码语言:javascript
复制
public class EssWebChromeClient extends WebChromeClient {

     private Activity mActivity;

    public EssWebChromeClient(Activity activity) {
        mActivity = activity;
    }
    ///省略部分代码
    // For Android >= 3.0
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
        EssH5Sdk.getInstance().recordVideoForApiBelow21(uploadMsg, acceptType, mActivity);
    }
    // For Android >= 4.1
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
        EssH5Sdk.getInstance().recordVideoForApiBelow21(uploadMsg, acceptType, mActivity);
    }

    @Override
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
        if(EssH5Sdk.getInstance().recordVideoForApi21(webView, filePathCallback, mActivity,fileChooserParams)){
            return true;
        }else{
            return super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
        }
    }
}

这里我们注意以下,openFileChooser函数中会有一个acceptType的参数;

这个参数实际上是对应我们H5那个input框中的accept属性,需要我们关注:

accept 属性是一个字符串,它定义了文件 input 应该接受的文件类型。表示在 file 类型的 <input> 元素中用户可以选择的文件类型。每个唯一文件类型说明符可以采用下列形式之一:

  • 一个以英文句号(".")开头的合法的不区分大小写的文件名扩展名。例如: .jpg.pdf 或 .doc
  • 一个不带扩展名的 MIME 类型字符串。
  • 字符串 audio/*, 表示“任何音频文件”。
  • 字符串 video/*,表示 “任何视频文件”。
  • 字符串 image/*,表示 “任何图片文件”。

这里还有一个属性值得我们去关注:

capture 属性是一个字符串,如果accept 属性指出了 input 是图片或者视频类型,则它指定了使用哪个摄像头去这些数据。

值 :user 表示应该使用前置摄像头和/或麦克风。

值: environment 表示应该使用后置摄像头和/或麦克风。

step2、好了,当用户点击选择文件时,已经触发了我们的WebChromeClient中的选择文件的回调,接下来,我们实现原生拉起的想起拍照或者是:

代码语言:javascript
复制
  public void recordVideoForApiBelow21(ValueCallback<Uri> uploadCallback, String acceptType, Activity activity) {
        if("image/*".equals(acceptType)){
            setUploadMessage(uploadMsg);
            startCamera(activity);
        }else if ("video/*".equals(acceptType)) {
            setUploadCallback(uploadCallback);
            recordVideo(activity);
        }
   }
   
  @TargetApi(21)
  public boolean recordVideoForApi21(WebView webView, ValueCallback<Uri[]> filePathCallback, Activity activity, WebChromeClient.FileChooserParams fileChooserParams){
        String acceptType = fileChooserParams.getAcceptTypes()[0];
        if("image/*".equals(acceptType)){
            setUploadCallbackV21(filePathCallback);
            startCamera(activity);
            return true;
        }
        if ("video/*".equals(acceptType) ){ 
            setUploadCallbackV21(filePathCallback);
            recordVideo(activity);
            return true;
        }
        return false;
  }

这里的我们注意到两个版本的api其实对于回调的形式是有区别的,21以上是接受一个Uri[]的callback,而低于21是接收一个Url的callback,这里注意一下就好,然后,我们看startCamera和recordVideo具体如何实现:

这里不妨先看一个简单的,如何录制视频:

代码语言:javascript
复制
private void recordVideo(Activity activity){
        try {
            Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
            intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.putExtra("android.intent.extras.CAMERA_FACING", 1); // 调用前置摄像头

            activity.startActivityForResult(intent, VIDEO_REQUEST);
        } catch (Exception e) {
            e.printStackTrace();
        }
}

录制视频比较简单,当然我配置了默认拉起前置摄像头,基于具体业务场景,比如做人脸识别,有时候还是有一定的帮助的。

那么,录制玩视频,这个startActivityForResult,就会有一个onActivityResult的回调,我们去取他的Intent data,那么结果并调用相应的callback,应该还记得上面设置的按个callback吧:

代码语言:javascript
复制
if (requestCode == VIDEO_REQUEST) { //根据请求码判断返回的是否是h5刷脸结果
     Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
     Uri[] uris = result == null ? null : new Uri[]{result};
     if (mUploadCallbackAboveL != null) {
            mUploadCallbackV21.onReceiveValue(uris);
            setUploadCallbackAboveL(null);
     } else {
            mUploadMessage.onReceiveValue(result);
            setUploadMessage(null);
     }
}

所以,我们看到了,无非就是基于不同的api来掉用起回调函数。

所以,同样的到来,拍照也是这样一个套路:

代码语言:javascript
复制
private void takeCamera(Activity activity) {
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        takePictureIntent.putExtra("android.intent.extras.CAMERA_FACING", 0); // 调用后置摄像头
        //https://ptyagicodecamp.github.io/accessing-pictures-using-fileprovider.html
        if (takePictureIntent.resolveActivity(activity.getPackageManager()) != null) {
            File photoFile = null;
            try {
                photoFile = createImageFile(activity);
                takePictureIntent.putExtra("PhotoPath", mCameraFilePath);
            } catch (IOException ex) {
                Log.e("TAG", "Unable to create Image File", ex);
            }
            //适配7.0
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
                if (photoFile != null) {
                    Uri photoURI = FileProvider.getUriForFile(activity,
                             "com.tencent.xxx.fileprovider", photoFile);
                    takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
                }
            } else {
                if (photoFile != null) {
                    mCameraFilePath = "file:" + photoFile.getAbsolutePath();
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                            Uri.fromFile(photoFile));
                } else {
                    takePictureIntent = null;
                }
            }
        }
        activity.startActivityForResult(takePictureIntent, TAKE_PHOTO_REQUEST);

    }
    
    
    private File createImageFile(Activity activity) throws IOException {
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.CHINA).format(new Date());
        String imageFileName = "JPEG_" + timeStamp + "_";
        File storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        File image = File.createTempFile(
                imageFileName,  /* 前缀 */
                ".jpg",         /* 后缀 */
                storageDir      /* 文件夹 */
        );
        mCameraFilePath = image.getAbsolutePath();
        return image;
    }

等等,这里需要注意的是,7.0之后,Android系统不允许已file:的方式暴露文件,需要使用FileProvider,所以,这里需要在AndroidManifest.xml配置文件中去什么一个provider:

代码语言:javascript
复制
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.tencent.xxx.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

android:authorities的取值需要注意一致,不然getUriForFile肯定就是crash了,而且是一个JNI的crash,莫名其妙,让你定位问题都及其蛋疼。

file_path.xml的内容如下:

代码语言:javascript
复制
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images" path="Android/data/com.tencent.xxx/files/Pictures" />
</paths>

因为我们拍照存储的临时文件,防止在相册中:Environment.DIRECTORY_PICTURES,所以这里的path就是这个,当然,这个path你断点调试一下,抓一下photoFile 这个变量的路径,自然就知道改填啥了。

ok,依然是到了我们的onActivityResult环节:

代码语言:javascript
复制
       if (requestCode == TAKE_PHOTO_REQUEST){
            if ( resultCode != RESULT_OK){//用户取消,传回一个空
                if (mUploadCallbackAboveL != null) {
                    mUploadCallbackAboveL.onReceiveValue(null);
                    setUploadCallbackV21(null);
                } else if (mUploadMessage != null) {
                    mUploadMessage.onReceiveValue(null);
                    setUploadMessage(null);
                }
                return;
            }
            Uri result = (data == null) ? null : data.getData();

            if (result == null && hasFile(mCameraFilePath)) {
                result = Uri.fromFile(new File(mCameraFilePath));
            }
            Uri[] uris = result == null ? null : new Uri[]{result};
            if (mUploadCallbackAboveL != null) {
                mUploadCallbackAboveL.onReceiveValue(uris);
                setUploadCallbackV21(null);
            } else if (mUploadMessage != null) {
                mUploadMessage.onReceiveValue(result);
                setUploadMessage(null);
            }
        }

这里需要注意一下,无论用户取消还是最终选择了,这里的data始终是null,但是我们可以通过resultCode来区分是否用户取消,用户取消的话,回调函数传回一个null就OK啦。

以上,就是WebChromeClient的具体细节,实现好之后,我们需要和webview关联上:

代码语言:javascript
复制
mWebView.setWebChromeClient(new EssWebChromeClient(H5Activity.this));

至此,webview上实现h5拍照,和录像的功能就完成了。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 对于iOS环境上,简单的两个配置就OK啦
  • 对于Android环境,就会比较复杂一点点:
    • step1、我们需要实现一个自己的 WebChromeClient,其主要目的就是为了拦截FileChooser这个选择文件的动作:
      • 这个参数实际上是对应我们H5那个input框中的accept属性,需要我们关注:
      • 这里还有一个属性值得我们去关注:
    • step2、好了,当用户点击选择文件时,已经触发了我们的WebChromeClient中的选择文件的回调,接下来,我们实现原生拉起的想起拍照或者是:
      • 这里不妨先看一个简单的,如何录制视频:
      • 所以,同样的到来,拍照也是这样一个套路:
      • ok,依然是到了我们的onActivityResult环节:
相关产品与服务
人脸识别
腾讯云神图·人脸识别(Face Recognition)基于腾讯优图强大的面部分析技术,提供包括人脸检测与分析、比对、搜索、验证、五官定位、活体检测等多种功能,为开发者和企业提供高性能高可用的人脸识别服务。 可应用于在线娱乐、在线身份认证等多种应用场景,充分满足各行业客户的人脸属性识别及用户身份确认等需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档