@scr2em/capacitor-scanner 6.0.30 → 6.0.33

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 CHANGED
@@ -262,7 +262,7 @@ Set the camera zoom level.
262
262
 
263
263
  #### StartOptions
264
264
 
265
- <code>{ /** * Barcode formats to detect when barcodeDetection is enabled. */ formats?: BarcodeFormat[]; /** * Camera direction to use. Defaults to 'BACK'. */ cameraDirection?: 'BACK' | 'FRONT'; /** * Enable barcode detection on start. Defaults to false. * When enabled, the camera will automatically detect barcodes and emit 'barcodeScanned' events. */ barcodeDetection?: boolean; /** * Whether to show the highlight overlay for detected barcodes. Defaults to false. * Only applies when barcodeDetection is true. */ barcodeHighlight?: boolean; /** * Enable object detection (business card) on start. Defaults to false. * When enabled, the camera will automatically detect business cards and emit 'rectangleDetected' events. */ objectDetection?: boolean; /** * Whether to show the highlight overlay for detected business cards. Defaults to true. * Only applies when objectDetection is true. */ objectHighlight?: boolean; /** * Minimum interval in seconds between consecutive 'rectangleDetected' events. * Only applies when objectDetection is true. * Defaults to 0 (no throttling beyond initial stability check). */ rectangleEmitIntervalSeconds?: number; /** * Optional regex pattern to filter scanned barcodes. * If provided, only barcodes matching this pattern will be reported. */ regex?: string; /** * Optional regex flags (e.g., 'i' for case-insensitive, 'm' for multiline). */ regexFlags?: string; }</code>
265
+ <code>{ /** * Barcode formats to detect when barcodeDetection is enabled. */ formats?: BarcodeFormat[]; /** * Camera direction to use. Defaults to 'BACK'. */ cameraDirection?: 'BACK' | 'FRONT'; /** * Enable barcode detection on start. Defaults to false. * When enabled, the camera will automatically detect barcodes and emit 'barcodeScanned' events. */ barcodeDetection?: boolean; /** * Whether to show the highlight overlay for detected barcodes. Defaults to false. * Only applies when barcodeDetection is true. */ barcodeHighlight?: boolean; /** * Enable object detection (business card) on start. Defaults to false. * When enabled, the camera will automatically detect business cards and emit 'rectangleDetected' events. */ objectDetection?: boolean; /** * Whether to show the highlight overlay for detected business cards. Defaults to true. * Only applies when objectDetection is true. */ objectHighlight?: boolean; /** * Minimum interval in seconds between consecutive 'rectangleDetected' events. * Only applies when objectDetection is true. * Defaults to 0 (no throttling beyond initial stability check). */ rectangleEmitIntervalSeconds?: number; /** * Optional regex pattern to filter scanned barcodes. * If provided, only barcodes matching this pattern will be reported. */ regex?: string; /** * Optional regex flags (e.g., 'i' for case-insensitive, 'm' for multiline). */ regexFlags?: string; /** * Cooldown in seconds after any detection event when both barcode and object detection are enabled. * After a barcode or rectangle is emitted, ALL detection pauses for this duration. * When set, barcode dedup TTL becomes 2x this value and rectangle emit interval becomes 2x this value. * Only applies when both barcodeDetection and objectDetection are true. * Defaults to 0 (no throttle, both detections run simultaneously). */ detectionThrottleSeconds?: number; }</code>
266
266
 
267
267
 
268
268
  #### CapturePhotoOptions
@@ -92,7 +92,7 @@ public class CapacitorScannerPlugin extends Plugin {
92
92
  private ProcessCameraProvider cameraProvider;
93
93
  private BarcodeScanner scanner;
94
94
  private RectangleDetector rectangleDetector;
95
- private final TTLMap<String, VoteStatus> scannedCodesVotes = new TTLMap<>(5000); // 5 second TTL
95
+ private TTLMap<String, VoteStatus> scannedCodesVotes = new TTLMap<>(5000); // 5 second TTL
96
96
  private final int voteThreshold = 2;
97
97
  private final Executor executor = Executors.newSingleThreadExecutor();
98
98
  private final AtomicBoolean isScanning = new AtomicBoolean(false);
@@ -143,6 +143,11 @@ public class CapacitorScannerPlugin extends Plugin {
143
143
  // Configurable interval between rectangle detection event emissions (in milliseconds)
144
144
  private long rectangleEmitIntervalMs = 0; // Default: 0 (no throttling)
145
145
 
146
+ // Detection throttle: pause ALL detection after any emit (when both types enabled)
147
+ private long detectionThrottleMs = 0; // Default: 0 (no throttle)
148
+ private long lastDetectionEmitTime = 0;
149
+ private boolean bothDetectionsEnabled = false;
150
+
146
151
  /**
147
152
  * Calculates the processing interval in milliseconds based on the desired
148
153
  * frequency per second.
@@ -209,25 +214,47 @@ public class CapacitorScannerPlugin extends Plugin {
209
214
  }
210
215
  }
211
216
 
217
+ // Read detection throttle
218
+ if (call.getData().has("detectionThrottleSeconds")) {
219
+ try {
220
+ double throttleSeconds = call.getData().getDouble("detectionThrottleSeconds");
221
+ detectionThrottleMs = Math.max(0, (long) (throttleSeconds * 1000));
222
+ } catch (Exception e) {
223
+ detectionThrottleMs = 0;
224
+ }
225
+ } else {
226
+ detectionThrottleMs = 0;
227
+ }
228
+ lastDetectionEmitTime = 0;
229
+ bothDetectionsEnabled = enableBarcodeDetection && enableObjectDetection;
230
+
212
231
  // Enable object detection (businessCard) if requested
213
232
  if (enableObjectDetection) {
214
233
  enabledDetectionTypes.add("businessCard");
215
234
  if (enableObjectHighlight) {
216
235
  enabledHighlightTypes.add("businessCard");
217
236
  }
218
- // Read rectangleEmitIntervalSeconds if provided
237
+ // Read rectangleEmitIntervalSeconds: use 2X when both enabled with throttle
238
+ long emitInterval = 0;
219
239
  if (call.getData().has("rectangleEmitIntervalSeconds")) {
220
240
  try {
221
241
  double intervalSeconds = call.getData().getDouble("rectangleEmitIntervalSeconds");
222
- rectangleEmitIntervalMs = Math.max(0, (long) (intervalSeconds * 1000));
223
- echo("Using custom rectangleEmitIntervalSeconds: " + intervalSeconds + "s (" + rectangleEmitIntervalMs + "ms)");
242
+ emitInterval = Math.max(0, (long) (intervalSeconds * 1000));
224
243
  } catch (Exception e) {
225
- echo("Error parsing rectangleEmitIntervalSeconds: " + e.getMessage() + ". Using default 0ms");
226
- rectangleEmitIntervalMs = 0;
244
+ emitInterval = 0;
227
245
  }
228
- } else {
229
- rectangleEmitIntervalMs = 0;
230
246
  }
247
+ if (bothDetectionsEnabled && detectionThrottleMs > 0) {
248
+ emitInterval = Math.max(emitInterval, detectionThrottleMs * 2);
249
+ }
250
+ rectangleEmitIntervalMs = emitInterval;
251
+ }
252
+
253
+ // When both enabled with throttle, set barcode votes TTL to 2X
254
+ if (bothDetectionsEnabled && detectionThrottleMs > 0) {
255
+ scannedCodesVotes = new TTLMap<>(detectionThrottleMs * 2);
256
+ } else {
257
+ scannedCodesVotes = new TTLMap<>(5000);
231
258
  }
232
259
 
233
260
  try {
@@ -464,8 +491,18 @@ public class CapacitorScannerPlugin extends Plugin {
464
491
  try {
465
492
  InputImage image = InputImage.fromMediaImage(mediaImage, rotationDegrees);
466
493
 
467
- boolean barcodeEnabled = scanner != null && enabledDetectionTypes.contains("barcode");
468
- boolean rectangleEnabled = rectangleDetector != null && enabledDetectionTypes.contains("businessCard");
494
+ // Check if detection is throttled (cooldown after any emit when both types enabled)
495
+ boolean isThrottled = bothDetectionsEnabled
496
+ && detectionThrottleMs > 0
497
+ && lastDetectionEmitTime > 0
498
+ && (System.currentTimeMillis() - lastDetectionEmitTime) < detectionThrottleMs;
499
+
500
+ boolean barcodeEnabled = scanner != null
501
+ && enabledDetectionTypes.contains("barcode")
502
+ && !isThrottled;
503
+ boolean rectangleEnabled = rectangleDetector != null
504
+ && enabledDetectionTypes.contains("businessCard")
505
+ && !isThrottled;
469
506
 
470
507
  // Rectangle detection (synchronous) - runs first while imageProxy is still open
471
508
  if (rectangleEnabled) {
@@ -876,6 +913,10 @@ public class CapacitorScannerPlugin extends Plugin {
876
913
  data.put("scannedCode", rawValue);
877
914
  data.put("format", BarcodeFormatHelper.barcodeFormatToString(format));
878
915
  notifyListeners("barcodeScanned", data, true);
916
+
917
+ // Record emit time for detection throttle
918
+ lastDetectionEmitTime = System.currentTimeMillis();
919
+
879
920
  echo("Barcode " + rawValue + " scanned with format: "
880
921
  + BarcodeFormatHelper.barcodeFormatToString(format));
881
922
  }
@@ -913,6 +954,8 @@ public class CapacitorScannerPlugin extends Plugin {
913
954
 
914
955
  // Update the last notification timestamp
915
956
  lastRectangleNotificationTime = currentTime;
957
+ // Record emit time for detection throttle
958
+ lastDetectionEmitTime = currentTime;
916
959
  } else {
917
960
  echo("Skipping notification - last one was less than 1 second ago");
918
961
  }
@@ -1150,6 +1193,10 @@ public class CapacitorScannerPlugin extends Plugin {
1150
1193
  lastRectangleProcessingTime = 0;
1151
1194
  lastRectangleDetectionTime = 0;
1152
1195
  rectangleEmitIntervalMs = 0;
1196
+ detectionThrottleMs = 0;
1197
+ lastDetectionEmitTime = 0;
1198
+ bothDetectionsEnabled = false;
1199
+ scannedCodesVotes = new TTLMap<>(5000);
1153
1200
 
1154
1201
  // Clear detection histories and caches
1155
1202
  enabledDetectionTypes.clear();
package/dist/docs.json CHANGED
@@ -344,7 +344,7 @@
344
344
  "docs": "",
345
345
  "types": [
346
346
  {
347
- "text": "{\n /**\n * Barcode formats to detect when barcodeDetection is enabled.\n */\n formats?: BarcodeFormat[];\n /**\n * Camera direction to use. Defaults to 'BACK'.\n */\n cameraDirection?: 'BACK' | 'FRONT';\n /**\n * Enable barcode detection on start. Defaults to false.\n * When enabled, the camera will automatically detect barcodes and emit 'barcodeScanned' events.\n */\n barcodeDetection?: boolean;\n /**\n * Whether to show the highlight overlay for detected barcodes. Defaults to false.\n * Only applies when barcodeDetection is true.\n */\n barcodeHighlight?: boolean;\n /**\n * Enable object detection (business card) on start. Defaults to false.\n * When enabled, the camera will automatically detect business cards and emit 'rectangleDetected' events.\n */\n objectDetection?: boolean;\n /**\n * Whether to show the highlight overlay for detected business cards. Defaults to true.\n * Only applies when objectDetection is true.\n */\n objectHighlight?: boolean;\n /**\n * Minimum interval in seconds between consecutive 'rectangleDetected' events.\n * Only applies when objectDetection is true.\n * Defaults to 0 (no throttling beyond initial stability check).\n */\n rectangleEmitIntervalSeconds?: number;\n /**\n * Optional regex pattern to filter scanned barcodes.\n * If provided, only barcodes matching this pattern will be reported.\n */\n regex?: string;\n /**\n * Optional regex flags (e.g., 'i' for case-insensitive, 'm' for multiline).\n */\n regexFlags?: string;\n}",
347
+ "text": "{\n /**\n * Barcode formats to detect when barcodeDetection is enabled.\n */\n formats?: BarcodeFormat[];\n /**\n * Camera direction to use. Defaults to 'BACK'.\n */\n cameraDirection?: 'BACK' | 'FRONT';\n /**\n * Enable barcode detection on start. Defaults to false.\n * When enabled, the camera will automatically detect barcodes and emit 'barcodeScanned' events.\n */\n barcodeDetection?: boolean;\n /**\n * Whether to show the highlight overlay for detected barcodes. Defaults to false.\n * Only applies when barcodeDetection is true.\n */\n barcodeHighlight?: boolean;\n /**\n * Enable object detection (business card) on start. Defaults to false.\n * When enabled, the camera will automatically detect business cards and emit 'rectangleDetected' events.\n */\n objectDetection?: boolean;\n /**\n * Whether to show the highlight overlay for detected business cards. Defaults to true.\n * Only applies when objectDetection is true.\n */\n objectHighlight?: boolean;\n /**\n * Minimum interval in seconds between consecutive 'rectangleDetected' events.\n * Only applies when objectDetection is true.\n * Defaults to 0 (no throttling beyond initial stability check).\n */\n rectangleEmitIntervalSeconds?: number;\n /**\n * Optional regex pattern to filter scanned barcodes.\n * If provided, only barcodes matching this pattern will be reported.\n */\n regex?: string;\n /**\n * Optional regex flags (e.g., 'i' for case-insensitive, 'm' for multiline).\n */\n regexFlags?: string;\n /**\n * Cooldown in seconds after any detection event when both barcode and object detection are enabled.\n * After a barcode or rectangle is emitted, ALL detection pauses for this duration.\n * When set, barcode dedup TTL becomes 2x this value and rectangle emit interval becomes 2x this value.\n * Only applies when both barcodeDetection and objectDetection are true.\n * Defaults to 0 (no throttle, both detections run simultaneously).\n */\n detectionThrottleSeconds?: number;\n}",
348
348
  "complexTypes": [
349
349
  "BarcodeFormat"
350
350
  ]
@@ -114,6 +114,14 @@ export declare type StartOptions = {
114
114
  * Optional regex flags (e.g., 'i' for case-insensitive, 'm' for multiline).
115
115
  */
116
116
  regexFlags?: string;
117
+ /**
118
+ * Cooldown in seconds after any detection event when both barcode and object detection are enabled.
119
+ * After a barcode or rectangle is emitted, ALL detection pauses for this duration.
120
+ * When set, barcode dedup TTL becomes 2x this value and rectangle emit interval becomes 2x this value.
121
+ * Only applies when both barcodeDetection and objectDetection are true.
122
+ * Defaults to 0 (no throttle, both detections run simultaneously).
123
+ */
124
+ detectionThrottleSeconds?: number;
117
125
  };
118
126
  export declare type BarcodeScannedEvent = {
119
127
  scannedCode: string;
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAkKA,MAAM,CAAN,IAAY,aAYX;AAZD,WAAY,aAAa;IACvB,gCAAe,CAAA;IACf,mCAAkB,CAAA;IAClB,mCAAkB,CAAA;IAClB,qCAAoB,CAAA;IACpB,2CAA0B,CAAA;IAC1B,+BAAc,CAAA;IACd,iCAAgB,CAAA;IAChB,gCAAe,CAAA;IACf,mCAAkB,CAAA;IAClB,mCAAkB,CAAA;IAClB,+BAAc,CAAA;AAChB,CAAC,EAZW,aAAa,KAAb,aAAa,QAYxB","sourcesContent":["export interface CapacitorScannerPlugin {\n /**\n * Start the camera preview with optional barcode and object detection.\n * @param options - Configuration options for the camera and detection\n */\n start(options?: StartOptions): Promise<void>;\n\n /**\n * Stop the camera preview and all detection.\n */\n stop(): Promise<void>;\n\n openSettings(): Promise<void>;\n\n capturePhoto(options?: CapturePhotoOptions): Promise<CapturePhotoResult>;\n\n checkPermissions(): Promise<PermissionsResult>;\n\n requestPermissions(): Promise<PermissionsResult>;\n\n flipCamera(): Promise<void>;\n\n toggleFlash(): Promise<FlashResult>;\n\n addListener(event: 'barcodeScanned', listenerFunc: (result: BarcodeScannedEvent) => void): Promise<void>;\n\n addListener(event: 'rectangleDetected', listenerFunc: (result: RectangleDetectedEvent) => void): Promise<void>;\n\n removeAllListeners(): Promise<void>;\n\n /**\n * Enable object detection (business card). This will start detecting business cards.\n * @param options - Optional configuration for object detection\n */\n enableObjectDetection(options?: ObjectDetectionOptions): Promise<{ enabled: true }>;\n\n /**\n * Disable object detection. This will stop detecting business cards.\n */\n disableObjectDetection(): Promise<{ enabled: false }>;\n\n /**\n * Enable barcode detection. This will start detecting barcodes.\n * @param options - Optional configuration for barcode detection\n */\n enableBarcodeDetection(options?: BarcodeDetectionOptions): Promise<{ enabled: true }>;\n\n /**\n * Disable barcode detection. This will stop detecting barcodes and clear cached barcodes.\n */\n disableBarcodeDetection(): Promise<{ enabled: false }>;\n\n /**\n * Set the camera zoom level.\n * @param options - The zoom options containing the level (1, 2, or 3)\n */\n zoom(options: ZoomOptions): Promise<ZoomResult>;\n}\n\nexport type BarcodeDetectionOptions = {\n /**\n * Whether to show the highlight overlay for detected barcodes.\n * Defaults to false.\n */\n showHighlight?: boolean;\n}\n\nexport type ObjectDetectionOptions = {\n /**\n * Whether to show the highlight overlay for detected business cards.\n * Defaults to true.\n */\n showHighlight?: boolean,\n /**\n * Minimum interval in seconds between consecutive 'rectangleDetected' events.\n * After an event is emitted, no new events will be emitted until this interval passes.\n * Set to 0 to emit events as soon as stability is detected.\n * Defaults to 0 (no throttling beyond initial stability check).\n */\n emitIntervalSeconds?: number\n}\n\nexport type StartOptions = {\n /**\n * Barcode formats to detect when barcodeDetection is enabled.\n */\n formats?: BarcodeFormat[];\n /**\n * Camera direction to use. Defaults to 'BACK'.\n */\n cameraDirection?: 'BACK' | 'FRONT';\n /**\n * Enable barcode detection on start. Defaults to false.\n * When enabled, the camera will automatically detect barcodes and emit 'barcodeScanned' events.\n */\n barcodeDetection?: boolean;\n /**\n * Whether to show the highlight overlay for detected barcodes. Defaults to false.\n * Only applies when barcodeDetection is true.\n */\n barcodeHighlight?: boolean;\n /**\n * Enable object detection (business card) on start. Defaults to false.\n * When enabled, the camera will automatically detect business cards and emit 'rectangleDetected' events.\n */\n objectDetection?: boolean;\n /**\n * Whether to show the highlight overlay for detected business cards. Defaults to true.\n * Only applies when objectDetection is true.\n */\n objectHighlight?: boolean;\n /**\n * Minimum interval in seconds between consecutive 'rectangleDetected' events.\n * Only applies when objectDetection is true.\n * Defaults to 0 (no throttling beyond initial stability check).\n */\n rectangleEmitIntervalSeconds?: number;\n /**\n * Optional regex pattern to filter scanned barcodes.\n * If provided, only barcodes matching this pattern will be reported.\n */\n regex?: string;\n /**\n * Optional regex flags (e.g., 'i' for case-insensitive, 'm' for multiline).\n */\n regexFlags?: string;\n};\n\nexport type BarcodeScannedEvent = { scannedCode: string; format: string };\n\n\nexport type RectangleDetectedEvent = {\n detected: true\n};\n\nexport type CapturePhotoOptions = {\n /**\n * The desired quality of the captured image, expressed as a value between 0.0 (lowest quality, smallest file size)\n * and 1.0 (highest quality, largest file size). Defaults to 1.0.\n * This parameter directly influences the compression level of the resulting JPEG image.\n */\n qualityRatio?: number;\n};\n\nexport type PermissionsResult = { camera: 'prompt' | 'denied' | 'granted' };\n\nexport type CapturePhotoResult = { imageBase64: string };\n\nexport type FlashResult = { enabled: boolean };\n\nexport type ZoomOptions = {\n /**\n * The zoom level to set. Must be 1, 2, or 3.\n * - 1 = 1.0x (no zoom)\n * - 2 = 2.0x\n * - 3 = 3.0x\n */\n level: 1 | 2 | 3;\n};\n\nexport type ZoomResult = { level: number };\n\nexport enum BarcodeFormat {\n Aztec = 'AZTEC',\n Code39 = 'CODE_39',\n Code93 = 'CODE_93',\n Code128 = 'CODE_128',\n DataMatrix = 'DATA_MATRIX',\n Ean8 = 'EAN_8',\n Ean13 = 'EAN_13',\n Itf14 = 'ITF14',\n Pdf417 = 'PDF_417',\n QrCode = 'QR_CODE',\n UpcE = 'UPC_E',\n}\n\ndeclare global {\n interface PluginRegistry {\n QRScanner: CapacitorScannerPlugin;\n }\n}"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AA0KA,MAAM,CAAN,IAAY,aAYX;AAZD,WAAY,aAAa;IACvB,gCAAe,CAAA;IACf,mCAAkB,CAAA;IAClB,mCAAkB,CAAA;IAClB,qCAAoB,CAAA;IACpB,2CAA0B,CAAA;IAC1B,+BAAc,CAAA;IACd,iCAAgB,CAAA;IAChB,gCAAe,CAAA;IACf,mCAAkB,CAAA;IAClB,mCAAkB,CAAA;IAClB,+BAAc,CAAA;AAChB,CAAC,EAZW,aAAa,KAAb,aAAa,QAYxB","sourcesContent":["export interface CapacitorScannerPlugin {\n /**\n * Start the camera preview with optional barcode and object detection.\n * @param options - Configuration options for the camera and detection\n */\n start(options?: StartOptions): Promise<void>;\n\n /**\n * Stop the camera preview and all detection.\n */\n stop(): Promise<void>;\n\n openSettings(): Promise<void>;\n\n capturePhoto(options?: CapturePhotoOptions): Promise<CapturePhotoResult>;\n\n checkPermissions(): Promise<PermissionsResult>;\n\n requestPermissions(): Promise<PermissionsResult>;\n\n flipCamera(): Promise<void>;\n\n toggleFlash(): Promise<FlashResult>;\n\n addListener(event: 'barcodeScanned', listenerFunc: (result: BarcodeScannedEvent) => void): Promise<void>;\n\n addListener(event: 'rectangleDetected', listenerFunc: (result: RectangleDetectedEvent) => void): Promise<void>;\n\n removeAllListeners(): Promise<void>;\n\n /**\n * Enable object detection (business card). This will start detecting business cards.\n * @param options - Optional configuration for object detection\n */\n enableObjectDetection(options?: ObjectDetectionOptions): Promise<{ enabled: true }>;\n\n /**\n * Disable object detection. This will stop detecting business cards.\n */\n disableObjectDetection(): Promise<{ enabled: false }>;\n\n /**\n * Enable barcode detection. This will start detecting barcodes.\n * @param options - Optional configuration for barcode detection\n */\n enableBarcodeDetection(options?: BarcodeDetectionOptions): Promise<{ enabled: true }>;\n\n /**\n * Disable barcode detection. This will stop detecting barcodes and clear cached barcodes.\n */\n disableBarcodeDetection(): Promise<{ enabled: false }>;\n\n /**\n * Set the camera zoom level.\n * @param options - The zoom options containing the level (1, 2, or 3)\n */\n zoom(options: ZoomOptions): Promise<ZoomResult>;\n}\n\nexport type BarcodeDetectionOptions = {\n /**\n * Whether to show the highlight overlay for detected barcodes.\n * Defaults to false.\n */\n showHighlight?: boolean;\n}\n\nexport type ObjectDetectionOptions = {\n /**\n * Whether to show the highlight overlay for detected business cards.\n * Defaults to true.\n */\n showHighlight?: boolean,\n /**\n * Minimum interval in seconds between consecutive 'rectangleDetected' events.\n * After an event is emitted, no new events will be emitted until this interval passes.\n * Set to 0 to emit events as soon as stability is detected.\n * Defaults to 0 (no throttling beyond initial stability check).\n */\n emitIntervalSeconds?: number\n}\n\nexport type StartOptions = {\n /**\n * Barcode formats to detect when barcodeDetection is enabled.\n */\n formats?: BarcodeFormat[];\n /**\n * Camera direction to use. Defaults to 'BACK'.\n */\n cameraDirection?: 'BACK' | 'FRONT';\n /**\n * Enable barcode detection on start. Defaults to false.\n * When enabled, the camera will automatically detect barcodes and emit 'barcodeScanned' events.\n */\n barcodeDetection?: boolean;\n /**\n * Whether to show the highlight overlay for detected barcodes. Defaults to false.\n * Only applies when barcodeDetection is true.\n */\n barcodeHighlight?: boolean;\n /**\n * Enable object detection (business card) on start. Defaults to false.\n * When enabled, the camera will automatically detect business cards and emit 'rectangleDetected' events.\n */\n objectDetection?: boolean;\n /**\n * Whether to show the highlight overlay for detected business cards. Defaults to true.\n * Only applies when objectDetection is true.\n */\n objectHighlight?: boolean;\n /**\n * Minimum interval in seconds between consecutive 'rectangleDetected' events.\n * Only applies when objectDetection is true.\n * Defaults to 0 (no throttling beyond initial stability check).\n */\n rectangleEmitIntervalSeconds?: number;\n /**\n * Optional regex pattern to filter scanned barcodes.\n * If provided, only barcodes matching this pattern will be reported.\n */\n regex?: string;\n /**\n * Optional regex flags (e.g., 'i' for case-insensitive, 'm' for multiline).\n */\n regexFlags?: string;\n /**\n * Cooldown in seconds after any detection event when both barcode and object detection are enabled.\n * After a barcode or rectangle is emitted, ALL detection pauses for this duration.\n * When set, barcode dedup TTL becomes 2x this value and rectangle emit interval becomes 2x this value.\n * Only applies when both barcodeDetection and objectDetection are true.\n * Defaults to 0 (no throttle, both detections run simultaneously).\n */\n detectionThrottleSeconds?: number;\n};\n\nexport type BarcodeScannedEvent = { scannedCode: string; format: string };\n\n\nexport type RectangleDetectedEvent = {\n detected: true\n};\n\nexport type CapturePhotoOptions = {\n /**\n * The desired quality of the captured image, expressed as a value between 0.0 (lowest quality, smallest file size)\n * and 1.0 (highest quality, largest file size). Defaults to 1.0.\n * This parameter directly influences the compression level of the resulting JPEG image.\n */\n qualityRatio?: number;\n};\n\nexport type PermissionsResult = { camera: 'prompt' | 'denied' | 'granted' };\n\nexport type CapturePhotoResult = { imageBase64: string };\n\nexport type FlashResult = { enabled: boolean };\n\nexport type ZoomOptions = {\n /**\n * The zoom level to set. Must be 1, 2, or 3.\n * - 1 = 1.0x (no zoom)\n * - 2 = 2.0x\n * - 3 = 3.0x\n */\n level: 1 | 2 | 3;\n};\n\nexport type ZoomResult = { level: number };\n\nexport enum BarcodeFormat {\n Aztec = 'AZTEC',\n Code39 = 'CODE_39',\n Code93 = 'CODE_93',\n Code128 = 'CODE_128',\n DataMatrix = 'DATA_MATRIX',\n Ean8 = 'EAN_8',\n Ean13 = 'EAN_13',\n Itf14 = 'ITF14',\n Pdf417 = 'PDF_417',\n QrCode = 'QR_CODE',\n UpcE = 'UPC_E',\n}\n\ndeclare global {\n interface PluginRegistry {\n QRScanner: CapacitorScannerPlugin;\n }\n}"]}
@@ -51,6 +51,11 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
51
51
  private var lastBarcodeDetectionTime: Date?
52
52
  private let barcodeVisibilityTimeout: TimeInterval = 1.0
53
53
 
54
+ // Detection throttle: pause ALL detection after any emit (when both types enabled)
55
+ private var detectionThrottleSeconds: TimeInterval = 0
56
+ private var lastDetectionEmitTime: Date?
57
+ private var bothDetectionsEnabled: Bool = false
58
+
54
59
  // Detection state (controls what detection runs)
55
60
  private var enabledDetectionTypes: Set<String> = []
56
61
  // Highlight state (controls what overlay is shown)
@@ -447,6 +452,11 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
447
452
  }
448
453
  }
449
454
 
455
+ // Read detection throttle
456
+ self.detectionThrottleSeconds = call.getDouble("detectionThrottleSeconds") ?? 0
457
+ self.lastDetectionEmitTime = nil
458
+ self.bothDetectionsEnabled = enableBarcodeDetection && enableObjectDetection
459
+
450
460
  // Enable object detection (businessCard) if requested
451
461
  if enableObjectDetection {
452
462
  self.enabledDetectionTypes.insert("businessCard")
@@ -459,11 +469,19 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
459
469
  if enableObjectHighlight {
460
470
  self.rectangleDetector?.isHighlightEnabled = true
461
471
  }
462
- // Set emit interval if provided
463
- let emitInterval = call.getDouble("rectangleEmitIntervalSeconds") ?? 0
472
+ // Set emit interval: use 2X when both enabled with throttle, otherwise use provided value
473
+ var emitInterval = call.getDouble("rectangleEmitIntervalSeconds") ?? 0
474
+ if self.bothDetectionsEnabled && self.detectionThrottleSeconds > 0 {
475
+ emitInterval = max(emitInterval, self.detectionThrottleSeconds * 2)
476
+ }
464
477
  self.rectangleDetector?.setEmitInterval(emitInterval)
465
478
  }
466
479
 
480
+ // When both enabled with throttle, set barcode votes TTL to 2X
481
+ if self.bothDetectionsEnabled && self.detectionThrottleSeconds > 0 {
482
+ self.scannedCodesVotes = TTLMap<String, VoteStatus>(ttlSeconds: self.detectionThrottleSeconds * 2)
483
+ }
484
+
467
485
  // Handle Regex Filter
468
486
  if let pattern = call.getString("regex") {
469
487
  let flags = call.getString("regexFlags") ?? ""
@@ -596,6 +614,10 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
596
614
  self.enabledDetectionTypes.removeAll()
597
615
  self.enabledHighlightTypes.removeAll()
598
616
  self.lastBarcodeDetectionTime = nil
617
+ self.lastDetectionEmitTime = nil
618
+ self.detectionThrottleSeconds = 0
619
+ self.bothDetectionsEnabled = false
620
+ self.scannedCodesVotes = TTLMap<String, VoteStatus>(ttlSeconds: 5)
599
621
  self.removeOrientationChangeObserver()
600
622
  }
601
623
 
@@ -811,8 +833,14 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
811
833
  "format": CapacitorScannerHelpers.convertBarcodeScannerFormatToString(bestObservation.symbology),
812
834
  ])
813
835
 
814
- // Reset votes after successful scan
815
- self.scannedCodesVotes.clear()
836
+ // Record emit time for detection throttle
837
+ self.lastDetectionEmitTime = Date()
838
+
839
+ // When both enabled: keep done flag for dedup (TTL handles expiry)
840
+ // When barcode only: clear to allow immediate re-voting
841
+ if !self.bothDetectionsEnabled {
842
+ self.scannedCodesVotes.clear()
843
+ }
816
844
  }
817
845
  }
818
846
 
@@ -829,6 +857,9 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
829
857
  "bottomLeft": ["x": corners.bottomLeft.x, "y": corners.bottomLeft.y],
830
858
  "bottomRight": ["x": corners.bottomRight.x, "y": corners.bottomRight.y],
831
859
  ])
860
+
861
+ // Record emit time for detection throttle
862
+ self.lastDetectionEmitTime = Date()
832
863
  }
833
864
 
834
865
  @objc func flipCamera(_ call: CAPPluginCall) {
@@ -1020,11 +1051,20 @@ extension CapacitorScannerPlugin: AVCaptureVideoDataOutputSampleBufferDelegate {
1020
1051
  // Prepare detection requests
1021
1052
  var requests: [VNRequest] = []
1022
1053
 
1023
- if self.enabledDetectionTypes.contains("barcode") {
1054
+ // Check if detection is throttled (cooldown after any emit when both types enabled)
1055
+ let isThrottled: Bool = {
1056
+ guard self.bothDetectionsEnabled,
1057
+ self.detectionThrottleSeconds > 0,
1058
+ let lastEmit = self.lastDetectionEmitTime else { return false }
1059
+ return Date().timeIntervalSince(lastEmit) < self.detectionThrottleSeconds
1060
+ }()
1061
+
1062
+ if !isThrottled, self.enabledDetectionTypes.contains("barcode") {
1024
1063
  requests.append(self.barcodeDetectionRequest)
1025
1064
  }
1026
1065
 
1027
- if self.enabledDetectionTypes.contains("businessCard"),
1066
+ if !isThrottled,
1067
+ self.enabledDetectionTypes.contains("businessCard"),
1028
1068
  let detector = self.rectangleDetector {
1029
1069
  detector.updatePixelBuffer(pixelBuffer)
1030
1070
  requests.append(detector.visionRequest)
@@ -61,7 +61,7 @@ class RectangleDetector {
61
61
  let request = VNDetectRectanglesRequest { [weak self] request, error in
62
62
  self?.processDetection(request, error: error)
63
63
  }
64
- request.maximumObservations = 1
64
+ request.maximumObservations = 10
65
65
  request.minimumAspectRatio = 0.5
66
66
  request.maximumAspectRatio = 0.8
67
67
  request.minimumSize = 0.2
@@ -149,22 +149,16 @@ class RectangleDetector {
149
149
  return
150
150
  }
151
151
 
152
- // Find the rectangle with highest confidence
153
- guard let bestCandidate = observations.max(by: { $0.confidence < $1.confidence }) else {
152
+ // Find the largest rectangle by area
153
+ guard let bestCandidate = observations
154
+ .filter({ $0.confidence >= confidenceThreshold && isInsideScreen($0) })
155
+ .max(by: { area(of: $0) < area(of: $1) }) else {
154
156
  // Don't reset stability here - let checkTimeout() handle the grace period
155
157
  return
156
158
  }
157
159
 
158
- // Check if all corners are inside the screen with buffer
159
- guard isInsideScreen(bestCandidate) else {
160
- // Don't reset stability here - let checkTimeout() handle the grace period
161
- return
162
- }
163
-
164
- // Store rectangle if confidence is high enough
165
- if bestCandidate.confidence >= confidenceThreshold {
166
- captureSnapshot(for: bestCandidate)
167
- }
160
+ // Store rectangle with snapshot for cropping
161
+ captureSnapshot(for: bestCandidate)
168
162
 
169
163
  // Update detection time
170
164
  lastDetectionTime = Date()
@@ -265,6 +259,17 @@ class RectangleDetector {
265
259
  return sqrt(dx * dx + dy * dy)
266
260
  }
267
261
 
262
+ /// Approximate area of a rectangle observation using the shoelace formula (normalized coordinates)
263
+ private func area(of obs: VNRectangleObservation) -> CGFloat {
264
+ let pts = [obs.topLeft, obs.topRight, obs.bottomRight, obs.bottomLeft]
265
+ var sum: CGFloat = 0
266
+ for i in 0..<pts.count {
267
+ let j = (i + 1) % pts.count
268
+ sum += pts[i].x * pts[j].y - pts[j].x * pts[i].y
269
+ }
270
+ return abs(sum) / 2.0
271
+ }
272
+
268
273
  private func isInsideScreen(_ observation: VNRectangleObservation) -> Bool {
269
274
  let corners = [
270
275
  observation.topLeft,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scr2em/capacitor-scanner",
3
- "version": "6.0.30",
3
+ "version": "6.0.33",
4
4
  "description": "scan codes",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",