@capgo/camera-preview 8.4.8 → 8.5.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 +157 -6
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraPreview.java +131 -3
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java +149 -15
- package/android/src/main/java/app/capgo/capacitor/camera/preview/model/CameraSessionConfiguration.java +14 -1
- package/dist/docs.json +366 -7
- package/dist/esm/definitions.d.ts +108 -7
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +15 -1
- package/dist/esm/web.js +19 -1
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +19 -1
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +19 -1
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +154 -29
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +62 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -376,6 +376,12 @@ Documentation for the [uploader](https://github.com/Cap-go/capacitor-uploader)
|
|
|
376
376
|
* [`setOpacity(...)`](#setopacity)
|
|
377
377
|
* [`stopRecordVideo()`](#stoprecordvideo)
|
|
378
378
|
* [`startRecordVideo(...)`](#startrecordvideo)
|
|
379
|
+
* [`setVideoQuality(...)`](#setvideoquality)
|
|
380
|
+
* [`getVideoQuality()`](#getvideoquality)
|
|
381
|
+
* [`getSupportedVideoQualities()`](#getsupportedvideoqualities)
|
|
382
|
+
* [`setVideoCodec(...)`](#setvideocodec)
|
|
383
|
+
* [`getVideoCodec()`](#getvideocodec)
|
|
384
|
+
* [`getSupportedVideoCodecs()`](#getsupportedvideocodecs)
|
|
379
385
|
* [`isRunning()`](#isrunning)
|
|
380
386
|
* [`getAvailableDevices()`](#getavailabledevices)
|
|
381
387
|
* [`getZoom()`](#getzoom)
|
|
@@ -392,6 +398,7 @@ Documentation for the [uploader](https://github.com/Cap-go/capacitor-uploader)
|
|
|
392
398
|
* [`addListener('orientationChange', ...)`](#addlistenerorientationchange-)
|
|
393
399
|
* [`addListener('barcodeScanned', ...)`](#addlistenerbarcodescanned-)
|
|
394
400
|
* [`addListener('barcodeScanError', ...)`](#addlistenerbarcodescanerror-)
|
|
401
|
+
* [`addListener('recordingFinished', ...)`](#addlistenerrecordingfinished-)
|
|
395
402
|
* [`deleteFile(...)`](#deletefile)
|
|
396
403
|
* [`getSafeAreaInsets()`](#getsafeareainsets)
|
|
397
404
|
* [`getOrientation()`](#getorientation)
|
|
@@ -728,12 +735,12 @@ Sets the opacity of the camera preview.
|
|
|
728
735
|
### stopRecordVideo()
|
|
729
736
|
|
|
730
737
|
```typescript
|
|
731
|
-
stopRecordVideo() => Promise<
|
|
738
|
+
stopRecordVideo() => Promise<RecordingFinishedEvent>
|
|
732
739
|
```
|
|
733
740
|
|
|
734
741
|
Stops an ongoing video recording.
|
|
735
742
|
|
|
736
|
-
**Returns:** <code>Promise<
|
|
743
|
+
**Returns:** <code>Promise<<a href="#recordingfinishedevent">RecordingFinishedEvent</a>></code>
|
|
737
744
|
|
|
738
745
|
**Since:** 0.0.1
|
|
739
746
|
|
|
@@ -748,15 +755,109 @@ startRecordVideo(options: CameraPreviewOptions) => Promise<void>
|
|
|
748
755
|
|
|
749
756
|
Starts recording a video.
|
|
750
757
|
|
|
751
|
-
| Param | Type | Description
|
|
752
|
-
| ------------- | --------------------------------------------------------------------- |
|
|
753
|
-
| **`options`** | <code><a href="#camerapreviewoptions">CameraPreviewOptions</a></code> | - The options for video recording.
|
|
758
|
+
| Param | Type | Description |
|
|
759
|
+
| ------------- | --------------------------------------------------------------------- | ---------------------------------- |
|
|
760
|
+
| **`options`** | <code><a href="#camerapreviewoptions">CameraPreviewOptions</a></code> | - The options for video recording. |
|
|
754
761
|
|
|
755
762
|
**Since:** 0.0.1
|
|
756
763
|
|
|
757
764
|
--------------------
|
|
758
765
|
|
|
759
766
|
|
|
767
|
+
### setVideoQuality(...)
|
|
768
|
+
|
|
769
|
+
```typescript
|
|
770
|
+
setVideoQuality(options: { quality: VideoQuality; }) => Promise<void>
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
Sets the video recording quality for the active camera session.
|
|
774
|
+
|
|
775
|
+
| Param | Type | Description |
|
|
776
|
+
| ------------- | ------------------------------------------------------------------- | ---------------------------- |
|
|
777
|
+
| **`options`** | <code>{ quality: <a href="#videoquality">VideoQuality</a>; }</code> | - The desired video quality. |
|
|
778
|
+
|
|
779
|
+
**Since:** 8.5.0
|
|
780
|
+
|
|
781
|
+
--------------------
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
### getVideoQuality()
|
|
785
|
+
|
|
786
|
+
```typescript
|
|
787
|
+
getVideoQuality() => Promise<{ quality: VideoQuality; }>
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
Gets the current video recording quality.
|
|
791
|
+
|
|
792
|
+
**Returns:** <code>Promise<{ quality: <a href="#videoquality">VideoQuality</a>; }></code>
|
|
793
|
+
|
|
794
|
+
**Since:** 8.5.0
|
|
795
|
+
|
|
796
|
+
--------------------
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
### getSupportedVideoQualities()
|
|
800
|
+
|
|
801
|
+
```typescript
|
|
802
|
+
getSupportedVideoQualities() => Promise<{ qualities: VideoQuality[]; }>
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
Returns the video qualities supported by the active camera.
|
|
806
|
+
|
|
807
|
+
**Returns:** <code>Promise<{ qualities: VideoQuality[]; }></code>
|
|
808
|
+
|
|
809
|
+
**Since:** 8.5.0
|
|
810
|
+
|
|
811
|
+
--------------------
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
### setVideoCodec(...)
|
|
815
|
+
|
|
816
|
+
```typescript
|
|
817
|
+
setVideoCodec(options: { codec: VideoCodec; }) => Promise<void>
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
Sets the video codec used when recording.
|
|
821
|
+
|
|
822
|
+
| Param | Type | Description |
|
|
823
|
+
| ------------- | ------------------------------------------------------------- | -------------------- |
|
|
824
|
+
| **`options`** | <code>{ codec: <a href="#videocodec">VideoCodec</a>; }</code> | - The desired codec. |
|
|
825
|
+
|
|
826
|
+
**Since:** 8.5.0
|
|
827
|
+
|
|
828
|
+
--------------------
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
### getVideoCodec()
|
|
832
|
+
|
|
833
|
+
```typescript
|
|
834
|
+
getVideoCodec() => Promise<{ codec: VideoCodec; }>
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
Gets the current video codec used for recording.
|
|
838
|
+
|
|
839
|
+
**Returns:** <code>Promise<{ codec: <a href="#videocodec">VideoCodec</a>; }></code>
|
|
840
|
+
|
|
841
|
+
**Since:** 8.5.0
|
|
842
|
+
|
|
843
|
+
--------------------
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
### getSupportedVideoCodecs()
|
|
847
|
+
|
|
848
|
+
```typescript
|
|
849
|
+
getSupportedVideoCodecs() => Promise<{ codecs: VideoCodec[]; }>
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
Returns the video codecs supported by the active camera.
|
|
853
|
+
|
|
854
|
+
**Returns:** <code>Promise<{ codecs: VideoCodec[]; }></code>
|
|
855
|
+
|
|
856
|
+
**Since:** 8.5.0
|
|
857
|
+
|
|
858
|
+
--------------------
|
|
859
|
+
|
|
860
|
+
|
|
760
861
|
### isRunning()
|
|
761
862
|
|
|
762
863
|
```typescript
|
|
@@ -1031,6 +1132,26 @@ Adds a listener for non-fatal barcode scanner errors.
|
|
|
1031
1132
|
--------------------
|
|
1032
1133
|
|
|
1033
1134
|
|
|
1135
|
+
### addListener('recordingFinished', ...)
|
|
1136
|
+
|
|
1137
|
+
```typescript
|
|
1138
|
+
addListener(eventName: 'recordingFinished', listenerFunc: (data: RecordingFinishedEvent) => void) => Promise<PluginListenerHandle>
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
Adds a listener fired when a video recording finishes natively, including automatic stops.
|
|
1142
|
+
|
|
1143
|
+
| Param | Type |
|
|
1144
|
+
| ------------------ | -------------------------------------------------------------------------------------------- |
|
|
1145
|
+
| **`eventName`** | <code>'recordingFinished'</code> |
|
|
1146
|
+
| **`listenerFunc`** | <code>(data: <a href="#recordingfinishedevent">RecordingFinishedEvent</a>) => void</code> |
|
|
1147
|
+
|
|
1148
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
1149
|
+
|
|
1150
|
+
**Since:** 8.4.7
|
|
1151
|
+
|
|
1152
|
+
--------------------
|
|
1153
|
+
|
|
1154
|
+
|
|
1034
1155
|
### deleteFile(...)
|
|
1035
1156
|
|
|
1036
1157
|
```typescript
|
|
@@ -1218,7 +1339,10 @@ Defines the configuration options for starting the camera preview.
|
|
|
1218
1339
|
| **`positioning`** | <code><a href="#camerapositioning">CameraPositioning</a></code> | The vertical positioning of the camera preview. | <code>"center"</code> | 2.3.0 |
|
|
1219
1340
|
| **`enableVideoMode`** | <code>boolean</code> | If true, enables video capture capabilities when the camera starts. | <code>false</code> | 7.11.0 |
|
|
1220
1341
|
| **`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> | |
|
|
1221
|
-
| **`videoQuality`** | <code>
|
|
1342
|
+
| **`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> | |
|
|
1343
|
+
| **`maxDuration`** | <code>number</code> | Maximum recording duration in seconds. Recording stops automatically when reached. | | |
|
|
1344
|
+
| **`maxFileSize`** | <code>number</code> | Maximum recording file size in bytes. Recording stops automatically when reached. | | |
|
|
1345
|
+
| **`videoCodec`** | <code><a href="#videocodec">VideoCodec</a></code> | Preferred video codec for recording. | <code>"avc1"</code> | |
|
|
1222
1346
|
| **`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 |
|
|
1223
1347
|
|
|
1224
1348
|
|
|
@@ -1310,6 +1434,14 @@ Defines the options for setting the camera preview's opacity.
|
|
|
1310
1434
|
| **`opacity`** | <code>number</code> | The opacity percentage, from 0.0 (fully transparent) to 1.0 (fully opaque). | <code>1.0</code> |
|
|
1311
1435
|
|
|
1312
1436
|
|
|
1437
|
+
#### RecordingFinishedEvent
|
|
1438
|
+
|
|
1439
|
+
| Prop | Type | Description |
|
|
1440
|
+
| ------------------- | --------------------------------------------------------------------------- | ------------------------------------ |
|
|
1441
|
+
| **`videoFilePath`** | <code>string</code> | The path to the recorded video file. |
|
|
1442
|
+
| **`reason`** | <code><a href="#recordingfinishedreason">RecordingFinishedReason</a></code> | Why the recording stopped. |
|
|
1443
|
+
|
|
1444
|
+
|
|
1313
1445
|
#### CameraDevice
|
|
1314
1446
|
|
|
1315
1447
|
Represents a physical camera on the device (e.g., the front-facing camera).
|
|
@@ -1417,6 +1549,20 @@ iOS: Values are expressed in physical pixels and exclude status bar.
|
|
|
1417
1549
|
<code>'center' | 'top' | 'bottom'</code>
|
|
1418
1550
|
|
|
1419
1551
|
|
|
1552
|
+
#### VideoQuality
|
|
1553
|
+
|
|
1554
|
+
<code>'low' | 'medium' | 'high' | '2160p' | '1080p' | '720p' | '480p' | '4:3'</code>
|
|
1555
|
+
|
|
1556
|
+
|
|
1557
|
+
#### VideoCodec
|
|
1558
|
+
|
|
1559
|
+
Video codec identifiers used when recording.
|
|
1560
|
+
- `avc1`: H.264
|
|
1561
|
+
- `hvc1`: HEVC / H.265
|
|
1562
|
+
|
|
1563
|
+
<code>'avc1' | 'hvc1' | 'jpeg' | 'apcn' | 'ap4h'</code>
|
|
1564
|
+
|
|
1565
|
+
|
|
1420
1566
|
#### BarcodeScannerFormat
|
|
1421
1567
|
|
|
1422
1568
|
<code>'aztec' | 'codabar' | 'code_39' | 'code_93' | 'code_128' | 'data_matrix' | 'ean_8' | 'ean_13' | 'itf' | 'pdf417' | 'qr_code' | 'upc_a' | 'upc_e'</code>
|
|
@@ -1447,6 +1593,11 @@ From T, pick a set of properties whose keys are in the union K
|
|
|
1447
1593
|
<code>{
|
|
1448
1594
|
[P in K]: T[P];
|
|
1449
1595
|
}</code>
|
|
1450
1596
|
|
|
1451
1597
|
|
|
1598
|
+
#### RecordingFinishedReason
|
|
1599
|
+
|
|
1600
|
+
<code>'manual' | 'maxDuration' | 'maxFileSize'</code>
|
|
1601
|
+
|
|
1602
|
+
|
|
1452
1603
|
#### FlashMode
|
|
1453
1604
|
|
|
1454
1605
|
<code><a href="#camerapreviewflashmode">CameraPreviewFlashMode</a></code>
|
|
@@ -51,6 +51,7 @@ import com.getcapacitor.annotation.PermissionCallback;
|
|
|
51
51
|
import com.google.android.gms.location.FusedLocationProviderClient;
|
|
52
52
|
import com.google.android.gms.location.LocationServices;
|
|
53
53
|
import java.util.ArrayList;
|
|
54
|
+
import java.util.Arrays;
|
|
54
55
|
import java.util.List;
|
|
55
56
|
import java.util.Locale;
|
|
56
57
|
import java.util.Objects;
|
|
@@ -2372,6 +2373,14 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2372
2373
|
notifyListeners("barcodeScanError", data);
|
|
2373
2374
|
}
|
|
2374
2375
|
|
|
2376
|
+
@Override
|
|
2377
|
+
public void onVideoRecordingFinished(String filePath, String reason) {
|
|
2378
|
+
JSObject data = new JSObject();
|
|
2379
|
+
data.put("videoFilePath", filePath);
|
|
2380
|
+
data.put("reason", reason);
|
|
2381
|
+
notifyListeners("recordingFinished", data);
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2375
2384
|
@Override
|
|
2376
2385
|
public void onCameraStartError(CameraXView source, String message) {
|
|
2377
2386
|
if (cameraXView != null && cameraXView != source) {
|
|
@@ -2602,6 +2611,96 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2602
2611
|
}
|
|
2603
2612
|
}
|
|
2604
2613
|
|
|
2614
|
+
@PluginMethod
|
|
2615
|
+
public void setVideoQuality(PluginCall call) {
|
|
2616
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
2617
|
+
call.reject("Camera is not running");
|
|
2618
|
+
return;
|
|
2619
|
+
}
|
|
2620
|
+
String quality = call.getString("quality");
|
|
2621
|
+
if (quality == null) {
|
|
2622
|
+
call.reject("quality is required");
|
|
2623
|
+
return;
|
|
2624
|
+
}
|
|
2625
|
+
try {
|
|
2626
|
+
cameraXView.setVideoQualitySetting(quality);
|
|
2627
|
+
call.resolve();
|
|
2628
|
+
} catch (IllegalArgumentException e) {
|
|
2629
|
+
call.reject(e.getMessage());
|
|
2630
|
+
} catch (Exception e) {
|
|
2631
|
+
call.reject("Failed to set video quality: " + e.getMessage());
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
@PluginMethod
|
|
2636
|
+
public void getVideoQuality(PluginCall call) {
|
|
2637
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
2638
|
+
call.reject("Camera is not running");
|
|
2639
|
+
return;
|
|
2640
|
+
}
|
|
2641
|
+
JSObject ret = new JSObject();
|
|
2642
|
+
ret.put("quality", cameraXView.getVideoQualitySetting());
|
|
2643
|
+
call.resolve(ret);
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
@PluginMethod
|
|
2647
|
+
public void getSupportedVideoQualities(PluginCall call) {
|
|
2648
|
+
List<String> qualities = cameraXView != null
|
|
2649
|
+
? cameraXView.getSupportedVideoQualities()
|
|
2650
|
+
: Arrays.asList("low", "medium", "high", "2160p", "1080p", "720p", "480p", "4:3");
|
|
2651
|
+
JSONArray arr = new JSONArray();
|
|
2652
|
+
for (String quality : qualities) {
|
|
2653
|
+
arr.put(quality);
|
|
2654
|
+
}
|
|
2655
|
+
JSObject ret = new JSObject();
|
|
2656
|
+
ret.put("qualities", arr);
|
|
2657
|
+
call.resolve(ret);
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
@PluginMethod
|
|
2661
|
+
public void setVideoCodec(PluginCall call) {
|
|
2662
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
2663
|
+
call.reject("Camera is not running");
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
String codec = call.getString("codec");
|
|
2667
|
+
if (codec == null) {
|
|
2668
|
+
call.reject("codec is required");
|
|
2669
|
+
return;
|
|
2670
|
+
}
|
|
2671
|
+
try {
|
|
2672
|
+
cameraXView.setVideoCodecSetting(codec);
|
|
2673
|
+
call.resolve();
|
|
2674
|
+
} catch (IllegalArgumentException e) {
|
|
2675
|
+
call.reject(e.getMessage());
|
|
2676
|
+
} catch (Exception e) {
|
|
2677
|
+
call.reject("Failed to set video codec: " + e.getMessage());
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
@PluginMethod
|
|
2682
|
+
public void getVideoCodec(PluginCall call) {
|
|
2683
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
2684
|
+
call.reject("Camera is not running");
|
|
2685
|
+
return;
|
|
2686
|
+
}
|
|
2687
|
+
JSObject ret = new JSObject();
|
|
2688
|
+
ret.put("codec", cameraXView.getVideoCodecSetting());
|
|
2689
|
+
call.resolve(ret);
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
@PluginMethod
|
|
2693
|
+
public void getSupportedVideoCodecs(PluginCall call) {
|
|
2694
|
+
List<String> codecs = cameraXView != null ? cameraXView.getSupportedVideoCodecs() : Arrays.asList("avc1");
|
|
2695
|
+
JSONArray arr = new JSONArray();
|
|
2696
|
+
for (String codec : codecs) {
|
|
2697
|
+
arr.put(codec);
|
|
2698
|
+
}
|
|
2699
|
+
JSObject ret = new JSObject();
|
|
2700
|
+
ret.put("codecs", arr);
|
|
2701
|
+
call.resolve(ret);
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2605
2704
|
@PluginMethod
|
|
2606
2705
|
public void startRecordVideo(PluginCall call) {
|
|
2607
2706
|
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
@@ -2617,7 +2716,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2617
2716
|
|
|
2618
2717
|
if (PermissionState.GRANTED.equals(getPermissionState(permissionAlias))) {
|
|
2619
2718
|
try {
|
|
2620
|
-
|
|
2719
|
+
applyVideoCodecFromCall(call);
|
|
2720
|
+
cameraXView.startRecordVideo(getMaxDurationMillis(call), getMaxFileSize(call));
|
|
2621
2721
|
call.resolve();
|
|
2622
2722
|
} catch (Exception e) {
|
|
2623
2723
|
call.reject("Failed to start video recording: " + e.getMessage());
|
|
@@ -2640,11 +2740,12 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2640
2740
|
cameraXView.stopRecordVideo(
|
|
2641
2741
|
new CameraXView.VideoRecordingCallback() {
|
|
2642
2742
|
@Override
|
|
2643
|
-
public void onSuccess(String filePath) {
|
|
2743
|
+
public void onSuccess(String filePath, String reason) {
|
|
2644
2744
|
PluginCall saved = bridge.getSavedCall(cbId);
|
|
2645
2745
|
if (saved != null) {
|
|
2646
2746
|
JSObject result = new JSObject();
|
|
2647
2747
|
result.put("videoFilePath", filePath);
|
|
2748
|
+
result.put("reason", reason);
|
|
2648
2749
|
saved.resolve(result);
|
|
2649
2750
|
bridge.releaseCall(saved);
|
|
2650
2751
|
}
|
|
@@ -2665,6 +2766,32 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2665
2766
|
}
|
|
2666
2767
|
}
|
|
2667
2768
|
|
|
2769
|
+
private void applyVideoCodecFromCall(PluginCall call) {
|
|
2770
|
+
String codec = call.getString("videoCodec");
|
|
2771
|
+
if (codec != null && cameraXView != null) {
|
|
2772
|
+
cameraXView.setVideoCodecSetting(codec);
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
private Long getMaxDurationMillis(PluginCall call) {
|
|
2777
|
+
if (!call.getData().has("maxDuration") || call.getData().isNull("maxDuration")) {
|
|
2778
|
+
return null;
|
|
2779
|
+
}
|
|
2780
|
+
double seconds = call.getData().optDouble("maxDuration", 0D);
|
|
2781
|
+
if (seconds <= 0D) {
|
|
2782
|
+
return null;
|
|
2783
|
+
}
|
|
2784
|
+
return Math.max(1L, Math.round(seconds * 1000D));
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
private Long getMaxFileSize(PluginCall call) {
|
|
2788
|
+
if (!call.getData().has("maxFileSize") || call.getData().isNull("maxFileSize")) {
|
|
2789
|
+
return null;
|
|
2790
|
+
}
|
|
2791
|
+
long bytes = call.getData().optLong("maxFileSize", 0L);
|
|
2792
|
+
return bytes > 0L ? bytes : null;
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2668
2795
|
@PermissionCallback
|
|
2669
2796
|
private void handleVideoRecordingPermissionResult(PluginCall call) {
|
|
2670
2797
|
// Use the persisted session value to determine which permission we requested
|
|
@@ -2676,7 +2803,8 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
|
|
|
2676
2803
|
PermissionState.GRANTED.equals(getPermissionState(CAMERA_WITH_AUDIO_PERMISSION_ALIAS))
|
|
2677
2804
|
) {
|
|
2678
2805
|
try {
|
|
2679
|
-
|
|
2806
|
+
applyVideoCodecFromCall(call);
|
|
2807
|
+
cameraXView.startRecordVideo(getMaxDurationMillis(call), getMaxFileSize(call));
|
|
2680
2808
|
call.resolve();
|
|
2681
2809
|
} catch (Exception e) {
|
|
2682
2810
|
call.reject("Failed to start video recording: " + e.getMessage());
|
|
@@ -23,6 +23,8 @@ import android.hardware.camera2.CameraManager;
|
|
|
23
23
|
import android.hardware.camera2.CaptureRequest;
|
|
24
24
|
import android.location.Location;
|
|
25
25
|
import android.media.Image;
|
|
26
|
+
import android.media.MediaCodecList;
|
|
27
|
+
import android.media.MediaFormat;
|
|
26
28
|
import android.media.MediaScannerConnection;
|
|
27
29
|
import android.os.Build;
|
|
28
30
|
import android.os.Environment;
|
|
@@ -128,6 +130,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
128
130
|
void onSampleTakenError(String message);
|
|
129
131
|
void onBarcodesScanned(JSONArray barcodes);
|
|
130
132
|
void onBarcodeScanError(String message);
|
|
133
|
+
void onVideoRecordingFinished(String filePath, String reason);
|
|
131
134
|
void onCameraStarted(int width, int height, int x, int y);
|
|
132
135
|
void onCameraStartError(CameraXView source, String message);
|
|
133
136
|
void onCameraStopped(CameraXView source);
|
|
@@ -139,7 +142,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
public interface VideoRecordingCallback {
|
|
142
|
-
void onSuccess(String filePath);
|
|
145
|
+
void onSuccess(String filePath, String reason);
|
|
143
146
|
void onError(String message);
|
|
144
147
|
}
|
|
145
148
|
|
|
@@ -1093,24 +1096,36 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1093
1096
|
// Get quality from sessionConfig default to high if null
|
|
1094
1097
|
String videoQuality = sessionConfig.getVideoQuality() != null ? sessionConfig.getVideoQuality() : "high";
|
|
1095
1098
|
|
|
1096
|
-
switch (videoQuality.toLowerCase()) {
|
|
1097
|
-
case "
|
|
1098
|
-
// Target SD, allow falling back to lower if needed
|
|
1099
|
+
switch (videoQuality.toLowerCase(Locale.US)) {
|
|
1100
|
+
case "2160p":
|
|
1099
1101
|
qualitySelector = QualitySelector.fromOrderedList(
|
|
1100
|
-
Arrays.asList(Quality.
|
|
1101
|
-
FallbackStrategy.lowerQualityOrHigherThan(Quality.
|
|
1102
|
+
Arrays.asList(Quality.UHD, Quality.FHD, Quality.HD, Quality.SD),
|
|
1103
|
+
FallbackStrategy.lowerQualityOrHigherThan(Quality.UHD)
|
|
1102
1104
|
);
|
|
1103
1105
|
break;
|
|
1106
|
+
case "1080p":
|
|
1107
|
+
qualitySelector = QualitySelector.fromOrderedList(
|
|
1108
|
+
Arrays.asList(Quality.FHD, Quality.HD, Quality.SD),
|
|
1109
|
+
FallbackStrategy.higherQualityOrLowerThan(Quality.FHD)
|
|
1110
|
+
);
|
|
1111
|
+
break;
|
|
1112
|
+
case "720p":
|
|
1104
1113
|
case "medium":
|
|
1105
|
-
// Target HD, allow falling back to SD
|
|
1106
1114
|
qualitySelector = QualitySelector.fromOrderedList(
|
|
1107
1115
|
Arrays.asList(Quality.HD, Quality.SD),
|
|
1108
1116
|
FallbackStrategy.lowerQualityOrHigherThan(Quality.HD)
|
|
1109
1117
|
);
|
|
1110
1118
|
break;
|
|
1119
|
+
case "480p":
|
|
1120
|
+
case "low":
|
|
1121
|
+
qualitySelector = QualitySelector.fromOrderedList(
|
|
1122
|
+
Arrays.asList(Quality.SD, Quality.LOWEST),
|
|
1123
|
+
FallbackStrategy.lowerQualityOrHigherThan(Quality.SD)
|
|
1124
|
+
);
|
|
1125
|
+
break;
|
|
1111
1126
|
case "high":
|
|
1127
|
+
case "4:3":
|
|
1112
1128
|
default:
|
|
1113
|
-
// Target FHD allow falling back SD
|
|
1114
1129
|
qualitySelector = QualitySelector.fromOrderedList(
|
|
1115
1130
|
Arrays.asList(Quality.FHD, Quality.HD, Quality.SD),
|
|
1116
1131
|
FallbackStrategy.higherQualityOrLowerThan(Quality.FHD)
|
|
@@ -1118,7 +1133,12 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1118
1133
|
break;
|
|
1119
1134
|
}
|
|
1120
1135
|
|
|
1121
|
-
Recorder
|
|
1136
|
+
Recorder.Builder recorderBuilder = new Recorder.Builder().setQualitySelector(qualitySelector);
|
|
1137
|
+
String videoCodec = sessionConfig.getVideoCodec() != null ? sessionConfig.getVideoCodec() : "avc1";
|
|
1138
|
+
if ("hvc1".equalsIgnoreCase(videoCodec)) {
|
|
1139
|
+
recorderBuilder.setVideoCapabilitiesSource(Recorder.VIDEO_CAPABILITIES_SOURCE_CODEC_CAPABILITIES);
|
|
1140
|
+
}
|
|
1141
|
+
Recorder recorder = recorderBuilder.build();
|
|
1122
1142
|
videoCapture = VideoCapture.withOutput(recorder);
|
|
1123
1143
|
}
|
|
1124
1144
|
|
|
@@ -4644,7 +4664,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
4644
4664
|
}
|
|
4645
4665
|
|
|
4646
4666
|
/** @noinspection ResultOfMethodCallIgnored*/
|
|
4647
|
-
public void startRecordVideo() throws Exception {
|
|
4667
|
+
public void startRecordVideo(Long maxDurationMillis, Long maxFileSize) throws Exception {
|
|
4648
4668
|
if (videoCapture == null) {
|
|
4649
4669
|
throw new Exception("VideoCapture is not initialized");
|
|
4650
4670
|
}
|
|
@@ -4666,7 +4686,14 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
4666
4686
|
}
|
|
4667
4687
|
currentVideoFile = new File(outputDir, fileName);
|
|
4668
4688
|
|
|
4669
|
-
FileOutputOptions
|
|
4689
|
+
FileOutputOptions.Builder outputOptionsBuilder = new FileOutputOptions.Builder(currentVideoFile);
|
|
4690
|
+
if (maxDurationMillis != null && maxDurationMillis > 0) {
|
|
4691
|
+
outputOptionsBuilder.setDurationLimitMillis(maxDurationMillis);
|
|
4692
|
+
}
|
|
4693
|
+
if (maxFileSize != null && maxFileSize > 0) {
|
|
4694
|
+
outputOptionsBuilder.setFileSizeLimit(maxFileSize);
|
|
4695
|
+
}
|
|
4696
|
+
FileOutputOptions outputOptions = outputOptionsBuilder.build();
|
|
4670
4697
|
|
|
4671
4698
|
// Create recording event listener
|
|
4672
4699
|
androidx.core.util.Consumer<VideoRecordEvent> videoRecordEventListener = (videoRecordEvent) -> {
|
|
@@ -4712,11 +4739,18 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
4712
4739
|
}
|
|
4713
4740
|
|
|
4714
4741
|
private void handleRecordingFinalized(VideoRecordEvent.Finalize finalizeEvent) {
|
|
4715
|
-
|
|
4716
|
-
|
|
4742
|
+
String reason = getRecordingFinishReason(finalizeEvent.getError());
|
|
4743
|
+
boolean hasVideoFile = currentVideoFile != null && currentVideoFile.exists();
|
|
4744
|
+
boolean finishedWithUsableFile = !finalizeEvent.hasError() || isRecordingLimitReached(finalizeEvent.getError());
|
|
4745
|
+
|
|
4746
|
+
if (finishedWithUsableFile && hasVideoFile) {
|
|
4747
|
+
String filePath = "file://" + currentVideoFile.getAbsolutePath();
|
|
4748
|
+
Log.d(TAG, "Video recording completed: " + reason);
|
|
4717
4749
|
if (currentVideoCallback != null) {
|
|
4718
|
-
|
|
4719
|
-
|
|
4750
|
+
currentVideoCallback.onSuccess(filePath, reason);
|
|
4751
|
+
}
|
|
4752
|
+
if (listener != null) {
|
|
4753
|
+
listener.onVideoRecordingFinished(filePath, reason);
|
|
4720
4754
|
}
|
|
4721
4755
|
} else {
|
|
4722
4756
|
Log.e(TAG, "Video recording failed: " + finalizeEvent.getError());
|
|
@@ -4730,4 +4764,104 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
4730
4764
|
currentVideoFile = null;
|
|
4731
4765
|
currentVideoCallback = null;
|
|
4732
4766
|
}
|
|
4767
|
+
|
|
4768
|
+
private boolean isRecordingLimitReached(int error) {
|
|
4769
|
+
return (
|
|
4770
|
+
error == VideoRecordEvent.Finalize.ERROR_DURATION_LIMIT_REACHED ||
|
|
4771
|
+
error == VideoRecordEvent.Finalize.ERROR_FILE_SIZE_LIMIT_REACHED
|
|
4772
|
+
);
|
|
4773
|
+
}
|
|
4774
|
+
|
|
4775
|
+
private String getRecordingFinishReason(int error) {
|
|
4776
|
+
if (error == VideoRecordEvent.Finalize.ERROR_DURATION_LIMIT_REACHED) {
|
|
4777
|
+
return "maxDuration";
|
|
4778
|
+
}
|
|
4779
|
+
if (error == VideoRecordEvent.Finalize.ERROR_FILE_SIZE_LIMIT_REACHED) {
|
|
4780
|
+
return "maxFileSize";
|
|
4781
|
+
}
|
|
4782
|
+
return "manual";
|
|
4783
|
+
}
|
|
4784
|
+
|
|
4785
|
+
private static final List<String> ALL_VIDEO_QUALITIES = Arrays.asList("low", "medium", "high", "2160p", "1080p", "720p", "480p", "4:3");
|
|
4786
|
+
|
|
4787
|
+
private static final List<String> ALL_VIDEO_CODECS = Arrays.asList("avc1", "hvc1");
|
|
4788
|
+
|
|
4789
|
+
public String getVideoQualitySetting() {
|
|
4790
|
+
if (sessionConfig == null) {
|
|
4791
|
+
return "high";
|
|
4792
|
+
}
|
|
4793
|
+
return sessionConfig.getVideoQuality();
|
|
4794
|
+
}
|
|
4795
|
+
|
|
4796
|
+
public void setVideoQualitySetting(String quality) {
|
|
4797
|
+
if (sessionConfig == null) {
|
|
4798
|
+
throw new IllegalStateException("Camera session is not running");
|
|
4799
|
+
}
|
|
4800
|
+
String normalized = quality != null ? quality.toLowerCase(Locale.US) : "high";
|
|
4801
|
+
if (!ALL_VIDEO_QUALITIES.contains(normalized)) {
|
|
4802
|
+
throw new IllegalArgumentException("Unsupported video quality: " + quality);
|
|
4803
|
+
}
|
|
4804
|
+
if (normalized.equals(sessionConfig.getVideoQuality())) {
|
|
4805
|
+
return;
|
|
4806
|
+
}
|
|
4807
|
+
sessionConfig.setVideoQuality(normalized);
|
|
4808
|
+
if (sessionConfig.isVideoModeEnabled() && isRunning) {
|
|
4809
|
+
mainExecutor.execute(this::bindCameraUseCases);
|
|
4810
|
+
}
|
|
4811
|
+
}
|
|
4812
|
+
|
|
4813
|
+
public List<String> getSupportedVideoQualities() {
|
|
4814
|
+
return new ArrayList<>(ALL_VIDEO_QUALITIES);
|
|
4815
|
+
}
|
|
4816
|
+
|
|
4817
|
+
public String getVideoCodecSetting() {
|
|
4818
|
+
if (sessionConfig == null) {
|
|
4819
|
+
return "avc1";
|
|
4820
|
+
}
|
|
4821
|
+
return sessionConfig.getVideoCodec();
|
|
4822
|
+
}
|
|
4823
|
+
|
|
4824
|
+
public void setVideoCodecSetting(String codec) {
|
|
4825
|
+
if (sessionConfig == null) {
|
|
4826
|
+
throw new IllegalStateException("Camera session is not running");
|
|
4827
|
+
}
|
|
4828
|
+
String normalized = codec != null ? codec.toLowerCase(Locale.US) : "avc1";
|
|
4829
|
+
if (!ALL_VIDEO_CODECS.contains(normalized)) {
|
|
4830
|
+
throw new IllegalArgumentException("Unsupported video codec: " + codec);
|
|
4831
|
+
}
|
|
4832
|
+
if (normalized.equals(sessionConfig.getVideoCodec())) {
|
|
4833
|
+
return;
|
|
4834
|
+
}
|
|
4835
|
+
sessionConfig.setVideoCodec(normalized);
|
|
4836
|
+
if (sessionConfig.isVideoModeEnabled() && isRunning) {
|
|
4837
|
+
mainExecutor.execute(this::bindCameraUseCases);
|
|
4838
|
+
}
|
|
4839
|
+
}
|
|
4840
|
+
|
|
4841
|
+
public List<String> getSupportedVideoCodecs() {
|
|
4842
|
+
List<String> codecs = new ArrayList<>();
|
|
4843
|
+
codecs.add("avc1");
|
|
4844
|
+
if (isVideoCodecSupported(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
|
|
4845
|
+
codecs.add("hvc1");
|
|
4846
|
+
}
|
|
4847
|
+
return codecs;
|
|
4848
|
+
}
|
|
4849
|
+
|
|
4850
|
+
private boolean isVideoCodecSupported(String mimeType) {
|
|
4851
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
|
4852
|
+
return false;
|
|
4853
|
+
}
|
|
4854
|
+
MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
|
|
4855
|
+
for (android.media.MediaCodecInfo codecInfo : codecList.getCodecInfos()) {
|
|
4856
|
+
if (!codecInfo.isEncoder()) {
|
|
4857
|
+
continue;
|
|
4858
|
+
}
|
|
4859
|
+
for (String type : codecInfo.getSupportedTypes()) {
|
|
4860
|
+
if (mimeType.equalsIgnoreCase(type)) {
|
|
4861
|
+
return true;
|
|
4862
|
+
}
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
return false;
|
|
4866
|
+
}
|
|
4733
4867
|
}
|