captain-ui
Version:
有赞vue wap业务组件库
440 lines (386 loc) • 12.3 kB
JavaScript
import "vant/es/toast/style";
import _Toast from "vant/es/toast";
import "vant/es/popup/style";
import _Popup from "vant/es/popup";
import "vant/es/uploader/style";
import _Uploader from "vant/es/uploader";
import "vant/es/dialog/style";
import _Dialog from "vant/es/dialog";
import Vue from 'vue';
// import '../../common/tracking.js';
// import '../../common/tracking-data-face.js';
// import '../../common/getUserMediaPolyfill.js';
import { getVideoURL, delay } from '../../common/index.js';
import { upload } from '../../../cert-id-ocr/src/utils/upload'; // import { isWebRTCSupported } from '../../../constants/index.js';
import { prefixCls } from '../../constants'; // 录制时间
var VIDEO_RECORD_DURATION = 5000; // 轮询失败尝试次数
var MAX_LOOP_FAIL_COUNT = 5; // webRTC视频限制参数
var CONSTRAINTS_OPTION = {
video: {
frameRate: {
ideal: 10,
max: 15
},
facingMode: 'user'
}
}; // 视频录制参数
var MediaRecorderOptions = {
videoBitsPerSecond: 600000
};
export default {
render: function render() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c('main', {
class: [_vm.prefixCls + '__wrapper']
}, [_c('section', {
class: [_vm.prefixCls + '__record-wrapper']
}, [_c('div', {
class: [_vm.prefixCls + '__record-content']
}, [_c('div', {
class: [_vm.prefixCls + '__record-scan'],
on: {
"click": _vm.startCapture
}
}), _c('p', {
class: [_vm.prefixCls + '__record-tips']
}, [_vm._v("请将脸部正对镜头,保持光线充足")])])]), _c('section', {
class: [_vm.prefixCls + '__tips-wrapper']
}, [_c('header', {
class: [_vm.prefixCls + '__tips-header']
}, [_c('h4', [_vm._v("请在拍摄时朗读4位验证数字")]), _vm._m(0)]), _c('p', {
class: [_vm.prefixCls + '__tips-body']
}, _vm._l(_vm.randomNums, function (n, index) {
return _c('span', {
key: index
}, [_vm._v("\n " + _vm._s(n) + "\n ")]);
}), 0), _c('p', {
class: [_vm.prefixCls + '__tips-footer']
}, [_vm._v("建议视频时长不超过5秒")])]), _c('van-uploader', {
directives: [{
name: "show",
rawName: "v-show",
value: false,
expression: "false"
}],
ref: "uploader",
attrs: {
"multiple": false,
"disabled": _vm.uploading,
"before-read": _vm.beforeRead,
"after-read": _vm.execute,
"capture": "user",
"accept": "video/*"
}
}), _c('van-popup', {
class: [_vm.prefixCls + '__recorder-wrapper'],
attrs: {
"overlay": false,
"lazy-render": false,
"position": "bottom",
"data-comment": "异步将 src 赋给 video"
},
on: {
"opened": _vm.execute
},
model: {
value: _vm.capturing,
callback: function callback($$v) {
_vm.capturing = $$v;
},
expression: "capturing"
}
}, [_c('video', {
ref: "video",
attrs: {
"preload": "",
"autoplay": "",
"loop": "",
"muted": "",
"controls": "",
"playsinline": ""
},
domProps: {
"muted": true
}
}), _vm.isWebRTCSupported ? _c('div', {
staticClass: "recognition__recorder-mask"
}, [_c('p', {
class: [_vm.prefixCls + '__recorder-face']
}), _c('p', {
class: [_vm.prefixCls + '__recorder-instruction']
}, [_vm._v(_vm._s(_vm.instruction))])]) : _vm._e()])], 1);
},
staticRenderFns: [function () {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c('p', [_c('span', [_vm._v("牢记数字")]), _c('span', [_vm._v(">")]), _c('span', [_vm._v("开始拍摄")]), _c('span', [_vm._v(">")]), _c('span', [_vm._v("边录边读")])]);
}],
name: 'cap-cert-recognition',
components: {
'van-dialog': _Dialog,
'van-uploader': _Uploader,
'van-popup': _Popup
},
props: {
randomNums: {
type: Array,
default: function _default() {
return [];
},
required: true
},
action: {
type: String,
default: ''
},
videoMaxSize: {
type: Number,
default: 9 * 1024 * 1024
},
tokenUrl: {
type: String,
default: '',
required: true
},
submitRecognition: {
type: Function,
default: function _default() {
return Promise.resolve();
}
},
queryRecognition: {
type: Function,
default: function _default() {
return Promise.resolve();
}
}
},
data: function data() {
return {
prefixCls: prefixCls + '-recognition',
isWebRTCSupported: false,
// 不支持 WebRTC 需 fallback 到 input 模式 TODO:暂时默认为false
capturing: false,
uploading: false,
loopFailCount: 0,
// 连续轮询失败次数,超过 MAX_LOOP_FAIL_COUNT 就认为服务端异常,停止轮询
instruction: '请将脸部正对镜头',
stream: null,
// webRTC stream
trackTask: null,
// tracking task
noWebrtcDialogOpened: false // 不支持webrtc的确认Dialog
};
},
methods: {
startCapture: function startCapture() {
var _this = this;
this.$emit('on-start-capture');
if (this.isWebRTCSupported) {
this.stream = null;
navigator.mediaDevices.getUserMedia(CONSTRAINTS_OPTION).then(function (stream) {
_this.stream = stream;
_this.capturing = true;
}, function () {
// webRTC回调识别fallback
_this.isWebRTCSupported = false;
}).catch(function () {
// webRTC回调识别fallback
_this.isWebRTCSupported = false;
});
} else {
// this.noWebrtcDialogOpened = true;
this.onConfirmNoWebrtcDialog();
}
},
onConfirmNoWebrtcDialog: function onConfirmNoWebrtcDialog() {
this.$refs.uploader.$refs.input.click();
},
beforeRead: function beforeRead(video) {
var overSize = video.size > this.videoMaxSize;
if (overSize) {
var megabyte = (this.videoMaxSize / 1024 / 1024).toFixed(1);
_Toast("\u89C6\u9891\u5C3A\u5BF8\u8D85\u8FC7\u9650\u5236\uFF0C\u6700\u5927\u4E3A" + megabyte + "M");
return false;
}
return true;
},
/**
* 根据 webRTC stream 或 视频文件进行识别、处理、上传、提交、查询结果一系列工作
* @param {File|undefined} file 支持 webRTC时为 undefined,否则为录像文件
*/
execute: function execute(_temp) {
var _this2 = this;
var _ref = _temp === void 0 ? {} : _temp,
file = _ref.file;
var isWebRTCSupported = this.isWebRTCSupported;
var capturePromise;
if (isWebRTCSupported) {
var video = this.$refs.video;
video.playsInline = true;
var source = this.stream;
if ('srcObject' in video) {
video.srcObject = source;
} else {
video.src = getVideoURL(source);
}
capturePromise = this.startRecognize(video).then(this.startRecord).then(function (record) {
// 录制结束后进入识别
_Toast.loading({
duration: 0,
mask: true,
message: '识别中…'
});
return record;
});
} else {
capturePromise = Promise.resolve().then(function () {
_Toast.loading({
duration: 0,
mask: true,
message: '识别中…'
});
return file;
});
}
capturePromise.then(this.uploadFile).then(this.commitRecognize).then(this.queryRecognizeRes).then(function () {
_Toast.clear();
_this2.$emit('success');
}).catch(function (msg) {
_Toast.clear();
if (msg) _Toast(String(msg));
_this2.$emit('fail');
}).finally(function () {
_this2.capturing = false;
if (_this2.stream && _this2.stream.getTracks) {
_this2.stream.getTracks().forEach(function (track) {
return track.stop();
});
}
});
},
/**
* 使用 tracking.js 对视频进行人脸检测
* @param {Element} video 视频元素
* @return {Promise}
*/
startRecognize: function startRecognize(video) {
var _this3 = this;
var recognizePromise = new Promise(function (resolve) {
var tracker = new window.tracking.ObjectTracker(['face']);
_this3.instruction = '请将脸部正对镜头';
tracker.on('track', function (ev) {
var data = ev.data; // TODO: 根据 data 数据对人脸的检测进行更严格的控制,比如人脸和蒙层上蓝框的匹配程度
// 检测到人脸
if (data.length !== 0) {
_this3.instruction = '点点头';
resolve();
}
}); // 内部方法,原生暴露的方法欠缺 webRTC API 兼容处理
_this3.trackTask = window.tracking.trackVideo_(video, tracker);
});
return recognizePromise;
},
/**
* 使用 MediaRecorder 录制15秒的视频
* @return {Promise}
*/
startRecord: function startRecord() {
var _this4 = this;
// 停止track识别,防止卡顿
this.trackTask.stop();
this.trackTask = null;
var recordPromise = new Promise(function (resolve) {
var stream = _this4.stream;
var recorder = new MediaRecorder(stream, MediaRecorderOptions);
var chunks = [];
recorder.ondataavailable = function (e) {
chunks.push(e.data);
};
recorder.onstop = function () {
var blob = new Blob(chunks, {
type: recorder.mimeType
});
chunks = [];
resolve(blob);
};
setTimeout(function () {
recorder.stop();
}, VIDEO_RECORD_DURATION);
recorder.start();
});
return recordPromise;
},
uploadFile: function uploadFile(file) {
var _this5 = this;
this.uploading = true;
return upload([file], this.tokenUrl).then(function (_ref2) {
var url = _ref2[0];
_this5.uploading = false;
if (url === '') {
return Promise.reject('视频上传失败');
}
return Promise.resolve(url);
});
},
commitRecognize: function commitRecognize(videoUrl) {
var _this6 = this;
return this.submitRecognition({
videoUrl: videoUrl,
action: this.action
}).then(function (_ref3) {
var status = _ref3.status,
bizNo = _ref3.bizNo;
if (status === -1) {
_Dialog.alert({
message: '识别失败,可能是由于您的证件照片不够清晰,请重新上传身份证照片。'
}).then(function () {
return _this6.$emit('fail-confirm');
});
return Promise.reject();
}
return {
bizNo: bizNo
};
});
},
queryRecognizeRes: function queryRecognizeRes(_ref4) {
var _this7 = this;
var bizNo = _ref4.bizNo;
return this.queryRecognition({
bizNo: bizNo
}).then(function (_ref5) {
var success = _ref5.success,
status = _ref5.status;
_this7.loopFailCount = 0;
if (success) {
return;
}
if (status === 1) {
return delay(1000).then(function () {
return _this7.queryRecognizeRes({
bizNo: bizNo
});
});
}
return Promise.reject('检测失败,请重试');
}, function (err) {
if (err && err.code && err.msg) {
return Promise.reject(err.msg);
} // 防止因网络超时等导致的前端中断请求,而后端其实执行成功
if (_this7.loopFailCount < MAX_LOOP_FAIL_COUNT) {
_this7.loopFailCount += 1;
return delay(1000).then(function () {
return _this7.queryRecognizeRes({
bizNo: bizNo
});
});
}
return Promise.reject('检测失败,请重试');
});
}
}
};