@capgo/camera-preview 8.2.1 → 8.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -809,7 +809,8 @@ Switches the active camera to the one with the specified `deviceId`.
809
809
  getDeviceId() => Promise<{ deviceId: string; }>
810
810
  ```
811
811
 
812
- Gets the ID of the currently active camera device.
812
+ Gets the ID of the camera device that is currently bound.
813
+ On Android, if a physical-lens request falls back to a logical camera, this returns the bound logical camera ID.
813
814
 
814
815
  **Returns:** <code>Promise&lt;{ deviceId: string; }&gt;</code>
815
816
 
@@ -1071,34 +1072,35 @@ Get the native Capacitor plugin version
1071
1072
 
1072
1073
  Defines the configuration options for starting the camera preview.
1073
1074
 
1074
- | Prop | Type | Description | Default | Since |
1075
- | ---------------------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ------ |
1076
- | **`parent`** | <code>string</code> | The parent element to attach the video preview to. | | |
1077
- | **`className`** | <code>string</code> | A CSS class name to add to the preview element. | | |
1078
- | **`width`** | <code>number</code> | The width of the preview in pixels. Defaults to the screen width. | | |
1079
- | **`height`** | <code>number</code> | The height of the preview in pixels. Defaults to the screen height. | | |
1080
- | **`x`** | <code>number</code> | The horizontal origin of the preview, in pixels. | | |
1081
- | **`y`** | <code>number</code> | The vertical origin of the preview, in pixels. | | |
1082
- | **`aspectRatio`** | <code>'4:3' \| '16:9'</code> | The aspect ratio of the camera preview, '4:3' or '16:9' or 'fill'. Cannot be set if width or height is provided, otherwise the call will be rejected. Use setPreviewSize to adjust size after starting. | | 2.0.0 |
1083
- | **`aspectMode`** | <code>'cover' \| 'contain'</code> | Controls how the camera preview fills the available space. - 'contain': Fits the entire preview within the space, may show letterboxing (default). - 'cover': Fills the entire space, may crop edges of the preview. | <code>"contain"</code> | |
1084
- | **`gridMode`** | <code><a href="#gridmode">GridMode</a></code> | The grid overlay to display on the camera preview. | <code>"none"</code> | 2.1.0 |
1085
- | **`includeSafeAreaInsets`** | <code>boolean</code> | Adjusts the y-position to account for safe areas (e.g., notches). | <code>false</code> | |
1086
- | **`toBack`** | <code>boolean</code> | If true, places the preview behind the webview. | <code>true</code> | |
1087
- | **`paddingBottom`** | <code>number</code> | Bottom padding for the preview, in pixels. | | |
1088
- | **`rotateWhenOrientationChanged`** | <code>boolean</code> | Whether to rotate the preview when the device orientation changes. | <code>true</code> | |
1089
- | **`position`** | <code>string</code> | The camera to use. | <code>"rear"</code> | |
1090
- | **`storeToFile`** | <code>boolean</code> | If true, saves the captured image to a file and returns the file path. If false, returns a base64 encoded string. | <code>false</code> | |
1091
- | **`disableExifHeaderStripping`** | <code>boolean</code> | If true, prevents the plugin from rotating the image based on EXIF data. | <code>false</code> | |
1092
- | **`disableAudio`** | <code>boolean</code> | If true, disables the audio stream, preventing audio permission requests. | <code>true</code> | |
1093
- | **`lockAndroidOrientation`** | <code>boolean</code> | If true, locks the device orientation while the camera is active. | <code>false</code> | |
1094
- | **`enableOpacity`** | <code>boolean</code> | If true, allows the camera preview's opacity to be changed. | <code>false</code> | |
1095
- | **`disableFocusIndicator`** | <code>boolean</code> | If true, disables the visual focus indicator when tapping to focus. | <code>false</code> | |
1096
- | **`deviceId`** | <code>string</code> | The `deviceId` of the camera to use. If provided, `position` is ignored. | | |
1097
- | **`initialZoomLevel`** | <code>number</code> | The initial zoom level when starting the camera preview. If the requested zoom level is not available, the native plugin will reject. | <code>1.0</code> | 2.2.0 |
1098
- | **`positioning`** | <code><a href="#camerapositioning">CameraPositioning</a></code> | The vertical positioning of the camera preview. | <code>"center"</code> | 2.3.0 |
1099
- | **`enableVideoMode`** | <code>boolean</code> | If true, enables video capture capabilities when the camera starts. | <code>false</code> | 7.11.0 |
1100
- | **`force`** | <code>boolean</code> | If true, forces the camera to start/restart even if it's already running or busy. This will kill the current camera session and start a new one, ignoring all state checks. | <code>false</code> | |
1101
- | **`videoQuality`** | <code>'low' \| 'medium' \| 'high'</code> | Sets the quality of video for recording. Options: 'low', 'medium', 'high' | <code>"high"</code> | |
1075
+ | Prop | Type | Description | Default | Since |
1076
+ | ----------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ------ |
1077
+ | **`parent`** | <code>string</code> | The parent element to attach the video preview to. | | |
1078
+ | **`className`** | <code>string</code> | A CSS class name to add to the preview element. | | |
1079
+ | **`width`** | <code>number</code> | The width of the preview in pixels. Defaults to the screen width. | | |
1080
+ | **`height`** | <code>number</code> | The height of the preview in pixels. Defaults to the screen height. | | |
1081
+ | **`x`** | <code>number</code> | The horizontal origin of the preview, in pixels. | | |
1082
+ | **`y`** | <code>number</code> | The vertical origin of the preview, in pixels. | | |
1083
+ | **`aspectRatio`** | <code>'4:3' \| '16:9'</code> | The aspect ratio of the camera preview, '4:3' or '16:9' or 'fill'. Cannot be set if width or height is provided, otherwise the call will be rejected. Use setPreviewSize to adjust size after starting. | | 2.0.0 |
1084
+ | **`aspectMode`** | <code>'cover' \| 'contain'</code> | Controls how the camera preview fills the available space. - 'contain': Fits the entire preview within the space, may show letterboxing (default). - 'cover': Fills the entire space, may crop edges of the preview. | <code>"contain"</code> | |
1085
+ | **`gridMode`** | <code><a href="#gridmode">GridMode</a></code> | The grid overlay to display on the camera preview. | <code>"none"</code> | 2.1.0 |
1086
+ | **`includeSafeAreaInsets`** | <code>boolean</code> | Adjusts the y-position to account for safe areas (e.g., notches). | <code>false</code> | |
1087
+ | **`toBack`** | <code>boolean</code> | If true, places the preview behind the webview. | <code>true</code> | |
1088
+ | **`paddingBottom`** | <code>number</code> | Bottom padding for the preview, in pixels. | | |
1089
+ | **`rotateWhenOrientationChanged`** | <code>boolean</code> | Whether to rotate the preview when the device orientation changes. | <code>true</code> | |
1090
+ | **`position`** | <code>string</code> | The camera to use. | <code>"rear"</code> | |
1091
+ | **`storeToFile`** | <code>boolean</code> | If true, saves the captured image to a file and returns the file path. If false, returns a base64 encoded string. | <code>false</code> | |
1092
+ | **`disableExifHeaderStripping`** | <code>boolean</code> | If true, prevents the plugin from rotating the image based on EXIF data. | <code>false</code> | |
1093
+ | **`disableAudio`** | <code>boolean</code> | If true, disables the audio stream, preventing audio permission requests. | <code>true</code> | |
1094
+ | **`lockAndroidOrientation`** | <code>boolean</code> | If true, locks the device orientation while the camera is active. | <code>false</code> | |
1095
+ | **`enableOpacity`** | <code>boolean</code> | If true, allows the camera preview's opacity to be changed. | <code>false</code> | |
1096
+ | **`disableFocusIndicator`** | <code>boolean</code> | If true, disables the visual focus indicator when tapping to focus. | <code>false</code> | |
1097
+ | **`deviceId`** | <code>string</code> | The `deviceId` of the camera to use. If provided, `position` is ignored. | | |
1098
+ | **`enablePhysicalDeviceSelection`** | <code>boolean</code> | On Android, attempts to bind a physical camera directly when `deviceId` refers to a physical lens. Disabled by default because OEM support is inconsistent; when false, Android keeps the current logical-camera fallback behavior. | <code>false</code> | |
1099
+ | **`initialZoomLevel`** | <code>number</code> | The initial zoom level when starting the camera preview. If the requested zoom level is not available, the native plugin will reject. | <code>1.0</code> | 2.2.0 |
1100
+ | **`positioning`** | <code><a href="#camerapositioning">CameraPositioning</a></code> | The vertical positioning of the camera preview. | <code>"center"</code> | 2.3.0 |
1101
+ | **`enableVideoMode`** | <code>boolean</code> | If true, enables video capture capabilities when the camera starts. | <code>false</code> | 7.11.0 |
1102
+ | **`force`** | <code>boolean</code> | If true, forces the camera to start/restart even if it's already running or busy. This will kill the current camera session and start a new one, ignoring all state checks. | <code>false</code> | |
1103
+ | **`videoQuality`** | <code>'low' \| 'medium' \| 'high'</code> | Sets the quality of video for recording. Options: 'low', 'medium', 'high' | <code>"high"</code> | |
1102
1104
 
1103
1105
 
1104
1106
  #### ExifData
@@ -20,6 +20,10 @@ apply plugin: 'com.android.library'
20
20
  android {
21
21
  namespace = "app.capgo.capacitor.camera.preview.capacitorcamerapreview"
22
22
  compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
23
+ compileOptions {
24
+ sourceCompatibility JavaVersion.VERSION_21
25
+ targetCompatibility JavaVersion.VERSION_21
26
+ }
23
27
  defaultConfig {
24
28
  minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
25
29
  targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
@@ -15,6 +15,7 @@ import android.graphics.drawable.ColorDrawable;
15
15
  import android.graphics.drawable.Drawable;
16
16
  import android.location.Location;
17
17
  import android.net.Uri;
18
+ import android.os.Build;
18
19
  import android.provider.Settings;
19
20
  import android.util.DisplayMetrics;
20
21
  import android.util.Log;
@@ -46,6 +47,7 @@ import com.getcapacitor.annotation.PermissionCallback;
46
47
  import com.google.android.gms.location.FusedLocationProviderClient;
47
48
  import com.google.android.gms.location.LocationServices;
48
49
  import java.util.List;
50
+ import java.util.Locale;
49
51
  import java.util.Objects;
50
52
  import org.json.JSONObject;
51
53
 
@@ -132,6 +134,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
132
134
  private String lastOrientationStr = "unknown";
133
135
  private boolean lastDisableAudio = true;
134
136
  private Drawable originalWindowBackground;
137
+ private Float originalWebViewAlpha;
138
+ private Drawable originalWebViewParentBackground;
135
139
  private boolean isCameraPermissionDialogShowing = false;
136
140
 
137
141
  @PluginMethod
@@ -428,6 +432,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
428
432
  } catch (Exception ignored) {}
429
433
  originalWindowBackground = null;
430
434
  }
435
+ restoreWebViewVisualState();
431
436
  call.resolve();
432
437
  });
433
438
  }
@@ -905,6 +910,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
905
910
  //noinspection DataFlowIssue
906
911
  final boolean disableFocusIndicator = call.getBoolean("disableFocusIndicator", false);
907
912
  final boolean enableVideoMode = Boolean.TRUE.equals(call.getBoolean("enableVideoMode", false));
913
+ final boolean enablePhysicalDeviceSelection = Boolean.TRUE.equals(call.getBoolean("enablePhysicalDeviceSelection", false));
908
914
  final String videoQuality = call.getString("videoQuality", "high");
909
915
 
910
916
  // Check for conflict between aspectRatio and size
@@ -914,8 +920,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
914
920
  }
915
921
 
916
922
  float targetZoom = initialZoomLevel;
917
- // Check if the selected device is a physical ultra-wide
918
- if (originalDeviceId != null) {
923
+ if (!enablePhysicalDeviceSelection && originalDeviceId != null) {
919
924
  List<CameraDevice> devices = CameraXView.getAvailableDevicesStatic(getContext());
920
925
  for (CameraDevice device : devices) {
921
926
  if (originalDeviceId.equals(device.getDeviceId()) && !device.isLogical()) {
@@ -923,13 +928,13 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
923
928
  if ("ultraWide".equals(lens.getDeviceType())) {
924
929
  Log.d("CameraPreview", "Ultra-wide lens selected. Targeting 0.5x zoom on logical camera.");
925
930
  targetZoom = 0.5f;
926
- // Force the use of the logical camera by clearing the specific deviceId
931
+ // Preserve existing default behavior unless the new Android flag is explicitly enabled.
927
932
  deviceId = null;
928
933
  break;
929
934
  }
930
935
  }
931
936
  }
932
- if (deviceId == null) break; // Exit outer loop once we've made our decision
937
+ if (deviceId == null) break;
933
938
  }
934
939
  }
935
940
 
@@ -1211,6 +1216,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
1211
1216
  );
1212
1217
  config.setTargetZoom(finalTargetZoom);
1213
1218
  config.setCentered(isCentered);
1219
+ config.setEnablePhysicalDeviceSelection(enablePhysicalDeviceSelection);
1214
1220
 
1215
1221
  bridge.saveCall(call);
1216
1222
  cameraStartCallbackId = call.getCallbackId();
@@ -1478,6 +1484,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
1478
1484
  if (cameraXView == source) {
1479
1485
  cameraXView = null;
1480
1486
  }
1487
+ restoreWebViewVisualState();
1481
1488
 
1482
1489
  PluginCall queuedCall = null;
1483
1490
  synchronized (pendingStartLock) {
@@ -1518,6 +1525,80 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
1518
1525
  return false;
1519
1526
  }
1520
1527
 
1528
+ private boolean isMiuiDevice() {
1529
+ String manufacturer = Build.MANUFACTURER != null ? Build.MANUFACTURER.toLowerCase(Locale.US) : "";
1530
+ String brand = Build.BRAND != null ? Build.BRAND.toLowerCase(Locale.US) : "";
1531
+ return manufacturer.contains("xiaomi") || brand.contains("xiaomi") || brand.contains("redmi") || brand.contains("poco");
1532
+ }
1533
+
1534
+ private void applyTransparentBackgroundsForToBack() {
1535
+ if (!isToBackMode()) {
1536
+ return;
1537
+ }
1538
+ Activity activity = getActivity();
1539
+ WebView webView = getBridge().getWebView();
1540
+ if (activity == null || webView == null) {
1541
+ return;
1542
+ }
1543
+
1544
+ if (originalWebViewAlpha == null) {
1545
+ originalWebViewAlpha = webView.getAlpha();
1546
+ }
1547
+
1548
+ final ViewGroup webViewParent = (ViewGroup) webView.getParent();
1549
+ if (webViewParent != null && originalWebViewParentBackground == null) {
1550
+ originalWebViewParentBackground = webViewParent.getBackground();
1551
+ }
1552
+
1553
+ Runnable apply = () -> {
1554
+ try {
1555
+ activity.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
1556
+ if (webViewParent != null) {
1557
+ webViewParent.setBackgroundColor(Color.TRANSPARENT);
1558
+ }
1559
+ // Keep a tiny alpha on MIUI/Xiaomi devices to avoid compositor bugs that treat
1560
+ // fully transparent layers as black.
1561
+ webView.setBackgroundColor(Color.argb(1, 255, 255, 255));
1562
+ webView.setAlpha(isMiuiDevice() ? 0.99f : (originalWebViewAlpha != null ? originalWebViewAlpha : 1f));
1563
+ } catch (Exception e) {
1564
+ Log.w(TAG, "Failed to set backgrounds to transparent", e);
1565
+ }
1566
+ };
1567
+
1568
+ activity.runOnUiThread(() -> {
1569
+ apply.run();
1570
+ if (isMiuiDevice()) {
1571
+ webView.postDelayed(apply, 50);
1572
+ webView.postDelayed(apply, 250);
1573
+ }
1574
+ });
1575
+ }
1576
+
1577
+ private void restoreWebViewVisualState() {
1578
+ Activity activity = getActivity();
1579
+ WebView webView = getBridge().getWebView();
1580
+ final Float alphaToRestore = originalWebViewAlpha;
1581
+ final Drawable parentBackground = originalWebViewParentBackground;
1582
+ originalWebViewAlpha = null;
1583
+ originalWebViewParentBackground = null;
1584
+
1585
+ if (activity == null || webView == null) {
1586
+ return;
1587
+ }
1588
+
1589
+ final ViewGroup webViewParent = (ViewGroup) webView.getParent();
1590
+ activity.runOnUiThread(() -> {
1591
+ try {
1592
+ if (alphaToRestore != null) {
1593
+ webView.setAlpha(alphaToRestore);
1594
+ }
1595
+ if (webViewParent != null && parentBackground != null) {
1596
+ webViewParent.setBackground(parentBackground);
1597
+ }
1598
+ } catch (Exception ignored) {}
1599
+ });
1600
+ }
1601
+
1521
1602
  @Override
1522
1603
  public void onCameraStarted(int width, int height, int x, int y) {
1523
1604
  // Always transition window and WebView backgrounds to transparent when the camera starts,
@@ -1528,22 +1609,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
1528
1609
  // window and WebView stay black after every background/foreground transition.
1529
1610
  // Both backgrounds are set together in the same UI thread operation to avoid race
1530
1611
  // conditions and compositor layering issues.
1531
- if (isToBackMode()) {
1532
- getBridge()
1533
- .getActivity()
1534
- .runOnUiThread(() -> {
1535
- try {
1536
- // Set window background to transparent
1537
- getBridge().getActivity().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
1538
- // Set webview background to almost-transparent for MIUI compatibility
1539
- // Use alpha=1 instead of 0 to work around MIUI/Xiaomi rendering issues
1540
- // where Color.TRANSPARENT (alpha=0) is not rendered correctly
1541
- getBridge().getWebView().setBackgroundColor(Color.argb(1, 255, 255, 255));
1542
- } catch (Exception e) {
1543
- Log.w(TAG, "Failed to set backgrounds to transparent", e);
1544
- }
1545
- });
1546
- }
1612
+ applyTransparentBackgroundsForToBack();
1547
1613
 
1548
1614
  PluginCall call = bridge.getSavedCall(cameraStartCallbackId);
1549
1615
  if (call != null) {
@@ -1734,6 +1800,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
1734
1800
  });
1735
1801
  }
1736
1802
  }
1803
+ restoreWebViewVisualState();
1737
1804
  }
1738
1805
 
1739
1806
  @PluginMethod