Skip to content
Open
1 change: 1 addition & 0 deletions plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<framework src="androidx.camera:camera-camera2:1.1.0" />
<framework src="androidx.camera:camera-lifecycle:1.1.0" />
<framework src="androidx.camera:camera-view:1.1.0" />
<framework src="androidx.camera:camera-video:1.1.0" />

<source-file src="src/android/CameraPreviewFragment.java" target-dir="src/com/spoon/simplecamerapreview" />
<source-file src="src/android/SimpleCameraPreview.java" target-dir="src/com/spoon/simplecamerapreview" />
Expand Down
93 changes: 86 additions & 7 deletions src/android/CameraPreviewFragment.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package com.spoon.simplecamerapreview;

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Point;
import android.hardware.camera2.CameraCharacteristics;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
Expand All @@ -29,8 +33,17 @@
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.video.FileOutputOptions;
import androidx.camera.video.Quality;
import androidx.camera.video.QualitySelector;
import androidx.camera.video.Recorder;
import androidx.camera.video.Recording;
import androidx.camera.video.VideoCapture;
import androidx.camera.video.VideoRecordEvent;
import androidx.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import androidx.exifinterface.media.ExifInterface;
import androidx.fragment.app.Fragment;

Expand All @@ -51,6 +64,11 @@ interface CameraCallback {
void onCompleted(Exception err, String nativePath);
}

interface VideoCallback {
void onStart(Boolean recording, String nativePath);
void onStop(Boolean recording, String nativePath);
}

interface CameraStartedCallback {
void onCameraStarted(Exception err);
}
Expand All @@ -76,6 +94,9 @@ public class CameraPreviewFragment extends Fragment {
private PreviewView viewFinder;
private Preview preview;
private ImageCapture imageCapture;
private VideoCapture<Recorder> videoCapture;
Recording recording = null;
ProcessCameraProvider cameraProvider = null;
private Camera camera;
private CameraStartedCallback startCameraCallback;
private Location location;
Expand Down Expand Up @@ -120,13 +141,11 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
viewFinder.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT));
containerView.addView(viewFinder);
startCamera();

return containerView;
}

public void startCamera() {
ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(getActivity());
ProcessCameraProvider cameraProvider = null;

try {
cameraProvider = cameraProviderFuture.get();
Expand All @@ -136,8 +155,8 @@ public void startCamera() {
startCameraCallback.onCameraStarted(new Exception("Unable to start camera"));
return;
}
setUpCamera(captureDevice,cameraProvider);

setUpCamera(captureDevice,cameraProvider);
preview.setSurfaceProvider(viewFinder.getSurfaceProvider());

if (startCameraCallback != null) {
Expand Down Expand Up @@ -258,6 +277,60 @@ public void hasFlash(HasFlashCallback hasFlashCallback) {
hasFlashCallback.onResult(camera.getCameraInfo().hasFlashUnit());
}

public void stopCaptureVideo() {
if (recording != null) {
recording.stop();
recording = null;
}
}

public void captureVideo(VideoCallback videoCallback) {
if (recording != null) {
recording.stop();
recording = null;
return;
}
UUID uuid = UUID.randomUUID();

String filename = uuid.toString() + ".mp4";
if (ActivityCompat.checkSelfPermission(this.getContext(), Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this.getActivity(), new String[]{Manifest.permission.RECORD_AUDIO}, 200);
}
File videoFile = new File(
getContext().getFilesDir(),
filename
);

FileOutputOptions outputOptions = new FileOutputOptions.Builder(videoFile).build();

recording = videoCapture.getOutput()
.prepareRecording(this.getContext().getApplicationContext(), outputOptions)
.withAudioEnabled()
.start(ContextCompat.getMainExecutor(this.getContext()), videoRecordEvent -> {
if (videoRecordEvent instanceof VideoRecordEvent.Start) {
videoCallback.onStart(true, null);
} else if (videoRecordEvent instanceof VideoRecordEvent.Finalize) {
VideoRecordEvent.Finalize finalizeEvent = (VideoRecordEvent.Finalize) videoRecordEvent;
if (finalizeEvent.hasError()) {
// Handle the error
int errorCode = finalizeEvent.getError();
Throwable errorCause = finalizeEvent.getCause();
Log.e(TAG, "Video recording error: " + errorCode, errorCause);
} else {
// Handle video saved
videoCallback.onStop(false, Uri.fromFile(videoFile).toString());
Uri savedUri = finalizeEvent.getOutputResults().getOutputUri();
Log.i(TAG, "Video saved to: " + savedUri);
}
recording = null;
}
// Other event types can be handled if needed
});

}



public void takePicture(boolean useFlash, CameraCallback takePictureCallback) {
if (torchActivated) {
useFlash = true;
Expand Down Expand Up @@ -398,18 +471,24 @@ public void setUpCamera(String captureDevice, ProcessCameraProvider cameraProvid
targetResolution = CameraPreviewFragment.calculateResolution(getContext(), targetSize);
}

Recorder recorder = new Recorder.Builder()
.setQualitySelector(QualitySelector.from(Quality.LOWEST))
.build();
videoCapture = VideoCapture.withOutput(recorder);


preview = new Preview.Builder().build();
imageCapture = new ImageCapture.Builder()
.setTargetResolution(targetResolution)
.build();

cameraProvider.unbindAll();
try {
camera = cameraProvider.bindToLifecycle(
getActivity(),
cameraSelector,
preview,
imageCapture
imageCapture,
videoCapture
);
} catch (IllegalArgumentException e) {
// Error with result in capturing image with default resolution
Expand All @@ -420,9 +499,9 @@ public void setUpCamera(String captureDevice, ProcessCameraProvider cameraProvid
getActivity(),
cameraSelector,
preview,
imageCapture
imageCapture,
videoCapture
);
}

}
}
68 changes: 68 additions & 0 deletions src/android/SimpleCameraPreview.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,15 @@ public class SimpleCameraPreview extends CordovaPlugin {
private LocationListener mLocationCallback;
private ViewParent webViewParent;

private CallbackContext videoCallback;

private static final int containerViewId = 20;
private static final int DIRECTION_FRONT = 0;
private static final int DIRECTION_BACK = 1;
private static final int REQUEST_CODE_PERMISSIONS = 4582679;
private static final String[] REQUIRED_PERMISSIONS = {Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION};


public SimpleCameraPreview() {
super();
}
Expand All @@ -72,6 +75,13 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo
case "torchSwitch":
return torchSwitch(args.getBoolean(0), callbackContext);

case "captureVideo":
return captureVideo(callbackContext);

case "stopCaptureVideo":
this.videoStopCallback = callbackContext;
return stopCaptureVideo(callbackContext);

case "deviceHasFlash":
return deviceHasFlash(callbackContext);

Expand Down Expand Up @@ -267,6 +277,64 @@ public void fetchLocation() {
}
}

CallbackContext videoStopCallback;

private boolean stopCaptureVideo(CallbackContext callbackContext) {
if (fragment == null) {
callbackContext.error("Camera is closed");
return true;
}
fragment.stopCaptureVideo();
return true;
}
private boolean captureVideo(CallbackContext callbackContext) {
if (fragment == null) {
callbackContext.error("Camera is closed");
return true;
}

fragment.captureVideo(new VideoCallback() {
public void onStart(Boolean recording, String nativePath) {
JSONObject data = new JSONObject();
if (recording) {
try {
data.put("recording", true);
data.put("nativePath", null);
} catch (JSONException e) {
e.printStackTrace();
callbackContext.error("Cannot send recording data");
return;
}

PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, data);
// pluginResult.setKeepCallback(true);
callbackContext.sendPluginResult(pluginResult);
}
}

public void onStop(Boolean recording, String nativePath) {
JSONObject data = new JSONObject();
try {
data.put("recording", false);
data.put("nativePath", nativePath);
} catch (JSONException e) {
e.printStackTrace();
callbackContext.error("Cannot send recording data");
return;
}
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, data);
// pluginResult.setKeepCallback(true);
if (videoStopCallback != null) {
videoStopCallback.sendPluginResult(pluginResult);
videoStopCallback = null;
return;
}
callbackContext.sendPluginResult(pluginResult);
}
});
return true;
}

private boolean capture(boolean useFlash, CallbackContext callbackContext) {
if (fragment == null) {
callbackContext.error("Camera is closed");
Expand Down
8 changes: 8 additions & 0 deletions www/SimpleCameraPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ SimpleCameraPreview.capture = function (options, onSuccess, onError) {
exec(onSuccess, onError, PLUGIN_NAME, "capture", [options.flash]);
};

SimpleCameraPreview.captureVideo = function (onSuccess, onError) {
exec(onSuccess, onError, PLUGIN_NAME, "captureVideo");
};

SimpleCameraPreview.stopCaptureVideo = function (onSuccess, onError) {
exec(onSuccess, onError, PLUGIN_NAME, "stopCaptureVideo");
};

SimpleCameraPreview.setSize = function (options, onSuccess, onError) {
exec(onSuccess, onError, PLUGIN_NAME, "setSize", [options]);
};
Expand Down