Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList

private final String pluginVersion = "";

/**
* Handle the plugin's pause lifecycle by pausing face detection and stopping the camera preview.
*
* Pauses the FaceDetectionManager if present, saves the current CameraSessionConfiguration to
* `lastSessionConfig`, and stops the running CameraXView session so the camera is released while
* the app is in the background.
*/
@Override
protected void handleOnPause() {
super.handleOnPause();
Expand All @@ -81,6 +88,11 @@ protected void handleOnPause() {
}
}

/**
* Restarts the camera session and resumes face detection when the plugin returns to the foreground.
*
* If a previous session configuration exists, recreates the CameraXView if needed, restores its listener, and starts the session. Also notifies the face detection manager that the app has moved to the foreground.
*/
@Override
protected void handleOnResume() {
super.handleOnResume();
Expand Down Expand Up @@ -1978,6 +1990,11 @@ private int getStatusBarHeightPx() {
return result;
}

/**
* Provide the plugin's version string.
*
* Resolves the call with a JS object containing the "version" property.
*/
@PluginMethod
public void getPluginVersion(final PluginCall call) {
try {
Expand All @@ -1995,6 +2012,17 @@ public void getPluginVersion(final PluginCall call) {

private FaceDetectionManager faceDetectionManager;

/**
* Starts face detection for the running camera preview using options supplied by the plugin call.
*
* Reads optional parameters from the call (performanceMode, trackingEnabled, detectLandmarks,
* detectClassifications, maxFaces, minFaceSize), ensures a FaceDetectionManager exists, begins
* detection with a listener that emits "faceDetection" events, enables face detection on the
* active CameraXView, and resolves the call when detection is started. If the camera preview is
* not running the call is rejected; any startup failure rejects the call with an error message.
*
* @param call the PluginCall containing optional face detection configuration
*/
@PluginMethod
public void startFaceDetection(final PluginCall call) {
if (cameraXView == null || !cameraXView.isRunning()) {
Expand Down Expand Up @@ -2060,6 +2088,13 @@ public void onFaceDetectionError(String error) {
}
}

/**
* Stops face detection if active and disables face detection on the camera preview.
*
* Resolves the provided PluginCall on success; rejects the call with an error message if stopping fails.
*
* @param call the PluginCall used to report success or failure
*/
@PluginMethod
public void stopFaceDetection(final PluginCall call) {
try {
Expand All @@ -2075,11 +2110,16 @@ public void stopFaceDetection(final PluginCall call) {
}
}

/**
* Reports whether face detection is currently active.
*
* @return a JSObject containing `isDetecting`: `true` if face detection is running, `false` otherwise.
*/
@PluginMethod
public void isFaceDetectionRunning(final PluginCall call) {
boolean isDetecting = faceDetectionManager != null && faceDetectionManager.isRunning();
JSObject ret = new JSObject();
ret.put("isDetecting", isDetecting);
call.resolve(ret);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,14 @@ private void removePreviewView() {
webView.setBackgroundColor(android.graphics.Color.WHITE);
}

/**
* Binds and configures CameraX use cases (Preview, ImageCapture and, when enabled, VideoCapture and ImageAnalysis)
* according to the current session configuration and starts the camera lifecycle.
*
* <p>On success this sets internal running state, applies configured zoom/flash/exposure defaults, updates preview
* scale/rotation and invokes the listener's onCameraStarted callback with the actual preview width, height and
* position (x, y). On failure the listener's onCameraStartError is invoked with an error message.</p>
*/
@OptIn(markerClass = ExperimentalCamera2Interop.class)
private void bindCameraUseCases() {
if (cameraProvider == null) return;
Expand Down Expand Up @@ -3513,6 +3521,14 @@ public void stopRecordVideo(VideoRecordingCallback callback) {
Log.d(TAG, "Video recording stop requested");
}

/**
* Handle completion of a video recording and notify the registered callback.
*
* Notifies the current video recording callback with the recorded file URI on success
* or an error message on failure, then clears recording-related state fields.
*
* @param finalizeEvent the finalize event containing the recording result and any error
*/
private void handleRecordingFinalized(VideoRecordEvent.Finalize finalizeEvent) {
if (!finalizeEvent.hasError()) {
Log.d(TAG, "Video recording completed successfully");
Expand All @@ -3535,7 +3551,13 @@ private void handleRecordingFinalized(VideoRecordEvent.Finalize finalizeEvent) {

// ========================================
// Face Detection Methods
// ========================================
/**
* Enables face detection by attaching the provided FaceDetectionManager and binding an ImageAnalysis use case to deliver camera frames.
*
* If an ImageAnalysis use case does not already exist, one is created and an analyzer is set to forward frames to the manager when it is running. The camera is then rebound with the analysis use case active.
*
* @param manager the FaceDetectionManager that will receive camera frames for face detection and processing
*/

public void enableFaceDetection(FaceDetectionManager manager) {
this.faceDetectionManager = manager;
Expand All @@ -3559,6 +3581,12 @@ public void enableFaceDetection(FaceDetectionManager manager) {
rebindCameraWithAnalysis();
}

/**
* Disables face detection for this CameraXView.
*
* Clears the configured FaceDetectionManager and stops feeding frames to any analyzer, then rebinds
* camera use cases without the ImageAnalysis use case so face detection processing ceases.
*/
public void disableFaceDetection() {
this.faceDetectionManager = null;
if (imageAnalysis != null) {
Expand All @@ -3568,6 +3596,11 @@ public void disableFaceDetection() {
rebindCameraWithoutAnalysis();
}

/**
* Rebinds the active camera use cases to include ImageAnalysis when present so face-detection frames are delivered.
*
* If the camera provider is not initialized or the view is not running, this method returns without action.
*/
private void rebindCameraWithAnalysis() {
if (cameraProvider == null || !isRunning) {
return;
Expand Down Expand Up @@ -3602,6 +3635,13 @@ private void rebindCameraWithAnalysis() {
}
}

/**
* Rebinds the camera use cases to the lifecycle without the ImageAnalysis use case (disables face detection).
*
* If the camera provider is unavailable or the view is not running, this method returns without action.
* It unbinds all existing use cases, creates and binds a Preview plus any active ImageCapture and VideoCapture
* use cases, and logs success or failure.
*/
private void rebindCameraWithoutAnalysis() {
if (cameraProvider == null || !isRunning) {
return;
Expand Down Expand Up @@ -3632,4 +3672,4 @@ private void rebindCameraWithoutAnalysis() {
Log.e(TAG, "Failed to rebind camera without analysis", e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,17 @@ public FaceAlignmentValidator() {
}

/**
* Create validator with custom thresholds
* Constructs a FaceAlignmentValidator using the provided threshold and bound values.
*
* @param maxRollDegrees maximum allowed absolute roll angle in degrees before roll is considered misaligned
* @param maxPitchDegrees maximum allowed absolute pitch angle in degrees before pitch is considered misaligned
* @param maxYawDegrees maximum allowed absolute yaw angle in degrees before yaw is considered misaligned
* @param minFaceSize minimum allowed face size (fraction of frame width/height) before the face is considered too small
* @param maxFaceSize maximum allowed face size (fraction of frame width/height) before the face is considered too large
* @param minCenterX minimum allowed normalized X coordinate of the face center
* @param maxCenterX maximum allowed normalized X coordinate of the face center
* @param minCenterY minimum allowed normalized Y coordinate of the face center
* @param maxCenterY maximum allowed normalized Y coordinate of the face center
*/
public FaceAlignmentValidator(
double maxRollDegrees,
Expand All @@ -70,16 +80,17 @@ public FaceAlignmentValidator(
}

/**
* Validate face alignment
* Validate face alignment and produce per-aspect validity flags and feedback.
*
* @param rollAngle Head roll angle in degrees (tilt left/right)
* @param pitchAngle Head pitch angle in degrees (nod up/down)
* @param yawAngle Head yaw angle in degrees (turn left/right)
* @param boundsX Face bounding box X (normalized 0-1)
* @param boundsY Face bounding box Y (normalized 0-1)
* @param boundsWidth Face bounding box width (normalized 0-1)
* @param boundsHeight Face bounding box height (normalized 0-1)
* @return Validation result with detailed feedback
* @param rollAngle Head roll angle in degrees; positive values indicate tilt to the right.
* @param pitchAngle Head pitch angle in degrees; positive values indicate looking down.
* @param yawAngle Head yaw angle in degrees; positive values indicate turning to the right.
* @param boundsX Face bounding box X coordinate normalized to [0,1] relative to the frame.
* @param boundsY Face bounding box Y coordinate normalized to [0,1] relative to the frame.
* @param boundsWidth Face bounding box width normalized to [0,1] relative to the frame.
* @param boundsHeight Face bounding box height normalized to [0,1] relative to the frame.
* @return AlignmentResult containing overall validity, individual validation flags (roll, pitch, yaw, size, centering),
* and optional human-readable feedback messages for any failing checks.
*/
public AlignmentResult validate(
float rollAngle,
Expand Down Expand Up @@ -176,7 +187,10 @@ public static class AlignmentResult {
public String centeringFeedback = null;

/**
* Get primary feedback message (first issue found)
* Selects the most important face-alignment feedback message.
*
* @return the first non-null specific feedback in this order: roll, pitch, yaw, size, centering;
* if no specific feedback is present, returns "Face aligned perfectly".
*/
public String getPrimaryFeedback() {
if (rollFeedback != null) return rollFeedback;
Expand All @@ -188,7 +202,9 @@ public String getPrimaryFeedback() {
}

/**
* Get all feedback messages
* Collects all non-null per-aspect feedback messages in priority order.
*
* @return an array containing all non-null feedback messages in priority order: roll, pitch, yaw, size, centering
*/
public String[] getAllFeedback() {
java.util.ArrayList<String> feedback = new java.util.ArrayList<>();
Expand All @@ -200,4 +216,4 @@ public String[] getAllFeedback() {
return feedback.toArray(new String[0]);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ public class FaceCoordinateTransformer {
private final int rotation; // 0, 90, 180, 270

/**
* Creates a coordinate transformer
* Create a transformer that maps coordinates from camera frame space to normalized preview space (0–1), accounting for rotation and aspect-ratio differences.
*
* @param frameWidth Width of camera frame in pixels
* @param frameHeight Height of camera frame in pixels
* @param previewWidth Width of preview view in pixels
* @param previewHeight Height of preview view in pixels
* @param rotation Rotation in degrees (0, 90, 180, 270)
* @param frameWidth width of the camera frame in pixels
* @param frameHeight height of the camera frame in pixels
* @param previewWidth width of the preview view in pixels
* @param previewHeight height of the preview view in pixels
* @param rotation rotation in degrees; one of 0, 90, 180, or 270
*/
public FaceCoordinateTransformer(int frameWidth, int frameHeight, int previewWidth, int previewHeight, int rotation) {
this.frameWidth = frameWidth;
Expand All @@ -50,11 +50,12 @@ public FaceCoordinateTransformer(int frameWidth, int frameHeight, int previewWid
}

/**
* Transform a bounding box from frame coordinates to normalized preview coordinates (0-1)
*
* @param frameBounds Bounding box in frame pixel coordinates
* @return Normalized bounds (x, y, width, height) in range 0-1 relative to preview
*/
* Transforms a bounding box from camera frame pixel coordinates into normalized preview coordinates (0–1),
* accounting for rotation and aspect-ratio differences (letterboxing/pillarboxing).
*
* @param frameBounds Bounding box in camera frame pixel coordinates
* @return Normalized bounds (x, y, width, height) in the range 0–1 relative to the preview
*/
@NonNull
public NormalizedRect transformBounds(@NonNull Rect frameBounds) {
// Step 1: Normalize to frame (0-1)
Expand Down Expand Up @@ -130,10 +131,11 @@ public NormalizedRect transformBounds(@NonNull Rect frameBounds) {
}

/**
* Transform a point from frame coordinates to normalized preview coordinates (0-1)
* Convert a point from camera frame pixel coordinates into normalized preview coordinates (0–1),
* accounting for rotation and aspect-ratio adjustments.
*
* @param framePoint Point in frame pixel coordinates
* @return Normalized point (x, y) in range 0-1 relative to preview
* @param framePoint the point in frame pixel coordinates
* @return the normalized point with `x` and `y` in the range 0 to 1 relative to the preview
*/
@NonNull
public NormalizedPoint transformPoint(@NonNull PointF framePoint) {
Expand All @@ -153,6 +155,14 @@ public static class NormalizedRect {
public final double width;
public final double height;

/**
* Create a NormalizedRect using normalized preview-space coordinates.
*
* @param x the horizontal coordinate of the rectangle's top-left corner in the preview, where 0.0 is left and 1.0 is right
* @param y the vertical coordinate of the rectangle's top-left corner in the preview, where 0.0 is top and 1.0 is bottom
* @param width the rectangle's width as a fraction of the preview width (0.0–1.0 for fully normalized sizes)
* @param height the rectangle's height as a fraction of the preview height (0.0–1.0 for fully normalized sizes)
*/
public NormalizedRect(double x, double y, double width, double height) {
this.x = x;
this.y = y;
Expand All @@ -169,9 +179,15 @@ public static class NormalizedPoint {
public final double x;
public final double y;

/**
* Creates a NormalizedPoint representing a point in normalized preview coordinates.
*
* @param x the horizontal coordinate in preview space, where 0.0 is the left edge and 1.0 is the right edge
* @param y the vertical coordinate in preview space, where 0.0 is the top edge and 1.0 is the bottom edge
*/
public NormalizedPoint(double x, double y) {
this.x = x;
this.y = y;
}
}
}
}
Loading