📸 iOS + Android + Web camera toolkit for Flutter, powered by AVFoundation, CameraX, and browser MediaDevices API. Render the native preview, switch lenses, stream frames, capture photos, record video, tune exposure/white balance/torch/zoom, and listen to lifecycle + orientation + AF/AE state – all from Dart.
Platform coverage: iOS + Android + Web. Other platforms no-op safely.
- 🔍 Lens discovery & switching – list every lens (front included by default; exclude with
includeFrontCameras: false) and reconfigure withswitchLens. - 🖼️ Native preview widget –
IrisCameraPreviewwrapsAVCaptureVideoPreviewLayerwith tap-to-focus + overlay hooks. - 📸 Still capture –
capturePhotowith flash/ISO/exposure overrides. Long exposure is supported; query the device max viagetMaxExposureDuration. - 📸 Burst –
captureBurst(count, options)supports long exposure/ISO overrides, optional file saving (directory,filenamePrefix), and progress events viaburstProgressStream. - 🎛️ Pro controls – focus mode/point, exposure mode/point/EV, white balance, frame rate range, torch, zoom, resolution presets.
- 📡 Streams – live BGRA image stream, orientation stream, lifecycle state stream, AF/AE state stream.
- 🔧 Lifecycle – explicit
initialize/pause/resume/disposeand structured errors viaIrisCameraException. - 🎥 Video – start/stop file-based recording (iOS/Android), optional audio.
- Android: minSdk 26+, targetSdk 34 (CameraX 1.3.x)
- iOS: iOS 15.0+
- Web: Modern browsers with MediaDevices API support (Chrome, Firefox, Safari, Edge)
flutter pub add iris_cameraimport 'package:iris_camera/iris_camera.dart';
final camera = IrisCamera();
final lenses = await camera.listAvailableLenses(); // includeFrontCameras defaults to true
await camera.switchLens(lenses.first.category);
final photo = await camera.capturePhoto(
options: const PhotoCaptureOptions(flashMode: PhotoFlashMode.auto),
);Live preview:
final focusController = FocusIndicatorController();
IrisCameraPreview(
aspectRatio: 3 / 2,
enableTapToFocus: true,
showFocusIndicator: true,
onTapFocus: (point) => camera.setFocus(point: point),
focusIndicatorController: focusController,
);Add to ios/Runner/Info.plist (both are required or the app will crash when accessing camera/mic):
<key>NSCameraUsageDescription</key>
<string>This app needs the camera to capture photos.</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app needs the microphone for recording video with audio.</string>That’s it. Permissions are requested automatically on first use.
Exclude front cameras by calling
listAvailableLenses(includeFrontCameras: false).
Add the camera permission to your app manifest (the plugin also declares it for you):
<uses-permission android:name="android.permission.CAMERA" />
<!-- Needed for video with audio -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />iris_camera will prompt for runtime permission automatically before accessing the camera. The preview is rendered via a native PreviewView, and tap-to-focus works the same as iOS.
No additional configuration required. The browser will automatically prompt for camera permission when accessing the camera. Ensure your site is served over HTTPS (required for camera access).
Note: Some advanced features have limited support on web:
- Focus/exposure point control is simulated (browser limitation)
- White balance temperature/tint is not available
- Video recording outputs WebM format (blob URL)
- Torch/flash depends on browser and device support
Key methods:
listAvailableLenses({includeFrontCameras})→List<CameraLensDescriptor>switchLens(CameraLensCategory category)→CameraLensDescriptorcapturePhoto({PhotoCaptureOptions options})→Uint8ListcaptureBurst({count, PhotoCaptureOptions options, directory, filenamePrefix})→List<Uint8List>or saved file paths whendirectoryis setburstProgressStream→BurstProgressEvent(total, completed, status, error?)getMaxExposureDuration()→Duration(use to clamp long exposures)startVideoRecording({filePath, enableAudio})→String pathstopVideoRecording()→String path- Focus:
setFocus(point/lensPosition),setFocusMode,focusExposureStateStream - Exposure:
setExposureMode,setExposurePoint,setExposureOffset,getMin/MaxExposureOffset,getExposureOffsetStepSize - Zoom/torch/WB:
setZoom,setTorch,setWhiteBalance - Frame/format:
setFrameRateRange,setResolutionPreset - Streams:
imageStream,orientationStream,stateStream - Lifecycle:
initialize,pauseSession,resumeSession,disposeSession - Errors:
IrisCameraException(code, message, details)
Data classes:
CameraLensDescriptor(id,name,position,category,supportsFocus, optionalfocalLength,fieldOfView)PhotoCaptureOptions(flashMode,exposureDuration,iso)OrientationEvent,CameraStateEvent,FocusExposureStateEvent,IrisImageFrame
Widget:
IrisCameraPreviewwith tap-to-focus + focus indicator styling/control.
| Capability | iris_camera | camera |
|---|---|---|
| Still photos | ✅ Shared session JPEG capture | ✅ |
| Live preview widget | ✅ IrisCameraPreview (iOS/Android/Web) |
✅ |
| Lens discovery/switching | ✅ Enumerate + switch by category (wide/ultraWide/telephoto/etc.), front opt-in | ⚪️ List only (no switching API) |
| Tap/manual focus | ✅ Tap/point focus; iOS also supports lensPosition | ✅ |
| Exposure controls | ✅ mode/point/EV/ISO/exposure duration | ✅ (mode/point/offset) |
| White balance override | ✅ iOS: temperature/tint; Android: auto/lock only | ⚪️ (not exposed) |
| Zoom | ✅ | ✅ |
| Torch | ✅ (torch separate from flash) | ✅ |
| Frame rate range | ✅ min/max FPS | ⚪️ limited |
| Resolution preset | ✅ | ✅ |
| Live image stream | ✅ BGRA | ✅ |
| Orientation stream | ✅ device/video | ✅ |
| AF/AE state stream | ✅ | ⚪️ basic focus/exposure mode only |
| Lifecycle controls | ✅ initialize/pause/resume/dispose + state stream | ✅ (controller init/dispose) |
| Video recording | ✅ (iOS/Android/Web) | ✅ |
| Web | ✅ (MediaDevices API) | ✅ |
final lenses = await camera.listAvailableLenses();
final tele = lenses.firstWhere(
(lens) => lens.category == CameraLensCategory.telephoto,
orElse: () => lenses.first,
);
await camera.switchLens(tele.category);
await camera.initialize();
camera.stateStream.listen((event) => debugPrint('state=${event.state}'));
camera.focusExposureStateStream.listen((event) => debugPrint('af/ae=${event.state}'));
await camera.setExposureMode(ExposureMode.locked);
await camera.setFocusMode(FocusMode.locked);
final photo = await camera.capturePhoto();MIT — see LICENSE.