camera 0.11.4
camera: ^0.11.4 copied to clipboard
Metadata
A Flutter plugin for controlling the camera. Supports previewing the camera feed, capturing images and video, and streaming image buffers to Dart.
// Copyright 2013 The Flutter Authors// Use of this source code is governed by a BSD-style license that can be// found in the LICENSE file.import 'dart:async';import 'dart:io';import 'package:camera/camera.dart';import 'package:flutter/foundation.dart';import 'package:flutter/material.dart';import 'package:flutter/scheduler.dart';import 'package:video_player/video_player.dart';/// Camera example home widget.class CameraExampleHome extends StatefulWidget { /// Default Constructor const CameraExampleHome({super.key}); @override State<CameraExampleHome> createState() { return _CameraExampleHomeState(); }}/// Returns a suitable camera icon for [direction].IconData getCameraLensIcon(CameraLensDirection direction) { switch (direction) { case CameraLensDirection.back: return Icons.camera_rear; case CameraLensDirection.front: return Icons.camera_front; case CameraLensDirection.external: return Icons.camera; } // This enum is from a different package, so a new value could be added at // any time. The example should keep working if that happens. // ignore: dead_code return Icons.camera;}void _logError(String code, String? message) { // ignore: avoid_print print('Error: $code${message == null ? '' : '\nError Message: $message'}');}class _CameraExampleHomeState extends State<CameraExampleHome> with WidgetsBindingObserver, TickerProviderStateMixin { CameraController? controller; XFile? imageFile; XFile? videoFile; VideoPlayerController? videoController; VoidCallback? videoPlayerListener; bool enableAudio = true; double _minAvailableExposureOffset = 0.0; double _maxAvailableExposureOffset = 0.0; double _currentExposureOffset = 0.0; late final AnimationController _flashModeControlRowAnimationController; late final CurvedAnimation _flashModeControlRowAnimation; late final AnimationController _exposureModeControlRowAnimationController; late final CurvedAnimation _exposureModeControlRowAnimation; late final AnimationController _focusModeControlRowAnimationController; late final CurvedAnimation _focusModeControlRowAnimation; double _minAvailableZoom = 1.0; double _maxAvailableZoom = 1.0; double _currentScale = 1.0; double _baseScale = 1.0; // Counting pointers (number of user fingers on screen) int _pointers = 0; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _flashModeControlRowAnimationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _flashModeControlRowAnimation = CurvedAnimation( parent: _flashModeControlRowAnimationController, curve: Curves.easeInCubic, ); _exposureModeControlRowAnimationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _exposureModeControlRowAnimation = CurvedAnimation( parent: _exposureModeControlRowAnimationController, curve: Curves.easeInCubic, ); _focusModeControlRowAnimationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _focusModeControlRowAnimation = CurvedAnimation( parent: _focusModeControlRowAnimationController, curve: Curves.easeInCubic, ); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _flashModeControlRowAnimationController.dispose(); _flashModeControlRowAnimation.dispose(); _exposureModeControlRowAnimationController.dispose(); _exposureModeControlRowAnimation.dispose(); _focusModeControlRowAnimationController.dispose(); _focusModeControlRowAnimation.dispose(); super.dispose(); } // #docregion AppLifecycle @override void didChangeAppLifecycleState(AppLifecycleState state) { final CameraController? cameraController = controller; // App state changed before we got the chance to initialize. if (cameraController == null || !cameraController.value.isInitialized) { return; } if (state == AppLifecycleState.inactive) { cameraController.dispose(); } else if (state == AppLifecycleState.resumed) { _initializeCameraController(cameraController.description); } } // #enddocregion AppLifecycle @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Camera example')), body: Column( children: <Widget>[ Expanded( child: Container( decoration: BoxDecoration( color: Colors.black, border: Border.all( color: controller != null && controller!.value.isRecordingVideo ? Colors.redAccent : Colors.grey, width: 3.0, ), ), child: Padding( padding: const EdgeInsets.all(1.0), child: Center(child: _cameraPreviewWidget()), ), ), ), _captureControlRowWidget(), _modeControlRowWidget(), Padding( padding: const EdgeInsets.all(5.0), child: Row( children: <Widget>[_cameraTogglesRowWidget(), _thumbnailWidget()], ), ), ], ), ); } /// Display the preview from the camera (or a message if the preview is not available). Widget _cameraPreviewWidget() { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { return const Text( 'Tap a camera', style: TextStyle( color: Colors.white, fontSize: 24.0, fontWeight: FontWeight.w900, ), ); } else { return Listener( onPointerDown: (_) => _pointers++, onPointerUp: (_) => _pointers--, child: CameraPreview( controller!, child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return GestureDetector( behavior: HitTestBehavior.opaque, onScaleStart: _handleScaleStart, onScaleUpdate: _handleScaleUpdate, onTapDown: (TapDownDetails details) => onViewFinderTap(details, constraints), ); }, ), ), ); } } void _handleScaleStart(ScaleStartDetails details) { _baseScale = _currentScale; } Future<void> _handleScaleUpdate(ScaleUpdateDetails details) async { // When there are not exactly two fingers on screen don't scale if (controller == null || _pointers != 2) { return; } _currentScale = (_baseScale * details.scale).clamp( _minAvailableZoom, _maxAvailableZoom, ); await controller!.setZoomLevel(_currentScale); } /// Display the thumbnail of the captured image or video. Widget _thumbnailWidget() { return Row( mainAxisSize: MainAxisSize.min, children: <Widget>[ if (videoController case final VideoPlayerController controller?) Container( width: 64.0, height: 64.0, decoration: BoxDecoration(border: Border.all(color: Colors.pink)), child: Center( child: AspectRatio( aspectRatio: controller.value.aspectRatio, child: VideoPlayer(controller), ), ), ) else if (imageFile?.path case final String path) Container( width: 64.0, height: 64.0, decoration: BoxDecoration(border: Border.all(color: Colors.pink)), // The captured image on the web contains a network-accessible URL // pointing to a location within the browser. It may be displayed // either with Image.network or Image.memory after loading the image // bytes to memory. child: kIsWeb ? Image.network(path) : Image.file(File(path)), ), ], ); } /// Display a bar with buttons to change the flash and exposure modes Widget _modeControlRowWidget() { return Column( children: <Widget>[ Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ IconButton( icon: const Icon(Icons.flash_on), color: Colors.blue, onPressed: controller != null ? onFlashModeButtonPressed : null, ), // The exposure and focus mode are currently not supported on the web. ...!kIsWeb ? <Widget>[ IconButton( icon: const Icon(Icons.exposure), color: Colors.blue, onPressed: controller != null ? onExposureModeButtonPressed : null, ), IconButton( icon: const Icon(Icons.filter_center_focus), color: Colors.blue, onPressed: controller != null ? onFocusModeButtonPressed : null, ), ] : <Widget>[], IconButton( icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute), color: Colors.blue, onPressed: controller != null ? onAudioModeButtonPressed : null, ), IconButton( icon: Icon( controller?.value.isCaptureOrientationLocked ?? false ? Icons.screen_lock_rotation : Icons.screen_rotation, ), color: Colors.blue, onPressed: controller != null ? onCaptureOrientationLockButtonPressed : null, ), ], ), _flashModeControlRowWidget(), _exposureModeControlRowWidget(), _focusModeControlRowWidget(), ], ); } Widget _flashModeControlRowWidget() { return SizeTransition( sizeFactor: _flashModeControlRowAnimation, child: ClipRect( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ IconButton( icon: const Icon(Icons.flash_off), color: controller?.value.flashMode == FlashMode.off ? Colors.orange : Colors.blue, onPressed: controller != null ? () => onSetFlashModeButtonPressed(FlashMode.off) : null, ), IconButton( icon: const Icon(Icons.flash_auto), color: controller?.value.flashMode == FlashMode.auto ? Colors.orange : Colors.blue, onPressed: controller != null ? () => onSetFlashModeButtonPressed(FlashMode.auto) : null, ), IconButton( icon: const Icon(Icons.flash_on), color: controller?.value.flashMode == FlashMode.always ? Colors.orange : Colors.blue, onPressed: controller != null ? () => onSetFlashModeButtonPressed(FlashMode.always) : null, ), IconButton( icon: const Icon(Icons.highlight), color: controller?.value.flashMode == FlashMode.torch ? Colors.orange : Colors.blue, onPressed: controller != null ? () => onSetFlashModeButtonPressed(FlashMode.torch) : null, ), ], ), ), ); } Widget _exposureModeControlRowWidget() { final ButtonStyle styleAuto = TextButton.styleFrom( foregroundColor: controller?.value.exposureMode == ExposureMode.auto ? Colors.orange : Colors.blue, ); final ButtonStyle styleLocked = TextButton.styleFrom( foregroundColor: controller?.value.exposureMode == ExposureMode.locked ? Colors.orange : Colors.blue, ); return SizeTransition( sizeFactor: _exposureModeControlRowAnimation, child: ClipRect( child: ColoredBox( color: Colors.grey.shade50, child: Column( children: <Widget>[ const Center(child: Text('Exposure Mode')), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ TextButton( style: styleAuto, onPressed: controller != null ? () => onSetExposureModeButtonPressed(ExposureMode.auto) : null, onLongPress: () { if (controller != null) { controller!.setExposurePoint(null); showInSnackBar('Resetting exposure point'); } }, child: const Text('AUTO'), ), TextButton( style: styleLocked, onPressed: controller != null ? () => onSetExposureModeButtonPressed( ExposureMode.locked, ) : null, child: const Text('LOCKED'), ), TextButton( style: styleLocked, onPressed: controller != null ? () => controller!.setExposureOffset(0.0) : null, child: const Text('RESET OFFSET'), ), ], ), const Center(child: Text('Exposure Offset')), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ Text(_minAvailableExposureOffset.toString()), Slider( value: _currentExposureOffset, min: _minAvailableExposureOffset, max: _maxAvailableExposureOffset, label: _currentExposureOffset.toString(), onChanged: _minAvailableExposureOffset == _maxAvailableExposureOffset ? null : setExposureOffset, ), Text(_maxAvailableExposureOffset.toString()), ], ), ], ), ), ), ); } Widget _focusModeControlRowWidget() { final ButtonStyle styleAuto = TextButton.styleFrom( foregroundColor: controller?.value.focusMode == FocusMode.auto ? Colors.orange : Colors.blue, ); final ButtonStyle styleLocked = TextButton.styleFrom( foregroundColor: controller?.value.focusMode == FocusMode.locked ? Colors.orange : Colors.blue, ); return SizeTransition( sizeFactor: _focusModeControlRowAnimation, child: ClipRect( child: ColoredBox( color: Colors.grey.shade50, child: Column( children: <Widget>[ const Center(child: Text('Focus Mode')), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ TextButton( style: styleAuto, onPressed: controller != null ? () => onSetFocusModeButtonPressed(FocusMode.auto) : null, onLongPress: () { if (controller != null) { controller!.setFocusPoint(null); } showInSnackBar('Resetting focus point'); }, child: const Text('AUTO'), ), TextButton( style: styleLocked, onPressed: controller != null ? () => onSetFocusModeButtonPressed(FocusMode.locked) : null, child: const Text('LOCKED'), ), ], ), ], ), ), ), ); } /// Display the control bar with buttons to take pictures and record videos. Widget _captureControlRowWidget() { final CameraController? cameraController = controller; return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ IconButton( icon: const Icon(Icons.camera_alt), color: Colors.blue, onPressed: cameraController != null && cameraController.value.isInitialized && !cameraController.value.isRecordingVideo ? onTakePictureButtonPressed : null, ), IconButton( icon: const Icon(Icons.videocam), color: Colors.blue, onPressed: cameraController != null && cameraController.value.isInitialized && !cameraController.value.isRecordingVideo ? onVideoRecordButtonPressed : null, ), IconButton( icon: cameraController != null && cameraController.value.isRecordingPaused ? const Icon(Icons.play_arrow) : const Icon(Icons.pause), color: Colors.blue, onPressed: cameraController != null && cameraController.value.isInitialized && cameraController.value.isRecordingVideo ? cameraController.value.isRecordingPaused ? onResumeButtonPressed : onPauseButtonPressed : null, ), IconButton( icon: const Icon(Icons.stop), color: Colors.red, onPressed: cameraController != null && cameraController.value.isInitialized && cameraController.value.isRecordingVideo ? onStopButtonPressed : null, ), IconButton( icon: const Icon(Icons.pause_presentation), color: cameraController != null && cameraController.value.isPreviewPaused ? Colors.red : Colors.blue, onPressed: cameraController == null ? null : onPausePreviewButtonPressed, ), ], ); } /// Display a row of toggle to select the camera (or a message if no camera is available). Widget _cameraTogglesRowWidget() { final toggles = <Widget>[]; void onChanged(CameraDescription? description) { if (description == null) { return; } onNewCameraSelected(description); } if (_cameras.isEmpty) { SchedulerBinding.instance.addPostFrameCallback((_) async { showInSnackBar('No camera found.'); }); return const Text('None'); } else { for (final CameraDescription cameraDescription in _cameras) { toggles.add( SizedBox( width: 90.0, child: RadioListTile<CameraDescription>( title: Icon(getCameraLensIcon(cameraDescription.lensDirection)), value: cameraDescription, ), ), ); } } return Expanded( child: SizedBox( height: 56.0, child: RadioGroup<CameraDescription>( groupValue: controller?.description, onChanged: onChanged, child: ListView(scrollDirection: Axis.horizontal, children: toggles), ), ), ); } String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); void showInSnackBar(String message) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(message))); } void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { if (controller == null) { return; } final CameraController cameraController = controller!; final offset = Offset( details.localPosition.dx / constraints.maxWidth, details.localPosition.dy / constraints.maxHeight, ); cameraController.setExposurePoint(offset); cameraController.setFocusPoint(offset); } Future<void> onNewCameraSelected(CameraDescription cameraDescription) async { if (controller != null) { return controller!.setDescription(cameraDescription); } else { return _initializeCameraController(cameraDescription); } } Future<void> _initializeCameraController( CameraDescription cameraDescription, ) async { final cameraController = CameraController( cameraDescription, kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, enableAudio: enableAudio, imageFormatGroup: ImageFormatGroup.jpeg, ); controller = cameraController; // If the controller is updated then update the UI. cameraController.addListener(() { if (mounted) { setState(() {}); } if (cameraController.value.hasError) { showInSnackBar( 'Camera error ${cameraController.value.errorDescription}', ); } }); try { await cameraController.initialize(); await Future.wait(<Future<Object?>>[ // The exposure mode is currently not supported on the web. ...!kIsWeb ? <Future<Object?>>[ cameraController.getMinExposureOffset().then( (double value) => _minAvailableExposureOffset = value, ), cameraController.getMaxExposureOffset().then( (double value) => _maxAvailableExposureOffset = value, ), ] : <Future<Object?>>[], cameraController.getMaxZoomLevel().then( (double value) => _maxAvailableZoom = value, ), cameraController.getMinZoomLevel().then( (double value) => _minAvailableZoom = value, ), ]); } on CameraException catch (e) { switch (e.code) { case 'CameraAccessDenied': showInSnackBar('You have denied camera access.'); case 'CameraAccessDeniedWithoutPrompt': // iOS only showInSnackBar('Please go to Settings app to enable camera access.'); case 'CameraAccessRestricted': // iOS only showInSnackBar('Camera access is restricted.'); case 'AudioAccessDenied': showInSnackBar('You have denied audio access.'); case 'AudioAccessDeniedWithoutPrompt': // iOS only showInSnackBar('Please go to Settings app to enable audio access.'); case 'AudioAccessRestricted': // iOS only showInSnackBar('Audio access is restricted.'); default: _showCameraException(e); } } if (mounted) { setState(() {}); } } void onTakePictureButtonPressed() { takePicture().then((XFile? file) { if (mounted) { setState(() { imageFile = file; videoController?.dispose(); videoController = null; }); if (file != null) { showInSnackBar('Picture saved to ${file.path}'); } } }); } void onFlashModeButtonPressed() { if (_flashModeControlRowAnimationController.value == 1) { _flashModeControlRowAnimationController.reverse(); } else { _flashModeControlRowAnimationController.forward(); _exposureModeControlRowAnimationController.reverse(); _focusModeControlRowAnimationController.reverse(); } } void onExposureModeButtonPressed() { if (_exposureModeControlRowAnimationController.value == 1) { _exposureModeControlRowAnimationController.reverse(); } else { _exposureModeControlRowAnimationController.forward(); _flashModeControlRowAnimationController.reverse(); _focusModeControlRowAnimationController.reverse(); } } void onFocusModeButtonPressed() { if (_focusModeControlRowAnimationController.value == 1) { _focusModeControlRowAnimationController.reverse(); } else { _focusModeControlRowAnimationController.forward(); _flashModeControlRowAnimationController.reverse(); _exposureModeControlRowAnimationController.reverse(); } } void onAudioModeButtonPressed() { enableAudio = !enableAudio; if (controller != null) { onNewCameraSelected(controller!.description); } } Future<void> onCaptureOrientationLockButtonPressed() async { try { if (controller != null) { final CameraController cameraController = controller!; if (cameraController.value.isCaptureOrientationLocked) { await cameraController.unlockCaptureOrientation(); showInSnackBar('Capture orientation unlocked'); } else { await cameraController.lockCaptureOrientation(); showInSnackBar( 'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}', ); } } } on CameraException catch (e) { _showCameraException(e); } } void onSetFlashModeButtonPressed(FlashMode mode) { setFlashMode(mode).then((_) { if (mounted) { setState(() {}); } showInSnackBar('Flash mode set to ${mode.toString().split('.').last}'); }); } void onSetExposureModeButtonPressed(ExposureMode mode) { setExposureMode(mode).then((_) { if (mounted) { setState(() {}); } showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}'); }); } void onSetFocusModeButtonPressed(FocusMode mode) { setFocusMode(mode).then((_) { if (mounted) { setState(() {}); } showInSnackBar('Focus mode set to ${mode.toString().split('.').last}'); }); } void onVideoRecordButtonPressed() { startVideoRecording().then((_) { if (mounted) { setState(() {}); } }); } void onStopButtonPressed() { stopVideoRecording().then((XFile? file) { if (mounted) { setState(() {}); } if (file != null) { showInSnackBar('Video recorded to ${file.path}'); videoFile = file; _startVideoPlayer(); } }); } Future<void> onPausePreviewButtonPressed() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return; } if (cameraController.value.isPreviewPaused) { await cameraController.resumePreview(); } else { await cameraController.pausePreview(); } if (mounted) { setState(() {}); } } void onPauseButtonPressed() { pauseVideoRecording().then((_) { if (mounted) { setState(() {}); } showInSnackBar('Video recording paused'); }); } void onResumeButtonPressed() { resumeVideoRecording().then((_) { if (mounted) { setState(() {}); } showInSnackBar('Video recording resumed'); }); } Future<void> startVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return; } if (cameraController.value.isRecordingVideo) { // A recording is already started, do nothing. return; } try { await cameraController.startVideoRecording(); } on CameraException catch (e) { _showCameraException(e); return; } } Future<XFile?> stopVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return null; } try { return await cameraController.stopVideoRecording(); } on CameraException catch (e) { _showCameraException(e); return null; } } Future<void> pauseVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return; } try { await cameraController.pauseVideoRecording(); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future<void> resumeVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return; } try { await cameraController.resumeVideoRecording(); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future<void> setFlashMode(FlashMode mode) async { if (controller == null) { return; } try { await controller!.setFlashMode(mode); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future<void> setExposureMode(ExposureMode mode) async { if (controller == null) { return; } try { await controller!.setExposureMode(mode); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future<void> setExposureOffset(double offset) async { if (controller == null) { return; } setState(() { _currentExposureOffset = offset; }); try { offset = await controller!.setExposureOffset(offset); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future<void> setFocusMode(FocusMode mode) async { if (controller == null) { return; } try { await controller!.setFocusMode(mode); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future<void> _startVideoPlayer() async { if (videoFile == null) { return; } final vController = kIsWeb ? VideoPlayerController.networkUrl(Uri.parse(videoFile!.path)) : VideoPlayerController.file(File(videoFile!.path)); videoPlayerListener = () { if (videoController != null) { // Refreshing the state to update video player with the correct ratio. if (mounted) { setState(() {}); } videoController!.removeListener(videoPlayerListener!); } }; vController.addListener(videoPlayerListener!); await vController.setLooping(true); await vController.initialize(); await videoController?.dispose(); if (mounted) { setState(() { imageFile = null; videoController = vController; }); } await vController.play(); } Future<XFile?> takePicture() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return null; } if (cameraController.value.isTakingPicture) { // A capture is already pending, do nothing. return null; } try { final XFile file = await cameraController.takePicture(); return file; } on CameraException catch (e) { _showCameraException(e); return null; } } void _showCameraException(CameraException e) { _logError(e.code, e.description); showInSnackBar('Error: ${e.code}\n${e.description}'); }}/// CameraApp is the Main Application.class CameraApp extends StatelessWidget { /// Default Constructor const CameraApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(home: CameraExampleHome()); }}/// Getting available cameras for testing.@visibleForTestingList<CameraDescription> get cameras => _cameras;List<CameraDescription> _cameras = <CameraDescription>[];Future<void> main() async { // Fetch the available cameras before initializing the app. try { WidgetsFlutterBinding.ensureInitialized(); _cameras = await availableCameras(); } on CameraException catch (e) { _logError(e.code, e.description); } runApp(const CameraApp());}Publisher
Weekly Downloads
Metadata
A Flutter plugin for controlling the camera. Supports previewing the camera feed, capturing images and video, and streaming image buffers to Dart.
Repository (GitHub)
View/report issues
Contributing
Topics
Documentation
License
BSD-3-Clause (license)
Dependencies
camera_android_camerax,camera_avfoundation,camera_platform_interface,camera_web,flutter,flutter_plugin_android_lifecycle
More
Packages that depend on camera
Packages that implement camera
← Metadata
Publisher
Weekly Downloads
Metadata
A Flutter plugin for controlling the camera. Supports previewing the camera feed, capturing images and video, and streaming image buffers to Dart.
Repository (GitHub)
View/report issues
Contributing
Topics
Documentation
License
BSD-3-Clause (license)
Dependencies
camera_android_camerax,camera_avfoundation,camera_platform_interface,camera_web,flutter,flutter_plugin_android_lifecycle
More
Packages that depend on camera
Packages that implement camera