@capgo/camera-preview 8.11.1 → 8.11.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/gradle/wrapper/gradle-wrapper.properties +1 -1
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraPreview.java +64 -213
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java +6 -1
- package/android/src/main/java/app/capgo/capacitor/camera/preview/ToBackCompositorHelper.java +36 -0
- package/android/src/test/java/app/capgo/capacitor/camera/preview/ToBackCompositorHelperTest.java +39 -0
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +70 -38
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +5 -5
- package/package.json +2 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
distributionBase=GRADLE_USER_HOME
|
|
2
2
|
distributionPath=wrapper/dists
|
|
3
|
-
distributionUrl=https\://services.gradle.org/distributions/gradle-
|
|
3
|
+
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-all.zip
|
|
4
4
|
networkTimeout=10000
|
|
5
5
|
validateDistributionUrl=true
|
|
6
6
|
zipStoreBase=GRADLE_USER_HOME
|
|
@@ -15,7 +15,6 @@ import android.content.pm.ActivityInfo;
|
|
|
15
15
|
import android.content.pm.PackageManager;
|
|
16
16
|
import android.content.res.Configuration;
|
|
17
17
|
import android.graphics.Color;
|
|
18
|
-
import android.graphics.drawable.ColorDrawable;
|
|
19
18
|
import android.graphics.drawable.Drawable;
|
|
20
19
|
import android.location.Location;
|
|
21
20
|
import android.net.Uri;
|
|
@@ -54,7 +53,6 @@ import java.util.ArrayList;
|
|
|
54
53
|
import java.util.Arrays;
|
|
55
54
|
import java.util.Collections;
|
|
56
55
|
import java.util.List;
|
|
57
|
-
import java.util.Locale;
|
|
58
56
|
import java.util.Objects;
|
|
59
57
|
import org.json.JSONArray;
|
|
60
58
|
import org.json.JSONObject;
|
|
@@ -104,11 +102,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
104
102
|
cameraXView.setListener(this);
|
|
105
103
|
}
|
|
106
104
|
if (lastSessionConfig.isToBack()) {
|
|
107
|
-
|
|
108
|
-
activateTransparentBackgroundsForToBack(cameraXView);
|
|
109
|
-
} else {
|
|
110
|
-
prepareTransparentBackgroundsForToBack(cameraXView);
|
|
111
|
-
}
|
|
105
|
+
applyToBackVisualState(cameraXView);
|
|
112
106
|
} else {
|
|
113
107
|
toBackVisualStateActive = false;
|
|
114
108
|
}
|
|
@@ -128,9 +122,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
128
122
|
lastSessionConfig = null;
|
|
129
123
|
clearActiveBarcodeScanner();
|
|
130
124
|
toBackVisualStateActive = false;
|
|
131
|
-
restoreOriginalWindowBackground(getBridge().getActivity());
|
|
132
125
|
restoreWebViewVisualState();
|
|
133
|
-
restoreSystemUiForToBackMode(getBridge().getActivity());
|
|
134
126
|
unregisterScreenLockReceiver();
|
|
135
127
|
}
|
|
136
128
|
|
|
@@ -144,9 +136,10 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
144
136
|
static final String CAMERA_WITH_LOCATION_PERMISSION_ALIAS = "cameraWithLocation";
|
|
145
137
|
static final String MICROPHONE_ONLY_PERMISSION_ALIAS = "microphoneOnly";
|
|
146
138
|
|
|
147
|
-
private
|
|
148
|
-
private
|
|
149
|
-
private
|
|
139
|
+
private PluginCall pendingCaptureCall;
|
|
140
|
+
private PluginCall pendingSampleCall;
|
|
141
|
+
private PluginCall pendingCameraStartedCall;
|
|
142
|
+
private PluginCall pendingStopRecordCall;
|
|
150
143
|
private final Object pendingStartLock = new Object();
|
|
151
144
|
private PluginCall pendingStartCall;
|
|
152
145
|
private int previousOrientationRequest = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
|
|
@@ -159,16 +152,12 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
159
152
|
private String lastOrientationStr = "unknown";
|
|
160
153
|
private boolean lastDisableAudio = true;
|
|
161
154
|
private boolean lastIncludeSafeAreaInsets = false;
|
|
162
|
-
private Drawable originalWindowBackground;
|
|
163
|
-
private boolean originalWindowBackgroundCaptured = false;
|
|
164
155
|
private Drawable originalWebViewBackground;
|
|
165
156
|
private boolean originalWebViewBackgroundCaptured = false;
|
|
166
157
|
private Float originalWebViewAlpha;
|
|
158
|
+
private Integer originalWebViewLayerType;
|
|
167
159
|
private Drawable originalWebViewParentBackground;
|
|
168
160
|
private boolean originalWebViewParentBackgroundCaptured = false;
|
|
169
|
-
private Integer originalStatusBarColor;
|
|
170
|
-
private Integer originalNavigationBarColor;
|
|
171
|
-
private Boolean originalNavigationBarContrastEnforced;
|
|
172
161
|
private volatile boolean toBackVisualStateActive = false;
|
|
173
162
|
private boolean isCameraPermissionDialogShowing = false;
|
|
174
163
|
private boolean pendingStartBarcodeScanner = false;
|
|
@@ -518,8 +507,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
518
507
|
}
|
|
519
508
|
|
|
520
509
|
private void proceedWithCapture(PluginCall call, Location location) {
|
|
521
|
-
|
|
522
|
-
|
|
510
|
+
call.setKeepAlive(true);
|
|
511
|
+
pendingCaptureCall = call;
|
|
523
512
|
|
|
524
513
|
Integer quality = Objects.requireNonNull(call.getInt("quality", 85));
|
|
525
514
|
final boolean saveToGallery = Boolean.TRUE.equals(call.getBoolean("saveToGallery"));
|
|
@@ -538,8 +527,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
538
527
|
call.reject("Camera is not running");
|
|
539
528
|
return;
|
|
540
529
|
}
|
|
541
|
-
|
|
542
|
-
|
|
530
|
+
call.setKeepAlive(true);
|
|
531
|
+
pendingSampleCall = call;
|
|
543
532
|
Integer quality = Objects.requireNonNull(call.getInt("quality", 85));
|
|
544
533
|
final boolean mirrorFrontCamera = Boolean.TRUE.equals(call.getBoolean("mirrorFrontCamera"));
|
|
545
534
|
cameraXView.captureSample(quality, mirrorFrontCamera);
|
|
@@ -798,6 +787,9 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
798
787
|
) {
|
|
799
788
|
cameraXView = new CameraXView(getContext(), getBridge().getWebView());
|
|
800
789
|
cameraXView.setListener(this);
|
|
790
|
+
if (lastSessionConfig.isToBack()) {
|
|
791
|
+
applyToBackVisualState(cameraXView);
|
|
792
|
+
}
|
|
801
793
|
requestBarcodeScannerRestartAfterCameraResume();
|
|
802
794
|
cameraRestartAfterResumeInProgress = true;
|
|
803
795
|
cameraXView.startSession(lastSessionConfig);
|
|
@@ -840,9 +832,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
840
832
|
lastSessionConfig = null;
|
|
841
833
|
clearActiveBarcodeScanner();
|
|
842
834
|
toBackVisualStateActive = false;
|
|
843
|
-
restoreOriginalWindowBackground(getBridge().getActivity());
|
|
844
835
|
restoreWebViewVisualState();
|
|
845
|
-
restoreSystemUiForToBackMode(getBridge().getActivity());
|
|
846
836
|
call.resolve();
|
|
847
837
|
});
|
|
848
838
|
}
|
|
@@ -1360,13 +1350,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
1360
1350
|
getBridge()
|
|
1361
1351
|
.getActivity()
|
|
1362
1352
|
.runOnUiThread(() -> {
|
|
1363
|
-
lockSystemUiForToBackMode(getBridge().getActivity(), toBack);
|
|
1364
1353
|
if (toBack) {
|
|
1365
|
-
|
|
1366
|
-
activateTransparentBackgroundsForToBack(cameraXView);
|
|
1367
|
-
} else {
|
|
1368
|
-
prepareTransparentBackgroundsForToBack(cameraXView);
|
|
1369
|
-
}
|
|
1354
|
+
applyToBackVisualState(cameraXView);
|
|
1370
1355
|
} else {
|
|
1371
1356
|
toBackVisualStateActive = false;
|
|
1372
1357
|
}
|
|
@@ -1651,8 +1636,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
1651
1636
|
clearActiveBarcodeScanner();
|
|
1652
1637
|
}
|
|
1653
1638
|
|
|
1654
|
-
|
|
1655
|
-
|
|
1639
|
+
call.setKeepAlive(true);
|
|
1640
|
+
pendingCameraStartedCall = call;
|
|
1656
1641
|
cameraXView.startSession(config);
|
|
1657
1642
|
|
|
1658
1643
|
// Setup orientation listener to mirror iOS screenResize emission
|
|
@@ -1908,7 +1893,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
1908
1893
|
|
|
1909
1894
|
@Override
|
|
1910
1895
|
public void onPictureTaken(String base64, JSONObject exif) {
|
|
1911
|
-
PluginCall pluginCall =
|
|
1896
|
+
PluginCall pluginCall = pendingCaptureCall;
|
|
1912
1897
|
if (pluginCall == null) {
|
|
1913
1898
|
Log.e("CameraPreview", "onPictureTaken: captureCallbackId is null");
|
|
1914
1899
|
return;
|
|
@@ -1917,18 +1902,20 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
1917
1902
|
result.put("value", base64);
|
|
1918
1903
|
result.put("exif", exif);
|
|
1919
1904
|
pluginCall.resolve(result);
|
|
1920
|
-
|
|
1905
|
+
pluginCall.setKeepAlive(false);
|
|
1906
|
+
pendingCaptureCall = null;
|
|
1921
1907
|
}
|
|
1922
1908
|
|
|
1923
1909
|
@Override
|
|
1924
1910
|
public void onPictureTakenError(String message) {
|
|
1925
|
-
PluginCall pluginCall =
|
|
1911
|
+
PluginCall pluginCall = pendingCaptureCall;
|
|
1926
1912
|
if (pluginCall == null) {
|
|
1927
1913
|
Log.e("CameraPreview", "onPictureTakenError: captureCallbackId is null");
|
|
1928
1914
|
return;
|
|
1929
1915
|
}
|
|
1930
1916
|
pluginCall.reject(message);
|
|
1931
|
-
|
|
1917
|
+
pluginCall.setKeepAlive(false);
|
|
1918
|
+
pendingCaptureCall = null;
|
|
1932
1919
|
}
|
|
1933
1920
|
|
|
1934
1921
|
@Override
|
|
@@ -1985,36 +1972,6 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
1985
1972
|
return false;
|
|
1986
1973
|
}
|
|
1987
1974
|
|
|
1988
|
-
private boolean isMiuiDevice() {
|
|
1989
|
-
String manufacturer = Build.MANUFACTURER != null ? Build.MANUFACTURER.toLowerCase(Locale.US) : "";
|
|
1990
|
-
String brand = Build.BRAND != null ? Build.BRAND.toLowerCase(Locale.US) : "";
|
|
1991
|
-
return manufacturer.contains("xiaomi") || brand.contains("xiaomi") || brand.contains("redmi") || brand.contains("poco");
|
|
1992
|
-
}
|
|
1993
|
-
|
|
1994
|
-
private boolean usesFullStackTransparentBackgroundWorkaround() {
|
|
1995
|
-
String manufacturer = Build.MANUFACTURER != null ? Build.MANUFACTURER.toLowerCase(Locale.US) : "";
|
|
1996
|
-
String brand = Build.BRAND != null ? Build.BRAND.toLowerCase(Locale.US) : "";
|
|
1997
|
-
return (
|
|
1998
|
-
isMiuiDevice() ||
|
|
1999
|
-
manufacturer.contains("huawei") ||
|
|
2000
|
-
manufacturer.contains("honor") ||
|
|
2001
|
-
brand.contains("huawei") ||
|
|
2002
|
-
brand.contains("honor")
|
|
2003
|
-
);
|
|
2004
|
-
}
|
|
2005
|
-
|
|
2006
|
-
private void captureOriginalWindowBackground(Activity activity) {
|
|
2007
|
-
if (activity == null) {
|
|
2008
|
-
return;
|
|
2009
|
-
}
|
|
2010
|
-
synchronized (this) {
|
|
2011
|
-
if (!originalWindowBackgroundCaptured) {
|
|
2012
|
-
originalWindowBackground = activity.getWindow().getDecorView().getBackground();
|
|
2013
|
-
originalWindowBackgroundCaptured = true;
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
|
|
2018
1975
|
private void captureOriginalWebViewVisualState(WebView webView, ViewGroup webViewParent) {
|
|
2019
1976
|
if (webView == null) {
|
|
2020
1977
|
return;
|
|
@@ -2027,6 +1984,9 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2027
1984
|
if (originalWebViewAlpha == null) {
|
|
2028
1985
|
originalWebViewAlpha = webView.getAlpha();
|
|
2029
1986
|
}
|
|
1987
|
+
if (originalWebViewLayerType == null) {
|
|
1988
|
+
originalWebViewLayerType = webView.getLayerType();
|
|
1989
|
+
}
|
|
2030
1990
|
if (webViewParent != null && !originalWebViewParentBackgroundCaptured) {
|
|
2031
1991
|
originalWebViewParentBackground = webViewParent.getBackground();
|
|
2032
1992
|
originalWebViewParentBackgroundCaptured = true;
|
|
@@ -2034,95 +1994,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2034
1994
|
}
|
|
2035
1995
|
}
|
|
2036
1996
|
|
|
2037
|
-
private void
|
|
2038
|
-
final Drawable backgroundToRestore;
|
|
2039
|
-
final boolean captured;
|
|
2040
|
-
synchronized (this) {
|
|
2041
|
-
backgroundToRestore = originalWindowBackground;
|
|
2042
|
-
captured = originalWindowBackgroundCaptured;
|
|
2043
|
-
originalWindowBackground = null;
|
|
2044
|
-
originalWindowBackgroundCaptured = false;
|
|
2045
|
-
}
|
|
2046
|
-
|
|
2047
|
-
if (!captured || activity == null) {
|
|
2048
|
-
return;
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
|
-
activity.runOnUiThread(() -> {
|
|
2052
|
-
try {
|
|
2053
|
-
activity.getWindow().setBackgroundDrawable(backgroundToRestore);
|
|
2054
|
-
} catch (Exception e) {
|
|
2055
|
-
Log.w(TAG, "Failed to restore window background", e);
|
|
2056
|
-
}
|
|
2057
|
-
});
|
|
2058
|
-
}
|
|
2059
|
-
|
|
2060
|
-
private int toOpaqueColor(int color) {
|
|
2061
|
-
return Color.argb(255, Color.red(color), Color.green(color), Color.blue(color));
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
|
-
private void lockSystemUiForToBackMode(Activity activity, boolean toBack) {
|
|
2065
|
-
if (activity == null) {
|
|
2066
|
-
return;
|
|
2067
|
-
}
|
|
2068
|
-
if (!toBack) {
|
|
2069
|
-
restoreSystemUiForToBackMode(activity);
|
|
2070
|
-
return;
|
|
2071
|
-
}
|
|
2072
|
-
|
|
2073
|
-
try {
|
|
2074
|
-
if (originalStatusBarColor == null) {
|
|
2075
|
-
originalStatusBarColor = activity.getWindow().getStatusBarColor();
|
|
2076
|
-
}
|
|
2077
|
-
if (originalNavigationBarColor == null) {
|
|
2078
|
-
originalNavigationBarColor = activity.getWindow().getNavigationBarColor();
|
|
2079
|
-
}
|
|
2080
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && originalNavigationBarContrastEnforced == null) {
|
|
2081
|
-
originalNavigationBarContrastEnforced = activity.getWindow().isNavigationBarContrastEnforced();
|
|
2082
|
-
}
|
|
2083
|
-
|
|
2084
|
-
int statusBarColor = toOpaqueColor(originalStatusBarColor != null ? originalStatusBarColor : Color.BLACK);
|
|
2085
|
-
int navigationBarColor = toOpaqueColor(originalNavigationBarColor != null ? originalNavigationBarColor : Color.BLACK);
|
|
2086
|
-
|
|
2087
|
-
activity.getWindow().setStatusBarColor(statusBarColor);
|
|
2088
|
-
activity.getWindow().setNavigationBarColor(navigationBarColor);
|
|
2089
|
-
|
|
2090
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
2091
|
-
activity.getWindow().setNavigationBarContrastEnforced(false);
|
|
2092
|
-
}
|
|
2093
|
-
} catch (Exception e) {
|
|
2094
|
-
Log.w(TAG, "Failed to lock system UI colors for toBack mode", e);
|
|
2095
|
-
}
|
|
2096
|
-
}
|
|
2097
|
-
|
|
2098
|
-
private void restoreSystemUiForToBackMode(Activity activity) {
|
|
2099
|
-
final Integer statusBarColor = originalStatusBarColor;
|
|
2100
|
-
final Integer navigationBarColor = originalNavigationBarColor;
|
|
2101
|
-
final Boolean navigationBarContrastEnforced = originalNavigationBarContrastEnforced;
|
|
2102
|
-
originalStatusBarColor = null;
|
|
2103
|
-
originalNavigationBarColor = null;
|
|
2104
|
-
originalNavigationBarContrastEnforced = null;
|
|
2105
|
-
|
|
2106
|
-
if (activity == null) {
|
|
2107
|
-
return;
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
|
-
activity.runOnUiThread(() -> {
|
|
2111
|
-
try {
|
|
2112
|
-
if (statusBarColor != null) {
|
|
2113
|
-
activity.getWindow().setStatusBarColor(statusBarColor);
|
|
2114
|
-
}
|
|
2115
|
-
if (navigationBarColor != null) {
|
|
2116
|
-
activity.getWindow().setNavigationBarColor(navigationBarColor);
|
|
2117
|
-
}
|
|
2118
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && navigationBarContrastEnforced != null) {
|
|
2119
|
-
activity.getWindow().setNavigationBarContrastEnforced(navigationBarContrastEnforced);
|
|
2120
|
-
}
|
|
2121
|
-
} catch (Exception ignored) {}
|
|
2122
|
-
});
|
|
2123
|
-
}
|
|
2124
|
-
|
|
2125
|
-
private void prepareTransparentBackgroundsForToBack(CameraXView visualStateOwner) {
|
|
1997
|
+
private void applyToBackVisualState(CameraXView visualStateOwner) {
|
|
2126
1998
|
Activity activity = getActivity();
|
|
2127
1999
|
WebView webView = getBridge().getWebView();
|
|
2128
2000
|
if (activity == null || webView == null) {
|
|
@@ -2131,34 +2003,20 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2131
2003
|
|
|
2132
2004
|
toBackVisualStateActive = true;
|
|
2133
2005
|
final ViewGroup webViewParent = (ViewGroup) webView.getParent();
|
|
2134
|
-
captureOriginalWindowBackground(activity);
|
|
2135
2006
|
captureOriginalWebViewVisualState(webView, webViewParent);
|
|
2136
|
-
}
|
|
2137
|
-
|
|
2138
|
-
private void activateTransparentBackgroundsForToBack(CameraXView visualStateOwner) {
|
|
2139
|
-
prepareTransparentBackgroundsForToBack(visualStateOwner);
|
|
2140
|
-
Activity activity = getActivity();
|
|
2141
|
-
WebView webView = getBridge().getWebView();
|
|
2142
|
-
if (activity == null || webView == null) {
|
|
2143
|
-
return;
|
|
2144
|
-
}
|
|
2145
|
-
|
|
2146
|
-
final ViewGroup webViewParent = (ViewGroup) webView.getParent();
|
|
2147
2007
|
|
|
2148
2008
|
Runnable apply = () -> {
|
|
2149
2009
|
try {
|
|
2150
2010
|
if (!toBackVisualStateActive || visualStateOwner == null || cameraXView != visualStateOwner) {
|
|
2151
2011
|
return;
|
|
2152
2012
|
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
activity.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
|
|
2013
|
+
if (ToBackCompositorHelper.shouldTransparentizeWebViewParent() && webViewParent != null) {
|
|
2014
|
+
webViewParent.setBackgroundColor(ToBackCompositorHelper.resolveWebViewBackgroundColor());
|
|
2156
2015
|
}
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
webView.
|
|
2161
|
-
webView.setAlpha(isMiuiDevice() ? 0.99f : (originalWebViewAlpha != null ? originalWebViewAlpha : 1f));
|
|
2016
|
+
webView.setBackgroundColor(ToBackCompositorHelper.resolveWebViewBackgroundColor());
|
|
2017
|
+
webView.setLayerType(ToBackCompositorHelper.resolveWebViewLayerType(), null);
|
|
2018
|
+
float alpha = ToBackCompositorHelper.resolveWebViewAlpha(originalWebViewAlpha != null ? originalWebViewAlpha : 1f);
|
|
2019
|
+
webView.setAlpha(alpha);
|
|
2162
2020
|
if (webViewParent != null) {
|
|
2163
2021
|
webViewParent.requestTransparentRegion(webView);
|
|
2164
2022
|
}
|
|
@@ -2167,20 +2025,14 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2167
2025
|
}
|
|
2168
2026
|
};
|
|
2169
2027
|
|
|
2170
|
-
activity.runOnUiThread(
|
|
2171
|
-
apply.run();
|
|
2172
|
-
if (isMiuiDevice()) {
|
|
2173
|
-
webView.postDelayed(apply, 50);
|
|
2174
|
-
webView.postDelayed(apply, 250);
|
|
2175
|
-
}
|
|
2176
|
-
});
|
|
2028
|
+
activity.runOnUiThread(apply);
|
|
2177
2029
|
}
|
|
2178
2030
|
|
|
2179
2031
|
private void applyTransparentBackgroundsForToBack() {
|
|
2180
2032
|
if (!isToBackMode()) {
|
|
2181
2033
|
return;
|
|
2182
2034
|
}
|
|
2183
|
-
|
|
2035
|
+
applyToBackVisualState(cameraXView);
|
|
2184
2036
|
}
|
|
2185
2037
|
|
|
2186
2038
|
private void restoreWebViewVisualState() {
|
|
@@ -2191,24 +2043,27 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2191
2043
|
Activity activity = getActivity();
|
|
2192
2044
|
WebView webView = getBridge().getWebView();
|
|
2193
2045
|
final Float alphaToRestore;
|
|
2046
|
+
final Integer layerTypeToRestore;
|
|
2194
2047
|
final Drawable webViewBackground;
|
|
2195
2048
|
final boolean webViewBackgroundCaptured;
|
|
2196
2049
|
final Drawable parentBackground;
|
|
2197
2050
|
final boolean parentBackgroundCaptured;
|
|
2198
2051
|
synchronized (this) {
|
|
2199
2052
|
alphaToRestore = originalWebViewAlpha;
|
|
2053
|
+
layerTypeToRestore = originalWebViewLayerType;
|
|
2200
2054
|
webViewBackground = originalWebViewBackground;
|
|
2201
2055
|
webViewBackgroundCaptured = originalWebViewBackgroundCaptured;
|
|
2202
2056
|
parentBackground = originalWebViewParentBackground;
|
|
2203
2057
|
parentBackgroundCaptured = originalWebViewParentBackgroundCaptured;
|
|
2204
2058
|
originalWebViewAlpha = null;
|
|
2059
|
+
originalWebViewLayerType = null;
|
|
2205
2060
|
originalWebViewBackground = null;
|
|
2206
2061
|
originalWebViewBackgroundCaptured = false;
|
|
2207
2062
|
originalWebViewParentBackground = null;
|
|
2208
2063
|
originalWebViewParentBackgroundCaptured = false;
|
|
2209
2064
|
}
|
|
2210
2065
|
|
|
2211
|
-
if (alphaToRestore == null && !webViewBackgroundCaptured && !parentBackgroundCaptured) {
|
|
2066
|
+
if (alphaToRestore == null && layerTypeToRestore == null && !webViewBackgroundCaptured && !parentBackgroundCaptured) {
|
|
2212
2067
|
return;
|
|
2213
2068
|
}
|
|
2214
2069
|
|
|
@@ -2227,6 +2082,9 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2227
2082
|
} else {
|
|
2228
2083
|
webView.setBackgroundColor(DEFAULT_WEB_VIEW_BACKGROUND);
|
|
2229
2084
|
}
|
|
2085
|
+
if (layerTypeToRestore != null) {
|
|
2086
|
+
webView.setLayerType(layerTypeToRestore, null);
|
|
2087
|
+
}
|
|
2230
2088
|
if (webViewParent != null && parentBackgroundCaptured) {
|
|
2231
2089
|
webViewParent.setBackground(parentBackground);
|
|
2232
2090
|
}
|
|
@@ -2237,17 +2095,10 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2237
2095
|
@Override
|
|
2238
2096
|
public void onCameraStarted(int width, int height, int x, int y) {
|
|
2239
2097
|
cameraRestartAfterResumeInProgress = false;
|
|
2240
|
-
//
|
|
2241
|
-
// regardless of whether there is a pending JS call. This is critical for the
|
|
2242
|
-
// background/foreground resume cycle: on resume, handleOnResume() sets backgrounds to
|
|
2243
|
-
// black (to prevent flicker) and then restarts the camera session, but
|
|
2244
|
-
// cameraStartCallbackId is null at that point. Without this unconditional block the
|
|
2245
|
-
// window and WebView stay black after every background/foreground transition.
|
|
2246
|
-
// Both backgrounds are set together in the same UI thread operation to avoid race
|
|
2247
|
-
// conditions and compositor layering issues.
|
|
2098
|
+
// Re-apply WebView transparency after the preview is bound (idempotent safety net for resume).
|
|
2248
2099
|
applyTransparentBackgroundsForToBack();
|
|
2249
2100
|
|
|
2250
|
-
PluginCall call =
|
|
2101
|
+
PluginCall call = pendingCameraStartedCall;
|
|
2251
2102
|
if (call != null) {
|
|
2252
2103
|
// Convert pixel values back to logical units
|
|
2253
2104
|
DisplayMetrics metrics = getBridge().getActivity().getResources().getDisplayMetrics();
|
|
@@ -2417,27 +2268,27 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2417
2268
|
|
|
2418
2269
|
private void resolveCameraStartCall(PluginCall call, JSObject result) {
|
|
2419
2270
|
call.resolve(result);
|
|
2420
|
-
|
|
2421
|
-
|
|
2271
|
+
call.setKeepAlive(false);
|
|
2272
|
+
pendingCameraStartedCall = null; // Prevent re-use
|
|
2422
2273
|
resetPendingStartBarcodeScanner();
|
|
2423
2274
|
}
|
|
2424
2275
|
|
|
2425
2276
|
private void rejectCameraStartCall(PluginCall call, String message) {
|
|
2426
2277
|
call.reject(message);
|
|
2427
|
-
|
|
2428
|
-
|
|
2278
|
+
call.setKeepAlive(false);
|
|
2279
|
+
pendingCameraStartedCall = null;
|
|
2429
2280
|
resetPendingStartBarcodeScanner();
|
|
2430
2281
|
}
|
|
2431
2282
|
|
|
2432
2283
|
@Override
|
|
2433
2284
|
public void onSampleTaken(String result) {
|
|
2434
|
-
PluginCall call =
|
|
2285
|
+
PluginCall call = pendingSampleCall;
|
|
2435
2286
|
if (call != null) {
|
|
2436
2287
|
JSObject ret = new JSObject();
|
|
2437
2288
|
ret.put("value", result);
|
|
2438
2289
|
call.resolve(ret);
|
|
2439
|
-
|
|
2440
|
-
|
|
2290
|
+
call.setKeepAlive(false);
|
|
2291
|
+
pendingSampleCall = null;
|
|
2441
2292
|
} else {
|
|
2442
2293
|
Log.w("CameraPreview", "onSampleTaken: no pending call to resolve");
|
|
2443
2294
|
}
|
|
@@ -2445,11 +2296,11 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2445
2296
|
|
|
2446
2297
|
@Override
|
|
2447
2298
|
public void onSampleTakenError(String message) {
|
|
2448
|
-
PluginCall call =
|
|
2299
|
+
PluginCall call = pendingSampleCall;
|
|
2449
2300
|
if (call != null) {
|
|
2450
2301
|
call.reject(message);
|
|
2451
|
-
|
|
2452
|
-
|
|
2302
|
+
call.setKeepAlive(false);
|
|
2303
|
+
pendingSampleCall = null;
|
|
2453
2304
|
} else {
|
|
2454
2305
|
Log.e("CameraPreview", "Sample taken error (no pending call): " + message);
|
|
2455
2306
|
}
|
|
@@ -2495,17 +2346,15 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2495
2346
|
}
|
|
2496
2347
|
}
|
|
2497
2348
|
|
|
2498
|
-
PluginCall call =
|
|
2349
|
+
PluginCall call = pendingCameraStartedCall;
|
|
2499
2350
|
if (call != null) {
|
|
2500
2351
|
call.reject(message);
|
|
2501
|
-
|
|
2502
|
-
|
|
2352
|
+
call.setKeepAlive(false);
|
|
2353
|
+
pendingCameraStartedCall = null;
|
|
2503
2354
|
resetPendingStartBarcodeScanner();
|
|
2504
2355
|
}
|
|
2505
2356
|
|
|
2506
|
-
restoreOriginalWindowBackground(getBridge().getActivity());
|
|
2507
2357
|
restoreWebViewVisualState();
|
|
2508
|
-
restoreSystemUiForToBackMode(getBridge().getActivity());
|
|
2509
2358
|
}
|
|
2510
2359
|
|
|
2511
2360
|
@PluginMethod
|
|
@@ -2882,28 +2731,30 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2882
2731
|
}
|
|
2883
2732
|
|
|
2884
2733
|
try {
|
|
2885
|
-
|
|
2886
|
-
|
|
2734
|
+
call.setKeepAlive(true);
|
|
2735
|
+
pendingStopRecordCall = call;
|
|
2887
2736
|
cameraXView.stopRecordVideo(
|
|
2888
2737
|
new CameraXView.VideoRecordingCallback() {
|
|
2889
2738
|
@Override
|
|
2890
2739
|
public void onSuccess(String filePath, String reason) {
|
|
2891
|
-
PluginCall saved =
|
|
2740
|
+
PluginCall saved = pendingStopRecordCall;
|
|
2741
|
+
pendingStopRecordCall = null;
|
|
2892
2742
|
if (saved != null) {
|
|
2893
2743
|
JSObject result = new JSObject();
|
|
2894
2744
|
result.put("videoFilePath", filePath);
|
|
2895
2745
|
result.put("reason", reason);
|
|
2896
2746
|
saved.resolve(result);
|
|
2897
|
-
|
|
2747
|
+
saved.setKeepAlive(false);
|
|
2898
2748
|
}
|
|
2899
2749
|
}
|
|
2900
2750
|
|
|
2901
2751
|
@Override
|
|
2902
2752
|
public void onError(String message) {
|
|
2903
|
-
PluginCall saved =
|
|
2753
|
+
PluginCall saved = pendingStopRecordCall;
|
|
2754
|
+
pendingStopRecordCall = null;
|
|
2904
2755
|
if (saved != null) {
|
|
2905
2756
|
saved.reject("Failed to stop video recording: " + message);
|
|
2906
|
-
|
|
2757
|
+
saved.setKeepAlive(false);
|
|
2907
2758
|
}
|
|
2908
2759
|
}
|
|
2909
2760
|
}
|
|
@@ -739,7 +739,12 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
739
739
|
previewContainer.setFocusable(true);
|
|
740
740
|
|
|
741
741
|
// Disable any potential drawing artifacts that might cause 1px offset
|
|
742
|
-
|
|
742
|
+
boolean toBack = sessionConfig != null && sessionConfig.isToBack();
|
|
743
|
+
if (ToBackCompositorHelper.shouldUseHardwareLayerOnPreviewContainer(toBack)) {
|
|
744
|
+
previewContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
|
745
|
+
} else {
|
|
746
|
+
previewContainer.setLayerType(View.LAYER_TYPE_NONE, null);
|
|
747
|
+
}
|
|
743
748
|
|
|
744
749
|
// Ensure no clip bounds that might cause visual offset
|
|
745
750
|
previewContainer.setClipChildren(false);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package app.capgo.capacitor.camera.preview;
|
|
2
|
+
|
|
3
|
+
import android.graphics.Color;
|
|
4
|
+
import android.view.View;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Policy for Android {@code toBack} compositing.
|
|
8
|
+
* <p>
|
|
9
|
+
* Camera preview sits behind the Capacitor WebView. Only the WebView stack should be made
|
|
10
|
+
* transparent — never the Activity window drawable or system bar colors. Touching those causes
|
|
11
|
+
* status/navigation bar flicker (Pixel/Android 16) and fights edge-to-edge scrims.
|
|
12
|
+
*/
|
|
13
|
+
public final class ToBackCompositorHelper {
|
|
14
|
+
|
|
15
|
+
private ToBackCompositorHelper() {}
|
|
16
|
+
|
|
17
|
+
public static boolean shouldTransparentizeWebViewParent() {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public static int resolveWebViewBackgroundColor() {
|
|
22
|
+
return Color.TRANSPARENT;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public static float resolveWebViewAlpha(float originalAlpha) {
|
|
26
|
+
return originalAlpha;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public static boolean shouldUseHardwareLayerOnPreviewContainer(boolean toBack) {
|
|
30
|
+
return !toBack;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public static int resolveWebViewLayerType() {
|
|
34
|
+
return View.LAYER_TYPE_HARDWARE;
|
|
35
|
+
}
|
|
36
|
+
}
|
package/android/src/test/java/app/capgo/capacitor/camera/preview/ToBackCompositorHelperTest.java
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
package app.capgo.capacitor.camera.preview;
|
|
2
|
+
|
|
3
|
+
import static org.junit.Assert.assertEquals;
|
|
4
|
+
import static org.junit.Assert.assertFalse;
|
|
5
|
+
import static org.junit.Assert.assertTrue;
|
|
6
|
+
|
|
7
|
+
import android.graphics.Color;
|
|
8
|
+
import android.view.View;
|
|
9
|
+
import org.junit.Test;
|
|
10
|
+
|
|
11
|
+
public class ToBackCompositorHelperTest {
|
|
12
|
+
|
|
13
|
+
@Test
|
|
14
|
+
public void transparentizesWebViewParentOnAllDevices() {
|
|
15
|
+
assertTrue(ToBackCompositorHelper.shouldTransparentizeWebViewParent());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@Test
|
|
19
|
+
public void webViewBackgroundIsFullyTransparent() {
|
|
20
|
+
assertEquals(Color.TRANSPARENT, ToBackCompositorHelper.resolveWebViewBackgroundColor());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@Test
|
|
24
|
+
public void preservesOriginalWebViewAlpha() {
|
|
25
|
+
assertEquals(0.75f, ToBackCompositorHelper.resolveWebViewAlpha(0.75f), 0.0001f);
|
|
26
|
+
assertEquals(1f, ToBackCompositorHelper.resolveWebViewAlpha(1f), 0.0001f);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@Test
|
|
30
|
+
public void skipsHardwareLayerOnPreviewContainerWhenToBack() {
|
|
31
|
+
assertFalse(ToBackCompositorHelper.shouldUseHardwareLayerOnPreviewContainer(true));
|
|
32
|
+
assertTrue(ToBackCompositorHelper.shouldUseHardwareLayerOnPreviewContainer(false));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Test
|
|
36
|
+
public void usesHardwareLayerForWebViewTransparency() {
|
|
37
|
+
assertEquals(View.LAYER_TYPE_HARDWARE, ToBackCompositorHelper.resolveWebViewLayerType());
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -82,8 +82,8 @@ class CameraController: NSObject {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
// 2. Reset Exposure to the center ONLY if it is not explicitly locked
|
|
86
|
-
if
|
|
85
|
+
// 2. Reset Exposure to the center ONLY if it is not explicitly locked by the user
|
|
86
|
+
if self.shouldAutoAdjustExposureOnFocus {
|
|
87
87
|
if device.isExposureModeSupported(.continuousAutoExposure) {
|
|
88
88
|
device.exposureMode = .continuousAutoExposure
|
|
89
89
|
if device.isExposurePointOfInterestSupported {
|
|
@@ -105,6 +105,14 @@ class CameraController: NSObject {
|
|
|
105
105
|
|
|
106
106
|
var captureSession: AVCaptureSession?
|
|
107
107
|
var disableFocusIndicator: Bool = false
|
|
108
|
+
/// App-level exposure preference. AVFoundation's `.autoExpose` auto-transitions to
|
|
109
|
+
/// `.locked` after one-shot metering, so device.exposureMode alone cannot tell whether
|
|
110
|
+
/// the user explicitly locked exposure via setExposureMode("LOCK").
|
|
111
|
+
private var preferredExposureMode: String = "CONTINUOUS"
|
|
112
|
+
/// AUTO/CONTINUOUS allow tap/restore to reset metering + EV; LOCK/CUSTOM must keep user settings.
|
|
113
|
+
private var shouldAutoAdjustExposureOnFocus: Bool {
|
|
114
|
+
preferredExposureMode == "AUTO" || preferredExposureMode == "CONTINUOUS"
|
|
115
|
+
}
|
|
108
116
|
private var focusExposureRestoreGeneration: UInt = 0
|
|
109
117
|
private var focusExposureRestoreTimeoutWorkItem: DispatchWorkItem?
|
|
110
118
|
private let focusExposureRestoreTimeout: TimeInterval = 3.0
|
|
@@ -1290,12 +1298,13 @@ extension CameraController {
|
|
|
1290
1298
|
self.rearCameraInput = newInput
|
|
1291
1299
|
self.currentCameraPosition = .rear
|
|
1292
1300
|
}
|
|
1293
|
-
// (Lightweight focus config; non-fatal on failure)
|
|
1301
|
+
// (Lightweight focus/exposure config; non-fatal on failure)
|
|
1294
1302
|
try? targetDevice.lockForConfiguration()
|
|
1295
1303
|
if targetDevice.isFocusModeSupported(.continuousAutoFocus) {
|
|
1296
1304
|
targetDevice.focusMode = .continuousAutoFocus
|
|
1297
1305
|
}
|
|
1298
1306
|
targetDevice.unlockForConfiguration()
|
|
1307
|
+
try? self.applyPreferredExposureMode(to: targetDevice)
|
|
1299
1308
|
|
|
1300
1309
|
// Restore audio input if it existed
|
|
1301
1310
|
if let audioInput = existingAudioInput, captureSession.canAddInput(audioInput) {
|
|
@@ -2224,7 +2233,8 @@ extension CameraController {
|
|
|
2224
2233
|
device.focusPointOfInterest = point
|
|
2225
2234
|
}
|
|
2226
2235
|
|
|
2227
|
-
|
|
2236
|
+
// Prefer app-level mode: `.autoExpose` leaves device.exposureMode as `.locked`.
|
|
2237
|
+
if adjustExposure, self.shouldAutoAdjustExposureOnFocus {
|
|
2228
2238
|
if device.isExposurePointOfInterestSupported, device.isExposureModeSupported(.autoExpose) {
|
|
2229
2239
|
device.exposureMode = .autoExpose
|
|
2230
2240
|
device.setExposureTargetBias(0.0) { _ in }
|
|
@@ -2282,7 +2292,9 @@ extension CameraController {
|
|
|
2282
2292
|
}
|
|
2283
2293
|
}
|
|
2284
2294
|
|
|
2285
|
-
|
|
2295
|
+
// Restore continuous AE for AUTO/CONTINUOUS only (preserve LOCK/CUSTOM).
|
|
2296
|
+
// Do not trust device.exposureMode here: one-shot `.autoExpose` ends in `.locked`.
|
|
2297
|
+
if self.shouldAutoAdjustExposureOnFocus {
|
|
2286
2298
|
if device.isExposureModeSupported(.continuousAutoExposure) {
|
|
2287
2299
|
device.exposureMode = .continuousAutoExposure
|
|
2288
2300
|
if device.isExposurePointOfInterestSupported {
|
|
@@ -2302,13 +2314,7 @@ extension CameraController {
|
|
|
2302
2314
|
guard let device = self.activeVideoDevice() else { return }
|
|
2303
2315
|
|
|
2304
2316
|
let centerPoint = CGPoint(x: 0.5, y: 0.5)
|
|
2305
|
-
let adjustExposure
|
|
2306
|
-
do {
|
|
2307
|
-
let exposureMode = try self.getExposureMode()
|
|
2308
|
-
adjustExposure = exposureMode == "AUTO" || exposureMode == "CONTINUOUS"
|
|
2309
|
-
} catch {
|
|
2310
|
-
adjustExposure = true
|
|
2311
|
-
}
|
|
2317
|
+
let adjustExposure = self.shouldAutoAdjustExposureOnFocus
|
|
2312
2318
|
|
|
2313
2319
|
do {
|
|
2314
2320
|
try self.applyOneShotFocusAndExposure(at: centerPoint, on: device, adjustExposure: adjustExposure)
|
|
@@ -2498,14 +2504,15 @@ extension CameraController {
|
|
|
2498
2504
|
self.rearCameraInput = newInput
|
|
2499
2505
|
self.rearCamera = targetDevice
|
|
2500
2506
|
self.currentCameraPosition = .rear
|
|
2507
|
+
}
|
|
2501
2508
|
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
}
|
|
2507
|
-
targetDevice.unlockForConfiguration()
|
|
2509
|
+
// Lightweight focus/exposure config; non-fatal on failure
|
|
2510
|
+
try? targetDevice.lockForConfiguration()
|
|
2511
|
+
if targetDevice.isFocusModeSupported(.continuousAutoFocus) {
|
|
2512
|
+
targetDevice.focusMode = .continuousAutoFocus
|
|
2508
2513
|
}
|
|
2514
|
+
targetDevice.unlockForConfiguration()
|
|
2515
|
+
try? self.applyPreferredExposureMode(to: targetDevice)
|
|
2509
2516
|
} else {
|
|
2510
2517
|
throw CameraControllerError.invalidOperation
|
|
2511
2518
|
}
|
|
@@ -2564,6 +2571,7 @@ extension CameraController {
|
|
|
2564
2571
|
|
|
2565
2572
|
self.captureSession = nil
|
|
2566
2573
|
self.currentCameraPosition = nil
|
|
2574
|
+
self.preferredExposureMode = "CONTINUOUS"
|
|
2567
2575
|
|
|
2568
2576
|
// Reset output preparation status
|
|
2569
2577
|
self.outputsPrepared = false
|
|
@@ -2576,6 +2584,39 @@ extension CameraController {
|
|
|
2576
2584
|
|
|
2577
2585
|
// MARK: - Exposure Controls
|
|
2578
2586
|
|
|
2587
|
+
private func applyPreferredExposureMode(to device: AVCaptureDevice) throws {
|
|
2588
|
+
let mode = self.preferredExposureMode
|
|
2589
|
+
let desiredMode: AVCaptureDevice.ExposureMode
|
|
2590
|
+
switch mode {
|
|
2591
|
+
case "LOCK":
|
|
2592
|
+
desiredMode = .locked
|
|
2593
|
+
case "AUTO":
|
|
2594
|
+
desiredMode = .autoExpose
|
|
2595
|
+
case "CUSTOM":
|
|
2596
|
+
desiredMode = .custom
|
|
2597
|
+
default:
|
|
2598
|
+
desiredMode = .continuousAutoExposure
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
guard device.isExposureModeSupported(desiredMode) else {
|
|
2602
|
+
// Fall back to continuous when the preferred mode is unavailable on this lens.
|
|
2603
|
+
if device.isExposureModeSupported(.continuousAutoExposure) {
|
|
2604
|
+
try device.lockForConfiguration()
|
|
2605
|
+
device.exposureMode = .continuousAutoExposure
|
|
2606
|
+
device.setExposureTargetBias(0.0) { _ in }
|
|
2607
|
+
device.unlockForConfiguration()
|
|
2608
|
+
}
|
|
2609
|
+
return
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
try device.lockForConfiguration()
|
|
2613
|
+
device.exposureMode = desiredMode
|
|
2614
|
+
if desiredMode == .autoExpose || desiredMode == .continuousAutoExposure {
|
|
2615
|
+
device.setExposureTargetBias(0.0) { _ in }
|
|
2616
|
+
}
|
|
2617
|
+
device.unlockForConfiguration()
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2579
2620
|
func getExposureModes() throws -> [String] {
|
|
2580
2621
|
var currentCamera: AVCaptureDevice?
|
|
2581
2622
|
switch currentCameraPosition {
|
|
@@ -2610,22 +2651,12 @@ extension CameraController {
|
|
|
2610
2651
|
break
|
|
2611
2652
|
}
|
|
2612
2653
|
|
|
2613
|
-
guard
|
|
2654
|
+
guard currentCamera != nil else {
|
|
2614
2655
|
throw CameraControllerError.noCamerasAvailable
|
|
2615
2656
|
}
|
|
2616
2657
|
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
return "LOCK"
|
|
2620
|
-
case .autoExpose:
|
|
2621
|
-
return "AUTO"
|
|
2622
|
-
case .continuousAutoExposure:
|
|
2623
|
-
return "CONTINUOUS"
|
|
2624
|
-
case .custom:
|
|
2625
|
-
return "CUSTOM"
|
|
2626
|
-
@unknown default:
|
|
2627
|
-
return "CONTINUOUS"
|
|
2628
|
-
}
|
|
2658
|
+
// App preference, not device.exposureMode: one-shot `.autoExpose` ends as `.locked`.
|
|
2659
|
+
return self.preferredExposureMode
|
|
2629
2660
|
}
|
|
2630
2661
|
|
|
2631
2662
|
func setExposureMode(mode: String) throws {
|
|
@@ -2662,14 +2693,15 @@ extension CameraController {
|
|
|
2662
2693
|
throw CameraControllerError.invalidOperation
|
|
2663
2694
|
}
|
|
2664
2695
|
|
|
2696
|
+
switch normalized {
|
|
2697
|
+
case "LOCK", "AUTO", "CONTINUOUS", "CUSTOM":
|
|
2698
|
+
self.preferredExposureMode = normalized
|
|
2699
|
+
default:
|
|
2700
|
+
self.preferredExposureMode = "CONTINUOUS"
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2665
2703
|
do {
|
|
2666
|
-
try
|
|
2667
|
-
device.exposureMode = finalMode
|
|
2668
|
-
// Reset EV to 0 when switching to AUTO or CONTINUOUS
|
|
2669
|
-
if finalMode == .autoExpose || finalMode == .continuousAutoExposure {
|
|
2670
|
-
device.setExposureTargetBias(0.0) { _ in }
|
|
2671
|
-
}
|
|
2672
|
-
device.unlockForConfiguration()
|
|
2704
|
+
try self.applyPreferredExposureMode(to: device)
|
|
2673
2705
|
} catch {
|
|
2674
2706
|
throw CameraControllerError.invalidOperation
|
|
2675
2707
|
}
|
|
@@ -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.11.
|
|
38
|
+
private let pluginVersion: String = "8.11.3"
|
|
39
39
|
public let identifier = "CameraPreviewPlugin"
|
|
40
40
|
public let jsName = "CameraPreview"
|
|
41
41
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -1233,7 +1233,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1233
1233
|
print("[CameraPreview] Location permission not determined, requesting...")
|
|
1234
1234
|
// Save the call for the delegate callback
|
|
1235
1235
|
print("[CameraPreview] Saving call for location authorization flow")
|
|
1236
|
-
|
|
1236
|
+
call.keepAlive = true
|
|
1237
1237
|
self.permissionCallID = call.callbackId
|
|
1238
1238
|
self.waitingForLocation = true
|
|
1239
1239
|
|
|
@@ -2029,14 +2029,14 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
2029
2029
|
print("[CameraPreview] Location authorized, getting location for capture")
|
|
2030
2030
|
self.getCurrentLocation { _ in
|
|
2031
2031
|
self.performCapture(call: call)
|
|
2032
|
-
|
|
2032
|
+
call.keepAlive = false
|
|
2033
2033
|
self.permissionCallID = nil
|
|
2034
2034
|
self.waitingForLocation = false
|
|
2035
2035
|
}
|
|
2036
2036
|
case .denied, .restricted:
|
|
2037
2037
|
print("[CameraPreview] Location denied, rejecting capture")
|
|
2038
2038
|
call.reject("Location permission denied")
|
|
2039
|
-
|
|
2039
|
+
call.keepAlive = false
|
|
2040
2040
|
self.permissionCallID = nil
|
|
2041
2041
|
self.waitingForLocation = false
|
|
2042
2042
|
case .notDetermined:
|
|
@@ -2045,7 +2045,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
2045
2045
|
@unknown default:
|
|
2046
2046
|
print("[CameraPreview] Unknown status, rejecting capture")
|
|
2047
2047
|
call.reject("Unknown location permission status")
|
|
2048
|
-
|
|
2048
|
+
call.keepAlive = false
|
|
2049
2049
|
self.permissionCallID = nil
|
|
2050
2050
|
self.waitingForLocation = false
|
|
2051
2051
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/camera-preview",
|
|
3
|
-
"version": "8.11.
|
|
3
|
+
"version": "8.11.3",
|
|
4
4
|
"description": "Camera preview",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"watch": "tsc --watch",
|
|
57
57
|
"prepublishOnly": "bun run build",
|
|
58
58
|
"check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs",
|
|
59
|
+
"check:cap9-deprecated": "bun scripts/check-cap9-deprecated.mjs",
|
|
59
60
|
"example:install": "cd example-app && bun install --frozen-lockfile",
|
|
60
61
|
"example:build": "bun run build && cd example-app && bun install --frozen-lockfile && bun run build",
|
|
61
62
|
"changelog:ai": "node scripts/generate-ai-changelog.mjs"
|