diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..e241e4c
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,63 @@
+name: CI
+
+on:
+ push:
+ branches: [ main, ios_support ]
+ pull_request:
+
+jobs:
+ analyze-and-test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: subosito/flutter-action@v2
+ with:
+ channel: stable
+ - name: Install dependencies
+ run: flutter pub get
+ - name: Verify formatting
+ run: dart format --output=none --set-exit-if-changed .
+ - name: Analyze
+ run: flutter analyze
+ - name: Run tests
+ run: flutter test
+ - name: Publish dry-run
+ run: flutter pub publish --dry-run
+ - name: pana score
+ run: |
+ dart pub global activate pana
+ dart pub global run pana --no-warning --exit-code-threshold 20 .
+
+ build-android:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ - uses: subosito/flutter-action@v2
+ with:
+ channel: stable
+ - name: Build example APK (compiles Kotlin + native C)
+ run: |
+ cd example
+ flutter pub get
+ flutter build apk --debug
+
+ build-ios:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: subosito/flutter-action@v2
+ with:
+ channel: stable
+ - name: Build example iOS (compiles Swift + native C)
+ run: |
+ cd example
+ flutter pub get
+ flutter build ios --no-codesign --debug
+ - name: Lint podspec
+ run: |
+ cd ios
+ pod lib lint --allow-warnings || true
diff --git a/.github/workflows/native-build.yml b/.github/workflows/native-build.yml
deleted file mode 100644
index 83f448c..0000000
--- a/.github/workflows/native-build.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-name: Build Native Binaries
-
-on:
- push:
- branches: [ main ]
- pull_request:
- branches: [ main ]
-
-jobs:
- build-android:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- - name: Set up JDK 17
- uses: actions/setup-java@v3
- with:
- java-version: '17'
- distribution: 'temurin'
- - name: Build Android AAR
- run: |
- cd android
- ./gradlew assembleRelease
-
- build-ios:
- runs-on: macos-latest
- steps:
- - uses: actions/checkout@v3
- - name: Build iOS Framework
- run: |
- # In a real project, we'd use xcodebuild
- echo "Validating iOS Project..."
- cd ios
- # Add validation commands here
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 219d031..3499acb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,15 +1,54 @@
-## 0.0.1
+# Changelog
-* Initial release of `flutter_native_vision_camera`.
-* Integrated FFI for high-performance frame processing.
-* Support for zero-copy preview textures on Android and iOS.
-* Basic camera controls (zoom, focus, flash).
-* Vision API for real-time frame access.
+## 0.0.4
+
+Correctness, honesty, and packaging pass.
+
+* **Fixed:** removed the deprecated `package` attribute from the Android manifest (broke builds under AGP 8.x;
+ the namespace is set in `build.gradle`).
+* **Fixed:** Android video recording no longer crashes β `RECORD_AUDIO` is now declared and audio is gated behind a
+ runtime permission check, falling back to video-only when the mic is unavailable.
+* **Fixed:** `requestMicrophonePermission` now actually awaits the permission dialog result.
+* **Fixed:** frame processor no longer routes frames through a no-op relay isolate. The Dart callback is now
+ delivered directly via `NativeCallable.listener`; documentation corrected to state it runs on the main isolate
+ event loop (not a background isolate).
+* **Fixed:** `CameraPreview` now rebuilds when the controller updates (e.g. after async init or device switch).
+* **Fixed:** Android tap-to-focus now targets the correct metering point.
+* **Improved:** Android frames now expose all YUV planes with correct row strides (previously only the Y plane).
+* **Improved:** iOS reports the real frame pixel format and retains preview buffers safely.
+* **Packaging:** real podspec metadata, version lockstep, honest README/feature matrix, `topics`/`issue_tracker`,
+ CI (format/analyze/test/dry-run/pana + per-platform native builds), dropped the unused
+ `plugin_platform_interface` dependency.
+* **iOS:** implemented video recording (AVAssetWriter, keeps the frame stream live), `setFocusDistance`,
+ barcode symbology filtering, teardown-on-reinit, `CVPixelBuffer` retain/lock balance, honest pixel format,
+ main-thread results. Device-verified on iOS 18 (preview, photo, video, scanning).
+* **Orientation (structural):** preview rotation is now read from the camera framework
+ (Android `SurfaceRequest.TransformationInfo`, iOS sensor-relative) and applied once in `CameraPreview` β
+ fixing the recurring 90Β° tilt. Added `controller.previewRotation` / `displayPreviewSize`; scanner overlay
+ and camera page no longer compensate for rotation themselves. iOS Vision boxes now use the buffer orientation.
+* **Mirroring:** a single `mirror` flag on `initialize` drives both the front-camera preview and the saved
+ photo/video; the preview no longer double-mirrors on Android. `CameraPreview` gains `ResizeMode.contain`.
+* **Android:** dynamic capture orientation, correct tap-to-focus metering, real photo orientation.
+* **API:** exposed per-plane strides (`Frame.planeBytesPerRow`/`planePixelStride`) so chroma planes can be
+ walked correctly; added `CameraDevices.getCameraFormat(...)` to pick a format by resolution/fps.
+* **Docs:** comprehensive README (compile-correct quickstart with permissions, photo/video/scan/lifecycle
+ snippets, a correct native C/C++ plugin guide, requirements, expanded platform matrix, Limitations & Roadmap);
+ rewrote the example README; corrected the "background isolate" claim in the API docs to match reality
+ (main isolate); documented `takeSnapshot` (iOS-only), `setExposure` units, and `initialize`'s
+ `pixelFormat`/`mirror`.
+* **Example:** the Native Vision Camera page now reads the raw frame buffer over FFI and shows a live average
+ brightness, demonstrating the headline feature; fixed the C++ sample's row-stride indexing.
-### 0.0.2
+## 0.0.3
+
+* Migrated the Android embedding to AndroidX for better compatibility.
+
+## 0.0.2
* Fixed pubspec metadata (homepage, repository).
-### 0.0.3
+## 0.0.1
-* Replaced Android2 with AndroidX for better compatibility.
\ No newline at end of file
+* Initial release of `flutter_native_vision_camera`.
+* FFI pipeline for frame access, zero-copy preview textures, basic camera controls (zoom, focus, flash),
+ and real-time barcode scanning.
diff --git a/README.md b/README.md
index 4996f00..274d46d 100644
--- a/README.md
+++ b/README.md
@@ -1,148 +1,292 @@
# Flutter Native Vision Camera
-A high-performance, FFI-powered camera plugin for Flutter that puts efficiency and hardware control first.
-
-Built for developers who need more than just a preview: **Real-time AI, low-latency filters, and professional-grade video control.**
+A high-performance, FFI-powered camera plugin for Flutter, built for **real-time
+on-device vision**: direct, low-overhead access to camera frames for your own
+ML/CV code, integrated barcode/QR scanning, and full hardware control.
[](https://pub.dev/packages/flutter_native_vision_camera)
-## Why use this instead of `camera`?
+> **Status:** Active development (`0.0.x`) β the API may change before `1.0.0`.
+> **Device-verified** on Android (Pixel 8) and iOS 18: preview, photo, video
+> recording, barcode/QR scanning, zoom/torch/focus, orientation, and mirroring.
-The official `camera` package is great for simple use cases, but it often falls short when building high-performance vision applications (AI, QR scanning, custom filters). This package was built to solve those limitations.
+## Why use this instead of `camera`?
-### π Key Advantages
+The official [`camera`](https://pub.dev/packages/camera) package is excellent for
+general capture. This package targets the niche it doesn't cover: a **real-time
+frame pipeline** where you read raw camera buffers for your own vision code,
+with integrated scanning, all sharing one GPU-texture preview.
| Feature | `camera` (standard) | `flutter_native_vision_camera` |
|---------|---------------------|--------------------------------|
-| **Rendering** | Platform Channels (YUV -> Bitmap -> Skia) | **Zero-Copy GPU Textures** (OES -> Flutter Texture) |
-| **Orientation** | Matches UI Orientation (Buggy for video) | **Hardware-Level Sensing** (Correct even if UI is locked) |
-| **Frame Processing** | Async via Isolate (High Latency) | **Synchronous Background FFI** (Near Zero Latency) |
-| **QR/Barcodes** | Third-party plugin required | **Optimized MLKit Integration** built-in |
-| **Zoom/Focus** | Basic level support | Unified request management (Works during recording) |
+| **Preview** | Platform texture | **GPU texture** (Android zero-copy `SurfaceProducer`; iOS `CVPixelBuffer`) |
+| **Frame access** | `startImageStream` over the platform channel (serialized) | **Direct FFI pointer access** to plane buffers β no channel serialization |
+| **Native frame hook** | β | **Synchronous C/C++ plugin** on the camera thread (zero-latency) |
+| **Barcodes / QR** | Separate plugin | **Integrated** (Android MLKit, iOS Vision) |
## Features
-- **Zero-Copy Rendering**: Frames go directly from the camera sensor to the GPU and then to Flutter's `Texture` widget. No expensive copying or CPU-side bitmap conversion.
-- **Physical Orientation Engine**: High-fidelity rotation sensing using native `OrientationEventListener`. Recorded videos and previews always have the correct orientation, even on orientation-locked apps.
-- **Integrated MLKit**: High-speed, hardware-accelerated barcode and QR code scanning.
-- **Unified Request Loop**: Change zoom, torch, exposure, or recording state without dropping frame buffers.
-- **FFI Background Pipeline**: Process camera frames in C++/Rust/Dart FFI with zero memory copying.
+- **GPU-texture preview** with automatic orientation.
+- **FFI frame access** β read raw Y/U/V (Android) or BGRA (iOS) planes directly.
+- **Synchronous native C/C++ plugins** invoked on the camera thread.
+- **Integrated barcode/QR scanning** (Android MLKit, iOS Vision).
+- **Hardware controls** β zoom, torch, exposure, tap-to-focus, video recording.
-## Frame Processors
+## Requirements
-Frame processors allow you to run code for every frame the camera captures. Unlike the standard `camera` plugin, `flutter_native_vision_camera` runs these **synchronously on a background thread**.
+- **Flutter** β₯ 3.22, **Dart** β₯ 3.8
+- **iOS** 13.0+, **Android** `minSdk` 21+
+- A **physical device** β simulators/emulators have no real camera.
-### How it works
-1. **Zero Latency**: Frames are delivered to the processor as soon as they are available from the sensor.
-2. **Background Execution**: Your code runs on a separate background Isolate or in Native C++, so the main UI thread stays at a silky smooth 60/120 FPS.
-3. **Memory Safety**: The `Frame` object is valid only for the duration of the callback. If you need to keep data, copy it out of the frame's buffer.
+## Getting Started
-### Dart Frame Processor
+### Install
-Use for ML tasks (using `google_mlkit_*` or `tflite_flutter`) or image manipulation in Dart.
+```bash
+flutter pub add flutter_native_vision_camera
+```
-```dart
-await controller.setFrameProcessor((frame) {
- // 'frame' contains pointers to YUV/RGB buffers
- final bytes = frame.getPlane(0).bytes; // Direct access to Y-plane
-
- // Do your analysis here...
-});
+### Permissions
+
+**iOS** β add to `ios/Runner/Info.plist`:
+
+```xml
+NSCameraUsageDescription
+This app needs camera access to capture photos and video.
+NSMicrophoneUsageDescription
+This app needs microphone access to record video with audio.
```
-### π High-Performance FFI Plugins
+**Android** β `CAMERA` and `RECORD_AUDIO` are declared by the plugin; request
+them at runtime (shown below).
-One of the unique features of `flutter_native_vision_camera` is the ability to write **Synchronous C++ Plugins**.
+### Basic usage β preview
-Instead of sending expensive image data back and forth over Method Channels or into separate Dart Isolates, you can hook directly into the native frame loop.
+```dart
+import 'package:flutter/material.dart';
+import 'package:flutter_native_vision_camera/flutter_native_vision_camera.dart';
-```cpp
-// Your high-performance C++ code
-void onFrame(FrameHandle frame) {
- void* y_plane = Frame_getPlanePointer(frame, 0);
- // Do heavy AI math here on the background thread
+// Once, at startup (e.g. in main()):
+initializeVisionCamera();
+
+class CameraScreen extends StatefulWidget {
+ const CameraScreen({super.key});
+ @override
+ State createState() => _CameraScreenState();
}
-```
-## Getting Started
+class _CameraScreenState extends State {
+ final controller = CameraController();
+
+ @override
+ void initState() {
+ super.initState();
+ _start();
+ }
+
+ Future _start() async {
+ // 0. Permission (otherwise you get a black preview).
+ if (await CameraPermissions.requestCameraPermission() !=
+ PermissionStatus.granted) {
+ return;
+ }
+ // 1. Pick a device.
+ final devices = await CameraDevices.getAvailableCameraDevices();
+ final back =
+ CameraDevices.getCameraDevice(devices, CameraPosition.back) ??
+ devices.first;
+ // 2. Initialize + start.
+ await controller.initialize(back, enablePhoto: true);
+ await controller.setActive(true);
+ if (mounted) setState(() {});
+ }
+
+ @override
+ void dispose() {
+ controller.dispose(); // Release the hardware.
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) =>
+ CameraPreview(controller: controller, resizeMode: ResizeMode.cover);
+}
+```
-### Installation
+### Take a photo
-Add to your `pubspec.yaml`:
+`takePhoto()` requires `enablePhoto: true` at init.
-```yaml
-dependencies:
- flutter_native_vision_camera: ^1.0.0
+```dart
+final photo = await controller.takePhoto();
+Image.file(File(photo.path)); // photo.width / photo.height / photo.path
```
-### Basic Usage
+### Record video
+
+Initialize with `enableVideo: true`, then:
```dart
-final controller = CameraController();
+final dir = await getTemporaryDirectory();
+await controller.startRecording('${dir.path}/clip.mp4');
+// ... seamlessly zoom / toggle torch while recording ...
+await controller.stopRecording();
+```
-// 1. Initialize with a device
-await controller.initialize(
- devices.first,
- enableVideo: true,
- codeScanner: CodeScannerConfiguration(),
-);
+> The `mirror` flag on `initialize` controls the front-camera selfie mirror for
+> **both** the preview and the saved photo/video (default `true`; set `false` to
+> save what the camera actually sees).
-// 2. Start streaming
-await controller.setActive(true);
+### Scan barcodes / QR codes
-// 3. Listen for codes
+```dart
+await controller.initialize(
+ device,
+ codeScanner: CodeScannerConfiguration(
+ codeTypes: [CodeType.qr, CodeType.ean13, CodeType.code128],
+ ),
+);
controller.onCodeScanned.listen((codes) {
- print('Detected: ${codes.first.value}');
+ for (final code in codes) debugPrint('${code.type}: ${code.value}');
});
-
-// 4. Render in your widget tree
-ValueListenableBuilder(
- valueListenable: controller,
- builder: (context, state, _) {
- if (state == CameraState.uninitialized) return CircularProgressIndicator();
- return Texture(textureId: controller.textureId!);
- },
-)
```
-### Lifecycle Management
+### Lifecycle management
-Keeping the camera hardware active is resource-intensive. You **must** dispose of the controller when it's no longer needed to release the hardware and stop background threads.
+Release the hardware on `dispose()`, and pause/resume with the app lifecycle:
```dart
-@override
-void dispose() {
- // Shuts down the camera, sessions, and background isolates
- controller.dispose();
- super.dispose();
+class _S extends State with WidgetsBindingObserver {
+ @override
+ void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); }
+ @override
+ void dispose() {
+ WidgetsBinding.instance.removeObserver(this);
+ controller.dispose();
+ super.dispose();
+ }
+ @override
+ void didChangeAppLifecycleState(AppLifecycleState state) {
+ if (!controller.isInitialized) return;
+ if (state == AppLifecycleState.inactive ||
+ state == AppLifecycleState.paused) {
+ controller.setActive(false);
+ } else if (state == AppLifecycleState.resumed) {
+ controller.setActive(true);
+ }
+ }
}
```
-### Video Recording & Zooming
+## Frame Processors
-Unlike many other plugins, you can smoothly zoom or toggle the torch *while* recording video without causing any frame drops or freezes.
+Run code for every camera frame β the package's headline feature.
+
+### Threading model β read this
+
+- **Dart frame callback:** delivered **asynchronously on the main isolate's
+ event loop** (via `dart:ffi` `NativeCallable.listener`). It does **not** run
+ on a background isolate and does **not** block the camera thread. Keep the work
+ light, or copy data out and hand it to your own isolate. (A true off-isolate
+ worklet model is on the roadmap.)
+- **Native C/C++ hook:** runs **synchronously on the camera thread** with zero
+ added latency β use this for the heaviest work.
```dart
-await controller.startRecording('/path/to/video.mp4');
+await controller.setFrameProcessor((frame) {
+ // FFI hot path: read pixels here for ML/CV.
+ final yPlane = frame.getPlaneData(0); // Uint8List view
+ final stride = frame.planeBytesPerRow(0); // bytes per row (>= width)
+ final avgLuma = frame.computeLuminance(0, 0, frame.width, frame.height);
+});
+```
-// This call seamlessly updates the ongoing recording request
-await controller.setZoom(2.5);
+> `getPlaneData(i)` returns a **direct view** of native memory. Use
+> `planeBytesPerRow(i)` / `planePixelStride(i)` to walk it correctly (rows are
+> padded). `computeLuminance` is **YUV/Android-only** β on iOS (BGRA) it returns
+> `0.0`; read `getPlaneData(0)` instead.
-await controller.stopRecording();
+### Keeping a frame past the callback
+
+The `Frame` and its buffers are valid **only during the callback**. To use the
+data later (e.g. on another isolate), either copy it out synchronously:
+
+```dart
+final bytes = Uint8List.fromList(frame.getPlaneData(0)); // owns a copy
```
+β¦or extend the native lifetime by balancing the ref-count exactly once:
+
+```dart
+frame.incrementRefCount(); // keep the buffer alive
+// ... use frame asynchronously ...
+frame.decrementRefCount(); // release it (mandatory)
+```
+
+### High-performance native C/C++ plugin
+
+For the heaviest work, hook the synchronous frame loop in C++ (`src/VisionCamera.hpp`):
+
+```cpp
+#include "VisionCamera.hpp"
+
+class BrightnessPlugin : public vision::Plugin {
+public:
+ const char* name() const override { return "brightness"; }
+ void onFrame(const vision::Frame& frame) override {
+ const uint8_t* y = frame.data(); // Y / first plane
+ int stride = frame.bytesPerRow();
+ // ...heavy SIMD/AI math on the camera thread...
+ }
+};
+
+// Register once (e.g. from an init function you call via FFI at startup):
+extern "C" __attribute__((visibility("default"))) void registerMyPlugins() {
+ vision::Registry::instance().addPlugin(std::make_shared());
+}
+```
+
+Put your `.cpp` alongside the plugin sources; it links via CMake on Android and
+the podspec on iOS. See `src/VisionCamera_NativePluginExample.cpp` for a full
+working example.
+
## Platform Support
-| Platform | Support | Notes |
-|----------|---------|-------|
-| **Android** | β
Full | CameraX, MLKit, FFI, Recording |
-| **iOS** | β οΈ Partial | AVFoundation, Vision API, Preview only. Recording & FFI coming soon. |
+| Capability | Android | iOS |
+|------------|:-------:|:---:|
+| Preview (GPU texture) | β
| β
|
+| Photo capture | β
| β
|
+| Video recording (+ audio) | β
CameraX | β
AVAssetWriter |
+| Barcode / QR scanning | β
MLKit | β
Vision |
+| Frame processor (Dart, main isolate) | β
YUV planes | β
BGRA |
+| Native C/C++ plugin (camera thread) | β
| β
|
+| Zoom / torch / exposure / tap-focus | β
| β
|
+| Manual focus distance | β¬ | β
|
+| `takeSnapshot` | β¬ | β
|
+| `regionOfInterest` (scanning) | β¬ | β
|
+| Minimum OS | `minSdk` 21 | iOS 13.0 |
+
+Legend: β
supported Β· β¬ not implemented yet. Symbology coverage differs slightly
+between MLKit (Android) and Vision (iOS).
+
+## Limitations & Roadmap
+
+- **No web / desktop** β Android + iOS only.
+- **Frame processor runs on the main isolate** today; a true off-isolate worklet
+ model is planned.
+- **Recording options** (`RecordVideoOptions`, codec/HDR) and **pause/resume**
+ are not yet wired on all platforms; `startRecording` takes a path string.
+- **Mirror** is set at `initialize` time (no runtime toggle yet).
+- **Controls** (`setZoom`/`setTorch`/etc.) are no-ops until `setActive(true)`.
+- No RAW capture / multi-camera; `takeSnapshot` is iOS-only.
+- Planned API polish: typed `CameraException`, `TorchMode` enum, `switchCamera`,
+ richer error reporting.
## Credits & Attribution
-This package is a Flutter port and expansion of the concepts introduced by [react-native-vision-camera](https://github.com/mrousavy/react-native-vision-camera), originally created by [Marc Rousavy](https://github.com/mrousavy).
-
-We aim to bring the same high-performance, low-level camera control to the Flutter ecosystem, while leveraging Flutter's unique strengths like synchronous FFI and specialized `Texture` rendering.
+Inspired by [react-native-vision-camera](https://github.com/mrousavy/react-native-vision-camera)
+by [Marc Rousavy](https://github.com/mrousavy) β bringing the same high-performance,
+low-level camera philosophy to Flutter via synchronous FFI and `Texture` rendering.
## License
diff --git a/android/build.gradle b/android/build.gradle
index b69b5f6..6275bc0 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,7 +1,7 @@
// The Android Gradle Plugin builds the native code with the Android NDK.
group = "dev.jentejan.flutter_native_vision_camera"
-version = "1.0"
+version = "0.0.4"
buildscript {
repositories {
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index fd0a1f2..79538e8 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -1,4 +1,7 @@
-
+
+
+
+
+
diff --git a/android/src/main/kotlin/dev/jentejan/flutter_native_vision_camera/FlutterNativeVisionCameraPlugin.kt b/android/src/main/kotlin/dev/jentejan/flutter_native_vision_camera/FlutterNativeVisionCameraPlugin.kt
index 70b45f2..dd9fffc 100644
--- a/android/src/main/kotlin/dev/jentejan/flutter_native_vision_camera/FlutterNativeVisionCameraPlugin.kt
+++ b/android/src/main/kotlin/dev/jentejan/flutter_native_vision_camera/FlutterNativeVisionCameraPlugin.kt
@@ -57,9 +57,16 @@ class FlutterNativeVisionCameraPlugin : FlutterPlugin, MethodCallHandler, Activi
private lateinit var lifecycleRegistry: LifecycleRegistry
override val lifecycle: Lifecycle get() = lifecycleRegistry
- // Native dispatcher
- private external fun nativeDispatchFrame(buffer: java.nio.ByteBuffer, width: Int, height: Int, format: Int, orientation: Int, timestamp: Double, id: Long, address: Long)
- private external fun nativeSetFrameProcessorCallback(callback: Long)
+ // Native dispatcher. Passes every image plane (with its row/pixel stride and
+ // byte size) so the Dart/C++ side can read true multi-plane YUV.
+ private external fun nativeDispatchFrame(
+ p0: java.nio.ByteBuffer, p1: java.nio.ByteBuffer?, p2: java.nio.ByteBuffer?,
+ rs0: Int, rs1: Int, rs2: Int,
+ ps0: Int, ps1: Int, ps2: Int,
+ sz0: Int, sz1: Int, sz2: Int,
+ numPlanes: Int,
+ width: Int, height: Int, format: Int, orientation: Int, timestamp: Double, id: Long
+ )
private lateinit var channel: MethodChannel
private lateinit var textureRegistry: TextureRegistry
@@ -67,6 +74,7 @@ class FlutterNativeVisionCameraPlugin : FlutterPlugin, MethodCallHandler, Activi
private var activity: Activity? = null
private var binding: ActivityPluginBinding? = null
private var pendingPermissionResult: MethodChannel.Result? = null
+ private var pendingMicPermissionResult: MethodChannel.Result? = null
// CameraX components
private var cameraProvider: ProcessCameraProvider? = null
@@ -92,6 +100,7 @@ class FlutterNativeVisionCameraPlugin : FlutterPlugin, MethodCallHandler, Activi
private var barcodeScanner: BarcodeScanner? = null
@Volatile private var isCodeScannerEnabled = false
private var currentFormat: Map? = null
+ private var mirrorCaptures: Boolean = false
private var recordingStartTime: Long = 0
private var pendingVideoResult: MethodChannel.Result? = null
@@ -112,6 +121,7 @@ class FlutterNativeVisionCameraPlugin : FlutterPlugin, MethodCallHandler, Activi
companion object {
private const val CHANNEL_NAME = "dev.jentejan.flutter_native_vision_camera/camera"
private const val CAMERA_PERMISSION_REQUEST = 1001
+ private const val MIC_PERMISSION_REQUEST = 1002
private val frameIdCounter = AtomicLong(0)
@@ -135,6 +145,13 @@ class FlutterNativeVisionCameraPlugin : FlutterPlugin, MethodCallHandler, Activi
return currentCount
}
+ @JvmStatic
+ @androidx.annotation.Keep
+ fun retainFrame(id: Long): Int {
+ val managed = activeFrames[id] ?: return 0
+ return managed.refCount.incrementAndGet()
+ }
+
fun clearFrames() {
activeFrames.forEach { (id, managed) ->
try {
@@ -178,13 +195,21 @@ class FlutterNativeVisionCameraPlugin : FlutterPlugin, MethodCallHandler, Activi
orientationEventListener = object : OrientationEventListener(context) {
override fun onOrientationChanged(orientation: Int) {
if (orientation == ORIENTATION_UNKNOWN) return
- physicalOrientation = when {
+ val newRotation = when {
orientation < 45 || orientation > 315 -> Surface.ROTATION_0
orientation in 45..134 -> Surface.ROTATION_270
orientation in 135..224 -> Surface.ROTATION_180
orientation in 225..314 -> Surface.ROTATION_90
else -> Surface.ROTATION_0
}
+ if (newRotation != physicalOrientation) {
+ physicalOrientation = newRotation
+ // Keep capture outputs correctly oriented from the physical
+ // sensor angle, even when the UI is orientation-locked.
+ imageCapture?.targetRotation = newRotation
+ videoCapture?.targetRotation = newRotation
+ imageAnalysis?.targetRotation = newRotation
+ }
}
}
orientationEventListener?.enable()
@@ -229,6 +254,7 @@ class FlutterNativeVisionCameraPlugin : FlutterPlugin, MethodCallHandler, Activi
val enablePhoto = call.argument("enablePhoto") ?: false
val enableVideo = call.argument("enableVideo") ?: false
val codeScanner = call.argument