react18 PC调用电脑摄像头

'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>
  );
}