Open toniojst opened 6 years ago
In my application, I have an "open popup scanner" and a "close" button on the popup. Both buttons' onclick handler calls the function _scan.
Edit: ... the formatting of the code keeps getting messed up.
_scan = result => {
if (this.state.isScanning === true) {
this.setState({
open: false,
isScanning: false,
});
Quagga.offDetected();
Quagga.offProcessed();
document.getElementById('interactive').innerHTML = '';
Quagga.stop();
} else {
this.setState({
open: true,
isScanning: true,
});
setTimeout(this._componentDidMount, 100);
}
};
_componentDidMount = () => {
Quagga.init(
{
inputStream: {
type: 'LiveStream',
constraints: {
width: { min: this.state.width },
height: { min: this.state.height },
facingMode: 'environment',
aspectRatio: { min: 1, max: 2 },
},
},
locator: {
patchSize: 'large',
halfSample: false,
},
numOfWorkers: navigator.hardwareConcurrency || 4,
frequency: 10,
decoder: {
readers: [{ format: 'code_128_reader', config: {} }, { format: 'code_39_reader', config: {} }],
},
locate: true,
},
function(err) {
if (err) {
return managed_log(err);
}
Quagga.start();
},
);
I experienced a similar problem: https://github.com/serratus/quaggaJS/issues/281
Though I only experience it if I do it really rapidly. Ensuring that there's a 100 or so ms before allowing the user to call stop or start between, seems to prevent it. I suspect it may be external to Quagga, but there might be a way to alleviate it, I haven't done any digging.
Any luck with this issue?
No becuse i dont need it anymore.
I'm suffering from the same issue. I need to scan two different bar-code having the same type, but different validation to perform in onDetected. This bug thogether with bug #262 this lib is useless for me.
@hbinkle I don't exactly follow what you're trying to do that isn't working, could you provide some code or at least psuedo code?
Hello @ericblade, thanks for comming back on this. My Szenario is as follows:
import React, { useEffect, useRef, useState, useCallback } from 'react';
import { FormControl, Select, MenuItem, makeStyles, createStyles, Theme, TextField } from '@material-ui/core';
import Quagga, { QuaggaJSResultObject } from '@ericblade/quagga2'; // ES6
import SettingsIcon from '@material-ui/icons/Settings';
import { useDefaultProps, useOnScreen } from '../../globals/utils';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
container: {
// transformOrigin: 'top left',
// transform: scale(100%)
,
position: 'relative',
maxWidth: '100%',
//width: '640px',
},
cameraButton: {
marginTop: theme.spacing(0),
marginBottom: theme.spacing(1),
},
cameraButtonIcon: {
width: 16,
heigth: 16,
},
cameraButtonUse: {
//marginTop: -240, //half of the heigth of the video, because of the scale 0.5
marginBottom: theme.spacing(1),
width: 16,
heigth: '16px;important',
},
input: {
marginBottom: theme.spacing(1),
},
}),
);
// **
// using quagga2 derived from quaggajs: https://github.com/ericblade/quagga2
// https://github.com/serratus/quaggaJS
// https://serratus.github.io/quaggaJS/
// example code from: https://codesandbox.io/s/quaggajs-on-react-jw7mv
// **
const config = { inputStream: { type: 'LiveStream', constraints: { facingMode: 'environment', width: { ideal: 1920 }, aspectRatio: { min: 1, max: 100 }, deviceId: '', }, }, locator: { patchSize: 'medium', halfSample: true, }, numOfWorkers: 4, frequency: 10, decoder: { readers: ['code_128_reader'], // type of barcode, possible values: code_128_reader (default) ean_reader, ean_8_reader, code_39_reader, code_39_vin_reader, codabar_reader, upc_reader, upc_e_reader, i2of5_reader, 2of5_reader, code_93_reader }, locate: true, };
interface Device { groupId: string; deviceId: string; label: string; kind: string; }
/* *
@interface BarcodeScannerProps */ interface BarcodeScannerProps { active: boolean; scanContextName: string;
/**
/**
/**
showCameraSelection?: boolean; }
declare global { interface Window { quagga: { lastScannedCode: string; currentScanContext: string; }; } }
const beep = async () => { const snd = new Audio( 'data:audio/wav;base64,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qJrx4KOQjahZxWbcZgztj2c49nKmkId44S71j0c8eV9yDK6uPRzx5X18eDvjvQ6yKo9ZSS6l//8elePK/Lf//IInrOF/FvDoADYAGBMGb7FtErm5MXMlmPAJQVgWta7Zx2go+8xJ0UiCb8LHHdftWyLJE0QIAIsI+UbXu67dZMjmgDGCGl1H+vpF4NSDckSIkk7Vd+sxEhBQMRU8j/12UIRhzSaUdQ+rQU5kGeFxm+hb1oh6pWWmv3uvmReDl0UnvtapVaIzo1jZbf/pD6ElLqSX+rUmOQNpJFa/r+sa4e/pBlAABoAAAAA3CUgShLdGIxsY7AUABPRrgCABdDuQ5GC7DqPQCgbbJUAoRSUj+NIEig0YfyWUho1VBBBA//uQZB4ABZx5zfMakeAAAAmwAAAAF5F3P0w9GtAAACfAAAAAwLhMDmAYWMgVEG1U0FIGCBgXBXAtfMH10000EEEEEECUBYln03TTTdNBDZopopYvrTTdNa325mImNg3TTPV9q3pmY0xoO6bv3r00y+IDGid/9aaaZTGMuj9mpu9Mpio1dXrr5HERTZSmqU36A3CumzN/9Robv/Xx4v9ijkSRSNLQhAWumap82WRSBUqXStV/YcS+XVLnSS+WLDroqArFkMEsAS+eWmrUzrO0oEmE40RlMZ5+ODIkAyKAGUwZ3mVKmcamcJnMW26MRPgUw6j+LkhyHGVGYjSUUKNpuJUQoOIAyDvEyG8S5yfK6dhZc0Tx1KI/gviKL6qvvFs1+bWtaz58uUNnryq6kt5RzOCkPWlVqVX2a/EEBUdU1KrXLf40GoiiFXK///qpoiDXrOgqDR38JB0bw7SoL+ZB9o1RCkQjQ2CBYZKd/+VJxZRRZlqSkKiws0WFxUyCwsKiMy7hUVFhIaCrNQsKkTIsLivwKKigsj8XYlwt/WKi2N4d//uQRCSAAjURNIHpMZBGYiaQPSYyAAABLAAAAAAAACWAAAAApUF/Mg+0aohSIRobBAsMlO//Kk4soosy1JSFRYWaLC4qZBYWFRGZdwqKiwkNBVmoWFSJkWFxX4FFRQWR+LsS4W/rFRb/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////VEFHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU291bmRib3kuZGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAwNGh0dHA6Ly93d3cuc291bmRib3kuZGUAAAAAAAAAACU=', ); try { await snd.play(); } catch (e) { // do nothing, simply no beep } };
const resetSameCodeBlockIntervall = 3;
const useOnDetected = (onDetected: (code: string) => void, scanContextName: string) => { window.quagga = { lastScannedCode: '', currentScanContext: scanContextName }; // work around quagga bug #262 by setting a global scan context and ignore call out of context
const onDetectedCode = async (result: QuaggaJSResultObject, usedScanId: string) => { if (!onDetected) return; if (window.quagga.currentScanContext !== usedScanId) return; // work around quagga bug #262 const code = result.codeResult.code; if (window.quagga.lastScannedCode !== code) { window.quagga.lastScannedCode = code; await beep(); onDetected(code); setInterval(() => { window.quagga.lastScannedCode = ''; }, 1000 * resetSameCodeBlockIntervall); } }; return onDetectedCode; };
export const BarcodeScanner = (props: BarcodeScannerProps) => { const classes = useStyles({}); const { showCameraSelection } = useDefaultProps(props, { showCameraSelection: true, }); const onDetected = useOnDetected(props.onDetected, props.scanContextName);
const [selectedCamera, setSelectedCamera] = useState
const videoRef = useRef
const onProcessed = (result: QuaggaJSResultObject) => { const drawingCtx = Quagga.canvas.ctx.overlay, drawingCanvas = Quagga.canvas.dom.overlay; const width = Number(drawingCanvas.getAttribute('width')); const height = Number(drawingCanvas.getAttribute('height')); drawingCtx.clearRect(0, 0, width, height);
const marginVert = 120;
const marginHori = 60;
drawingCtx.beginPath();
drawingCtx.lineWidth = 10;
drawingCtx.strokeStyle = 'lightgreen';
drawingCtx.rect(marginHori, marginVert, width - 2 * marginHori, height - 2 * marginVert);
drawingCtx.stroke();
drawingCtx.beginPath();
drawingCtx.strokeStyle = 'red';
drawingCtx.moveTo(marginHori, height / 2);
drawingCtx.lineTo(width - marginHori, height / 2);
drawingCtx.stroke();
if (result) {
if (result.boxes) {
result.boxes
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.filter(function (box: any) {
return box !== result.box;
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.forEach(function (box: any) {
Quagga.ImageDebug.drawPath(box, { x: 0, y: 1 }, drawingCtx, {
color: 'red',
lineWidth: 2,
});
});
}
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, { x: 0, y: 1 }, drawingCtx, {
color: 'green',
lineWidth: 2,
});
}
if (result.codeResult && result.codeResult.code) {
Quagga.ImageDebug.drawPath(result.line, { x: 'x', y: 'y' }, drawingCtx, { color: 'green', lineWidth: 3 });
}
}
};
//detecting boxes on stream Quagga.onProcessed(onProcessed);
const doOnDetected = useCallback( async (result: QuaggaJSResultObject) => { if (isIntersecting) await onDetected(result, props.scanContextName); }, [isIntersecting, onDetected, props.scanContextName], ); Quagga.onDetected(doOnDetected);
const detectCameraAccess = React.useCallback(() => { const successCallback = function () { if (!mediaStreamActive) { setMediaStreamActive(true); console.log('Scanner camera acvtive.'); } }; const errorCallback = function (error: { name: string }) { if (error.name == 'NotAllowedError' || error.name == 'PermissionDismissedError') { if (mediaStreamActive) { setMediaStreamActive(false); console.error('Scanner camera: ' + error); } } }; if (navigator.mediaDevices) navigator.mediaDevices.getUserMedia({ video: true }).then(successCallback, errorCallback); else setMediaStreamActive(false); }, [mediaStreamActive]);
useEffect(() => { if (selectedCamera) config.inputStream.constraints.deviceId = selectedCamera;
if (props.active) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (!isQuaggaInitialized.current)
Quagga.init(config, (err) => {
if (err) {
console.log(err, 'error msg');
} else {
Quagga.start();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
isQuaggaInitialized.current = true;
return () => {
Quagga.offProcessed(onProcessed); // try to work around quagga bug #262
Quagga.offDetected(doOnDetected); // try to work around quagga bug #262
Quagga.stop();
};
}
});
else {
Quagga.start();
}
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (isQuaggaInitialized.current) {
Quagga.offProcessed(onProcessed); // try to work around quagga bug #262
Quagga.offDetected(doOnDetected); // try to work around quagga bug #262
Quagga.stop();
}
}
detectCameraAccess();
}, [detectCameraAccess, doOnDetected, onDetected, props, selectedCamera]);
const showSelection = showCameraSelection && mediaStreamActive;
return ( <> {/* // If you do not specify a target, // QuaggaJS would look for an element that matches // the CSS selector
<div id="BarcodeScannContainer" className={classes.container} hidden={!mediaStreamActive}>
<div id="interactive" className="viewport">
<video
ref={videoRef}
id="scannerVideoElem"
className="videoCamera"
autoPlay={true}
preload="auto"
src=""
muted={true}
playsInline={true}
style={{ maxWidth: '100%' }}
/>
{/* https://github.com/serratus/quaggaJS/issues/226 */}
<canvas
className="drawingBuffer"
style={{ position: 'absolute', top: 0, left: 0, opacity: 0.4, maxWidth: '100%' }}
/>
</div>
</div>
{showSelection && (
<CameraSelection
selectedCameraId={selectedCamera}
onSelectionChanged={(selected) => {
isQuaggaInitialized.current = false;
setSelectedCamera(selected);
}}
className={classes.cameraButtonUse}
/>
)}
</>
); };
interface CameraSelectionProps { selectedCameraId: string; onSelectionChanged: (cameraId: string) => void; className?: string; }
export const CameraSelection = (props: CameraSelectionProps) => {
const classes = useStyles({});
const [cameraMenuItems, setCameraMenuItems] = useState<JSX.Element[]>([]);
const [selectedCamera, setSelectedCamera] = useState
const buildCameraMenuItems = useCallback(() => { Quagga.CameraAccess.enumerateVideoDevices().then(function (devices) { if (cameraMenuItems.length > 0) return; devices.forEach(function (device: Device) { cameraMenuItems.push(
, ); }); if (cameraMenuItems.length > 0) { if (!selectedCamera) setSelectedCamera(devices[0].deviceId); setCameraMenuItems(cameraMenuItems); } }); }, [cameraMenuItems, selectedCamera]); useEffect(() => { if (cameraMenuItems.length === 0) buildCameraMenuItems(); }, [buildCameraMenuItems, cameraMenuItems.length]); const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => { const newSelCam = event.target.value as string; setSelectedCamera(newSelCam); props.onSelectionChanged(newSelCam); }; return (WOW, ok, that is quite a lot of code to chew on. I think there's a lot to learn from it too, things I didn't know, etc. There's definitely enough there that I can't just glance at it on my laptop and say "oh, here's the problem" or "ok, this is something we need to look into" or anything like that :-)
OK, so, i distinctly remember doing a basic test for this sort of problem, back when I first started working on the library, and was not able to duplicate it. So, I'm not sure if this is something inside Quagga that is either acting wrong, or acting unexpectedly at least, with more complex circumstances.. or if maybe it lies in the user side code .. i just don't know without taking a much closer look at it.
What I do know, is that my personal application that uses Quagga, routinely calls stop and start, at either user button press, or imperatively during an onDetected callback, and i am not receiving multiple calls to onDetected after it is restarted.
I've taken a quick look through my current source code on my fork, and i'm not seeing anything obvious that would prevent stop, or offDetected from working. My code doesn't necessarily reflect this repo, although i haven't tried to make any changes to this behavior presently.
OK, so, now I'm looking through what you've got there, and I do see one issue sticking out -- the code that looks like this
return () => {
Quagga.offProcessed(onProcessed); // try to work around quagga bug #262
Quagga.offDetected(doOnDetected); // try to work around quagga bug #262
Quagga.stop();
};
That function needs to be returned from your useEffect() function, not from the Quagga.init() callback.
I'm not sure without taking a closer look that I don't have time for right at the moment, if your other code that calls offDetected and stop() at the bottom of the useEffect() is ever being hit, though.
Unless I'm totally misreading that, it doesn't look like you're returning any function from useEffect(), and you should be returning a function that will undo the effect. At the very least, that will cause things to be rather strange when you unmount the component.
(i wrote this message over the course of about 20 minutes while digging through these things piece by piece, if it sounds a little weird at the top, once you get to the bottom :) )
@ericblade , thanks for you detailed answer and spending time on my code. That method growed and I was not seeing the forest for the trees. I moved that return down to the end of the useEffect function and now I can't reproduce my issues any longer in a dev build. But without the useEffect return it should have worked too, I guess. Because props.active is false right before the component gets unmounted. It's hit as expected. However obviously it does not have same effect. ---edit---- TOO fast with my answer. The problem is still there: in a prod build I still can reproduce the issue. Put my component an a modal dialog, scan a code, close the dialog. Repeat this 3-4 times. Then after 3rd or 4th time you will notice the beep sounding again and again until you close the browser or reload the page.
hmm. Sounds like some sort of a timing issue, doesn't really point either to or against a library problem. Maybe I'll see if I can insert what you've got into https://github.com/ericblade/quagga2-react-example as a minimal example, and see if I can figure out what's happening.
Something that might be interesting to try might be changing useEffect for useLayoutEffect there, as Quagga does sometimes do some things to the DOM on start. I don't really suspect that of being the source of this problem though.
Hello
I have a problem, I would like to load the Quagge script only when you click on a button, so I must run, when loading the page, Quagge.stop (); and when clicking button run Quagge.start () ;. But if I run Quagge.stop () me once Quagge.start (); does not work. So when I stop it, I can not run it anymore. So i test simple with alert functions by myself.
Quagga.start(); <-- this work alert('start'); <-- this work Quagga.stop(); <-- this work alert('stop'); <-- this work Quagga.start(); <-- this NOT work alert('start'); <-- this NOT work
I want to do that when Quagga.init I will add Quage.stop () and then
jQuery ('#scan-button'). on ("click", function () { alert ('start Quagge'); Quagga.start (); });
So what would be wrong?