@capgo/camera-preview 7.6.1 → 7.8.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 +143 -0
- package/android/build.gradle +2 -2
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +194 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +255 -1
- package/dist/docs.json +131 -0
- package/dist/esm/definitions.d.ts +47 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +21 -1
- package/dist/esm/web.js +19 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +19 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +19 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +185 -8
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +115 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -95,6 +95,53 @@ await CameraPreview.deleteFile({ path: filePath })
|
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
|
|
98
|
+
## Exposure controls (iOS & Android)
|
|
99
|
+
|
|
100
|
+
This plugin exposes camera exposure controls on iOS and Android:
|
|
101
|
+
|
|
102
|
+
- Exposure modes: `"AUTO" | "LOCK" | "CONTINUOUS" | "CUSTOM"`
|
|
103
|
+
- Exposure compensation (EV bias): get range `{ min, max, step }`, read current value, and set new value
|
|
104
|
+
|
|
105
|
+
Platform notes:
|
|
106
|
+
|
|
107
|
+
- iOS: The camera starts in `CONTINUOUS` by default. Switching to `AUTO` or `CONTINUOUS` resets EV to 0. The `step` value is approximated to 0.1 since iOS does not expose the bias step.
|
|
108
|
+
- Android: AE lock/unlock and mode are handled via CameraX + Camera2 interop. The `step` value comes from CameraX `ExposureState` and may vary per device.
|
|
109
|
+
|
|
110
|
+
Example (TypeScript):
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { CameraPreview } from '@capgo/camera-preview';
|
|
114
|
+
|
|
115
|
+
// Query supported modes
|
|
116
|
+
const { modes } = await CameraPreview.getExposureModes();
|
|
117
|
+
console.log('Supported exposure modes:', modes);
|
|
118
|
+
|
|
119
|
+
// Get current mode
|
|
120
|
+
const { mode } = await CameraPreview.getExposureMode();
|
|
121
|
+
console.log('Current exposure mode:', mode);
|
|
122
|
+
|
|
123
|
+
// Set mode (AUTO | LOCK | CONTINUOUS | CUSTOM)
|
|
124
|
+
await CameraPreview.setExposureMode({ mode: 'CONTINUOUS' });
|
|
125
|
+
|
|
126
|
+
// Get EV range (with step)
|
|
127
|
+
const { min, max, step } = await CameraPreview.getExposureCompensationRange();
|
|
128
|
+
console.log('EV range:', { min, max, step });
|
|
129
|
+
|
|
130
|
+
// Read current EV
|
|
131
|
+
const { value: currentEV } = await CameraPreview.getExposureCompensation();
|
|
132
|
+
console.log('Current EV:', currentEV);
|
|
133
|
+
|
|
134
|
+
// Increment EV by one step and clamp to range
|
|
135
|
+
const nextEV = Math.max(min, Math.min(max, currentEV + step));
|
|
136
|
+
await CameraPreview.setExposureCompensation({ value: nextEV });
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Example app (Ionic):
|
|
140
|
+
|
|
141
|
+
- Exposure mode toggle (sun icon) cycles through modes.
|
|
142
|
+
- EV controls (+/−) are placed in a top‑right floating action bar, outside the preview area.
|
|
143
|
+
|
|
144
|
+
|
|
98
145
|
# Installation
|
|
99
146
|
|
|
100
147
|
```
|
|
@@ -267,6 +314,12 @@ Documentation for the [uploader](https://github.com/Cap-go/capacitor-uploader)
|
|
|
267
314
|
* [`deleteFile(...)`](#deletefile)
|
|
268
315
|
* [`getSafeAreaInsets()`](#getsafeareainsets)
|
|
269
316
|
* [`getOrientation()`](#getorientation)
|
|
317
|
+
* [`getExposureModes()`](#getexposuremodes)
|
|
318
|
+
* [`getExposureMode()`](#getexposuremode)
|
|
319
|
+
* [`setExposureMode(...)`](#setexposuremode)
|
|
320
|
+
* [`getExposureCompensationRange()`](#getexposurecompensationrange)
|
|
321
|
+
* [`getExposureCompensation()`](#getexposurecompensation)
|
|
322
|
+
* [`setExposureCompensation(...)`](#setexposurecompensation)
|
|
270
323
|
* [Interfaces](#interfaces)
|
|
271
324
|
* [Type Aliases](#type-aliases)
|
|
272
325
|
* [Enums](#enums)
|
|
@@ -828,6 +881,89 @@ Gets the current device orientation in a cross-platform format.
|
|
|
828
881
|
--------------------
|
|
829
882
|
|
|
830
883
|
|
|
884
|
+
### getExposureModes()
|
|
885
|
+
|
|
886
|
+
```typescript
|
|
887
|
+
getExposureModes() => Promise<{ modes: ExposureMode[]; }>
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
Returns the exposure modes supported by the active camera.
|
|
891
|
+
Modes can include: 'locked', 'auto', 'continuous', 'custom'.
|
|
892
|
+
|
|
893
|
+
**Returns:** <code>Promise<{ modes: ExposureMode[]; }></code>
|
|
894
|
+
|
|
895
|
+
--------------------
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
### getExposureMode()
|
|
899
|
+
|
|
900
|
+
```typescript
|
|
901
|
+
getExposureMode() => Promise<{ mode: ExposureMode; }>
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
Returns the current exposure mode.
|
|
905
|
+
|
|
906
|
+
**Returns:** <code>Promise<{ mode: <a href="#exposuremode">ExposureMode</a>; }></code>
|
|
907
|
+
|
|
908
|
+
--------------------
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
### setExposureMode(...)
|
|
912
|
+
|
|
913
|
+
```typescript
|
|
914
|
+
setExposureMode(options: { mode: ExposureMode; }) => Promise<void>
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
Sets the exposure mode.
|
|
918
|
+
|
|
919
|
+
| Param | Type |
|
|
920
|
+
| ------------- | ---------------------------------------------------------------- |
|
|
921
|
+
| **`options`** | <code>{ mode: <a href="#exposuremode">ExposureMode</a>; }</code> |
|
|
922
|
+
|
|
923
|
+
--------------------
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
### getExposureCompensationRange()
|
|
927
|
+
|
|
928
|
+
```typescript
|
|
929
|
+
getExposureCompensationRange() => Promise<{ min: number; max: number; step: number; }>
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
Returns the exposure compensation (EV bias) supported range.
|
|
933
|
+
|
|
934
|
+
**Returns:** <code>Promise<{ min: number; max: number; step: number; }></code>
|
|
935
|
+
|
|
936
|
+
--------------------
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
### getExposureCompensation()
|
|
940
|
+
|
|
941
|
+
```typescript
|
|
942
|
+
getExposureCompensation() => Promise<{ value: number; }>
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
Returns the current exposure compensation (EV bias).
|
|
946
|
+
|
|
947
|
+
**Returns:** <code>Promise<{ value: number; }></code>
|
|
948
|
+
|
|
949
|
+
--------------------
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
### setExposureCompensation(...)
|
|
953
|
+
|
|
954
|
+
```typescript
|
|
955
|
+
setExposureCompensation(options: { value: number; }) => Promise<void>
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
Sets the exposure compensation (EV bias). Value will be clamped to range.
|
|
959
|
+
|
|
960
|
+
| Param | Type |
|
|
961
|
+
| ------------- | ------------------------------- |
|
|
962
|
+
| **`options`** | <code>{ value: number; }</code> |
|
|
963
|
+
|
|
964
|
+
--------------------
|
|
965
|
+
|
|
966
|
+
|
|
831
967
|
### Interfaces
|
|
832
968
|
|
|
833
969
|
|
|
@@ -1023,6 +1159,13 @@ Canonical device orientation values across platforms.
|
|
|
1023
1159
|
<code>"portrait" | "landscape" | "landscape-left" | "landscape-right" | "portrait-upside-down" | "unknown"</code>
|
|
1024
1160
|
|
|
1025
1161
|
|
|
1162
|
+
#### ExposureMode
|
|
1163
|
+
|
|
1164
|
+
Reusable exposure mode type for cross-platform support.
|
|
1165
|
+
|
|
1166
|
+
<code>"AUTO" | "LOCK" | "CONTINUOUS" | "CUSTOM"</code>
|
|
1167
|
+
|
|
1168
|
+
|
|
1026
1169
|
### Enums
|
|
1027
1170
|
|
|
1028
1171
|
|
package/android/build.gradle
CHANGED
|
@@ -53,13 +53,13 @@ dependencies {
|
|
|
53
53
|
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.3.0'
|
|
54
54
|
|
|
55
55
|
// CameraX dependencies
|
|
56
|
-
def camerax_version = "1.5.0-
|
|
56
|
+
def camerax_version = "1.5.0-rc01"
|
|
57
57
|
implementation "androidx.camera:camera-core:${camerax_version}"
|
|
58
58
|
implementation "androidx.camera:camera-camera2:${camerax_version}"
|
|
59
59
|
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
|
|
60
60
|
implementation "androidx.camera:camera-view:${camerax_version}"
|
|
61
61
|
implementation "androidx.camera:camera-extensions:${camerax_version}"
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
testImplementation "junit:junit:$junitVersion"
|
|
64
64
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
65
65
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
@@ -80,8 +80,106 @@ public class CameraPreview
|
|
|
80
80
|
private Location lastLocation;
|
|
81
81
|
private OrientationEventListener orientationListener;
|
|
82
82
|
private int lastOrientation = Configuration.ORIENTATION_UNDEFINED;
|
|
83
|
+
private boolean lastDisableAudio = true;
|
|
83
84
|
|
|
84
85
|
@PluginMethod
|
|
86
|
+
public void getExposureModes(PluginCall call) {
|
|
87
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
88
|
+
call.reject("Camera is not running");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
JSArray arr = new JSArray();
|
|
92
|
+
for (String m : cameraXView.getExposureModes()) arr.put(m);
|
|
93
|
+
JSObject ret = new JSObject();
|
|
94
|
+
ret.put("modes", arr);
|
|
95
|
+
call.resolve(ret);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@PluginMethod
|
|
99
|
+
public void getExposureMode(PluginCall call) {
|
|
100
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
101
|
+
call.reject("Camera is not running");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
JSObject ret = new JSObject();
|
|
105
|
+
ret.put("mode", cameraXView.getExposureMode());
|
|
106
|
+
call.resolve(ret);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@PluginMethod
|
|
110
|
+
public void setExposureMode(PluginCall call) {
|
|
111
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
112
|
+
call.reject("Camera is not running");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
String mode = call.getString("mode");
|
|
116
|
+
if (mode == null || mode.isEmpty()) {
|
|
117
|
+
call.reject("mode parameter is required");
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
cameraXView.setExposureMode(mode);
|
|
122
|
+
call.resolve();
|
|
123
|
+
} catch (Exception e) {
|
|
124
|
+
call.reject("Failed to set exposure mode: " + e.getMessage());
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@PluginMethod
|
|
129
|
+
public void getExposureCompensationRange(PluginCall call) {
|
|
130
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
131
|
+
call.reject("Camera is not running");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
float[] range = cameraXView.getExposureCompensationRange();
|
|
136
|
+
JSObject ret = new JSObject();
|
|
137
|
+
ret.put("min", range[0]);
|
|
138
|
+
ret.put("max", range[1]);
|
|
139
|
+
ret.put("step", range.length > 2 ? range[2] : 0.1);
|
|
140
|
+
call.resolve(ret);
|
|
141
|
+
} catch (Exception e) {
|
|
142
|
+
call.reject(
|
|
143
|
+
"Failed to get exposure compensation range: " + e.getMessage()
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@PluginMethod
|
|
149
|
+
public void getExposureCompensation(PluginCall call) {
|
|
150
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
151
|
+
call.reject("Camera is not running");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
float value = cameraXView.getExposureCompensation();
|
|
156
|
+
JSObject ret = new JSObject();
|
|
157
|
+
ret.put("value", value);
|
|
158
|
+
call.resolve(ret);
|
|
159
|
+
} catch (Exception e) {
|
|
160
|
+
call.reject("Failed to get exposure compensation: " + e.getMessage());
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@PluginMethod
|
|
165
|
+
public void setExposureCompensation(PluginCall call) {
|
|
166
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
167
|
+
call.reject("Camera is not running");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
Float value = call.getFloat("value");
|
|
171
|
+
if (value == null) {
|
|
172
|
+
call.reject("value parameter is required");
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
cameraXView.setExposureCompensation(value);
|
|
177
|
+
call.resolve();
|
|
178
|
+
} catch (Exception e) {
|
|
179
|
+
call.reject("Failed to set exposure compensation: " + e.getMessage());
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
85
183
|
public void getOrientation(PluginCall call) {
|
|
86
184
|
int orientation = getContext()
|
|
87
185
|
.getResources()
|
|
@@ -592,6 +690,7 @@ public class CameraPreview
|
|
|
592
690
|
final boolean disableAudio = Boolean.TRUE.equals(
|
|
593
691
|
call.getBoolean("disableAudio", true)
|
|
594
692
|
);
|
|
693
|
+
this.lastDisableAudio = disableAudio;
|
|
595
694
|
final String aspectRatio = call.getString("aspectRatio", "4:3");
|
|
596
695
|
final String gridMode = call.getString("gridMode", "none");
|
|
597
696
|
final String positioning = call.getString("positioning", "top");
|
|
@@ -1669,6 +1768,101 @@ public class CameraPreview
|
|
|
1669
1768
|
}
|
|
1670
1769
|
}
|
|
1671
1770
|
|
|
1771
|
+
@PluginMethod
|
|
1772
|
+
public void startRecordVideo(PluginCall call) {
|
|
1773
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
1774
|
+
call.reject("Camera is not running");
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
boolean disableAudio = call.getBoolean("disableAudio") != null
|
|
1779
|
+
? Boolean.TRUE.equals(call.getBoolean("disableAudio"))
|
|
1780
|
+
: this.lastDisableAudio;
|
|
1781
|
+
String permissionAlias = disableAudio
|
|
1782
|
+
? CAMERA_ONLY_PERMISSION_ALIAS
|
|
1783
|
+
: CAMERA_WITH_AUDIO_PERMISSION_ALIAS;
|
|
1784
|
+
|
|
1785
|
+
if (PermissionState.GRANTED.equals(getPermissionState(permissionAlias))) {
|
|
1786
|
+
try {
|
|
1787
|
+
cameraXView.startRecordVideo();
|
|
1788
|
+
call.resolve();
|
|
1789
|
+
} catch (Exception e) {
|
|
1790
|
+
call.reject("Failed to start video recording: " + e.getMessage());
|
|
1791
|
+
}
|
|
1792
|
+
} else {
|
|
1793
|
+
requestPermissionForAlias(
|
|
1794
|
+
permissionAlias,
|
|
1795
|
+
call,
|
|
1796
|
+
"handleVideoRecordingPermissionResult"
|
|
1797
|
+
);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
@PluginMethod
|
|
1802
|
+
public void stopRecordVideo(PluginCall call) {
|
|
1803
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
1804
|
+
call.reject("Camera is not running");
|
|
1805
|
+
return;
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
try {
|
|
1809
|
+
bridge.saveCall(call);
|
|
1810
|
+
final String cbId = call.getCallbackId();
|
|
1811
|
+
cameraXView.stopRecordVideo(
|
|
1812
|
+
new CameraXView.VideoRecordingCallback() {
|
|
1813
|
+
@Override
|
|
1814
|
+
public void onSuccess(String filePath) {
|
|
1815
|
+
PluginCall saved = bridge.getSavedCall(cbId);
|
|
1816
|
+
if (saved != null) {
|
|
1817
|
+
JSObject result = new JSObject();
|
|
1818
|
+
result.put("videoFilePath", filePath);
|
|
1819
|
+
saved.resolve(result);
|
|
1820
|
+
bridge.releaseCall(saved);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
@Override
|
|
1825
|
+
public void onError(String message) {
|
|
1826
|
+
PluginCall saved = bridge.getSavedCall(cbId);
|
|
1827
|
+
if (saved != null) {
|
|
1828
|
+
saved.reject("Failed to stop video recording: " + message);
|
|
1829
|
+
bridge.releaseCall(saved);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
);
|
|
1834
|
+
} catch (Exception e) {
|
|
1835
|
+
call.reject("Failed to stop video recording: " + e.getMessage());
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
@PermissionCallback
|
|
1840
|
+
private void handleVideoRecordingPermissionResult(PluginCall call) {
|
|
1841
|
+
// Use the persisted session value to determine which permission we requested
|
|
1842
|
+
String permissionAlias = this.lastDisableAudio
|
|
1843
|
+
? CAMERA_ONLY_PERMISSION_ALIAS
|
|
1844
|
+
: CAMERA_WITH_AUDIO_PERMISSION_ALIAS;
|
|
1845
|
+
|
|
1846
|
+
// Check if either permission is granted (mirroring handleCameraPermissionResult)
|
|
1847
|
+
if (
|
|
1848
|
+
PermissionState.GRANTED.equals(
|
|
1849
|
+
getPermissionState(CAMERA_ONLY_PERMISSION_ALIAS)
|
|
1850
|
+
) ||
|
|
1851
|
+
PermissionState.GRANTED.equals(
|
|
1852
|
+
getPermissionState(CAMERA_WITH_AUDIO_PERMISSION_ALIAS)
|
|
1853
|
+
)
|
|
1854
|
+
) {
|
|
1855
|
+
try {
|
|
1856
|
+
cameraXView.startRecordVideo();
|
|
1857
|
+
call.resolve();
|
|
1858
|
+
} catch (Exception e) {
|
|
1859
|
+
call.reject("Failed to start video recording: " + e.getMessage());
|
|
1860
|
+
}
|
|
1861
|
+
} else {
|
|
1862
|
+
call.reject("Permission denied for video recording");
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1672
1866
|
@PluginMethod
|
|
1673
1867
|
public void getSafeAreaInsets(PluginCall call) {
|
|
1674
1868
|
JSObject ret = new JSObject();
|