iris_camera
πΈ 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.
Highlights
- π 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.
Install
Supported platforms
- 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_camera
import '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,
);
iOS setup
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).
Android setup
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.
Web setup
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
API quick reference
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.
iris_camera vs camera (iOS/Android)
| 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) | β |
Example flow
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();
License
MIT β see LICENSE.