@capgo/camera-preview 8.4.2 → 8.4.3
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/android/src/main/java/app/capgo/capacitor/camera/preview/CameraPreview.java +179 -67
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java +149 -69
- package/android/src/main/java/app/capgo/capacitor/camera/preview/model/CameraSessionConfiguration.java +9 -0
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +1 -1
- package/package.json +1 -1
|
@@ -83,21 +83,20 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
83
83
|
protected void handleOnResume() {
|
|
84
84
|
super.handleOnResume();
|
|
85
85
|
if (lastSessionConfig != null) {
|
|
86
|
-
// Set to black to avoid flicker, transparent set later
|
|
87
|
-
if (lastSessionConfig.isToBack()) {
|
|
88
|
-
try {
|
|
89
|
-
getBridge()
|
|
90
|
-
.getActivity()
|
|
91
|
-
.getWindow()
|
|
92
|
-
.setBackgroundDrawable(new android.graphics.drawable.ColorDrawable(android.graphics.Color.BLACK));
|
|
93
|
-
getBridge().getWebView().setBackgroundColor(android.graphics.Color.BLACK);
|
|
94
|
-
} catch (Exception ignored) {}
|
|
95
|
-
}
|
|
96
86
|
// Recreate camera with last known configuration
|
|
97
|
-
if (cameraXView == null) {
|
|
87
|
+
if (cameraXView == null || !cameraXView.isRunning() || cameraXView.isStopping()) {
|
|
98
88
|
cameraXView = new CameraXView(getContext(), getBridge().getWebView());
|
|
99
89
|
cameraXView.setListener(this);
|
|
100
90
|
}
|
|
91
|
+
if (lastSessionConfig.isToBack()) {
|
|
92
|
+
if (usesFullStackTransparentBackgroundWorkaround()) {
|
|
93
|
+
activateTransparentBackgroundsForToBack(cameraXView);
|
|
94
|
+
} else {
|
|
95
|
+
prepareTransparentBackgroundsForToBack(cameraXView);
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
toBackVisualStateActive = false;
|
|
99
|
+
}
|
|
101
100
|
cameraXView.startSession(lastSessionConfig);
|
|
102
101
|
}
|
|
103
102
|
}
|
|
@@ -110,12 +109,16 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
110
109
|
cameraXView = null;
|
|
111
110
|
}
|
|
112
111
|
lastSessionConfig = null;
|
|
112
|
+
toBackVisualStateActive = false;
|
|
113
|
+
restoreOriginalWindowBackground(getBridge().getActivity());
|
|
114
|
+
restoreWebViewVisualState();
|
|
113
115
|
restoreSystemUiForToBackMode(getBridge().getActivity());
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
private CameraSessionConfiguration lastSessionConfig;
|
|
117
119
|
|
|
118
120
|
private static final String TAG = "CameraPreview CameraXView";
|
|
121
|
+
private static final int DEFAULT_WEB_VIEW_BACKGROUND = Color.WHITE;
|
|
119
122
|
|
|
120
123
|
static final String CAMERA_WITH_AUDIO_PERMISSION_ALIAS = "cameraWithAudio";
|
|
121
124
|
static final String CAMERA_ONLY_PERMISSION_ALIAS = "cameraOnly";
|
|
@@ -138,11 +141,16 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
138
141
|
private boolean lastDisableAudio = true;
|
|
139
142
|
private boolean lastIncludeSafeAreaInsets = false;
|
|
140
143
|
private Drawable originalWindowBackground;
|
|
144
|
+
private boolean originalWindowBackgroundCaptured = false;
|
|
145
|
+
private Drawable originalWebViewBackground;
|
|
146
|
+
private boolean originalWebViewBackgroundCaptured = false;
|
|
141
147
|
private Float originalWebViewAlpha;
|
|
142
148
|
private Drawable originalWebViewParentBackground;
|
|
149
|
+
private boolean originalWebViewParentBackgroundCaptured = false;
|
|
143
150
|
private Integer originalStatusBarColor;
|
|
144
151
|
private Integer originalNavigationBarColor;
|
|
145
152
|
private Boolean originalNavigationBarContrastEnforced;
|
|
153
|
+
private volatile boolean toBackVisualStateActive = false;
|
|
146
154
|
private boolean isCameraPermissionDialogShowing = false;
|
|
147
155
|
private boolean pendingStartBarcodeScanner = false;
|
|
148
156
|
private List<String> pendingStartBarcodeFormats = new ArrayList<>();
|
|
@@ -524,13 +532,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
524
532
|
}
|
|
525
533
|
// Manual stops should not trigger automatic resume with stale config
|
|
526
534
|
lastSessionConfig = null;
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
try {
|
|
530
|
-
getBridge().getActivity().getWindow().setBackgroundDrawable(originalWindowBackground);
|
|
531
|
-
} catch (Exception ignored) {}
|
|
532
|
-
originalWindowBackground = null;
|
|
533
|
-
}
|
|
535
|
+
toBackVisualStateActive = false;
|
|
536
|
+
restoreOriginalWindowBackground(getBridge().getActivity());
|
|
534
537
|
restoreWebViewVisualState();
|
|
535
538
|
restoreSystemUiForToBackMode(getBridge().getActivity());
|
|
536
539
|
call.resolve();
|
|
@@ -1051,16 +1054,14 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
1051
1054
|
.getActivity()
|
|
1052
1055
|
.runOnUiThread(() -> {
|
|
1053
1056
|
lockSystemUiForToBackMode(getBridge().getActivity(), toBack);
|
|
1054
|
-
// Ensure transparent background when preview is behind the WebView (Android 10 fix)
|
|
1055
1057
|
if (toBack) {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
} catch (Exception ignored) {}
|
|
1058
|
+
if (usesFullStackTransparentBackgroundWorkaround()) {
|
|
1059
|
+
activateTransparentBackgroundsForToBack(cameraXView);
|
|
1060
|
+
} else {
|
|
1061
|
+
prepareTransparentBackgroundsForToBack(cameraXView);
|
|
1062
|
+
}
|
|
1063
|
+
} else {
|
|
1064
|
+
toBackVisualStateActive = false;
|
|
1064
1065
|
}
|
|
1065
1066
|
DisplayMetrics metrics = getBridge().getActivity().getResources().getDisplayMetrics();
|
|
1066
1067
|
if (lockOrientation) {
|
|
@@ -1337,6 +1338,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
1337
1338
|
config.setTargetZoom(finalTargetZoom);
|
|
1338
1339
|
config.setCentered(isCentered);
|
|
1339
1340
|
config.setEnablePhysicalDeviceSelection(enablePhysicalDeviceSelection);
|
|
1341
|
+
config.setBarcodeScannerEnabled(barcodeScannerOptions != null);
|
|
1340
1342
|
setPendingStartBarcodeScanner(barcodeScannerOptions);
|
|
1341
1343
|
|
|
1342
1344
|
bridge.saveCall(call);
|
|
@@ -1626,10 +1628,12 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
1626
1628
|
return;
|
|
1627
1629
|
}
|
|
1628
1630
|
// Ensure reference is cleared once the originating CameraXView has fully stopped
|
|
1629
|
-
if (cameraXView == source) {
|
|
1631
|
+
if (source != null && cameraXView == source) {
|
|
1630
1632
|
cameraXView = null;
|
|
1631
1633
|
}
|
|
1632
|
-
|
|
1634
|
+
if (!toBackVisualStateActive) {
|
|
1635
|
+
restoreWebViewVisualState();
|
|
1636
|
+
}
|
|
1633
1637
|
|
|
1634
1638
|
PluginCall queuedCall = null;
|
|
1635
1639
|
synchronized (pendingStartLock) {
|
|
@@ -1676,6 +1680,72 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
1676
1680
|
return manufacturer.contains("xiaomi") || brand.contains("xiaomi") || brand.contains("redmi") || brand.contains("poco");
|
|
1677
1681
|
}
|
|
1678
1682
|
|
|
1683
|
+
private boolean usesFullStackTransparentBackgroundWorkaround() {
|
|
1684
|
+
String manufacturer = Build.MANUFACTURER != null ? Build.MANUFACTURER.toLowerCase(Locale.US) : "";
|
|
1685
|
+
String brand = Build.BRAND != null ? Build.BRAND.toLowerCase(Locale.US) : "";
|
|
1686
|
+
return (
|
|
1687
|
+
isMiuiDevice() ||
|
|
1688
|
+
manufacturer.contains("huawei") ||
|
|
1689
|
+
manufacturer.contains("honor") ||
|
|
1690
|
+
brand.contains("huawei") ||
|
|
1691
|
+
brand.contains("honor")
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
private void captureOriginalWindowBackground(Activity activity) {
|
|
1696
|
+
if (activity == null) {
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
synchronized (this) {
|
|
1700
|
+
if (!originalWindowBackgroundCaptured) {
|
|
1701
|
+
originalWindowBackground = activity.getWindow().getDecorView().getBackground();
|
|
1702
|
+
originalWindowBackgroundCaptured = true;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
private void captureOriginalWebViewVisualState(WebView webView, ViewGroup webViewParent) {
|
|
1708
|
+
if (webView == null) {
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
synchronized (this) {
|
|
1712
|
+
if (!originalWebViewBackgroundCaptured) {
|
|
1713
|
+
originalWebViewBackground = webView.getBackground();
|
|
1714
|
+
originalWebViewBackgroundCaptured = true;
|
|
1715
|
+
}
|
|
1716
|
+
if (originalWebViewAlpha == null) {
|
|
1717
|
+
originalWebViewAlpha = webView.getAlpha();
|
|
1718
|
+
}
|
|
1719
|
+
if (webViewParent != null && !originalWebViewParentBackgroundCaptured) {
|
|
1720
|
+
originalWebViewParentBackground = webViewParent.getBackground();
|
|
1721
|
+
originalWebViewParentBackgroundCaptured = true;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
private void restoreOriginalWindowBackground(Activity activity) {
|
|
1727
|
+
final Drawable backgroundToRestore;
|
|
1728
|
+
final boolean captured;
|
|
1729
|
+
synchronized (this) {
|
|
1730
|
+
backgroundToRestore = originalWindowBackground;
|
|
1731
|
+
captured = originalWindowBackgroundCaptured;
|
|
1732
|
+
originalWindowBackground = null;
|
|
1733
|
+
originalWindowBackgroundCaptured = false;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
if (!captured || activity == null) {
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
activity.runOnUiThread(() -> {
|
|
1741
|
+
try {
|
|
1742
|
+
activity.getWindow().setBackgroundDrawable(backgroundToRestore);
|
|
1743
|
+
} catch (Exception e) {
|
|
1744
|
+
Log.w(TAG, "Failed to restore window background", e);
|
|
1745
|
+
}
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1679
1749
|
private int toOpaqueColor(int color) {
|
|
1680
1750
|
return Color.argb(255, Color.red(color), Color.green(color), Color.blue(color));
|
|
1681
1751
|
}
|
|
@@ -1741,35 +1811,46 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
1741
1811
|
});
|
|
1742
1812
|
}
|
|
1743
1813
|
|
|
1744
|
-
private void
|
|
1745
|
-
if (!isToBackMode()) {
|
|
1746
|
-
return;
|
|
1747
|
-
}
|
|
1814
|
+
private void prepareTransparentBackgroundsForToBack(CameraXView visualStateOwner) {
|
|
1748
1815
|
Activity activity = getActivity();
|
|
1749
1816
|
WebView webView = getBridge().getWebView();
|
|
1750
1817
|
if (activity == null || webView == null) {
|
|
1751
1818
|
return;
|
|
1752
1819
|
}
|
|
1753
1820
|
|
|
1754
|
-
|
|
1755
|
-
|
|
1821
|
+
toBackVisualStateActive = true;
|
|
1822
|
+
final ViewGroup webViewParent = (ViewGroup) webView.getParent();
|
|
1823
|
+
captureOriginalWindowBackground(activity);
|
|
1824
|
+
captureOriginalWebViewVisualState(webView, webViewParent);
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
private void activateTransparentBackgroundsForToBack(CameraXView visualStateOwner) {
|
|
1828
|
+
prepareTransparentBackgroundsForToBack(visualStateOwner);
|
|
1829
|
+
Activity activity = getActivity();
|
|
1830
|
+
WebView webView = getBridge().getWebView();
|
|
1831
|
+
if (activity == null || webView == null) {
|
|
1832
|
+
return;
|
|
1756
1833
|
}
|
|
1757
1834
|
|
|
1758
1835
|
final ViewGroup webViewParent = (ViewGroup) webView.getParent();
|
|
1759
|
-
if (webViewParent != null && originalWebViewParentBackground == null) {
|
|
1760
|
-
originalWebViewParentBackground = webViewParent.getBackground();
|
|
1761
|
-
}
|
|
1762
1836
|
|
|
1763
1837
|
Runnable apply = () -> {
|
|
1764
1838
|
try {
|
|
1765
|
-
|
|
1766
|
-
|
|
1839
|
+
if (!toBackVisualStateActive || visualStateOwner == null || cameraXView != visualStateOwner) {
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
boolean fullStackWorkaround = usesFullStackTransparentBackgroundWorkaround();
|
|
1843
|
+
if (fullStackWorkaround) {
|
|
1844
|
+
activity.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
|
|
1845
|
+
}
|
|
1846
|
+
if (webViewParent != null && fullStackWorkaround) {
|
|
1767
1847
|
webViewParent.setBackgroundColor(Color.TRANSPARENT);
|
|
1768
1848
|
}
|
|
1769
|
-
|
|
1770
|
-
// fully transparent layers as black.
|
|
1771
|
-
webView.setBackgroundColor(Color.argb(1, 255, 255, 255));
|
|
1849
|
+
webView.setBackgroundColor(isMiuiDevice() ? Color.argb(1, 255, 255, 255) : Color.TRANSPARENT);
|
|
1772
1850
|
webView.setAlpha(isMiuiDevice() ? 0.99f : (originalWebViewAlpha != null ? originalWebViewAlpha : 1f));
|
|
1851
|
+
if (webViewParent != null) {
|
|
1852
|
+
webViewParent.requestTransparentRegion(webView);
|
|
1853
|
+
}
|
|
1773
1854
|
} catch (Exception e) {
|
|
1774
1855
|
Log.w(TAG, "Failed to set backgrounds to transparent", e);
|
|
1775
1856
|
}
|
|
@@ -1784,13 +1865,41 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
1784
1865
|
});
|
|
1785
1866
|
}
|
|
1786
1867
|
|
|
1868
|
+
private void applyTransparentBackgroundsForToBack() {
|
|
1869
|
+
if (!isToBackMode()) {
|
|
1870
|
+
return;
|
|
1871
|
+
}
|
|
1872
|
+
activateTransparentBackgroundsForToBack(cameraXView);
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1787
1875
|
private void restoreWebViewVisualState() {
|
|
1876
|
+
if (toBackVisualStateActive) {
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1788
1880
|
Activity activity = getActivity();
|
|
1789
1881
|
WebView webView = getBridge().getWebView();
|
|
1790
|
-
final Float alphaToRestore
|
|
1791
|
-
final Drawable
|
|
1792
|
-
|
|
1793
|
-
|
|
1882
|
+
final Float alphaToRestore;
|
|
1883
|
+
final Drawable webViewBackground;
|
|
1884
|
+
final boolean webViewBackgroundCaptured;
|
|
1885
|
+
final Drawable parentBackground;
|
|
1886
|
+
final boolean parentBackgroundCaptured;
|
|
1887
|
+
synchronized (this) {
|
|
1888
|
+
alphaToRestore = originalWebViewAlpha;
|
|
1889
|
+
webViewBackground = originalWebViewBackground;
|
|
1890
|
+
webViewBackgroundCaptured = originalWebViewBackgroundCaptured;
|
|
1891
|
+
parentBackground = originalWebViewParentBackground;
|
|
1892
|
+
parentBackgroundCaptured = originalWebViewParentBackgroundCaptured;
|
|
1893
|
+
originalWebViewAlpha = null;
|
|
1894
|
+
originalWebViewBackground = null;
|
|
1895
|
+
originalWebViewBackgroundCaptured = false;
|
|
1896
|
+
originalWebViewParentBackground = null;
|
|
1897
|
+
originalWebViewParentBackgroundCaptured = false;
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
if (alphaToRestore == null && !webViewBackgroundCaptured && !parentBackgroundCaptured) {
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1794
1903
|
|
|
1795
1904
|
if (activity == null || webView == null) {
|
|
1796
1905
|
return;
|
|
@@ -1802,7 +1911,12 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
1802
1911
|
if (alphaToRestore != null) {
|
|
1803
1912
|
webView.setAlpha(alphaToRestore);
|
|
1804
1913
|
}
|
|
1805
|
-
if (
|
|
1914
|
+
if (webViewBackgroundCaptured) {
|
|
1915
|
+
webView.setBackground(webViewBackground);
|
|
1916
|
+
} else {
|
|
1917
|
+
webView.setBackgroundColor(DEFAULT_WEB_VIEW_BACKGROUND);
|
|
1918
|
+
}
|
|
1919
|
+
if (webViewParent != null && parentBackgroundCaptured) {
|
|
1806
1920
|
webViewParent.setBackground(parentBackground);
|
|
1807
1921
|
}
|
|
1808
1922
|
} catch (Exception ignored) {}
|
|
@@ -2042,7 +2156,22 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2042
2156
|
}
|
|
2043
2157
|
|
|
2044
2158
|
@Override
|
|
2045
|
-
public void onCameraStartError(String message) {
|
|
2159
|
+
public void onCameraStartError(CameraXView source, String message) {
|
|
2160
|
+
if (cameraXView != null && cameraXView != source) {
|
|
2161
|
+
Log.d(TAG, "onCameraStartError: ignoring callback from stale instance");
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
toBackVisualStateActive = false;
|
|
2165
|
+
if (cameraXView == source) {
|
|
2166
|
+
try {
|
|
2167
|
+
// Keep the reference until onCameraStopped clears it after native cleanup.
|
|
2168
|
+
source.stopSession();
|
|
2169
|
+
} catch (Exception e) {
|
|
2170
|
+
Log.w(TAG, "onCameraStartError: failed to stop failed camera session", e);
|
|
2171
|
+
cameraXView = null;
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2046
2175
|
PluginCall call = bridge.getSavedCall(cameraStartCallbackId);
|
|
2047
2176
|
if (call != null) {
|
|
2048
2177
|
call.reject(message);
|
|
@@ -2051,24 +2180,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2051
2180
|
resetPendingStartBarcodeScanner();
|
|
2052
2181
|
}
|
|
2053
2182
|
|
|
2054
|
-
|
|
2055
|
-
// Use synchronized block to ensure only one thread captures and clears the background.
|
|
2056
|
-
// Even if multiple errors occur, only the first will have a non-null background to restore.
|
|
2057
|
-
synchronized (this) {
|
|
2058
|
-
final Drawable backgroundToRestore = originalWindowBackground;
|
|
2059
|
-
if (backgroundToRestore != null) {
|
|
2060
|
-
originalWindowBackground = null; // Clear immediately so other threads won't restore
|
|
2061
|
-
getBridge()
|
|
2062
|
-
.getActivity()
|
|
2063
|
-
.runOnUiThread(() -> {
|
|
2064
|
-
try {
|
|
2065
|
-
getBridge().getActivity().getWindow().setBackgroundDrawable(backgroundToRestore);
|
|
2066
|
-
} catch (Exception e) {
|
|
2067
|
-
Log.w(TAG, "Failed to restore window background on error", e);
|
|
2068
|
-
}
|
|
2069
|
-
});
|
|
2070
|
-
}
|
|
2071
|
-
}
|
|
2183
|
+
restoreOriginalWindowBackground(getBridge().getActivity());
|
|
2072
2184
|
restoreWebViewVisualState();
|
|
2073
2185
|
restoreSystemUiForToBackMode(getBridge().getActivity());
|
|
2074
2186
|
}
|
|
@@ -129,7 +129,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
129
129
|
void onBarcodesScanned(JSONArray barcodes);
|
|
130
130
|
void onBarcodeScanError(String message);
|
|
131
131
|
void onCameraStarted(int width, int height, int x, int y);
|
|
132
|
-
void onCameraStartError(String message);
|
|
132
|
+
void onCameraStartError(CameraXView source, String message);
|
|
133
133
|
void onCameraStopped(CameraXView source);
|
|
134
134
|
}
|
|
135
135
|
|
|
@@ -191,6 +191,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
191
191
|
private volatile boolean isBarcodeFrameProcessing = false;
|
|
192
192
|
private volatile long lastBarcodeFrameAtMs = 0L;
|
|
193
193
|
private volatile long barcodeDetectionIntervalMs = 500L;
|
|
194
|
+
private boolean cameraStartedCallbackSent = false;
|
|
194
195
|
|
|
195
196
|
// Operation coordination (acts like a semaphore to prevent stop during active ops)
|
|
196
197
|
private final Object operationLock = new Object();
|
|
@@ -481,13 +482,13 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
481
482
|
// runnable is still queued — never transition backward from DESTROYED.
|
|
482
483
|
if (lifecycleRegistry.getCurrentState() == Lifecycle.State.DESTROYED) {
|
|
483
484
|
if (listener != null) {
|
|
484
|
-
listener.onCameraStartError("Camera start aborted: lifecycle destroyed");
|
|
485
|
+
listener.onCameraStartError(this, "Camera start aborted: lifecycle destroyed");
|
|
485
486
|
}
|
|
486
487
|
return;
|
|
487
488
|
}
|
|
488
489
|
if (stopRequested) {
|
|
489
490
|
if (listener != null) {
|
|
490
|
-
listener.onCameraStartError("Camera start aborted: stop requested");
|
|
491
|
+
listener.onCameraStartError(this, "Camera start aborted: stop requested");
|
|
491
492
|
}
|
|
492
493
|
return;
|
|
493
494
|
}
|
|
@@ -496,44 +497,45 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
496
497
|
lifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
|
|
497
498
|
if (lifecycleRegistry.getCurrentState() == Lifecycle.State.DESTROYED) {
|
|
498
499
|
if (listener != null) {
|
|
499
|
-
listener.onCameraStartError("Camera start aborted: lifecycle destroyed");
|
|
500
|
+
listener.onCameraStartError(this, "Camera start aborted: lifecycle destroyed");
|
|
500
501
|
}
|
|
501
502
|
return;
|
|
502
503
|
}
|
|
503
504
|
if (stopRequested) {
|
|
504
505
|
if (listener != null) {
|
|
505
|
-
listener.onCameraStartError("Camera start aborted: stop requested");
|
|
506
|
+
listener.onCameraStartError(this, "Camera start aborted: stop requested");
|
|
506
507
|
}
|
|
507
508
|
return;
|
|
508
509
|
}
|
|
509
510
|
}
|
|
510
511
|
if (lifecycleRegistry.getCurrentState() == Lifecycle.State.DESTROYED) {
|
|
511
512
|
if (listener != null) {
|
|
512
|
-
listener.onCameraStartError("Camera start aborted: lifecycle destroyed");
|
|
513
|
+
listener.onCameraStartError(this, "Camera start aborted: lifecycle destroyed");
|
|
513
514
|
}
|
|
514
515
|
return;
|
|
515
516
|
}
|
|
516
517
|
if (stopRequested) {
|
|
517
518
|
if (listener != null) {
|
|
518
|
-
listener.onCameraStartError("Camera start aborted: stop requested");
|
|
519
|
+
listener.onCameraStartError(this, "Camera start aborted: stop requested");
|
|
519
520
|
}
|
|
520
521
|
return;
|
|
521
522
|
}
|
|
522
523
|
lifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
|
|
523
524
|
if (lifecycleRegistry.getCurrentState() == Lifecycle.State.DESTROYED) {
|
|
524
525
|
if (listener != null) {
|
|
525
|
-
listener.onCameraStartError("Camera start aborted: lifecycle destroyed");
|
|
526
|
+
listener.onCameraStartError(this, "Camera start aborted: lifecycle destroyed");
|
|
526
527
|
}
|
|
527
528
|
return;
|
|
528
529
|
}
|
|
529
530
|
if (stopRequested) {
|
|
530
531
|
if (listener != null) {
|
|
531
|
-
listener.onCameraStartError("Camera start aborted: stop requested");
|
|
532
|
+
listener.onCameraStartError(this, "Camera start aborted: stop requested");
|
|
532
533
|
}
|
|
533
534
|
return;
|
|
534
535
|
}
|
|
535
536
|
|
|
536
537
|
this.sessionConfig = config;
|
|
538
|
+
cameraStartedCallbackSent = false;
|
|
537
539
|
cameraExecutor = Executors.newSingleThreadExecutor();
|
|
538
540
|
requestEnumeratedDeviceCacheRefresh();
|
|
539
541
|
|
|
@@ -629,6 +631,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
629
631
|
if (cameraProvider != null) {
|
|
630
632
|
cameraProvider.unbindAll();
|
|
631
633
|
}
|
|
634
|
+
barcodeAnalysis = null;
|
|
632
635
|
if (cameraExecutor != null) {
|
|
633
636
|
cameraExecutor.shutdown();
|
|
634
637
|
}
|
|
@@ -654,7 +657,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
654
657
|
private void restoreWebViewBackground() {
|
|
655
658
|
// Capture sessionConfig reference once to avoid race conditions
|
|
656
659
|
CameraSessionConfiguration config = sessionConfig;
|
|
657
|
-
boolean shouldRestore = config
|
|
660
|
+
boolean shouldRestore = config == null || !config.isToBack();
|
|
658
661
|
if (shouldRestore) {
|
|
659
662
|
// Capture background color before posting to UI thread
|
|
660
663
|
final int backgroundColorToRestore = originalWebViewBackground;
|
|
@@ -674,26 +677,26 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
674
677
|
try {
|
|
675
678
|
if (lifecycleRegistry.getCurrentState() == Lifecycle.State.DESTROYED) {
|
|
676
679
|
if (listener != null) {
|
|
677
|
-
listener.onCameraStartError("Camera binding cancelled: lifecycle destroyed (before provider)");
|
|
680
|
+
listener.onCameraStartError(this, "Camera binding cancelled: lifecycle destroyed (before provider)");
|
|
678
681
|
}
|
|
679
682
|
return;
|
|
680
683
|
}
|
|
681
684
|
if (stopRequested) {
|
|
682
685
|
if (listener != null) {
|
|
683
|
-
listener.onCameraStartError("Camera binding cancelled: stop requested (before provider)");
|
|
686
|
+
listener.onCameraStartError(this, "Camera binding cancelled: stop requested (before provider)");
|
|
684
687
|
}
|
|
685
688
|
return;
|
|
686
689
|
}
|
|
687
690
|
cameraProvider = cameraProviderFuture.get();
|
|
688
691
|
if (lifecycleRegistry.getCurrentState() == Lifecycle.State.DESTROYED) {
|
|
689
692
|
if (listener != null) {
|
|
690
|
-
listener.onCameraStartError("Camera binding cancelled: lifecycle destroyed (after provider)");
|
|
693
|
+
listener.onCameraStartError(this, "Camera binding cancelled: lifecycle destroyed (after provider)");
|
|
691
694
|
}
|
|
692
695
|
return;
|
|
693
696
|
}
|
|
694
697
|
if (stopRequested) {
|
|
695
698
|
if (listener != null) {
|
|
696
|
-
listener.onCameraStartError("Camera binding cancelled: stop requested (after provider)");
|
|
699
|
+
listener.onCameraStartError(this, "Camera binding cancelled: stop requested (after provider)");
|
|
697
700
|
}
|
|
698
701
|
return;
|
|
699
702
|
}
|
|
@@ -703,7 +706,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
703
706
|
// Restore webView background on error
|
|
704
707
|
restoreWebViewBackground();
|
|
705
708
|
if (listener != null) {
|
|
706
|
-
listener.onCameraStartError("Error initializing camera: " + e.getMessage());
|
|
709
|
+
listener.onCameraStartError(this, "Error initializing camera: " + e.getMessage());
|
|
707
710
|
}
|
|
708
711
|
}
|
|
709
712
|
},
|
|
@@ -715,13 +718,11 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
715
718
|
if (previewView != null) {
|
|
716
719
|
removePreviewView();
|
|
717
720
|
}
|
|
718
|
-
if (sessionConfig.isToBack()) {
|
|
719
|
-
// Set to black initially to prevent flickering, will be transparent after camera starts
|
|
720
|
-
webView.setBackgroundColor(android.graphics.Color.BLACK);
|
|
721
|
-
}
|
|
722
|
-
|
|
723
721
|
// Create a container to hold both the preview and grid overlay
|
|
724
722
|
previewContainer = new FrameLayout(context);
|
|
723
|
+
if (sessionConfig != null && sessionConfig.isToBack()) {
|
|
724
|
+
previewContainer.setBackgroundColor(Color.TRANSPARENT);
|
|
725
|
+
}
|
|
725
726
|
// Ensure container can receive touch events
|
|
726
727
|
previewContainer.setClickable(true);
|
|
727
728
|
previewContainer.setFocusable(true);
|
|
@@ -735,9 +736,11 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
735
736
|
|
|
736
737
|
// Create and setup the preview view
|
|
737
738
|
previewView = new PreviewView(context);
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
739
|
+
if (sessionConfig != null && sessionConfig.isToBack()) {
|
|
740
|
+
previewView.setBackgroundColor(Color.TRANSPARENT);
|
|
741
|
+
}
|
|
742
|
+
PreviewView.ImplementationMode implementationMode = choosePreviewImplementationMode();
|
|
743
|
+
previewView.setImplementationMode(implementationMode);
|
|
741
744
|
// Set scale type based on aspectMode: 'contain' uses FIT, 'cover' uses FILL
|
|
742
745
|
String aspectMode = sessionConfig != null ? sessionConfig.getAspectMode() : "contain";
|
|
743
746
|
previewView.setScaleType("cover".equals(aspectMode) ? PreviewView.ScaleType.FILL_CENTER : PreviewView.ScaleType.FIT_CENTER);
|
|
@@ -753,6 +756,14 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
753
756
|
new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
|
|
754
757
|
);
|
|
755
758
|
|
|
759
|
+
previewView
|
|
760
|
+
.getPreviewStreamState()
|
|
761
|
+
.observe(this, (streamState) -> {
|
|
762
|
+
if (sessionConfig != null && sessionConfig.isToBack() && streamState == PreviewView.StreamState.STREAMING) {
|
|
763
|
+
notifyCameraStartedIfNeeded("streaming");
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
|
|
756
767
|
// Create and setup the grid overlay
|
|
757
768
|
gridOverlayView = new GridOverlayView(context);
|
|
758
769
|
// Make grid overlay not intercept touch events
|
|
@@ -781,7 +792,17 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
781
792
|
if (parent != null) {
|
|
782
793
|
FrameLayout.LayoutParams layoutParams = calculatePreviewLayoutParams();
|
|
783
794
|
parent.addView(previewContainer, layoutParams);
|
|
784
|
-
if (sessionConfig.isToBack())
|
|
795
|
+
if (sessionConfig.isToBack()) {
|
|
796
|
+
webView.bringToFront();
|
|
797
|
+
parent.requestTransparentRegion(webView);
|
|
798
|
+
webView.post(() -> {
|
|
799
|
+
webView.bringToFront();
|
|
800
|
+
ViewGroup currentParent = (ViewGroup) webView.getParent();
|
|
801
|
+
if (currentParent != null) {
|
|
802
|
+
currentParent.requestTransparentRegion(webView);
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
}
|
|
785
806
|
|
|
786
807
|
// Log the actual position after layout
|
|
787
808
|
previewContainer.post(() -> {
|
|
@@ -811,6 +832,10 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
811
832
|
}
|
|
812
833
|
}
|
|
813
834
|
|
|
835
|
+
private PreviewView.ImplementationMode choosePreviewImplementationMode() {
|
|
836
|
+
return PreviewView.ImplementationMode.COMPATIBLE;
|
|
837
|
+
}
|
|
838
|
+
|
|
814
839
|
/**
|
|
815
840
|
* Compute layout parameters for the camera preview container based on the current session configuration,
|
|
816
841
|
* device screen size, WebView/parent geometry, and optional aspect-ratio centering.
|
|
@@ -1000,7 +1025,9 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1000
1025
|
if (focusIndicatorView != null) {
|
|
1001
1026
|
focusIndicatorView = null;
|
|
1002
1027
|
}
|
|
1003
|
-
|
|
1028
|
+
if (sessionConfig == null || !sessionConfig.isToBack()) {
|
|
1029
|
+
webView.setBackgroundColor(originalWebViewBackground);
|
|
1030
|
+
}
|
|
1004
1031
|
}
|
|
1005
1032
|
|
|
1006
1033
|
@OptIn(markerClass = ExperimentalCamera2Interop.class)
|
|
@@ -1050,6 +1077,14 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1050
1077
|
.setTargetRotation(rotation)
|
|
1051
1078
|
.build();
|
|
1052
1079
|
sampleImageCapture = imageCapture;
|
|
1080
|
+
barcodeAnalysis = null;
|
|
1081
|
+
|
|
1082
|
+
if (!sessionConfig.isVideoModeEnabled() && sessionConfig.isBarcodeScannerEnabled()) {
|
|
1083
|
+
barcodeAnalysis = createBarcodeAnalysisUseCase();
|
|
1084
|
+
if (isBarcodeScannerActive && barcodeScanner != null && cameraExecutor != null) {
|
|
1085
|
+
barcodeAnalysis.setAnalyzer(cameraExecutor, this::analyzeBarcodeImage);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1053
1088
|
|
|
1054
1089
|
// Only setup VideoCapture if enableVideoMode is true
|
|
1055
1090
|
if (sessionConfig.isVideoModeEnabled()) {
|
|
@@ -1214,6 +1249,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1214
1249
|
if (initialZoom < minZoom || initialZoom > maxZoom) {
|
|
1215
1250
|
if (listener != null) {
|
|
1216
1251
|
listener.onCameraStartError(
|
|
1252
|
+
this,
|
|
1217
1253
|
"Initial zoom level " +
|
|
1218
1254
|
initialZoom +
|
|
1219
1255
|
" is not available. " +
|
|
@@ -1233,43 +1269,78 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1233
1269
|
isRunning = true;
|
|
1234
1270
|
Log.d(TAG, "bindCameraUseCases: Camera bound successfully");
|
|
1235
1271
|
if (listener != null) {
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1272
|
+
if (sessionConfig != null && sessionConfig.isToBack()) {
|
|
1273
|
+
PreviewView.StreamState streamState = previewView != null ? previewView.getPreviewStreamState().getValue() : null;
|
|
1274
|
+
if (streamState == PreviewView.StreamState.STREAMING) {
|
|
1275
|
+
notifyCameraStartedIfNeeded("already-streaming");
|
|
1276
|
+
} else if (previewContainer != null) {
|
|
1277
|
+
previewContainer.postDelayed(
|
|
1278
|
+
() -> {
|
|
1279
|
+
PreviewView.StreamState latestState = previewView != null
|
|
1280
|
+
? previewView.getPreviewStreamState().getValue()
|
|
1281
|
+
: null;
|
|
1282
|
+
if (latestState == PreviewView.StreamState.STREAMING) {
|
|
1283
|
+
notifyCameraStartedIfNeeded("watchdog-streaming");
|
|
1284
|
+
}
|
|
1285
|
+
},
|
|
1286
|
+
300
|
|
1287
|
+
);
|
|
1288
|
+
previewContainer.postDelayed(
|
|
1289
|
+
() -> {
|
|
1290
|
+
PreviewView.StreamState latestState = previewView != null
|
|
1291
|
+
? previewView.getPreviewStreamState().getValue()
|
|
1292
|
+
: null;
|
|
1293
|
+
if (!cameraStartedCallbackSent && latestState == PreviewView.StreamState.STREAMING) {
|
|
1294
|
+
notifyCameraStartedIfNeeded("fallback-streaming");
|
|
1295
|
+
}
|
|
1296
|
+
},
|
|
1297
|
+
1500
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
} else {
|
|
1301
|
+
notifyCameraStartedIfNeeded("bound");
|
|
1302
|
+
}
|
|
1264
1303
|
}
|
|
1265
1304
|
} catch (Exception e) {
|
|
1266
1305
|
// Restore webView background on error
|
|
1267
1306
|
restoreWebViewBackground();
|
|
1268
|
-
if (listener != null) listener.onCameraStartError("Error binding camera: " + e.getMessage());
|
|
1307
|
+
if (listener != null) listener.onCameraStartError(this, "Error binding camera: " + e.getMessage());
|
|
1269
1308
|
}
|
|
1270
1309
|
});
|
|
1271
1310
|
}
|
|
1272
1311
|
|
|
1312
|
+
private void notifyCameraStartedIfNeeded(String reason) {
|
|
1313
|
+
if (cameraStartedCallbackSent || listener == null || previewContainer == null) {
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
cameraStartedCallbackSent = true;
|
|
1317
|
+
previewContainer.post(() -> {
|
|
1318
|
+
if (listener == null || previewContainer == null) {
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
int actualWidth = getPreviewWidth();
|
|
1323
|
+
int actualHeight = getPreviewHeight();
|
|
1324
|
+
int actualX = getPreviewX();
|
|
1325
|
+
int actualY = getPreviewY();
|
|
1326
|
+
|
|
1327
|
+
Log.d(
|
|
1328
|
+
TAG,
|
|
1329
|
+
"onCameraStarted callback - actualX=" +
|
|
1330
|
+
actualX +
|
|
1331
|
+
", actualY=" +
|
|
1332
|
+
actualY +
|
|
1333
|
+
", actualWidth=" +
|
|
1334
|
+
actualWidth +
|
|
1335
|
+
", actualHeight=" +
|
|
1336
|
+
actualHeight
|
|
1337
|
+
);
|
|
1338
|
+
|
|
1339
|
+
updateGridOverlayBounds();
|
|
1340
|
+
listener.onCameraStarted(actualWidth, actualHeight, actualX, actualY);
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1273
1344
|
@OptIn(markerClass = ExperimentalCamera2Interop.class)
|
|
1274
1345
|
private CameraSelector buildCameraSelector() {
|
|
1275
1346
|
return buildCameraBindingPlan(sessionConfig).selector;
|
|
@@ -1523,6 +1594,8 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1523
1594
|
private void bindConfiguredUseCases(CameraBindingPlan bindingPlan, Preview preview) {
|
|
1524
1595
|
if (sessionConfig.isVideoModeEnabled() && videoCapture != null) {
|
|
1525
1596
|
camera = cameraProvider.bindToLifecycle(this, bindingPlan.selector, preview, imageCapture, videoCapture);
|
|
1597
|
+
} else if (barcodeAnalysis != null) {
|
|
1598
|
+
camera = cameraProvider.bindToLifecycle(this, bindingPlan.selector, preview, imageCapture, barcodeAnalysis);
|
|
1526
1599
|
} else {
|
|
1527
1600
|
camera = cameraProvider.bindToLifecycle(this, bindingPlan.selector, preview, imageCapture);
|
|
1528
1601
|
}
|
|
@@ -1551,26 +1624,20 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1551
1624
|
|
|
1552
1625
|
mainExecutor.execute(() -> {
|
|
1553
1626
|
try {
|
|
1554
|
-
stopBarcodeScannerInternal(
|
|
1627
|
+
stopBarcodeScannerInternal(false);
|
|
1555
1628
|
barcodeScanner = createBarcodeScanner(formats);
|
|
1556
1629
|
barcodeDetectionIntervalMs = Math.max(100L, detectionIntervalMs);
|
|
1557
1630
|
lastBarcodeFrameAtMs = 0L;
|
|
1558
1631
|
isBarcodeFrameProcessing = false;
|
|
1559
1632
|
isBarcodeScannerActive = true;
|
|
1560
1633
|
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
)
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
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);
|
|
1634
|
+
if (barcodeAnalysis == null) {
|
|
1635
|
+
barcodeAnalysis = createBarcodeAnalysisUseCase();
|
|
1636
|
+
barcodeAnalysis.setAnalyzer(cameraExecutor, this::analyzeBarcodeImage);
|
|
1637
|
+
cameraProvider.bindToLifecycle(this, currentCameraSelector, barcodeAnalysis);
|
|
1638
|
+
} else {
|
|
1639
|
+
barcodeAnalysis.setAnalyzer(cameraExecutor, this::analyzeBarcodeImage);
|
|
1640
|
+
}
|
|
1574
1641
|
callback.onStarted();
|
|
1575
1642
|
} catch (Exception e) {
|
|
1576
1643
|
stopBarcodeScannerInternal(true);
|
|
@@ -1583,6 +1650,17 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1583
1650
|
mainExecutor.execute(() -> stopBarcodeScannerInternal(true));
|
|
1584
1651
|
}
|
|
1585
1652
|
|
|
1653
|
+
private ImageAnalysis createBarcodeAnalysisUseCase() {
|
|
1654
|
+
ResolutionSelector barcodeResolutionSelector = new ResolutionSelector.Builder()
|
|
1655
|
+
.setResolutionStrategy(new ResolutionStrategy(new Size(1280, 720), ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER))
|
|
1656
|
+
.build();
|
|
1657
|
+
|
|
1658
|
+
return new ImageAnalysis.Builder()
|
|
1659
|
+
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
|
1660
|
+
.setResolutionSelector(barcodeResolutionSelector)
|
|
1661
|
+
.build();
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1586
1664
|
private void stopBarcodeScannerInternal(boolean unbindAnalysis) {
|
|
1587
1665
|
isBarcodeScannerActive = false;
|
|
1588
1666
|
isBarcodeFrameProcessing = false;
|
|
@@ -1595,9 +1673,10 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1595
1673
|
cameraProvider.unbind(barcodeAnalysis);
|
|
1596
1674
|
} catch (Exception e) {
|
|
1597
1675
|
Log.w(TAG, "stopBarcodeScannerInternal: failed to unbind barcode analysis", e);
|
|
1676
|
+
} finally {
|
|
1677
|
+
barcodeAnalysis = null;
|
|
1598
1678
|
}
|
|
1599
1679
|
}
|
|
1600
|
-
barcodeAnalysis = null;
|
|
1601
1680
|
}
|
|
1602
1681
|
|
|
1603
1682
|
if (barcodeScanner != null) {
|
|
@@ -1803,6 +1882,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1803
1882
|
target.setCentered(source.isCentered());
|
|
1804
1883
|
target.setTargetZoom(source.getTargetZoom());
|
|
1805
1884
|
target.setEnablePhysicalDeviceSelection(source.isPhysicalDeviceSelectionEnabled());
|
|
1885
|
+
target.setBarcodeScannerEnabled(source.isBarcodeScannerEnabled());
|
|
1806
1886
|
}
|
|
1807
1887
|
|
|
1808
1888
|
private void requestEnumeratedDeviceCacheRefresh() {
|
|
@@ -27,6 +27,7 @@ public class CameraSessionConfiguration {
|
|
|
27
27
|
private boolean isCentered = false;
|
|
28
28
|
private final String videoQuality;
|
|
29
29
|
private boolean enablePhysicalDeviceSelection = false;
|
|
30
|
+
private boolean barcodeScannerEnabled = false;
|
|
30
31
|
|
|
31
32
|
public CameraSessionConfiguration(
|
|
32
33
|
String deviceId,
|
|
@@ -158,6 +159,14 @@ public class CameraSessionConfiguration {
|
|
|
158
159
|
this.enablePhysicalDeviceSelection = enablePhysicalDeviceSelection;
|
|
159
160
|
}
|
|
160
161
|
|
|
162
|
+
public boolean isBarcodeScannerEnabled() {
|
|
163
|
+
return barcodeScannerEnabled;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
public void setBarcodeScannerEnabled(boolean barcodeScannerEnabled) {
|
|
167
|
+
this.barcodeScannerEnabled = barcodeScannerEnabled;
|
|
168
|
+
}
|
|
169
|
+
|
|
161
170
|
// Additional getters with "get" prefix for compatibility
|
|
162
171
|
public boolean getToBack() {
|
|
163
172
|
return toBack;
|
|
@@ -35,7 +35,7 @@ extension UIWindow {
|
|
|
35
35
|
*/
|
|
36
36
|
@objc(CameraPreview)
|
|
37
37
|
public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelegate {
|
|
38
|
-
private let pluginVersion: String = "8.4.
|
|
38
|
+
private let pluginVersion: String = "8.4.3"
|
|
39
39
|
public let identifier = "CameraPreviewPlugin"
|
|
40
40
|
public let jsName = "CameraPreview"
|
|
41
41
|
public let pluginMethods: [CAPPluginMethod] = [
|