All files / hooks useBarcodeScanner.ts

2.27% Statements 1/44
0% Branches 0/20
0% Functions 0/8
2.5% Lines 1/40

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78                      1x                                                                                                                                    
import { useEffect, useRef, useState } from 'react';
import { BrowserMultiFormatReader } from '@zxing/browser';
 
type BarcodeDetection = { rawValue: string };
type BarcodeDetectorClass = new () => { detect: (source: ImageBitmapSource) => Promise<BarcodeDetection[]> };
 
export interface BarcodeResult {
  code?: string;
  error?: string;
}
 
export const useBarcodeScanner = () => {
  const videoRef = useRef<HTMLVideoElement | null>(null);
  const [result, setResult] = useState<BarcodeResult>({});
 
  useEffect(() => {
    const reader = new BrowserMultiFormatReader();
    let active = true;
    let stop: (() => void) | undefined;
    let currentVideoElement: HTMLVideoElement | null = null;
 
    const start = async () => {
      try {
        const detectorClass = (
          window as typeof window & { BarcodeDetector?: BarcodeDetectorClass }
        ).BarcodeDetector;
        if (detectorClass) {
          const detector = new detectorClass();
          const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } });
          if (videoRef.current) {
            videoRef.current.srcObject = stream;
            await videoRef.current.play();
            currentVideoElement = videoRef.current;
          }
          const scan = async () => {
            if (!active || !videoRef.current) return;
            const tracks = (videoRef.current.srcObject as MediaStream | null)?.getVideoTracks();
            if (!tracks || tracks.length === 0) return;
            const frame = await createImageBitmap(videoRef.current);
            const codes = await detector.detect(frame);
            if (codes.length > 0) {
              setResult({ code: codes[0].rawValue });
            } else {
              requestAnimationFrame(scan);
            }
          };
          void scan();
        } else {
          const controls = await reader.decodeFromVideoDevice(
            undefined,
            videoRef.current ?? undefined,
            (decoded) => {
              if (decoded) {
                setResult({ code: decoded.getText() });
              }
            },
          );
          stop = () => controls.stop();
        }
      } catch (error) {
        setResult({ error: (error as Error).message });
      }
    };
 
    void start();
    return () => {
      active = false;
      stop?.();
      const stream = currentVideoElement?.srcObject as MediaStream | null;
      if (stream) {
        stream.getTracks().forEach((track) => track.stop());
      }
    };
  }, []);
 
  return { videoRef, result };
};