Skip to content

Commit

Permalink
perf: BarcodeDetector polyfill only if no native support
Browse files Browse the repository at this point in the history
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
  • Loading branch information
gruhn committed Sep 6, 2024
1 parent a5dbaa2 commit a5a0a2a
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 13 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
# Icon must end with two \r
Icon

# VitePress
.vitepress/cache
.vitepress/dist

# Thumbnails
._*
Expand Down
60 changes: 50 additions & 10 deletions src/misc/scanner.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
import { type DetectedBarcode, type BarcodeFormat, BarcodeDetector } from 'barcode-detector/pure'
import { type DetectedBarcode, type BarcodeFormat, BarcodeDetector, type BarcodeDetectorOptions } from 'barcode-detector/pure'
import { eventOn } from './callforth'
import { DropImageFetchError } from './errors'

declare global {
interface Window {
BarcodeDetector?: typeof BarcodeDetector
}
}

/**
* Singleton `BarcodeDetector` instance used by `QrcodeStream`. This is firtly to avoid
* the overhead of creating a new instances for scanning each frame. And secondly, the
* instances can seamlessly be replaced in the middle of the scanning process, if the
* `formats` prop of `QrcodeStream` is changed.
*
* This instance is not used by `QrcodeCapture` and `QrcodeDropZone`, because it may not
* have the right `formats` configured. For these components we create one-off `BarcodeDetector`
* instances because it does not happen so frequently anyway (see: `processFile`/`processUrl`).
*/
let barcodeDetector: BarcodeDetector
export const setScanningFormats = (formats: BarcodeFormat[]) => {
barcodeDetector = new BarcodeDetector({ formats })

/**
* Seamlessly updates the set of used barcode formats during scanning.
*/
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:
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 })
}
}

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

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

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

const image = await imageElementFromUrl(url)

Expand Down
4 changes: 2 additions & 2 deletions src/misc/shimGetUserMedia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { shimGetUserMedia as safariShim } from 'webrtc-adapter/dist/safari/safar
import { detectBrowser } from 'webrtc-adapter/dist/utils'

import { StreamApiNotSupportedError } from './errors'
import { indempotent } from './util'
import { idempotent } from './util'

export default indempotent(() => {
export default idempotent(() => {
const browserDetails = detectBrowser(window)

switch (browserDetails.browser) {
Expand Down
2 changes: 1 addition & 1 deletion src/misc/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Takes a function `action` and returns a new function, that behaves
* like action but when called a second time does nothing.
*/
export const indempotent = <T>(action: (x: any) => T) => {
export const idempotent = <T>(action: (x: any) => T) => {
let called = false
let result: T | undefined = undefined

Expand Down

0 comments on commit a5a0a2a

Please sign in to comment.