@capgo/camera-preview 8.3.7 → 8.3.8-beta.pr356.31.1
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 +187 -35
- package/android/build.gradle +1 -0
- package/android/src/main/AndroidManifest.xml +5 -0
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraPreview.java +337 -9
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java +280 -0
- package/dist/docs.json +345 -11
- package/dist/esm/definitions.d.ts +83 -7
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +13 -3
- package/dist/esm/web.js +169 -9
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +169 -9
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +169 -9
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +167 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +72 -3
- package/package.json +7 -3
|
@@ -22,9 +22,11 @@ import android.hardware.camera2.CameraCharacteristics;
|
|
|
22
22
|
import android.hardware.camera2.CameraManager;
|
|
23
23
|
import android.hardware.camera2.CaptureRequest;
|
|
24
24
|
import android.location.Location;
|
|
25
|
+
import android.media.Image;
|
|
25
26
|
import android.media.MediaScannerConnection;
|
|
26
27
|
import android.os.Build;
|
|
27
28
|
import android.os.Environment;
|
|
29
|
+
import android.os.SystemClock;
|
|
28
30
|
import android.util.Base64;
|
|
29
31
|
import android.util.DisplayMetrics;
|
|
30
32
|
import android.util.Log;
|
|
@@ -47,9 +49,11 @@ import androidx.camera.core.AspectRatio;
|
|
|
47
49
|
import androidx.camera.core.Camera;
|
|
48
50
|
import androidx.camera.core.CameraInfo;
|
|
49
51
|
import androidx.camera.core.CameraSelector;
|
|
52
|
+
import androidx.camera.core.ExperimentalGetImage;
|
|
50
53
|
import androidx.camera.core.ExposureState;
|
|
51
54
|
import androidx.camera.core.FocusMeteringAction;
|
|
52
55
|
import androidx.camera.core.FocusMeteringResult;
|
|
56
|
+
import androidx.camera.core.ImageAnalysis;
|
|
53
57
|
import androidx.camera.core.ImageCapture;
|
|
54
58
|
import androidx.camera.core.ImageCaptureException;
|
|
55
59
|
import androidx.camera.core.ImageProxy;
|
|
@@ -83,6 +87,11 @@ import app.capgo.capacitor.camera.preview.model.CameraSessionConfiguration;
|
|
|
83
87
|
import app.capgo.capacitor.camera.preview.model.LensInfo;
|
|
84
88
|
import app.capgo.capacitor.camera.preview.model.ZoomFactors;
|
|
85
89
|
import com.google.common.util.concurrent.ListenableFuture;
|
|
90
|
+
import com.google.mlkit.vision.barcode.BarcodeScanner;
|
|
91
|
+
import com.google.mlkit.vision.barcode.BarcodeScannerOptions;
|
|
92
|
+
import com.google.mlkit.vision.barcode.BarcodeScanning;
|
|
93
|
+
import com.google.mlkit.vision.barcode.common.Barcode;
|
|
94
|
+
import com.google.mlkit.vision.common.InputImage;
|
|
86
95
|
import java.io.ByteArrayInputStream;
|
|
87
96
|
import java.io.ByteArrayOutputStream;
|
|
88
97
|
import java.io.File;
|
|
@@ -104,6 +113,7 @@ import java.util.concurrent.Executor;
|
|
|
104
113
|
import java.util.concurrent.ExecutorService;
|
|
105
114
|
import java.util.concurrent.Executors;
|
|
106
115
|
import java.util.concurrent.RejectedExecutionException;
|
|
116
|
+
import org.json.JSONArray;
|
|
107
117
|
import org.json.JSONObject;
|
|
108
118
|
|
|
109
119
|
public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
@@ -116,11 +126,18 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
116
126
|
void onPictureTakenError(String message);
|
|
117
127
|
void onSampleTaken(String result);
|
|
118
128
|
void onSampleTakenError(String message);
|
|
129
|
+
void onBarcodesScanned(JSONArray barcodes);
|
|
130
|
+
void onBarcodeScanError(String message);
|
|
119
131
|
void onCameraStarted(int width, int height, int x, int y);
|
|
120
132
|
void onCameraStartError(String message);
|
|
121
133
|
void onCameraStopped(CameraXView source);
|
|
122
134
|
}
|
|
123
135
|
|
|
136
|
+
public interface BarcodeScannerStartCallback {
|
|
137
|
+
void onStarted();
|
|
138
|
+
void onError(String message);
|
|
139
|
+
}
|
|
140
|
+
|
|
124
141
|
public interface VideoRecordingCallback {
|
|
125
142
|
void onSuccess(String filePath);
|
|
126
143
|
void onError(String message);
|
|
@@ -130,6 +147,8 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
130
147
|
private Camera camera;
|
|
131
148
|
private ImageCapture imageCapture;
|
|
132
149
|
private ImageCapture sampleImageCapture;
|
|
150
|
+
private ImageAnalysis barcodeAnalysis;
|
|
151
|
+
private BarcodeScanner barcodeScanner;
|
|
133
152
|
private VideoCapture<Recorder> videoCapture;
|
|
134
153
|
private Recording currentRecording;
|
|
135
154
|
private File currentVideoFile;
|
|
@@ -168,6 +187,10 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
168
187
|
private volatile boolean isCapturingPhoto = false;
|
|
169
188
|
private volatile boolean stopRequested = false;
|
|
170
189
|
private volatile boolean previewDetachedOnDeferredStop = false;
|
|
190
|
+
private volatile boolean isBarcodeScannerActive = false;
|
|
191
|
+
private volatile boolean isBarcodeFrameProcessing = false;
|
|
192
|
+
private volatile long lastBarcodeFrameAtMs = 0L;
|
|
193
|
+
private volatile long barcodeDetectionIntervalMs = 500L;
|
|
171
194
|
|
|
172
195
|
// Operation coordination (acts like a semaphore to prevent stop during active ops)
|
|
173
196
|
private final Object operationLock = new Object();
|
|
@@ -601,6 +624,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
601
624
|
|
|
602
625
|
mainExecutor.execute(() -> {
|
|
603
626
|
try {
|
|
627
|
+
stopBarcodeScannerInternal(false);
|
|
604
628
|
lifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
|
|
605
629
|
if (cameraProvider != null) {
|
|
606
630
|
cameraProvider.unbindAll();
|
|
@@ -1519,6 +1543,262 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1519
1543
|
);
|
|
1520
1544
|
}
|
|
1521
1545
|
|
|
1546
|
+
public void startBarcodeScanner(List<String> formats, int detectionIntervalMs, BarcodeScannerStartCallback callback) {
|
|
1547
|
+
if (!isRunning || cameraProvider == null || currentCameraSelector == null || cameraExecutor == null) {
|
|
1548
|
+
callback.onError("Camera is not running");
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
mainExecutor.execute(() -> {
|
|
1553
|
+
try {
|
|
1554
|
+
stopBarcodeScannerInternal(true);
|
|
1555
|
+
barcodeScanner = createBarcodeScanner(formats);
|
|
1556
|
+
barcodeDetectionIntervalMs = Math.max(100L, detectionIntervalMs);
|
|
1557
|
+
lastBarcodeFrameAtMs = 0L;
|
|
1558
|
+
isBarcodeFrameProcessing = false;
|
|
1559
|
+
isBarcodeScannerActive = true;
|
|
1560
|
+
|
|
1561
|
+
ResolutionSelector barcodeResolutionSelector = new ResolutionSelector.Builder()
|
|
1562
|
+
.setResolutionStrategy(
|
|
1563
|
+
new ResolutionStrategy(new Size(1280, 720), ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER)
|
|
1564
|
+
)
|
|
1565
|
+
.build();
|
|
1566
|
+
|
|
1567
|
+
barcodeAnalysis = new ImageAnalysis.Builder()
|
|
1568
|
+
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
|
1569
|
+
.setResolutionSelector(barcodeResolutionSelector)
|
|
1570
|
+
.build();
|
|
1571
|
+
barcodeAnalysis.setAnalyzer(cameraExecutor, this::analyzeBarcodeImage);
|
|
1572
|
+
|
|
1573
|
+
cameraProvider.bindToLifecycle(this, currentCameraSelector, barcodeAnalysis);
|
|
1574
|
+
callback.onStarted();
|
|
1575
|
+
} catch (Exception e) {
|
|
1576
|
+
stopBarcodeScannerInternal(true);
|
|
1577
|
+
callback.onError("Failed to start barcode scanner: " + e.getMessage());
|
|
1578
|
+
}
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
public void stopBarcodeScanner() {
|
|
1583
|
+
mainExecutor.execute(() -> stopBarcodeScannerInternal(true));
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
private void stopBarcodeScannerInternal(boolean unbindAnalysis) {
|
|
1587
|
+
isBarcodeScannerActive = false;
|
|
1588
|
+
isBarcodeFrameProcessing = false;
|
|
1589
|
+
lastBarcodeFrameAtMs = 0L;
|
|
1590
|
+
|
|
1591
|
+
if (barcodeAnalysis != null) {
|
|
1592
|
+
barcodeAnalysis.clearAnalyzer();
|
|
1593
|
+
if (unbindAnalysis && cameraProvider != null) {
|
|
1594
|
+
try {
|
|
1595
|
+
cameraProvider.unbind(barcodeAnalysis);
|
|
1596
|
+
} catch (Exception e) {
|
|
1597
|
+
Log.w(TAG, "stopBarcodeScannerInternal: failed to unbind barcode analysis", e);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
barcodeAnalysis = null;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
if (barcodeScanner != null) {
|
|
1604
|
+
try {
|
|
1605
|
+
barcodeScanner.close();
|
|
1606
|
+
} catch (Exception e) {
|
|
1607
|
+
Log.w(TAG, "stopBarcodeScannerInternal: failed to close scanner", e);
|
|
1608
|
+
}
|
|
1609
|
+
barcodeScanner = null;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
private BarcodeScanner createBarcodeScanner(List<String> formats) {
|
|
1614
|
+
if (formats == null || formats.isEmpty()) {
|
|
1615
|
+
return BarcodeScanning.getClient();
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
int[] mlKitFormats = toMlKitBarcodeFormats(formats);
|
|
1619
|
+
if (mlKitFormats.length == 0) {
|
|
1620
|
+
throw new IllegalArgumentException("No supported barcode formats requested");
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
BarcodeScannerOptions.Builder builder = new BarcodeScannerOptions.Builder();
|
|
1624
|
+
int[] extraFormats = Arrays.copyOfRange(mlKitFormats, 1, mlKitFormats.length);
|
|
1625
|
+
builder.setBarcodeFormats(mlKitFormats[0], extraFormats);
|
|
1626
|
+
return BarcodeScanning.getClient(builder.build());
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
private int[] toMlKitBarcodeFormats(List<String> formats) {
|
|
1630
|
+
if (formats == null || formats.isEmpty()) {
|
|
1631
|
+
return new int[0];
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
List<Integer> mappedFormats = new ArrayList<>();
|
|
1635
|
+
for (String format : formats) {
|
|
1636
|
+
int mappedFormat = toMlKitBarcodeFormat(format);
|
|
1637
|
+
if (mappedFormat != -1 && !mappedFormats.contains(mappedFormat)) {
|
|
1638
|
+
mappedFormats.add(mappedFormat);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
int[] result = new int[mappedFormats.size()];
|
|
1643
|
+
for (int i = 0; i < mappedFormats.size(); i++) {
|
|
1644
|
+
result[i] = mappedFormats.get(i);
|
|
1645
|
+
}
|
|
1646
|
+
return result;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
private int toMlKitBarcodeFormat(String format) {
|
|
1650
|
+
if (format == null) {
|
|
1651
|
+
return -1;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
switch (format) {
|
|
1655
|
+
case "aztec":
|
|
1656
|
+
return Barcode.FORMAT_AZTEC;
|
|
1657
|
+
case "codabar":
|
|
1658
|
+
return Barcode.FORMAT_CODABAR;
|
|
1659
|
+
case "code_39":
|
|
1660
|
+
return Barcode.FORMAT_CODE_39;
|
|
1661
|
+
case "code_93":
|
|
1662
|
+
return Barcode.FORMAT_CODE_93;
|
|
1663
|
+
case "code_128":
|
|
1664
|
+
return Barcode.FORMAT_CODE_128;
|
|
1665
|
+
case "data_matrix":
|
|
1666
|
+
return Barcode.FORMAT_DATA_MATRIX;
|
|
1667
|
+
case "ean_8":
|
|
1668
|
+
return Barcode.FORMAT_EAN_8;
|
|
1669
|
+
case "ean_13":
|
|
1670
|
+
return Barcode.FORMAT_EAN_13;
|
|
1671
|
+
case "itf":
|
|
1672
|
+
return Barcode.FORMAT_ITF;
|
|
1673
|
+
case "pdf417":
|
|
1674
|
+
return Barcode.FORMAT_PDF417;
|
|
1675
|
+
case "qr_code":
|
|
1676
|
+
return Barcode.FORMAT_QR_CODE;
|
|
1677
|
+
case "upc_a":
|
|
1678
|
+
return Barcode.FORMAT_UPC_A;
|
|
1679
|
+
case "upc_e":
|
|
1680
|
+
return Barcode.FORMAT_UPC_E;
|
|
1681
|
+
default:
|
|
1682
|
+
return -1;
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
@OptIn(markerClass = ExperimentalGetImage.class)
|
|
1687
|
+
private void analyzeBarcodeImage(@NonNull ImageProxy imageProxy) {
|
|
1688
|
+
BarcodeScanner scanner = barcodeScanner;
|
|
1689
|
+
long now = SystemClock.elapsedRealtime();
|
|
1690
|
+
|
|
1691
|
+
if (
|
|
1692
|
+
!isBarcodeScannerActive ||
|
|
1693
|
+
scanner == null ||
|
|
1694
|
+
isBarcodeFrameProcessing ||
|
|
1695
|
+
now - lastBarcodeFrameAtMs < barcodeDetectionIntervalMs
|
|
1696
|
+
) {
|
|
1697
|
+
imageProxy.close();
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
Image mediaImage = imageProxy.getImage();
|
|
1702
|
+
if (mediaImage == null) {
|
|
1703
|
+
imageProxy.close();
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
isBarcodeFrameProcessing = true;
|
|
1708
|
+
lastBarcodeFrameAtMs = now;
|
|
1709
|
+
|
|
1710
|
+
InputImage image = InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees());
|
|
1711
|
+
scanner
|
|
1712
|
+
.process(image)
|
|
1713
|
+
.addOnSuccessListener((barcodes) -> {
|
|
1714
|
+
if (!isBarcodeScannerActive || barcodes.isEmpty() || listener == null) {
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
JSONArray result = new JSONArray();
|
|
1719
|
+
for (Barcode barcode : barcodes) {
|
|
1720
|
+
JSONObject barcodeJson = barcodeToJson(barcode);
|
|
1721
|
+
if (barcodeJson != null) {
|
|
1722
|
+
result.put(barcodeJson);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
if (result.length() > 0) {
|
|
1727
|
+
listener.onBarcodesScanned(result);
|
|
1728
|
+
}
|
|
1729
|
+
})
|
|
1730
|
+
.addOnFailureListener((e) -> {
|
|
1731
|
+
if (isBarcodeScannerActive && listener != null) {
|
|
1732
|
+
listener.onBarcodeScanError("Barcode scan failed: " + e.getMessage());
|
|
1733
|
+
}
|
|
1734
|
+
})
|
|
1735
|
+
.addOnCompleteListener((task) -> {
|
|
1736
|
+
isBarcodeFrameProcessing = false;
|
|
1737
|
+
imageProxy.close();
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
private JSONObject barcodeToJson(Barcode barcode) {
|
|
1742
|
+
String value = barcode.getRawValue();
|
|
1743
|
+
if (value == null || value.isEmpty()) {
|
|
1744
|
+
return null;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
JSONObject barcodeJson = new JSONObject();
|
|
1748
|
+
try {
|
|
1749
|
+
barcodeJson.put("value", value);
|
|
1750
|
+
barcodeJson.put("format", fromMlKitBarcodeFormat(barcode.getFormat()));
|
|
1751
|
+
|
|
1752
|
+
String displayValue = barcode.getDisplayValue();
|
|
1753
|
+
if (displayValue != null) {
|
|
1754
|
+
barcodeJson.put("displayValue", displayValue);
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
byte[] rawBytes = barcode.getRawBytes();
|
|
1758
|
+
if (rawBytes != null && rawBytes.length > 0) {
|
|
1759
|
+
barcodeJson.put("rawBytes", Base64.encodeToString(rawBytes, Base64.NO_WRAP));
|
|
1760
|
+
}
|
|
1761
|
+
} catch (Exception e) {
|
|
1762
|
+
Log.w(TAG, "barcodeToJson: failed to serialize barcode", e);
|
|
1763
|
+
return null;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
return barcodeJson;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
private String fromMlKitBarcodeFormat(int format) {
|
|
1770
|
+
switch (format) {
|
|
1771
|
+
case Barcode.FORMAT_AZTEC:
|
|
1772
|
+
return "aztec";
|
|
1773
|
+
case Barcode.FORMAT_CODABAR:
|
|
1774
|
+
return "codabar";
|
|
1775
|
+
case Barcode.FORMAT_CODE_39:
|
|
1776
|
+
return "code_39";
|
|
1777
|
+
case Barcode.FORMAT_CODE_93:
|
|
1778
|
+
return "code_93";
|
|
1779
|
+
case Barcode.FORMAT_CODE_128:
|
|
1780
|
+
return "code_128";
|
|
1781
|
+
case Barcode.FORMAT_DATA_MATRIX:
|
|
1782
|
+
return "data_matrix";
|
|
1783
|
+
case Barcode.FORMAT_EAN_8:
|
|
1784
|
+
return "ean_8";
|
|
1785
|
+
case Barcode.FORMAT_EAN_13:
|
|
1786
|
+
return "ean_13";
|
|
1787
|
+
case Barcode.FORMAT_ITF:
|
|
1788
|
+
return "itf";
|
|
1789
|
+
case Barcode.FORMAT_PDF417:
|
|
1790
|
+
return "pdf417";
|
|
1791
|
+
case Barcode.FORMAT_QR_CODE:
|
|
1792
|
+
return "qr_code";
|
|
1793
|
+
case Barcode.FORMAT_UPC_A:
|
|
1794
|
+
return "upc_a";
|
|
1795
|
+
case Barcode.FORMAT_UPC_E:
|
|
1796
|
+
return "upc_e";
|
|
1797
|
+
default:
|
|
1798
|
+
return "unknown";
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1522
1802
|
private void copyMutableSessionConfigState(CameraSessionConfiguration source, CameraSessionConfiguration target) {
|
|
1523
1803
|
target.setCentered(source.isCentered());
|
|
1524
1804
|
target.setTargetZoom(source.getTargetZoom());
|