@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 +39 -3
- package/android/src/main/java/com/leadliaion/capacitorscanner/CapacitorScannerPlugin.java +115 -6
- package/dist/docs.json +54 -3
- package/dist/esm/definitions.d.ts +14 -1
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorScannerPlugin/CapacitorScannerPlugin.swift +103 -1
- package/package.json +1 -1
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<<a href="#capturephotoresult">CapturePhotoResult</a>></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<<a href="#flashresult">FlashResult</a>></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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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":"
|
|
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
|
-
|
|
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 {
|