Skip to content

Commit

Permalink
fix: native BarocdeDetector doesn't support requested formats (#451)
Browse files Browse the repository at this point in the history
fix: native `BarocdeDetector` doesn't support requested formats

Previously we always used the native `BarocdeDetector` implementation if
available. However, the native API might be available but does not
support the barcode formats requested by the user. For example, `"qr_code"`
might be supported but the user wants to scan `["qr_code", "aztec"]`.
In these cases, we now also fallback to the polyfill implementation.

Closes #450
  • Loading branch information
gruhn authored Sep 20, 2024
1 parent aa7c5aa commit 7dcd6c8
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 20 deletions.
20 changes: 19 additions & 1 deletion docs/api/QrcodeStream.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,30 @@ However changing the value of `paused` resets this internal cache.
- **Default:** `['qr_code']`

The `formats` prop defines which barcode formats are detected.
[Supported Formats](https://github.com/Sec-ant/barcode-detector?tab=readme-ov-file#barcode-detector).
By default, only QR codes are selected,
so if you want to scan other barcode formats,
you have to modify this prop.
See: [supported formats](https://github.com/Sec-ant/barcode-detector?tab=readme-ov-file#barcode-detector).

```html
<qrcode-stream :formats="['qr_code', 'code_128']"></qrcode-stream>
```

::: warning
Don't select more barcode formats than needed.
Scanning becomes more expensive the more formats you select.
:::

Under the hood, we use the standard
[`BarcodeDetector`](https://developer.mozilla.org/en-US/docs/Web/API/BarcodeDetector)
browser API.
Support varies across devices, operating systems and browsers.
The component will prefer to use the native implementation if available and otherwise falls back to a polyfill implementation.
Note that even if the native implementation is availabe,
the component still might use the polyfill.
For example, if the native implementation only supports the
format `'qr_code'` but the you select the formats `['qr_code', 'aztec']`.

### `camera-on` <Badge text="since v5.0.0" type="info" />

- **Payload Type:** `Promise<MediaTrackCapabilities>`
Expand Down
6 changes: 3 additions & 3 deletions src/components/QrcodeStream.vue
Original file line number Diff line number Diff line change
Expand Up @@ -231,10 +231,10 @@ watch(
{ deep: true }
)
// Set formats will create a new BarcodeDetector instance with the given formats.
watch(formatsCached, (formats) => {
// `setScanningFormats` will create a new BarcodeDetector instance with the given formats.
watch(formatsCached, async formats => {
if (isMounted.value) {
setScanningFormats(formats)
await setScanningFormats(formats)
}
})
Expand Down
52 changes: 36 additions & 16 deletions src/misc/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,44 @@ declare global {
let barcodeDetector: BarcodeDetector

/**
* Seamlessly updates the set of used barcode formats during scanning.
* Constructs a `BarcodeDetector` instance, given a list of targeted barcode formats.
* Preferably, we want to use the native `BarcodeDetector` implementation if supported.
* Otherwise, we fall back to the polyfill implementation.
*
* Note, that we can't just monkey patch the polyfill on load, i.e.
*
* window.BarcodeDetector ??= BarcodeDetector
*
* for two reasons. Firstly, this is not SSR compatible, because `window` is not available
* during SSR. Secondly, even if the native implementation is availabe, we still might
* want to use the polyfill. For example, if the native implementation only supports the
* format `"qr_code"` but the user wants to scan `["qr_code", "aztec"]` (see #450).
*/
export function setScanningFormats(formats: BarcodeFormat[]) {
// Only use the `BarcodeDetector` polyfill if the API is not supported natively.
//
// Note, that we can't just monkey patch the API on load, i.e.
//
// globalThis.BarcodeDetector ??= BarcodeDetector
//
// because that is not SSR compatible. If the polyfill is applied during SSR, then
// it's actually missing at runtime. Thus, we have to check the API support at runtime:
async function createBarcodeDetector(formats: BarcodeFormat[]): Promise<BarcodeDetector> {
if (window.BarcodeDetector === undefined) {
console.debug('[vue-qrcode-reader] BarcodeDetector not available: will use polyfill.')
barcodeDetector = new BarcodeDetector({ formats })
} else {
console.debug('[vue-qrcode-reader] BarcodeDetector available: will use native API.')
barcodeDetector = new window.BarcodeDetector({ formats })
console.debug('[vue-qrcode-reader] Native BarcodeDetector not supported. Will use polyfill.')
return new BarcodeDetector({ formats })
}

const allSupportedFormats = await window.BarcodeDetector.getSupportedFormats()
const unsupportedFormats = formats.filter(format => !allSupportedFormats.includes(format))

if (unsupportedFormats.length > 0) {
console.debug(`[vue-qrcode-reader] Native BarcodeDetector does not support formats ${JSON.stringify(unsupportedFormats)}. Will use polyfill.`)
return new BarcodeDetector({ formats })
}

console.debug('[vue-qrcode-reader] Will use native BarcodeDetector.')
return new window.BarcodeDetector({ formats })
}

/**
* Update the set of targeted barcode formats. In particular, this function
* can be called during scanning and the camera stream doesn't have to be
* interrupted.
*/
export async function setScanningFormats(formats: BarcodeFormat[]) {
barcodeDetector = await createBarcodeDetector(formats)
}

type ScanHandler = (_: DetectedBarcode[]) => void
Expand All @@ -62,7 +82,7 @@ export const keepScanning = async (
}
) => {
console.debug('[vue-qrcode-reader] start scanning')
setScanningFormats(formats)
await setScanningFormats(formats)

const processFrame =
(state: { lastScanned: number; contentBefore: string[]; lastScanHadContent: boolean }) =>
Expand Down

0 comments on commit 7dcd6c8

Please sign in to comment.