modified barcodeScanner to work in ipad
This commit is contained in:
Generated
+14
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<DetectedBarcode[]>;
|
||||
static getSupportedFormats(): Promise<string[]>;
|
||||
}
|
||||
}
|
||||
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<HTMLVideoElement>(null);
|
||||
const lastScan = useRef<string | null>(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<void>((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
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user