Skip to content

Commit aa7c5aa

Browse files
committed
perf: BarcodeDetector polyfill only if no native support
Still using polyfill for `QrcodeDropZone` and `QrcodeCapture` even on platforms with native `BarcodeDetector` is available, because on some of them `BarcodeDetector.detect` does not support `Blob` / `File` inputs. See: #447
1 parent 8272a5d commit aa7c5aa

File tree

4 files changed

+56
-13
lines changed

4 files changed

+56
-13
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
# Icon must end with two \r
2626
Icon
2727

28+
# VitePress
29+
.vitepress/cache
30+
.vitepress/dist
2831

2932
# Thumbnails
3033
._*

src/misc/scanner.ts

+50-10
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,44 @@
1-
import { type DetectedBarcode, type BarcodeFormat, BarcodeDetector } from 'barcode-detector/pure'
1+
import { type DetectedBarcode, type BarcodeFormat, BarcodeDetector, type BarcodeDetectorOptions } from 'barcode-detector/pure'
22
import { eventOn } from './callforth'
33
import { DropImageFetchError } from './errors'
44

5+
declare global {
6+
interface Window {
7+
BarcodeDetector?: typeof BarcodeDetector
8+
}
9+
}
10+
11+
/**
12+
* Singleton `BarcodeDetector` instance used by `QrcodeStream`. This is firtly to avoid
13+
* the overhead of creating a new instances for scanning each frame. And secondly, the
14+
* instances can seamlessly be replaced in the middle of the scanning process, if the
15+
* `formats` prop of `QrcodeStream` is changed.
16+
*
17+
* This instance is not used by `QrcodeCapture` and `QrcodeDropZone`, because it may not
18+
* have the right `formats` configured. For these components we create one-off `BarcodeDetector`
19+
* instances because it does not happen so frequently anyway (see: `processFile`/`processUrl`).
20+
*/
521
let barcodeDetector: BarcodeDetector
6-
export const setScanningFormats = (formats: BarcodeFormat[]) => {
7-
barcodeDetector = new BarcodeDetector({ formats })
22+
23+
/**
24+
* Seamlessly updates the set of used barcode formats during scanning.
25+
*/
26+
export function setScanningFormats(formats: BarcodeFormat[]) {
27+
// Only use the `BarcodeDetector` polyfill if the API is not supported natively.
28+
//
29+
// Note, that we can't just monkey patch the API on load, i.e.
30+
//
31+
// globalThis.BarcodeDetector ??= BarcodeDetector
32+
//
33+
// because that is not SSR compatible. If the polyfill is applied during SSR, then
34+
// it's actually missing at runtime. Thus, we have to check the API support at runtime:
35+
if (window.BarcodeDetector === undefined) {
36+
console.debug('[vue-qrcode-reader] BarcodeDetector not available: will use polyfill.')
37+
barcodeDetector = new BarcodeDetector({ formats })
38+
} else {
39+
console.debug('[vue-qrcode-reader] BarcodeDetector available: will use native API.')
40+
barcodeDetector = new window.BarcodeDetector({ formats })
41+
}
842
}
943

1044
type ScanHandler = (_: DetectedBarcode[]) => void
@@ -28,7 +62,7 @@ export const keepScanning = async (
2862
}
2963
) => {
3064
console.debug('[vue-qrcode-reader] start scanning')
31-
barcodeDetector = new BarcodeDetector({ formats })
65+
setScanningFormats(formats)
3266

3367
const processFrame =
3468
(state: { lastScanned: number; contentBefore: string[]; lastScanHadContent: boolean }) =>
@@ -140,9 +174,12 @@ export const processFile = async (
140174
file: File,
141175
formats: BarcodeFormat[] = ['qr_code']
142176
): Promise<DetectedBarcode[]> => {
143-
const barcodeDetector = new BarcodeDetector({
144-
formats
145-
})
177+
// To scan files/urls we use one-off `BarcodeDetector` instnaces,
178+
// since we don't scan as often as camera frames. Note, that we
179+
// always use the polyfill. This is because (at the time of writing)
180+
// some browser/OS combinations don't support `Blob`/`File` inputs
181+
// into the `detect` function.
182+
const barcodeDetector = new BarcodeDetector({ formats })
146183

147184
return await barcodeDetector.detect(file)
148185
}
@@ -151,9 +188,12 @@ export const processUrl = async (
151188
url: string,
152189
formats: BarcodeFormat[] = ['qr_code']
153190
): Promise<DetectedBarcode[]> => {
154-
const barcodeDetector = new BarcodeDetector({
155-
formats
156-
})
191+
// To scan files/urls we use one-off `BarcodeDetector` instnaces,
192+
// since we don't scan as often as camera frames. Note, that we
193+
// always use the polyfill. This is because (at the time of writing)
194+
// some browser/OS combinations don't support `Blob`/`File` inputs
195+
// into the `detect` function.
196+
const barcodeDetector = new BarcodeDetector({ formats })
157197

158198
const image = await imageElementFromUrl(url)
159199

src/misc/shimGetUserMedia.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import { shimGetUserMedia as safariShim } from 'webrtc-adapter/dist/safari/safar
88
import { detectBrowser } from 'webrtc-adapter/dist/utils'
99

1010
import { StreamApiNotSupportedError } from './errors'
11-
import { indempotent } from './util'
11+
import { idempotent } from './util'
1212

13-
export default indempotent(() => {
13+
export default idempotent(() => {
1414
const browserDetails = detectBrowser(window)
1515

1616
switch (browserDetails.browser) {

src/misc/util.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Takes a function `action` and returns a new function, that behaves
33
* like action but when called a second time does nothing.
44
*/
5-
export const indempotent = <T>(action: (x: any) => T) => {
5+
export const idempotent = <T>(action: (x: any) => T) => {
66
let called = false
77
let result: T | undefined = undefined
88

0 commit comments

Comments
 (0)