多摄像头手机中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
相关产品推荐
相关产品推荐

