@scr2em/capacitor-scanner 6.0.6 → 6.0.8

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
@@ -16,9 +16,11 @@ npx cap sync
16
16
  * [`startScanning(...)`](#startscanning)
17
17
  * [`stopScanning()`](#stopscanning)
18
18
  * [`openSettings()`](#opensettings)
19
- * [`capturePhoto()`](#capturephoto)
19
+ * [`capturePhoto(...)`](#capturephoto)
20
20
  * [`checkPermissions()`](#checkpermissions)
21
21
  * [`requestPermissions()`](#requestpermissions)
22
+ * [`flipCamera()`](#flipcamera)
23
+ * [`toggleFlash()`](#toggleflash)
22
24
  * [`addListener('barcodeScanned', ...)`](#addlistenerbarcodescanned-)
23
25
  * [`removeAllListeners()`](#removealllisteners)
24
26
  * [Type Aliases](#type-aliases)
@@ -60,12 +62,16 @@ openSettings() => Promise<void>
60
62
  --------------------
61
63
 
62
64
 
63
- ### capturePhoto()
65
+ ### capturePhoto(...)
64
66
 
65
67
  ```typescript
66
- capturePhoto() => Promise<CapturePhotoResult>
68
+ capturePhoto(options?: CapturePhotoOptions | undefined) => Promise<CapturePhotoResult>
67
69
  ```
68
70
 
71
+ | Param | Type |
72
+ | ------------- | ------------------------------------------------------------------- |
73
+ | **`options`** | <code><a href="#capturephotooptions">CapturePhotoOptions</a></code> |
74
+
69
75
  **Returns:** <code>Promise&lt;<a href="#capturephotoresult">CapturePhotoResult</a>&gt;</code>
70
76
 
71
77
  --------------------
@@ -93,6 +99,26 @@ requestPermissions() => Promise<PermissionsResult>
93
99
  --------------------
94
100
 
95
101
 
102
+ ### flipCamera()
103
+
104
+ ```typescript
105
+ flipCamera() => Promise<void>
106
+ ```
107
+
108
+ --------------------
109
+
110
+
111
+ ### toggleFlash()
112
+
113
+ ```typescript
114
+ toggleFlash() => Promise<FlashResult>
115
+ ```
116
+
117
+ **Returns:** <code>Promise&lt;<a href="#flashresult">FlashResult</a>&gt;</code>
118
+
119
+ --------------------
120
+
121
+
96
122
  ### addListener('barcodeScanned', ...)
97
123
 
98
124
  ```typescript
@@ -129,11 +155,21 @@ removeAllListeners() => Promise<void>
129
155
  <code>{ imageBase64: string }</code>
130
156
 
131
157
 
158
+ #### CapturePhotoOptions
159
+
160
+ <code>{ /** * The desired quality of the captured image, expressed as a value between 0.0 (lowest quality, smallest file size) * and 1.0 (highest quality, largest file size). Defaults to 1.0. * This parameter directly influences the compression level of the resulting JPEG image. */ qualityRatio?: number; }</code>
161
+
162
+
132
163
  #### PermissionsResult
133
164
 
134
165
  <code>{ camera: 'prompt' | 'denied' | 'granted' }</code>
135
166
 
136
167
 
168
+ #### FlashResult
169
+
170
+ <code>{ enabled: boolean }</code>
171
+
172
+
137
173
  #### BarcodeScannedEvent
138
174
 
139
175
  <code>{ scannedCode: string; format: string }</code>
@@ -2,6 +2,9 @@ package com.leadliaion.capacitorscanner;
2
2
 
3
3
  import android.Manifest;
4
4
  import android.content.Intent;
5
+ import android.content.pm.PackageManager;
6
+ import android.graphics.Bitmap;
7
+ import android.graphics.BitmapFactory;
5
8
  import android.graphics.Color;
6
9
  import android.net.Uri;
7
10
  import android.provider.Settings;
@@ -17,6 +20,7 @@ import android.view.View;
17
20
  import com.getcapacitor.JSArray;
18
21
 
19
22
  import androidx.annotation.NonNull;
23
+ import androidx.camera.core.Camera;
20
24
  import androidx.camera.core.CameraSelector;
21
25
 
22
26
  import androidx.camera.core.ExperimentalGetImage;
@@ -79,6 +83,10 @@ public class CapacitorScannerPlugin extends Plugin {
79
83
  private ImageAnalysis imageAnalysis;
80
84
  private ImageCapture imageCapture;
81
85
 
86
+ private boolean isFlashEnabled = false;
87
+ private int currentLensFacing = CameraSelector.LENS_FACING_BACK;
88
+
89
+
82
90
 
83
91
  @Override
84
92
  public void load() {
@@ -209,6 +217,9 @@ public class CapacitorScannerPlugin extends Plugin {
209
217
  });
210
218
  }
211
219
 
220
+ private Camera camera;
221
+
222
+
212
223
  private void bindCamera(@NonNull ProcessCameraProvider cameraProvider, PreviewView previewView, int lensFacing, PluginCall call) {
213
224
  cameraProvider.unbindAll();
214
225
 
@@ -239,16 +250,17 @@ public class CapacitorScannerPlugin extends Plugin {
239
250
  .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
240
251
  .setTargetRotation(rotation)
241
252
  .build();
242
-
243
253
  imageAnalysis.setAnalyzer(executor, new BarcodeAnalyzer(call));
244
254
 
245
255
  imageCapture = new ImageCapture.Builder()
246
256
  .setTargetResolution(targetResolution)
247
257
  .setTargetRotation(rotation)
258
+ .setFlashMode(isFlashEnabled ? ImageCapture.FLASH_MODE_ON : ImageCapture.FLASH_MODE_OFF)
248
259
  .build();
249
260
 
250
261
  try {
251
- cameraProvider.bindToLifecycle(getActivity(), cameraSelector, preview, imageAnalysis, imageCapture);
262
+ // Store the Camera instance for later use
263
+ camera = cameraProvider.bindToLifecycle(getActivity(), cameraSelector, preview, imageAnalysis, imageCapture);
252
264
  preview.setSurfaceProvider(previewView.getSurfaceProvider());
253
265
  } catch (Exception e) {
254
266
  echo("Failed to bind camera to lifecycle: " + e.getMessage());
@@ -380,6 +392,12 @@ public class CapacitorScannerPlugin extends Plugin {
380
392
  return;
381
393
  }
382
394
 
395
+ // Ratio must be between 0 and 1, where 0 or 1 means no reduction.
396
+ Double qualityRatioObj = call.getDouble("qualityRatio", 1.0);
397
+ double qualityRatio = qualityRatioObj != null ? qualityRatioObj : 1.0;
398
+
399
+
400
+ // Temporary stream to receive the captured image data.
383
401
  ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
384
402
 
385
403
  ImageCapture.OutputFileOptions outputOptions =
@@ -390,13 +408,45 @@ public class CapacitorScannerPlugin extends Plugin {
390
408
  @Override
391
409
  public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
392
410
  try {
393
- byte[] bytes = outputStream.toByteArray();
411
+ // Get the original image bytes.
412
+ byte[] originalBytes = outputStream.toByteArray();
413
+ int originalSize = originalBytes.length; // in bytes
414
+
415
+ // If qualityRatio is between 0 and 1 exclusively, perform quality reduction.
416
+ if (qualityRatio > 0 && qualityRatio < 1) {
417
+ // Calculate the target size in bytes.
418
+ int targetSize = (int) (originalSize * qualityRatio);
419
+
420
+ // Decode the captured image into a Bitmap.
421
+ Bitmap originalBitmap = BitmapFactory.decodeByteArray(originalBytes, 0, originalSize);
422
+ if (originalBitmap == null) {
423
+ call.reject("Failed to decode captured image.");
424
+ return;
425
+ }
426
+
427
+ // Prepare a stream to hold the compressed bitmap.
428
+ ByteArrayOutputStream compressedStream = new ByteArrayOutputStream();
429
+ int quality = 100; // start with maximum quality (100)
394
430
 
395
- String base64 = Base64.encodeToString(bytes, Base64.NO_WRAP);
431
+ // Iteratively compress the bitmap until it reaches the target file size.
432
+ do {
433
+ compressedStream.reset();
434
+ originalBitmap.compress(Bitmap.CompressFormat.JPEG, quality, compressedStream);
435
+ quality -= 5; // Lower the quality step by step.
436
+ } while (compressedStream.toByteArray().length > targetSize && quality > 10);
437
+
438
+ // Use the compressed image bytes.
439
+ originalBytes = compressedStream.toByteArray();
440
+
441
+ // Cleanup: recycle the bitmap and close the compressed stream.
442
+ originalBitmap.recycle();
443
+ compressedStream.close();
444
+ }
396
445
 
446
+ // Convert the final image bytes to a Base64 string.
447
+ String base64 = Base64.encodeToString(originalBytes, Base64.NO_WRAP);
397
448
  JSObject ret = new JSObject();
398
449
  ret.put("imageBase64", "data:image/jpeg;base64," + base64);
399
-
400
450
  call.resolve(ret);
401
451
  } catch (Exception e) {
402
452
  call.reject("Failed to process image: " + e.getMessage());
@@ -416,13 +466,17 @@ public class CapacitorScannerPlugin extends Plugin {
416
466
  try {
417
467
  outputStream.close();
418
468
  } catch (IOException e) {
419
- echo("Failed to close output stream: " + e.getMessage());
469
+ echo("Failed to close output stream: " + e.getMessage());
420
470
  }
421
471
  }
422
472
  });
423
473
  }
424
474
 
425
475
 
476
+
477
+
478
+
479
+
426
480
  @PluginMethod
427
481
  public void checkPermissions(PluginCall call) {
428
482
  PermissionState cameraPermState = getPermissionState("camera");
@@ -466,6 +520,61 @@ public class CapacitorScannerPlugin extends Plugin {
466
520
  }
467
521
  }
468
522
 
523
+
524
+ @PluginMethod
525
+ public void flipCamera(PluginCall call) {
526
+ if (!isScanning.get()) {
527
+ call.reject("Camera is not running");
528
+ return;
529
+ }
530
+
531
+ getActivity().runOnUiThread(() -> {
532
+ try {
533
+ currentLensFacing = (currentLensFacing == CameraSelector.LENS_FACING_BACK)
534
+ ? CameraSelector.LENS_FACING_FRONT
535
+ : CameraSelector.LENS_FACING_BACK;
536
+
537
+ bindCamera(cameraProvider, previewView, currentLensFacing, call);
538
+ call.resolve();
539
+ } catch (Exception e) {
540
+ call.reject("Failed to flip camera: " + e.getMessage());
541
+ }
542
+ });
543
+ }
544
+
545
+ @PluginMethod
546
+ public void toggleFlash(PluginCall call) {
547
+ if (!isScanning.get()) {
548
+ call.reject("Camera is not running");
549
+ return;
550
+ }
551
+
552
+ if (!getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
553
+ call.reject("Device doesn't have flash capability");
554
+ return;
555
+ }
556
+
557
+ getActivity().runOnUiThread(() -> {
558
+ try {
559
+ if (camera != null) {
560
+ // Toggle the flash state
561
+ isFlashEnabled = !isFlashEnabled;
562
+ camera.getCameraControl().enableTorch(isFlashEnabled);
563
+
564
+ JSObject ret = new JSObject();
565
+ ret.put("enabled", isFlashEnabled);
566
+ call.resolve(ret);
567
+ } else {
568
+ call.reject("Camera is not initialized");
569
+ }
570
+ } catch (Exception e) {
571
+ call.reject("Failed to toggle flash: " + e.getMessage());
572
+ }
573
+ });
574
+ }
575
+
576
+
577
+
469
578
  private void logLongMessage(String message) {
470
579
  if (message.length() > 4000) {
471
580
  int chunkCount = message.length() / 4000;
package/dist/docs.json CHANGED
@@ -45,13 +45,20 @@
45
45
  },
46
46
  {
47
47
  "name": "capturePhoto",
48
- "signature": "() => Promise<CapturePhotoResult>",
49
- "parameters": [],
48
+ "signature": "(options?: CapturePhotoOptions | undefined) => Promise<CapturePhotoResult>",
49
+ "parameters": [
50
+ {
51
+ "name": "options",
52
+ "docs": "",
53
+ "type": "CapturePhotoOptions | undefined"
54
+ }
55
+ ],
50
56
  "returns": "Promise<CapturePhotoResult>",
51
57
  "tags": [],
52
58
  "docs": "",
53
59
  "complexTypes": [
54
- "CapturePhotoResult"
60
+ "CapturePhotoResult",
61
+ "CapturePhotoOptions"
55
62
  ],
56
63
  "slug": "capturephoto"
57
64
  },
@@ -79,6 +86,28 @@
79
86
  ],
80
87
  "slug": "requestpermissions"
81
88
  },
89
+ {
90
+ "name": "flipCamera",
91
+ "signature": "() => Promise<void>",
92
+ "parameters": [],
93
+ "returns": "Promise<void>",
94
+ "tags": [],
95
+ "docs": "",
96
+ "complexTypes": [],
97
+ "slug": "flipcamera"
98
+ },
99
+ {
100
+ "name": "toggleFlash",
101
+ "signature": "() => Promise<FlashResult>",
102
+ "parameters": [],
103
+ "returns": "Promise<FlashResult>",
104
+ "tags": [],
105
+ "docs": "",
106
+ "complexTypes": [
107
+ "FlashResult"
108
+ ],
109
+ "slug": "toggleflash"
110
+ },
82
111
  {
83
112
  "name": "addListener",
84
113
  "signature": "(event: 'barcodeScanned', listenerFunc: (result: BarcodeScannedEvent) => void) => Promise<void>",
@@ -215,6 +244,17 @@
215
244
  }
216
245
  ]
217
246
  },
247
+ {
248
+ "name": "CapturePhotoOptions",
249
+ "slug": "capturephotooptions",
250
+ "docs": "",
251
+ "types": [
252
+ {
253
+ "text": "{\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}",
254
+ "complexTypes": []
255
+ }
256
+ ]
257
+ },
218
258
  {
219
259
  "name": "PermissionsResult",
220
260
  "slug": "permissionsresult",
@@ -226,6 +266,17 @@
226
266
  }
227
267
  ]
228
268
  },
269
+ {
270
+ "name": "FlashResult",
271
+ "slug": "flashresult",
272
+ "docs": "",
273
+ "types": [
274
+ {
275
+ "text": "{ enabled: boolean }",
276
+ "complexTypes": []
277
+ }
278
+ ]
279
+ },
229
280
  {
230
281
  "name": "BarcodeScannedEvent",
231
282
  "slug": "barcodescannedevent",
@@ -2,9 +2,11 @@ export interface CapacitorScannerPlugin {
2
2
  startScanning(options?: ScannerOptions): Promise<void>;
3
3
  stopScanning(): Promise<void>;
4
4
  openSettings(): Promise<void>;
5
- capturePhoto(): Promise<CapturePhotoResult>;
5
+ capturePhoto(options?: CapturePhotoOptions): Promise<CapturePhotoResult>;
6
6
  checkPermissions(): Promise<PermissionsResult>;
7
7
  requestPermissions(): Promise<PermissionsResult>;
8
+ flipCamera(): Promise<void>;
9
+ toggleFlash(): Promise<FlashResult>;
8
10
  addListener(event: 'barcodeScanned', listenerFunc: (result: BarcodeScannedEvent) => void): Promise<void>;
9
11
  removeAllListeners(): Promise<void>;
10
12
  }
@@ -17,12 +19,23 @@ export declare type BarcodeScannedEvent = {
17
19
  scannedCode: string;
18
20
  format: string;
19
21
  };
22
+ export declare type CapturePhotoOptions = {
23
+ /**
24
+ * The desired quality of the captured image, expressed as a value between 0.0 (lowest quality, smallest file size)
25
+ * and 1.0 (highest quality, largest file size). Defaults to 1.0.
26
+ * This parameter directly influences the compression level of the resulting JPEG image.
27
+ */
28
+ qualityRatio?: number;
29
+ };
20
30
  export declare type PermissionsResult = {
21
31
  camera: 'prompt' | 'denied' | 'granted';
22
32
  };
23
33
  export declare type CapturePhotoResult = {
24
34
  imageBase64: string;
25
35
  };
36
+ export declare type FlashResult = {
37
+ enabled: boolean;
38
+ };
26
39
  export declare enum BarcodeFormat {
27
40
  Aztec = "AZTEC",
28
41
  Code39 = "CODE_39",
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAuBA,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 startScanning(options?: ScannerOptions): Promise<void>;\n stopScanning(): Promise<void>;\n openSettings(): Promise<void>;\n capturePhoto(): Promise<CapturePhotoResult>;\n checkPermissions(): Promise<PermissionsResult>;\n requestPermissions(): Promise<PermissionsResult>;\n addListener(event: 'barcodeScanned', listenerFunc: (result: BarcodeScannedEvent) => void): Promise<void>;\n removeAllListeners(): Promise<void>;\n}\n\nexport type ScannerOptions = {\n formats?: BarcodeFormat[];\n cameraDirection?: 'BACK' | 'FRONT';\n debounceTimeInMilli?: number\n};\n\nexport type BarcodeScannedEvent = { scannedCode: string; format: string };\n\nexport type PermissionsResult = { camera: 'prompt' | 'denied' | 'granted' };\n\nexport type CapturePhotoResult = { imageBase64: string };\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}\n"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAoCA,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 startScanning(options?: ScannerOptions): Promise<void>;\n stopScanning(): Promise<void>;\n openSettings(): Promise<void>;\n capturePhoto(options?: CapturePhotoOptions): Promise<CapturePhotoResult>;\n checkPermissions(): Promise<PermissionsResult>;\n requestPermissions(): Promise<PermissionsResult>;\n flipCamera(): Promise<void>;\n toggleFlash(): Promise<FlashResult>;\n addListener(event: 'barcodeScanned', listenerFunc: (result: BarcodeScannedEvent) => void): Promise<void>;\n removeAllListeners(): Promise<void>;\n}\n\nexport type ScannerOptions = {\n formats?: BarcodeFormat[];\n cameraDirection?: 'BACK' | 'FRONT';\n debounceTimeInMilli?: number\n};\n\nexport type BarcodeScannedEvent = { scannedCode: string; format: string };\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 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}"]}
@@ -23,6 +23,8 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
23
23
  CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise),
24
24
  CAPPluginMethod(name: "openSettings", returnType: CAPPluginReturnPromise),
25
25
  CAPPluginMethod(name: "capturePhoto", returnType: CAPPluginReturnPromise),
26
+ CAPPluginMethod(name: "flipCamera", returnType: CAPPluginReturnPromise),
27
+ CAPPluginMethod(name: "toggleFlash", returnType: CAPPluginReturnPromise),
26
28
  ]
27
29
 
28
30
  private var captureSession: AVCaptureSession?
@@ -54,7 +56,24 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
54
56
  return
55
57
  }
56
58
 
57
- let photoSettings = AVCapturePhotoSettings()
59
+ // Get quality ratio from options, default to 1.0, clamp between 0.0 and 1.0
60
+ let quality = call.getFloat("qualityRatio", 1.0)
61
+ let clampedQuality = max(0.0, min(1.0, quality))
62
+ let qualityNumber = NSNumber(value: clampedQuality) // Convert to NSNumber
63
+
64
+ var photoSettings = AVCapturePhotoSettings() // Initialize with default settings
65
+
66
+ // Check if JPEG is supported and apply compression settings if it is
67
+ if photoOutput.supportedPhotoCodecTypes(for: .jpg).contains(.jpeg) {
68
+ // Create settings specific for JPEG with quality
69
+ photoSettings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg, // Use the type directly
70
+ AVVideoCompressionPropertiesKey: [AVVideoQualityKey: qualityNumber]])
71
+ photoSettings.photoQualityPrioritization = .quality // Prioritize quality
72
+ } else {
73
+ print("JPEG codec not supported for photo capture. Using default settings.")
74
+ // photoSettings remains the default initialized one
75
+ }
76
+
58
77
  self.capturePhotoCall = call
59
78
  photoOutput.capturePhoto(with: photoSettings, delegate: self)
60
79
  }
@@ -376,6 +395,89 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
376
395
  self.scannedCodesVotes[payload] = voteStatus
377
396
  }
378
397
  }
398
+
399
+ @objc func flipCamera(_ call: CAPPluginCall) {
400
+ guard let session = self.captureSession else {
401
+ call.reject("Scanning session not active.")
402
+ return
403
+ }
404
+
405
+ DispatchQueue.global(qos: .userInitiated).async {
406
+ session.beginConfiguration()
407
+ // Defer ensures commitConfiguration is called even if errors occur
408
+ defer { session.commitConfiguration() }
409
+
410
+ guard let currentInput = session.inputs.first as? AVCaptureDeviceInput else {
411
+ call.reject("Could not get current camera input.")
412
+ return
413
+ }
414
+
415
+ let currentPosition = currentInput.device.position
416
+ let preferredPosition: AVCaptureDevice.Position = (currentPosition == .back) ? .front : .back
417
+
418
+ guard let newDevice = self.getCaptureDevice(position: preferredPosition) else {
419
+ call.reject("Could not find camera for position: \(preferredPosition).")
420
+ return
421
+ }
422
+
423
+ guard let newInput = try? AVCaptureDeviceInput(device: newDevice) else {
424
+ call.reject("Could not create input for new camera device.")
425
+ return
426
+ }
427
+
428
+ // Remove the existing input
429
+ session.removeInput(currentInput)
430
+
431
+ // Add the new input
432
+ if session.canAddInput(newInput) {
433
+ session.addInput(newInput)
434
+
435
+ // Session automatically handles connecting outputs (like photoOutput and videoDataOutput)
436
+ // to the new input. No explicit reconnection needed here.
437
+
438
+ // Ensure preview layer orientation is updated for the new camera
439
+ DispatchQueue.main.async {
440
+ self.updatePreviewOrientation()
441
+ call.resolve() // Resolve the call on the main thread after UI update
442
+ }
443
+
444
+ } else {
445
+ // Important: If adding the new input fails, try to add the old one back!
446
+ print("Failed to add new input, attempting to restore previous input.")
447
+ if session.canAddInput(currentInput) {
448
+ session.addInput(currentInput)
449
+ }
450
+ call.reject("Could not add new camera input to session.")
451
+ }
452
+ }
453
+ }
454
+
455
+ @objc func toggleFlash(_ call: CAPPluginCall) {
456
+ guard let session = self.captureSession, let currentInput = session.inputs.first as? AVCaptureDeviceInput else {
457
+ call.reject("Scanning session not active or camera input not found.")
458
+ return
459
+ }
460
+
461
+ let device = currentInput.device
462
+
463
+ guard device.hasTorch, device.isTorchAvailable else {
464
+ call.resolve(["enabled": false]) // Report flash as disabled if not available/supported
465
+ return
466
+ }
467
+
468
+ do {
469
+ try device.lockForConfiguration()
470
+ let currentMode = device.torchMode
471
+ let newMode: AVCaptureDevice.TorchMode = (currentMode == .on) ? .off : .on
472
+ if device.isTorchModeSupported(newMode) {
473
+ device.torchMode = newMode
474
+ }
475
+ device.unlockForConfiguration()
476
+ call.resolve(["enabled": device.torchMode == .on])
477
+ } catch {
478
+ call.reject("Could not lock device for flash configuration: \(error.localizedDescription)")
479
+ }
480
+ }
379
481
  }
380
482
 
381
483
  extension CapacitorScannerPlugin: AVCaptureVideoDataOutputSampleBufferDelegate {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scr2em/capacitor-scanner",
3
- "version": "6.0.6",
3
+ "version": "6.0.8",
4
4
  "description": "scan codes",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",