react18 PC调用电脑摄像头
- 未分类
- 0分钟前
- 1 热度
- 0 评论
'use client';
import React, { useEffect, useRef, useState } from 'react';
import styles from './index.module.scss';
import { Button, Iconfont } from '@/components';
import { i18n } from '@/lib/i18n';
import { isFunction } from '@/utils/isUtil';
import { msg } from '@/utils/messageUtil';
const debug = require('debug')('debug:PaiZhao');
export default function PaiZhao(props) {
const width = 472;
const height = 354;
const cameraVideoRef = useRef(null);
const cameraCanvasRef = useRef(null);
// 图片url
const [img, setImg] = useState('');
// 摄像头是否打开
const [isOpen, setIsOpen] = useState(false);
// 等待拍照
const [waitingTakePic, setWaitingTakePic] = useState(true);
// 拍照完成
const [completedTakePic, setCompletedTakePic] = useState(false);
// 拍照状态
const [statusTakePic, setStatusTakePic] = useState('');
// 拍照结果
const [checkresult, setCheckresult] = useState({});
// 图片质量检测结果
const qualityPhotoMap = {
Good: () => (
<div className={styles.checkPhotoBox}>
<div className={styles.checkIcon}>
<Iconfont type="icon-check-radio-1" size={32} color="#08AA18" />
</div>
<div className={styles.checkTitle}>{i18n('GOOD_QUALITY_OF_PHOTO')}</div>
<div className={styles.checkDesc}>{i18n('DETAILS_ARE_READABLE')}</div>
</div>
),
Poor: () => (
<div className={styles.checkPhotoBox}>
<div className={styles.checkIcon}>
<Iconfont type="icon-close1" size={32} color="#CE1126" />
</div>
<div className={styles.checkTitle}>{i18n('POOR_QUALITY_OF_PHOTO')}</div>
<div className={styles.checkDesc}>
{i18n('OOPS!_PLEASE_UPLOAD_A_VALID_IDENTIFICATION_CARD_')}
</div>
</div>
),
};
// 打开摄像头
function successFunc(mediaStream) {
const video = cameraVideoRef.current;
// const video = document.getElementById('cameraVideo') as HTMLVideoElement;
// 旧的浏览器可能没有srcObject
if ('srcObject' in video) {
video.srcObject = mediaStream;
}
video.onloadedmetadata = () => {
video.play();
};
}
// 摄像头打开失败
function errorFunc(error) {
debug('摄像头访问失败:', 'error.name:', error.name, 'error.message:', error.message);
// 重置摄像头按钮
setIsOpen(false);
// 具体错误处理
if (error.name === 'NotAllowedError') {
// msg.warning('请允许摄像头权限');
msg.warning('Please allow camera permissions');
} else if (error.name === 'NotFoundError') {
// msg.warning('未检测到摄像头设备');
msg.warning('No camera device detected');
}
// always check for errors at the end.
}
// 启动摄像头
const openMedia = () => {
// 1. 环境检测
if (!navigator.mediaDevices?.getUserMedia) {
// msg.error('浏览器不支持媒体设备API');
msg.error('Browser does not support media device API');
return;
}
// 2. 权限请求 打开摄像头
const opt = {
audio: false,
video: { width, height },
};
navigator.mediaDevices?.getUserMedia(opt).then(successFunc).catch(errorFunc);
setIsOpen(true);
setWaitingTakePic(true);
setImg('');
};
// 关闭摄像头
const closeMedia = () => {
const video = cameraVideoRef.current;
const stream = video.srcObject;
if ('getTracks' in stream) {
const tracks = stream?.getTracks();
tracks.forEach(track => {
track.stop();
setIsOpen(false);
});
}
};
const compressImage = () =>
// eslint-disable-next-line consistent-return
new Promise(resolve => {
// 获取图片资源
const video = cameraVideoRef.current;
const canvas = cameraCanvasRef.current;
if (canvas == null) {
return resolve(null);
}
const name = `${new Date().getTime()}.jpg`;
const type = 'image/jpeg';
const ctx = canvas.getContext('2d');
debug();
// 将图片画在画布上
// ctx.drawImage(video, 0, 0, 300, 150);
ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
// 画布转成blob格式的图片
canvas.toBlob(
blob => {
const dataURL = canvas.toDataURL(type);
const _file = new File([blob], name, { type });
debug('compressImage', {
type,
_file,
url: dataURL,
compressImage: true,
});
closeMedia();
setImg(dataURL);
resolve({ type, _file, url: dataURL, compressImage: true });
},
'image/jpeg',
0.5,
);
});
// 第二步:拍照完成回调
const saveMedia = async () => {
try {
// 保存到本地
const fileTemp = await compressImage();
setWaitingTakePic(false);
let flag = true;
let result = {};
if (props.onSelectCheck) {
result = await props.onSelectCheck({ file: fileTemp?._file });
flag = result.checkFlag;
}
setCompletedTakePic(true);
setCheckresult(result);
setStatusTakePic(flag ? 'Good' : 'Poor');
} catch (error) {
setCompletedTakePic(true);
setStatusTakePic('Poor');
}
};
// 第三步:提交图片
const submit = async () => {
if (isFunction(props.onSave)) {
props.onSave(checkresult);
}
};
useEffect(() => {
// openMedia(); // 打开摄像头
}, []);
return (
<div className={styles.cameraWrap}>
<div className="openAndCloseBtn">
{!isOpen ? (
<Button onClick={openMedia} className="btnBase">
{i18n('OPEN_CAMERA')}
</Button>
) : (
<Button onClick={closeMedia} className="btnBase">
{i18n('CLOSE_CAMERA')}
</Button>
)}
</div>
<div style={{ display: img ? 'none' : 'block' }}>
<div
className="PaiZhao video"
style={{ display: img ? 'none' : 'block' }}
>
<video id="cameraVideo" ref={cameraVideoRef} className="video">
<track kind="captions" srcLang="zh" label="label" />
</video>
</div>
<div
className="PaiZhao canvas"
style={{ display: img ? 'none' : 'block' }}
>
<canvas id="cameraCanvas" ref={cameraCanvasRef} className="canvas" />
</div>
</div>
{img && <img id="imgTag" src={img} alt="imgTag" />}
{!waitingTakePic &&
(!completedTakePic ? (
<div className={styles.checkPhotoBox}>
<div className={styles.checkLoading}>
<Iconfont type="icon-loading" size={32} color="#929292" />
</div>
<div className={styles.checkTitle}>
{i18n('CHECKING_THE_QUALITY')}
</div>
<div className={styles.checkDesc}>
{i18n('PLEASE_ENSURE_THAT_DETAILS_ARE_READABLE')}
</div>
</div>
) : (
qualityPhotoMap[statusTakePic]()
))}
{/* <div className={styles.btnBox}>
<Button onClick={saveMedia}>保存</Button>
<Button onClick={openMedia}>开启摄像头</Button>
<Button onClick={closeMedia}>关闭摄像头</Button>
</div> */}
<div className={styles.btnBox}>
{/* <div className={styles.btnRetryBox}>
<div onClick={this.onRetry} className={styles.btnRetry}>{i18n('RETAKE_PHOTO')}</div>
</div> */}
{img && (
<Button onClick={openMedia} className="btnBase mr-[16px]">
{i18n('RETAKE_PHOTO')}
</Button>
)}
{!img && (
<Button
disabled={!isOpen}
onClick={saveMedia}
className="btnBase mr-[16px]"
>
{i18n('SAVE')}
</Button>
)}
<Button disabled={!img} onClick={submit} className={styles.btnSave}>
{i18n('SUBMIT')}
</Button>
</div>
</div>
);
}