Video output not changed when changing source in zxing js library

Viewed 856

I have a react component:

import React, { useEffect, useState } from 'react';
import { BarcodeFormat, BrowserMultiFormatReader, DecodeHintType } from '@zxing/library';

function BarcodeScanner ( props )
{
    const [ videoInputDevices, setVideoInputDevices ] = useState ( [] );
    const [ selectedVideoDevice, selectVideoDevice ] = useState ( '' );
    const hints = new Map ();
    const formats = [ BarcodeFormat.CODE_128, BarcodeFormat.CODE_39, BarcodeFormat.EAN_8, BarcodeFormat.EAN_13 ];
    hints.set ( DecodeHintType.POSSIBLE_FORMATS, formats );
    const reader = new BrowserMultiFormatReader ( hints );
    useEffect ( () =>
    {
        ( async () =>
        {
            const videoInputDeviceList = await reader.listVideoInputDevices ();
            setVideoInputDevices ( videoInputDeviceList );
            if ( videoInputDeviceList.length > 0 )
            {
                selectVideoDevice ( videoInputDeviceList[ 0 ].deviceId );
            }
        } ) ();
    }, [] );

    useEffect ( () =>
    {
        if ( selectedVideoDevice )
        {
            reader.reset ();
            reader.decodeFromVideoDevice ( selectedVideoDevice, 'videoElement', ( res ) =>
            {
                if ( res )
                {
                    console.log ( 'result is', res );
                    alert ( res.getText () );
                }
            } ).then ( res => console.log ( 'result', res ) ).catch ( err => console.log ( 'error', err ) );
        }
    }, [ selectedVideoDevice ] );

    return <div
        onChange={ ( event ) =>
        {
            const deviceId = event.target.value;
            selectVideoDevice ( deviceId );
        } }>
        <select>
            {
                videoInputDevices.map ( ( inputDevice, index ) =>
                {
                    return <option value={ inputDevice.deviceId }
                                   key={ index }>
                        { inputDevice.label || inputDevice.deviceId }
                    </option>
                } )
            }
        </select>
        <br/>
        <video id="videoElement" width="600" height="400" style={ { border: "1px solid gray" } }/>
    </div>
}

export default BarcodeScanner;

The code is straightforward. I get the list of inputs and load them in a select field. On the first run I choose first video input device, and if user changes the device, barcode reader restarts and uses new input device.

The problem is when I change source of video on my android device from rear-facing camera to front-facing camera my video output does not change.

Thanks.

1 Answers

I did some testing with windows, linux and android, firefox and chrome based browsers and fixed some hickups and compile warnings. It seems that there where some initialization/lifetime issues that maybe caused your observation.

import React, { useMemo, useState } from "react";
import {
  BarcodeFormat,
  BrowserMultiFormatReader,
  DecodeHintType,
} from "@zxing/library";
import QrCodeParser from "./QrCodeParser";


function BarcodeScanner(props) {
  const [videoInputDevices, setVideoInputDevices] = useState([]);
  const [selectedVideoDevice, selectVideoDevice] = useState("");
  const [sum, setSum] = useState("");

  useMemo(() => {
    const hints = new Map();
    const formats = [BarcodeFormat.QR_CODE];
    hints.set(DecodeHintType.POSSIBLE_FORMATS, formats);
    const reader = new BrowserMultiFormatReader(hints);
    (async () => {
      const videoInputDeviceList = await reader.listVideoInputDevices();
      setVideoInputDevices(videoInputDeviceList);
      if (videoInputDeviceList.length > 0 && selectedVideoDevice==null) {
          selectVideoDevice(videoInputDeviceList[0].deviceId);
      }
    })();

    reader
      .decodeFromVideoDevice(selectedVideoDevice, "videoElement", (res) => {
        if (res) {
          const rawText = res.getText();
         
          setSum( rawText );
        }
      })
      .then((res) => console.log("result", res))
      .catch((err) => console.log("error", err));
  }, [selectedVideoDevice]);

  return (
    <div
      onChange={(event) => {
        const deviceId = event.target.value;
        selectVideoDevice(deviceId);
      }}
    >
      <select>
        {videoInputDevices.map((inputDevice, index) => {
          return (
            <option value={inputDevice.deviceId} key={index}>
              {inputDevice.label || inputDevice.deviceId}
            </option>
          );
        })}
      </select>
      <br />
      <video
        id="videoElement"
        width="600"
        height="400"
        style={{ border: "1px solid gray" }}
      />
      <div id="sum">{sum}</div>
    </div>
  );
}

Github

Related