@mleonard9/vin-scanner 1.3.0 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -38
- package/android/src/main/java/com/visioncamerabarcodescanner/VisionCameraBarcodeScannerModule.kt +26 -17
- package/lib/commonjs/ManualVinInput.js +146 -0
- package/lib/commonjs/ManualVinInput.js.map +1 -0
- package/lib/commonjs/PendingVinBanner.js +120 -0
- package/lib/commonjs/PendingVinBanner.js.map +1 -0
- package/lib/commonjs/TextVinPrompt.js +132 -0
- package/lib/commonjs/TextVinPrompt.js.map +1 -0
- package/lib/commonjs/haptics.js +36 -0
- package/lib/commonjs/haptics.js.map +1 -0
- package/lib/commonjs/index.js +184 -13
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/scanBarcodes.js.map +1 -1
- package/lib/commonjs/scanText.js.map +1 -1
- package/lib/commonjs/useVinScanner.js +164 -6
- package/lib/commonjs/useVinScanner.js.map +1 -1
- package/lib/commonjs/vinUtils.js +26 -12
- package/lib/commonjs/vinUtils.js.map +1 -1
- package/lib/module/ManualVinInput.js +138 -0
- package/lib/module/ManualVinInput.js.map +1 -0
- package/lib/module/PendingVinBanner.js +112 -0
- package/lib/module/PendingVinBanner.js.map +1 -0
- package/lib/module/TextVinPrompt.js +124 -0
- package/lib/module/TextVinPrompt.js.map +1 -0
- package/lib/module/haptics.js +27 -0
- package/lib/module/haptics.js.map +1 -0
- package/lib/module/index.js +171 -12
- package/lib/module/index.js.map +1 -1
- package/lib/module/scanBarcodes.js.map +1 -1
- package/lib/module/scanText.js.map +1 -1
- package/lib/module/useVinScanner.js +165 -7
- package/lib/module/useVinScanner.js.map +1 -1
- package/lib/module/vinUtils.js +26 -12
- package/lib/module/vinUtils.js.map +1 -1
- package/lib/typescript/src/ManualVinInput.d.ts +11 -0
- package/lib/typescript/src/ManualVinInput.d.ts.map +1 -0
- package/lib/typescript/src/PendingVinBanner.d.ts +17 -0
- package/lib/typescript/src/PendingVinBanner.d.ts.map +1 -0
- package/lib/typescript/src/TextVinPrompt.d.ts +20 -0
- package/lib/typescript/src/TextVinPrompt.d.ts.map +1 -0
- package/lib/typescript/src/haptics.d.ts +4 -0
- package/lib/typescript/src/haptics.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/scanBarcodes.d.ts.map +1 -1
- package/lib/typescript/src/scanText.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +46 -7
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/useVinScanner.d.ts +3 -1
- package/lib/typescript/src/useVinScanner.d.ts.map +1 -1
- package/lib/typescript/src/vinUtils.d.ts +7 -2
- package/lib/typescript/src/vinUtils.d.ts.map +1 -1
- package/package.json +5 -3
- package/src/ManualVinInput.tsx +184 -0
- package/src/PendingVinBanner.tsx +127 -0
- package/src/TextVinPrompt.tsx +147 -0
- package/src/haptics.ts +29 -0
- package/src/index.tsx +219 -22
- package/src/scanBarcodes.ts +5 -1
- package/src/scanText.ts +5 -1
- package/src/types.ts +55 -11
- package/src/useVinScanner.ts +209 -19
- package/src/vinUtils.ts +141 -82
- package/lib/commonjs/VinScannerOverlay.js +0 -60
- package/lib/commonjs/VinScannerOverlay.js.map +0 -1
- package/lib/module/VinScannerOverlay.js +0 -53
- package/lib/module/VinScannerOverlay.js.map +0 -1
- package/lib/typescript/src/VinScannerOverlay.d.ts +0 -14
- package/lib/typescript/src/VinScannerOverlay.d.ts.map +0 -1
- package/src/VinScannerOverlay.tsx +0 -55
package/src/index.tsx
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import React, { forwardRef, useMemo } from 'react';
|
|
1
|
+
import React, { forwardRef, useMemo, useCallback } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Camera as VisionCamera,
|
|
4
4
|
type ReadonlyFrameProcessor,
|
|
5
5
|
useFrameProcessor,
|
|
6
6
|
} from 'react-native-vision-camera';
|
|
7
7
|
import { useRunOnJS } from 'react-native-worklets-core';
|
|
8
|
+
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
9
|
+
import { runOnJS } from 'react-native-reanimated';
|
|
8
10
|
import { createBarcodeScannerPlugin } from './scanBarcodes';
|
|
9
11
|
import { createTextRecognitionPlugin } from './scanText';
|
|
10
12
|
import type {
|
|
@@ -35,11 +37,44 @@ export const Camera = forwardRef(function Camera(
|
|
|
35
37
|
const barcodeScanner = useBarcodeScanner(resolvedOptions.barcode);
|
|
36
38
|
const textScanner = useTextScanner(resolvedOptions.text);
|
|
37
39
|
|
|
40
|
+
const lastEmittedVin = React.useRef<string | null>(null);
|
|
41
|
+
const lastEmitTimestamp = React.useRef(0);
|
|
42
|
+
const lastFrameTimestampRef = React.useRef(0);
|
|
43
|
+
const frameCounterRef = React.useRef(0);
|
|
44
|
+
const sessionSeen = React.useRef<Set<string>>(new Set());
|
|
45
|
+
|
|
38
46
|
const handleDetections = useRunOnJS(
|
|
39
47
|
(payload: WorkletPayload) => {
|
|
40
48
|
const candidates = buildVinCandidates(payload, resolvedOptions);
|
|
41
|
-
const
|
|
49
|
+
const barcodeCandidates = candidates.filter(
|
|
50
|
+
(c) => c.source === 'barcode'
|
|
51
|
+
);
|
|
52
|
+
const textCandidates = candidates.filter((c) => c.source === 'text');
|
|
53
|
+
|
|
54
|
+
if (barcodeCandidates.length > 0) {
|
|
55
|
+
const first = pickFirstCandidate(barcodeCandidates, resolvedOptions);
|
|
56
|
+
callback({
|
|
57
|
+
timestamp: payload.timestamp,
|
|
58
|
+
duration: 0,
|
|
59
|
+
candidates: barcodeCandidates,
|
|
60
|
+
firstCandidate: first,
|
|
61
|
+
raw: {
|
|
62
|
+
barcodes: payload.barcodes ?? [],
|
|
63
|
+
textBlocks: payload.textBlocks ?? [],
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (
|
|
70
|
+
resolvedOptions.text.requireConfirmation &&
|
|
71
|
+
textCandidates.length > 0
|
|
72
|
+
) {
|
|
73
|
+
options?.onTextPending?.(textCandidates);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
42
76
|
|
|
77
|
+
const firstCandidate = pickFirstCandidate(candidates, resolvedOptions);
|
|
43
78
|
callback({
|
|
44
79
|
timestamp: payload.timestamp,
|
|
45
80
|
duration: 0,
|
|
@@ -51,41 +86,201 @@ export const Camera = forwardRef(function Camera(
|
|
|
51
86
|
},
|
|
52
87
|
});
|
|
53
88
|
},
|
|
54
|
-
[callback, resolvedOptions]
|
|
89
|
+
[callback, options?.onTextPending, resolvedOptions]
|
|
55
90
|
);
|
|
56
91
|
|
|
57
92
|
const scanBarcodes = barcodeScanner?.scanBarcodes;
|
|
58
93
|
const scanText = textScanner?.scanText;
|
|
59
94
|
|
|
95
|
+
const noBarcodeFrameCount = React.useRef(0);
|
|
96
|
+
const useBarcodeFallback = React.useRef(false);
|
|
97
|
+
|
|
60
98
|
const frameProcessor: ReadonlyFrameProcessor = useFrameProcessor(
|
|
61
99
|
(frame) => {
|
|
62
100
|
'worklet';
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
101
|
+
|
|
102
|
+
const now =
|
|
103
|
+
typeof frame?.timestamp === 'number' ? frame.timestamp : Date.now();
|
|
104
|
+
|
|
105
|
+
// Max FPS throttle (matches hook behavior)
|
|
106
|
+
const maxFps = resolvedOptions.detection.maxFrameRate;
|
|
107
|
+
if (maxFps > 0) {
|
|
108
|
+
const minInterval = 1000 / maxFps;
|
|
109
|
+
if (
|
|
110
|
+
lastFrameTimestampRef.current > 0 &&
|
|
111
|
+
now - lastFrameTimestampRef.current < minInterval
|
|
112
|
+
) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
lastFrameTimestampRef.current = now;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const orientationOverride =
|
|
119
|
+
resolvedOptions.detection.forceOrientation ?? null;
|
|
120
|
+
const regionOverride = resolvedOptions.detection.scanRegion ?? null;
|
|
121
|
+
|
|
122
|
+
// Frame quality gate (luma + sharpness)
|
|
123
|
+
const minLuma = resolvedOptions.detection.minLuma;
|
|
124
|
+
const minSharpness = resolvedOptions.detection.minSharpness;
|
|
125
|
+
if (minLuma > 0 || minSharpness > 0) {
|
|
126
|
+
const planes = (frame as any)?.planes;
|
|
127
|
+
if (planes?.length > 0) {
|
|
128
|
+
const yPlane = planes[0];
|
|
129
|
+
const bytes: Uint8Array | undefined = yPlane?.bytes;
|
|
130
|
+
const width: number | undefined = yPlane?.width;
|
|
131
|
+
const height: number | undefined = yPlane?.height;
|
|
132
|
+
const stride: number | undefined = yPlane?.bytesPerRow;
|
|
133
|
+
if (bytes && width && height && stride) {
|
|
134
|
+
let lumaSum = 0;
|
|
135
|
+
const sampleStep = 8;
|
|
136
|
+
const sampleCount = Math.floor(bytes.length / sampleStep);
|
|
137
|
+
for (let i = 0; i < bytes.length; i += sampleStep) {
|
|
138
|
+
lumaSum += bytes[i]!;
|
|
139
|
+
}
|
|
140
|
+
const meanLuma = lumaSum / Math.max(1, sampleCount);
|
|
141
|
+
if (minLuma > 0 && meanLuma < minLuma) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (minSharpness > 0) {
|
|
146
|
+
const step = Math.max(2, Math.floor(width / 96));
|
|
147
|
+
let sharpAccum = 0;
|
|
148
|
+
let sharpCount = 0;
|
|
149
|
+
for (let y = step; y < height - step; y += step) {
|
|
150
|
+
const row = y * stride;
|
|
151
|
+
for (let x = step; x < width - step; x += step) {
|
|
152
|
+
const idx = row + x;
|
|
153
|
+
const gx = bytes[idx + 1]! - bytes[idx - 1]!;
|
|
154
|
+
const gy = bytes[idx + stride]! - bytes[idx - stride]!;
|
|
155
|
+
sharpAccum += Math.abs(gx) + Math.abs(gy);
|
|
156
|
+
sharpCount += 1;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const sharpness = sharpAccum / Math.max(1, sharpCount);
|
|
160
|
+
if (sharpness < minSharpness) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const pluginArgs =
|
|
169
|
+
orientationOverride || regionOverride
|
|
170
|
+
? {
|
|
171
|
+
...(orientationOverride && { orientation: orientationOverride }),
|
|
172
|
+
...(regionOverride && { scanRegion: regionOverride }),
|
|
173
|
+
}
|
|
174
|
+
: undefined;
|
|
175
|
+
|
|
176
|
+
// Run barcode every frame
|
|
177
|
+
const barcodes = scanBarcodes
|
|
178
|
+
? scanBarcodes(
|
|
179
|
+
frame,
|
|
180
|
+
useBarcodeFallback.current ? { all: true } : pluginArgs
|
|
181
|
+
)
|
|
182
|
+
: [];
|
|
183
|
+
|
|
184
|
+
// Run text every Nth frame per textScanInterval
|
|
185
|
+
frameCounterRef.current += 1;
|
|
186
|
+
const frameIndex = frameCounterRef.current;
|
|
187
|
+
const textScanInterval = resolvedOptions.detection.textScanInterval;
|
|
188
|
+
const shouldRunText =
|
|
189
|
+
scanText &&
|
|
190
|
+
(textScanInterval <= 1 || frameIndex % textScanInterval === 0);
|
|
191
|
+
let textBlocks =
|
|
192
|
+
shouldRunText && scanText ? (scanText(frame, pluginArgs) ?? []) : [];
|
|
193
|
+
if (
|
|
194
|
+
scanText &&
|
|
195
|
+
(textBlocks ?? []).length === 0 &&
|
|
196
|
+
(barcodes ?? []).length === 0 &&
|
|
197
|
+
!shouldRunText
|
|
198
|
+
) {
|
|
199
|
+
textBlocks = scanText(frame, pluginArgs) ?? [];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const payload = {
|
|
66
203
|
barcodes: barcodes ?? [],
|
|
67
204
|
textBlocks: textBlocks ?? [],
|
|
68
|
-
timestamp:
|
|
69
|
-
|
|
70
|
-
|
|
205
|
+
timestamp: now,
|
|
206
|
+
} as WorkletPayload;
|
|
207
|
+
|
|
208
|
+
const candidates = buildVinCandidates(payload, resolvedOptions).filter(
|
|
209
|
+
(c) => c.confidence >= resolvedOptions.detection.minConfidence
|
|
210
|
+
);
|
|
211
|
+
const firstCandidate = pickFirstCandidate(candidates, resolvedOptions);
|
|
212
|
+
|
|
213
|
+
// Adaptive barcode fallback: if no barcode hits for N frames, scan all formats.
|
|
214
|
+
const hasBarcode = (payload.barcodes ?? []).length > 0;
|
|
215
|
+
if (!hasBarcode) {
|
|
216
|
+
noBarcodeFrameCount.current += 1;
|
|
217
|
+
if (
|
|
218
|
+
noBarcodeFrameCount.current >=
|
|
219
|
+
resolvedOptions.detection.barcodeFallbackAfter
|
|
220
|
+
) {
|
|
221
|
+
useBarcodeFallback.current = true;
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
noBarcodeFrameCount.current = 0;
|
|
225
|
+
useBarcodeFallback.current = false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Duplicate debounce (matches hook behavior)
|
|
229
|
+
if (firstCandidate) {
|
|
230
|
+
const debounceMs = resolvedOptions.duplicateDebounceMs ?? 1500;
|
|
231
|
+
const isDuplicate =
|
|
232
|
+
firstCandidate.value === lastEmittedVin.current &&
|
|
233
|
+
now - lastEmitTimestamp.current < debounceMs;
|
|
234
|
+
const seen = sessionSeen.current.has(firstCandidate.value);
|
|
235
|
+
|
|
236
|
+
if (isDuplicate || seen) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
lastEmittedVin.current = firstCandidate.value;
|
|
240
|
+
lastEmitTimestamp.current = now;
|
|
241
|
+
sessionSeen.current.add(firstCandidate.value);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
handleDetections(payload);
|
|
245
|
+
},
|
|
246
|
+
[scanBarcodes, scanText, handleDetections, resolvedOptions]
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
// Tap to focus callback
|
|
250
|
+
const focus = useCallback(
|
|
251
|
+
(point: { x: number; y: number }) => {
|
|
252
|
+
const camera = (ref as any)?.current;
|
|
253
|
+
if (camera == null) return;
|
|
254
|
+
camera.focus(point);
|
|
71
255
|
},
|
|
72
|
-
[
|
|
256
|
+
[ref]
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
// Create tap gesture for focus
|
|
260
|
+
const tapGesture = Gesture.Tap().onEnd(
|
|
261
|
+
({ x, y }: { x: number; y: number }) => {
|
|
262
|
+
runOnJS(focus)({ x, y });
|
|
263
|
+
}
|
|
73
264
|
);
|
|
74
265
|
|
|
75
266
|
return (
|
|
76
267
|
<>
|
|
77
268
|
{!!device && (
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
269
|
+
<GestureDetector gesture={tapGesture}>
|
|
270
|
+
<VisionCamera
|
|
271
|
+
pixelFormat="yuv"
|
|
272
|
+
ref={ref}
|
|
273
|
+
frameProcessor={frameProcessor}
|
|
274
|
+
device={device}
|
|
275
|
+
enableZoomGesture={true}
|
|
276
|
+
lowLightBoost={options?.cameraSettings?.lowLightBoost ?? true}
|
|
277
|
+
videoStabilizationMode={
|
|
278
|
+
options?.cameraSettings?.videoStabilizationMode ?? 'cinematic'
|
|
279
|
+
}
|
|
280
|
+
fps={Math.min(30, Math.max(24, options?.cameraSettings?.fps ?? 24))}
|
|
281
|
+
{...rest}
|
|
282
|
+
/>
|
|
283
|
+
</GestureDetector>
|
|
89
284
|
)}
|
|
90
285
|
</>
|
|
91
286
|
);
|
|
@@ -135,5 +330,7 @@ export type {
|
|
|
135
330
|
} from './types';
|
|
136
331
|
|
|
137
332
|
export { useVinScanner } from './useVinScanner';
|
|
138
|
-
export { VinScannerOverlay } from './VinScannerOverlay';
|
|
139
333
|
export { isValidVin } from './vinUtils';
|
|
334
|
+
export { TextVinPrompt } from './TextVinPrompt';
|
|
335
|
+
export { PendingVinBanner } from './PendingVinBanner';
|
|
336
|
+
export { ManualVinInput } from './ManualVinInput';
|
package/src/scanBarcodes.ts
CHANGED
|
@@ -116,7 +116,11 @@ const mergeArgs = (
|
|
|
116
116
|
'worklet';
|
|
117
117
|
const merged: BarcodePluginArgs = { ...baseArgs };
|
|
118
118
|
if (overrides) {
|
|
119
|
-
const {
|
|
119
|
+
const {
|
|
120
|
+
orientation: overrideOrientation,
|
|
121
|
+
scanRegion: overrideScanRegion,
|
|
122
|
+
...formatMap
|
|
123
|
+
} = overrides;
|
|
120
124
|
Object.entries(formatMap).forEach(([key, value]) => {
|
|
121
125
|
merged[key as BarcodeFormat] = value as boolean | undefined;
|
|
122
126
|
});
|
package/src/scanText.ts
CHANGED
|
@@ -90,7 +90,11 @@ const mergeTextArgs = (
|
|
|
90
90
|
...base,
|
|
91
91
|
};
|
|
92
92
|
if (overrides) {
|
|
93
|
-
const {
|
|
93
|
+
const {
|
|
94
|
+
orientation: overrideOrientation,
|
|
95
|
+
scanRegion: overrideScanRegion,
|
|
96
|
+
...rest
|
|
97
|
+
} = overrides;
|
|
94
98
|
Object.assign(merged, rest);
|
|
95
99
|
if (overrideScanRegion) {
|
|
96
100
|
merged.scanRegion = overrideScanRegion;
|
package/src/types.ts
CHANGED
|
@@ -102,7 +102,12 @@ export type BoundingBox = {
|
|
|
102
102
|
|
|
103
103
|
export type VinCandidateSource = 'barcode' | 'text';
|
|
104
104
|
|
|
105
|
-
export type VinTextOrigin =
|
|
105
|
+
export type VinTextOrigin =
|
|
106
|
+
| 'rawValue'
|
|
107
|
+
| 'displayValue'
|
|
108
|
+
| 'block'
|
|
109
|
+
| 'line'
|
|
110
|
+
| 'element';
|
|
106
111
|
|
|
107
112
|
export type VinCandidate = {
|
|
108
113
|
value: string;
|
|
@@ -112,9 +117,9 @@ export type VinCandidate = {
|
|
|
112
117
|
origin?: VinTextOrigin;
|
|
113
118
|
boundingBox?: BoundingBox;
|
|
114
119
|
rawPayload?:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
| BarcodeDetection
|
|
121
|
+
| TextDetection
|
|
122
|
+
| { resultText?: string; [key: string]: unknown };
|
|
118
123
|
};
|
|
119
124
|
|
|
120
125
|
export type VinScannerEvent = {
|
|
@@ -147,6 +152,17 @@ export type BarcodeScannerOptions = {
|
|
|
147
152
|
export type TextScannerOptions = {
|
|
148
153
|
enabled?: boolean;
|
|
149
154
|
language?: TextRecognitionLanguage;
|
|
155
|
+
/**
|
|
156
|
+
* When true, text VINs are surfaced via `onTextPending` and must be
|
|
157
|
+
* confirmed manually; barcode VINs still emit immediately.
|
|
158
|
+
* Default: false
|
|
159
|
+
*/
|
|
160
|
+
requireConfirmation?: boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Auto-dismiss pending text candidates after this many ms.
|
|
163
|
+
* Default: 5000
|
|
164
|
+
*/
|
|
165
|
+
pendingTtlMs?: number;
|
|
150
166
|
};
|
|
151
167
|
|
|
152
168
|
export type ScanRegion = {
|
|
@@ -196,9 +212,29 @@ export type DetectionOptions = {
|
|
|
196
212
|
/**
|
|
197
213
|
* Enable intelligent frame quality checks to skip blurry or dark frames.
|
|
198
214
|
* This improves accuracy by only processing high-quality frames.
|
|
199
|
-
*
|
|
215
|
+
* Note: currently unused; retained for backward compatibility.
|
|
200
216
|
*/
|
|
201
217
|
enableFrameQualityCheck?: boolean;
|
|
218
|
+
/**
|
|
219
|
+
* Minimum average luma (0-255) required to process the frame. Lower = darker.
|
|
220
|
+
* Set to 0 to disable.
|
|
221
|
+
*/
|
|
222
|
+
minLuma?: number;
|
|
223
|
+
/**
|
|
224
|
+
* Minimum sharpness score (simple gradient metric) required to process.
|
|
225
|
+
* Set to 0 to disable.
|
|
226
|
+
*/
|
|
227
|
+
minSharpness?: number;
|
|
228
|
+
/**
|
|
229
|
+
* Minimum candidate confidence required before emitting.
|
|
230
|
+
* Default: 0.6
|
|
231
|
+
*/
|
|
232
|
+
minConfidence?: number;
|
|
233
|
+
/**
|
|
234
|
+
* Frames without barcode hits before falling back to scanning all formats.
|
|
235
|
+
* Default: 45 frames.
|
|
236
|
+
*/
|
|
237
|
+
barcodeFallbackAfter?: number;
|
|
202
238
|
};
|
|
203
239
|
|
|
204
240
|
export type OverlayColors = {
|
|
@@ -211,11 +247,11 @@ export type OverlayColors = {
|
|
|
211
247
|
};
|
|
212
248
|
|
|
213
249
|
export type CameraSettings = {
|
|
214
|
-
/** Target FPS for camera. Default:
|
|
250
|
+
/** Target FPS for camera. Clamped to 24–30. Default: 24 */
|
|
215
251
|
fps?: number;
|
|
216
252
|
/** Enable low light boost. Default: true */
|
|
217
253
|
lowLightBoost?: boolean;
|
|
218
|
-
/** Video stabilization mode. Default: '
|
|
254
|
+
/** Video stabilization mode. Default: 'cinematic' */
|
|
219
255
|
videoStabilizationMode?: 'off' | 'standard' | 'cinematic' | 'auto';
|
|
220
256
|
};
|
|
221
257
|
|
|
@@ -225,13 +261,21 @@ export type VinScannerOptions = {
|
|
|
225
261
|
detection?: DetectionOptions;
|
|
226
262
|
onResult?: (result: VinCandidate[], event: VinScannerEvent) => void;
|
|
227
263
|
/**
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
|
|
264
|
+
* Called when text candidates are detected and `text.requireConfirmation` is true.
|
|
265
|
+
* Use this to show a confirmation UI and then call `confirmTextCandidate`.
|
|
266
|
+
*/
|
|
267
|
+
onTextPending?: (pending: VinCandidate[]) => void;
|
|
268
|
+
/**
|
|
269
|
+
* Enable built-in haptic notifications (if react-native-haptic-feedback is installed).
|
|
270
|
+
* Default: true
|
|
271
|
+
*/
|
|
272
|
+
haptics?: boolean;
|
|
273
|
+
/**
|
|
274
|
+
* Deprecated: overlay component removed. Ignored.
|
|
231
275
|
*/
|
|
232
276
|
showOverlay?: boolean;
|
|
233
277
|
/**
|
|
234
|
-
*
|
|
278
|
+
* Deprecated: overlay component removed. Ignored.
|
|
235
279
|
*/
|
|
236
280
|
overlayColors?: Partial<OverlayColors>;
|
|
237
281
|
/**
|