h5纯网页实现调用设备摄像头进行扫描二维码
兼容性自测:在
android7
上的 UC
、 chrome
和 ios13.3.1
上的 safri
浏览器上测试可正常扫码, ios11
上测试有问题主要过程就是利用 MediaDevices.getUserMedia()
拿到 设备摄像头
的媒体流,使用 video
播放,然后 使用canvas
来 每一帧 绘制 video
的画面, 再定时使用 qrcode
来解析 canvas
生成的图片,识别二维码
MDN
上的MediaDevices.getUserMedia()
和 MediaDevices.enumerateDevices()
介绍
MediaDevices.getUserMedia() 会提示用户给予使用媒体输入的许可,媒体输入会产生一个MediaStream,里面包含了请求的媒体类型的轨道。 此流可以包含一个视频轨道(来自硬件或者虚拟视频源,比如相机、视频采集设备和屏幕共享服务等等)、一个音频轨道(同样来自硬件或虚拟音频源,比如麦克风、A/D转换器等等),也可能是其它轨道类型。它返回一个 Promise 对 象,成功后会resolve回调一个 MediaStream 对象。若用户拒绝了使用权限,或者需要的媒体源不可用,promise会reject回调一个 PermissionDeniedError 或者 NotFoundError 。
MediaDevices 的方法 enumerateDevices() 请求一个可用的媒体输入和输出设备的列表,例如麦克风,摄像机,耳机设备
等。 返回的 Promise 完成时,会带有一个描述设备的 MediaDeviceInfo 的数组。
首先在 index.html
里引入 qrcode
相关的库文件(包含了解析二维码图片),qrcode下载地址,另外,本示例写在 vue
项目里
作者的qrcode项目的git地址:https://github.com/LazarSoft/jsqrcode
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
<script type="text/javascript" src="qrcode/grid.js"></script>
<script type="text/javascript" src="qrcode/version.js"></script>
<script type="text/javascript" src="qrcode/detector.js"></script>
<script type="text/javascript" src="qrcode/formatinf.js"></script>
<script type="text/javascript" src="qrcode/errorlevel.js"></script>
<script type="text/javascript" src="qrcode/bitmat.js"></script>
<script type="text/javascript" src="qrcode/datablock.js"></script>
<script type="text/javascript" src="qrcode/bmparser.js"></script>
<script type="text/javascript" src="qrcode/datamask.js"></script>
<script type="text/javascript" src="qrcode/rsdecoder.js"></script>
<script type="text/javascript" src="qrcode/gf256poly.js"></script>
<script type="text/javascript" src="qrcode/gf256.js"></script>
<script type="text/javascript" src="qrcode/decoder.js"></script>
<script type="text/javascript" src="qrcode/qrcode.js"></script>
<script type="text/javascript" src="qrcode/findpat.js"></script>
<script type="text/javascript" src="qrcode/alignpat.js"></script>
<script type="text/javascript" src="qrcode/databr.js"></script>
</body>
页面上一个 canvas
和一个 video
<div class="scan-modal">
<canvas id="qr-canvas" ref="canvas"></canvas>
<div class="scan-tips">放入框内,自动扫描</div>
<div id="outdiv" style="display: block;transform: scale(1);opacity: 0">
<video id="video"
ref="video"
:muted="true"
webkit-playsinline="true"
:width="vWidth"
:height="vHeight"
:autoplay="true"
x5-video-player-type="h5"
playsinline></video>
</div>
</div>
这块自动播放在某些设备上会出问题,注意加上 `autoplay` 和 `muted` 属性
第一步,兼容处理 getUserMedia
和 enumerateDevices
(android部分摄像头获取处理)
function isAndroid() {
var u = navigator.userAgent;
return u.indexOf('Android') > -1 || u.indexOf('Adr') > -1;
}
let mediaConfig = isAndroid() ? {} : {
audio: false,
video: {facingMode: "environment"}
}
if (isAndroid) {
// 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
if (navigator.mediaDevices.enumerateDevices === undefined) {
navigator.mediaDevices.enumerateDevices = function (constraints) {
var enumerateDevices = navigator.enumerateDevices
// 首先,如果有enumerateDevices的话,就获得它
// 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
if (!enumerateDevices) {
return Promise.reject(new Error('enumerateDevices is not implemented in this
browser'));
}
// 否则,为老的navigator.enumerateDevices方法包裹一个Promise
return new Promise(function (resolve, reject) {
enumerateDevices.call(navigator, constraints, resolve, reject);
});
}
}
// 这一步操作是为了处理android设备的摄像头获取方式
navigator.mediaDevices.enumerateDevices().then(function (devices) {
let videos = []
devices.forEach(function (dv) {
var kind = dv.kind;
if (kind.indexOf('video') !== -1) {
videos.push(dv.deviceId);
}
});
// 默认使用后置摄像头。根据测试,在android手机上后置摄像头的id会在之后获取
mediaConfig = {
audio: false,
video: {deviceId: videos[videos.length - 1]}
}
});
}
// 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
// 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function (constraints) {
var getUserMedia = navigator.getUserMedia || navigator.webKitGetUserMedia ||
navigator.moxGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia
// 首先,如果有getUserMedia的话,就获得它
// 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
if (!getUserMedia) {
return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
}
// 否则,为老的navigator.getUserMedia方法包裹一个Promise
return new Promise(function (resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
}
第二步,获取媒体流,使用 video
播放
let _this = this
navigator.mediaDevices.getUserMedia(mediaConfig).then(function (stream) {
_this.stream = stream
// 旧的浏览器可能没有srcObject
_this.$nextTick(_ => {
_this.video = document.getElementById('video');
_this.canvas = document.getElementById("qr-canvas");
var width = document.body.clientWidth - 30;
var height = width = 260;
_this.canvas.style.width = width + "px";
_this.canvas.style.height = height + "px";
_this.canvas.width = width;
_this.canvas.height = height;
_this.ctx = _this.$refs.canvas.getContext("2d");
_this.ctx.clearRect(0, 0, 260, 260);
_this.vHeight = height + "px";
_this.vWidth = width + "px";
if ("srcObject" in video) {
_this.video.srcObject = _this.stream;
_this.video.muted = true
} else {
// 防止在新的浏览器里使用它,应为它已经不再支持了
_this.video.src = window.URL.createObjectURL(_this.stream);
}
_this.video.onloadedmetadata = function (e) {
_this.playVideo()
};
})
})
.catch(function (err) {
_this.isScan = false
console.log(err.name + ": " + err.message);
});
第三步,使用 canvas
每一帧绘制 video
画面,使用 qrcode
定时解析图片
playVideo() {
let _this = this
_this.video.muted = true
_this.video.play();
_this.$nextTick(_ => {
_this.drawInterval = window.setInterval(function () {
_this.ctx.drawImage(_this.video, 0, 0);
}, 60);
// // 定时进行图片转换成二维码
_this.getImgTiming = window.setInterval(function () {
_this.getQrCode();
}, 1000);
})
}
// 关闭摄像头
closeCamera() {
if (!this.$refs['video']) {
return
}
if (!this.$refs['video'].srcObject) {
this.$refs['video'].src = null;
return
}
let stream = this.$refs['video'].srcObject
let tracks = stream.getTracks()
tracks.forEach(track => {
track.stop()
})
this.$refs['video'].srcObject = null
}
// qrcode解析回调
qrcode.callback = function (res) {
if (res == 'error decoding QR Code') {
} else {
clearInterval(_this.drawInterval)
clearInterval(_this.getImgTiming);
_this.code = res
_this.isScan = false
_this.video && _this.video.pause()
return false;
}
};
// 转化图片,并解析
getQrCode() {
var dataURL = this.canvas.toDataURL("image/png");
var re = this.getBlobBydataURI(dataURL, 'image/png');
qrcode.decode(this.getObjectURL(re));
},
getObjectURL(file) {
var url = null;
if (window.createObjectURL != undefined) { // basic
url = window.createObjectURL(file);
} else if (window.URL != undefined) { // mozilla(firefox)
url = window.URL.createObjectURL(file);
} else if (window.webkitURL != undefined) { // webkit or chrome
url = window.webkitURL.createObjectURL(file);
}
return url;
},
getBlobBydataURI(dataURI, type) {
var binary = atob(dataURI.split(',')[1]);
var array = [];
for (var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], {type: type});
},
相关资料
https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getUserMedia
https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator/mediaDevices
如果觉得此文章有用或者有其他使用问题,欢迎在下方留言~
版权属于:xigua
本文链接:https://xianh5.com/archives/19/
转载时须注明出处及本声明
1 条评论
看的我热血沸腾啊https://www.ea55.com/