网页端调用摄像头+截图

5 阅读2分钟

前言

该文章记录一下工作中遇到的一些问题,后续将会逐步增加,所有内容均从网上整理而来,加上自己得理解做一个整合,方便工作中使用。

1.需求

  • 开启摄像头,生成视频
  • 截图
  • 关闭摄像头

2.代码


//检测是否有摄像头硬件
  const checkForCamera = async () => {
    try {
      const devices = await navigator.mediaDevices.enumerateDevices();
      const videoDevices = devices.filter(device => device.kind === 'videoinput');
      let hasCamera = videoDevices.length > 0

      if (!hasCamera) {
        console.log('未检测到摄像头设备,请检查硬件连接!')
      }
      return hasCamera
    } catch (error) {
      console.log(error)
    }
  };
  
  //检测浏览器调用摄像头权限
  const checkPermissionStatus = async () => {
    if (!navigator.permissions) {
      return "unknown"; // 旧浏览器兼容性处理
    }
    try {
      const status = await navigator.permissions.query({
        name: "camera" 
      });
      return status;
    } catch (err) {
      console.error("权限查询失败:", err);
      return "unknown";
    }
  };
  
  //根据摄像头权限状态-处理
  const handlePermissionStatus = (status:string) => {
    switch (status) {
      case "granted":
        startCamera(); // 权限允许时启动摄像头
        break;
      case "denied":
        // 权限拒绝时显示引导弹窗
        console.log("摄像头权限被拒绝,请在浏览器设置中开启权限");
        break;
      case "prompt":
        // 权限未决(首次请求),尝试获取权限
        startCamera();
        break;
      default:
        console.log('浏览器版本不支持!')
        break;
    }
  };
    
  // 启动摄像头
  const startCamera = async () => {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ 
        video: {  width: 400, height: 260 },
        audio: false 
      });
      //MediaStream对象挂载在window上,好全局删除
      window.floatCameraMediaStream = stream
      
      //将stream(视频流)赋值给video元素的srcObject属性
      if (videoRef.current) {
        videoRef.current.srcObject = stream;
        videoRef.current.play();
      }
    } catch (error) {
      showPermissionModal('开启摄像头失败!')
      setLoading(false);
    }
  };  
    
   // 停止摄像头
  const stopCamera = () => {
  
    // 方案一:
     let videoStream = window.floatCameraMediaStream 
     if (videoStream) {
       if (typeof videoStream.stop === "function") {
         videoStream.stop();
       } else {
         videoStream.getTracks().forEach(track => track.stop());
       }
     }
     
    //方案二
    if (videoRef.current && videoRef.current.srcObject) {
      const stream = videoRef.current.srcObjec;
      stream.getTracks().forEach(track => track.stop());
      videoRef.current.srcObject = null;
    }
  };   
    
    
    
  const init = async ()=>{
    try {
      //1.检测是否有摄像头
      let hasCamera = await checkForCamera()
      if(!hasCamera) return

      //2.检测权限
      let hasRootStatus:any  = await checkPermissionStatus()
      handlePermissionStatus(hasRootStatus.state)
    } catch (error) {
      console.log(error)
    }finally{
      startInterval()
    }
  }  
  
  // 截图功能
  const shotImg = async () => {
    if (!videoRef.current || !canvasRef.current) return;
    
    try {
      const video = videoRef.current;
      const canvas = canvasRef.current;
      
      // 适配设备像素比以获得更高清截图
      const scale = window.devicePixelRatio || 1;
      
      // 设置canvas尺寸为视频尺寸的2倍(提高清晰度)
      canvas.width = video.videoWidth * scale;
      canvas.height = video.videoHeight * scale;
      
      // 获取绘图上下文并应用缩放
      const ctx = canvas.getContext('2d');
      if (ctx) {
        // 应用缩放
        ctx.scale(scale, scale);
        
        // 启用抗锯齿
        ctx.imageSmoothingEnabled = true;
        ctx.imageSmoothingQuality = 'high';
        
        // 绘制当前视频帧到canvas
        ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
        
        // 将canvas内容转换为高质量JPEG图片URL
        const imageUrl = canvas.toDataURL('image/jpeg', 1); // 质量参数0.95 (0-1)
        
        message.success('截图成功',imageUrl);
      }
    } catch (error) {
      message.error('截图失败,请重试');
    } 
  };