gruhn / vue-qrcode-reader

A set of Vue.js components for detecting and decoding QR codes.
https://gruhn.github.io/vue-qrcode-reader
MIT License
2.03k stars 330 forks source link

(in promise) DOMException: Failed to execute 'detect' on 'BarcodeDetector': Invalid element or state #353

Closed dofirfauzi closed 10 months ago

dofirfauzi commented 11 months ago

Describe the bug I try on chrome mobile device and safari desktop there is no error and work properly but scanner error on google chrome desktop. return error Uncaught (in promise) vue-qrcode-reader.js:2091 Uncaught (in promise) (in promise) DOMException: Failed to execute 'detect' on 'BarcodeDetector': Invalid element or state

Desktop (please complete the following information):

My Code Like This

<template>
  <div class="container mx-auto">
            <qrcode-stream
              :track="paintBoundingBox"
              @detect="onDetect"
              @error="onError"
            />
            <p class="error">{{ error }}</p>
            <p class="decode-result">
              Last result: <b>{{ result }}</b>
            </p>
    </div>
</template>
<script setup>
import { QrcodeStream } from "vue-qrcode-reader";

const result = ref("");
const error = ref("");

const onDetect = (detectedCodes) => {
  console.log(detectedCodes);

  const [firstCode] = detectedCodes;
  result.value = firstCode.rawValue;
};

const onError = (err) => {
  error.value = `[${err.name}]: `;

  if (err.name === "NotAllowedError") {
    error.value += "you need to grant camera access permission";
  } else if (err.name === "NotFoundError") {
    error.value += "no camera on this device";
  } else if (err.name === "NotSupportedError") {
    error.value += "secure context required (HTTPS, localhost)";
  } else if (err.name === "NotReadableError") {
    error.value += "is the camera already in use?";
  } else if (err.name === "OverconstrainedError") {
    error.value += "installed cameras are not suitable";
  } else if (err.name === "StreamApiNotSupportedError") {
    error.value += "Stream API is not supported in this browser";
  } else if (err.name === "InsecureContextError") {
    error.value +=
      "Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.";
  } else {
    error.value += err.message;
  }
};

const paintBoundingBox = (detectedCodes, ctx) => {
  for (const detectedCode of detectedCodes) {
    const {
      boundingBox: { x, y, width, height },
    } = detectedCode;

    ctx.lineWidth = 2;
    ctx.strokeStyle = "#007bff";
    ctx.strokeRect(x, y, width, height);
  }
};

</script>

<style>
.qrcode-stream-overlay {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
}
</style>
gruhn commented 11 months ago

Can you reproduce this issue on the demo page?

https://vue-qrcode-reader.netlify.app/

Sec-ant commented 11 months ago

I want to leave some thoughts here.

vue-qrcode-reader uses @sec-ant/barcode-detector as a polyfill of the Barcode Detection API. However the @sec-ant/barcode-detector package runs in a non-intrusive way:

globalThis.BarcodeDetector ??= BarcodeDetector;

So if there's a BarcodeDetector already defined in the global, the polyfill won't kick in.

Per the compatibility table on MDN, BarcodeDetector is fully supported on Chrome Android and not supported on Safari Desktop at all. So the polyfill is not needed in the former and is fully functional in the latter. However BarcodeDetector is partially supported on Chrome Desktop in a macOS system, so the polyfill in this case won't work and the browser native implementation is used. And this error is probably caused by the native implementation not supporting videos as input or sth like that.

But I have to say I don't have a macOS device and I'm not familiar with the vue-qrcode-reader code base, so I cannot say for sure this is the problem.

If this is true, the recent versions of @sec-ant/barcode-detector provide a subpath import which acts as a pure module. So you can explicitly import the polyfilled BarcodeDetector like this:

import { BarcodeDetector } from "@sec-ant/barcode-detector/pure";

And also override the native one explicitly:

globalThis.BarcodeDetector = BarcodeDetector;
dofirfauzi commented 10 months ago

Can you reproduce this issue on the demo page?

https://vue-qrcode-reader.netlify.app/ @gruhn

I try on same browser and device, chrome on mac os, there is no error on demo page. but on my app the scanner return error on vue-qrcode-reader.js line 2091

var Mi = async (n, {
  detectHandler: a,
  locateHandler: o,
  minDelay: u
}) => {
  const d = new BarcodeDetector({ formats: ["qr_code"] }), p = (m) => async (w) => {
    if (n.readyState > 1) {
      const { lastScanned: S, contentBefore: v, lastScanHadContent: b } = m;
      if (w - S < u)
        window.requestAnimationFrame(p(m));
      else {
        const $ = await d.detect(n), C = $.some((F) => !v.includes(F.rawValue));
        C && a($);
        const E = $.length > 0;
        E && o($), !E && b && o($);
        const A = {
          lastScanned: w,
          lastScanHadContent: E,
          // It can happen that a QR code is constantly in view of the camera but 
          // maybe a scanned frame is a bit blurry and we detect nothing but in the 
          // next frame we detect the code again. We also want to avoid emitting 
          // a `detect` event in such a case. So we don't reset `contentBefore`, 
          // if we detect nothing, only if we detect something new.
          contentBefore: C ? $.map((F) => F.rawValue) : v
        };
        window.requestAnimationFrame(p(A));
      }
    }
  };
  p({
    lastScanned: performance.now(),
    contentBefore: [],
    lastScanHadContent: false
  })(performance.now());
};
dofirfauzi commented 10 months ago

I want to leave some thoughts here.

vue-qrcode-reader uses @sec-ant/barcode-detector as a polyfill of the Barcode Detection API. However the @sec-ant/barcode-detector package runs in a non-intrusive way:

globalThis.BarcodeDetector ??= BarcodeDetector;

So if there's a BarcodeDetector already defined in the global, the polyfill won't kick in.

Per the compatibility table on MDN, BarcodeDetector is fully supported on Chrome Android and not supported on Safari Desktop at all. So the polyfill is not needed in the former and is fully functional in the latter. However BarcodeDetector is partially supported on Chrome Desktop in a macOS system, so the polyfill in this case won't work and the browser native implementation is used. And this error is probably caused by the native implementation not supporting videos as input or sth like that.

But I have to say I don't have a macOS device and I'm not familiar with the vue-qrcode-reader code base, so I cannot say for sure this is the problem.

If this is true, the recent versions of @sec-ant/barcode-detector provide a subpath import which acts as a pure module. So you can explicitly import the polyfilled BarcodeDetector like this:

import { BarcodeDetector } from "@sec-ant/barcode-detector/pure";

And also override the native one explicitly:

globalThis.BarcodeDetector = BarcodeDetector;

thanks @Sec-ant ,, your solution is work

gruhn commented 10 months ago

However BarcodeDetector is partially supported on Chrome Desktop in a macOS system, so the polyfill in this case won't work and the browser native implementation is used. And this error is probably caused by the native implementation not supporting videos as input or sth like that.

@Sec-ant thanks a lot. Let's follow your suggestion here also ( PR: #355 )

gruhn commented 10 months ago

alright closing this then