Skip to content

Conversation

@dpa99c
Copy link

@dpa99c dpa99c commented Jun 22, 2024

Problem

The Android implementation currently relies on the deprecated Camera (v1) API. On newer devices this can cause mismatches between the preview/captured image and the stock camera app, and in some cases v1 is no longer fully supported. This has led to real-world issues like incorrect preview/capture framing (see #275).

At the same time, Camera2 support is inconsistent across the Android ecosystem. Some devices expose full Camera2 capabilities while others (including newer but lower-end models) deliberately hobble Camera2 to legacy/limited modes. We therefore need a Camera2 implementation with feature detection and a robust Camera1 fallback, rather than a hard switch.

In addition, there is no cross‑platform zoom API, and the plugin does not yet expose camera capability discovery needed for future per‑lens selection (see #256).

Analysis

This PR contains a rewrite of the Android implementation to use the Camera v2 API instead of the deprecated Camera API.

The main reason for this change is to fix issues where the preview/captured image from this plugin did not correspond to the preview/captured image from the default Camera app. This addresses #275.

It also opens the way to fetch the device's camera details and select a specific camera/lens based on these (see #256). While the fetch side is implemented by this PR (getCameraCharacteristics()), the selection of a specific camera/lens is not (as it was not needed for this use case).

It also implements getZoom(), setZoom(), and getMaxZoom() on both Android and iOS, reworking prior work from #326 on Android to work with the Camera v2 API.

Solution

This PR rewrites the Android implementation to use the Camera2 API by default, with runtime detection to fall back to Camera1 when Camera2 is unavailable or inadequate. The Camera2 path aligns preview and capture behavior with the platform camera app, addressing the framing mismatch in #275.

It also introduces a cross‑platform zoom surface (getZoom, setZoom, getMaxZoom, plus getMaxZoomLimit/setMaxZoomLimit) and exposes Android camera characteristics via getCameraCharacteristics() so future lens selection is possible without re‑architecting.

Implementation details

Android (Camera2 + fallback)

  • android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java

    • Routes all calls to Camera1 vs Camera2 based on support detection and setApi.
    • Camera2 support detection now runs on API 21+ with graceful fallback.
  • android/src/main/java/com/ahm/capacitor/camera/preview/camera2api/Camera2Activity.java

    • New Camera2 fragment implementing preview, capture, record, zoom, and orientation handling.
    • Adds robust session lifecycle management, zoom, and flash mode mapping.
  • android/src/main/java/com/ahm/capacitor/camera/preview/camera2api/Camera2Preview.java

    • New Camera2 bridge layer handling plugin calls, timeouts, and camera characteristics output.
  • android/src/main/java/com/ahm/capacitor/camera/preview/camera1api/Camera1Preview.java

    • Camera1 bridge layer with call lifecycle fixes and zoom APIs.
  • android/src/main/java/com/ahm/capacitor/camera/preview/camera1api/Camera1Activity.java

    • Legacy Camera1 implementation moved under camera1api with refactored constants.
  • android/src/main/java/com/ahm/capacitor/camera/preview/TapGestureDetector.java

    • Made public for reuse by Camera2.

iOS

  • ios/Sources/CameraPreviewPlugin/CameraPreviewPlugin.swift

    • Wires zoom/max‑zoom‑limit into start and replaces hardcoded /2 scaling with UIScreen.main.scale.
  • ios/Sources/CameraPreviewPlugin/CameraController.swift

    • Adds zoom/max‑zoom‑limit and guards against force‑unwrap crashes.

Web + TypeScript

  • src/definitions.ts

    • Adds zoom APIs and maxZoomLimit typing to match native signatures.
  • src/web.ts

    • Web stubs for new APIs with consistent parameter names.
  • README.md

    • Documents zoom APIs and max zoom limit option.

Review comments

All substantive review comments have been addressed in the updated implementation. Highlights include:

  • Call lifecycle fixes: setOpacity now resolves correctly on Camera2; start/stop record callbacks follow the actual recorder state.
  • Crash prevention: guarded updatePreview, getCurrentZoomLevel, and null‑safe handling of camera characteristics and preview sizes.
  • API consistency: maxZoomLimit parameter naming aligned across native, TS, web, and docs.
  • Camera2 flash support: mapped semantic flash modes (off/on/auto/red‑eye/torch) to Camera2 AE/FLASH modes, with safe parsing.
  • Device compatibility: Camera2 support detection now runs on API 21+ and falls back safely.
  • iOS correctness: removed force‑unwraps and fixed preview coordinate scaling for non‑2x screens.

One minor stylistic suggestion (removing a redundant null check in Camera2 stop cleanup) was left intentionally as harmless defensive code.

@forgr-owner
Copy link

Hey @dpa99c feel free to propose your PR here: Cap-go/capacitor-camera-preview#61
There is bounty of $100

@varshith257
Copy link

varshith257 commented Jul 7, 2024

@dpa99c Is this changes works? Maybe a demo video demonstrating this changes could be good to add in PR description.

It could get easy for @pbowyer or @arielhernandezmusa to get quick reviewed. Hope this get merged soon. I am waiting for the Camera2 API to be integrated in the capacitor soon :)

@ryaa
Copy link
Member

ryaa commented Dec 19, 2024

I am struggling to get the preview to display with the correct aspect ratio, with this PR

It seems that I faced the same issue while testing this PR with my existing implementation. Here is my preview UI
Screenshot_20241219_082741_BOSS811

and here is the photo taken/saved
B833400772_lnFJEuRMY

In my implementation I use the below camera preview options (note that x, y, width and height are not provided!)

      const footerHeight = (this.footer && this.footer.nativeElement) ? this.footer.nativeElement.offsetHeight : 44;
      const cameraPreviewOptions: CameraPreviewOptions = {
        position: 'rear',
        paddingBottom: footerHeight,
        rotateWhenOrientationChanged: true,
        lockAndroidOrientation: false,
        toBack: true,
        disableAudio: true,
        disableExifHeaderStripping: false
      };

while the test/demo app for this PR uses this https://github.com/dpa99c/capacitor-camera-preview-test/blob/master/src/views/HomePage.vue#L95 camera preview options (note that x, y, width and height are provided!) which makes the preview to be a square with the same width/height. Therefore, I think that, the camera preview works incorrectly when x, y, width and height are not provided. Please note that these are optional (see https://github.com/capacitor-community/camera-preview?tab=readme-ov-file#startoptions) and, I think, that the camera plugin should work as before when these are not provided.

@eggbeard
Copy link

I've been unable to get this code to work reliably across all a variety of devices / multiple cameras and aspect ratios. It's pretty close for some android tablets that we have especially landscape mode, but in portrait it's ever so slightly out. Then on different devices (still android) with multiple cameras - which is really where this is needed I end up either with weird aspect ratio changes or a picture that is at a significantly different zoom between the preview and the picture. I'm out of ideas

@ryaa
Copy link
Member

ryaa commented Dec 21, 2024

I've been unable to get this code to work reliably across all a variety of devices / multiple cameras and aspect ratios.

Can you please list devices and conditions where you see the problem? Thank you
I will do more testing myself and will report the same details. I hope that this should be useful information.

dpa99c and others added 20 commits March 21, 2025 10:08
…ent ANR on devices with flaky OEM camera HAL implementations.
…devices that don’t fully support the latter
- camera1: introduce constants and replace magic numbers
  - Add DEFAULT_PICTURE_QUALITY, FRONT_CAMERA_RECOMPRESS_JPEG_QUALITY, ASPECT_TOLERANCE, TAP_AREA_* and area/coeff constants.
  - Use constants for takePicture quality, aspect tolerance and tap focus/ metering calculations.
  - Limit picture resolution selection using MAX_PICTURE_PIXELS_BELOW_2MP.
  - Remove unused import.

- camera1-preview: add saved-call lifecycle and timeouts, fix callbacks
  - Add Handler/Looper-based timeout scheduling and cancellation for saved PluginCalls.
  - Set calls to keepAlive for capture/snapshot/record/camera start and schedule timeouts.
  - Add onPictureTaken/onSnapshotTaken and corresponding error handlers to resolve/reject and release saved calls.
  - Improve record start/stop handling to avoid leaked saved calls and race conditions.

- preview: extract ASPECT_TOLERANCE constant
  - Move local aspect tolerance literal into a shared constant for clarity and reuse.

- camera2: add defaults, handler fallbacks and robust retry/error handling
  - Introduce DEFAULT_PICTURE_QUALITY, ASPECT_TOLERANCE, TAP_AREA_BASE, FOCUS/METERING coeffs and METERING_WEIGHT.
  - Use a fallback handler (main looper) for image readers when background handler is unavailable.
  - Ensure background thread is started before opening camera.
  - Reset configureSessionRetryCount on successful config/capture to avoid stale retry state.
  - Notify eventListener.onCameraStartedError on camera device errors and on persistent configure failures.
  - Use constants for picture quality and tap-area logic.

- camera2-preview: saved-call lifecycle, timeouts and null-safety
  - Add timeout scheduling and cancellation for saved calls; cancel on success and reject + release on timeout.
  - Set keepAlive on saved calls and resolve/reject + release them safely in all on* callbacks.
  - Guard against null fragment when flipping or starting actions.
  - Ensure start/stop record and camera start flows resolve/reject the correct saved calls and avoid leaking callback ids.
…n safety to reduce NPEs and improve stability
- Corrected capture-session configuration order to avoid committing without a matching beginConfiguration() and to start the session only after all outputs are added. Also set the photo completion callback before capture to avoid races.
- Hardened stop to always clean up preview layers and orientation observers and to treat already-stopped as success (reduces noisy errors).
- Fixed setMaxZoomLimit to read the correct parameter (maxZoomLimit).
Copilot AI review requested due to automatic review settings January 23, 2026 14:21
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Reworks the plugin’s native camera implementation to support Android Camera2 (v2) while also adding cross-platform zoom APIs and exposing Android camera characteristics to enable future multi-camera selection.

Changes:

  • Introduces a new Android Camera2-based implementation with Camera1 fallback selection logic.
  • Adds zoom APIs (getZoom, setZoom, getMaxZoom, plus max-zoom limiting) across Android/iOS and exposes Android getCameraCharacteristics().
  • Updates TypeScript definitions, web stubs, and README documentation for the new API surface.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 23 comments.

Show a summary per file
File Description
src/web.ts Adds web stubs for new zoom + Camera2-related APIs (throws unimplemented).
src/definitions.ts Extends plugin typings/options to include zoom APIs, max zoom limit, camera characteristics, and API selection.
ios/Sources/CameraPreviewPlugin/CameraPreviewPlugin.swift Registers zoom/max-zoom-limit methods and wires zoom limit into start/handler logic.
ios/Sources/CameraPreviewPlugin/CameraController.swift Adds zoom/max-zoom-limit implementation and emits zoom change events.
android/src/main/java/com/ahm/capacitor/camera/preview/camera2api/Camera2Preview.java New Camera2 bridge layer handling plugin calls, timeouts, and camera characteristics.
android/src/main/java/com/ahm/capacitor/camera/preview/camera2api/Camera2Activity.java New Camera2 Fragment implementing preview/capture/record/zoom and orientation handling.
android/src/main/java/com/ahm/capacitor/camera/preview/camera1api/Preview.java Moves Camera1 preview implementation into a dedicated package and refactors constants.
android/src/main/java/com/ahm/capacitor/camera/preview/camera1api/CustomTextureView.java Updates package namespace for Camera1 components.
android/src/main/java/com/ahm/capacitor/camera/preview/camera1api/CustomSurfaceView.java Updates package namespace for Camera1 components.
android/src/main/java/com/ahm/capacitor/camera/preview/camera1api/Camera1Preview.java New Camera1 bridge layer (timeouts/call lifecycle fixes, zoom APIs).
android/src/main/java/com/ahm/capacitor/camera/preview/camera1api/Camera1Activity.java Renames/refactors the old CameraActivity to Camera1Activity and adjusts camera switching/rotation logic.
android/src/main/java/com/ahm/capacitor/camera/preview/TapGestureDetector.java Makes TapGestureDetector public for reuse by Camera2 implementation.
android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java Routes calls to Camera1 vs Camera2 and adds API selection + Camera2 support reporting.
README.md Documents max zoom limit option and new zoom-related methods.
Comments suppressed due to low confidence (1)

android/src/main/java/com/ahm/capacitor/camera/preview/camera1api/Camera1Activity.java:457

  • switchCamera() now advances cameraCurrentlyLocked by (cameraCurrentlyLocked + 1) % numberOfCameras, which cycles through all cameras (including multiple rear lenses) rather than toggling front/back. This changes the behavior of the public flip() API on Camera1 devices and may lead to unexpected lens selection. If flip() is intended to mean front↔rear, select the next camera based on CameraInfo.facing instead of cycling by index.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@dpa99c
Copy link
Author

dpa99c commented Jan 23, 2026

The preview aspect ratio/offset issues described by @eggbeard should now be addressed, and the latest master branch changes have been merged into this PR.

Note that while it now defaults to Camera2, the implementation performs feature detection and falls back to the Camera(1) API if the Camera2 API is not sufficiently supported on the device.

My test project can be used to validate this PR branch implementation:

git clone https://github.com/dpa99c/capacitor-camera-preview-test.git
cd capacitor-camera-preview-test
npm install
npm install git@github.com:dpa99c/capacitor-camera-preview.git#android_camera_v2
npm run cap:update
npm run cap:build
npm run cap:run:android

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants