@scr2em/capacitor-scanner 6.0.29 → 6.0.30

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; debounceTimeInMilli?: 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; }</code>
266
266
 
267
267
 
268
268
  #### CapturePhotoOptions
@@ -449,152 +449,154 @@ public class CapacitorScannerPlugin extends Plugin {
449
449
 
450
450
  @Override
451
451
  public void analyze(@NonNull ImageProxy imageProxy) {
452
- try (imageProxy) {
453
- @ExperimentalGetImage
454
- android.media.Image mediaImage = imageProxy.getImage();
455
- if (mediaImage != null) {
456
- InputImage image = InputImage.fromMediaImage(mediaImage,
457
- imageProxy.getImageInfo().getRotationDegrees());
458
-
459
- // Barcode detection - only process if barcode detection is enabled
460
- if (scanner != null && enabledDetectionTypes.contains("barcode")) {
461
- scanner.process(image)
462
- .addOnSuccessListener(executor, barcodes -> {
463
- processBarcodes(barcodes);
464
- // Only display barcode overlay if highlight is enabled
465
- if (enabledHighlightTypes.contains("barcode")) {
466
- displayDetectedBarcodes(barcodes, imageProxy.getWidth(), imageProxy.getHeight(),
467
- imageProxy.getImageInfo().getRotationDegrees());
468
- }
469
- })
470
- .addOnFailureListener(executor, e -> {
471
- echo("Failed to process image for barcode: " + e.getMessage());
472
- });
473
- }
452
+ @ExperimentalGetImage
453
+ android.media.Image mediaImage = imageProxy.getImage();
454
+ if (mediaImage == null) {
455
+ imageProxy.close();
456
+ return;
457
+ }
474
458
 
475
- // Rectangle detection
476
- if (rectangleDetector != null && enabledDetectionTypes.contains("businessCard")) {
477
- DetectionResult detectionResult = null;
478
-
479
- // Only perform detection if enough time has passed since last detection
480
- // This throttles processing based on configured frequency
481
- long currentTime = System.currentTimeMillis();
482
- long processingIntervalMs = calculateProcessingInterval(processingFrequency);
483
- if (currentTime - lastRectangleProcessingTime >= processingIntervalMs) {
484
- // Perform new detection
485
- echo("Starting rectangle detection for businessCard");
486
- Bitmap bitmap = null;
487
- try {
488
- // Convert ImageProxy to Bitmap using the sample's toBitmap method
489
- bitmap = imageProxyToBitmap(imageProxy);
459
+ // Capture metadata upfront so it's available after imageProxy is closed
460
+ final int imageWidth = imageProxy.getWidth();
461
+ final int imageHeight = imageProxy.getHeight();
462
+ final int rotationDegrees = imageProxy.getImageInfo().getRotationDegrees();
490
463
 
491
- echo("Converting image for rectangle detection - bitmap size: " + bitmap.getWidth()
492
- + "x" + bitmap.getHeight());
464
+ try {
465
+ InputImage image = InputImage.fromMediaImage(mediaImage, rotationDegrees);
466
+
467
+ boolean barcodeEnabled = scanner != null && enabledDetectionTypes.contains("barcode");
468
+ boolean rectangleEnabled = rectangleDetector != null && enabledDetectionTypes.contains("businessCard");
469
+
470
+ // Rectangle detection (synchronous) - runs first while imageProxy is still open
471
+ if (rectangleEnabled) {
472
+ DetectionResult detectionResult = null;
473
+
474
+ // Only perform detection if enough time has passed since last detection
475
+ // This throttles processing based on configured frequency
476
+ long currentTime = System.currentTimeMillis();
477
+ long processingIntervalMs = calculateProcessingInterval(processingFrequency);
478
+ if (currentTime - lastRectangleProcessingTime >= processingIntervalMs) {
479
+ // Perform new detection
480
+ Bitmap bitmap = null;
481
+ try {
482
+ // Convert ImageProxy to Bitmap using the sample's toBitmap method
483
+ bitmap = imageProxyToBitmap(imageProxy);
493
484
 
494
- long startTime = System.currentTimeMillis();
495
- DetectionResult rawResult = rectangleDetector.detectRectangles(bitmap);
496
- echo("Rectangle detection took " + (System.currentTimeMillis() - startTime) + "ms");
485
+ DetectionResult rawResult = rectangleDetector.detectRectangles(bitmap);
497
486
 
498
- // Apply geometric filtering to eliminate false positives
499
- List<Rectangle> filteredRectangles = filterRectangles(rawResult.getRectangles(),
500
- rawResult.getImageSize());
487
+ // Apply geometric filtering to eliminate false positives
488
+ List<Rectangle> filteredRectangles = filterRectangles(rawResult.getRectangles(),
489
+ rawResult.getImageSize());
501
490
 
502
- // 2) record hit/miss in sliding window
503
- boolean isHit = !filteredRectangles.isEmpty();
504
- detectionHistory.add(isHit);
505
- if (detectionHistory.size() > DETECTION_WINDOW) {
506
- detectionHistory.poll();
507
- }
508
- if (isHit) {
509
- lastGoodRects = filteredRectangles; // update last known good
510
- echo("✅ Hit - saved " + filteredRectangles.size() + " good rectangles");
511
- } else {
512
- echo("❌ Miss - keeping " + lastGoodRects.size() + " previous rectangles");
513
- }
491
+ // 2) record hit/miss in sliding window
492
+ boolean isHit = !filteredRectangles.isEmpty();
493
+ detectionHistory.add(isHit);
494
+ if (detectionHistory.size() > DETECTION_WINDOW) {
495
+ detectionHistory.poll();
496
+ }
497
+ if (isHit) {
498
+ lastGoodRects = filteredRectangles; // update last known good
499
+ }
514
500
 
515
- // 3) decide whether to show based on sliding window
516
- long hits = 0;
517
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
518
- hits = detectionHistory.stream().filter(b -> b).count();
519
- } else {
520
- // For older Android versions, count manually
521
- for (Boolean hit : detectionHistory) {
522
- if (hit)
523
- hits++;
524
- }
525
- }
526
- boolean shouldShow = hits >= Math.ceil(PRESENCE_THRESHOLD * detectionHistory.size());
527
- List<Rectangle> rectanglesToShow = shouldShow ? lastGoodRects : Collections.emptyList();
528
- echo("Window: " + hits + "/" + detectionHistory.size() + " hits - shouldShow: "
529
- + shouldShow);
530
-
531
- // Create detection result with the rectangles we decided to show
532
- detectionResult = new DetectionResult(rawResult.getImageSize(), rectanglesToShow);
533
-
534
- // Store the detection result for reuse on non-processing frames
535
- if (!rectanglesToShow.isEmpty()) {
536
- lastGoodDetectionResult = detectionResult;
537
- lastRectangleDetectionTime = currentTime;
501
+ // 3) decide whether to show based on sliding window
502
+ long hits = 0;
503
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
504
+ hits = detectionHistory.stream().filter(b -> b).count();
505
+ } else {
506
+ // For older Android versions, count manually
507
+ for (Boolean hit : detectionHistory) {
508
+ if (hit)
509
+ hits++;
538
510
  }
511
+ }
512
+ boolean shouldShow = hits >= Math.ceil(PRESENCE_THRESHOLD * detectionHistory.size());
513
+ List<Rectangle> rectanglesToShow = shouldShow ? lastGoodRects : Collections.emptyList();
539
514
 
540
- // Process rectangle detection results to emit events if we're showing
541
- // rectangles
542
- if (!rectanglesToShow.isEmpty()) {
543
- processRectangles(detectionResult);
544
- }
515
+ // Create detection result with the rectangles we decided to show
516
+ detectionResult = new DetectionResult(rawResult.getImageSize(), rectanglesToShow);
545
517
 
546
- // Update last processing time
547
- lastRectangleProcessingTime = currentTime;
548
- } catch (Exception e) {
549
- echo("Failed to process image for rectangle detection: " + e.getMessage());
550
- e.printStackTrace();
551
- detectionResult = new DetectionResult(
552
- new Size(imageProxy.getWidth(), imageProxy.getHeight()), new ArrayList<>());
553
- } finally {
554
- // Cleanup bitmap resource in all code paths
555
- if (bitmap != null) {
556
- bitmap.recycle();
557
- }
518
+ // Store the detection result for reuse on non-processing frames
519
+ if (!rectanglesToShow.isEmpty()) {
520
+ lastGoodDetectionResult = detectionResult;
521
+ lastRectangleDetectionTime = currentTime;
558
522
  }
559
- } else {
560
- // For frames we're not processing, reuse the last good detection result
561
- // This ensures the UI stays smooth even though we're only detecting 10
562
- // times/second
563
- if (lastGoodDetectionResult != null &&
564
- (currentTime - lastRectangleDetectionTime) < CACHE_MS) {
565
- detectionResult = lastGoodDetectionResult;
566
- echo("Reusing last good detection result (age: "
567
- + (currentTime - lastRectangleProcessingTime) + "ms)");
568
- } else {
569
- // Create empty result if we have no recent good detection
570
- detectionResult = new DetectionResult(
571
- new Size(imageProxy.getWidth(), imageProxy.getHeight()), new ArrayList<>());
523
+
524
+ // Process rectangle detection results to emit events if we're showing
525
+ // rectangles
526
+ if (!rectanglesToShow.isEmpty()) {
527
+ processRectangles(detectionResult);
572
528
  }
573
- }
574
529
 
575
- // Always display detection results for every frame to keep UI smooth
576
- // This runs for both freshly detected rectangles and reused detection results
577
- final DetectionResult finalResult = detectionResult;
578
- getActivity().runOnUiThread(() -> {
579
- // Only display rectangle overlay if highlight is enabled
580
- if (enabledHighlightTypes.contains("businessCard")) {
581
- // Show the rectangles
582
- displayDetectedRectangles(finalResult,
583
- imageProxy.getWidth(),
584
- imageProxy.getHeight(),
585
- imageProxy.getImageInfo().getRotationDegrees());
530
+ // Update last processing time
531
+ lastRectangleProcessingTime = currentTime;
532
+ } catch (Exception e) {
533
+ echo("Failed to process image for rectangle detection: " + e.getMessage());
534
+ e.printStackTrace();
535
+ detectionResult = new DetectionResult(
536
+ new Size(imageWidth, imageHeight), new ArrayList<>());
537
+ } finally {
538
+ // Cleanup bitmap resource in all code paths
539
+ if (bitmap != null) {
540
+ bitmap.recycle();
586
541
  }
587
- });
542
+ }
588
543
  } else {
589
- // When businessCard detection is disabled, we don't need to do anything here.
590
- // The overlay is cleared by the disableObjectDetection method.
544
+ // For frames we're not processing, reuse the last good detection result
545
+ // This ensures the UI stays smooth even though we're only detecting 10
546
+ // times/second
547
+ if (lastGoodDetectionResult != null &&
548
+ (currentTime - lastRectangleDetectionTime) < CACHE_MS) {
549
+ detectionResult = lastGoodDetectionResult;
550
+ } else {
551
+ // Create empty result if we have no recent good detection
552
+ detectionResult = new DetectionResult(
553
+ new Size(imageWidth, imageHeight), new ArrayList<>());
554
+ }
591
555
  }
556
+
557
+ // Always display detection results for every frame to keep UI smooth
558
+ // This runs for both freshly detected rectangles and reused detection results
559
+ final DetectionResult finalResult = detectionResult;
560
+ getActivity().runOnUiThread(() -> {
561
+ // Only display rectangle overlay if highlight is enabled
562
+ if (enabledHighlightTypes.contains("businessCard")) {
563
+ // Show the rectangles
564
+ displayDetectedRectangles(finalResult,
565
+ imageWidth,
566
+ imageHeight,
567
+ rotationDegrees);
568
+ }
569
+ });
570
+ }
571
+
572
+ // Barcode detection (async) - close imageProxy only after ML Kit finishes
573
+ // processing. The imageProxy must stay open so ML Kit can read the underlying
574
+ // image buffer.
575
+ if (barcodeEnabled) {
576
+ scanner.process(image)
577
+ .addOnSuccessListener(executor, barcodes -> {
578
+ processBarcodes(barcodes);
579
+ // Only display barcode overlay if highlight is enabled
580
+ if (enabledHighlightTypes.contains("barcode")) {
581
+ displayDetectedBarcodes(barcodes, imageWidth, imageHeight,
582
+ rotationDegrees);
583
+ }
584
+ })
585
+ .addOnFailureListener(executor, e -> {
586
+ echo("Failed to process image for barcode: " + e.getMessage());
587
+ })
588
+ .addOnCompleteListener(executor, task -> {
589
+ imageProxy.close();
590
+ });
591
+ } else {
592
+ // No async work pending — safe to close now
593
+ imageProxy.close();
592
594
  }
593
595
  } catch (Exception e) {
594
596
  echo("Unexpected error in image analysis: " + e.getMessage());
595
597
  e.printStackTrace();
598
+ imageProxy.close();
596
599
  }
597
- // Always close the ImageProxy, in all code paths
598
600
  }
599
601
  }
600
602
 
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 debounceTimeInMilli?: 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}",
348
348
  "complexTypes": [
349
349
  "BarcodeFormat"
350
350
  ]
@@ -105,7 +105,6 @@ export declare type StartOptions = {
105
105
  * Defaults to 0 (no throttling beyond initial stability check).
106
106
  */
107
107
  rectangleEmitIntervalSeconds?: number;
108
- debounceTimeInMilli?: number;
109
108
  /**
110
109
  * Optional regex pattern to filter scanned barcodes.
111
110
  * If provided, only barcodes matching this pattern will be reported.
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAmKA,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 debounceTimeInMilli?: 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":"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}"]}
@@ -129,10 +129,8 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
129
129
  private func setupOverlayView() {
130
130
  guard let cameraView = self.cameraView,
131
131
  let previewLayer = self.previewLayer else {
132
- print("[CapacitorScanner] setupOverlayView: cameraView or previewLayer is nil")
133
132
  return
134
133
  }
135
- print("[CapacitorScanner] setupOverlayView: setting up overlay")
136
134
 
137
135
  // Remove existing overlay if present
138
136
  self.overlayView?.removeFromSuperview()
@@ -825,7 +823,6 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
825
823
  // MARK: - RectangleDetectorDelegate
826
824
 
827
825
  func rectangleDetector(_ detector: RectangleDetector, didDetect corners: RectangleCorners) {
828
- print("[CapacitorScanner] rectangleDetector delegate called - emitting rectangleDetected event")
829
826
  self.notifyListeners("rectangleDetected", data: [
830
827
  "topLeft": ["x": corners.topLeft.x, "y": corners.topLeft.y],
831
828
  "topRight": ["x": corners.topRight.x, "y": corners.topRight.y],
@@ -1029,7 +1026,6 @@ extension CapacitorScannerPlugin: AVCaptureVideoDataOutputSampleBufferDelegate {
1029
1026
 
1030
1027
  if self.enabledDetectionTypes.contains("businessCard"),
1031
1028
  let detector = self.rectangleDetector {
1032
- print("[CapacitorScanner] Processing frame for businessCard detection")
1033
1029
  detector.updatePixelBuffer(pixelBuffer)
1034
1030
  requests.append(detector.visionRequest)
1035
1031
  }
@@ -1041,15 +1037,19 @@ extension CapacitorScannerPlugin: AVCaptureVideoDataOutputSampleBufferDelegate {
1041
1037
  print("Failed to perform Vision request: \(error)")
1042
1038
  }
1043
1039
 
1044
- // Check overlay timeouts
1045
- DispatchQueue.main.async {
1046
- let now = Date()
1047
- if let t = self.lastBarcodeDetectionTime, now.timeIntervalSince(t) > self.barcodeVisibilityTimeout {
1048
- self.barcodeLayer?.removeFromSuperlayer()
1049
- self.barcodeLayer = nil
1050
- self.prevBarcodeRect = nil
1040
+ // Check overlay timeouts only when there's something to clean up
1041
+ let needsBarcodeCheck = self.barcodeLayer != nil
1042
+ let needsRectCheck = self.rectangleDetector != nil
1043
+ if needsBarcodeCheck || needsRectCheck {
1044
+ DispatchQueue.main.async {
1045
+ let now = Date()
1046
+ if let t = self.lastBarcodeDetectionTime, now.timeIntervalSince(t) > self.barcodeVisibilityTimeout {
1047
+ self.barcodeLayer?.removeFromSuperlayer()
1048
+ self.barcodeLayer = nil
1049
+ self.prevBarcodeRect = nil
1050
+ }
1051
+ self.rectangleDetector?.checkTimeout()
1051
1052
  }
1052
- self.rectangleDetector?.checkTimeout()
1053
1053
  }
1054
1054
  }
1055
1055
  }
@@ -41,6 +41,7 @@ class RectangleDetector {
41
41
  private(set) var lastDetectedRectangle: DetectedRectangle?
42
42
  private(set) var lastDetectionTime: Date?
43
43
  private var currentPixelBuffer: CVPixelBuffer?
44
+ private let ciContext = CIContext(options: nil)
44
45
 
45
46
  // Stability tracking
46
47
  private var previousCorners: RectangleCorners?
@@ -73,7 +74,6 @@ class RectangleDetector {
73
74
 
74
75
  /// Configure the detector with overlay support
75
76
  func configure(overlayLayer: CALayer, previewLayer: AVCaptureVideoPreviewLayer) {
76
- print("[RectangleDetector] configure called, isHighlightEnabled: \(isHighlightEnabled)")
77
77
  self.overlayParentLayer = overlayLayer
78
78
  self.previewLayer = previewLayer
79
79
 
@@ -132,41 +132,31 @@ class RectangleDetector {
132
132
 
133
133
  private func setupSmoother() {
134
134
  guard let parentLayer = overlayParentLayer, smoother == nil else {
135
- print("[RectangleDetector] setupSmoother: skipped (parentLayer nil: \(overlayParentLayer == nil), smoother exists: \(smoother != nil))")
136
135
  return
137
136
  }
138
- print("[RectangleDetector] setupSmoother: creating smoother")
139
137
  smoother = RectangleSmoother()
140
138
  smoother?.setup(in: parentLayer)
141
139
  }
142
140
 
143
141
  private func processDetection(_ request: VNRequest, error: Error?) {
144
- print("[RectangleDetector] processDetection called")
145
-
146
142
  if let error = error {
147
- print("[RectangleDetector] Detection error: \(error)")
148
143
  // Don't reset stability here - let checkTimeout() handle the grace period
149
144
  return
150
145
  }
151
146
 
152
147
  guard let observations = request.results as? [VNRectangleObservation] else {
153
- print("[RectangleDetector] No observations in results")
154
148
  // Don't reset stability here - let checkTimeout() handle the grace period
155
149
  return
156
150
  }
157
151
 
158
152
  // Find the rectangle with highest confidence
159
153
  guard let bestCandidate = observations.max(by: { $0.confidence < $1.confidence }) else {
160
- print("[RectangleDetector] No rectangles found (empty observations)")
161
154
  // Don't reset stability here - let checkTimeout() handle the grace period
162
155
  return
163
156
  }
164
157
 
165
- print("[RectangleDetector] Found rectangle with confidence: \(bestCandidate.confidence)")
166
-
167
158
  // Check if all corners are inside the screen with buffer
168
159
  guard isInsideScreen(bestCandidate) else {
169
- print("[RectangleDetector] Rectangle outside screen bounds")
170
160
  // Don't reset stability here - let checkTimeout() handle the grace period
171
161
  return
172
162
  }
@@ -204,11 +194,8 @@ class RectangleDetector {
204
194
  // MARK: - Stability Tracking
205
195
 
206
196
  private func updateStability(with corners: RectangleCorners) {
207
- print("[RectangleDetector] updateStability - hasPrevious: \(previousCorners != nil), timerActive: \(stabilityStartTime != nil)")
208
-
209
197
  // First detection - just store corners, don't start timer yet
210
198
  guard let previous = previousCorners else {
211
- print("[RectangleDetector] First detection - storing corners, no timer yet")
212
199
  previousCorners = corners
213
200
  stabilityStartTime = nil
214
201
  return
@@ -219,11 +206,9 @@ class RectangleDetector {
219
206
 
220
207
  // Check how much the rectangle moved
221
208
  let movement = maxCornerMovement(from: previous, to: corners)
222
- print("[RectangleDetector] Movement: \(movement), threshold: \(stabilityThreshold)")
223
209
 
224
210
  // Too much movement - reset timer completely
225
211
  if movement >= stabilityThreshold {
226
- print("[RectangleDetector] Too much movement (\(movement) >= \(stabilityThreshold)) - resetting timer")
227
212
  stabilityStartTime = nil
228
213
  return
229
214
  }
@@ -234,13 +219,11 @@ class RectangleDetector {
234
219
  if stabilityStartTime == nil {
235
220
  // Start the stability timer - but DON'T check duration yet
236
221
  stabilityStartTime = now
237
- print("[RectangleDetector] Stability timer STARTED")
238
222
  return
239
223
  }
240
224
 
241
225
  // Timer was already running - check elapsed time
242
226
  let elapsed = now.timeIntervalSince(stabilityStartTime!)
243
- print("[RectangleDetector] Stability elapsed: \(String(format: "%.2f", elapsed))s / \(stabilityDuration)s required")
244
227
 
245
228
  // Need to be stable for the required duration before any emission
246
229
  guard elapsed >= stabilityDuration else { return }
@@ -250,13 +233,11 @@ class RectangleDetector {
250
233
  if let lastEmit = lastEmitTime {
251
234
  let timeSinceLastEmit = now.timeIntervalSince(lastEmit)
252
235
  canEmit = timeSinceLastEmit >= emitIntervalSeconds
253
- print("[RectangleDetector] Time since last emit: \(String(format: "%.2f", timeSinceLastEmit))s, interval: \(emitIntervalSeconds)s")
254
236
  } else {
255
237
  canEmit = true
256
238
  }
257
239
 
258
240
  if canEmit {
259
- print("[RectangleDetector] STABLE for \(elapsed)s - emitting event")
260
241
  lastEmitTime = now
261
242
  delegate?.rectangleDetector(self, didDetect: corners)
262
243
  }
@@ -264,9 +245,6 @@ class RectangleDetector {
264
245
 
265
246
  /// Called when no valid rectangle is detected - resets all tracking state
266
247
  private func handleNoDetection() {
267
- if stabilityStartTime != nil {
268
- print("[RectangleDetector] No detection - resetting stability timer")
269
- }
270
248
  stabilityStartTime = nil
271
249
  previousCorners = nil
272
250
  }
@@ -311,9 +289,8 @@ class RectangleDetector {
311
289
  }
312
290
 
313
291
  let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
314
- let context = CIContext(options: nil)
315
292
 
316
- guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else {
293
+ guard let cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent) else {
317
294
  lastDetectedRectangle = DetectedRectangle(observation: observation, snapshot: nil)
318
295
  return
319
296
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scr2em/capacitor-scanner",
3
- "version": "6.0.29",
3
+ "version": "6.0.30",
4
4
  "description": "scan codes",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",