@capgo/camera-preview 8.11.9 → 8.11.10

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
@@ -5,7 +5,7 @@ Show a live camera preview inside your Capacitor app, behind or around your own
5
5
  <a href="https://capgo.app/?ref=plugin_camera_preview"><img src="https://capgo.app/readme-banner.svg?repo=Cap-go/capacitor-camera-preview" alt="Capgo - Instant updates for Capacitor" /></a>
6
6
 
7
7
  <div align="center">
8
- <p><b>Capgo</b>: open-source live updates for Ionic and Capacitor apps. Ship OTA fixes and features instantly, without waiting for app store review.</p>
8
+ <p><b>Capgo</b>: push fixes to your Capacitor users in minutes, build signed iOS and Android apps without a Mac, and roll back in one click.</p>
9
9
  <h2><a href="https://capgo.app/register/?ref=plugin_camera_preview">➡️ Get started for free</a></h2>
10
10
  <p>14-day unlimited free trial. No credit card required</p>
11
11
  <p><a href="https://capgo.app/consulting/?ref=plugin_camera_preview">Missing a feature? We'll build the plugin for you 💪</a></p>
@@ -434,6 +434,8 @@ Documentation for the [uploader](https://github.com/Cap-go/capacitor-uploader)
434
434
  * [`addListener('barcodeScanned', ...)`](#addlistenerbarcodescanned-)
435
435
  * [`addListener('barcodeScanError', ...)`](#addlistenerbarcodescanerror-)
436
436
  * [`addListener('recordingFinished', ...)`](#addlistenerrecordingfinished-)
437
+ * [`addListener('cameraInterrupted', ...)`](#addlistenercamerainterrupted-)
438
+ * [`addListener('cameraInterruptionEnded', ...)`](#addlistenercamerainterruptionended-)
437
439
  * [`deleteFile(...)`](#deletefile)
438
440
  * [`getSafeAreaInsets()`](#getsafeareainsets)
439
441
  * [`getOrientation()`](#getorientation)
@@ -1259,6 +1261,47 @@ Adds a listener fired when a video recording finishes natively, including automa
1259
1261
  --------------------
1260
1262
 
1261
1263
 
1264
+ ### addListener('cameraInterrupted', ...)
1265
+
1266
+ ```typescript
1267
+ addListener(eventName: 'cameraInterrupted', listenerFunc: (data: CameraInterruptedEvent) => void) => Promise<PluginListenerHandle>
1268
+ ```
1269
+
1270
+ Adds a listener for capture session interruptions (for example an active phone call).
1271
+
1272
+ | Param | Type |
1273
+ | ------------------ | -------------------------------------------------------------------------------------------- |
1274
+ | **`eventName`** | <code>'cameraInterrupted'</code> |
1275
+ | **`listenerFunc`** | <code>(data: <a href="#camerainterruptedevent">CameraInterruptedEvent</a>) =&gt; void</code> |
1276
+
1277
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
1278
+
1279
+ **Since:** 8.11.7
1280
+
1281
+ --------------------
1282
+
1283
+
1284
+ ### addListener('cameraInterruptionEnded', ...)
1285
+
1286
+ ```typescript
1287
+ addListener(eventName: 'cameraInterruptionEnded', listenerFunc: () => void) => Promise<PluginListenerHandle>
1288
+ ```
1289
+
1290
+ Adds a listener fired when a capture session interruption ends.
1291
+ Native recovery may still be restarting the session; wait for preview frames before assuming the stream is live.
1292
+
1293
+ | Param | Type |
1294
+ | ------------------ | -------------------------------------- |
1295
+ | **`eventName`** | <code>'cameraInterruptionEnded'</code> |
1296
+ | **`listenerFunc`** | <code>() =&gt; void</code> |
1297
+
1298
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
1299
+
1300
+ **Since:** 8.11.7
1301
+
1302
+ --------------------
1303
+
1304
+
1262
1305
  ### deleteFile(...)
1263
1306
 
1264
1307
  ```typescript
@@ -1724,6 +1767,16 @@ Represents the detailed information of the currently active lens.
1724
1767
  | **`message`** | <code>string</code> | Native scanner error message. |
1725
1768
 
1726
1769
 
1770
+ #### CameraInterruptedEvent
1771
+
1772
+ Emitted when the native capture session is interrupted (for example during a phone call).
1773
+
1774
+ | Prop | Type | Description |
1775
+ | ------------------ | -------------------------------------------- | --------------------------------------------------------------------------------- |
1776
+ | **`reason`** | <code>'runtimeError' \| 'interrupted'</code> | Why the session was interrupted. |
1777
+ | **`audioDropped`** | <code>boolean</code> | When true, the plugin removed the microphone input and continued with video only. |
1778
+
1779
+
1727
1780
  #### SafeAreaInsets
1728
1781
 
1729
1782
  Represents safe area insets for devices.
@@ -68,7 +68,6 @@ dependencies {
68
68
  implementation "androidx.camera:camera-lifecycle:${camerax_version}"
69
69
  implementation "androidx.camera:camera-view:${camerax_version}"
70
70
  implementation "androidx.camera:camera-extensions:${camerax_version}"
71
- implementation 'com.google.guava:guava:33.7.2-android'
72
71
  implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1'
73
72
 
74
73
  testImplementation "junit:junit:$junitVersion"
@@ -1,6 +1,6 @@
1
1
  distributionBase=GRADLE_USER_HOME
2
2
  distributionPath=wrapper/dists
3
- distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-all.zip
3
+ distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip
4
4
  networkTimeout=10000
5
5
  validateDistributionUrl=true
6
6
  zipStoreBase=GRADLE_USER_HOME
@@ -135,10 +135,9 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
135
135
  static final String CAMERA_WITH_LOCATION_PERMISSION_ALIAS = "cameraWithLocation";
136
136
  static final String MICROPHONE_ONLY_PERMISSION_ALIAS = "microphoneOnly";
137
137
 
138
- private PluginCall pendingCaptureCall;
139
- private PluginCall pendingSampleCall;
140
- private PluginCall pendingCameraStartedCall;
141
- private PluginCall pendingStopRecordCall;
138
+ private String captureCallbackId = "";
139
+ private String sampleCallbackId = "";
140
+ private String cameraStartCallbackId = "";
142
141
  private final Object pendingStartLock = new Object();
143
142
  private PluginCall pendingStartCall;
144
143
  private int previousOrientationRequest = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -490,23 +489,21 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
490
489
  if (fusedLocationClient == null) {
491
490
  fusedLocationClient = LocationServices.getFusedLocationProviderClient(getContext());
492
491
  }
493
- fusedLocationClient
494
- .getLastLocation()
495
- .addOnCompleteListener((task) -> {
496
- if (task.isCanceled()) {
497
- Logger.warn("Location lookup cancelled. Capturing photo without location data.");
498
- proceedWithCapture(call, null);
499
- return;
500
- }
501
- if (task.isSuccessful()) {
502
- lastLocation = task.getResult();
503
- proceedWithCapture(call, lastLocation);
504
- return;
505
- }
506
- Exception error = task.getException();
507
- Logger.error("Failed to get location: " + (error != null ? error.getMessage() : "unknown error"));
492
+ fusedLocationClient.getLastLocation().addOnCompleteListener((task) -> {
493
+ if (task.isCanceled()) {
494
+ Logger.warn("Location lookup cancelled. Capturing photo without location data.");
508
495
  proceedWithCapture(call, null);
509
- });
496
+ return;
497
+ }
498
+ if (task.isSuccessful()) {
499
+ lastLocation = task.getResult();
500
+ proceedWithCapture(call, lastLocation);
501
+ return;
502
+ }
503
+ Exception error = task.getException();
504
+ Logger.error("Failed to get location: " + (error != null ? error.getMessage() : "unknown error"));
505
+ proceedWithCapture(call, null);
506
+ });
510
507
  }
511
508
 
512
509
  private void captureWithoutLocation(PluginCall call) {
@@ -514,8 +511,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
514
511
  }
515
512
 
516
513
  private void proceedWithCapture(PluginCall call, Location location) {
517
- call.setKeepAlive(true);
518
- pendingCaptureCall = call;
514
+ bridge.saveCall(call);
515
+ captureCallbackId = call.getCallbackId();
519
516
 
520
517
  Integer quality = Objects.requireNonNull(call.getInt("quality", 85));
521
518
  final boolean saveToGallery = Boolean.TRUE.equals(call.getBoolean("saveToGallery"));
@@ -534,8 +531,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
534
531
  call.reject("Camera is not running");
535
532
  return;
536
533
  }
537
- call.setKeepAlive(true);
538
- pendingSampleCall = call;
534
+ bridge.saveCall(call);
535
+ sampleCallbackId = call.getCallbackId();
539
536
  Integer quality = Objects.requireNonNull(call.getInt("quality", 85));
540
537
  final boolean mirrorFrontCamera = Boolean.TRUE.equals(call.getBoolean("mirrorFrontCamera"));
541
538
  cameraXView.captureSample(quality, mirrorFrontCamera);
@@ -809,39 +806,37 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
809
806
  public void stop(final PluginCall call) {
810
807
  boolean force = Boolean.TRUE.equals(call.getBoolean("force", false));
811
808
 
812
- bridge
813
- .getActivity()
814
- .runOnUiThread(() -> {
815
- getBridge().getActivity().setRequestedOrientation(previousOrientationRequest);
809
+ bridge.getActivity().runOnUiThread(() -> {
810
+ getBridge().getActivity().setRequestedOrientation(previousOrientationRequest);
816
811
 
817
- // Disable and clear orientation listener
818
- if (orientationListener != null) {
819
- orientationListener.disable();
820
- orientationListener = null;
821
- lastOrientation = Configuration.ORIENTATION_UNDEFINED;
822
- }
812
+ // Disable and clear orientation listener
813
+ if (orientationListener != null) {
814
+ orientationListener.disable();
815
+ orientationListener = null;
816
+ lastOrientation = Configuration.ORIENTATION_UNDEFINED;
817
+ }
823
818
 
824
- // Remove any rotation overlay if present
825
- if (rotationOverlay != null && rotationOverlay.getParent() != null) {
826
- ((ViewGroup) rotationOverlay.getParent()).removeView(rotationOverlay);
827
- rotationOverlay = null;
828
- }
819
+ // Remove any rotation overlay if present
820
+ if (rotationOverlay != null && rotationOverlay.getParent() != null) {
821
+ ((ViewGroup) rotationOverlay.getParent()).removeView(rotationOverlay);
822
+ rotationOverlay = null;
823
+ }
829
824
 
830
- if (cameraXView != null) {
831
- cameraXView.stopSession();
832
- // If force is true, always drop the reference
833
- // Otherwise only drop the reference if no deferred stop is pending
834
- if (force || !cameraXView.isStopDeferred()) {
835
- cameraXView = null;
836
- }
825
+ if (cameraXView != null) {
826
+ cameraXView.stopSession();
827
+ // If force is true, always drop the reference
828
+ // Otherwise only drop the reference if no deferred stop is pending
829
+ if (force || !cameraXView.isStopDeferred()) {
830
+ cameraXView = null;
837
831
  }
838
- // Manual stops should not trigger automatic resume with stale config
839
- lastSessionConfig = null;
840
- clearActiveBarcodeScanner();
841
- toBackVisualStateActive = false;
842
- restoreWebViewVisualState();
843
- call.resolve();
844
- });
832
+ }
833
+ // Manual stops should not trigger automatic resume with stale config
834
+ lastSessionConfig = null;
835
+ clearActiveBarcodeScanner();
836
+ toBackVisualStateActive = false;
837
+ restoreWebViewVisualState();
838
+ call.resolve();
839
+ });
845
840
  }
846
841
 
847
842
  @PluginMethod
@@ -1229,9 +1224,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
1229
1224
  result.put("microphone", mapPermissionState(audioState));
1230
1225
  }
1231
1226
 
1232
- boolean showSettingsAlert = call.getBoolean("showSettingsAlert") != null
1233
- ? Boolean.TRUE.equals(call.getBoolean("showSettingsAlert"))
1234
- : false;
1227
+ boolean showSettingsAlert =
1228
+ call.getBoolean("showSettingsAlert") != null ? Boolean.TRUE.equals(call.getBoolean("showSettingsAlert")) : false;
1235
1229
 
1236
1230
  String cameraStateString = result.getString("camera");
1237
1231
  boolean cameraNeedsSettings = "denied".equals(cameraStateString) || "prompt-with-rationale".equals(cameraStateString);
@@ -1279,12 +1273,10 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
1279
1273
  String originalDeviceId = call.getString("deviceId");
1280
1274
  String deviceId = originalDeviceId; // Use a mutable variable
1281
1275
 
1282
- final String position = (positionParam == null ||
1283
- positionParam.isEmpty() ||
1284
- "rear".equals(positionParam) ||
1285
- "back".equals(positionParam))
1286
- ? "back"
1287
- : "front";
1276
+ final String position =
1277
+ positionParam == null || positionParam.isEmpty() || "rear".equals(positionParam) || "back".equals(positionParam)
1278
+ ? "back"
1279
+ : "front";
1288
1280
  // Use -1 as default to indicate centering is needed when x/y not provided
1289
1281
  final Integer xParam = call.getInt("x");
1290
1282
  final Integer yParam = call.getInt("y");
@@ -1611,7 +1603,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
1611
1603
  Log.d("CameraPreview", "========================");
1612
1604
 
1613
1605
  // Pass along whether we're centering so CameraXView knows not to add insets
1614
- boolean isCentered = (x == -1 || y == -1);
1606
+ boolean isCentered = x == -1 || y == -1;
1615
1607
 
1616
1608
  CameraSessionConfiguration config = new CameraSessionConfiguration(
1617
1609
  finalDeviceId,
@@ -1643,8 +1635,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
1643
1635
  clearActiveBarcodeScanner();
1644
1636
  }
1645
1637
 
1646
- call.setKeepAlive(true);
1647
- pendingCameraStartedCall = call;
1638
+ bridge.saveCall(call);
1639
+ cameraStartCallbackId = call.getCallbackId();
1648
1640
  cameraXView.startSession(config);
1649
1641
 
1650
1642
  // Setup orientation listener to mirror iOS screenResize emission
@@ -1828,24 +1820,21 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
1828
1820
  if (rotationOverlay != null && rotationOverlay.getParent() != null) {
1829
1821
  // Shorter delay for faster transition
1830
1822
  int delay = "4:3".equals(ar) ? 200 : 150;
1831
- rotationOverlay.postDelayed(
1832
- () -> {
1833
- if (rotationOverlay != null && rotationOverlay.getParent() != null) {
1834
- rotationOverlay
1835
- .animate()
1836
- .alpha(0f)
1837
- .setDuration(100) // Faster fade out
1838
- .withEndAction(() -> {
1839
- if (rotationOverlay != null && rotationOverlay.getParent() != null) {
1840
- ((ViewGroup) rotationOverlay.getParent()).removeView(rotationOverlay);
1841
- rotationOverlay = null;
1842
- }
1843
- })
1844
- .start();
1845
- }
1846
- },
1847
- delay
1848
- );
1823
+ rotationOverlay.postDelayed(() -> {
1824
+ if (rotationOverlay != null && rotationOverlay.getParent() != null) {
1825
+ rotationOverlay
1826
+ .animate()
1827
+ .alpha(0f)
1828
+ .setDuration(100) // Faster fade out
1829
+ .withEndAction(() -> {
1830
+ if (rotationOverlay != null && rotationOverlay.getParent() != null) {
1831
+ ((ViewGroup) rotationOverlay.getParent()).removeView(rotationOverlay);
1832
+ rotationOverlay = null;
1833
+ }
1834
+ })
1835
+ .start();
1836
+ }
1837
+ }, delay);
1849
1838
  }
1850
1839
 
1851
1840
  Log.d(TAG, "================================================================================");
@@ -1900,7 +1889,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
1900
1889
 
1901
1890
  @Override
1902
1891
  public void onPictureTaken(String base64, JSONObject exif) {
1903
- PluginCall pluginCall = pendingCaptureCall;
1892
+ PluginCall pluginCall = bridge.getSavedCall(captureCallbackId);
1904
1893
  if (pluginCall == null) {
1905
1894
  Log.e("CameraPreview", "onPictureTaken: captureCallbackId is null");
1906
1895
  return;
@@ -1909,20 +1898,18 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
1909
1898
  result.put("value", base64);
1910
1899
  result.put("exif", exif);
1911
1900
  pluginCall.resolve(result);
1912
- pluginCall.setKeepAlive(false);
1913
- pendingCaptureCall = null;
1901
+ bridge.releaseCall(pluginCall);
1914
1902
  }
1915
1903
 
1916
1904
  @Override
1917
1905
  public void onPictureTakenError(String message) {
1918
- PluginCall pluginCall = pendingCaptureCall;
1906
+ PluginCall pluginCall = bridge.getSavedCall(captureCallbackId);
1919
1907
  if (pluginCall == null) {
1920
1908
  Log.e("CameraPreview", "onPictureTakenError: captureCallbackId is null");
1921
1909
  return;
1922
1910
  }
1923
1911
  pluginCall.reject(message);
1924
- pluginCall.setKeepAlive(false);
1925
- pendingCaptureCall = null;
1912
+ bridge.releaseCall(pluginCall);
1926
1913
  }
1927
1914
 
1928
1915
  @Override
@@ -2105,7 +2092,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
2105
2092
  // Re-apply WebView transparency after the preview is bound (idempotent safety net for resume).
2106
2093
  applyTransparentBackgroundsForToBack();
2107
2094
 
2108
- PluginCall call = pendingCameraStartedCall;
2095
+ PluginCall call = bridge.getSavedCall(cameraStartCallbackId);
2109
2096
  if (call != null) {
2110
2097
  // Convert pixel values back to logical units
2111
2098
  DisplayMetrics metrics = getBridge().getActivity().getResources().getDisplayMetrics();
@@ -2141,7 +2128,7 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
2141
2128
  Log.d("CameraPreview", "9. WEBVIEW INSET - " + webViewTopInset);
2142
2129
  Log.d(
2143
2130
  "CameraPreview",
2144
- "9b. SAFE AREA - " + (lastIncludeSafeAreaInsets ? ("ENABLED (inset=" + safeAreaTopInsetPx + ")") : "DISABLED")
2131
+ "9b. SAFE AREA - " + (lastIncludeSafeAreaInsets ? "ENABLED (inset=" + safeAreaTopInsetPx + ")" : "DISABLED")
2145
2132
  );
2146
2133
  Log.d(
2147
2134
  "CameraPreview",
@@ -2150,13 +2137,13 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
2150
2137
  Log.d(
2151
2138
  "CameraPreview",
2152
2139
  "11. RETURNED (logical) - x=" +
2153
- (x / pixelRatio) +
2140
+ x / pixelRatio +
2154
2141
  ", y=" +
2155
- (relativeY / pixelRatio) +
2142
+ relativeY / pixelRatio +
2156
2143
  ", width=" +
2157
- (width / pixelRatio) +
2144
+ width / pixelRatio +
2158
2145
  ", height=" +
2159
- (height / pixelRatio)
2146
+ height / pixelRatio
2160
2147
  );
2161
2148
  Log.d("CameraPreview", "12. PIXEL RATIO - " + pixelRatio);
2162
2149
  Log.d("CameraPreview", "========================");
@@ -2275,27 +2262,27 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
2275
2262
 
2276
2263
  private void resolveCameraStartCall(PluginCall call, JSObject result) {
2277
2264
  call.resolve(result);
2278
- call.setKeepAlive(false);
2279
- pendingCameraStartedCall = null; // Prevent re-use
2265
+ bridge.releaseCall(call);
2266
+ cameraStartCallbackId = null; // Prevent re-use
2280
2267
  resetPendingStartBarcodeScanner();
2281
2268
  }
2282
2269
 
2283
2270
  private void rejectCameraStartCall(PluginCall call, String message) {
2284
2271
  call.reject(message);
2285
- call.setKeepAlive(false);
2286
- pendingCameraStartedCall = null;
2272
+ bridge.releaseCall(call);
2273
+ cameraStartCallbackId = null;
2287
2274
  resetPendingStartBarcodeScanner();
2288
2275
  }
2289
2276
 
2290
2277
  @Override
2291
2278
  public void onSampleTaken(String result) {
2292
- PluginCall call = pendingSampleCall;
2279
+ PluginCall call = bridge.getSavedCall(sampleCallbackId);
2293
2280
  if (call != null) {
2294
2281
  JSObject ret = new JSObject();
2295
2282
  ret.put("value", result);
2296
2283
  call.resolve(ret);
2297
- call.setKeepAlive(false);
2298
- pendingSampleCall = null;
2284
+ bridge.releaseCall(call);
2285
+ sampleCallbackId = null;
2299
2286
  } else {
2300
2287
  Log.w("CameraPreview", "onSampleTaken: no pending call to resolve");
2301
2288
  }
@@ -2303,11 +2290,11 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
2303
2290
 
2304
2291
  @Override
2305
2292
  public void onSampleTakenError(String message) {
2306
- PluginCall call = pendingSampleCall;
2293
+ PluginCall call = bridge.getSavedCall(sampleCallbackId);
2307
2294
  if (call != null) {
2308
2295
  call.reject(message);
2309
- call.setKeepAlive(false);
2310
- pendingSampleCall = null;
2296
+ bridge.releaseCall(call);
2297
+ sampleCallbackId = null;
2311
2298
  } else {
2312
2299
  Log.e("CameraPreview", "Sample taken error (no pending call): " + message);
2313
2300
  }
@@ -2353,11 +2340,11 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
2353
2340
  }
2354
2341
  }
2355
2342
 
2356
- PluginCall call = pendingCameraStartedCall;
2343
+ PluginCall call = bridge.getSavedCall(cameraStartCallbackId);
2357
2344
  if (call != null) {
2358
2345
  call.reject(message);
2359
- call.setKeepAlive(false);
2360
- pendingCameraStartedCall = null;
2346
+ bridge.releaseCall(call);
2347
+ cameraStartCallbackId = null;
2361
2348
  resetPendingStartBarcodeScanner();
2362
2349
  }
2363
2350
 
@@ -2515,8 +2502,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
2515
2502
  final int safeAreaTopInsetPx = lastIncludeSafeAreaInsets ? getSafeAreaTopInsetPx() : 0;
2516
2503
  final float pixelRatioFinal = pixelRatio;
2517
2504
 
2518
- int x = (xParam != null && xParam > 0) ? (int) (xParam * pixelRatio) : 0;
2519
- int y = (yParam != null && yParam > 0) ? (int) (yParam * pixelRatio) : 0;
2505
+ int x = xParam != null && xParam > 0 ? (int) (xParam * pixelRatio) : 0;
2506
+ int y = yParam != null && yParam > 0 ? (int) (yParam * pixelRatio) : 0;
2520
2507
 
2521
2508
  // Add inset to Y for coordinate conversion if needed.
2522
2509
  // - If the WebView is already offset from the screen top, use that.
@@ -2526,8 +2513,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
2526
2513
  } else if (!isWebViewOffset && lastIncludeSafeAreaInsets && safeAreaTopInsetPx > 0 && y > 0) {
2527
2514
  y += safeAreaTopInsetPx;
2528
2515
  }
2529
- int width = (widthParam != null && widthParam > 0) ? (int) (widthParam * pixelRatio) : 0;
2530
- int height = (heightParam != null && heightParam > 0) ? (int) (heightParam * pixelRatio) : 0;
2516
+ int width = widthParam != null && widthParam > 0 ? (int) (widthParam * pixelRatio) : 0;
2517
+ int height = heightParam != null && heightParam > 0 ? (int) (heightParam * pixelRatio) : 0;
2531
2518
 
2532
2519
  cameraXView.setPreviewSize(x, y, width, height, () -> {
2533
2520
  // Return the actual preview bounds after layout operations are complete
@@ -2597,9 +2584,10 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
2597
2584
 
2598
2585
  @PluginMethod
2599
2586
  public void getSupportedVideoQualities(PluginCall call) {
2600
- List<String> qualities = cameraXView != null
2601
- ? cameraXView.getSupportedVideoQualities()
2602
- : Arrays.asList("low", "medium", "high", "2160p", "1080p", "720p", "480p", "4:3");
2587
+ List<String> qualities =
2588
+ cameraXView != null
2589
+ ? cameraXView.getSupportedVideoQualities()
2590
+ : Arrays.asList("low", "medium", "high", "2160p", "1080p", "720p", "480p", "4:3");
2603
2591
  JSONArray arr = new JSONArray();
2604
2592
  for (String quality : qualities) {
2605
2593
  arr.put(quality);
@@ -2717,9 +2705,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
2717
2705
  return;
2718
2706
  }
2719
2707
 
2720
- boolean disableAudio = call.getBoolean("disableAudio") != null
2721
- ? Boolean.TRUE.equals(call.getBoolean("disableAudio"))
2722
- : this.lastDisableAudio;
2708
+ boolean disableAudio =
2709
+ call.getBoolean("disableAudio") != null ? Boolean.TRUE.equals(call.getBoolean("disableAudio")) : this.lastDisableAudio;
2723
2710
  this.lastDisableAudio = disableAudio;
2724
2711
  String permissionAlias = disableAudio ? CAMERA_ONLY_PERMISSION_ALIAS : CAMERA_WITH_AUDIO_PERMISSION_ALIAS;
2725
2712
 
@@ -2738,30 +2725,28 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
2738
2725
  }
2739
2726
 
2740
2727
  try {
2741
- call.setKeepAlive(true);
2742
- pendingStopRecordCall = call;
2728
+ bridge.saveCall(call);
2729
+ final String cbId = call.getCallbackId();
2743
2730
  cameraXView.stopRecordVideo(
2744
2731
  new CameraXView.VideoRecordingCallback() {
2745
2732
  @Override
2746
2733
  public void onSuccess(String filePath, String reason) {
2747
- PluginCall saved = pendingStopRecordCall;
2748
- pendingStopRecordCall = null;
2734
+ PluginCall saved = bridge.getSavedCall(cbId);
2749
2735
  if (saved != null) {
2750
2736
  JSObject result = new JSObject();
2751
2737
  result.put("videoFilePath", filePath);
2752
2738
  result.put("reason", reason);
2753
2739
  saved.resolve(result);
2754
- saved.setKeepAlive(false);
2740
+ bridge.releaseCall(saved);
2755
2741
  }
2756
2742
  }
2757
2743
 
2758
2744
  @Override
2759
2745
  public void onError(String message) {
2760
- PluginCall saved = pendingStopRecordCall;
2761
- pendingStopRecordCall = null;
2746
+ PluginCall saved = bridge.getSavedCall(cbId);
2762
2747
  if (saved != null) {
2763
2748
  saved.reject("Failed to stop video recording: " + message);
2764
- saved.setKeepAlive(false);
2749
+ bridge.releaseCall(saved);
2765
2750
  }
2766
2751
  }
2767
2752
  }