From 8f219c251a19701e87a1a4b847851f0041505a74 Mon Sep 17 00:00:00 2001 From: IO <320154041@pcpuma.acatlan.unam.mx> Date: Wed, 12 Nov 2025 17:50:38 -0600 Subject: [PATCH] modified barcodeScanner to work in ipad --- package-lock.json | 14 +++ package.json | 1 + src/components/BarcodeScanner.tsx | 147 +++++++++++++++++++----------- 3 files changed, 109 insertions(+), 53 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6287dd9..25b4aa4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@ericblade/quagga2": "^1.8.4", + "@zxing/browser": "^0.1.5", "@zxing/library": "^0.21.3", "axios": "^1.13.2", "js-cookie": "^3.0.5", @@ -2218,11 +2219,24 @@ "win32" ] }, + "node_modules/@zxing/browser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@zxing/browser/-/browser-0.1.5.tgz", + "integrity": "sha512-4Lmrn/il4+UNb87Gk8h1iWnhj39TASEHpd91CwwSJtY5u+wa0iH9qS0wNLAWbNVYXR66WmT5uiMhZ7oVTrKfxw==", + "license": "MIT", + "optionalDependencies": { + "@zxing/text-encoding": "^0.9.0" + }, + "peerDependencies": { + "@zxing/library": "^0.21.0" + } + }, "node_modules/@zxing/library": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.21.3.tgz", "integrity": "sha512-hZHqFe2JyH/ZxviJZosZjV+2s6EDSY0O24R+FQmlWZBZXP9IqMo7S3nb3+2LBWxodJQkSurdQGnqE7KXqrYgow==", "license": "MIT", + "peer": true, "dependencies": { "ts-custom-error": "^3.2.1" }, diff --git a/package.json b/package.json index 1b2894b..4bb973a 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@ericblade/quagga2": "^1.8.4", + "@zxing/browser": "^0.1.5", "@zxing/library": "^0.21.3", "axios": "^1.13.2", "js-cookie": "^3.0.5", diff --git a/src/components/BarcodeScanner.tsx b/src/components/BarcodeScanner.tsx index 7a13a76..fc73af8 100644 --- a/src/components/BarcodeScanner.tsx +++ b/src/components/BarcodeScanner.tsx @@ -2,27 +2,7 @@ import { useEffect, useRef } from "react"; import toast from "react-hot-toast"; - -// 👇 Declaraciones globales para BarcodeDetector -declare global { - interface BarcodeDetectorOptions { - formats?: string[]; - } - - interface DetectedBarcode { - rawValue: string; - format: string; - cornerPoints?: DOMPoint[]; - boundingBox?: DOMRectReadOnly; - } - - class BarcodeDetector { - constructor(options?: BarcodeDetectorOptions); - detect(source: ImageBitmapSource): Promise; - static getSupportedFormats(): Promise; - } -} -export {}; +import { BrowserMultiFormatReader } from "@zxing/browser"; interface BarcodeScannerProps { onScan: (code: string) => void; @@ -32,75 +12,134 @@ export default function BarcodeScanner({ onScan }: BarcodeScannerProps) { const videoRef = useRef(null); const lastScan = useRef(null); - // 🔹 Verifica soporte antes de montar el componente - if (typeof window !== "undefined" && !("BarcodeDetector" in window)) { - toast.error("BarcodeDetector API no soportada en este navegador"); - return null; - } - useEffect(() => { let stream: MediaStream | null = null; - let detector: BarcodeDetector | null = null; let animationFrame: number; + let zxingReader: BrowserMultiFormatReader | null = null; + let detector: any = null; + + /** 🚀 Intentar usar BarcodeDetector API (moderno) */ + const startBarcodeDetector = async () => { + const supported = "BarcodeDetector" in window; + if (!supported) { + console.warn("BarcodeDetector no soportado, usando ZXing..."); + return startZXing(); + } - const startCamera = async () => { try { stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" }, }); if (!videoRef.current) return; - videoRef.current.srcObject = stream; - // Esperar a que el video esté listo antes de reproducirlo - await new Promise((resolve) => { - videoRef.current!.onloadedmetadata = () => resolve(true); + // ✅ Esperar a que realmente empiece a reproducirse + await new Promise((resolve) => { + videoRef.current!.onloadedmetadata = async () => { + try { + await videoRef.current!.play(); + resolve(); + } catch (err) { + console.error("Error al reproducir cámara:", err); + resolve(); + } + }; }); - await videoRef.current.play().catch((err) => { - console.warn("No se pudo reproducir automáticamente:", err); + detector = new (window as any).BarcodeDetector({ + formats: ["code_128", "ean_13", "qr_code"], }); - if (!("BarcodeDetector" in window)) { - console.error("BarcodeDetector API no soportada en este navegador"); - return; - } - - detector = new BarcodeDetector({ formats: ["code_128", "ean_13"] }); - const detect = async () => { - if (!videoRef.current || !detector) return; + if (!videoRef.current) return; + + // ❗ Safari puede lanzar Invalid element si no hay frame válido + if (videoRef.current.readyState < 2) { + animationFrame = requestAnimationFrame(detect); + return; + } + try { const barcodes = await detector.detect(videoRef.current); if (barcodes.length > 0) { - const rawValue = barcodes[0].rawValue.trim(); - if (rawValue && rawValue !== lastScan.current) { - lastScan.current = rawValue; - const processed = rawValue.startsWith("0") - ? rawValue.slice(1) - : rawValue; + const raw = barcodes[0].rawValue.trim(); + if (raw && raw !== lastScan.current) { + lastScan.current = raw; + const processed = raw.startsWith("0") ? raw.slice(1) : raw; onScan(processed); } } - } catch (err) { - console.error(err); + } catch (err: any) { + // Safari puede lanzar este error hasta que haya frame visible + if ( + err.message?.includes("Invalid element") || + err.name === "InvalidStateError" + ) { + // Ignorar y seguir intentando + } else { + console.error("Error detectando código:", err); + } } + animationFrame = requestAnimationFrame(detect); }; detect(); } catch (err) { - console.error("Error al iniciar cámara:", err); + console.error("Error con BarcodeDetector:", err); toast.error("No se pudo acceder a la cámara"); + startZXing(); } }; - startCamera(); + /** 🧩 Fallback con ZXing-JS (para iPad/Safari sin soporte nativo) */ + const startZXing = async () => { + try { + zxingReader = new BrowserMultiFormatReader(); + const devices = await BrowserMultiFormatReader.listVideoInputDevices(); + if (devices.length === 0) { + toast.error("No se detectó cámara disponible"); + return; + } + + const selectedDeviceId = devices[0].deviceId; + + await zxingReader.decodeFromVideoDevice( + selectedDeviceId, + videoRef.current!, + (result, err) => { + if (result) { + const code = result.getText().trim(); + if (code && code !== lastScan.current) { + lastScan.current = code; + const processed = code.startsWith("0") ? code.slice(1) : code; + onScan(processed); + } + } + } + ); + } catch (error) { + console.error("Error al iniciar ZXing:", error); + toast.error("Error al iniciar escáner"); + } + }; + + startBarcodeDetector(); + + /** 🧹 Limpieza */ return () => { if (animationFrame) cancelAnimationFrame(animationFrame); if (stream) stream.getTracks().forEach((t) => t.stop()); + + if (zxingReader) { + if (typeof (zxingReader as any).stopContinuousDecode === "function") { + (zxingReader as any).stopContinuousDecode(); + } else if (typeof (zxingReader as any).reset === "function") { + (zxingReader as any).reset(); + } + } }; }, [onScan]); @@ -115,6 +154,8 @@ export default function BarcodeScanner({ onScan }: BarcodeScannerProps) { borderRadius: "12px", objectFit: "cover", }} + muted + playsInline /> ); }