@capgo/camera-preview 8.9.0 → 8.11.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 +43 -40
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraPreview.java +12 -4
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java +86 -9
- package/android/src/main/java/app/capgo/capacitor/camera/preview/model/CameraSessionConfiguration.java +9 -0
- package/dist/docs.json +53 -1
- package/dist/esm/definitions.d.ts +26 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.js +2 -2
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +2 -2
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +2 -2
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +16 -4
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +26 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -780,7 +780,7 @@ startRecordVideo(options: CameraPreviewOptions) => Promise<void>
|
|
|
780
780
|
|
|
781
781
|
Starts recording a video.
|
|
782
782
|
|
|
783
|
-
Supports `frameRate`, `videoCodec`, `videoStabilizationMode`, `maxDuration`, `maxFileSize`, `disableAudio`, and `
|
|
783
|
+
Supports `frameRate`, `videoCodec`, `videoStabilizationMode`, `maxDuration`, `maxFileSize`, `disableAudio`, `allowHapticsAndSystemSoundsDuringRecording`, and `mirrorFrontCamera` on each call.
|
|
784
784
|
|
|
785
785
|
| Param | Type | Description |
|
|
786
786
|
| ------------- | --------------------------------------------------------------------- | ---------------------------------- |
|
|
@@ -1499,42 +1499,43 @@ Get the native Capacitor plugin version
|
|
|
1499
1499
|
|
|
1500
1500
|
Defines the configuration options for starting the camera preview.
|
|
1501
1501
|
|
|
1502
|
-
| Prop | Type | Description
|
|
1503
|
-
| ------------------------------------------------ | ---------------------------------------------------------------------------------- |
|
|
1504
|
-
| **`parent`** | <code>string</code> | The parent element to attach the video preview to.
|
|
1505
|
-
| **`className`** | <code>string</code> | A CSS class name to add to the preview element.
|
|
1506
|
-
| **`width`** | <code>number</code> | The width of the preview in pixels. Defaults to the screen width.
|
|
1507
|
-
| **`height`** | <code>number</code> | The height of the preview in pixels. Defaults to the screen height.
|
|
1508
|
-
| **`x`** | <code>number</code> | The horizontal origin of the preview, in pixels.
|
|
1509
|
-
| **`y`** | <code>number</code> | The vertical origin of the preview, in pixels.
|
|
1510
|
-
| **`aspectRatio`** | <code><a href="#camerapreviewaspectratio">CameraPreviewAspectRatio</a></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.
|
|
1511
|
-
| **`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.
|
|
1512
|
-
| **`gridMode`** | <code><a href="#gridmode">GridMode</a></code> | The grid overlay to display on the camera preview.
|
|
1513
|
-
| **`includeSafeAreaInsets`** | <code>boolean</code> | Adjusts the y-position to account for safe areas (e.g., notches).
|
|
1514
|
-
| **`toBack`** | <code>boolean</code> | If true, places the preview behind the webview.
|
|
1515
|
-
| **`paddingBottom`** | <code>number</code> | Bottom padding for the preview, in pixels.
|
|
1516
|
-
| **`rotateWhenOrientationChanged`** | <code>boolean</code> | Whether to rotate the preview when the device orientation changes.
|
|
1517
|
-
| **`position`** | <code>string</code> | The camera to use.
|
|
1518
|
-
| **`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.
|
|
1519
|
-
| **`disableExifHeaderStripping`** | <code>boolean</code> | If true, prevents the plugin from rotating the image based on EXIF data.
|
|
1520
|
-
| **`disableAudio`** | <code>boolean</code> | If true, disables the audio stream, preventing audio permission requests.
|
|
1521
|
-
| **`lockAndroidOrientation`** | <code>boolean</code> | If true, locks the device orientation while the camera is active.
|
|
1522
|
-
| **`enableOpacity`** | <code>boolean</code> | If true, allows the camera preview's opacity to be changed.
|
|
1523
|
-
| **`disableFocusIndicator`** | <code>boolean</code> | If true, disables the visual focus indicator when tapping to focus.
|
|
1524
|
-
| **`deviceId`** | <code>string</code> | The `deviceId` of the camera to use. If provided, `position` is ignored.
|
|
1525
|
-
| **`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.
|
|
1526
|
-
| **`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.
|
|
1527
|
-
| **`positioning`** | <code><a href="#camerapositioning">CameraPositioning</a></code> | The vertical positioning of the camera preview.
|
|
1528
|
-
| **`enableVideoMode`** | <code>boolean</code> | If true, enables video capture capabilities when the camera starts.
|
|
1529
|
-
| **`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.
|
|
1530
|
-
| **`videoQuality`** | <code><a href="#videoquality">VideoQuality</a></code> | Sets the quality of video for recording. Options: 'low', 'medium', 'high', '2160p', '1080p', '720p', '480p', '4:3'
|
|
1531
|
-
| **`maxDuration`** | <code>number</code> | Maximum recording duration in seconds. Recording stops automatically when reached.
|
|
1532
|
-
| **`maxFileSize`** | <code>number</code> | Maximum recording file size in bytes. Recording stops automatically when reached.
|
|
1533
|
-
| **`videoCodec`** | <code><a href="#videocodec">VideoCodec</a></code> | Preferred video codec for recording.
|
|
1534
|
-
| **`frameRate`** | <code>number</code> | Target video frame rate in frames per second. Applied when recording starts. Use `getSupportedVideoFrameRates()` to list supported values.
|
|
1535
|
-
| **`videoStabilizationMode`** | <code><a href="#videostabilizationmode">VideoStabilizationMode</a></code> | Preferred video stabilization mode for recording.
|
|
1536
|
-
| **`allowHapticsAndSystemSoundsDuringRecording`** | <code>boolean</code> | When `true`, allows haptic feedback and system sounds while recording video with audio. iOS suppresses haptics during audio input recording unless this is enabled. Requires `disableAudio: false`.
|
|
1537
|
-
| **`
|
|
1502
|
+
| Prop | Type | Description | Default | Since |
|
|
1503
|
+
| ------------------------------------------------ | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ------ |
|
|
1504
|
+
| **`parent`** | <code>string</code> | The parent element to attach the video preview to. | | |
|
|
1505
|
+
| **`className`** | <code>string</code> | A CSS class name to add to the preview element. | | |
|
|
1506
|
+
| **`width`** | <code>number</code> | The width of the preview in pixels. Defaults to the screen width. | | |
|
|
1507
|
+
| **`height`** | <code>number</code> | The height of the preview in pixels. Defaults to the screen height. | | |
|
|
1508
|
+
| **`x`** | <code>number</code> | The horizontal origin of the preview, in pixels. | | |
|
|
1509
|
+
| **`y`** | <code>number</code> | The vertical origin of the preview, in pixels. | | |
|
|
1510
|
+
| **`aspectRatio`** | <code><a href="#camerapreviewaspectratio">CameraPreviewAspectRatio</a></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 |
|
|
1511
|
+
| **`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> | |
|
|
1512
|
+
| **`gridMode`** | <code><a href="#gridmode">GridMode</a></code> | The grid overlay to display on the camera preview. | <code>"none"</code> | 2.1.0 |
|
|
1513
|
+
| **`includeSafeAreaInsets`** | <code>boolean</code> | Adjusts the y-position to account for safe areas (e.g., notches). | <code>false</code> | |
|
|
1514
|
+
| **`toBack`** | <code>boolean</code> | If true, places the preview behind the webview. | <code>true</code> | |
|
|
1515
|
+
| **`paddingBottom`** | <code>number</code> | Bottom padding for the preview, in pixels. | | |
|
|
1516
|
+
| **`rotateWhenOrientationChanged`** | <code>boolean</code> | Whether to rotate the preview when the device orientation changes. | <code>true</code> | |
|
|
1517
|
+
| **`position`** | <code>string</code> | The camera to use. | <code>"rear"</code> | |
|
|
1518
|
+
| **`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> | |
|
|
1519
|
+
| **`disableExifHeaderStripping`** | <code>boolean</code> | If true, prevents the plugin from rotating the image based on EXIF data. | <code>false</code> | |
|
|
1520
|
+
| **`disableAudio`** | <code>boolean</code> | If true, disables the audio stream, preventing audio permission requests. | <code>true</code> | |
|
|
1521
|
+
| **`lockAndroidOrientation`** | <code>boolean</code> | If true, locks the device orientation while the camera is active. | <code>false</code> | |
|
|
1522
|
+
| **`enableOpacity`** | <code>boolean</code> | If true, allows the camera preview's opacity to be changed. | <code>false</code> | |
|
|
1523
|
+
| **`disableFocusIndicator`** | <code>boolean</code> | If true, disables the visual focus indicator when tapping to focus. | <code>false</code> | |
|
|
1524
|
+
| **`deviceId`** | <code>string</code> | The `deviceId` of the camera to use. If provided, `position` is ignored. | | |
|
|
1525
|
+
| **`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> | |
|
|
1526
|
+
| **`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 |
|
|
1527
|
+
| **`positioning`** | <code><a href="#camerapositioning">CameraPositioning</a></code> | The vertical positioning of the camera preview. | <code>"center"</code> | 2.3.0 |
|
|
1528
|
+
| **`enableVideoMode`** | <code>boolean</code> | If true, enables video capture capabilities when the camera starts. | <code>false</code> | 7.11.0 |
|
|
1529
|
+
| **`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> | |
|
|
1530
|
+
| **`videoQuality`** | <code><a href="#videoquality">VideoQuality</a></code> | Sets the quality of video for recording. Options: 'low', 'medium', 'high', '2160p', '1080p', '720p', '480p', '4:3' | <code>"high"</code> | |
|
|
1531
|
+
| **`maxDuration`** | <code>number</code> | Maximum recording duration in seconds. Recording stops automatically when reached. | | |
|
|
1532
|
+
| **`maxFileSize`** | <code>number</code> | Maximum recording file size in bytes. Recording stops automatically when reached. | | |
|
|
1533
|
+
| **`videoCodec`** | <code><a href="#videocodec">VideoCodec</a></code> | Preferred video codec for recording. | <code>"avc1"</code> | |
|
|
1534
|
+
| **`frameRate`** | <code>number</code> | Target video frame rate in frames per second. Applied when recording starts. Use `getSupportedVideoFrameRates()` to list supported values. | | 8.6.0 |
|
|
1535
|
+
| **`videoStabilizationMode`** | <code><a href="#videostabilizationmode">VideoStabilizationMode</a></code> | Preferred video stabilization mode for recording. | <code>"off"</code> | |
|
|
1536
|
+
| **`allowHapticsAndSystemSoundsDuringRecording`** | <code>boolean</code> | When `true`, allows haptic feedback and system sounds while recording video with audio. iOS suppresses haptics during audio input recording unless this is enabled. Requires `disableAudio: false`. | <code>false</code> | 8.8.0 |
|
|
1537
|
+
| **`mirrorFrontCamera`** | <code>boolean</code> | When `true` and the front camera is active, mirrors the recorded video file so it matches the selfie-style preview (text and gestures are not reversed). Does not affect the live preview. Defaults to `false` to preserve existing behavior. | <code>false</code> | 8.10.0 |
|
|
1538
|
+
| **`barcodeScanner`** | <code>boolean \| <a href="#barcodescanneroptions">BarcodeScannerOptions</a></code> | Starts barcode scanning together with the camera preview. Set to `true` or pass options to scan all supported formats. Omit this option to keep barcode scanning disabled at startup. | | 8.8.0 |
|
|
1538
1539
|
|
|
1539
1540
|
|
|
1540
1541
|
#### BarcodeScannerOptions
|
|
@@ -1565,15 +1566,17 @@ Defines the options for capturing a picture.
|
|
|
1565
1566
|
| **`embedTimestamp`** | <code>boolean</code> | If true, the plugin will embed a timestamp in the top-right corner of the image. | <code>false</code> | 7.17.0 |
|
|
1566
1567
|
| **`embedLocation`** | <code>boolean</code> | If true, the plugin will embed the current location in the top-right corner of the image. Requires `withExifLocation` to be enabled. | <code>false</code> | 7.18.0 |
|
|
1567
1568
|
| **`photoQualityPrioritization`** | <code>'speed' \| 'balanced' \| 'quality'</code> | Sets the priority for photo quality vs. capture speed. - "speed": Prioritizes faster capture times, may reduce image quality. - "balanced": Aims for a balance between quality and speed. - "quality": Prioritizes image quality, may reduce capture speed. See https://developer.apple.com/documentation/avfoundation/avcapturephotosettings/photoqualityprioritization for details. | <code>"speed"</code> | 7.21.0 |
|
|
1569
|
+
| **`mirrorFrontCamera`** | <code>boolean</code> | If true and the front camera is active, horizontally mirror the captured photo so it matches a selfie-style preview. Rear camera captures are never mirrored. Defaults to false to preserve the previous non-mirrored behavior. | <code>false</code> | 8.10.0 |
|
|
1568
1570
|
|
|
1569
1571
|
|
|
1570
1572
|
#### CameraSampleOptions
|
|
1571
1573
|
|
|
1572
1574
|
Defines the options for capturing a sample frame from the camera preview.
|
|
1573
1575
|
|
|
1574
|
-
| Prop
|
|
1575
|
-
|
|
|
1576
|
-
| **`quality`**
|
|
1576
|
+
| Prop | Type | Description | Default | Since |
|
|
1577
|
+
| ----------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ------ |
|
|
1578
|
+
| **`quality`** | <code>number</code> | The quality of the captured sample, from 0 to 100. | <code>85</code> | |
|
|
1579
|
+
| **`mirrorFrontCamera`** | <code>boolean</code> | If true and the front camera is active, horizontally mirror the captured sample so it matches a selfie-style preview. Rear camera samples are never mirrored. Defaults to false to preserve the previous non-mirrored behavior. | <code>false</code> | 8.10.0 |
|
|
1577
1580
|
|
|
1578
1581
|
|
|
1579
1582
|
#### CameraPermissionStatus
|
|
@@ -527,8 +527,9 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
527
527
|
Integer height = call.getInt("height");
|
|
528
528
|
final boolean embedTimestamp = Boolean.TRUE.equals(call.getBoolean("embedTimestamp"));
|
|
529
529
|
final boolean embedLocation = Boolean.TRUE.equals(call.getBoolean("embedLocation"));
|
|
530
|
+
final boolean mirrorFrontCamera = Boolean.TRUE.equals(call.getBoolean("mirrorFrontCamera"));
|
|
530
531
|
|
|
531
|
-
cameraXView.capturePhoto(quality, saveToGallery, width, height, location, embedTimestamp, embedLocation);
|
|
532
|
+
cameraXView.capturePhoto(quality, saveToGallery, width, height, location, embedTimestamp, embedLocation, mirrorFrontCamera);
|
|
532
533
|
}
|
|
533
534
|
|
|
534
535
|
@PluginMethod
|
|
@@ -540,7 +541,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
540
541
|
bridge.saveCall(call);
|
|
541
542
|
sampleCallbackId = call.getCallbackId();
|
|
542
543
|
Integer quality = Objects.requireNonNull(call.getInt("quality", 85));
|
|
543
|
-
|
|
544
|
+
final boolean mirrorFrontCamera = Boolean.TRUE.equals(call.getBoolean("mirrorFrontCamera"));
|
|
545
|
+
cameraXView.captureSample(quality, mirrorFrontCamera);
|
|
544
546
|
}
|
|
545
547
|
|
|
546
548
|
@PluginMethod
|
|
@@ -2953,8 +2955,14 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2953
2955
|
applyVideoCodecFromCall(call);
|
|
2954
2956
|
applyVideoStabilizationFromCall(call);
|
|
2955
2957
|
Integer frameRate = call.getInt("frameRate");
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
+
boolean mirrorFrontCamera = Boolean.TRUE.equals(call.getBoolean("mirrorFrontCamera", false));
|
|
2959
|
+
cameraXView.startRecordVideo(
|
|
2960
|
+
getMaxDurationMillis(call),
|
|
2961
|
+
getMaxFileSize(call),
|
|
2962
|
+
frameRate,
|
|
2963
|
+
mirrorFrontCamera,
|
|
2964
|
+
call::resolve,
|
|
2965
|
+
(message) -> call.reject("Failed to start video recording: " + message)
|
|
2958
2966
|
);
|
|
2959
2967
|
} catch (Exception e) {
|
|
2960
2968
|
call.reject("Failed to start video recording: " + e.getMessage());
|
|
@@ -62,6 +62,7 @@ import androidx.camera.core.ImageCaptureException;
|
|
|
62
62
|
import androidx.camera.core.ImageProxy;
|
|
63
63
|
import androidx.camera.core.MeteringPoint;
|
|
64
64
|
import androidx.camera.core.MeteringPointFactory;
|
|
65
|
+
import androidx.camera.core.MirrorMode;
|
|
65
66
|
import androidx.camera.core.Preview;
|
|
66
67
|
import androidx.camera.core.ResolutionInfo;
|
|
67
68
|
import androidx.camera.core.TorchState;
|
|
@@ -1153,6 +1154,9 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1153
1154
|
VideoCapture.Builder<Recorder> videoCaptureBuilder = new VideoCapture.Builder<>(recorder);
|
|
1154
1155
|
videoCaptureBuilder = applyTargetFpsToVideoCaptureBuilder(videoCaptureBuilder);
|
|
1155
1156
|
videoCaptureBuilder.setVideoStabilizationEnabled(isVideoStabilizationEnabledForSession());
|
|
1157
|
+
videoCaptureBuilder.setMirrorMode(
|
|
1158
|
+
sessionConfig.isMirrorFrontCamera() ? MirrorMode.MIRROR_MODE_ON_FRONT_ONLY : MirrorMode.MIRROR_MODE_OFF
|
|
1159
|
+
);
|
|
1156
1160
|
videoCapture = videoCaptureBuilder.build();
|
|
1157
1161
|
}
|
|
1158
1162
|
|
|
@@ -2026,7 +2030,8 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2026
2030
|
Integer height,
|
|
2027
2031
|
Location location,
|
|
2028
2032
|
final boolean embedTimestamp,
|
|
2029
|
-
final boolean embedLocation
|
|
2033
|
+
final boolean embedLocation,
|
|
2034
|
+
final boolean mirrorFrontCamera
|
|
2030
2035
|
) {
|
|
2031
2036
|
if (imageCapture == null) {
|
|
2032
2037
|
if (listener != null) {
|
|
@@ -2060,7 +2065,9 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2060
2065
|
", embedTimestamp: " +
|
|
2061
2066
|
embedTimestamp +
|
|
2062
2067
|
", embedLocation: " +
|
|
2063
|
-
embedLocation
|
|
2068
|
+
embedLocation +
|
|
2069
|
+
", mirrorFrontCamera: " +
|
|
2070
|
+
mirrorFrontCamera
|
|
2064
2071
|
);
|
|
2065
2072
|
|
|
2066
2073
|
boolean dispatched = false;
|
|
@@ -2116,6 +2123,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2116
2123
|
if (width != null || height != null) {
|
|
2117
2124
|
Bitmap bitmap = BitmapFactory.decodeByteArray(originalCaptureBytes, 0, originalCaptureBytes.length);
|
|
2118
2125
|
bitmap = applyExifOrientation(bitmap, exifInterface);
|
|
2126
|
+
bitmap = maybeMirrorFrontCameraBitmap(bitmap, mirrorFrontCamera);
|
|
2119
2127
|
Bitmap resizedBitmap = resizeBitmapToMaxDimensions(bitmap, width, height);
|
|
2120
2128
|
if (embedTimestamp || embedLocation) {
|
|
2121
2129
|
resizedBitmap = drawTimestampAndLocationOntoBitmap(
|
|
@@ -2144,6 +2152,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2144
2152
|
// No explicit size/ratio: crop to match current preview content
|
|
2145
2153
|
Bitmap originalBitmap = BitmapFactory.decodeByteArray(originalCaptureBytes, 0, originalCaptureBytes.length);
|
|
2146
2154
|
originalBitmap = applyExifOrientation(originalBitmap, exifInterface);
|
|
2155
|
+
originalBitmap = maybeMirrorFrontCameraBitmap(originalBitmap, mirrorFrontCamera);
|
|
2147
2156
|
Bitmap previewCropped = cropBitmapToMatchPreview(originalBitmap);
|
|
2148
2157
|
if (embedTimestamp || embedLocation) {
|
|
2149
2158
|
previewCropped = drawTimestampAndLocationOntoBitmap(
|
|
@@ -2401,6 +2410,29 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2401
2410
|
}
|
|
2402
2411
|
}
|
|
2403
2412
|
|
|
2413
|
+
private boolean shouldMirrorFrontCamera(boolean mirrorFrontCamera) {
|
|
2414
|
+
return mirrorFrontCamera && sessionConfig != null && "front".equals(sessionConfig.getPosition());
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
private Bitmap maybeMirrorFrontCameraBitmap(Bitmap bitmap, boolean mirrorFrontCamera) {
|
|
2418
|
+
if (bitmap == null || !shouldMirrorFrontCamera(mirrorFrontCamera)) {
|
|
2419
|
+
return bitmap;
|
|
2420
|
+
}
|
|
2421
|
+
return mirrorBitmapHorizontally(bitmap);
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
private Bitmap mirrorBitmapHorizontally(Bitmap bitmap) {
|
|
2425
|
+
Matrix matrix = new Matrix();
|
|
2426
|
+
matrix.preScale(-1.0f, 1.0f);
|
|
2427
|
+
Bitmap mirrored = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
|
2428
|
+
if (mirrored != bitmap) {
|
|
2429
|
+
try {
|
|
2430
|
+
bitmap.recycle();
|
|
2431
|
+
} catch (Exception ignore) {}
|
|
2432
|
+
}
|
|
2433
|
+
return mirrored;
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2404
2436
|
private Bitmap applyExifOrientation(Bitmap bitmap, ExifInterface exif) {
|
|
2405
2437
|
try {
|
|
2406
2438
|
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
|
|
@@ -2763,7 +2795,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2763
2795
|
// Note: We avoid temporary files for EXIF writes. When we transform pixels (resize/crop),
|
|
2764
2796
|
// we recompress JPEG in-memory and update EXIF info only in the returned JSON, not in the bytes.
|
|
2765
2797
|
|
|
2766
|
-
public void captureSample(int quality) {
|
|
2798
|
+
public void captureSample(int quality, final boolean mirrorFrontCamera) {
|
|
2767
2799
|
if (sampleImageCapture == null) {
|
|
2768
2800
|
if (listener != null) {
|
|
2769
2801
|
listener.onSampleTakenError("Camera not ready");
|
|
@@ -2775,7 +2807,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2775
2807
|
Log.d(TAG, "captureSample: Ignored because stop is pending");
|
|
2776
2808
|
return;
|
|
2777
2809
|
}
|
|
2778
|
-
Log.d(TAG, "captureSample: Starting sample capture with quality: " + quality);
|
|
2810
|
+
Log.d(TAG, "captureSample: Starting sample capture with quality: " + quality + ", mirrorFrontCamera: " + mirrorFrontCamera);
|
|
2779
2811
|
|
|
2780
2812
|
boolean dispatched = false;
|
|
2781
2813
|
try {
|
|
@@ -2797,6 +2829,20 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2797
2829
|
try {
|
|
2798
2830
|
// Convert ImageProxy to byte array
|
|
2799
2831
|
byte[] bytes = imageProxyToByteArray(image);
|
|
2832
|
+
if (shouldMirrorFrontCamera(mirrorFrontCamera)) {
|
|
2833
|
+
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
|
2834
|
+
if (bitmap != null) {
|
|
2835
|
+
Bitmap mirrored = mirrorBitmapHorizontally(bitmap);
|
|
2836
|
+
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
2837
|
+
mirrored.compress(Bitmap.CompressFormat.JPEG, quality, stream);
|
|
2838
|
+
bytes = stream.toByteArray();
|
|
2839
|
+
if (mirrored != bitmap) {
|
|
2840
|
+
try {
|
|
2841
|
+
bitmap.recycle();
|
|
2842
|
+
} catch (Exception ignore) {}
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2800
2846
|
String base64 = Base64.encodeToString(bytes, Base64.NO_WRAP);
|
|
2801
2847
|
|
|
2802
2848
|
if (listener != null) {
|
|
@@ -3535,6 +3581,33 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
3535
3581
|
}
|
|
3536
3582
|
}
|
|
3537
3583
|
|
|
3584
|
+
private void applyMirrorFrontCamera(boolean mirrorFrontCamera, Runnable onSuccess, java.util.function.Consumer<String> onError) {
|
|
3585
|
+
mainExecutor.execute(() -> {
|
|
3586
|
+
try {
|
|
3587
|
+
if (sessionConfig == null) {
|
|
3588
|
+
throw new Exception("Camera session is not running");
|
|
3589
|
+
}
|
|
3590
|
+
if (currentRecording != null) {
|
|
3591
|
+
throw new Exception("Cannot change mirror mode while recording is in progress");
|
|
3592
|
+
}
|
|
3593
|
+
if (sessionConfig.isMirrorFrontCamera() == mirrorFrontCamera) {
|
|
3594
|
+
onSuccess.run();
|
|
3595
|
+
return;
|
|
3596
|
+
}
|
|
3597
|
+
sessionConfig.setMirrorFrontCamera(mirrorFrontCamera);
|
|
3598
|
+
if (sessionConfig.isVideoModeEnabled() && isRunning && cameraProvider != null) {
|
|
3599
|
+
pendingFrameRateBindSuccess = onSuccess;
|
|
3600
|
+
pendingFrameRateBindError = onError;
|
|
3601
|
+
bindCameraUseCases();
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
3604
|
+
onSuccess.run();
|
|
3605
|
+
} catch (Exception e) {
|
|
3606
|
+
onError.accept(e.getMessage());
|
|
3607
|
+
}
|
|
3608
|
+
});
|
|
3609
|
+
}
|
|
3610
|
+
|
|
3538
3611
|
private void completePendingFrameRateBindSuccess() {
|
|
3539
3612
|
if (pendingFrameRateBindSuccess == null) {
|
|
3540
3613
|
return;
|
|
@@ -4988,6 +5061,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
4988
5061
|
Long maxDurationMillis,
|
|
4989
5062
|
Long maxFileSize,
|
|
4990
5063
|
Integer frameRate,
|
|
5064
|
+
boolean mirrorFrontCamera,
|
|
4991
5065
|
Runnable onSuccess,
|
|
4992
5066
|
java.util.function.Consumer<String> onError
|
|
4993
5067
|
) {
|
|
@@ -5000,12 +5074,15 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
5000
5074
|
}
|
|
5001
5075
|
};
|
|
5002
5076
|
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5077
|
+
Runnable afterMirrorConfigured = () -> {
|
|
5078
|
+
if (frameRate == null) {
|
|
5079
|
+
mainExecutor.execute(startRecording);
|
|
5080
|
+
return;
|
|
5081
|
+
}
|
|
5082
|
+
setVideoFrameRate(frameRate, startRecording, onError);
|
|
5083
|
+
};
|
|
5007
5084
|
|
|
5008
|
-
|
|
5085
|
+
applyMirrorFrontCamera(mirrorFrontCamera, afterMirrorConfigured, onError);
|
|
5009
5086
|
}
|
|
5010
5087
|
|
|
5011
5088
|
/** @noinspection ResultOfMethodCallIgnored*/
|
|
@@ -30,6 +30,7 @@ public class CameraSessionConfiguration {
|
|
|
30
30
|
private String videoStabilizationMode = "off";
|
|
31
31
|
private boolean enablePhysicalDeviceSelection = false;
|
|
32
32
|
private boolean barcodeScannerEnabled = false;
|
|
33
|
+
private boolean mirrorFrontCamera = false;
|
|
33
34
|
|
|
34
35
|
public CameraSessionConfiguration(
|
|
35
36
|
String deviceId,
|
|
@@ -169,6 +170,14 @@ public class CameraSessionConfiguration {
|
|
|
169
170
|
this.videoStabilizationMode = mode != null ? mode : "off";
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
public boolean isMirrorFrontCamera() {
|
|
174
|
+
return mirrorFrontCamera;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public void setMirrorFrontCamera(boolean mirrorFrontCamera) {
|
|
178
|
+
this.mirrorFrontCamera = mirrorFrontCamera;
|
|
179
|
+
}
|
|
180
|
+
|
|
172
181
|
public boolean isPhysicalDeviceSelectionEnabled() {
|
|
173
182
|
return enablePhysicalDeviceSelection;
|
|
174
183
|
}
|
package/dist/docs.json
CHANGED
|
@@ -539,7 +539,7 @@
|
|
|
539
539
|
"text": "0.0.1"
|
|
540
540
|
}
|
|
541
541
|
],
|
|
542
|
-
"docs": "Starts recording a video.\n\nSupports `frameRate`, `videoCodec`, `videoStabilizationMode`, `maxDuration`, `maxFileSize`, `disableAudio`, and `
|
|
542
|
+
"docs": "Starts recording a video.\n\nSupports `frameRate`, `videoCodec`, `videoStabilizationMode`, `maxDuration`, `maxFileSize`, `disableAudio`, `allowHapticsAndSystemSoundsDuringRecording`, and `mirrorFrontCamera` on each call.",
|
|
543
543
|
"complexTypes": [
|
|
544
544
|
"CameraPreviewOptions"
|
|
545
545
|
],
|
|
@@ -2155,6 +2155,26 @@
|
|
|
2155
2155
|
"complexTypes": [],
|
|
2156
2156
|
"type": "boolean | undefined"
|
|
2157
2157
|
},
|
|
2158
|
+
{
|
|
2159
|
+
"name": "mirrorFrontCamera",
|
|
2160
|
+
"tags": [
|
|
2161
|
+
{
|
|
2162
|
+
"text": "ios, android",
|
|
2163
|
+
"name": "platform"
|
|
2164
|
+
},
|
|
2165
|
+
{
|
|
2166
|
+
"text": "false",
|
|
2167
|
+
"name": "default"
|
|
2168
|
+
},
|
|
2169
|
+
{
|
|
2170
|
+
"text": "8.10.0",
|
|
2171
|
+
"name": "since"
|
|
2172
|
+
}
|
|
2173
|
+
],
|
|
2174
|
+
"docs": "When `true` and the front camera is active, mirrors the recorded video file\nso it matches the selfie-style preview (text and gestures are not reversed).\nDoes not affect the live preview. Defaults to `false` to preserve existing behavior.",
|
|
2175
|
+
"complexTypes": [],
|
|
2176
|
+
"type": "boolean | undefined"
|
|
2177
|
+
},
|
|
2158
2178
|
{
|
|
2159
2179
|
"name": "barcodeScanner",
|
|
2160
2180
|
"tags": [
|
|
@@ -2343,6 +2363,22 @@
|
|
|
2343
2363
|
"docs": "Sets the priority for photo quality vs. capture speed.\n- \"speed\": Prioritizes faster capture times, may reduce image quality.\n- \"balanced\": Aims for a balance between quality and speed.\n- \"quality\": Prioritizes image quality, may reduce capture speed.\nSee https://developer.apple.com/documentation/avfoundation/avcapturephotosettings/photoqualityprioritization for details.",
|
|
2344
2364
|
"complexTypes": [],
|
|
2345
2365
|
"type": "'speed' | 'balanced' | 'quality' | undefined"
|
|
2366
|
+
},
|
|
2367
|
+
{
|
|
2368
|
+
"name": "mirrorFrontCamera",
|
|
2369
|
+
"tags": [
|
|
2370
|
+
{
|
|
2371
|
+
"text": "false",
|
|
2372
|
+
"name": "default"
|
|
2373
|
+
},
|
|
2374
|
+
{
|
|
2375
|
+
"text": "8.10.0",
|
|
2376
|
+
"name": "since"
|
|
2377
|
+
}
|
|
2378
|
+
],
|
|
2379
|
+
"docs": "If true and the front camera is active, horizontally mirror the captured photo\nso it matches a selfie-style preview. Rear camera captures are never mirrored.\nDefaults to false to preserve the previous non-mirrored behavior.",
|
|
2380
|
+
"complexTypes": [],
|
|
2381
|
+
"type": "boolean | undefined"
|
|
2346
2382
|
}
|
|
2347
2383
|
]
|
|
2348
2384
|
},
|
|
@@ -2364,6 +2400,22 @@
|
|
|
2364
2400
|
"docs": "The quality of the captured sample, from 0 to 100.",
|
|
2365
2401
|
"complexTypes": [],
|
|
2366
2402
|
"type": "number | undefined"
|
|
2403
|
+
},
|
|
2404
|
+
{
|
|
2405
|
+
"name": "mirrorFrontCamera",
|
|
2406
|
+
"tags": [
|
|
2407
|
+
{
|
|
2408
|
+
"text": "false",
|
|
2409
|
+
"name": "default"
|
|
2410
|
+
},
|
|
2411
|
+
{
|
|
2412
|
+
"text": "8.10.0",
|
|
2413
|
+
"name": "since"
|
|
2414
|
+
}
|
|
2415
|
+
],
|
|
2416
|
+
"docs": "If true and the front camera is active, horizontally mirror the captured sample\nso it matches a selfie-style preview. Rear camera samples are never mirrored.\nDefaults to false to preserve the previous non-mirrored behavior.",
|
|
2417
|
+
"complexTypes": [],
|
|
2418
|
+
"type": "boolean | undefined"
|
|
2367
2419
|
}
|
|
2368
2420
|
]
|
|
2369
2421
|
},
|
|
@@ -339,6 +339,15 @@ export interface CameraPreviewOptions {
|
|
|
339
339
|
* @since 8.8.0
|
|
340
340
|
*/
|
|
341
341
|
allowHapticsAndSystemSoundsDuringRecording?: boolean;
|
|
342
|
+
/**
|
|
343
|
+
* When `true` and the front camera is active, mirrors the recorded video file
|
|
344
|
+
* so it matches the selfie-style preview (text and gestures are not reversed).
|
|
345
|
+
* Does not affect the live preview. Defaults to `false` to preserve existing behavior.
|
|
346
|
+
* @platform ios, android
|
|
347
|
+
* @default false
|
|
348
|
+
* @since 8.10.0
|
|
349
|
+
*/
|
|
350
|
+
mirrorFrontCamera?: boolean;
|
|
342
351
|
/**
|
|
343
352
|
* Starts barcode scanning together with the camera preview.
|
|
344
353
|
* Set to `true` or pass options to scan all supported formats.
|
|
@@ -411,6 +420,14 @@ export interface CameraPreviewPictureOptions {
|
|
|
411
420
|
* @default "speed"
|
|
412
421
|
*/
|
|
413
422
|
photoQualityPrioritization?: 'speed' | 'balanced' | 'quality';
|
|
423
|
+
/**
|
|
424
|
+
* If true and the front camera is active, horizontally mirror the captured photo
|
|
425
|
+
* so it matches a selfie-style preview. Rear camera captures are never mirrored.
|
|
426
|
+
* Defaults to false to preserve the previous non-mirrored behavior.
|
|
427
|
+
* @default false
|
|
428
|
+
* @since 8.10.0
|
|
429
|
+
*/
|
|
430
|
+
mirrorFrontCamera?: boolean;
|
|
414
431
|
}
|
|
415
432
|
/** Represents EXIF data extracted from an image. */
|
|
416
433
|
export interface ExifData {
|
|
@@ -440,6 +457,14 @@ export interface CameraSampleOptions {
|
|
|
440
457
|
* @default 85
|
|
441
458
|
*/
|
|
442
459
|
quality?: number;
|
|
460
|
+
/**
|
|
461
|
+
* If true and the front camera is active, horizontally mirror the captured sample
|
|
462
|
+
* so it matches a selfie-style preview. Rear camera samples are never mirrored.
|
|
463
|
+
* Defaults to false to preserve the previous non-mirrored behavior.
|
|
464
|
+
* @default false
|
|
465
|
+
* @since 8.10.0
|
|
466
|
+
*/
|
|
467
|
+
mirrorFrontCamera?: boolean;
|
|
443
468
|
}
|
|
444
469
|
/**
|
|
445
470
|
* The available flash modes for the camera.
|
|
@@ -693,7 +718,7 @@ export interface CameraPreviewPlugin {
|
|
|
693
718
|
/**
|
|
694
719
|
* Starts recording a video.
|
|
695
720
|
*
|
|
696
|
-
* Supports `frameRate`, `videoCodec`, `videoStabilizationMode`, `maxDuration`, `maxFileSize`, `disableAudio`, and `
|
|
721
|
+
* Supports `frameRate`, `videoCodec`, `videoStabilizationMode`, `maxDuration`, `maxFileSize`, `disableAudio`, `allowHapticsAndSystemSoundsDuringRecording`, and `mirrorFrontCamera` on each call.
|
|
697
722
|
*
|
|
698
723
|
* @param {CameraPreviewOptions} options - The options for video recording.
|
|
699
724
|
* @returns {Promise<void>} A promise that resolves when video recording starts.
|