@capgo/camera-preview 7.7.0 → 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.
@@ -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-beta01"
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,6 +80,7 @@ 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
85
86
  public void getExposureModes(PluginCall call) {
@@ -689,6 +690,7 @@ public class CameraPreview
689
690
  final boolean disableAudio = Boolean.TRUE.equals(
690
691
  call.getBoolean("disableAudio", true)
691
692
  );
693
+ this.lastDisableAudio = disableAudio;
692
694
  final String aspectRatio = call.getString("aspectRatio", "4:3");
693
695
  final String gridMode = call.getString("gridMode", "none");
694
696
  final String positioning = call.getString("positioning", "top");
@@ -1766,6 +1768,101 @@ public class CameraPreview
1766
1768
  }
1767
1769
  }
1768
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
+
1769
1866
  @PluginMethod
1770
1867
  public void getSafeAreaInsets(PluginCall call) {
1771
1868
  JSObject ret = new JSObject();
@@ -56,6 +56,14 @@ import androidx.camera.core.resolutionselector.AspectRatioStrategy;
56
56
  import androidx.camera.core.resolutionselector.ResolutionSelector;
57
57
  import androidx.camera.core.resolutionselector.ResolutionStrategy;
58
58
  import androidx.camera.lifecycle.ProcessCameraProvider;
59
+ import androidx.camera.video.FallbackStrategy;
60
+ import androidx.camera.video.FileOutputOptions;
61
+ import androidx.camera.video.Quality;
62
+ import androidx.camera.video.QualitySelector;
63
+ import androidx.camera.video.Recorder;
64
+ import androidx.camera.video.Recording;
65
+ import androidx.camera.video.VideoCapture;
66
+ import androidx.camera.video.VideoRecordEvent;
59
67
  import androidx.camera.view.PreviewView;
60
68
  import androidx.core.content.ContextCompat;
61
69
  import androidx.exifinterface.media.ExifInterface;
@@ -99,10 +107,19 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
99
107
  void onCameraStartError(String message);
100
108
  }
101
109
 
110
+ public interface VideoRecordingCallback {
111
+ void onSuccess(String filePath);
112
+ void onError(String message);
113
+ }
114
+
102
115
  private ProcessCameraProvider cameraProvider;
103
116
  private Camera camera;
104
117
  private ImageCapture imageCapture;
105
118
  private ImageCapture sampleImageCapture;
119
+ private VideoCapture<Recorder> videoCapture;
120
+ private Recording currentRecording;
121
+ private File currentVideoFile;
122
+ private VideoRecordingCallback currentVideoCallback;
106
123
  private PreviewView previewView;
107
124
  private GridOverlayView gridOverlayView;
108
125
  private FrameLayout previewContainer;
@@ -725,6 +742,17 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
725
742
  .setTargetRotation(rotation)
726
743
  .build();
727
744
  sampleImageCapture = imageCapture;
745
+
746
+ // Setup VideoCapture with rotation and quality fallback
747
+ QualitySelector qualitySelector = QualitySelector.fromOrderedList(
748
+ Arrays.asList(Quality.FHD, Quality.HD, Quality.SD),
749
+ FallbackStrategy.higherQualityOrLowerThan(Quality.FHD)
750
+ );
751
+ Recorder recorder = new Recorder.Builder()
752
+ .setQualitySelector(qualitySelector)
753
+ .build();
754
+ videoCapture = VideoCapture.withOutput(recorder);
755
+
728
756
  preview.setSurfaceProvider(previewView.getSurfaceProvider());
729
757
  // Unbind any existing use cases and bind new ones
730
758
  cameraProvider.unbindAll();
@@ -732,7 +760,8 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
732
760
  this,
733
761
  currentCameraSelector,
734
762
  preview,
735
- imageCapture
763
+ imageCapture,
764
+ videoCapture
736
765
  );
737
766
 
738
767
  // Log details about the active camera
@@ -3702,4 +3731,103 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
3702
3731
  Log.e(TAG, "triggerAutoFocus: Failed to trigger autofocus", e);
3703
3732
  }
3704
3733
  }
3734
+
3735
+ public void startRecordVideo() throws Exception {
3736
+ if (videoCapture == null) {
3737
+ throw new Exception("VideoCapture is not initialized");
3738
+ }
3739
+
3740
+ if (currentRecording != null) {
3741
+ throw new Exception("Video recording is already in progress");
3742
+ }
3743
+
3744
+ // Create output file
3745
+ String fileName = "video_" + System.currentTimeMillis() + ".mp4";
3746
+ File outputDir = new File(
3747
+ context.getExternalFilesDir(Environment.DIRECTORY_MOVIES),
3748
+ "CameraPreview"
3749
+ );
3750
+ if (!outputDir.exists()) {
3751
+ outputDir.mkdirs();
3752
+ }
3753
+ currentVideoFile = new File(outputDir, fileName);
3754
+
3755
+ FileOutputOptions outputOptions = new FileOutputOptions.Builder(
3756
+ currentVideoFile
3757
+ ).build();
3758
+
3759
+ // Create recording event listener
3760
+ androidx.core.util.Consumer<VideoRecordEvent> videoRecordEventListener =
3761
+ videoRecordEvent -> {
3762
+ if (videoRecordEvent instanceof VideoRecordEvent.Start) {
3763
+ Log.d(TAG, "Video recording started");
3764
+ } else if (videoRecordEvent instanceof VideoRecordEvent.Finalize) {
3765
+ VideoRecordEvent.Finalize finalizeEvent =
3766
+ (VideoRecordEvent.Finalize) videoRecordEvent;
3767
+ handleRecordingFinalized(finalizeEvent);
3768
+ }
3769
+ };
3770
+
3771
+ // Start recording
3772
+ if (sessionConfig != null && !sessionConfig.isDisableAudio()) {
3773
+ currentRecording = videoCapture
3774
+ .getOutput()
3775
+ .prepareRecording(context, outputOptions)
3776
+ .withAudioEnabled()
3777
+ .start(
3778
+ ContextCompat.getMainExecutor(context),
3779
+ videoRecordEventListener
3780
+ );
3781
+ } else {
3782
+ currentRecording = videoCapture
3783
+ .getOutput()
3784
+ .prepareRecording(context, outputOptions)
3785
+ .start(
3786
+ ContextCompat.getMainExecutor(context),
3787
+ videoRecordEventListener
3788
+ );
3789
+ }
3790
+
3791
+ Log.d(
3792
+ TAG,
3793
+ "Video recording started to: " + currentVideoFile.getAbsolutePath()
3794
+ );
3795
+ }
3796
+
3797
+ public void stopRecordVideo(VideoRecordingCallback callback) {
3798
+ if (currentRecording == null) {
3799
+ callback.onError("No video recording in progress");
3800
+ return;
3801
+ }
3802
+
3803
+ // Store the callback to use when recording is finalized
3804
+ currentVideoCallback = callback;
3805
+ currentRecording.stop();
3806
+
3807
+ Log.d(TAG, "Video recording stop requested");
3808
+ }
3809
+
3810
+ private void handleRecordingFinalized(
3811
+ VideoRecordEvent.Finalize finalizeEvent
3812
+ ) {
3813
+ if (!finalizeEvent.hasError()) {
3814
+ Log.d(TAG, "Video recording completed successfully");
3815
+ if (currentVideoCallback != null) {
3816
+ String filePath = "file://" + currentVideoFile.getAbsolutePath();
3817
+ currentVideoCallback.onSuccess(filePath);
3818
+ }
3819
+ } else {
3820
+ Log.e(TAG, "Video recording failed: " + finalizeEvent.getError());
3821
+ if (currentVideoCallback != null) {
3822
+ currentVideoCallback.onError(
3823
+ "Video recording failed: " + finalizeEvent.getError()
3824
+ );
3825
+ }
3826
+ }
3827
+
3828
+ // Clean up
3829
+ currentRecording = null;
3830
+ currentVideoFile = null;
3831
+ currentVideoCallback = null;
3832
+ }
3705
3833
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/camera-preview",
3
- "version": "7.7.0",
3
+ "version": "7.8.0",
4
4
  "description": "Camera preview",
5
5
  "license": "MIT",
6
6
  "repository": {