@capgo/camera-preview 7.3.9 → 7.4.0-beta.2

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.
Files changed (56) hide show
  1. package/CapgoCameraPreview.podspec +16 -13
  2. package/README.md +306 -70
  3. package/android/.gradle/8.14.2/checksums/checksums.lock +0 -0
  4. package/android/.gradle/8.14.2/checksums/sha1-checksums.bin +0 -0
  5. package/android/.gradle/8.14.2/executionHistory/executionHistory.bin +0 -0
  6. package/android/.gradle/8.14.2/executionHistory/executionHistory.lock +0 -0
  7. package/android/.gradle/8.14.2/fileChanges/last-build.bin +0 -0
  8. package/android/.gradle/8.14.2/fileHashes/fileHashes.bin +0 -0
  9. package/android/.gradle/8.14.2/fileHashes/fileHashes.lock +0 -0
  10. package/android/.gradle/8.14.2/fileHashes/resourceHashesCache.bin +0 -0
  11. package/android/.gradle/8.14.2/gc.properties +0 -0
  12. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  13. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  14. package/android/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
  15. package/android/.gradle/file-system.probe +0 -0
  16. package/android/.gradle/vcs-1/gc.properties +0 -0
  17. package/android/build.gradle +9 -0
  18. package/android/src/main/AndroidManifest.xml +5 -0
  19. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +260 -551
  20. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +968 -0
  21. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraDevice.java +54 -0
  22. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraLens.java +70 -0
  23. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +65 -0
  24. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/LensInfo.java +34 -0
  25. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/ZoomFactors.java +34 -0
  26. package/dist/docs.json +729 -153
  27. package/dist/esm/definitions.d.ts +337 -80
  28. package/dist/esm/definitions.js +10 -1
  29. package/dist/esm/definitions.js.map +1 -1
  30. package/dist/esm/web.d.ts +27 -1
  31. package/dist/esm/web.js +248 -4
  32. package/dist/esm/web.js.map +1 -1
  33. package/dist/plugin.cjs.js +256 -4
  34. package/dist/plugin.cjs.js.map +1 -1
  35. package/dist/plugin.js +256 -4
  36. package/dist/plugin.js.map +1 -1
  37. package/ios/{Plugin → Sources/CapgoCameraPreview}/CameraController.swift +359 -34
  38. package/ios/{Plugin → Sources/CapgoCameraPreview}/Plugin.swift +348 -42
  39. package/ios/Tests/CameraPreviewPluginTests/CameraPreviewPluginTests.swift +15 -0
  40. package/package.json +1 -1
  41. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraActivity.java +0 -1279
  42. package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomSurfaceView.java +0 -29
  43. package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomTextureView.java +0 -39
  44. package/android/src/main/java/com/ahm/capacitor/camera/preview/Preview.java +0 -461
  45. package/android/src/main/java/com/ahm/capacitor/camera/preview/TapGestureDetector.java +0 -24
  46. package/ios/Plugin/Info.plist +0 -24
  47. package/ios/Plugin/Plugin.h +0 -10
  48. package/ios/Plugin/Plugin.m +0 -18
  49. package/ios/Plugin.xcodeproj/project.pbxproj +0 -593
  50. package/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  51. package/ios/Plugin.xcworkspace/contents.xcworkspacedata +0 -10
  52. package/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  53. package/ios/PluginTests/Info.plist +0 -22
  54. package/ios/PluginTests/PluginTests.swift +0 -83
  55. package/ios/Podfile +0 -13
  56. package/ios/Podfile.lock +0 -23
@@ -3,30 +3,11 @@ package com.ahm.capacitor.camera.preview;
3
3
  import static android.Manifest.permission.CAMERA;
4
4
  import static android.Manifest.permission.RECORD_AUDIO;
5
5
 
6
- import android.app.FragmentManager;
7
- import android.app.FragmentTransaction;
8
- import android.content.Context;
9
6
  import android.content.pm.ActivityInfo;
10
- import android.graphics.Color;
11
- import android.graphics.ImageFormat;
12
- import android.graphics.Point;
13
- import android.hardware.Camera;
14
- import android.hardware.camera2.CameraAccessException;
15
- import android.hardware.camera2.CameraCharacteristics;
16
- import android.hardware.camera2.CameraManager;
17
- import android.hardware.camera2.params.StreamConfigurationMap;
18
7
  import android.util.DisplayMetrics;
19
- import android.util.Size;
20
8
  import android.util.TypedValue;
21
- import android.view.Display;
22
- import android.view.MotionEvent;
23
- import android.view.View;
24
- import android.view.ViewGroup;
25
- import android.widget.FrameLayout;
26
- import androidx.annotation.NonNull;
27
9
  import com.getcapacitor.JSArray;
28
10
  import com.getcapacitor.JSObject;
29
- import com.getcapacitor.Logger;
30
11
  import com.getcapacitor.PermissionState;
31
12
  import com.getcapacitor.Plugin;
32
13
  import com.getcapacitor.PluginCall;
@@ -34,11 +15,16 @@ import com.getcapacitor.PluginMethod;
34
15
  import com.getcapacitor.annotation.CapacitorPlugin;
35
16
  import com.getcapacitor.annotation.Permission;
36
17
  import com.getcapacitor.annotation.PermissionCallback;
37
- import java.io.File;
38
- import java.util.ArrayList;
18
+ import com.ahm.capacitor.camera.preview.model.CameraDevice;
19
+ import com.ahm.capacitor.camera.preview.model.CameraSessionConfiguration;
20
+ import com.ahm.capacitor.camera.preview.model.ZoomFactors;
39
21
  import java.util.List;
40
22
  import java.util.Objects;
41
- import org.json.JSONArray;
23
+ import android.util.Size;
24
+ import android.util.Log;
25
+ import com.ahm.capacitor.camera.preview.model.LensInfo;
26
+
27
+ import org.json.JSONObject;
42
28
 
43
29
  @CapacitorPlugin(
44
30
  name = "CameraPreview",
@@ -55,29 +41,20 @@ import org.json.JSONArray;
55
41
  )
56
42
  public class CameraPreview
57
43
  extends Plugin
58
- implements CameraActivity.CameraPreviewListener {
44
+ implements CameraXView.CameraXViewListener {
59
45
 
60
46
  static final String CAMERA_WITH_AUDIO_PERMISSION_ALIAS = "cameraWithAudio";
61
47
  static final String CAMERA_ONLY_PERMISSION_ALIAS = "cameraOnly";
62
48
 
63
- private static String VIDEO_FILE_PATH = "";
64
- private static final String VIDEO_FILE_EXTENSION = ".mp4";
65
-
66
49
  private String captureCallbackId = "";
67
50
  private String snapshotCallbackId = "";
68
- private String recordCallbackId = "";
69
51
  private String cameraStartCallbackId = "";
70
-
71
- // keep track of previously specified orientation to support locking orientation:
72
- private int previousOrientationRequest =
73
- ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
74
-
75
- private CameraActivity fragment;
76
- private final int containerViewId = 20;
52
+ private int previousOrientationRequest = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
53
+ private CameraXView cameraXView;
77
54
 
78
55
  @PluginMethod
79
56
  public void start(PluginCall call) {
80
- boolean disableAudio = call.getBoolean("disableAudio", false);
57
+ boolean disableAudio = Boolean.TRUE.equals(call.getBoolean("disableAudio", true));
81
58
  String permissionAlias = disableAudio
82
59
  ? CAMERA_ONLY_PERMISSION_ALIAS
83
60
  : CAMERA_WITH_AUDIO_PERMISSION_ALIAS;
@@ -95,118 +72,39 @@ public class CameraPreview
95
72
 
96
73
  @PluginMethod
97
74
  public void flip(PluginCall call) {
98
- try {
99
- fragment.switchCamera();
100
- call.resolve();
101
- } catch (Exception e) {
102
- Logger.debug(getLogTag(), "Camera flip exception: " + e);
103
- call.reject("failed to flip camera");
104
- }
105
- }
106
-
107
- @PluginMethod
108
- public void setOpacity(PluginCall call) {
109
- if (!this.hasCamera(call)) {
75
+ if (cameraXView == null || !cameraXView.isRunning()) {
110
76
  call.reject("Camera is not running");
111
77
  return;
112
78
  }
113
-
114
- bridge.saveCall(call);
115
- Float opacity = Objects.requireNonNull(call.getFloat("opacity", 1F));
116
- fragment.setOpacity(opacity);
79
+ cameraXView.flipCamera();
80
+ call.resolve();
117
81
  }
118
82
 
119
83
  @PluginMethod
120
- public void capture(PluginCall call) {
121
- if (!this.hasCamera(call)) {
84
+ public void capture(final PluginCall call) {
85
+ if (cameraXView == null || !cameraXView.isRunning()) {
122
86
  call.reject("Camera is not running");
123
87
  return;
124
88
  }
89
+
125
90
  bridge.saveCall(call);
126
91
  captureCallbackId = call.getCallbackId();
127
92
 
128
93
  Integer quality = Objects.requireNonNull(call.getInt("quality", 85));
129
- // Image Dimensions - Optional
130
- Integer width = Objects.requireNonNull(call.getInt("width", 0));
131
- Integer height = Objects.requireNonNull(call.getInt("height", 0));
132
- fragment.takePicture(width, height, quality);
94
+ final boolean saveToGallery = Boolean.TRUE.equals(call.getBoolean("saveToGallery", false));
95
+ cameraXView.capturePhoto(quality, saveToGallery);
133
96
  }
134
97
 
135
98
  @PluginMethod
136
99
  public void captureSample(PluginCall call) {
137
- if (!this.hasCamera(call)) {
100
+ if (cameraXView == null || !cameraXView.isRunning()) {
138
101
  call.reject("Camera is not running");
139
102
  return;
140
103
  }
141
104
  bridge.saveCall(call);
142
105
  snapshotCallbackId = call.getCallbackId();
143
-
144
106
  Integer quality = Objects.requireNonNull(call.getInt("quality", 85));
145
- fragment.takeSnapshot(quality);
146
- }
147
-
148
- @PluginMethod
149
- public void getSupportedPictureSizes(final PluginCall call) {
150
- CameraManager cameraManager = (CameraManager) this.getContext()
151
- .getSystemService(Context.CAMERA_SERVICE);
152
-
153
- JSArray ret = new JSArray();
154
- try {
155
- String[] cameraIdList = cameraManager.getCameraIdList();
156
- for (String cameraId : cameraIdList) {
157
- CameraCharacteristics characteristics =
158
- cameraManager.getCameraCharacteristics(cameraId);
159
-
160
- // Determine the facing of the camera
161
- Integer lensFacing = characteristics.get(
162
- CameraCharacteristics.LENS_FACING
163
- );
164
- String facing = "Unknown";
165
- if (lensFacing != null) {
166
- switch (lensFacing) {
167
- case CameraCharacteristics.LENS_FACING_FRONT:
168
- facing = "Front";
169
- break;
170
- case CameraCharacteristics.LENS_FACING_BACK:
171
- facing = "Back";
172
- break;
173
- case CameraCharacteristics.LENS_FACING_EXTERNAL:
174
- facing = "External";
175
- break;
176
- }
177
- }
178
-
179
- StreamConfigurationMap map = characteristics.get(
180
- CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP
181
- );
182
- if (map == null) {
183
- continue;
184
- }
185
-
186
- Size[] jpegSizes = map.getOutputSizes(ImageFormat.JPEG);
187
- JSObject camera = new JSObject();
188
- camera.put("facing", facing);
189
- JSArray supportedPictureSizes = new JSArray();
190
- if (jpegSizes != null) {
191
- for (Size size : jpegSizes) {
192
- JSObject sizeJson = new JSObject();
193
- sizeJson.put("width", size.getWidth());
194
- sizeJson.put("height", size.getHeight());
195
- supportedPictureSizes.put(sizeJson);
196
- }
197
- camera.put("supportedPictureSizes", supportedPictureSizes);
198
- ret.put(camera);
199
- }
200
- }
201
- JSObject finalRet = new JSObject();
202
- finalRet.put("supportedPictureSizes", ret);
203
- call.resolve(finalRet);
204
- } catch (CameraAccessException ex) {
205
- Logger.error(getLogTag(), "Cannot call getSupportedPictureSizes", ex);
206
- call.reject(
207
- String.format("Cannot call getSupportedPictureSizes. Error: %s", ex)
208
- );
209
- }
107
+ cameraXView.captureSample(quality);
210
108
  }
211
109
 
212
110
  @PluginMethod
@@ -214,190 +112,204 @@ public class CameraPreview
214
112
  bridge
215
113
  .getActivity()
216
114
  .runOnUiThread(
217
- new Runnable() {
218
- @Override
219
- public void run() {
220
- FrameLayout containerView = getBridge()
221
- .getActivity()
222
- .findViewById(containerViewId);
223
-
224
- // allow orientation changes after closing camera:
225
- getBridge()
226
- .getActivity()
227
- .setRequestedOrientation(previousOrientationRequest);
228
-
229
- if (containerView != null) {
230
- ((ViewGroup) getBridge().getWebView().getParent()).removeView(
231
- containerView
232
- );
233
- getBridge().getWebView().setBackgroundColor(Color.WHITE);
234
- FragmentManager fragmentManager = getActivity()
235
- .getFragmentManager();
236
- FragmentTransaction fragmentTransaction =
237
- fragmentManager.beginTransaction();
238
- fragmentTransaction.remove(fragment);
239
- fragmentTransaction.commit();
240
- fragment = null;
241
-
242
- call.resolve();
243
- } else {
244
- call.reject("camera already stopped");
245
- }
115
+ () -> {
116
+ getBridge()
117
+ .getActivity()
118
+ .setRequestedOrientation(previousOrientationRequest);
119
+
120
+ if (cameraXView != null && cameraXView.isRunning()) {
121
+ cameraXView.stopSession();
122
+ cameraXView = null;
246
123
  }
124
+ call.resolve();
247
125
  }
248
126
  );
249
127
  }
250
128
 
251
129
  @PluginMethod
252
130
  public void getSupportedFlashModes(PluginCall call) {
253
- if (!this.hasCamera(call)) {
254
- call.reject("Camera is not running");
255
- return;
256
- }
257
-
258
- Camera camera = fragment.getCamera();
259
- Camera.Parameters params = camera.getParameters();
260
- List<String> supportedFlashModes;
261
- supportedFlashModes = params.getSupportedFlashModes();
262
- JSONArray jsonFlashModes = new JSONArray();
263
-
264
- if (supportedFlashModes != null) {
265
- for (int i = 0; i < supportedFlashModes.size(); i++) {
266
- jsonFlashModes.put(supportedFlashModes.get(i));
267
- }
131
+ List<String> supportedFlashModes = cameraXView.getSupportedFlashModes();
132
+ JSArray jsonFlashModes = new JSArray();
133
+ for (String mode : supportedFlashModes) {
134
+ jsonFlashModes.put(mode);
268
135
  }
269
-
270
136
  JSObject jsObject = new JSObject();
271
137
  jsObject.put("result", jsonFlashModes);
272
138
  call.resolve(jsObject);
273
139
  }
274
140
 
275
141
  @PluginMethod
276
- public void getHorizontalFov(PluginCall call) {
277
- if (!this.hasCamera(call)) {
278
- call.reject("Camera is not running");
142
+ public void setFlashMode(PluginCall call) {
143
+ String flashMode = call.getString("flashMode");
144
+ if (flashMode == null || flashMode.isEmpty()) {
145
+ call.reject("flashMode required parameter is missing");
279
146
  return;
280
147
  }
148
+ cameraXView.setFlashMode(flashMode);
149
+ call.resolve();
150
+ }
281
151
 
282
- Camera camera = fragment.getCamera();
283
- Camera.Parameters params = camera.getParameters();
284
-
285
- float horizontalViewAngle = params.getHorizontalViewAngle();
152
+ @PluginMethod
153
+ public void getAvailableDevices(PluginCall call) {
154
+ List<CameraDevice> devices = CameraXView.getAvailableDevicesStatic(getContext());
155
+ JSArray devicesArray = new JSArray();
156
+ for (CameraDevice device : devices) {
157
+ JSObject deviceJson = new JSObject();
158
+ deviceJson.put("deviceId", device.getDeviceId());
159
+ deviceJson.put("label", device.getLabel());
160
+ deviceJson.put("position", device.getPosition());
161
+ JSArray lensesArray = new JSArray();
162
+ for (com.ahm.capacitor.camera.preview.model.LensInfo lens : device.getLenses()) {
163
+ JSObject lensJson = new JSObject();
164
+ lensJson.put("focalLength", lens.getFocalLength());
165
+ lensJson.put("deviceType", lens.getDeviceType());
166
+ lensJson.put("baseZoomRatio", lens.getBaseZoomRatio());
167
+ lensJson.put("digitalZoom", lens.getDigitalZoom());
168
+ lensesArray.put(lensJson);
169
+ }
170
+ deviceJson.put("lenses", lensesArray);
171
+ deviceJson.put("minZoom", device.getMinZoom());
172
+ deviceJson.put("maxZoom", device.getMaxZoom());
173
+ devicesArray.put(deviceJson);
174
+ }
175
+ JSObject result = new JSObject();
176
+ result.put("devices", devicesArray);
177
+ call.resolve(result);
178
+ }
286
179
 
287
- JSObject jsObject = new JSObject();
288
- jsObject.put("result", horizontalViewAngle);
289
- call.resolve(jsObject);
180
+ @PluginMethod
181
+ public void getZoom(PluginCall call) {
182
+ ZoomFactors zoomFactors = cameraXView.getZoomFactors();
183
+ JSObject result = new JSObject();
184
+ result.put("min", zoomFactors.getMin());
185
+ result.put("max", zoomFactors.getMax());
186
+ result.put("current", zoomFactors.getCurrent());
187
+ call.resolve(result);
290
188
  }
291
189
 
292
190
  @PluginMethod
293
- public void setFlashMode(PluginCall call) {
294
- if (!this.hasCamera(call)) {
191
+ public void setZoom(PluginCall call) {
192
+ if (cameraXView == null || !cameraXView.isRunning()) {
295
193
  call.reject("Camera is not running");
296
194
  return;
297
195
  }
298
-
299
- String flashMode = call.getString("flashMode");
300
- if (flashMode == null || flashMode.isEmpty()) {
301
- call.reject("flashMode required parameter is missing");
196
+ Float level = call.getFloat("level");
197
+ if (level == null) {
198
+ call.reject("level parameter is required");
302
199
  return;
303
200
  }
201
+ try {
202
+ cameraXView.setZoom(level);
203
+ call.resolve();
204
+ } catch (Exception e) {
205
+ call.reject("Failed to set zoom: " + e.getMessage());
206
+ }
207
+ }
304
208
 
305
- Camera camera = fragment.getCamera();
306
- Camera.Parameters params = camera.getParameters();
307
-
308
- List<String> supportedFlashModes;
309
- supportedFlashModes = camera.getParameters().getSupportedFlashModes();
310
- if (
311
- supportedFlashModes != null && supportedFlashModes.contains(flashMode)
312
- ) {
313
- params.setFlashMode(flashMode);
314
- } else {
315
- call.reject("Flash mode not recognised: " + flashMode);
209
+ @PluginMethod
210
+ public void setDeviceId(PluginCall call) {
211
+ String deviceId = call.getString("deviceId");
212
+ if (deviceId == null || deviceId.isEmpty()) {
213
+ call.reject("deviceId parameter is required");
316
214
  return;
317
215
  }
318
-
319
- fragment.setCameraParameters(params);
320
-
216
+ if (cameraXView == null || !cameraXView.isRunning()) {
217
+ call.reject("Camera is not running");
218
+ return;
219
+ }
220
+ cameraXView.switchToDevice(deviceId);
321
221
  call.resolve();
322
222
  }
323
223
 
324
224
  @PluginMethod
325
- public void startRecordVideo(final PluginCall call) {
326
- if (!this.hasCamera(call)) {
225
+ public void getSupportedPictureSizes(final PluginCall call) {
226
+ JSArray supportedPictureSizesResult = new JSArray();
227
+ List<Size> rearSizes = CameraXView.getSupportedPictureSizes("rear");
228
+ JSObject rear = new JSObject();
229
+ rear.put("facing", "rear");
230
+ JSArray rearSizesJs = new JSArray();
231
+ for(Size size : rearSizes) {
232
+ JSObject sizeJs = new JSObject();
233
+ sizeJs.put("width", size.getWidth());
234
+ sizeJs.put("height", size.getHeight());
235
+ rearSizesJs.put(sizeJs);
236
+ }
237
+ rear.put("supportedPictureSizes", rearSizesJs);
238
+ supportedPictureSizesResult.put(rear);
239
+
240
+ List<Size> frontSizes = CameraXView.getSupportedPictureSizes("front");
241
+ JSObject front = new JSObject();
242
+ front.put("facing", "front");
243
+ JSArray frontSizesJs = new JSArray();
244
+ for(Size size : frontSizes) {
245
+ JSObject sizeJs = new JSObject();
246
+ sizeJs.put("width", size.getWidth());
247
+ sizeJs.put("height", size.getHeight());
248
+ frontSizesJs.put(sizeJs);
249
+ }
250
+ front.put("supportedPictureSizes", frontSizesJs);
251
+ supportedPictureSizesResult.put(front);
252
+
253
+ JSObject ret = new JSObject();
254
+ ret.put("supportedPictureSizes", supportedPictureSizesResult);
255
+ call.resolve(ret);
256
+ }
257
+
258
+ @PluginMethod
259
+ public void setOpacity(PluginCall call) {
260
+ if (cameraXView == null || !cameraXView.isRunning()) {
327
261
  call.reject("Camera is not running");
328
262
  return;
329
263
  }
330
- final String filename = "videoTmp";
331
- VIDEO_FILE_PATH = getActivity().getCacheDir().toString() + "/";
332
-
333
- final String position = Objects.requireNonNull(
334
- call.getString("position", "front")
335
- );
336
- final Integer width = Objects.requireNonNull(call.getInt("width", 0));
337
- final Integer height = Objects.requireNonNull(call.getInt("height", 0));
338
- final Boolean withFlash = Objects.requireNonNull(
339
- call.getBoolean("withFlash", false)
340
- );
341
- final Integer maxDuration = Objects.requireNonNull(
342
- call.getInt("maxDuration", 0)
343
- );
344
- // final Integer quality = Objects.requireNonNull(call.getInt("quality", 0));
345
- bridge.saveCall(call);
346
- recordCallbackId = call.getCallbackId();
347
-
348
- bridge
349
- .getActivity()
350
- .runOnUiThread(
351
- new Runnable() {
352
- @Override
353
- public void run() {
354
- fragment.startRecord(
355
- getFilePath(),
356
- position,
357
- width,
358
- height,
359
- 70,
360
- withFlash,
361
- maxDuration
362
- );
363
- }
364
- }
365
- );
366
-
264
+ Float opacity = call.getFloat("opacity", 1.0f);
265
+ cameraXView.setOpacity(opacity);
367
266
  call.resolve();
368
267
  }
369
268
 
370
269
  @PluginMethod
371
- public void stopRecordVideo(PluginCall call) {
372
- if (!this.hasCamera(call)) {
270
+ public void getHorizontalFov(PluginCall call) {
271
+ // CameraX does not provide a simple way to get FoV.
272
+ // This would require Camera2 interop to access camera characteristics.
273
+ // Returning a default/estimated value.
274
+ JSObject ret = new JSObject();
275
+ ret.put("result", 60.0); // A common default FoV
276
+ call.resolve(ret);
277
+ }
278
+
279
+ @PluginMethod
280
+ public void getDeviceId(PluginCall call) {
281
+ if (cameraXView == null || !cameraXView.isRunning()) {
373
282
  call.reject("Camera is not running");
374
283
  return;
375
284
  }
285
+ JSObject ret = new JSObject();
286
+ ret.put("deviceId", cameraXView.getCurrentDeviceId());
287
+ call.resolve(ret);
288
+ }
376
289
 
377
- System.out.println("stopRecordVideo - Callbackid=" + call.getCallbackId());
378
-
379
- bridge.saveCall(call);
380
- recordCallbackId = call.getCallbackId();
381
-
382
- // bridge.getActivity().runOnUiThread(new Runnable() {
383
- // @Override
384
- // public void run() {
385
- // fragment.stopRecord();
386
- // }
387
- // });
290
+ @PluginMethod
291
+ public void getFlashMode(PluginCall call) {
292
+ if (cameraXView == null || !cameraXView.isRunning()) {
293
+ call.reject("Camera is not running");
294
+ return;
295
+ }
296
+ JSObject ret = new JSObject();
297
+ ret.put("flashMode", cameraXView.getFlashMode());
298
+ call.resolve(ret);
299
+ }
388
300
 
389
- fragment.stopRecord();
390
- // call.resolve();
301
+ @PluginMethod
302
+ public void isRunning(PluginCall call) {
303
+ boolean running = cameraXView != null && cameraXView.isRunning();
304
+ JSObject jsObject = new JSObject();
305
+ jsObject.put("isRunning", running);
306
+ call.resolve(jsObject);
391
307
  }
392
308
 
393
309
  @PermissionCallback
394
310
  private void handleCameraPermissionResult(PluginCall call) {
395
- boolean disableAudio = call.getBoolean("disableAudio", false);
396
- String permissionAlias = disableAudio
397
- ? CAMERA_ONLY_PERMISSION_ALIAS
398
- : CAMERA_WITH_AUDIO_PERMISSION_ALIAS;
399
-
400
- if (PermissionState.GRANTED.equals(getPermissionState(permissionAlias))) {
311
+ if (PermissionState.GRANTED.equals(getPermissionState(CAMERA_ONLY_PERMISSION_ALIAS)) ||
312
+ PermissionState.GRANTED.equals(getPermissionState(CAMERA_WITH_AUDIO_PERMISSION_ALIAS))) {
401
313
  startCamera(call);
402
314
  } else {
403
315
  call.reject("Permission failed");
@@ -405,327 +317,124 @@ public class CameraPreview
405
317
  }
406
318
 
407
319
  private void startCamera(final PluginCall call) {
408
- String position = call.getString("position");
409
-
410
- if (position == null || position.isEmpty() || "rear".equals(position)) {
411
- position = "back";
412
- } else {
413
- position = "front";
414
- }
415
-
416
- @NonNull
417
- final Integer x = Objects.requireNonNull(call.getInt("x", 0));
418
- @NonNull
419
- final Integer y = Objects.requireNonNull(call.getInt("y", 0));
420
- @NonNull
421
- final Integer width = Objects.requireNonNull(call.getInt("width", 0));
422
- @NonNull
423
- final Integer height = Objects.requireNonNull(call.getInt("height", 0));
424
- @NonNull
425
- final Integer paddingBottom = Objects.requireNonNull(
426
- call.getInt("paddingBottom", 0)
427
- );
428
- final Boolean toBack = Objects.requireNonNull(
429
- call.getBoolean("toBack", false)
430
- );
431
- final Boolean storeToFile = Objects.requireNonNull(
432
- call.getBoolean("storeToFile", false)
433
- );
434
- final Boolean enableOpacity = Objects.requireNonNull(
435
- call.getBoolean("enableOpacity", false)
436
- );
437
- final Boolean enableZoom = Objects.requireNonNull(
438
- call.getBoolean("enableZoom", false)
439
- );
440
- final Boolean disableExifHeaderStripping = Objects.requireNonNull(
441
- call.getBoolean("disableExifHeaderStripping", false)
442
- );
443
- final Boolean lockOrientation = Objects.requireNonNull(
444
- call.getBoolean("lockAndroidOrientation", false)
445
- );
446
- previousOrientationRequest = getBridge()
447
- .getActivity()
448
- .getRequestedOrientation();
449
-
450
- fragment = new CameraActivity();
451
- fragment.setEventListener(this);
452
- fragment.defaultCamera = position;
453
- fragment.tapToTakePicture = false;
454
- fragment.dragEnabled = false;
455
- fragment.tapToFocus = true;
456
- fragment.disableExifHeaderStripping = disableExifHeaderStripping;
457
- fragment.storeToFile = storeToFile;
458
- fragment.toBack = toBack;
459
- fragment.enableOpacity = enableOpacity;
460
- fragment.enableZoom = enableZoom;
461
- fragment.disableAudio = call.getBoolean("disableAudio", false);
462
-
463
- bridge
464
- .getActivity()
465
- .runOnUiThread(
466
- new Runnable() {
467
- @Override
468
- public void run() {
469
- DisplayMetrics metrics = getBridge()
470
- .getActivity()
471
- .getResources()
472
- .getDisplayMetrics();
473
- // lock orientation if specified in options:
474
- if (lockOrientation) {
475
- getBridge()
476
- .getActivity()
477
- .setRequestedOrientation(
478
- ActivityInfo.SCREEN_ORIENTATION_LOCKED
479
- );
480
- }
481
-
482
- // offset
483
- int computedX = (int) TypedValue.applyDimension(
484
- TypedValue.COMPLEX_UNIT_DIP,
485
- x,
486
- metrics
487
- );
488
- int computedY = (int) TypedValue.applyDimension(
489
- TypedValue.COMPLEX_UNIT_DIP,
490
- y,
491
- metrics
492
- );
493
-
494
- // size
495
- int computedWidth;
496
- int computedHeight;
497
- int computedPaddingBottom;
498
-
499
- if (paddingBottom != 0) {
500
- computedPaddingBottom = (int) TypedValue.applyDimension(
501
- TypedValue.COMPLEX_UNIT_DIP,
502
- paddingBottom,
503
- metrics
504
- );
505
- } else {
506
- computedPaddingBottom = 0;
507
- }
508
-
509
- if (width != 0) {
510
- computedWidth = (int) TypedValue.applyDimension(
511
- TypedValue.COMPLEX_UNIT_DIP,
512
- width,
513
- metrics
514
- );
515
- } else {
516
- Display defaultDisplay = getBridge()
517
- .getActivity()
518
- .getWindowManager()
519
- .getDefaultDisplay();
520
- final Point size = new Point();
521
- defaultDisplay.getSize(size);
522
-
523
- computedWidth = (int) TypedValue.applyDimension(
524
- TypedValue.COMPLEX_UNIT_PX,
525
- size.x,
526
- metrics
527
- );
320
+ String positionParam = call.getString("position");
321
+ String originalDeviceId = call.getString("deviceId");
322
+ String deviceId = originalDeviceId; // Use a mutable variable
323
+
324
+ final String position = (positionParam == null || positionParam.isEmpty() || "rear".equals(positionParam) || "back".equals(positionParam)) ? "back" : "front";
325
+ final int x = call.getInt("x", 0);
326
+ final int y = call.getInt("y", 0);
327
+ final int width = call.getInt("width", 0);
328
+ final int height = call.getInt("height", 0);
329
+ final int paddingBottom = call.getInt("paddingBottom", 0);
330
+ final boolean toBack = Boolean.TRUE.equals(call.getBoolean("toBack", true));
331
+ final boolean storeToFile = Boolean.TRUE.equals(call.getBoolean("storeToFile", false));
332
+ final boolean enableOpacity = Boolean.TRUE.equals(call.getBoolean("enableOpacity", false));
333
+ final boolean enableZoom = Boolean.TRUE.equals(call.getBoolean("enableZoom", false));
334
+ final boolean disableExifHeaderStripping = Boolean.TRUE.equals(call.getBoolean("disableExifHeaderStripping", false));
335
+ final boolean lockOrientation = Boolean.TRUE.equals(call.getBoolean("lockAndroidOrientation", false));
336
+ final boolean disableAudio = Boolean.TRUE.equals(call.getBoolean("disableAudio", true));
337
+
338
+ float targetZoom = 1.0f;
339
+ // Check if the selected device is a physical ultra-wide
340
+ if (originalDeviceId != null) {
341
+ List<CameraDevice> devices = CameraXView.getAvailableDevicesStatic(getContext());
342
+ for (CameraDevice device : devices) {
343
+ if (originalDeviceId.equals(device.getDeviceId()) && !device.isLogical()) {
344
+ for (LensInfo lens : device.getLenses()) {
345
+ if ("ultraWide".equals(lens.getDeviceType())) {
346
+ Log.d("CameraPreview", "Ultra-wide lens selected. Targeting 0.5x zoom on logical camera.");
347
+ targetZoom = 0.5f;
348
+ // Force the use of the logical camera by clearing the specific deviceId
349
+ deviceId = null;
350
+ break;
351
+ }
352
+ }
528
353
  }
354
+ if (deviceId == null) break; // Exit outer loop once we've made our decision
355
+ }
356
+ }
529
357
 
530
- if (height != 0) {
531
- computedHeight =
532
- (int) TypedValue.applyDimension(
533
- TypedValue.COMPLEX_UNIT_DIP,
534
- height,
535
- metrics
536
- ) -
537
- computedPaddingBottom;
538
- } else {
539
- Display defaultDisplay = getBridge()
540
- .getActivity()
541
- .getWindowManager()
542
- .getDefaultDisplay();
543
- final Point size = new Point();
544
- defaultDisplay.getSize(size);
545
-
546
- computedHeight =
547
- (int) TypedValue.applyDimension(
548
- TypedValue.COMPLEX_UNIT_PX,
549
- size.y,
550
- metrics
551
- ) -
552
- computedPaddingBottom;
553
- }
358
+ previousOrientationRequest = getBridge().getActivity().getRequestedOrientation();
359
+ cameraXView = new CameraXView(getContext(), getBridge().getWebView());
360
+ cameraXView.setListener(this);
554
361
 
555
- fragment.setRect(
556
- computedX,
557
- computedY,
558
- computedWidth,
559
- computedHeight
560
- );
561
-
562
- FrameLayout containerView = getBridge()
563
- .getActivity()
564
- .findViewById(containerViewId);
565
- if (containerView == null) {
566
- containerView = new FrameLayout(
567
- getActivity().getApplicationContext()
568
- );
569
- containerView.setId(containerViewId);
570
-
571
- getBridge().getWebView().setBackgroundColor(Color.TRANSPARENT);
572
- ((ViewGroup) getBridge().getWebView().getParent()).addView(
573
- containerView
574
- );
575
- if (toBack) {
576
- getBridge()
577
- .getWebView()
578
- .getParent()
579
- .bringChildToFront(getBridge().getWebView());
580
- setupBroadcast();
581
- }
582
-
583
- FragmentManager fragmentManager = getBridge()
584
- .getActivity()
585
- .getFragmentManager();
586
- FragmentTransaction fragmentTransaction =
587
- fragmentManager.beginTransaction();
588
- fragmentTransaction.add(containerView.getId(), fragment);
589
- fragmentTransaction.commit();
590
-
591
- // NOTE: we don't return invoke call.resolve here because it must be invoked in onCameraStarted
592
- // otherwise the plugin start method might resolve/return before the camera is actually set in CameraActivity
593
- // onResume method (see this line mCamera = Camera.open(defaultCameraId);) and the next subsequent plugin
594
- // method invocations (for example, getSupportedFlashModes) might fails with "Camera is not running" error
595
- // because camera is not available yet and hasCamera method will return false
596
- // Please also see https://developer.android.com/reference/android/hardware/Camera.html#open%28int%29
597
- bridge.saveCall(call);
598
- cameraStartCallbackId = call.getCallbackId();
599
- } else {
600
- call.reject("camera already started");
601
- }
602
- }
362
+ String finalDeviceId = deviceId;
363
+ float finalTargetZoom = targetZoom;
364
+ getBridge().getActivity().runOnUiThread(() -> {
365
+ DisplayMetrics metrics = getBridge().getActivity().getResources().getDisplayMetrics();
366
+ if (lockOrientation) {
367
+ getBridge().getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
603
368
  }
604
- );
605
- }
606
-
607
- @Override
608
- protected void handleOnResume() {
609
- super.handleOnResume();
369
+ int computedX = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, x, metrics);
370
+ int computedY = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, y, metrics);
371
+ int computedWidth = width != 0 ? (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, width, metrics) : getBridge().getWebView().getWidth();
372
+ int computedHeight = height != 0 ? (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height, metrics) : getBridge().getWebView().getHeight();
373
+ computedHeight -= (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom, metrics);
374
+
375
+ CameraSessionConfiguration config = new CameraSessionConfiguration(finalDeviceId, position, computedX, computedY, computedWidth, computedHeight, paddingBottom, toBack, storeToFile, enableOpacity, enableZoom, disableExifHeaderStripping, disableAudio, 1.0f);
376
+ config.setTargetZoom(finalTargetZoom);
377
+
378
+ bridge.saveCall(call);
379
+ cameraStartCallbackId = call.getCallbackId();
380
+ cameraXView.startSession(config);
381
+ }
382
+ );
610
383
  }
611
384
 
612
385
  @Override
613
- public void onPictureTaken(String originalPicture) {
614
- JSObject jsObject = new JSObject();
615
- jsObject.put("value", originalPicture);
616
- bridge.getSavedCall(captureCallbackId).resolve(jsObject);
386
+ public void onPictureTaken(String base64, JSONObject exif) {
387
+ PluginCall pluginCall = bridge.getSavedCall(captureCallbackId);
388
+ if (pluginCall == null) {
389
+ Log.e("CameraPreview", "onPictureTaken: captureCallbackId is null");
390
+ return;
391
+ }
392
+ JSObject result = new JSObject();
393
+ result.put("value", base64);
394
+ result.put("exif", exif);
395
+ pluginCall.resolve(result);
396
+ bridge.releaseCall(pluginCall);
617
397
  }
618
398
 
619
399
  @Override
620
400
  public void onPictureTakenError(String message) {
621
- bridge.getSavedCall(captureCallbackId).reject(message);
401
+ PluginCall pluginCall = bridge.getSavedCall(captureCallbackId);
402
+ if (pluginCall == null) {
403
+ Log.e("CameraPreview", "onPictureTakenError: captureCallbackId is null");
404
+ return;
405
+ }
406
+ pluginCall.reject(message);
407
+ bridge.releaseCall(pluginCall);
622
408
  }
623
409
 
624
410
  @Override
625
- public void onSnapshotTaken(String originalPicture) {
411
+ public void onSampleTaken(String result) {
626
412
  JSObject jsObject = new JSObject();
627
- jsObject.put("value", originalPicture);
413
+ jsObject.put("value", result);
628
414
  bridge.getSavedCall(snapshotCallbackId).resolve(jsObject);
629
415
  }
630
416
 
631
417
  @Override
632
- public void onSnapshotTakenError(String message) {
418
+ public void onSampleTakenError(String message) {
633
419
  bridge.getSavedCall(snapshotCallbackId).reject(message);
634
420
  }
635
421
 
636
- @Override
637
- public void onFocusSet(int pointX, int pointY) {}
638
-
639
- @Override
640
- public void onFocusSetError(String message) {}
641
-
642
- @Override
643
- public void onBackButton() {}
644
-
645
422
  @Override
646
423
  public void onCameraStarted() {
647
424
  PluginCall pluginCall = bridge.getSavedCall(cameraStartCallbackId);
648
- pluginCall.resolve();
649
- bridge.releaseCall(pluginCall);
650
- }
651
-
652
- @Override
653
- public void onStartRecordVideo() {}
654
-
655
- @Override
656
- public void onStartRecordVideoError(String message) {
657
- bridge.getSavedCall(recordCallbackId).reject(message);
658
- }
659
-
660
- @Override
661
- public void onStopRecordVideo(String file) {
662
- PluginCall pluginCall = bridge.getSavedCall(recordCallbackId);
663
- JSObject jsObject = new JSObject();
664
- jsObject.put("videoFilePath", file);
665
- pluginCall.resolve(jsObject);
666
- }
667
-
668
- @Override
669
- public void onStopRecordVideoError(String error) {
670
- bridge.getSavedCall(recordCallbackId).reject(error);
671
- }
672
-
673
- private boolean hasView(PluginCall call) {
674
- if (fragment == null) {
675
- return false;
676
- }
677
-
678
- return true;
679
- }
680
-
681
- private boolean hasCamera(PluginCall call) {
682
- if (!this.hasView(call)) {
683
- return false;
684
- }
685
-
686
- if (fragment.getCamera() == null) {
687
- return false;
425
+ if (pluginCall != null) {
426
+ pluginCall.resolve();
427
+ bridge.releaseCall(pluginCall);
688
428
  }
689
-
690
- return true;
691
429
  }
692
430
 
693
- private String getFilePath() {
694
- String fileName = "videoTmp";
695
-
696
- int i = 1;
697
-
698
- while (
699
- new File(VIDEO_FILE_PATH + fileName + VIDEO_FILE_EXTENSION).exists()
700
- ) {
701
- // Add number suffix if file exists
702
- fileName = "videoTmp" + '_' + i;
703
- i++;
431
+ @Override
432
+ public void onCameraStartError(String message) {
433
+ PluginCall pluginCall = bridge.getSavedCall(cameraStartCallbackId);
434
+ if (pluginCall != null) {
435
+ pluginCall.reject(message);
436
+ bridge.releaseCall(pluginCall);
704
437
  }
705
-
706
- return VIDEO_FILE_PATH + fileName + VIDEO_FILE_EXTENSION;
707
438
  }
708
439
 
709
- private void setupBroadcast() {
710
- /** When touch event is triggered, relay it to camera view if needed so it can support pinch zoom */
711
-
712
- getBridge().getWebView().setClickable(true);
713
- getBridge()
714
- .getWebView()
715
- .setOnTouchListener(
716
- new View.OnTouchListener() {
717
- @Override
718
- public boolean onTouch(View v, MotionEvent event) {
719
- if (
720
- (null != fragment) &&
721
- (fragment.toBack == true) &&
722
- null != fragment.frameContainerLayout
723
- ) {
724
- fragment.frameContainerLayout.dispatchTouchEvent(event);
725
- }
726
- return false;
727
- }
728
- }
729
- );
730
- }
731
440
  }