You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多摄像头手机中QuaggaJS扫码组件摄像头切换异常问题

解决QuaggaJS在多摄像头设备上频繁切换摄像头的问题

我开发的基于React的条形码扫码组件,使用@ericblade/quagga2实现,在单摄像头手机上运行正常,但在多摄像头新款手机上无法固定摄像头,设备持续在所有摄像头间切换。原代码如下:

import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import Quagga from "@ericblade/quagga2";
import adapter from "webrtc-adapter";
import "./BarcodeScanner.css";

const BarcodeScanner = (props) => {
  const navigate = useNavigate();

  useEffect(() => {
    startQuagga();
  }, []);

  if (
    !navigator.mediaDevices &&
    !(typeof navigator.mediaDevices.getUserMedia === "function")
  ) {
    console.log("getUserMedia function is not available in this browser.");
    props.onError("getUserMedia function is not available in this browser");
    return;
  }

  function startQuagga() {
    try{
      Quagga.init(
        {
          inputStream: {
            name: "Live",
            type: "LiveStream",
            target: document.querySelector("#interactive"),
            constraints: {
              width: 640,
              height: 480,
              facingMode: "environment",
              
            },
            
          },
          locate: true,
          decoder: {
            readers: ["upc_reader", "code_128_reader"],
            
          },
        },
        function (err) {
          if (err != null) {
            console.log(err);
            props.onError(err);
            stopScanner();
            return;
          }
          console.log("Initialization finished. Ready to start");
          Quagga.start();
        }
      );
    }catch {
      props.onError("Failed to open camera");
    }
    
  }

  Quagga.onDetected((data) =>  {
    let countDecodedCodes = 0;
    let err = 0;
    for (let id in data.codeResult.decodedCodes) {
      let error = data.codeResult.decodedCodes[id];
      if (error.error != undefined) {
        countDecodedCodes++;
        err += parseFloat(error.error);
      }
    }
    if (err / countDecodedCodes < 0.9) {
      props.onDetected(data.codeResult.code);
      Quagga.stop();
    } 
  });

  const stopScanner = () => {
    console.log("stopping Quagga")
    Quagga.stop();
  };


  useEffect(() => {
    if (props.showBottomSheet === "false") {
      stopScanner();
    } 
  }, [props.showBottomSheet]);

  return <div className="barcode-scanner viewport" id="interactive"></div>;
};

export default BarcodeScanner;

问题原因

仅设置facingMode: "environment"在多摄像头设备上无法精准锁定某一个后置摄像头,QuaggaJS会尝试枚举所有符合"environment"类型的摄像头,导致设备不断切换摄像头。

解决方案

通过navigator.mediaDevices.enumerateDevices()获取所有媒体设备,筛选出指定类型的摄像头ID,在Quagga初始化时明确指定该设备ID,避免自动切换。

修改后的代码

import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import Quagga from "@ericblade/quagga2";
import adapter from "webrtc-adapter";
import "./BarcodeScanner.css";

const BarcodeScanner = (props) => {
  const navigate = useNavigate();
  // 存储选中的摄像头ID
  const [selectedDeviceId, setSelectedDeviceId] = useState(null);

  useEffect(() => {
    // 先枚举设备再初始化Quagga
    enumerateCameras().then(() => {
      if (selectedDeviceId) {
        startQuagga();
      } else {
        props.onError("未找到可用的后置摄像头");
      }
    }).catch(err => {
      props.onError(`枚举摄像头失败: ${err.message}`);
    });

    // 组件卸载时清理Quagga事件监听
    return () => {
      Quagga.offDetected();
      stopScanner();
    };
  }, [selectedDeviceId]);

  // 枚举摄像头设备,筛选后置摄像头
  const enumerateCameras = async () => {
    const devices = await navigator.mediaDevices.enumerateDevices();
    const videoDevices = devices.filter(device => device.kind === "videoinput");
    // 找到label包含"back"或"后置"的摄像头(根据设备语言调整)
    const backCamera = videoDevices.find(device => 
      device.label.toLowerCase().includes("back") || 
      device.label.includes("后置")
    );
    if (backCamera) {
      setSelectedDeviceId(backCamera.deviceId);
    } else {
      // 若无法通过label识别,默认选第一个视频设备
      setSelectedDeviceId(videoDevices[0]?.deviceId);
    }
  };

  if (
    !navigator.mediaDevices &&
    !(typeof navigator.mediaDevices.getUserMedia === "function")
  ) {
    console.log("getUserMedia function is not available in this browser.");
    props.onError("getUserMedia function is not available in this browser");
    return null;
  }

  function startQuagga() {
    try {
      Quagga.init(
        {
          inputStream: {
            name: "Live",
            type: "LiveStream",
            target: document.querySelector("#interactive"),
            constraints: {
              width: 640,
              height: 480,
              facingMode: "environment",
              // 明确指定设备ID
              deviceId: selectedDeviceId
            },
          },
          locate: true,
          decoder: {
            readers: ["upc_reader", "code_128_reader"],
          },
        },
        function (err) {
          if (err != null) {
            console.log(err);
            props.onError(err);
            stopScanner();
            return;
          }
          console.log("Initialization finished. Ready to start");
          Quagga.start();
        }
      );
    } catch {
      props.onError("Failed to open camera");
    }
  }

  Quagga.onDetected((data) => {
    let countDecodedCodes = 0;
    let err = 0;
    for (let id in data.codeResult.decodedCodes) {
      let error = data.codeResult.decodedCodes[id];
      if (error.error != undefined) {
        countDecodedCodes++;
        err += parseFloat(error.error);
      }
    }
    if (err / countDecodedCodes < 0.9) {
      props.onDetected(data.codeResult.code);
      Quagga.stop();
    }
  });

  const stopScanner = () => {
    console.log("stopping Quagga")
    Quagga.stop();
  };

  useEffect(() => {
    if (props.showBottomSheet === "false") {
      stopScanner();
    }
  }, [props.showBottomSheet]);

  return <div className="barcode-scanner viewport" id="interactive"></div>;
};

export default BarcodeScanner;

关键修改点

  • 添加enumerateCameras函数,枚举所有视频输入设备,筛选后置摄像头ID
  • 在Quagga的constraints中新增deviceId: selectedDeviceId,固定使用指定摄像头
  • 组件卸载时清理Quagga的onDetected事件监听,避免内存泄漏
  • 调整初始化逻辑,确保拿到设备ID后再启动Quagga

内容的提问来源于stack exchange,提问作者svalaie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 17:45:29