H5通过navigator.mediaDevices.getUserMedia调用手机摄像头

navigator.mediaDevices.getUserMedia

应项目要求,需要实现移动端app嵌入H5页面完成实人认证的功能。打开getUserMedia文档,链接如下:
https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getUserMedia
看上去很简单,最终却写的怀疑人生。

API环境

问题一:(为什么不管怎么配置都显示前置摄像头)
想正常使用API必须在https环境下进行,否则你会发现不管怎么写,都只能调用默认的摄像头(大部分都是前置,只有少部分是后置)
前端开发者可以将文件上传至"码云"仓库,获取https链接然后在手机上预览
链接:码云仓库入口

问题二:(API在安卓和ios效果一样吗?)
根据官方文档,目前navigator.mediaDevices.getUserMedia在ios上只支持11版本以上,且只能在safari正常运行。安卓目前没有发现版本限制,需要兼容的代码如下

if (navigator.mediaDevices === undefined) {
    navigator.mediaDevices = {};
 }
if (navigator.mediaDevices.getUserMedia === undefined) {
    navigator.mediaDevices.getUserMedia = function (constraints) {
    var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia;
	if (!getUserMedia) {
	    return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
	}
	return new Promise(function (resolve, reject) {
		getUserMedia.call(navigator, constraints, resolve, reject);
	});
  }
}

问题三:(第一次启用成功调用前置摄像头,第二次需要调用后置却黑屏或者失败)
失败的原因很多,列举两个一开始我遇到的问题
1.前置摄像头调用后,摄像功能需要关闭后才能正常执行第二次调用,否则会报错:设备被占用。解决方法,在每次执行调用方法前,先关闭摄像设备。

if (window.stream) {
    window.stream.getTracks().forEach(track => {
          track.stop();
    });
}

亲测有用,别的找了很多停止的方法都没有效果。
2.调用后置API的方法还是无法唤醒后置摄像头,于是我找到另外一个方法,通过查看手机摄像头ID,来直接唤醒后置。

var deviceInfoId="", //摄像头ID
    num = 0, //摄像头数量
    carema = []; //摄像头ID数组
    //在页面加载完成后获得设备ID数组
window.onload = navigator.mediaDevices.enumerateDevices().then(gotDevices);
function gotDevices(deviceInfos) {
        for (let i = 0; i < deviceInfos.length; ++i) {
            if (deviceInfos[i].kind === 'videoinput') {
                carema.push(deviceInfos[i].deviceId)
              }
        }
        deviceInfoId = carema[后置位置];
}
            var constraints = {
                audio: false,
                video: {
                    deviceId: deviceInfoId,
                    //放在app里面需要下面配置一下
                    "permissions": {
                        "audio-capture": {
                            "description": "Required to capture audio using getUserMedia()"
                        },
                        "video-capture": {
                            "description": "Required to capture video using getUserMedia()"
                        }
                    }
                }
            };
            navigator.mediaDevices.getUserMedia(constraints)
                .then(function (stream) {
                    var video = document.getElementById('video');
                    try {
                        window.stream = stream;
                        video.srcObject = stream;
                    } catch (error) {
                        video.src = window.URL.createObjectURL(stream);
                    }
                    this.localMediaStream = stream;
                    // video.play();   这个加不加好像没有影响
                })
                .catch(function (err) {
                    console.log(err.name + ": " + err.message);
                });

如果只是一部手机可以这样,但是测试了多部手机发现摄像头数组毫无规律可循,这个方法慎用。
如果页面上添加选择摄像设备的按钮的话,这个方法还是不错的。查看设备能调用几个摄像头链接如下:https://webrtc.github.io/samples/src/content/devices/input-output/
由于我们的项目页面不希望出现切换按钮,面对后置出现的众多BUG,最终选择放弃,使用input调用摄像头。

 <input class="card_input" v-on:change="appCapture($event)" type="file" accept="image/*" capture="camera" />

部分手机打开摄像头以后是黑屏,但是拍照仍然可以成像

解决方案:做了很多处理都没用,最后发现只要把参数中的分辨率(width,height)删除掉,就解决了黑屏问题

成功调用后用canvas实现成像并适应屏幕大小

我这里的代码是取video的宽高然后复制给canvas,这样可以让canvas和video保持一致,只用给video设置宽度100%,高度调节成合适的值,就实现了适应手机屏幕。

            var video = document.getElementById('video');
            var canvas = document.getElementById('canvas'),
                ctx = canvas.getContext('2d'),
                CHeight = video.clientHeight, //获取屏幕大小让canvas自适应
                CWidth = video.clientWidth;
            canvas.width = CWidth;
            canvas.height = CHeight;
            //localMediaStream 在data里定义一个{}
            if (localMediaStream) {
                ctx.drawImage(video, 0, 0, CWidth, CHeight);
                var dataURL = canvas.toDataURL('image/jpeg'); //dataURL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA'
                img.src = dataURL;

video成像镜像问题

API唤醒的前置摄像头是相反的,很不舒服很不舒服。
之后用css处理一下给video添加 transform: rotate(180deg),可以实现反转,但是还是没有达到和手机一样的效果。
这时候可以选择通过设备ID调用前置摄像头,前置摄像头的laval一直都是“default”,也有的是空值,但是也能实现。
配置代码如下:

            var constraints = window.constraints = {
                audio: false,
                video: {
                    sourceId: 'default',
                    facingMode:  { exact: "user" } 
                }
              };

完美调用自己手机的前置摄像头!!!
完整代码如下:
页面代码:

<div @click='moveToCameraAVG()' v-cloak>
     <img v-if="imginfo!==''" :src="imginfo" />
     <div class="warm_title2">点击自拍一张头像</div>
</div>
<video id="video" class="pic_video" playsinline autoplay x5-video-player-type="h5" style='object-fit:fill'></video>
<canvas id="canvas" class="canvas_pic" style='margin: 0;padding: 0;'></canvas>
<div class="bottom_div">
    <div>拍照</div>
    <img src='images/pic_btn.png' class="capture-btn" @click='captureAvg' />
</div>
        // 头像相机
        moveToCameraAVG() {
            var self = this;
            if (navigator.mediaDevices === undefined) {
                navigator.mediaDevices = {};
            }
            if (navigator.mediaDevices.getUserMedia === undefined) {
                navigator.mediaDevices.getUserMedia = function (constraints) {
                    var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia;
                    if (!getUserMedia) {
                        return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
                    }
                    return new Promise(function (resolve, reject) {
                        getUserMedia.call(navigator, constraints, resolve, reject);
                    });
                }
            }
            if (window.stream) {
                window.stream.getTracks().forEach(track => {
                    track.stop();
                });
            }
            var constraints = window.constraints = {
                audio: false,
                video: {
                    sourceId: 'default',
                    facingMode:  { exact: "user" } 
                }
              };
            navigator.mediaDevices.getUserMedia(constraints)
                .then(function (stream) {
                    var video = document.getElementById('video');
                    try {
                        window.stream = stream;
                        video.srcObject = stream;
                    } catch (error) {
                        video.src = window.URL.createObjectURL(stream);
                    }
                    self.localMediaStream = stream;
                    video.play();
                })
                .catch(function (err) {
                    alert(err.name + ": " + err.message);
                });
        },
        //停止摄像机
        stopCapture: function () {
            var video = document.getElementById('video');
            if (!video.srcObject) return
            let stream = video.srcObject
            let tracks = stream.getTracks();
            tracks.forEach(track => {
                track.stop()
            })
        },
        // 头像照片
        captureAvg() {
            var vm = this;
            var video = document.getElementById('video');
            var canvas = document.getElementById('canvas'),
                ctx = canvas.getContext('2d'),
                CHeight = video.clientHeight, //获取屏幕大小让canvas自适应
                CWidth = video.clientWidth;
            canvas.width = CWidth;
            canvas.height = CHeight;
            if (vm.localMediaStream) {
                ctx.drawImage(video, 0, 0, CWidth, CHeight);
                var dataURL = canvas.toDataURL('image/jpeg'); //dataURL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA'
                vm.imginfo = dataURL;
                // 停止摄像机
                video.pause();
                this.stopCapture();
            }
        },

希望可以帮助到一些人~也希望大家有其他建议的相互帮助和学习。

<think>好的,用户的问题是想在UniApp中实现安卓设备的摄像头权限调用,但遇到了navigator.mediaDevices.getUserMedia不可用的情况。首先,我需要回忆一下相关的知识。根据引用[1]和[2],getUserMedia在非安全环境下(比如HTTP)是undefined,而UniApp可能默认不是HTTPS环境,这可能是一个问题。不过用户提到的是安卓设备,可能是在原生应用里运行,这时候可能需要检查WebView的配置。 接下来,用户使用的是UniApp,可能需要用原生插件或者特定的组件。引用[3]提到在UniApp中使用live-pusher组件来实现摄像头功能,并且需要.nvue文件的支持。这说明直接在前端用JavaScript调用getUserMedia可能不可行,尤其是因为UniApp的框架限制。可能需要使用uni-app提供的原生组件,如live-pusher,或者通过原生插件的方式调用摄像头权限。 另外,权限配置也很重要。安卓需要摄像头权限,用户需要在manifest.json里添加权限声明。同时,动态权限请求也是必须的,特别是在Android 6.0以上。引用[3]中的例子使用了权限API,所以可能需要使用uni-app的权限相关API来请求用户授权。 还需要考虑UniApp的环境差异,比如在H5端和App端的区别。如果用户是在App端,可能需要使用原生模块,而在H5端则可能受限于浏览器的安全策略。根据引用[4],安全上下文的问题在H5端可能更突出,但App端可能通过WebView配置绕过,不过需要处理权限问题。 总结下来,解决方案可能包括:使用live-pusher组件、配置权限、处理动态权限请求、以及可能的原生插件开发。需要分步骤指导用户如何配置和使用这些功能,同时解答可能遇到的常见问题,比如为什么getUserMedia不可用,如何替代等。</think>在UniApp中实现Android设备摄像头权限调用,需结合原生能力与框架特性。以下是分步骤解决方案: ### 一、权限声明与动态请求 1. **配置`manifest.json`** 在`manifest.json`的`"app-plus"`模块添加摄像头权限声明: ```json "android": { "permissions": ["CAMERA"] } ``` 2. **动态权限请求** 使用`uni.authorize`接口请求权限: ```javascript uni.authorize({ scope: &#39;scope.camera&#39;, success: () => console.log("授权成功"), fail: (err) => console.log("授权失败", err) }); ``` ### 二、摄像头功能实现方案 由于UniApp的WebView环境限制,`navigator.mediaDevices.getUserMedia`在App端不可用[^3]。推荐以下两种实现方式: #### 方案1:使用`live-pusher`组件(原生渲染) 1. **创建`.nvue`文件** 新建`camera.nvue`文件,使用原生组件实现摄像头调用: ```html <template> <view> <live-pusher url="" mode="SD" :beauty="0" class="camera-view" ></live-pusher> <cover-image class="mask" src="/static/mask.png"></cover-image> </view> </template> ``` 2. **样式控制** 通过CSS实现遮罩层: ```css .camera-view { width: 750rpx; height: 100vh; } .mask { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 500rpx; height: 500rpx; } ``` #### 方案2:使用原生插件(需Android开发) 1. **开发原生模块** 通过`uni.requireNativePlugin`调用原生摄像头API: ```javascript const cameraModule = uni.requireNativePlugin(&#39;MyCameraModule&#39;); cameraModule.startCameraPreview(); ``` 2. **插件配置** 在原生插件中处理权限请求逻辑: ```java if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, CAMERA_REQUEST_CODE); } ``` ### 三、常见问题解决 1. **`navigator.mediaDevices undefined`问题** App端WebView未启用媒体设备API,应优先使用原生组件方案[^3][^4] 2. **H5端兼容方案** 若需H5支持,需部署HTTPS服务并通过浏览器安全策略: ```javascript if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { navigator.mediaDevices.getUserMedia({ video: true }) .then(stream => { /* 处理视频流 */ }) } ```
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值