Merge remote-tracking branch 'plugins-packages/main' into merge-flutter-plugins
66
packages/camera/camera/AUTHORS
Normal file
@ -0,0 +1,66 @@
|
||||
# Below is a list of people and organizations that have contributed
|
||||
# to the Flutter project. Names should be added to the list like so:
|
||||
#
|
||||
# Name/Organization <email address>
|
||||
|
||||
Google Inc.
|
||||
The Chromium Authors
|
||||
German Saprykin <saprykin.h@gmail.com>
|
||||
Benjamin Sauer <sauer.benjamin@gmail.com>
|
||||
larsenthomasj@gmail.com
|
||||
Ali Bitek <alibitek@protonmail.ch>
|
||||
Pol Batlló <pol.batllo@gmail.com>
|
||||
Anatoly Pulyaevskiy
|
||||
Hayden Flinner <haydenflinner@gmail.com>
|
||||
Stefano Rodriguez <hlsroddy@gmail.com>
|
||||
Salvatore Giordano <salvatoregiordanoo@gmail.com>
|
||||
Brian Armstrong <brian@flutter.institute>
|
||||
Paul DeMarco <paulmdemarco@gmail.com>
|
||||
Fabricio Nogueira <feufeu@gmail.com>
|
||||
Simon Lightfoot <simon@devangels.london>
|
||||
Ashton Thomas <ashton@acrinta.com>
|
||||
Thomas Danner <thmsdnnr@gmail.com>
|
||||
Diego Velásquez <diego.velasquez.lopez@gmail.com>
|
||||
Hajime Nakamura <nkmrhj@gmail.com>
|
||||
Tuyển Vũ Xuân <netsoft1985@gmail.com>
|
||||
Miguel Ruivo <miguel@miguelruivo.com>
|
||||
Sarthak Verma <sarthak@artiosys.com>
|
||||
Mike Diarmid <mike@invertase.io>
|
||||
Invertase <oss@invertase.io>
|
||||
Elliot Hesp <elliot@invertase.io>
|
||||
Vince Varga <vince.varga@smaho.com>
|
||||
Aawaz Gyawali <awazgyawali@gmail.com>
|
||||
EUI Limited <ian.evans3@admiralgroup.co.uk>
|
||||
Katarina Sheremet <katarina@sheremet.ch>
|
||||
Thomas Stockx <thomas@stockxit.com>
|
||||
Sarbagya Dhaubanjar <sarbagyastha@gmail.com>
|
||||
Ozkan Eksi <ozeksi@gmail.com>
|
||||
Rishab Nayak <rishab@bu.edu>
|
||||
ko2ic <ko2ic.dev@gmail.com>
|
||||
Jonathan Younger <jonathan@daikini.com>
|
||||
Jose Sanchez <josesm82@gmail.com>
|
||||
Debkanchan Samadder <debu.samadder@gmail.com>
|
||||
Audrius Karosevicius <audrius.karosevicius@gmail.com>
|
||||
Lukasz Piliszczuk <lukasz@intheloup.io>
|
||||
SoundReply Solutions GmbH <ch@soundreply.com>
|
||||
Rafal Wachol <rwachol@gmail.com>
|
||||
Pau Picas <pau.picas@gmail.com>
|
||||
Christian Weder <chrstian.weder@yapeal.ch>
|
||||
Alexandru Tuca <salexandru.tuca@outlook.com>
|
||||
Christian Weder <chrstian.weder@yapeal.ch>
|
||||
Rhodes Davis Jr. <rody.davis.jr@gmail.com>
|
||||
Luigi Agosti <luigi@tengio.com>
|
||||
Quentin Le Guennec <quentin@tengio.com>
|
||||
Koushik Ravikumar <koushik@tengio.com>
|
||||
Nissim Dsilva <nissim@tengio.com>
|
||||
Giancarlo Rocha <giancarloiff@gmail.com>
|
||||
Ryo Miyake <ryo@miyake.id>
|
||||
Théo Champion <contact.theochampion@gmail.com>
|
||||
Kazuki Yamaguchi <y.kazuki0614n@gmail.com>
|
||||
Eitan Schwartz <eshvartz@gmail.com>
|
||||
Chris Rutkowski <chrisrutkowski89@gmail.com>
|
||||
Juan Alvarez <juan.alvarez@resideo.com>
|
||||
Aleksandr Yurkovskiy <sanekyy@gmail.com>
|
||||
Anton Borries <mail@antonborri.es>
|
||||
Alex Li <google@alexv525.com>
|
||||
Rahul Raj <64.rahulraj@gmail.com>
|
719
packages/camera/camera/CHANGELOG.md
Normal file
@ -0,0 +1,719 @@
|
||||
## 0.10.3
|
||||
|
||||
* Adds back use of Optional type.
|
||||
|
||||
## 0.10.2+1
|
||||
|
||||
* Updates code for stricter lint checks.
|
||||
|
||||
## 0.10.2
|
||||
|
||||
* Implements option to also stream when recording a video.
|
||||
|
||||
## 0.10.1
|
||||
|
||||
* Remove usage of deprecated quiver Optional type.
|
||||
|
||||
## 0.10.0+5
|
||||
|
||||
* Updates code for stricter lint checks.
|
||||
|
||||
## 0.10.0+4
|
||||
|
||||
* Removes usage of `_ambiguate` method in example.
|
||||
* Updates minimum Flutter version to 3.0.
|
||||
|
||||
## 0.10.0+3
|
||||
|
||||
* Updates code for `no_leading_underscores_for_local_identifiers` lint.
|
||||
|
||||
## 0.10.0+2
|
||||
|
||||
* Updates imports for `prefer_relative_imports`.
|
||||
* Updates minimum Flutter version to 2.10.
|
||||
|
||||
## 0.10.0+1
|
||||
|
||||
* Fixes avoid_redundant_argument_values lint warnings and minor typos.
|
||||
|
||||
## 0.10.0
|
||||
|
||||
* **Breaking Change** Bumps default camera_web package version, which updates permission exception code from `cameraPermission` to `CameraAccessDenied`.
|
||||
* **Breaking Change** Bumps default camera_android package version, which updates permission exception code from `cameraPermission` to
|
||||
`CameraAccessDenied` and `AudioAccessDenied`.
|
||||
* Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/106316).
|
||||
|
||||
## 0.9.8+1
|
||||
|
||||
* Ignores deprecation warnings for upcoming styleFrom button API changes.
|
||||
|
||||
## 0.9.8
|
||||
|
||||
* Moves Android and iOS implementations to federated packages.
|
||||
* Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/104231).
|
||||
|
||||
## 0.9.7+1
|
||||
|
||||
* Moves streaming implementation to the platform interface package.
|
||||
|
||||
## 0.9.7
|
||||
|
||||
* Returns all the available cameras on iOS.
|
||||
|
||||
## 0.9.6
|
||||
|
||||
* Adds audio access permission handling logic on iOS to fix an issue with `prepareForVideoRecording` not awaiting for the audio permission request result.
|
||||
|
||||
## 0.9.5+1
|
||||
|
||||
* Suppresses warnings for pre-iOS-11 codepaths.
|
||||
|
||||
## 0.9.5
|
||||
|
||||
* Adds camera access permission handling logic on iOS to fix a related crash when using the camera for the first time.
|
||||
|
||||
## 0.9.4+24
|
||||
|
||||
* Fixes preview orientation when pausing preview with locked orientation.
|
||||
|
||||
## 0.9.4+23
|
||||
|
||||
* Minor fixes for new analysis options.
|
||||
|
||||
## 0.9.4+22
|
||||
|
||||
* Removes unnecessary imports.
|
||||
* Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors
|
||||
lint warnings.
|
||||
|
||||
## 0.9.4+21
|
||||
|
||||
* Fixes README code samples.
|
||||
|
||||
## 0.9.4+20
|
||||
|
||||
* Fixes an issue with the orientation of videos recorded in landscape on Android.
|
||||
|
||||
## 0.9.4+19
|
||||
|
||||
* Migrate deprecated Scaffold SnackBar methods to ScaffoldMessenger.
|
||||
|
||||
## 0.9.4+18
|
||||
|
||||
* Fixes a crash in iOS when streaming on low-performance devices.
|
||||
|
||||
## 0.9.4+17
|
||||
|
||||
* Removes obsolete information from README, and adds OS support table.
|
||||
|
||||
## 0.9.4+16
|
||||
|
||||
* Fixes a bug resulting in a `CameraAccessException` that prevents image
|
||||
capture on some Android devices.
|
||||
|
||||
## 0.9.4+15
|
||||
|
||||
* Uses dispatch queue for pixel buffer synchronization on iOS.
|
||||
* Minor iOS internal code cleanup related to queue helper functions.
|
||||
|
||||
## 0.9.4+14
|
||||
|
||||
* Restores compatibility with Flutter 2.5 and 2.8.
|
||||
|
||||
## 0.9.4+13
|
||||
|
||||
* Updates iOS camera's photo capture delegate reference on a background queue to prevent potential race conditions, and some related internal code cleanup.
|
||||
|
||||
## 0.9.4+12
|
||||
|
||||
* Skips unnecessary AppDelegate setup for unit tests on iOS.
|
||||
* Internal code cleanup for stricter analysis options.
|
||||
|
||||
## 0.9.4+11
|
||||
|
||||
* Manages iOS camera's orientation-related states on a background queue to prevent potential race conditions.
|
||||
|
||||
## 0.9.4+10
|
||||
|
||||
* iOS performance improvement by moving file writing from the main queue to a background IO queue.
|
||||
|
||||
## 0.9.4+9
|
||||
|
||||
* iOS performance improvement by moving sample buffer handling from the main queue to a background session queue.
|
||||
* Minor iOS internal code cleanup related to camera class and its delegate.
|
||||
* Minor iOS internal code cleanup related to resolution preset, video format, focus mode, exposure mode and device orientation.
|
||||
* Minor iOS internal code cleanup related to flash mode.
|
||||
|
||||
## 0.9.4+8
|
||||
|
||||
* Fixes a bug where ImageFormatGroup was ignored in `startImageStream` on iOS.
|
||||
|
||||
## 0.9.4+7
|
||||
|
||||
* Fixes a crash in iOS when passing null queue pointer into AVFoundation API due to race condition.
|
||||
* Minor iOS internal code cleanup related to dispatch queue.
|
||||
|
||||
## 0.9.4+6
|
||||
|
||||
* Fixes a crash in iOS when using image stream due to calling Flutter engine API on non-main thread.
|
||||
|
||||
## 0.9.4+5
|
||||
|
||||
* Fixes bug where calling a method after the camera was closed resulted in a Java `IllegalStateException` exception.
|
||||
* Fixes integration tests.
|
||||
|
||||
## 0.9.4+4
|
||||
|
||||
* Change Android compileSdkVersion to 31.
|
||||
* Remove usages of deprecated Android API `CamcorderProfile`.
|
||||
* Update gradle version to 7.0.2 on Android.
|
||||
|
||||
## 0.9.4+3
|
||||
|
||||
* Fix registerTexture and result being called on background thread on iOS.
|
||||
|
||||
## 0.9.4+2
|
||||
|
||||
* Updated package description;
|
||||
* Refactor unit test on iOS to make it compatible with new restrictions in Xcode 13 which only supports the use of the `XCUIDevice` in Xcode UI tests.
|
||||
|
||||
## 0.9.4+1
|
||||
|
||||
* Fixed Android implementation throwing IllegalStateException when switching to a different activity.
|
||||
|
||||
## 0.9.4
|
||||
|
||||
* Add web support by endorsing `package:camera_web`.
|
||||
|
||||
## 0.9.3+1
|
||||
|
||||
* Remove iOS 9 availability check around ultra high capture sessions.
|
||||
|
||||
## 0.9.3
|
||||
|
||||
* Update minimum Flutter SDK to 2.5 and iOS deployment target to 9.0.
|
||||
|
||||
## 0.9.2+2
|
||||
|
||||
* Ensure that setting the exposure offset returns the new offset value on Android.
|
||||
|
||||
## 0.9.2+1
|
||||
|
||||
* Fixed camera controller throwing an exception when being replaced in the preview widget.
|
||||
|
||||
## 0.9.2
|
||||
|
||||
* Added functions to pause and resume the camera preview.
|
||||
|
||||
## 0.9.1+1
|
||||
|
||||
* Replace `device_info` reference with `device_info_plus` in the [README.md](README.md)
|
||||
|
||||
## 0.9.1
|
||||
|
||||
* Added `lensAperture`, `sensorExposureTime` and `sensorSensitivity` properties to the `CameraImage` dto.
|
||||
|
||||
## 0.9.0
|
||||
|
||||
* Complete rewrite of Android plugin to fix many capture, focus, flash, orientation and exposure issues.
|
||||
* Fixed crash when opening front-facing cameras on some legacy android devices like Sony XZ.
|
||||
* Android Flash mode works with full precapture sequence.
|
||||
* Updated Android lint settings.
|
||||
|
||||
## 0.8.1+7
|
||||
|
||||
* Fix device orientation sometimes not affecting the camera preview orientation.
|
||||
|
||||
## 0.8.1+6
|
||||
|
||||
* Remove references to the Android V1 embedding.
|
||||
|
||||
## 0.8.1+5
|
||||
|
||||
* Make sure the `setFocusPoint` and `setExposurePoint` coordinates work correctly in all orientations on iOS (instead of only in portrait mode).
|
||||
|
||||
## 0.8.1+4
|
||||
|
||||
* Silenced warnings that may occur during build when using a very
|
||||
recent version of Flutter relating to null safety.
|
||||
|
||||
## 0.8.1+3
|
||||
|
||||
* Do not change camera orientation when iOS device is flat.
|
||||
|
||||
## 0.8.1+2
|
||||
|
||||
* Fix iOS crash when selecting an unsupported FocusMode.
|
||||
|
||||
## 0.8.1+1
|
||||
|
||||
* Migrate maven repository from jcenter to mavenCentral.
|
||||
|
||||
## 0.8.1
|
||||
|
||||
* Solved a rotation issue on iOS which caused the default preview to be displayed as landscape right instead of portrait.
|
||||
|
||||
## 0.8.0
|
||||
|
||||
* Stable null safety release.
|
||||
* Solved delay when using the zoom feature on iOS.
|
||||
* Added a timeout to the pre-capture sequence on Android to prevent crashes when the camera cannot get a focus.
|
||||
* Updates the example code listed in the [README.md](README.md), so it runs without errors when you simply copy/ paste it into a Flutter App.
|
||||
|
||||
## 0.7.0+4
|
||||
|
||||
* Fix crash when taking picture with orientation lock
|
||||
|
||||
## 0.7.0+3
|
||||
|
||||
* Clockwise rotation of focus point in android
|
||||
|
||||
## 0.7.0+2
|
||||
|
||||
* Fix example reference in README.
|
||||
* Revert compileSdkVersion back to 29 (from 30) as this is causing problems with add-to-app configurations.
|
||||
|
||||
## 0.7.0+1
|
||||
|
||||
* Ensure communication from JAVA to Dart is done on the main UI thread.
|
||||
|
||||
## 0.7.0
|
||||
|
||||
* BREAKING CHANGE: `CameraValue.aspectRatio` now returns `width / height` rather than `height / width`. [(commit)](https://github.com/flutter/plugins/commit/100c7470d4066b1d0f8f7e4ec6d7c943e736f970)
|
||||
* Added support for capture orientation locking on Android and iOS.
|
||||
* Fixed camera preview not rotating correctly on Android and iOS.
|
||||
* Fixed camera preview sometimes appearing stretched on Android and iOS.
|
||||
* Fixed videos & photos saving with the incorrect rotation on iOS.
|
||||
* New Features:
|
||||
* Adds auto focus support for Android and iOS implementations. [(commmit)](https://github.com/flutter/plugins/commit/71a831790220f898bf8120c8a23840ac6e742db5)
|
||||
* Adds ImageFormat selection for ImageStream and Video(iOS only). [(commit)](https://github.com/flutter/plugins/commit/da1b4638b750a5ff832d7be86a42831c42c6d6c0)
|
||||
* Bug Fixes:
|
||||
* Fixes crash when taking a picture on iOS devices without flash. [(commit)](https://github.com/flutter/plugins/commit/831344490984b1feec007afc9c8595d80b6c13f4)
|
||||
* Make sure the configured zoom scale is copied over to the final capture builder on Android. Fixes the issue where the preview is zoomed but the final picture is not. [(commit)](https://github.com/flutter/plugins/commit/5916f55664e1772a4c3f0c02c5c71fc11e491b76)
|
||||
* Fixes crash with using inner camera on some Android devices. [(commit)](https://github.com/flutter/plugins/commit/980b674cb4020c1927917426211a87e275346d5e)
|
||||
* Improved error feedback by differentiating between uninitialized and disposed camera controllers. [(commit)](https://github.com/flutter/plugins/commit/d0b7109f6b00a0eda03506fed2c74cc123ffc6f3)
|
||||
* Fixes picture captures causing a crash on some Huawei devices. [(commit)](https://github.com/flutter/plugins/commit/6d18db83f00f4861ffe485aba2d1f8aa08845ce6)
|
||||
|
||||
## 0.6.4+5
|
||||
|
||||
* Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets.
|
||||
|
||||
## 0.6.4+4
|
||||
|
||||
* Set camera auto focus enabled by default.
|
||||
|
||||
## 0.6.4+3
|
||||
|
||||
* Detect if selected camera supports auto focus and act accordingly on Android. This solves a problem where front facing cameras are not capturing the picture because auto focus is not supported.
|
||||
|
||||
## 0.6.4+2
|
||||
|
||||
* Set ImageStreamReader listener to null to prevent stale images when streaming images.
|
||||
|
||||
## 0.6.4+1
|
||||
|
||||
* Added closeCaptureSession() to stopVideoRecording in Camera.java to fix an Android 6 crash.
|
||||
|
||||
## 0.6.4
|
||||
|
||||
* Adds auto exposure support for Android and iOS implementations.
|
||||
|
||||
## 0.6.3+4
|
||||
|
||||
* Revert previous dependency update: Changed dependency on camera_platform_interface to >=1.04 <1.1.0.
|
||||
|
||||
## 0.6.3+3
|
||||
|
||||
* Updated dependency on camera_platform_interface to ^1.2.0.
|
||||
|
||||
## 0.6.3+2
|
||||
|
||||
* Fixes crash on Android which occurs after video recording has stopped just before taking a picture.
|
||||
|
||||
## 0.6.3+1
|
||||
|
||||
* Fixes flash & torch modes not working on some Android devices.
|
||||
|
||||
## 0.6.3
|
||||
|
||||
* Adds torch mode as a flash mode for Android and iOS implementations.
|
||||
|
||||
## 0.6.2+1
|
||||
|
||||
* Fix the API documentation for the `CameraController.takePicture` method.
|
||||
|
||||
## 0.6.2
|
||||
|
||||
* Add zoom support for Android and iOS implementations.
|
||||
|
||||
## 0.6.1+1
|
||||
|
||||
* Added implementation of the `didFinishProcessingPhoto` on iOS which allows saving image metadata (EXIF) on iOS 11 and up.
|
||||
|
||||
## 0.6.1
|
||||
|
||||
* Add flash support for Android and iOS implementations.
|
||||
|
||||
## 0.6.0+2
|
||||
|
||||
* Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276))
|
||||
|
||||
## 0.6.0+1
|
||||
|
||||
Updated README to inform users that iOS 10.0+ is needed for use
|
||||
|
||||
## 0.6.0
|
||||
|
||||
As part of implementing federated architecture and making the interface compatible with the web this version contains the following **breaking changes**:
|
||||
|
||||
Method changes in `CameraController`:
|
||||
- The `takePicture` method no longer accepts the `path` parameter, but instead returns the captured image as an instance of the `XFile` class;
|
||||
- The `startVideoRecording` method no longer accepts the `filePath`. Instead the recorded video is now returned as a `XFile` instance when the `stopVideoRecording` method completes;
|
||||
- The `stopVideoRecording` method now returns the captured video when it completes;
|
||||
- Added the `buildPreview` method which is now used to implement the CameraPreview widget.
|
||||
|
||||
## 0.5.8+19
|
||||
|
||||
* Update Flutter SDK constraint.
|
||||
|
||||
## 0.5.8+18
|
||||
|
||||
* Suppress unchecked warning in Android tests which prevented the tests to compile.
|
||||
|
||||
## 0.5.8+17
|
||||
|
||||
* Added Android 30 support.
|
||||
|
||||
## 0.5.8+16
|
||||
|
||||
* Moved package to camera/camera subdir, to allow for federated implementations.
|
||||
|
||||
## 0.5.8+15
|
||||
|
||||
* Added the `debugCheckIsDisposed` method which can be used in debug mode to validate if the `CameraController` class has been disposed.
|
||||
|
||||
## 0.5.8+14
|
||||
|
||||
* Changed the order of the setters for `mediaRecorder` in `MediaRecorderBuilder.java` to make it more readable.
|
||||
|
||||
## 0.5.8+13
|
||||
|
||||
* Added Dartdocs for all public APIs.
|
||||
|
||||
## 0.5.8+12
|
||||
|
||||
* Added information of video not working correctly on Android emulators to `README.md`.
|
||||
|
||||
## 0.5.8+11
|
||||
|
||||
* Fix rare nullptr exception on Android.
|
||||
* Updated README.md with information about handling App lifecycle changes.
|
||||
|
||||
## 0.5.8+10
|
||||
|
||||
* Suppress the `deprecated_member_use` warning in the example app for `ScaffoldMessenger.showSnackBar`.
|
||||
|
||||
## 0.5.8+9
|
||||
|
||||
* Update android compileSdkVersion to 29.
|
||||
|
||||
## 0.5.8+8
|
||||
|
||||
* Fixed garbled audio (in video) by setting audio encoding bitrate.
|
||||
|
||||
## 0.5.8+7
|
||||
|
||||
* Keep handling deprecated Android v1 classes for backward compatibility.
|
||||
|
||||
## 0.5.8+6
|
||||
|
||||
* Avoiding uses or overrides a deprecated API in CameraPlugin.java.
|
||||
|
||||
## 0.5.8+5
|
||||
|
||||
* Fix compilation/availability issues on iOS.
|
||||
|
||||
## 0.5.8+4
|
||||
|
||||
* Fixed bug caused by casting a `CameraAccessException` on Android.
|
||||
|
||||
## 0.5.8+3
|
||||
|
||||
* Fix bug in usage example in README.md
|
||||
|
||||
## 0.5.8+2
|
||||
|
||||
* Post-v2 embedding cleanups.
|
||||
|
||||
## 0.5.8+1
|
||||
|
||||
* Update lower bound of dart dependency to 2.1.0.
|
||||
|
||||
## 0.5.8
|
||||
|
||||
* Remove Android dependencies fallback.
|
||||
* Require Flutter SDK 1.12.13+hotfix.5 or greater.
|
||||
|
||||
## 0.5.7+5
|
||||
|
||||
* Replace deprecated `getFlutterEngine` call on Android.
|
||||
|
||||
## 0.5.7+4
|
||||
|
||||
* Add `pedantic` to dev_dependency.
|
||||
|
||||
## 0.5.7+3
|
||||
|
||||
* Fix an Android crash when permissions are requested multiple times.
|
||||
|
||||
## 0.5.7+2
|
||||
|
||||
* Remove the deprecated `author:` field from pubspec.yaml
|
||||
* Migrate the plugin to the pubspec platforms manifest.
|
||||
* Require Flutter SDK 1.10.0 or greater.
|
||||
|
||||
## 0.5.7+1
|
||||
|
||||
* Fix example null exception.
|
||||
|
||||
## 0.5.7
|
||||
|
||||
* Fix unawaited futures.
|
||||
|
||||
## 0.5.6+4
|
||||
|
||||
* Android: Use CameraDevice.TEMPLATE_RECORD to improve image streaming.
|
||||
|
||||
## 0.5.6+3
|
||||
|
||||
* Remove AndroidX warning.
|
||||
|
||||
## 0.5.6+2
|
||||
|
||||
* Include lifecycle dependency as a compileOnly one on Android to resolve
|
||||
potential version conflicts with other transitive libraries.
|
||||
|
||||
## 0.5.6+1
|
||||
|
||||
* Android: Use android.arch.lifecycle instead of androidx.lifecycle:lifecycle in `build.gradle` to support apps that has not been migrated to AndroidX.
|
||||
|
||||
## 0.5.6
|
||||
|
||||
* Add support for the v2 Android embedding. This shouldn't affect existing
|
||||
functionality.
|
||||
|
||||
## 0.5.5+1
|
||||
|
||||
* Fix event type check
|
||||
|
||||
## 0.5.5
|
||||
|
||||
* Define clang modules for iOS.
|
||||
|
||||
## 0.5.4+3
|
||||
|
||||
* Update and migrate iOS example project.
|
||||
|
||||
## 0.5.4+2
|
||||
|
||||
* Fix Android NullPointerException on devices with only front-facing camera.
|
||||
|
||||
## 0.5.4+1
|
||||
|
||||
* Fix Android pause and resume video crash when executing in APIs below 24.
|
||||
|
||||
## 0.5.4
|
||||
|
||||
* Add feature to pause and resume video recording.
|
||||
|
||||
## 0.5.3+1
|
||||
|
||||
* Fix too large request code for FragmentActivity users.
|
||||
|
||||
## 0.5.3
|
||||
|
||||
* Added new quality presets.
|
||||
* Now all quality presets can be used to control image capture quality.
|
||||
|
||||
## 0.5.2+2
|
||||
|
||||
* Fix memory leak related to not unregistering stream handler in FlutterEventChannel when disposing camera.
|
||||
|
||||
## 0.5.2+1
|
||||
|
||||
* Fix bug that prevented video recording with audio.
|
||||
|
||||
## 0.5.2
|
||||
|
||||
* Added capability to disable audio for the `CameraController`. (e.g. `CameraController(_, _,
|
||||
enableAudio: false);`)
|
||||
|
||||
## 0.5.1
|
||||
|
||||
* Can now be compiled with earlier Android sdks below 21 when
|
||||
`<uses-sdk tools:overrideLibrary="io.flutter.plugins.camera"/>` has been added to the project
|
||||
`AndroidManifest.xml`. For sdks below 21, the plugin won't be registered and calls to it will throw
|
||||
a `MissingPluginException.`
|
||||
|
||||
## 0.5.0
|
||||
|
||||
* **Breaking Change** This plugin no longer handles closing and opening the camera on Android
|
||||
lifecycle changes. Please use `WidgetsBindingObserver` to control camera resources on lifecycle
|
||||
changes. See example project for example using `WidgetsBindingObserver`.
|
||||
|
||||
## 0.4.3+2
|
||||
|
||||
* Bump the minimum Flutter version to 1.2.0.
|
||||
* Add template type parameter to `invokeMethod` calls.
|
||||
|
||||
## 0.4.3+1
|
||||
|
||||
* Catch additional `Exception`s from Android and throw as `CameraException`s.
|
||||
|
||||
## 0.4.3
|
||||
|
||||
* Add capability to prepare the capture session for video recording on iOS.
|
||||
|
||||
## 0.4.2
|
||||
|
||||
* Add sensor orientation value to `CameraDescription`.
|
||||
|
||||
## 0.4.1
|
||||
|
||||
* Camera methods are ran in a background thread on iOS.
|
||||
|
||||
## 0.4.0+3
|
||||
|
||||
* Fixed a crash when the plugin is registered by a background FlutterView.
|
||||
|
||||
## 0.4.0+2
|
||||
|
||||
* Fix orientation of captured photos when camera is used for the first time on Android.
|
||||
|
||||
## 0.4.0+1
|
||||
|
||||
* Remove categories.
|
||||
|
||||
## 0.4.0
|
||||
|
||||
* **Breaking Change** Change iOS image stream format to `ImageFormatGroup.bgra8888` from
|
||||
`ImageFormatGroup.yuv420`.
|
||||
|
||||
## 0.3.0+4
|
||||
|
||||
* Fixed bug causing black screen on some Android devices.
|
||||
|
||||
## 0.3.0+3
|
||||
|
||||
* Log a more detailed warning at build time about the previous AndroidX
|
||||
migration.
|
||||
|
||||
## 0.3.0+2
|
||||
|
||||
* Fix issue with calculating iOS image orientation in certain edge cases.
|
||||
|
||||
## 0.3.0+1
|
||||
|
||||
* Remove initial method call invocation from static camera method.
|
||||
|
||||
## 0.3.0
|
||||
|
||||
* **Breaking change**. Migrate from the deprecated original Android Support
|
||||
Library to AndroidX. This shouldn't result in any functional changes, but it
|
||||
requires any Android apps using this plugin to [also
|
||||
migrate](https://developer.android.com/jetpack/androidx/migrate) if they're
|
||||
using the original support library.
|
||||
|
||||
## 0.2.9+1
|
||||
|
||||
* Fix a crash when failing to start preview.
|
||||
|
||||
## 0.2.9
|
||||
|
||||
* Save photo orientation data on iOS.
|
||||
|
||||
## 0.2.8
|
||||
|
||||
* Add access to the image stream from Dart.
|
||||
* Use `cameraController.startImageStream(listener)` to process the images.
|
||||
|
||||
## 0.2.7
|
||||
|
||||
* Fix issue with crash when the physical device's orientation is unknown.
|
||||
|
||||
## 0.2.6
|
||||
|
||||
* Update the camera to use the physical device's orientation instead of the UI
|
||||
orientation on Android.
|
||||
|
||||
## 0.2.5
|
||||
|
||||
* Fix preview and video size with satisfying conditions of multiple outputs.
|
||||
|
||||
## 0.2.4
|
||||
|
||||
* Unregister the activity lifecycle callbacks when disposing the camera.
|
||||
|
||||
## 0.2.3
|
||||
|
||||
* Added path_provider and video_player as dev dependencies because the example uses them.
|
||||
* Updated example path_provider version to get Dart 2 support.
|
||||
|
||||
## 0.2.2
|
||||
|
||||
* iOS image capture is done in high quality (full camera size)
|
||||
|
||||
## 0.2.1
|
||||
|
||||
* Updated Gradle tooling to match Android Studio 3.1.2.
|
||||
|
||||
## 0.2.0
|
||||
|
||||
* Added support for video recording.
|
||||
* Changed the example app to add video recording.
|
||||
|
||||
A lot of **breaking changes** in this version:
|
||||
|
||||
Getter changes:
|
||||
- Removed `isStarted`
|
||||
- Renamed `initialized` to `isInitialized`
|
||||
- Added `isRecordingVideo`
|
||||
|
||||
Method changes:
|
||||
- Renamed `capture` to `takePicture`
|
||||
- Removed `start` (the preview starts automatically when `initialize` is called)
|
||||
- Added `startVideoRecording(String filePath)`
|
||||
- Removed `stop` (the preview stops automatically when `dispose` is called)
|
||||
- Added `stopVideoRecording`
|
||||
|
||||
## 0.1.2
|
||||
|
||||
* Fix Dart 2 runtime errors.
|
||||
|
||||
## 0.1.1
|
||||
|
||||
* Fix Dart 2 runtime error.
|
||||
|
||||
## 0.1.0
|
||||
|
||||
* **Breaking change**. Set SDK constraints to match the Flutter beta release.
|
||||
|
||||
## 0.0.4
|
||||
|
||||
* Revert regression of `CameraController.capture()` introduced in v. 0.0.3.
|
||||
|
||||
## 0.0.3
|
||||
|
||||
* Improved resource cleanup on Android. Avoids crash on Activity restart.
|
||||
* Made the Future returned by `CameraController.dispose()` and `CameraController.capture()` actually complete on
|
||||
Android.
|
||||
|
||||
## 0.0.2
|
||||
|
||||
* Simplified and upgraded Android project template to Android SDK 27.
|
||||
* Moved Android package to io.flutter.plugins.
|
||||
* Fixed warnings from the Dart 2.0 analyzer.
|
||||
|
||||
## 0.0.1
|
||||
|
||||
* Initial release
|
25
packages/camera/camera/LICENSE
Normal file
@ -0,0 +1,25 @@
|
||||
Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
174
packages/camera/camera/README.md
Normal file
@ -0,0 +1,174 @@
|
||||
# Camera Plugin
|
||||
|
||||
<?code-excerpt path-base="excerpts/packages/camera_example"?>
|
||||
|
||||
[](https://pub.dev/packages/camera)
|
||||
|
||||
A Flutter plugin for iOS, Android and Web allowing access to the device cameras.
|
||||
|
||||
| | Android | iOS | Web |
|
||||
|----------------|---------|----------|------------------------|
|
||||
| **Support** | SDK 21+ | iOS 10+* | [See `camera_web `][1] |
|
||||
|
||||
## Features
|
||||
|
||||
* Display live camera preview in a widget.
|
||||
* Snapshots can be captured and saved to a file.
|
||||
* Record video.
|
||||
* Add access to the image stream from Dart.
|
||||
|
||||
## Installation
|
||||
|
||||
First, add `camera` as a [dependency in your pubspec.yaml file](https://flutter.dev/using-packages/).
|
||||
|
||||
### iOS
|
||||
|
||||
\* The camera plugin compiles for any version of iOS, but its functionality
|
||||
requires iOS 10 or higher. If compiling for iOS 9, make sure to programmatically
|
||||
check the version of iOS running on the device before using any camera plugin features.
|
||||
The [device_info_plus](https://pub.dev/packages/device_info_plus) plugin, for example, can be used to check the iOS version.
|
||||
|
||||
Add two rows to the `ios/Runner/Info.plist`:
|
||||
|
||||
* one with the key `Privacy - Camera Usage Description` and a usage description.
|
||||
* and one with the key `Privacy - Microphone Usage Description` and a usage description.
|
||||
|
||||
If editing `Info.plist` as text, add:
|
||||
|
||||
```xml
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>your usage description here</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>your usage description here</string>
|
||||
```
|
||||
|
||||
### Android
|
||||
|
||||
Change the minimum Android sdk version to 21 (or higher) in your `android/app/build.gradle` file.
|
||||
|
||||
```groovy
|
||||
minSdkVersion 21
|
||||
```
|
||||
|
||||
It's important to note that the `MediaRecorder` class is not working properly on emulators, as stated in the documentation: https://developer.android.com/reference/android/media/MediaRecorder. Specifically, when recording a video with sound enabled and trying to play it back, the duration won't be correct and you will only see the first frame.
|
||||
|
||||
### Web integration
|
||||
|
||||
For web integration details, see the
|
||||
[`camera_web` package](https://pub.dev/packages/camera_web).
|
||||
|
||||
### Handling Lifecycle states
|
||||
|
||||
As of version [0.5.0](https://github.com/flutter/plugins/blob/main/packages/camera/CHANGELOG.md#050) of the camera plugin, lifecycle changes are no longer handled by the plugin. This means developers are now responsible to control camera resources when the lifecycle state is updated. Failure to do so might lead to unexpected behavior (for example as described in issue [#39109](https://github.com/flutter/flutter/issues/39109)). Handling lifecycle changes can be done by overriding the `didChangeAppLifecycleState` method like so:
|
||||
|
||||
<?code-excerpt "main.dart (AppLifecycle)"?>
|
||||
```dart
|
||||
@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) {
|
||||
onNewCameraSelected(cameraController.description);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Handling camera access permissions
|
||||
|
||||
Permission errors may be thrown when initializing the camera controller, and you are expected to handle them properly.
|
||||
|
||||
Here is a list of all permission error codes that can be thrown:
|
||||
|
||||
- `CameraAccessDenied`: Thrown when user denies the camera access permission.
|
||||
|
||||
- `CameraAccessDeniedWithoutPrompt`: iOS only for now. Thrown when user has previously denied the permission. iOS does not allow prompting alert dialog a second time. Users will have to go to Settings > Privacy > Camera in order to enable camera access.
|
||||
|
||||
- `CameraAccessRestricted`: iOS only for now. Thrown when camera access is restricted and users cannot grant permission (parental control).
|
||||
|
||||
- `AudioAccessDenied`: Thrown when user denies the audio access permission.
|
||||
|
||||
- `AudioAccessDeniedWithoutPrompt`: iOS only for now. Thrown when user has previously denied the permission. iOS does not allow prompting alert dialog a second time. Users will have to go to Settings > Privacy > Microphone in order to enable audio access.
|
||||
|
||||
- `AudioAccessRestricted`: iOS only for now. Thrown when audio access is restricted and users cannot grant permission (parental control).
|
||||
|
||||
### Example
|
||||
|
||||
Here is a small example flutter app displaying a full screen camera preview.
|
||||
|
||||
<?code-excerpt "readme_full_example.dart (FullAppExample)"?>
|
||||
```dart
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
late List<CameraDescription> _cameras;
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
_cameras = await availableCameras();
|
||||
runApp(const CameraApp());
|
||||
}
|
||||
|
||||
/// CameraApp is the Main Application.
|
||||
class CameraApp extends StatefulWidget {
|
||||
/// Default Constructor
|
||||
const CameraApp({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<CameraApp> createState() => _CameraAppState();
|
||||
}
|
||||
|
||||
class _CameraAppState extends State<CameraApp> {
|
||||
late CameraController controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = CameraController(_cameras[0], ResolutionPreset.max);
|
||||
controller.initialize().then((_) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {});
|
||||
}).catchError((Object e) {
|
||||
if (e is CameraException) {
|
||||
switch (e.code) {
|
||||
case 'CameraAccessDenied':
|
||||
// Handle access errors here.
|
||||
break;
|
||||
default:
|
||||
// Handle other errors here.
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!controller.value.isInitialized) {
|
||||
return Container();
|
||||
}
|
||||
return MaterialApp(
|
||||
home: CameraPreview(controller),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For a more elaborate usage example see [here](https://github.com/flutter/plugins/tree/main/packages/camera/camera/example).
|
||||
|
||||
[1]: https://pub.dev/packages/camera_web#limitations-on-the-web-platform
|
64
packages/camera/camera/example/android/app/build.gradle
Normal file
@ -0,0 +1,64 @@
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||
if (flutterRoot == null) {
|
||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.flutter.plugins.cameraexample"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
profile {
|
||||
matchingFallbacks = ['debug', 'release']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
}
|
5
packages/camera/camera/example/android/app/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
@ -0,0 +1,14 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface DartIntegrationTest {}
|
@ -0,0 +1,19 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.cameraexample;
|
||||
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
import dev.flutter.plugins.integration_test.FlutterTestRunner;
|
||||
import io.flutter.embedding.android.FlutterActivity;
|
||||
import io.flutter.plugins.DartIntegrationTest;
|
||||
import org.junit.Rule;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@DartIntegrationTest
|
||||
@RunWith(FlutterTestRunner.class)
|
||||
public class FlutterActivityTest {
|
||||
@Rule
|
||||
public ActivityTestRule<FlutterActivity> rule = new ActivityTestRule<>(FlutterActivity.class);
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="io.flutter.plugins.cameraexample">
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="camera_example">
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
|
||||
android:hardwareAccelerated="true"
|
||||
android:launchMode="singleTop"
|
||||
android:name="io.flutter.embedding.android.FlutterActivity"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<meta-data android:name="flutterEmbedding" android:value="2"/>
|
||||
</application>
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="true"/>
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.FLASHLIGHT"/>
|
||||
</manifest>
|
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
After Width: | Height: | Size: 544 B |
After Width: | Height: | Size: 442 B |
After Width: | Height: | Size: 721 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
Flutter draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
</resources>
|
29
packages/camera/camera/example/android/build.gradle
Normal file
@ -0,0 +1,29 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.1'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
4
packages/camera/camera/example/android/gradle.properties
Normal file
@ -0,0 +1,4 @@
|
||||
org.gradle.jvmargs=-Xmx4G
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=false
|
||||
android.enableR8=true
|
5
packages/camera/camera/example/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
|
15
packages/camera/camera/example/android/settings.gradle
Normal file
@ -0,0 +1,15 @@
|
||||
include ':app'
|
||||
|
||||
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
|
||||
|
||||
def plugins = new Properties()
|
||||
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
|
||||
if (pluginsFile.exists()) {
|
||||
pluginsFile.withInputStream { stream -> plugins.load(stream) }
|
||||
}
|
||||
|
||||
plugins.each { name, path ->
|
||||
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
|
||||
include ":$name"
|
||||
project(":$name").projectDir = pluginDirectory
|
||||
}
|
15
packages/camera/camera/example/build.excerpt.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
targets:
|
||||
$default:
|
||||
sources:
|
||||
include:
|
||||
- lib/**
|
||||
# Some default includes that aren't really used here but will prevent
|
||||
# false-negative warnings:
|
||||
- $package$
|
||||
- lib/$lib$
|
||||
exclude:
|
||||
- '**/.*/**'
|
||||
- '**/build/**'
|
||||
builders:
|
||||
code_excerpter|code_excerpter:
|
||||
enabled: true
|
293
packages/camera/camera/example/integration_test/camera_test.dart
Normal file
@ -0,0 +1,293 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// 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 'dart:ui';
|
||||
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
void main() {
|
||||
late Directory testDir;
|
||||
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
setUpAll(() async {
|
||||
final Directory extDir = await getTemporaryDirectory();
|
||||
testDir = await Directory('${extDir.path}/test').create(recursive: true);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await testDir.delete(recursive: true);
|
||||
});
|
||||
|
||||
final Map<ResolutionPreset, Size> presetExpectedSizes =
|
||||
<ResolutionPreset, Size>{
|
||||
ResolutionPreset.low:
|
||||
Platform.isAndroid ? const Size(240, 320) : const Size(288, 352),
|
||||
ResolutionPreset.medium:
|
||||
Platform.isAndroid ? const Size(480, 720) : const Size(480, 640),
|
||||
ResolutionPreset.high: const Size(720, 1280),
|
||||
ResolutionPreset.veryHigh: const Size(1080, 1920),
|
||||
ResolutionPreset.ultraHigh: const Size(2160, 3840),
|
||||
// Don't bother checking for max here since it could be anything.
|
||||
};
|
||||
|
||||
/// Verify that [actual] has dimensions that are at least as large as
|
||||
/// [expectedSize]. Allows for a mismatch in portrait vs landscape. Returns
|
||||
/// whether the dimensions exactly match.
|
||||
bool assertExpectedDimensions(Size expectedSize, Size actual) {
|
||||
expect(actual.shortestSide, lessThanOrEqualTo(expectedSize.shortestSide));
|
||||
expect(actual.longestSide, lessThanOrEqualTo(expectedSize.longestSide));
|
||||
return actual.shortestSide == expectedSize.shortestSide &&
|
||||
actual.longestSide == expectedSize.longestSide;
|
||||
}
|
||||
|
||||
// This tests that the capture is no bigger than the preset, since we have
|
||||
// automatic code to fall back to smaller sizes when we need to. Returns
|
||||
// whether the image is exactly the desired resolution.
|
||||
Future<bool> testCaptureImageResolution(
|
||||
CameraController controller, ResolutionPreset preset) async {
|
||||
final Size expectedSize = presetExpectedSizes[preset]!;
|
||||
|
||||
// Take Picture
|
||||
final XFile file = await controller.takePicture();
|
||||
|
||||
// Load picture
|
||||
final File fileImage = File(file.path);
|
||||
final Image image = await decodeImageFromList(fileImage.readAsBytesSync());
|
||||
|
||||
// Verify image dimensions are as expected
|
||||
expect(image, isNotNull);
|
||||
return assertExpectedDimensions(
|
||||
expectedSize, Size(image.height.toDouble(), image.width.toDouble()));
|
||||
}
|
||||
|
||||
testWidgets(
|
||||
'Capture specific image resolutions',
|
||||
(WidgetTester tester) async {
|
||||
final List<CameraDescription> cameras = await availableCameras();
|
||||
if (cameras.isEmpty) {
|
||||
return;
|
||||
}
|
||||
for (final CameraDescription cameraDescription in cameras) {
|
||||
bool previousPresetExactlySupported = true;
|
||||
for (final MapEntry<ResolutionPreset, Size> preset
|
||||
in presetExpectedSizes.entries) {
|
||||
final CameraController controller =
|
||||
CameraController(cameraDescription, preset.key);
|
||||
await controller.initialize();
|
||||
final bool presetExactlySupported =
|
||||
await testCaptureImageResolution(controller, preset.key);
|
||||
assert(!(!previousPresetExactlySupported && presetExactlySupported),
|
||||
'The camera took higher resolution pictures at a lower resolution.');
|
||||
previousPresetExactlySupported = presetExactlySupported;
|
||||
await controller.dispose();
|
||||
}
|
||||
}
|
||||
},
|
||||
// TODO(egarciad): Fix https://github.com/flutter/flutter/issues/93686.
|
||||
skip: true,
|
||||
);
|
||||
|
||||
// This tests that the capture is no bigger than the preset, since we have
|
||||
// automatic code to fall back to smaller sizes when we need to. Returns
|
||||
// whether the image is exactly the desired resolution.
|
||||
Future<bool> testCaptureVideoResolution(
|
||||
CameraController controller, ResolutionPreset preset) async {
|
||||
final Size expectedSize = presetExpectedSizes[preset]!;
|
||||
|
||||
// Take Video
|
||||
await controller.startVideoRecording();
|
||||
sleep(const Duration(milliseconds: 300));
|
||||
final XFile file = await controller.stopVideoRecording();
|
||||
|
||||
// Load video metadata
|
||||
final File videoFile = File(file.path);
|
||||
final VideoPlayerController videoController =
|
||||
VideoPlayerController.file(videoFile);
|
||||
await videoController.initialize();
|
||||
final Size video = videoController.value.size;
|
||||
|
||||
// Verify image dimensions are as expected
|
||||
expect(video, isNotNull);
|
||||
return assertExpectedDimensions(
|
||||
expectedSize, Size(video.height, video.width));
|
||||
}
|
||||
|
||||
testWidgets(
|
||||
'Capture specific video resolutions',
|
||||
(WidgetTester tester) async {
|
||||
final List<CameraDescription> cameras = await availableCameras();
|
||||
if (cameras.isEmpty) {
|
||||
return;
|
||||
}
|
||||
for (final CameraDescription cameraDescription in cameras) {
|
||||
bool previousPresetExactlySupported = true;
|
||||
for (final MapEntry<ResolutionPreset, Size> preset
|
||||
in presetExpectedSizes.entries) {
|
||||
final CameraController controller =
|
||||
CameraController(cameraDescription, preset.key);
|
||||
await controller.initialize();
|
||||
await controller.prepareForVideoRecording();
|
||||
final bool presetExactlySupported =
|
||||
await testCaptureVideoResolution(controller, preset.key);
|
||||
assert(!(!previousPresetExactlySupported && presetExactlySupported),
|
||||
'The camera took higher resolution pictures at a lower resolution.');
|
||||
previousPresetExactlySupported = presetExactlySupported;
|
||||
await controller.dispose();
|
||||
}
|
||||
}
|
||||
},
|
||||
// TODO(egarciad): Fix https://github.com/flutter/flutter/issues/93686.
|
||||
skip: true,
|
||||
);
|
||||
|
||||
testWidgets('Pause and resume video recording', (WidgetTester tester) async {
|
||||
final List<CameraDescription> cameras = await availableCameras();
|
||||
if (cameras.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final CameraController controller = CameraController(
|
||||
cameras[0],
|
||||
ResolutionPreset.low,
|
||||
enableAudio: false,
|
||||
);
|
||||
|
||||
await controller.initialize();
|
||||
await controller.prepareForVideoRecording();
|
||||
|
||||
int startPause;
|
||||
int timePaused = 0;
|
||||
|
||||
await controller.startVideoRecording();
|
||||
final int recordingStart = DateTime.now().millisecondsSinceEpoch;
|
||||
sleep(const Duration(milliseconds: 500));
|
||||
|
||||
await controller.pauseVideoRecording();
|
||||
startPause = DateTime.now().millisecondsSinceEpoch;
|
||||
sleep(const Duration(milliseconds: 500));
|
||||
await controller.resumeVideoRecording();
|
||||
timePaused += DateTime.now().millisecondsSinceEpoch - startPause;
|
||||
|
||||
sleep(const Duration(milliseconds: 500));
|
||||
|
||||
await controller.pauseVideoRecording();
|
||||
startPause = DateTime.now().millisecondsSinceEpoch;
|
||||
sleep(const Duration(milliseconds: 500));
|
||||
await controller.resumeVideoRecording();
|
||||
timePaused += DateTime.now().millisecondsSinceEpoch - startPause;
|
||||
|
||||
sleep(const Duration(milliseconds: 500));
|
||||
|
||||
final XFile file = await controller.stopVideoRecording();
|
||||
final int recordingTime =
|
||||
DateTime.now().millisecondsSinceEpoch - recordingStart;
|
||||
|
||||
final File videoFile = File(file.path);
|
||||
final VideoPlayerController videoController = VideoPlayerController.file(
|
||||
videoFile,
|
||||
);
|
||||
await videoController.initialize();
|
||||
final int duration = videoController.value.duration.inMilliseconds;
|
||||
await videoController.dispose();
|
||||
|
||||
expect(duration, lessThan(recordingTime - timePaused));
|
||||
}, skip: !Platform.isAndroid);
|
||||
|
||||
testWidgets(
|
||||
'Android image streaming',
|
||||
(WidgetTester tester) async {
|
||||
final List<CameraDescription> cameras = await availableCameras();
|
||||
if (cameras.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final CameraController controller = CameraController(
|
||||
cameras[0],
|
||||
ResolutionPreset.low,
|
||||
enableAudio: false,
|
||||
);
|
||||
|
||||
await controller.initialize();
|
||||
bool isDetecting = false;
|
||||
|
||||
await controller.startImageStream((CameraImage image) {
|
||||
if (isDetecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
isDetecting = true;
|
||||
|
||||
expectLater(image, isNotNull).whenComplete(() => isDetecting = false);
|
||||
});
|
||||
|
||||
expect(controller.value.isStreamingImages, true);
|
||||
|
||||
sleep(const Duration(milliseconds: 500));
|
||||
|
||||
await controller.stopImageStream();
|
||||
await controller.dispose();
|
||||
},
|
||||
skip: !Platform.isAndroid,
|
||||
);
|
||||
|
||||
/// Start streaming with specifying the ImageFormatGroup.
|
||||
Future<CameraImage> startStreaming(List<CameraDescription> cameras,
|
||||
ImageFormatGroup? imageFormatGroup) async {
|
||||
final CameraController controller = CameraController(
|
||||
cameras.first,
|
||||
ResolutionPreset.low,
|
||||
enableAudio: false,
|
||||
imageFormatGroup: imageFormatGroup,
|
||||
);
|
||||
|
||||
await controller.initialize();
|
||||
final Completer<CameraImage> completer = Completer<CameraImage>();
|
||||
|
||||
await controller.startImageStream((CameraImage image) {
|
||||
if (!completer.isCompleted) {
|
||||
Future<void>(() async {
|
||||
await controller.stopImageStream();
|
||||
await controller.dispose();
|
||||
}).then((Object? value) {
|
||||
completer.complete(image);
|
||||
});
|
||||
}
|
||||
});
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
testWidgets(
|
||||
'iOS image streaming with imageFormatGroup',
|
||||
(WidgetTester tester) async {
|
||||
final List<CameraDescription> cameras = await availableCameras();
|
||||
if (cameras.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
CameraImage image = await startStreaming(cameras, null);
|
||||
expect(image, isNotNull);
|
||||
expect(image.format.group, ImageFormatGroup.bgra8888);
|
||||
expect(image.planes.length, 1);
|
||||
|
||||
image = await startStreaming(cameras, ImageFormatGroup.yuv420);
|
||||
expect(image, isNotNull);
|
||||
expect(image.format.group, ImageFormatGroup.yuv420);
|
||||
expect(image.planes.length, 2);
|
||||
|
||||
image = await startStreaming(cameras, ImageFormatGroup.bgra8888);
|
||||
expect(image, isNotNull);
|
||||
expect(image.format.group, ImageFormatGroup.bgra8888);
|
||||
expect(image.planes.length, 1);
|
||||
},
|
||||
skip: !Platform.isIOS,
|
||||
);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>9.0</string>
|
||||
</dict>
|
||||
</plist>
|
@ -0,0 +1,3 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
@ -0,0 +1,3 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
38
packages/camera/camera/example/ios/Podfile
Normal file
@ -0,0 +1,38 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '9.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_ios_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
end
|
||||
end
|
@ -0,0 +1,472 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
236906D1621AE863A5B2E770 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 89D82918721FABF772705DB0 /* libPods-Runner.a */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
|
||||
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
14AE82C910C2A12F2ECB2094 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
1944D8072499F3B5E7653D44 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
59848A7CA98C1FADF8840207 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
||||
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
|
||||
89D82918721FABF772705DB0 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
9C5CC6CAD53AD388B2694F3A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
A24F9E418BA48BCC7409B117 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
236906D1621AE863A5B2E770 /* libPods-Runner.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
3242FD2B467C15C62200632F /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
89D82918721FABF772705DB0 /* libPods-Runner.a */,
|
||||
1944D8072499F3B5E7653D44 /* libPods-RunnerTests.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||
);
|
||||
name = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146E51CF9000F007C117D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
FD386F00E98D73419C929072 /* Pods */,
|
||||
3242FD2B467C15C62200632F /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146EF1CF9000F007C117D /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
|
||||
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
97C147021CF9000F007C117D /* Info.plist */,
|
||||
97C146F11CF9000F007C117D /* Supporting Files */,
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146F11CF9000F007C117D /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146F21CF9000F007C117D /* main.m */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD386F00E98D73419C929072 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
59848A7CA98C1FADF8840207 /* Pods-Runner.debug.xcconfig */,
|
||||
14AE82C910C2A12F2ECB2094 /* Pods-Runner.release.xcconfig */,
|
||||
9C5CC6CAD53AD388B2694F3A /* Pods-RunnerTests.debug.xcconfig */,
|
||||
A24F9E418BA48BCC7409B117 /* Pods-RunnerTests.release.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
9872F2A25E8A171A111468CD /* [CP] Check Pods Manifest.lock */,
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1300;
|
||||
ORGANIZATIONNAME = "The Flutter Authors";
|
||||
TargetAttributes = {
|
||||
97C146ED1CF9000F007C117D = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
9872F2A25E8A171A111468CD /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
|
||||
97C146F31CF9000F007C117D /* main.m in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C146FB1CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C147001CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
97C147031CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147041CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
97C147061CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.cameraExample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.cameraExample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147031CF9000F007C117D /* Debug */,
|
||||
97C147041CF9000F007C117D /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147061CF9000F007C117D /* Debug */,
|
||||
97C147071CF9000F007C117D /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
7
packages/camera/camera/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
@ -0,0 +1,104 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "03BB76672665316900CE5A93"
|
||||
BuildableName = "RunnerTests.xctest"
|
||||
BlueprintName = "RunnerTests"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
<AdditionalOption
|
||||
key = "NSZombieEnabled"
|
||||
value = "YES"
|
||||
isEnabled = "YES">
|
||||
</AdditionalOption>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
10
packages/camera/camera/example/ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
10
packages/camera/camera/example/ios/Runner/AppDelegate.h
Normal file
@ -0,0 +1,10 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import <Flutter/Flutter.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface AppDelegate : FlutterAppDelegate
|
||||
|
||||
@end
|
17
packages/camera/camera/example/ios/Runner/AppDelegate.m
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "AppDelegate.h"
|
||||
#include "GeneratedPluginRegistrant.h"
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application
|
||||
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
[GeneratedPluginRegistrant registerWithRegistry:self];
|
||||
// Override point for customization after application launch.
|
||||
return [super application:application didFinishLaunchingWithOptions:launchOptions];
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,121 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"size" : "1024x1024",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 564 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.5 KiB |
23
packages/camera/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
packages/camera/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
After Width: | Height: | Size: 68 B |
BIN
packages/camera/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
After Width: | Height: | Size: 68 B |
BIN
packages/camera/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
After Width: | Height: | Size: 68 B |
5
packages/camera/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
56
packages/camera/camera/example/ios/Runner/Info.plist
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>camera_example</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string></string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Can I use the camera please? Only for demo purpose of the app</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Only for demo purpose of the app</string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
19
packages/camera/camera/example/ios/Runner/main.m
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import <Flutter/Flutter.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "AppDelegate.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
@autoreleasepool {
|
||||
// The setup logic in `AppDelegate::didFinishLaunchingWithOptions:` eventually sends camera
|
||||
// operations on the background queue, which would run concurrently with the test cases during
|
||||
// unit tests, making the debugging process confusing. This setup is actually not necessary for
|
||||
// the unit tests, so it is better to skip the AppDelegate when running unit tests.
|
||||
BOOL isTesting = NSClassFromString(@"XCTestCase") != nil;
|
||||
return UIApplicationMain(argc, argv, nil,
|
||||
isTesting ? nil : NSStringFromClass([AppDelegate class]));
|
||||
}
|
||||
}
|
1080
packages/camera/camera/example/lib/main.dart
Normal file
69
packages/camera/camera/example/lib/readme_full_example.dart
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// #docregion FullAppExample
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
late List<CameraDescription> _cameras;
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
_cameras = await availableCameras();
|
||||
runApp(const CameraApp());
|
||||
}
|
||||
|
||||
/// CameraApp is the Main Application.
|
||||
class CameraApp extends StatefulWidget {
|
||||
/// Default Constructor
|
||||
const CameraApp({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<CameraApp> createState() => _CameraAppState();
|
||||
}
|
||||
|
||||
class _CameraAppState extends State<CameraApp> {
|
||||
late CameraController controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = CameraController(_cameras[0], ResolutionPreset.max);
|
||||
controller.initialize().then((_) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {});
|
||||
}).catchError((Object e) {
|
||||
if (e is CameraException) {
|
||||
switch (e.code) {
|
||||
case 'CameraAccessDenied':
|
||||
// Handle access errors here.
|
||||
break;
|
||||
default:
|
||||
// Handle other errors here.
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!controller.value.isInitialized) {
|
||||
return Container();
|
||||
}
|
||||
return MaterialApp(
|
||||
home: CameraPreview(controller),
|
||||
);
|
||||
}
|
||||
}
|
||||
// #enddocregion FullAppExample
|
32
packages/camera/camera/example/pubspec.yaml
Normal file
@ -0,0 +1,32 @@
|
||||
name: camera_example
|
||||
description: Demonstrates how to use the camera plugin.
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: ">=2.14.0 <3.0.0"
|
||||
flutter: ">=3.0.0"
|
||||
|
||||
dependencies:
|
||||
camera:
|
||||
# When depending on this package from a real application you should use:
|
||||
# camera: ^x.y.z
|
||||
# See https://dart.dev/tools/pub/dependencies#version-constraints
|
||||
# The example app is bundled with the plugin so we use a path dependency on
|
||||
# the parent directory to use the current plugin's version.
|
||||
path: ../
|
||||
flutter:
|
||||
sdk: flutter
|
||||
path_provider: ^2.0.0
|
||||
video_player: ^2.1.4
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.1.10
|
||||
flutter_driver:
|
||||
sdk: flutter
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
16
packages/camera/camera/example/test/main_test.dart
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:camera_example/main.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Test snackbar', (WidgetTester tester) async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await tester.pumpWidget(const CameraApp());
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(SnackBar), findsOneWidget);
|
||||
});
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// ignore_for_file: avoid_print
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_driver/flutter_driver.dart';
|
||||
|
||||
const String _examplePackage = 'io.flutter.plugins.cameraexample';
|
||||
|
||||
Future<void> main() async {
|
||||
if (!(Platform.isLinux || Platform.isMacOS)) {
|
||||
print('This test must be run on a POSIX host. Skipping...');
|
||||
exit(0);
|
||||
}
|
||||
final bool adbExists =
|
||||
Process.runSync('which', <String>['adb']).exitCode == 0;
|
||||
if (!adbExists) {
|
||||
print(r'This test needs ADB to exist on the $PATH. Skipping...');
|
||||
exit(0);
|
||||
}
|
||||
print('Granting camera permissions...');
|
||||
Process.runSync('adb', <String>[
|
||||
'shell',
|
||||
'pm',
|
||||
'grant',
|
||||
_examplePackage,
|
||||
'android.permission.CAMERA'
|
||||
]);
|
||||
Process.runSync('adb', <String>[
|
||||
'shell',
|
||||
'pm',
|
||||
'grant',
|
||||
_examplePackage,
|
||||
'android.permission.RECORD_AUDIO'
|
||||
]);
|
||||
print('Starting test.');
|
||||
final FlutterDriver driver = await FlutterDriver.connect();
|
||||
final String data = await driver.requestData(
|
||||
null,
|
||||
timeout: const Duration(minutes: 1),
|
||||
);
|
||||
await driver.close();
|
||||
print('Test finished. Revoking camera permissions...');
|
||||
Process.runSync('adb', <String>[
|
||||
'shell',
|
||||
'pm',
|
||||
'revoke',
|
||||
_examplePackage,
|
||||
'android.permission.CAMERA'
|
||||
]);
|
||||
Process.runSync('adb', <String>[
|
||||
'shell',
|
||||
'pm',
|
||||
'revoke',
|
||||
_examplePackage,
|
||||
'android.permission.RECORD_AUDIO'
|
||||
]);
|
||||
|
||||
final Map<String, dynamic> result = jsonDecode(data) as Map<String, dynamic>;
|
||||
exit(result['result'] == 'true' ? 0 : 1);
|
||||
}
|
BIN
packages/camera/camera/example/web/favicon.png
Normal file
After Width: | Height: | Size: 917 B |
BIN
packages/camera/camera/example/web/icons/Icon-192.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
packages/camera/camera/example/web/icons/Icon-512.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
39
packages/camera/camera/example/web/index.html
Normal file
@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file. -->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||
<meta name="description" content="An example of the camera on the web.">
|
||||
|
||||
<!-- iOS meta tags & icons -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="example">
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="shortcut icon" type="image/png" href="favicon.png" />
|
||||
|
||||
<title>Camera Web Example</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- This script installs service_worker.js to provide PWA functionality to
|
||||
application. For more information, see:
|
||||
https://developers.google.com/web/fundamentals/primers/service-workers -->
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function () {
|
||||
navigator.serviceWorker.register('flutter_service_worker.js');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script src="main.dart.js" type="application/javascript"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
23
packages/camera/camera/example/web/manifest.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "camera example",
|
||||
"short_name": "camera",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#0175C2",
|
||||
"theme_color": "#0175C2",
|
||||
"description": "An example of the camera on the web.",
|
||||
"orientation": "portrait-primary",
|
||||
"prefer_related_applications": false,
|
||||
"icons": [
|
||||
{
|
||||
"src": "icons/Icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icons/Icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
19
packages/camera/camera/lib/camera.dart
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
export 'package:camera_platform_interface/camera_platform_interface.dart'
|
||||
show
|
||||
CameraDescription,
|
||||
CameraException,
|
||||
CameraLensDirection,
|
||||
FlashMode,
|
||||
ExposureMode,
|
||||
FocusMode,
|
||||
ResolutionPreset,
|
||||
XFile,
|
||||
ImageFormatGroup;
|
||||
|
||||
export 'src/camera_controller.dart';
|
||||
export 'src/camera_image.dart';
|
||||
export 'src/camera_preview.dart';
|
957
packages/camera/camera/lib/src/camera_controller.dart
Normal file
@ -0,0 +1,957 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// 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:collection';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:camera_platform_interface/camera_platform_interface.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../camera.dart';
|
||||
|
||||
/// Signature for a callback receiving the a camera image.
|
||||
///
|
||||
/// This is used by [CameraController.startImageStream].
|
||||
// TODO(stuartmorgan): Fix this naming the next time there's a breaking change
|
||||
// to this package.
|
||||
// ignore: camel_case_types
|
||||
typedef onLatestImageAvailable = Function(CameraImage image);
|
||||
|
||||
/// Completes with a list of available cameras.
|
||||
///
|
||||
/// May throw a [CameraException].
|
||||
Future<List<CameraDescription>> availableCameras() async {
|
||||
return CameraPlatform.instance.availableCameras();
|
||||
}
|
||||
|
||||
// TODO(stuartmorgan): Remove this once the package requires 2.10, where the
|
||||
// dart:async `unawaited` accepts a nullable future.
|
||||
void _unawaited(Future<void>? future) {}
|
||||
|
||||
/// The state of a [CameraController].
|
||||
class CameraValue {
|
||||
/// Creates a new camera controller state.
|
||||
const CameraValue({
|
||||
required this.isInitialized,
|
||||
this.errorDescription,
|
||||
this.previewSize,
|
||||
required this.isRecordingVideo,
|
||||
required this.isTakingPicture,
|
||||
required this.isStreamingImages,
|
||||
required bool isRecordingPaused,
|
||||
required this.flashMode,
|
||||
required this.exposureMode,
|
||||
required this.focusMode,
|
||||
required this.exposurePointSupported,
|
||||
required this.focusPointSupported,
|
||||
required this.deviceOrientation,
|
||||
this.lockedCaptureOrientation,
|
||||
this.recordingOrientation,
|
||||
this.isPreviewPaused = false,
|
||||
this.previewPauseOrientation,
|
||||
}) : _isRecordingPaused = isRecordingPaused;
|
||||
|
||||
/// Creates a new camera controller state for an uninitialized controller.
|
||||
const CameraValue.uninitialized()
|
||||
: this(
|
||||
isInitialized: false,
|
||||
isRecordingVideo: false,
|
||||
isTakingPicture: false,
|
||||
isStreamingImages: false,
|
||||
isRecordingPaused: false,
|
||||
flashMode: FlashMode.auto,
|
||||
exposureMode: ExposureMode.auto,
|
||||
exposurePointSupported: false,
|
||||
focusMode: FocusMode.auto,
|
||||
focusPointSupported: false,
|
||||
deviceOrientation: DeviceOrientation.portraitUp,
|
||||
isPreviewPaused: false,
|
||||
);
|
||||
|
||||
/// True after [CameraController.initialize] has completed successfully.
|
||||
final bool isInitialized;
|
||||
|
||||
/// True when a picture capture request has been sent but as not yet returned.
|
||||
final bool isTakingPicture;
|
||||
|
||||
/// True when the camera is recording (not the same as previewing).
|
||||
final bool isRecordingVideo;
|
||||
|
||||
/// True when images from the camera are being streamed.
|
||||
final bool isStreamingImages;
|
||||
|
||||
final bool _isRecordingPaused;
|
||||
|
||||
/// True when the preview widget has been paused manually.
|
||||
final bool isPreviewPaused;
|
||||
|
||||
/// Set to the orientation the preview was paused in, if it is currently paused.
|
||||
final DeviceOrientation? previewPauseOrientation;
|
||||
|
||||
/// True when camera [isRecordingVideo] and recording is paused.
|
||||
bool get isRecordingPaused => isRecordingVideo && _isRecordingPaused;
|
||||
|
||||
/// Description of an error state.
|
||||
///
|
||||
/// This is null while the controller is not in an error state.
|
||||
/// When [hasError] is true this contains the error description.
|
||||
final String? errorDescription;
|
||||
|
||||
/// The size of the preview in pixels.
|
||||
///
|
||||
/// Is `null` until [isInitialized] is `true`.
|
||||
final Size? previewSize;
|
||||
|
||||
/// Convenience getter for `previewSize.width / previewSize.height`.
|
||||
///
|
||||
/// Can only be called when [initialize] is done.
|
||||
double get aspectRatio => previewSize!.width / previewSize!.height;
|
||||
|
||||
/// Whether the controller is in an error state.
|
||||
///
|
||||
/// When true [errorDescription] describes the error.
|
||||
bool get hasError => errorDescription != null;
|
||||
|
||||
/// The flash mode the camera is currently set to.
|
||||
final FlashMode flashMode;
|
||||
|
||||
/// The exposure mode the camera is currently set to.
|
||||
final ExposureMode exposureMode;
|
||||
|
||||
/// The focus mode the camera is currently set to.
|
||||
final FocusMode focusMode;
|
||||
|
||||
/// Whether setting the exposure point is supported.
|
||||
final bool exposurePointSupported;
|
||||
|
||||
/// Whether setting the focus point is supported.
|
||||
final bool focusPointSupported;
|
||||
|
||||
/// The current device UI orientation.
|
||||
final DeviceOrientation deviceOrientation;
|
||||
|
||||
/// The currently locked capture orientation.
|
||||
final DeviceOrientation? lockedCaptureOrientation;
|
||||
|
||||
/// Whether the capture orientation is currently locked.
|
||||
bool get isCaptureOrientationLocked => lockedCaptureOrientation != null;
|
||||
|
||||
/// The orientation of the currently running video recording.
|
||||
final DeviceOrientation? recordingOrientation;
|
||||
|
||||
/// Creates a modified copy of the object.
|
||||
///
|
||||
/// Explicitly specified fields get the specified value, all other fields get
|
||||
/// the same value of the current object.
|
||||
CameraValue copyWith({
|
||||
bool? isInitialized,
|
||||
bool? isRecordingVideo,
|
||||
bool? isTakingPicture,
|
||||
bool? isStreamingImages,
|
||||
String? errorDescription,
|
||||
Size? previewSize,
|
||||
bool? isRecordingPaused,
|
||||
FlashMode? flashMode,
|
||||
ExposureMode? exposureMode,
|
||||
FocusMode? focusMode,
|
||||
bool? exposurePointSupported,
|
||||
bool? focusPointSupported,
|
||||
DeviceOrientation? deviceOrientation,
|
||||
Optional<DeviceOrientation>? lockedCaptureOrientation,
|
||||
Optional<DeviceOrientation>? recordingOrientation,
|
||||
bool? isPreviewPaused,
|
||||
Optional<DeviceOrientation>? previewPauseOrientation,
|
||||
}) {
|
||||
return CameraValue(
|
||||
isInitialized: isInitialized ?? this.isInitialized,
|
||||
errorDescription: errorDescription,
|
||||
previewSize: previewSize ?? this.previewSize,
|
||||
isRecordingVideo: isRecordingVideo ?? this.isRecordingVideo,
|
||||
isTakingPicture: isTakingPicture ?? this.isTakingPicture,
|
||||
isStreamingImages: isStreamingImages ?? this.isStreamingImages,
|
||||
isRecordingPaused: isRecordingPaused ?? _isRecordingPaused,
|
||||
flashMode: flashMode ?? this.flashMode,
|
||||
exposureMode: exposureMode ?? this.exposureMode,
|
||||
focusMode: focusMode ?? this.focusMode,
|
||||
exposurePointSupported:
|
||||
exposurePointSupported ?? this.exposurePointSupported,
|
||||
focusPointSupported: focusPointSupported ?? this.focusPointSupported,
|
||||
deviceOrientation: deviceOrientation ?? this.deviceOrientation,
|
||||
lockedCaptureOrientation: lockedCaptureOrientation == null
|
||||
? this.lockedCaptureOrientation
|
||||
: lockedCaptureOrientation.orNull,
|
||||
recordingOrientation: recordingOrientation == null
|
||||
? this.recordingOrientation
|
||||
: recordingOrientation.orNull,
|
||||
isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused,
|
||||
previewPauseOrientation: previewPauseOrientation == null
|
||||
? this.previewPauseOrientation
|
||||
: previewPauseOrientation.orNull,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '${objectRuntimeType(this, 'CameraValue')}('
|
||||
'isRecordingVideo: $isRecordingVideo, '
|
||||
'isInitialized: $isInitialized, '
|
||||
'errorDescription: $errorDescription, '
|
||||
'previewSize: $previewSize, '
|
||||
'isStreamingImages: $isStreamingImages, '
|
||||
'flashMode: $flashMode, '
|
||||
'exposureMode: $exposureMode, '
|
||||
'focusMode: $focusMode, '
|
||||
'exposurePointSupported: $exposurePointSupported, '
|
||||
'focusPointSupported: $focusPointSupported, '
|
||||
'deviceOrientation: $deviceOrientation, '
|
||||
'lockedCaptureOrientation: $lockedCaptureOrientation, '
|
||||
'recordingOrientation: $recordingOrientation, '
|
||||
'isPreviewPaused: $isPreviewPaused, '
|
||||
'previewPausedOrientation: $previewPauseOrientation)';
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls a device camera.
|
||||
///
|
||||
/// Use [availableCameras] to get a list of available cameras.
|
||||
///
|
||||
/// Before using a [CameraController] a call to [initialize] must complete.
|
||||
///
|
||||
/// To show the camera preview on the screen use a [CameraPreview] widget.
|
||||
class CameraController extends ValueNotifier<CameraValue> {
|
||||
/// Creates a new camera controller in an uninitialized state.
|
||||
CameraController(
|
||||
this.description,
|
||||
this.resolutionPreset, {
|
||||
this.enableAudio = true,
|
||||
this.imageFormatGroup,
|
||||
}) : super(const CameraValue.uninitialized());
|
||||
|
||||
/// The properties of the camera device controlled by this controller.
|
||||
final CameraDescription description;
|
||||
|
||||
/// The resolution this controller is targeting.
|
||||
///
|
||||
/// This resolution preset is not guaranteed to be available on the device,
|
||||
/// if unavailable a lower resolution will be used.
|
||||
///
|
||||
/// See also: [ResolutionPreset].
|
||||
final ResolutionPreset resolutionPreset;
|
||||
|
||||
/// Whether to include audio when recording a video.
|
||||
final bool enableAudio;
|
||||
|
||||
/// The [ImageFormatGroup] describes the output of the raw image format.
|
||||
///
|
||||
/// When null the imageFormat will fallback to the platforms default.
|
||||
final ImageFormatGroup? imageFormatGroup;
|
||||
|
||||
/// The id of a camera that hasn't been initialized.
|
||||
@visibleForTesting
|
||||
static const int kUninitializedCameraId = -1;
|
||||
int _cameraId = kUninitializedCameraId;
|
||||
|
||||
bool _isDisposed = false;
|
||||
StreamSubscription<CameraImageData>? _imageStreamSubscription;
|
||||
FutureOr<bool>? _initCalled;
|
||||
StreamSubscription<DeviceOrientationChangedEvent>?
|
||||
_deviceOrientationSubscription;
|
||||
|
||||
/// Checks whether [CameraController.dispose] has completed successfully.
|
||||
///
|
||||
/// This is a no-op when asserts are disabled.
|
||||
void debugCheckIsDisposed() {
|
||||
assert(_isDisposed);
|
||||
}
|
||||
|
||||
/// The camera identifier with which the controller is associated.
|
||||
int get cameraId => _cameraId;
|
||||
|
||||
/// Initializes the camera on the device.
|
||||
///
|
||||
/// Throws a [CameraException] if the initialization fails.
|
||||
Future<void> initialize() async {
|
||||
if (_isDisposed) {
|
||||
throw CameraException(
|
||||
'Disposed CameraController',
|
||||
'initialize was called on a disposed CameraController',
|
||||
);
|
||||
}
|
||||
try {
|
||||
final Completer<CameraInitializedEvent> initializeCompleter =
|
||||
Completer<CameraInitializedEvent>();
|
||||
|
||||
_deviceOrientationSubscription = CameraPlatform.instance
|
||||
.onDeviceOrientationChanged()
|
||||
.listen((DeviceOrientationChangedEvent event) {
|
||||
value = value.copyWith(
|
||||
deviceOrientation: event.orientation,
|
||||
);
|
||||
});
|
||||
|
||||
_cameraId = await CameraPlatform.instance.createCamera(
|
||||
description,
|
||||
resolutionPreset,
|
||||
enableAudio: enableAudio,
|
||||
);
|
||||
|
||||
_unawaited(CameraPlatform.instance
|
||||
.onCameraInitialized(_cameraId)
|
||||
.first
|
||||
.then((CameraInitializedEvent event) {
|
||||
initializeCompleter.complete(event);
|
||||
}));
|
||||
|
||||
await CameraPlatform.instance.initializeCamera(
|
||||
_cameraId,
|
||||
imageFormatGroup: imageFormatGroup ?? ImageFormatGroup.unknown,
|
||||
);
|
||||
|
||||
value = value.copyWith(
|
||||
isInitialized: true,
|
||||
previewSize: await initializeCompleter.future
|
||||
.then((CameraInitializedEvent event) => Size(
|
||||
event.previewWidth,
|
||||
event.previewHeight,
|
||||
)),
|
||||
exposureMode: await initializeCompleter.future
|
||||
.then((CameraInitializedEvent event) => event.exposureMode),
|
||||
focusMode: await initializeCompleter.future
|
||||
.then((CameraInitializedEvent event) => event.focusMode),
|
||||
exposurePointSupported: await initializeCompleter.future.then(
|
||||
(CameraInitializedEvent event) => event.exposurePointSupported),
|
||||
focusPointSupported: await initializeCompleter.future
|
||||
.then((CameraInitializedEvent event) => event.focusPointSupported),
|
||||
);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
|
||||
_initCalled = true;
|
||||
}
|
||||
|
||||
/// Prepare the capture session for video recording.
|
||||
///
|
||||
/// Use of this method is optional, but it may be called for performance
|
||||
/// reasons on iOS.
|
||||
///
|
||||
/// Preparing audio can cause a minor delay in the CameraPreview view on iOS.
|
||||
/// If video recording is intended, calling this early eliminates this delay
|
||||
/// that would otherwise be experienced when video recording is started.
|
||||
/// This operation is a no-op on Android and Web.
|
||||
///
|
||||
/// Throws a [CameraException] if the prepare fails.
|
||||
Future<void> prepareForVideoRecording() async {
|
||||
await CameraPlatform.instance.prepareForVideoRecording();
|
||||
}
|
||||
|
||||
/// Pauses the current camera preview
|
||||
Future<void> pausePreview() async {
|
||||
if (value.isPreviewPaused) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await CameraPlatform.instance.pausePreview(_cameraId);
|
||||
value = value.copyWith(
|
||||
isPreviewPaused: true,
|
||||
previewPauseOrientation: Optional<DeviceOrientation>.of(
|
||||
value.lockedCaptureOrientation ?? value.deviceOrientation));
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Resumes the current camera preview
|
||||
Future<void> resumePreview() async {
|
||||
if (!value.isPreviewPaused) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await CameraPlatform.instance.resumePreview(_cameraId);
|
||||
value = value.copyWith(
|
||||
isPreviewPaused: false,
|
||||
previewPauseOrientation: const Optional<DeviceOrientation>.absent());
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Captures an image and returns the file where it was saved.
|
||||
///
|
||||
/// Throws a [CameraException] if the capture fails.
|
||||
Future<XFile> takePicture() async {
|
||||
_throwIfNotInitialized('takePicture');
|
||||
if (value.isTakingPicture) {
|
||||
throw CameraException(
|
||||
'Previous capture has not returned yet.',
|
||||
'takePicture was called before the previous capture returned.',
|
||||
);
|
||||
}
|
||||
try {
|
||||
value = value.copyWith(isTakingPicture: true);
|
||||
final XFile file = await CameraPlatform.instance.takePicture(_cameraId);
|
||||
value = value.copyWith(isTakingPicture: false);
|
||||
return file;
|
||||
} on PlatformException catch (e) {
|
||||
value = value.copyWith(isTakingPicture: false);
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Start streaming images from platform camera.
|
||||
///
|
||||
/// Settings for capturing images on iOS and Android is set to always use the
|
||||
/// latest image available from the camera and will drop all other images.
|
||||
///
|
||||
/// When running continuously with [CameraPreview] widget, this function runs
|
||||
/// best with [ResolutionPreset.low]. Running on [ResolutionPreset.high] can
|
||||
/// have significant frame rate drops for [CameraPreview] on lower end
|
||||
/// devices.
|
||||
///
|
||||
/// Throws a [CameraException] if image streaming or video recording has
|
||||
/// already started.
|
||||
///
|
||||
/// The `startImageStream` method is only available on Android and iOS (other
|
||||
/// platforms won't be supported in current setup).
|
||||
///
|
||||
// TODO(bmparr): Add settings for resolution and fps.
|
||||
Future<void> startImageStream(onLatestImageAvailable onAvailable) async {
|
||||
assert(defaultTargetPlatform == TargetPlatform.android ||
|
||||
defaultTargetPlatform == TargetPlatform.iOS);
|
||||
_throwIfNotInitialized('startImageStream');
|
||||
if (value.isRecordingVideo) {
|
||||
throw CameraException(
|
||||
'A video recording is already started.',
|
||||
'startImageStream was called while a video is being recorded.',
|
||||
);
|
||||
}
|
||||
if (value.isStreamingImages) {
|
||||
throw CameraException(
|
||||
'A camera has started streaming images.',
|
||||
'startImageStream was called while a camera was streaming images.',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
_imageStreamSubscription = CameraPlatform.instance
|
||||
.onStreamedFrameAvailable(_cameraId)
|
||||
.listen((CameraImageData imageData) {
|
||||
onAvailable(CameraImage.fromPlatformInterface(imageData));
|
||||
});
|
||||
value = value.copyWith(isStreamingImages: true);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop streaming images from platform camera.
|
||||
///
|
||||
/// Throws a [CameraException] if image streaming was not started or video
|
||||
/// recording was started.
|
||||
///
|
||||
/// The `stopImageStream` method is only available on Android and iOS (other
|
||||
/// platforms won't be supported in current setup).
|
||||
Future<void> stopImageStream() async {
|
||||
assert(defaultTargetPlatform == TargetPlatform.android ||
|
||||
defaultTargetPlatform == TargetPlatform.iOS);
|
||||
_throwIfNotInitialized('stopImageStream');
|
||||
if (!value.isStreamingImages) {
|
||||
throw CameraException(
|
||||
'No camera is streaming images',
|
||||
'stopImageStream was called when no camera is streaming images.',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
value = value.copyWith(isStreamingImages: false);
|
||||
await _imageStreamSubscription?.cancel();
|
||||
_imageStreamSubscription = null;
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Start a video recording.
|
||||
///
|
||||
/// You may optionally pass an [onAvailable] callback to also have the
|
||||
/// video frames streamed to this callback.
|
||||
///
|
||||
/// The video is returned as a [XFile] after calling [stopVideoRecording].
|
||||
/// Throws a [CameraException] if the capture fails.
|
||||
Future<void> startVideoRecording(
|
||||
{onLatestImageAvailable? onAvailable}) async {
|
||||
_throwIfNotInitialized('startVideoRecording');
|
||||
if (value.isRecordingVideo) {
|
||||
throw CameraException(
|
||||
'A video recording is already started.',
|
||||
'startVideoRecording was called when a recording is already started.',
|
||||
);
|
||||
}
|
||||
|
||||
Function(CameraImageData image)? streamCallback;
|
||||
if (onAvailable != null) {
|
||||
streamCallback = (CameraImageData imageData) {
|
||||
onAvailable(CameraImage.fromPlatformInterface(imageData));
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
await CameraPlatform.instance.startVideoCapturing(
|
||||
VideoCaptureOptions(_cameraId, streamCallback: streamCallback));
|
||||
value = value.copyWith(
|
||||
isRecordingVideo: true,
|
||||
isRecordingPaused: false,
|
||||
recordingOrientation: Optional<DeviceOrientation>.of(
|
||||
value.lockedCaptureOrientation ?? value.deviceOrientation),
|
||||
isStreamingImages: onAvailable != null);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops the video recording and returns the file where it was saved.
|
||||
///
|
||||
/// Throws a [CameraException] if the capture failed.
|
||||
Future<XFile> stopVideoRecording() async {
|
||||
_throwIfNotInitialized('stopVideoRecording');
|
||||
if (!value.isRecordingVideo) {
|
||||
throw CameraException(
|
||||
'No video is recording',
|
||||
'stopVideoRecording was called when no video is recording.',
|
||||
);
|
||||
}
|
||||
|
||||
if (value.isStreamingImages) {
|
||||
stopImageStream();
|
||||
}
|
||||
|
||||
try {
|
||||
final XFile file =
|
||||
await CameraPlatform.instance.stopVideoRecording(_cameraId);
|
||||
value = value.copyWith(
|
||||
isRecordingVideo: false,
|
||||
recordingOrientation: const Optional<DeviceOrientation>.absent(),
|
||||
);
|
||||
return file;
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Pause video recording.
|
||||
///
|
||||
/// This feature is only available on iOS and Android sdk 24+.
|
||||
Future<void> pauseVideoRecording() async {
|
||||
_throwIfNotInitialized('pauseVideoRecording');
|
||||
if (!value.isRecordingVideo) {
|
||||
throw CameraException(
|
||||
'No video is recording',
|
||||
'pauseVideoRecording was called when no video is recording.',
|
||||
);
|
||||
}
|
||||
try {
|
||||
await CameraPlatform.instance.pauseVideoRecording(_cameraId);
|
||||
value = value.copyWith(isRecordingPaused: true);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Resume video recording after pausing.
|
||||
///
|
||||
/// This feature is only available on iOS and Android sdk 24+.
|
||||
Future<void> resumeVideoRecording() async {
|
||||
_throwIfNotInitialized('resumeVideoRecording');
|
||||
if (!value.isRecordingVideo) {
|
||||
throw CameraException(
|
||||
'No video is recording',
|
||||
'resumeVideoRecording was called when no video is recording.',
|
||||
);
|
||||
}
|
||||
try {
|
||||
await CameraPlatform.instance.resumeVideoRecording(_cameraId);
|
||||
value = value.copyWith(isRecordingPaused: false);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a widget showing a live camera preview.
|
||||
Widget buildPreview() {
|
||||
_throwIfNotInitialized('buildPreview');
|
||||
try {
|
||||
return CameraPlatform.instance.buildPreview(_cameraId);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the maximum supported zoom level for the selected camera.
|
||||
Future<double> getMaxZoomLevel() {
|
||||
_throwIfNotInitialized('getMaxZoomLevel');
|
||||
try {
|
||||
return CameraPlatform.instance.getMaxZoomLevel(_cameraId);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the minimum supported zoom level for the selected camera.
|
||||
Future<double> getMinZoomLevel() {
|
||||
_throwIfNotInitialized('getMinZoomLevel');
|
||||
try {
|
||||
return CameraPlatform.instance.getMinZoomLevel(_cameraId);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the zoom level for the selected camera.
|
||||
///
|
||||
/// The supplied [zoom] value should be between 1.0 and the maximum supported
|
||||
/// zoom level returned by the `getMaxZoomLevel`. Throws an `CameraException`
|
||||
/// when an illegal zoom level is suplied.
|
||||
Future<void> setZoomLevel(double zoom) {
|
||||
_throwIfNotInitialized('setZoomLevel');
|
||||
try {
|
||||
return CameraPlatform.instance.setZoomLevel(_cameraId, zoom);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the flash mode for taking pictures.
|
||||
Future<void> setFlashMode(FlashMode mode) async {
|
||||
try {
|
||||
await CameraPlatform.instance.setFlashMode(_cameraId, mode);
|
||||
value = value.copyWith(flashMode: mode);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the exposure mode for taking pictures.
|
||||
Future<void> setExposureMode(ExposureMode mode) async {
|
||||
try {
|
||||
await CameraPlatform.instance.setExposureMode(_cameraId, mode);
|
||||
value = value.copyWith(exposureMode: mode);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the exposure point for automatically determining the exposure value.
|
||||
///
|
||||
/// Supplying a `null` value will reset the exposure point to it's default
|
||||
/// value.
|
||||
Future<void> setExposurePoint(Offset? point) async {
|
||||
if (point != null &&
|
||||
(point.dx < 0 || point.dx > 1 || point.dy < 0 || point.dy > 1)) {
|
||||
throw ArgumentError(
|
||||
'The values of point should be anywhere between (0,0) and (1,1).');
|
||||
}
|
||||
|
||||
try {
|
||||
await CameraPlatform.instance.setExposurePoint(
|
||||
_cameraId,
|
||||
point == null
|
||||
? null
|
||||
: Point<double>(
|
||||
point.dx,
|
||||
point.dy,
|
||||
),
|
||||
);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the minimum supported exposure offset for the selected camera in EV units.
|
||||
Future<double> getMinExposureOffset() async {
|
||||
_throwIfNotInitialized('getMinExposureOffset');
|
||||
try {
|
||||
return CameraPlatform.instance.getMinExposureOffset(_cameraId);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the maximum supported exposure offset for the selected camera in EV units.
|
||||
Future<double> getMaxExposureOffset() async {
|
||||
_throwIfNotInitialized('getMaxExposureOffset');
|
||||
try {
|
||||
return CameraPlatform.instance.getMaxExposureOffset(_cameraId);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the supported step size for exposure offset for the selected camera in EV units.
|
||||
///
|
||||
/// Returns 0 when the camera supports using a free value without stepping.
|
||||
Future<double> getExposureOffsetStepSize() async {
|
||||
_throwIfNotInitialized('getExposureOffsetStepSize');
|
||||
try {
|
||||
return CameraPlatform.instance.getExposureOffsetStepSize(_cameraId);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the exposure offset for the selected camera.
|
||||
///
|
||||
/// The supplied [offset] value should be in EV units. 1 EV unit represents a
|
||||
/// doubling in brightness. It should be between the minimum and maximum offsets
|
||||
/// obtained through `getMinExposureOffset` and `getMaxExposureOffset` respectively.
|
||||
/// Throws a `CameraException` when an illegal offset is supplied.
|
||||
///
|
||||
/// When the supplied [offset] value does not align with the step size obtained
|
||||
/// through `getExposureStepSize`, it will automatically be rounded to the nearest step.
|
||||
///
|
||||
/// Returns the (rounded) offset value that was set.
|
||||
Future<double> setExposureOffset(double offset) async {
|
||||
_throwIfNotInitialized('setExposureOffset');
|
||||
// Check if offset is in range
|
||||
final List<double> range = await Future.wait(
|
||||
<Future<double>>[getMinExposureOffset(), getMaxExposureOffset()]);
|
||||
if (offset < range[0] || offset > range[1]) {
|
||||
throw CameraException(
|
||||
'exposureOffsetOutOfBounds',
|
||||
'The provided exposure offset was outside the supported range for this device.',
|
||||
);
|
||||
}
|
||||
|
||||
// Round to the closest step if needed
|
||||
final double stepSize = await getExposureOffsetStepSize();
|
||||
if (stepSize > 0) {
|
||||
final double inv = 1.0 / stepSize;
|
||||
double roundedOffset = (offset * inv).roundToDouble() / inv;
|
||||
if (roundedOffset > range[1]) {
|
||||
roundedOffset = (offset * inv).floorToDouble() / inv;
|
||||
} else if (roundedOffset < range[0]) {
|
||||
roundedOffset = (offset * inv).ceilToDouble() / inv;
|
||||
}
|
||||
offset = roundedOffset;
|
||||
}
|
||||
|
||||
try {
|
||||
return CameraPlatform.instance.setExposureOffset(_cameraId, offset);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Locks the capture orientation.
|
||||
///
|
||||
/// If [orientation] is omitted, the current device orientation is used.
|
||||
Future<void> lockCaptureOrientation([DeviceOrientation? orientation]) async {
|
||||
try {
|
||||
await CameraPlatform.instance.lockCaptureOrientation(
|
||||
_cameraId, orientation ?? value.deviceOrientation);
|
||||
value = value.copyWith(
|
||||
lockedCaptureOrientation: Optional<DeviceOrientation>.of(
|
||||
orientation ?? value.deviceOrientation));
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the focus mode for taking pictures.
|
||||
Future<void> setFocusMode(FocusMode mode) async {
|
||||
try {
|
||||
await CameraPlatform.instance.setFocusMode(_cameraId, mode);
|
||||
value = value.copyWith(focusMode: mode);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Unlocks the capture orientation.
|
||||
Future<void> unlockCaptureOrientation() async {
|
||||
try {
|
||||
await CameraPlatform.instance.unlockCaptureOrientation(_cameraId);
|
||||
value = value.copyWith(
|
||||
lockedCaptureOrientation: const Optional<DeviceOrientation>.absent());
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the focus point for automatically determining the focus value.
|
||||
///
|
||||
/// Supplying a `null` value will reset the focus point to it's default
|
||||
/// value.
|
||||
Future<void> setFocusPoint(Offset? point) async {
|
||||
if (point != null &&
|
||||
(point.dx < 0 || point.dx > 1 || point.dy < 0 || point.dy > 1)) {
|
||||
throw ArgumentError(
|
||||
'The values of point should be anywhere between (0,0) and (1,1).');
|
||||
}
|
||||
try {
|
||||
await CameraPlatform.instance.setFocusPoint(
|
||||
_cameraId,
|
||||
point == null
|
||||
? null
|
||||
: Point<double>(
|
||||
point.dx,
|
||||
point.dy,
|
||||
),
|
||||
);
|
||||
} on PlatformException catch (e) {
|
||||
throw CameraException(e.code, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Releases the resources of this camera.
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
if (_isDisposed) {
|
||||
return;
|
||||
}
|
||||
_unawaited(_deviceOrientationSubscription?.cancel());
|
||||
_isDisposed = true;
|
||||
super.dispose();
|
||||
if (_initCalled != null) {
|
||||
await _initCalled;
|
||||
await CameraPlatform.instance.dispose(_cameraId);
|
||||
}
|
||||
}
|
||||
|
||||
void _throwIfNotInitialized(String functionName) {
|
||||
if (!value.isInitialized) {
|
||||
throw CameraException(
|
||||
'Uninitialized CameraController',
|
||||
'$functionName() was called on an uninitialized CameraController.',
|
||||
);
|
||||
}
|
||||
if (_isDisposed) {
|
||||
throw CameraException(
|
||||
'Disposed CameraController',
|
||||
'$functionName() was called on a disposed CameraController.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void removeListener(VoidCallback listener) {
|
||||
// Prevent ValueListenableBuilder in CameraPreview widget from causing an
|
||||
// exception to be thrown by attempting to remove its own listener after
|
||||
// the controller has already been disposed.
|
||||
if (!_isDisposed) {
|
||||
super.removeListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A value that might be absent.
|
||||
///
|
||||
/// Used to represent [DeviceOrientation]s that are optional but also able
|
||||
/// to be cleared.
|
||||
@immutable
|
||||
class Optional<T> extends IterableBase<T> {
|
||||
/// Constructs an empty Optional.
|
||||
const Optional.absent() : _value = null;
|
||||
|
||||
/// Constructs an Optional of the given [value].
|
||||
///
|
||||
/// Throws [ArgumentError] if [value] is null.
|
||||
Optional.of(T value) : _value = value {
|
||||
// TODO(cbracken): Delete and make this ctor const once mixed-mode
|
||||
// execution is no longer around.
|
||||
ArgumentError.checkNotNull(value);
|
||||
}
|
||||
|
||||
/// Constructs an Optional of the given [value].
|
||||
///
|
||||
/// If [value] is null, returns [absent()].
|
||||
const Optional.fromNullable(T? value) : _value = value;
|
||||
|
||||
final T? _value;
|
||||
|
||||
/// True when this optional contains a value.
|
||||
bool get isPresent => _value != null;
|
||||
|
||||
/// True when this optional contains no value.
|
||||
bool get isNotPresent => _value == null;
|
||||
|
||||
/// Gets the Optional value.
|
||||
///
|
||||
/// Throws [StateError] if [value] is null.
|
||||
T get value {
|
||||
if (_value == null) {
|
||||
throw StateError('value called on absent Optional.');
|
||||
}
|
||||
return _value!;
|
||||
}
|
||||
|
||||
/// Executes a function if the Optional value is present.
|
||||
void ifPresent(void Function(T value) ifPresent) {
|
||||
if (isPresent) {
|
||||
ifPresent(_value as T);
|
||||
}
|
||||
}
|
||||
|
||||
/// Execution a function if the Optional value is absent.
|
||||
void ifAbsent(void Function() ifAbsent) {
|
||||
if (!isPresent) {
|
||||
ifAbsent();
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the Optional value with a default.
|
||||
///
|
||||
/// The default is returned if the Optional is [absent()].
|
||||
///
|
||||
/// Throws [ArgumentError] if [defaultValue] is null.
|
||||
T or(T defaultValue) {
|
||||
return _value ?? defaultValue;
|
||||
}
|
||||
|
||||
/// Gets the Optional value, or `null` if there is none.
|
||||
T? get orNull => _value;
|
||||
|
||||
/// Transforms the Optional value.
|
||||
///
|
||||
/// If the Optional is [absent()], returns [absent()] without applying the transformer.
|
||||
///
|
||||
/// The transformer must not return `null`. If it does, an [ArgumentError] is thrown.
|
||||
Optional<S> transform<S>(S Function(T value) transformer) {
|
||||
return _value == null
|
||||
? Optional<S>.absent()
|
||||
: Optional<S>.of(transformer(_value as T));
|
||||
}
|
||||
|
||||
/// Transforms the Optional value.
|
||||
///
|
||||
/// If the Optional is [absent()], returns [absent()] without applying the transformer.
|
||||
///
|
||||
/// Returns [absent()] if the transformer returns `null`.
|
||||
Optional<S> transformNullable<S>(S? Function(T value) transformer) {
|
||||
return _value == null
|
||||
? Optional<S>.absent()
|
||||
: Optional<S>.fromNullable(transformer(_value as T));
|
||||
}
|
||||
|
||||
@override
|
||||
Iterator<T> get iterator =>
|
||||
isPresent ? <T>[_value as T].iterator : Iterable<T>.empty().iterator;
|
||||
|
||||
/// Delegates to the underlying [value] hashCode.
|
||||
@override
|
||||
int get hashCode => _value.hashCode;
|
||||
|
||||
/// Delegates to the underlying [value] operator==.
|
||||
@override
|
||||
bool operator ==(Object o) => o is Optional<T> && o._value == _value;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _value == null
|
||||
? 'Optional { absent }'
|
||||
: 'Optional { value: $_value }';
|
||||
}
|
||||
}
|
177
packages/camera/camera/lib/src/camera_image.dart
Normal file
@ -0,0 +1,177 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231)
|
||||
// ignore: unnecessary_import
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:camera_platform_interface/camera_platform_interface.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
// TODO(stuartmorgan): Remove all of these classes in a breaking change, and
|
||||
// vend the platform interface versions directly. See
|
||||
// https://github.com/flutter/flutter/issues/104188
|
||||
|
||||
/// A single color plane of image data.
|
||||
///
|
||||
/// The number and meaning of the planes in an image are determined by the
|
||||
/// format of the Image.
|
||||
class Plane {
|
||||
Plane._fromPlatformInterface(CameraImagePlane plane)
|
||||
: bytes = plane.bytes,
|
||||
bytesPerPixel = plane.bytesPerPixel,
|
||||
bytesPerRow = plane.bytesPerRow,
|
||||
height = plane.height,
|
||||
width = plane.width;
|
||||
|
||||
// Only used by the deprecated codepath that's kept to avoid breaking changes.
|
||||
// Never called by the plugin itself.
|
||||
Plane._fromPlatformData(Map<dynamic, dynamic> data)
|
||||
: bytes = data['bytes'] as Uint8List,
|
||||
bytesPerPixel = data['bytesPerPixel'] as int?,
|
||||
bytesPerRow = data['bytesPerRow'] as int,
|
||||
height = data['height'] as int?,
|
||||
width = data['width'] as int?;
|
||||
|
||||
/// Bytes representing this plane.
|
||||
final Uint8List bytes;
|
||||
|
||||
/// The distance between adjacent pixel samples on Android, in bytes.
|
||||
///
|
||||
/// Will be `null` on iOS.
|
||||
final int? bytesPerPixel;
|
||||
|
||||
/// The row stride for this color plane, in bytes.
|
||||
final int bytesPerRow;
|
||||
|
||||
/// Height of the pixel buffer on iOS.
|
||||
///
|
||||
/// Will be `null` on Android
|
||||
final int? height;
|
||||
|
||||
/// Width of the pixel buffer on iOS.
|
||||
///
|
||||
/// Will be `null` on Android.
|
||||
final int? width;
|
||||
}
|
||||
|
||||
/// Describes how pixels are represented in an image.
|
||||
class ImageFormat {
|
||||
ImageFormat._fromPlatformInterface(CameraImageFormat format)
|
||||
: group = format.group,
|
||||
raw = format.raw;
|
||||
|
||||
// Only used by the deprecated codepath that's kept to avoid breaking changes.
|
||||
// Never called by the plugin itself.
|
||||
ImageFormat._fromPlatformData(this.raw) : group = _asImageFormatGroup(raw);
|
||||
|
||||
/// Describes the format group the raw image format falls into.
|
||||
final ImageFormatGroup group;
|
||||
|
||||
/// Raw version of the format from the Android or iOS platform.
|
||||
///
|
||||
/// On Android, this is an `int` from class `android.graphics.ImageFormat`. See
|
||||
/// https://developer.android.com/reference/android/graphics/ImageFormat
|
||||
///
|
||||
/// On iOS, this is a `FourCharCode` constant from Pixel Format Identifiers.
|
||||
/// See https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers?language=objc
|
||||
final dynamic raw;
|
||||
}
|
||||
|
||||
// Only used by the deprecated codepath that's kept to avoid breaking changes.
|
||||
// Never called by the plugin itself.
|
||||
ImageFormatGroup _asImageFormatGroup(dynamic rawFormat) {
|
||||
if (defaultTargetPlatform == TargetPlatform.android) {
|
||||
switch (rawFormat) {
|
||||
// android.graphics.ImageFormat.YUV_420_888
|
||||
case 35:
|
||||
return ImageFormatGroup.yuv420;
|
||||
// android.graphics.ImageFormat.JPEG
|
||||
case 256:
|
||||
return ImageFormatGroup.jpeg;
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultTargetPlatform == TargetPlatform.iOS) {
|
||||
switch (rawFormat) {
|
||||
// kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
|
||||
case 875704438:
|
||||
return ImageFormatGroup.yuv420;
|
||||
// kCVPixelFormatType_32BGRA
|
||||
case 1111970369:
|
||||
return ImageFormatGroup.bgra8888;
|
||||
}
|
||||
}
|
||||
|
||||
return ImageFormatGroup.unknown;
|
||||
}
|
||||
|
||||
/// A single complete image buffer from the platform camera.
|
||||
///
|
||||
/// This class allows for direct application access to the pixel data of an
|
||||
/// Image through one or more [Uint8List]. Each buffer is encapsulated in a
|
||||
/// [Plane] that describes the layout of the pixel data in that plane. The
|
||||
/// [CameraImage] is not directly usable as a UI resource.
|
||||
///
|
||||
/// Although not all image formats are planar on iOS, we treat 1-dimensional
|
||||
/// images as single planar images.
|
||||
class CameraImage {
|
||||
/// Creates a [CameraImage] from the platform interface version.
|
||||
CameraImage.fromPlatformInterface(CameraImageData data)
|
||||
: format = ImageFormat._fromPlatformInterface(data.format),
|
||||
height = data.height,
|
||||
width = data.width,
|
||||
planes = List<Plane>.unmodifiable(data.planes.map<Plane>(
|
||||
(CameraImagePlane plane) => Plane._fromPlatformInterface(plane))),
|
||||
lensAperture = data.lensAperture,
|
||||
sensorExposureTime = data.sensorExposureTime,
|
||||
sensorSensitivity = data.sensorSensitivity;
|
||||
|
||||
/// Creates a [CameraImage] from method channel data.
|
||||
@Deprecated('Use fromPlatformInterface instead')
|
||||
CameraImage.fromPlatformData(Map<dynamic, dynamic> data)
|
||||
: format = ImageFormat._fromPlatformData(data['format']),
|
||||
height = data['height'] as int,
|
||||
width = data['width'] as int,
|
||||
lensAperture = data['lensAperture'] as double?,
|
||||
sensorExposureTime = data['sensorExposureTime'] as int?,
|
||||
sensorSensitivity = data['sensorSensitivity'] as double?,
|
||||
planes = List<Plane>.unmodifiable((data['planes'] as List<dynamic>)
|
||||
.map<Plane>((dynamic planeData) =>
|
||||
Plane._fromPlatformData(planeData as Map<dynamic, dynamic>)));
|
||||
|
||||
/// Format of the image provided.
|
||||
///
|
||||
/// Determines the number of planes needed to represent the image, and
|
||||
/// the general layout of the pixel data in each [Uint8List].
|
||||
final ImageFormat format;
|
||||
|
||||
/// Height of the image in pixels.
|
||||
///
|
||||
/// For formats where some color channels are subsampled, this is the height
|
||||
/// of the largest-resolution plane.
|
||||
final int height;
|
||||
|
||||
/// Width of the image in pixels.
|
||||
///
|
||||
/// For formats where some color channels are subsampled, this is the width
|
||||
/// of the largest-resolution plane.
|
||||
final int width;
|
||||
|
||||
/// The pixels planes for this image.
|
||||
///
|
||||
/// The number of planes is determined by the format of the image.
|
||||
final List<Plane> planes;
|
||||
|
||||
/// The aperture settings for this image.
|
||||
///
|
||||
/// Represented as an f-stop value.
|
||||
final double? lensAperture;
|
||||
|
||||
/// The sensor exposure time for this image in nanoseconds.
|
||||
final int? sensorExposureTime;
|
||||
|
||||
/// The sensor sensitivity in standard ISO arithmetic units.
|
||||
final double? sensorSensitivity;
|
||||
}
|
82
packages/camera/camera/lib/src/camera_preview.dart
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../camera.dart';
|
||||
|
||||
/// A widget showing a live camera preview.
|
||||
class CameraPreview extends StatelessWidget {
|
||||
/// Creates a preview widget for the given camera controller.
|
||||
const CameraPreview(this.controller, {Key? key, this.child})
|
||||
: super(key: key);
|
||||
|
||||
/// The controller for the camera that the preview is shown for.
|
||||
final CameraController controller;
|
||||
|
||||
/// A widget to overlay on top of the camera preview
|
||||
final Widget? child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return controller.value.isInitialized
|
||||
? ValueListenableBuilder<CameraValue>(
|
||||
valueListenable: controller,
|
||||
builder: (BuildContext context, Object? value, Widget? child) {
|
||||
return AspectRatio(
|
||||
aspectRatio: _isLandscape()
|
||||
? controller.value.aspectRatio
|
||||
: (1 / controller.value.aspectRatio),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: <Widget>[
|
||||
_wrapInRotatedBox(child: controller.buildPreview()),
|
||||
child ?? Container(),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
)
|
||||
: Container();
|
||||
}
|
||||
|
||||
Widget _wrapInRotatedBox({required Widget child}) {
|
||||
if (kIsWeb || defaultTargetPlatform != TargetPlatform.android) {
|
||||
return child;
|
||||
}
|
||||
|
||||
return RotatedBox(
|
||||
quarterTurns: _getQuarterTurns(),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
bool _isLandscape() {
|
||||
return <DeviceOrientation>[
|
||||
DeviceOrientation.landscapeLeft,
|
||||
DeviceOrientation.landscapeRight
|
||||
].contains(_getApplicableOrientation());
|
||||
}
|
||||
|
||||
int _getQuarterTurns() {
|
||||
final Map<DeviceOrientation, int> turns = <DeviceOrientation, int>{
|
||||
DeviceOrientation.portraitUp: 0,
|
||||
DeviceOrientation.landscapeRight: 1,
|
||||
DeviceOrientation.portraitDown: 2,
|
||||
DeviceOrientation.landscapeLeft: 3,
|
||||
};
|
||||
return turns[_getApplicableOrientation()]!;
|
||||
}
|
||||
|
||||
DeviceOrientation _getApplicableOrientation() {
|
||||
return controller.value.isRecordingVideo
|
||||
? controller.value.recordingOrientation!
|
||||
: (controller.value.previewPauseOrientation ??
|
||||
controller.value.lockedCaptureOrientation ??
|
||||
controller.value.deviceOrientation);
|
||||
}
|
||||
}
|
40
packages/camera/camera/pubspec.yaml
Normal file
@ -0,0 +1,40 @@
|
||||
name: camera
|
||||
description: A Flutter plugin for controlling the camera. Supports previewing
|
||||
the camera feed, capturing images and video, and streaming image buffers to
|
||||
Dart.
|
||||
repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera
|
||||
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
|
||||
version: 0.10.3
|
||||
|
||||
environment:
|
||||
sdk: ">=2.14.0 <3.0.0"
|
||||
flutter: ">=3.0.0"
|
||||
|
||||
flutter:
|
||||
plugin:
|
||||
platforms:
|
||||
android:
|
||||
default_package: camera_android
|
||||
ios:
|
||||
default_package: camera_avfoundation
|
||||
web:
|
||||
default_package: camera_web
|
||||
|
||||
dependencies:
|
||||
camera_android: ^0.10.1
|
||||
camera_avfoundation: ^0.9.9
|
||||
camera_platform_interface: ^2.3.2
|
||||
camera_web: ^0.3.1
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_plugin_android_lifecycle: ^2.0.2
|
||||
quiver: ^3.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_driver:
|
||||
sdk: flutter
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
mockito: ^5.0.0
|
||||
plugin_platform_interface: ^2.0.0
|
||||
video_player: ^2.0.0
|
243
packages/camera/camera/test/camera_image_stream_test.dart
Normal file
@ -0,0 +1,243 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:camera_platform_interface/camera_platform_interface.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'camera_test.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
late MockStreamingCameraPlatform mockPlatform;
|
||||
|
||||
setUp(() {
|
||||
mockPlatform = MockStreamingCameraPlatform();
|
||||
CameraPlatform.instance = mockPlatform;
|
||||
});
|
||||
|
||||
test('startImageStream() throws $CameraException when uninitialized', () {
|
||||
final CameraController cameraController = CameraController(
|
||||
const CameraDescription(
|
||||
name: 'cam',
|
||||
lensDirection: CameraLensDirection.back,
|
||||
sensorOrientation: 90),
|
||||
ResolutionPreset.max);
|
||||
|
||||
expect(
|
||||
() => cameraController.startImageStream((CameraImage image) => null),
|
||||
throwsA(
|
||||
isA<CameraException>()
|
||||
.having(
|
||||
(CameraException error) => error.code,
|
||||
'code',
|
||||
'Uninitialized CameraController',
|
||||
)
|
||||
.having(
|
||||
(CameraException error) => error.description,
|
||||
'description',
|
||||
'startImageStream() was called on an uninitialized CameraController.',
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('startImageStream() throws $CameraException when recording videos',
|
||||
() async {
|
||||
final CameraController cameraController = CameraController(
|
||||
const CameraDescription(
|
||||
name: 'cam',
|
||||
lensDirection: CameraLensDirection.back,
|
||||
sensorOrientation: 90),
|
||||
ResolutionPreset.max);
|
||||
|
||||
await cameraController.initialize();
|
||||
|
||||
cameraController.value =
|
||||
cameraController.value.copyWith(isRecordingVideo: true);
|
||||
|
||||
expect(
|
||||
() => cameraController.startImageStream((CameraImage image) => null),
|
||||
throwsA(isA<CameraException>().having(
|
||||
(CameraException error) => error.description,
|
||||
'A video recording is already started.',
|
||||
'startImageStream was called while a video is being recorded.',
|
||||
)));
|
||||
});
|
||||
test(
|
||||
'startImageStream() throws $CameraException when already streaming images',
|
||||
() async {
|
||||
final CameraController cameraController = CameraController(
|
||||
const CameraDescription(
|
||||
name: 'cam',
|
||||
lensDirection: CameraLensDirection.back,
|
||||
sensorOrientation: 90),
|
||||
ResolutionPreset.max);
|
||||
await cameraController.initialize();
|
||||
|
||||
cameraController.value =
|
||||
cameraController.value.copyWith(isStreamingImages: true);
|
||||
expect(
|
||||
() => cameraController.startImageStream((CameraImage image) => null),
|
||||
throwsA(isA<CameraException>().having(
|
||||
(CameraException error) => error.description,
|
||||
'A camera has started streaming images.',
|
||||
'startImageStream was called while a camera was streaming images.',
|
||||
)));
|
||||
});
|
||||
|
||||
test('startImageStream() calls CameraPlatform', () async {
|
||||
final CameraController cameraController = CameraController(
|
||||
const CameraDescription(
|
||||
name: 'cam',
|
||||
lensDirection: CameraLensDirection.back,
|
||||
sensorOrientation: 90),
|
||||
ResolutionPreset.max);
|
||||
await cameraController.initialize();
|
||||
|
||||
await cameraController.startImageStream((CameraImage image) => null);
|
||||
|
||||
expect(mockPlatform.streamCallLog,
|
||||
<String>['onStreamedFrameAvailable', 'listen']);
|
||||
});
|
||||
|
||||
test('stopImageStream() throws $CameraException when uninitialized', () {
|
||||
final CameraController cameraController = CameraController(
|
||||
const CameraDescription(
|
||||
name: 'cam',
|
||||
lensDirection: CameraLensDirection.back,
|
||||
sensorOrientation: 90),
|
||||
ResolutionPreset.max);
|
||||
|
||||
expect(
|
||||
cameraController.stopImageStream,
|
||||
throwsA(
|
||||
isA<CameraException>()
|
||||
.having(
|
||||
(CameraException error) => error.code,
|
||||
'code',
|
||||
'Uninitialized CameraController',
|
||||
)
|
||||
.having(
|
||||
(CameraException error) => error.description,
|
||||
'description',
|
||||
'stopImageStream() was called on an uninitialized CameraController.',
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('stopImageStream() throws $CameraException when not streaming images',
|
||||
() async {
|
||||
final CameraController cameraController = CameraController(
|
||||
const CameraDescription(
|
||||
name: 'cam',
|
||||
lensDirection: CameraLensDirection.back,
|
||||
sensorOrientation: 90),
|
||||
ResolutionPreset.max);
|
||||
await cameraController.initialize();
|
||||
|
||||
expect(
|
||||
cameraController.stopImageStream,
|
||||
throwsA(isA<CameraException>().having(
|
||||
(CameraException error) => error.description,
|
||||
'No camera is streaming images',
|
||||
'stopImageStream was called when no camera is streaming images.',
|
||||
)));
|
||||
});
|
||||
|
||||
test('stopImageStream() intended behaviour', () async {
|
||||
final CameraController cameraController = CameraController(
|
||||
const CameraDescription(
|
||||
name: 'cam',
|
||||
lensDirection: CameraLensDirection.back,
|
||||
sensorOrientation: 90),
|
||||
ResolutionPreset.max);
|
||||
await cameraController.initialize();
|
||||
await cameraController.startImageStream((CameraImage image) => null);
|
||||
await cameraController.stopImageStream();
|
||||
|
||||
expect(mockPlatform.streamCallLog,
|
||||
<String>['onStreamedFrameAvailable', 'listen', 'cancel']);
|
||||
});
|
||||
|
||||
test('startVideoRecording() can stream images', () async {
|
||||
final CameraController cameraController = CameraController(
|
||||
const CameraDescription(
|
||||
name: 'cam',
|
||||
lensDirection: CameraLensDirection.back,
|
||||
sensorOrientation: 90),
|
||||
ResolutionPreset.max);
|
||||
|
||||
await cameraController.initialize();
|
||||
|
||||
cameraController.startVideoRecording(
|
||||
onAvailable: (CameraImage image) => null);
|
||||
|
||||
expect(
|
||||
mockPlatform.streamCallLog.contains('startVideoCapturing with stream'),
|
||||
isTrue);
|
||||
});
|
||||
|
||||
test('startVideoRecording() by default does not stream', () async {
|
||||
final CameraController cameraController = CameraController(
|
||||
const CameraDescription(
|
||||
name: 'cam',
|
||||
lensDirection: CameraLensDirection.back,
|
||||
sensorOrientation: 90),
|
||||
ResolutionPreset.max);
|
||||
|
||||
await cameraController.initialize();
|
||||
|
||||
cameraController.startVideoRecording();
|
||||
|
||||
expect(mockPlatform.streamCallLog.contains('startVideoCapturing'), isTrue);
|
||||
});
|
||||
}
|
||||
|
||||
class MockStreamingCameraPlatform extends MockCameraPlatform {
|
||||
List<String> streamCallLog = <String>[];
|
||||
|
||||
StreamController<CameraImageData>? _streamController;
|
||||
|
||||
@override
|
||||
Stream<CameraImageData> onStreamedFrameAvailable(int cameraId,
|
||||
{CameraImageStreamOptions? options}) {
|
||||
streamCallLog.add('onStreamedFrameAvailable');
|
||||
_streamController = StreamController<CameraImageData>(
|
||||
onListen: _onFrameStreamListen,
|
||||
onCancel: _onFrameStreamCancel,
|
||||
);
|
||||
return _streamController!.stream;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<XFile> startVideoRecording(int cameraId,
|
||||
{Duration? maxVideoDuration}) {
|
||||
streamCallLog.add('startVideoRecording');
|
||||
return super
|
||||
.startVideoRecording(cameraId, maxVideoDuration: maxVideoDuration);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> startVideoCapturing(VideoCaptureOptions options) {
|
||||
if (options.streamCallback == null) {
|
||||
streamCallLog.add('startVideoCapturing');
|
||||
} else {
|
||||
streamCallLog.add('startVideoCapturing with stream');
|
||||
}
|
||||
return super.startVideoCapturing(options);
|
||||
}
|
||||
|
||||
void _onFrameStreamListen() {
|
||||
streamCallLog.add('listen');
|
||||
}
|
||||
|
||||
FutureOr<void> _onFrameStreamCancel() async {
|
||||
streamCallLog.add('cancel');
|
||||
_streamController = null;
|
||||
}
|
||||
}
|
187
packages/camera/camera/test/camera_image_test.dart
Normal file
@ -0,0 +1,187 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231)
|
||||
// ignore: unnecessary_import
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:camera_platform_interface/camera_platform_interface.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
test('translates correctly from platform interface classes', () {
|
||||
final CameraImageData originalImage = CameraImageData(
|
||||
format: const CameraImageFormat(ImageFormatGroup.jpeg, raw: 1234),
|
||||
planes: <CameraImagePlane>[
|
||||
CameraImagePlane(
|
||||
bytes: Uint8List.fromList(<int>[1, 2, 3, 4]),
|
||||
bytesPerRow: 20,
|
||||
bytesPerPixel: 3,
|
||||
width: 200,
|
||||
height: 100,
|
||||
),
|
||||
CameraImagePlane(
|
||||
bytes: Uint8List.fromList(<int>[5, 6, 7, 8]),
|
||||
bytesPerRow: 18,
|
||||
bytesPerPixel: 4,
|
||||
width: 220,
|
||||
height: 110,
|
||||
),
|
||||
],
|
||||
width: 640,
|
||||
height: 480,
|
||||
lensAperture: 2.5,
|
||||
sensorExposureTime: 5,
|
||||
sensorSensitivity: 1.3,
|
||||
);
|
||||
|
||||
final CameraImage image = CameraImage.fromPlatformInterface(originalImage);
|
||||
// Simple values.
|
||||
expect(image.width, 640);
|
||||
expect(image.height, 480);
|
||||
expect(image.lensAperture, 2.5);
|
||||
expect(image.sensorExposureTime, 5);
|
||||
expect(image.sensorSensitivity, 1.3);
|
||||
// Format.
|
||||
expect(image.format.group, ImageFormatGroup.jpeg);
|
||||
expect(image.format.raw, 1234);
|
||||
// Planes.
|
||||
expect(image.planes.length, originalImage.planes.length);
|
||||
for (int i = 0; i < image.planes.length; i++) {
|
||||
expect(
|
||||
image.planes[i].bytes.length, originalImage.planes[i].bytes.length);
|
||||
for (int j = 0; j < image.planes[i].bytes.length; j++) {
|
||||
expect(image.planes[i].bytes[j], originalImage.planes[i].bytes[j]);
|
||||
}
|
||||
expect(
|
||||
image.planes[i].bytesPerPixel, originalImage.planes[i].bytesPerPixel);
|
||||
expect(image.planes[i].bytesPerRow, originalImage.planes[i].bytesPerRow);
|
||||
expect(image.planes[i].width, originalImage.planes[i].width);
|
||||
expect(image.planes[i].height, originalImage.planes[i].height);
|
||||
}
|
||||
});
|
||||
|
||||
group('legacy constructors', () {
|
||||
test('$CameraImage can be created', () {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
final CameraImage cameraImage =
|
||||
CameraImage.fromPlatformData(<dynamic, dynamic>{
|
||||
'format': 35,
|
||||
'height': 1,
|
||||
'width': 4,
|
||||
'lensAperture': 1.8,
|
||||
'sensorExposureTime': 9991324,
|
||||
'sensorSensitivity': 92.0,
|
||||
'planes': <dynamic>[
|
||||
<dynamic, dynamic>{
|
||||
'bytes': Uint8List.fromList(<int>[1, 2, 3, 4]),
|
||||
'bytesPerPixel': 1,
|
||||
'bytesPerRow': 4,
|
||||
'height': 1,
|
||||
'width': 4
|
||||
}
|
||||
]
|
||||
});
|
||||
expect(cameraImage.height, 1);
|
||||
expect(cameraImage.width, 4);
|
||||
expect(cameraImage.format.group, ImageFormatGroup.yuv420);
|
||||
expect(cameraImage.planes.length, 1);
|
||||
});
|
||||
|
||||
test('$CameraImage has ImageFormatGroup.yuv420 for iOS', () {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
||||
|
||||
final CameraImage cameraImage =
|
||||
CameraImage.fromPlatformData(<dynamic, dynamic>{
|
||||
'format': 875704438,
|
||||
'height': 1,
|
||||
'width': 4,
|
||||
'lensAperture': 1.8,
|
||||
'sensorExposureTime': 9991324,
|
||||
'sensorSensitivity': 92.0,
|
||||
'planes': <dynamic>[
|
||||
<dynamic, dynamic>{
|
||||
'bytes': Uint8List.fromList(<int>[1, 2, 3, 4]),
|
||||
'bytesPerPixel': 1,
|
||||
'bytesPerRow': 4,
|
||||
'height': 1,
|
||||
'width': 4
|
||||
}
|
||||
]
|
||||
});
|
||||
expect(cameraImage.format.group, ImageFormatGroup.yuv420);
|
||||
});
|
||||
|
||||
test('$CameraImage has ImageFormatGroup.yuv420 for Android', () {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
|
||||
final CameraImage cameraImage =
|
||||
CameraImage.fromPlatformData(<dynamic, dynamic>{
|
||||
'format': 35,
|
||||
'height': 1,
|
||||
'width': 4,
|
||||
'lensAperture': 1.8,
|
||||
'sensorExposureTime': 9991324,
|
||||
'sensorSensitivity': 92.0,
|
||||
'planes': <dynamic>[
|
||||
<dynamic, dynamic>{
|
||||
'bytes': Uint8List.fromList(<int>[1, 2, 3, 4]),
|
||||
'bytesPerPixel': 1,
|
||||
'bytesPerRow': 4,
|
||||
'height': 1,
|
||||
'width': 4
|
||||
}
|
||||
]
|
||||
});
|
||||
expect(cameraImage.format.group, ImageFormatGroup.yuv420);
|
||||
});
|
||||
|
||||
test('$CameraImage has ImageFormatGroup.bgra8888 for iOS', () {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
||||
|
||||
final CameraImage cameraImage =
|
||||
CameraImage.fromPlatformData(<dynamic, dynamic>{
|
||||
'format': 1111970369,
|
||||
'height': 1,
|
||||
'width': 4,
|
||||
'lensAperture': 1.8,
|
||||
'sensorExposureTime': 9991324,
|
||||
'sensorSensitivity': 92.0,
|
||||
'planes': <dynamic>[
|
||||
<dynamic, dynamic>{
|
||||
'bytes': Uint8List.fromList(<int>[1, 2, 3, 4]),
|
||||
'bytesPerPixel': 1,
|
||||
'bytesPerRow': 4,
|
||||
'height': 1,
|
||||
'width': 4
|
||||
}
|
||||
]
|
||||
});
|
||||
expect(cameraImage.format.group, ImageFormatGroup.bgra8888);
|
||||
});
|
||||
test('$CameraImage has ImageFormatGroup.unknown', () {
|
||||
final CameraImage cameraImage =
|
||||
CameraImage.fromPlatformData(<dynamic, dynamic>{
|
||||
'format': null,
|
||||
'height': 1,
|
||||
'width': 4,
|
||||
'lensAperture': 1.8,
|
||||
'sensorExposureTime': 9991324,
|
||||
'sensorSensitivity': 92.0,
|
||||
'planes': <dynamic>[
|
||||
<dynamic, dynamic>{
|
||||
'bytes': Uint8List.fromList(<int>[1, 2, 3, 4]),
|
||||
'bytesPerPixel': 1,
|
||||
'bytesPerRow': 4,
|
||||
'height': 1,
|
||||
'width': 4
|
||||
}
|
||||
]
|
||||
});
|
||||
expect(cameraImage.format.group, ImageFormatGroup.unknown);
|
||||
});
|
||||
});
|
||||
}
|
244
packages/camera/camera/test/camera_preview_test.dart
Normal file
@ -0,0 +1,244 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class FakeController extends ValueNotifier<CameraValue>
|
||||
implements CameraController {
|
||||
FakeController() : super(const CameraValue.uninitialized());
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildPreview() {
|
||||
return const Texture(textureId: CameraController.kUninitializedCameraId);
|
||||
}
|
||||
|
||||
@override
|
||||
int get cameraId => CameraController.kUninitializedCameraId;
|
||||
|
||||
@override
|
||||
void debugCheckIsDisposed() {}
|
||||
|
||||
@override
|
||||
CameraDescription get description => const CameraDescription(
|
||||
name: '', lensDirection: CameraLensDirection.back, sensorOrientation: 0);
|
||||
|
||||
@override
|
||||
bool get enableAudio => false;
|
||||
|
||||
@override
|
||||
Future<double> getExposureOffsetStepSize() async => 1.0;
|
||||
|
||||
@override
|
||||
Future<double> getMaxExposureOffset() async => 1.0;
|
||||
|
||||
@override
|
||||
Future<double> getMaxZoomLevel() async => 1.0;
|
||||
|
||||
@override
|
||||
Future<double> getMinExposureOffset() async => 1.0;
|
||||
|
||||
@override
|
||||
Future<double> getMinZoomLevel() async => 1.0;
|
||||
|
||||
@override
|
||||
ImageFormatGroup? get imageFormatGroup => null;
|
||||
|
||||
@override
|
||||
Future<void> initialize() async {}
|
||||
|
||||
@override
|
||||
Future<void> lockCaptureOrientation([DeviceOrientation? orientation]) async {}
|
||||
|
||||
@override
|
||||
Future<void> pauseVideoRecording() async {}
|
||||
|
||||
@override
|
||||
Future<void> prepareForVideoRecording() async {}
|
||||
|
||||
@override
|
||||
ResolutionPreset get resolutionPreset => ResolutionPreset.low;
|
||||
|
||||
@override
|
||||
Future<void> resumeVideoRecording() async {}
|
||||
|
||||
@override
|
||||
Future<void> setExposureMode(ExposureMode mode) async {}
|
||||
|
||||
@override
|
||||
Future<double> setExposureOffset(double offset) async => offset;
|
||||
|
||||
@override
|
||||
Future<void> setExposurePoint(Offset? point) async {}
|
||||
|
||||
@override
|
||||
Future<void> setFlashMode(FlashMode mode) async {}
|
||||
|
||||
@override
|
||||
Future<void> setFocusMode(FocusMode mode) async {}
|
||||
|
||||
@override
|
||||
Future<void> setFocusPoint(Offset? point) async {}
|
||||
|
||||
@override
|
||||
Future<void> setZoomLevel(double zoom) async {}
|
||||
|
||||
@override
|
||||
Future<void> startImageStream(onLatestImageAvailable onAvailable) async {}
|
||||
|
||||
@override
|
||||
Future<void> startVideoRecording(
|
||||
{onLatestImageAvailable? onAvailable}) async {}
|
||||
|
||||
@override
|
||||
Future<void> stopImageStream() async {}
|
||||
|
||||
@override
|
||||
Future<XFile> stopVideoRecording() async => XFile('');
|
||||
|
||||
@override
|
||||
Future<XFile> takePicture() async => XFile('');
|
||||
|
||||
@override
|
||||
Future<void> unlockCaptureOrientation() async {}
|
||||
|
||||
@override
|
||||
Future<void> pausePreview() async {}
|
||||
|
||||
@override
|
||||
Future<void> resumePreview() async {}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('RotatedBox (Android only)', () {
|
||||
testWidgets(
|
||||
'when recording rotatedBox should turn according to recording orientation',
|
||||
(
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
|
||||
final FakeController controller = FakeController();
|
||||
controller.value = controller.value.copyWith(
|
||||
isInitialized: true,
|
||||
isRecordingVideo: true,
|
||||
deviceOrientation: DeviceOrientation.portraitUp,
|
||||
lockedCaptureOrientation:
|
||||
const Optional<DeviceOrientation>.fromNullable(
|
||||
DeviceOrientation.landscapeRight),
|
||||
recordingOrientation: const Optional<DeviceOrientation>.fromNullable(
|
||||
DeviceOrientation.landscapeLeft),
|
||||
previewSize: const Size(480, 640),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: CameraPreview(controller),
|
||||
),
|
||||
);
|
||||
expect(find.byType(RotatedBox), findsOneWidget);
|
||||
|
||||
final RotatedBox rotatedBox =
|
||||
tester.widget<RotatedBox>(find.byType(RotatedBox));
|
||||
expect(rotatedBox.quarterTurns, 3);
|
||||
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'when orientation locked rotatedBox should turn according to locked orientation',
|
||||
(
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
|
||||
final FakeController controller = FakeController();
|
||||
controller.value = controller.value.copyWith(
|
||||
isInitialized: true,
|
||||
deviceOrientation: DeviceOrientation.portraitUp,
|
||||
lockedCaptureOrientation:
|
||||
const Optional<DeviceOrientation>.fromNullable(
|
||||
DeviceOrientation.landscapeRight),
|
||||
recordingOrientation: const Optional<DeviceOrientation>.fromNullable(
|
||||
DeviceOrientation.landscapeLeft),
|
||||
previewSize: const Size(480, 640),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: CameraPreview(controller),
|
||||
),
|
||||
);
|
||||
expect(find.byType(RotatedBox), findsOneWidget);
|
||||
|
||||
final RotatedBox rotatedBox =
|
||||
tester.widget<RotatedBox>(find.byType(RotatedBox));
|
||||
expect(rotatedBox.quarterTurns, 1);
|
||||
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'when not locked and not recording rotatedBox should turn according to device orientation',
|
||||
(
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
|
||||
final FakeController controller = FakeController();
|
||||
controller.value = controller.value.copyWith(
|
||||
isInitialized: true,
|
||||
deviceOrientation: DeviceOrientation.portraitUp,
|
||||
recordingOrientation: const Optional<DeviceOrientation>.fromNullable(
|
||||
DeviceOrientation.landscapeLeft),
|
||||
previewSize: const Size(480, 640),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: CameraPreview(controller),
|
||||
),
|
||||
);
|
||||
expect(find.byType(RotatedBox), findsOneWidget);
|
||||
|
||||
final RotatedBox rotatedBox =
|
||||
tester.widget<RotatedBox>(find.byType(RotatedBox));
|
||||
expect(rotatedBox.quarterTurns, 0);
|
||||
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
});
|
||||
}, skip: kIsWeb);
|
||||
|
||||
testWidgets('when not on Android there should not be a rotated box',
|
||||
(WidgetTester tester) async {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
||||
final FakeController controller = FakeController();
|
||||
controller.value = controller.value.copyWith(
|
||||
isInitialized: true,
|
||||
previewSize: const Size(480, 640),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: CameraPreview(controller),
|
||||
),
|
||||
);
|
||||
expect(find.byType(RotatedBox), findsNothing);
|
||||
expect(find.byType(Texture), findsOneWidget);
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
});
|
||||
}
|
1537
packages/camera/camera/test/camera_test.dart
Normal file
150
packages/camera/camera/test/camera_value_test.dart
Normal file
@ -0,0 +1,150 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#106316)
|
||||
// ignore: unnecessary_import
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:camera/camera.dart';
|
||||
// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#106316)
|
||||
// ignore: unnecessary_import
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('camera_value', () {
|
||||
test('Can be created', () {
|
||||
const CameraValue cameraValue = CameraValue(
|
||||
isInitialized: false,
|
||||
previewSize: Size(10, 10),
|
||||
isRecordingPaused: false,
|
||||
isRecordingVideo: false,
|
||||
isTakingPicture: false,
|
||||
isStreamingImages: false,
|
||||
flashMode: FlashMode.auto,
|
||||
exposureMode: ExposureMode.auto,
|
||||
exposurePointSupported: true,
|
||||
focusMode: FocusMode.auto,
|
||||
deviceOrientation: DeviceOrientation.portraitUp,
|
||||
lockedCaptureOrientation: DeviceOrientation.portraitUp,
|
||||
recordingOrientation: DeviceOrientation.portraitUp,
|
||||
focusPointSupported: true,
|
||||
previewPauseOrientation: DeviceOrientation.portraitUp,
|
||||
);
|
||||
|
||||
expect(cameraValue, isA<CameraValue>());
|
||||
expect(cameraValue.isInitialized, isFalse);
|
||||
expect(cameraValue.errorDescription, null);
|
||||
expect(cameraValue.previewSize, const Size(10, 10));
|
||||
expect(cameraValue.isRecordingPaused, isFalse);
|
||||
expect(cameraValue.isRecordingVideo, isFalse);
|
||||
expect(cameraValue.isTakingPicture, isFalse);
|
||||
expect(cameraValue.isStreamingImages, isFalse);
|
||||
expect(cameraValue.flashMode, FlashMode.auto);
|
||||
expect(cameraValue.exposureMode, ExposureMode.auto);
|
||||
expect(cameraValue.exposurePointSupported, true);
|
||||
expect(cameraValue.deviceOrientation, DeviceOrientation.portraitUp);
|
||||
expect(
|
||||
cameraValue.lockedCaptureOrientation, DeviceOrientation.portraitUp);
|
||||
expect(cameraValue.recordingOrientation, DeviceOrientation.portraitUp);
|
||||
expect(cameraValue.isPreviewPaused, false);
|
||||
expect(cameraValue.previewPauseOrientation, DeviceOrientation.portraitUp);
|
||||
});
|
||||
|
||||
test('Can be created as uninitialized', () {
|
||||
const CameraValue cameraValue = CameraValue.uninitialized();
|
||||
|
||||
expect(cameraValue, isA<CameraValue>());
|
||||
expect(cameraValue.isInitialized, isFalse);
|
||||
expect(cameraValue.errorDescription, null);
|
||||
expect(cameraValue.previewSize, null);
|
||||
expect(cameraValue.isRecordingPaused, isFalse);
|
||||
expect(cameraValue.isRecordingVideo, isFalse);
|
||||
expect(cameraValue.isTakingPicture, isFalse);
|
||||
expect(cameraValue.isStreamingImages, isFalse);
|
||||
expect(cameraValue.flashMode, FlashMode.auto);
|
||||
expect(cameraValue.exposureMode, ExposureMode.auto);
|
||||
expect(cameraValue.exposurePointSupported, false);
|
||||
expect(cameraValue.focusMode, FocusMode.auto);
|
||||
expect(cameraValue.deviceOrientation, DeviceOrientation.portraitUp);
|
||||
expect(cameraValue.lockedCaptureOrientation, null);
|
||||
expect(cameraValue.recordingOrientation, null);
|
||||
expect(cameraValue.isPreviewPaused, isFalse);
|
||||
expect(cameraValue.previewPauseOrientation, null);
|
||||
});
|
||||
|
||||
test('Can be copied with isInitialized', () {
|
||||
const CameraValue cv = CameraValue.uninitialized();
|
||||
final CameraValue cameraValue = cv.copyWith(isInitialized: true);
|
||||
|
||||
expect(cameraValue, isA<CameraValue>());
|
||||
expect(cameraValue.isInitialized, isTrue);
|
||||
expect(cameraValue.errorDescription, null);
|
||||
expect(cameraValue.previewSize, null);
|
||||
expect(cameraValue.isRecordingPaused, isFalse);
|
||||
expect(cameraValue.isRecordingVideo, isFalse);
|
||||
expect(cameraValue.isTakingPicture, isFalse);
|
||||
expect(cameraValue.isStreamingImages, isFalse);
|
||||
expect(cameraValue.flashMode, FlashMode.auto);
|
||||
expect(cameraValue.focusMode, FocusMode.auto);
|
||||
expect(cameraValue.exposureMode, ExposureMode.auto);
|
||||
expect(cameraValue.exposurePointSupported, false);
|
||||
expect(cameraValue.deviceOrientation, DeviceOrientation.portraitUp);
|
||||
expect(cameraValue.lockedCaptureOrientation, null);
|
||||
expect(cameraValue.recordingOrientation, null);
|
||||
expect(cameraValue.isPreviewPaused, isFalse);
|
||||
expect(cameraValue.previewPauseOrientation, null);
|
||||
});
|
||||
|
||||
test('Has aspectRatio after setting size', () {
|
||||
const CameraValue cv = CameraValue.uninitialized();
|
||||
final CameraValue cameraValue =
|
||||
cv.copyWith(isInitialized: true, previewSize: const Size(20, 10));
|
||||
|
||||
expect(cameraValue.aspectRatio, 2.0);
|
||||
});
|
||||
|
||||
test('hasError is true after setting errorDescription', () {
|
||||
const CameraValue cv = CameraValue.uninitialized();
|
||||
final CameraValue cameraValue = cv.copyWith(errorDescription: 'error');
|
||||
|
||||
expect(cameraValue.hasError, isTrue);
|
||||
expect(cameraValue.errorDescription, 'error');
|
||||
});
|
||||
|
||||
test('Recording paused is false when not recording', () {
|
||||
const CameraValue cv = CameraValue.uninitialized();
|
||||
final CameraValue cameraValue = cv.copyWith(
|
||||
isInitialized: true,
|
||||
isRecordingVideo: false,
|
||||
isRecordingPaused: true);
|
||||
|
||||
expect(cameraValue.isRecordingPaused, isFalse);
|
||||
});
|
||||
|
||||
test('toString() works as expected', () {
|
||||
const CameraValue cameraValue = CameraValue(
|
||||
isInitialized: false,
|
||||
previewSize: Size(10, 10),
|
||||
isRecordingPaused: false,
|
||||
isRecordingVideo: false,
|
||||
isTakingPicture: false,
|
||||
isStreamingImages: false,
|
||||
flashMode: FlashMode.auto,
|
||||
exposureMode: ExposureMode.auto,
|
||||
focusMode: FocusMode.auto,
|
||||
exposurePointSupported: true,
|
||||
focusPointSupported: true,
|
||||
deviceOrientation: DeviceOrientation.portraitUp,
|
||||
lockedCaptureOrientation: DeviceOrientation.portraitUp,
|
||||
recordingOrientation: DeviceOrientation.portraitUp,
|
||||
isPreviewPaused: true,
|
||||
previewPauseOrientation: DeviceOrientation.portraitUp);
|
||||
|
||||
expect(cameraValue.toString(),
|
||||
'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false, flashMode: FlashMode.auto, exposureMode: ExposureMode.auto, focusMode: FocusMode.auto, exposurePointSupported: true, focusPointSupported: true, deviceOrientation: DeviceOrientation.portraitUp, lockedCaptureOrientation: DeviceOrientation.portraitUp, recordingOrientation: DeviceOrientation.portraitUp, isPreviewPaused: true, previewPausedOrientation: DeviceOrientation.portraitUp)');
|
||||
});
|
||||
});
|
||||
}
|
66
packages/camera/camera_android/AUTHORS
Normal file
@ -0,0 +1,66 @@
|
||||
# Below is a list of people and organizations that have contributed
|
||||
# to the Flutter project. Names should be added to the list like so:
|
||||
#
|
||||
# Name/Organization <email address>
|
||||
|
||||
Google Inc.
|
||||
The Chromium Authors
|
||||
German Saprykin <saprykin.h@gmail.com>
|
||||
Benjamin Sauer <sauer.benjamin@gmail.com>
|
||||
larsenthomasj@gmail.com
|
||||
Ali Bitek <alibitek@protonmail.ch>
|
||||
Pol Batlló <pol.batllo@gmail.com>
|
||||
Anatoly Pulyaevskiy
|
||||
Hayden Flinner <haydenflinner@gmail.com>
|
||||
Stefano Rodriguez <hlsroddy@gmail.com>
|
||||
Salvatore Giordano <salvatoregiordanoo@gmail.com>
|
||||
Brian Armstrong <brian@flutter.institute>
|
||||
Paul DeMarco <paulmdemarco@gmail.com>
|
||||
Fabricio Nogueira <feufeu@gmail.com>
|
||||
Simon Lightfoot <simon@devangels.london>
|
||||
Ashton Thomas <ashton@acrinta.com>
|
||||
Thomas Danner <thmsdnnr@gmail.com>
|
||||
Diego Velásquez <diego.velasquez.lopez@gmail.com>
|
||||
Hajime Nakamura <nkmrhj@gmail.com>
|
||||
Tuyển Vũ Xuân <netsoft1985@gmail.com>
|
||||
Miguel Ruivo <miguel@miguelruivo.com>
|
||||
Sarthak Verma <sarthak@artiosys.com>
|
||||
Mike Diarmid <mike@invertase.io>
|
||||
Invertase <oss@invertase.io>
|
||||
Elliot Hesp <elliot@invertase.io>
|
||||
Vince Varga <vince.varga@smaho.com>
|
||||
Aawaz Gyawali <awazgyawali@gmail.com>
|
||||
EUI Limited <ian.evans3@admiralgroup.co.uk>
|
||||
Katarina Sheremet <katarina@sheremet.ch>
|
||||
Thomas Stockx <thomas@stockxit.com>
|
||||
Sarbagya Dhaubanjar <sarbagyastha@gmail.com>
|
||||
Ozkan Eksi <ozeksi@gmail.com>
|
||||
Rishab Nayak <rishab@bu.edu>
|
||||
ko2ic <ko2ic.dev@gmail.com>
|
||||
Jonathan Younger <jonathan@daikini.com>
|
||||
Jose Sanchez <josesm82@gmail.com>
|
||||
Debkanchan Samadder <debu.samadder@gmail.com>
|
||||
Audrius Karosevicius <audrius.karosevicius@gmail.com>
|
||||
Lukasz Piliszczuk <lukasz@intheloup.io>
|
||||
SoundReply Solutions GmbH <ch@soundreply.com>
|
||||
Rafal Wachol <rwachol@gmail.com>
|
||||
Pau Picas <pau.picas@gmail.com>
|
||||
Christian Weder <chrstian.weder@yapeal.ch>
|
||||
Alexandru Tuca <salexandru.tuca@outlook.com>
|
||||
Christian Weder <chrstian.weder@yapeal.ch>
|
||||
Rhodes Davis Jr. <rody.davis.jr@gmail.com>
|
||||
Luigi Agosti <luigi@tengio.com>
|
||||
Quentin Le Guennec <quentin@tengio.com>
|
||||
Koushik Ravikumar <koushik@tengio.com>
|
||||
Nissim Dsilva <nissim@tengio.com>
|
||||
Giancarlo Rocha <giancarloiff@gmail.com>
|
||||
Ryo Miyake <ryo@miyake.id>
|
||||
Théo Champion <contact.theochampion@gmail.com>
|
||||
Kazuki Yamaguchi <y.kazuki0614n@gmail.com>
|
||||
Eitan Schwartz <eshvartz@gmail.com>
|
||||
Chris Rutkowski <chrisrutkowski89@gmail.com>
|
||||
Juan Alvarez <juan.alvarez@resideo.com>
|
||||
Aleksandr Yurkovskiy <sanekyy@gmail.com>
|
||||
Anton Borries <mail@antonborri.es>
|
||||
Alex Li <google@alexv525.com>
|
||||
Rahul Raj <64.rahulraj@gmail.com>
|
75
packages/camera/camera_android/CHANGELOG.md
Normal file
@ -0,0 +1,75 @@
|
||||
## 0.10.4
|
||||
|
||||
* Temporarily fixes issue with requested video profiles being null by falling back to deprecated behavior in that case.
|
||||
|
||||
## 0.10.3
|
||||
|
||||
* Adds back use of Optional type.
|
||||
* Updates minimum Flutter version to 3.0.
|
||||
|
||||
## 0.10.2+3
|
||||
|
||||
* Updates code for stricter lint checks.
|
||||
|
||||
## 0.10.2+2
|
||||
|
||||
* Fixes zoom computation for virtual cameras hiding physical cameras in Android 11+.
|
||||
* Removes the unused CameraZoom class from the codebase.
|
||||
|
||||
## 0.10.2+1
|
||||
|
||||
* Updates code for stricter lint checks.
|
||||
|
||||
## 0.10.2
|
||||
|
||||
* Remove usage of deprecated quiver Optional type.
|
||||
|
||||
## 0.10.1
|
||||
|
||||
* Implements an option to also stream when recording a video.
|
||||
|
||||
## 0.10.0+5
|
||||
|
||||
* Fixes `ArrayIndexOutOfBoundsException` when the permission request is interrupted.
|
||||
|
||||
## 0.10.0+4
|
||||
|
||||
* Upgrades `androidx.annotation` version to 1.5.0.
|
||||
|
||||
## 0.10.0+3
|
||||
|
||||
* Updates code for `no_leading_underscores_for_local_identifiers` lint.
|
||||
|
||||
## 0.10.0+2
|
||||
|
||||
* Removes call to `join` on the camera's background `HandlerThread`.
|
||||
* Updates minimum Flutter version to 2.10.
|
||||
|
||||
## 0.10.0+1
|
||||
|
||||
* Fixes avoid_redundant_argument_values lint warnings and minor typos.
|
||||
|
||||
## 0.10.0
|
||||
|
||||
* **Breaking Change** Updates Android camera access permission error codes to be consistent with other platforms. If your app still handles the legacy `cameraPermission` exception, please update it to handle the new permission exception codes that are noted in the README.
|
||||
* Ignores missing return warnings in preparation for [upcoming analysis changes](https://github.com/flutter/flutter/issues/105750).
|
||||
|
||||
## 0.9.8+3
|
||||
|
||||
* Skips duplicate calls to stop background thread and removes unnecessary closings of camera capture sessions on Android.
|
||||
|
||||
## 0.9.8+2
|
||||
|
||||
* Fixes exception in registerWith caused by the switch to an in-package method channel.
|
||||
|
||||
## 0.9.8+1
|
||||
|
||||
* Ignores deprecation warnings for upcoming styleFrom button API changes.
|
||||
|
||||
## 0.9.8
|
||||
|
||||
* Switches to internal method channel implementation.
|
||||
|
||||
## 0.9.7+1
|
||||
|
||||
* Splits from `camera` as a federated implementation.
|
25
packages/camera/camera_android/LICENSE
Normal file
@ -0,0 +1,25 @@
|
||||
Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
11
packages/camera/camera_android/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# camera\_android
|
||||
|
||||
The Android implementation of [`camera`][1].
|
||||
|
||||
## Usage
|
||||
|
||||
This package is [endorsed][2], which means you can simply use `camera`
|
||||
normally. This package will be automatically included in your app when you do.
|
||||
|
||||
[1]: https://pub.dev/packages/camera
|
||||
[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin
|
66
packages/camera/camera_android/android/build.gradle
Normal file
@ -0,0 +1,66 @@
|
||||
group 'io.flutter.plugins.camera'
|
||||
version '1.0-SNAPSHOT'
|
||||
def args = ["-Xlint:deprecation","-Xlint:unchecked"]
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.2'
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
project.getTasks().withType(JavaCompile){
|
||||
options.compilerArgs.addAll(args)
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
|
||||
defaultConfig {
|
||||
targetSdkVersion 31
|
||||
minSdkVersion 21
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
lintOptions {
|
||||
disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency'
|
||||
baseline file("lint-baseline.xml")
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
|
||||
testOptions {
|
||||
unitTests.includeAndroidResources = true
|
||||
unitTests.returnDefaultValues = true
|
||||
unitTests.all {
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
outputs.upToDateWhen {false}
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.annotation:annotation:1.5.0'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.mockito:mockito-inline:5.0.0'
|
||||
testImplementation 'androidx.test:core:1.4.0'
|
||||
testImplementation 'org.robolectric:robolectric:4.5'
|
||||
}
|
114
packages/camera/camera_android/android/lint-baseline.xml
Normal file
@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<issues format="5" by="lint 3.5.0" client="gradle" variant="debug" version="3.5.0">
|
||||
|
||||
<issue
|
||||
id="Assert"
|
||||
message="Assertions are unreliable in Dalvik and unimplemented in ART. Use `BuildConfig.DEBUG` conditional checks instead."
|
||||
errorLine1=" assert (boundaries.getWidth() > 0 && boundaries.getHeight() > 0);"
|
||||
errorLine2=" ~~~~~~">
|
||||
<location
|
||||
file="src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java"
|
||||
line="73"
|
||||
column="5"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="Assert"
|
||||
message="Assertions are unreliable in Dalvik and unimplemented in ART. Use `BuildConfig.DEBUG` conditional checks instead."
|
||||
errorLine1=" assert (x >= 0 && x <= 1);"
|
||||
errorLine2=" ~~~~~~">
|
||||
<location
|
||||
file="src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java"
|
||||
line="74"
|
||||
column="5"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="Assert"
|
||||
message="Assertions are unreliable in Dalvik and unimplemented in ART. Use `BuildConfig.DEBUG` conditional checks instead."
|
||||
errorLine1=" assert (y >= 0 && y <= 1);"
|
||||
errorLine2=" ~~~~~~">
|
||||
<location
|
||||
file="src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java"
|
||||
line="75"
|
||||
column="5"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="Assert"
|
||||
message="Assertions are unreliable in Dalvik and unimplemented in ART. Use `BuildConfig.DEBUG` conditional checks instead."
|
||||
errorLine1=" assert (maxBoundaries == null || maxBoundaries.getWidth() > 0);"
|
||||
errorLine2=" ~~~~~~">
|
||||
<location
|
||||
file="src/main/java/io/flutter/plugins/camera/CameraRegions.java"
|
||||
line="16"
|
||||
column="5"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="Assert"
|
||||
message="Assertions are unreliable in Dalvik and unimplemented in ART. Use `BuildConfig.DEBUG` conditional checks instead."
|
||||
errorLine1=" assert (maxBoundaries == null || maxBoundaries.getHeight() > 0);"
|
||||
errorLine2=" ~~~~~~">
|
||||
<location
|
||||
file="src/main/java/io/flutter/plugins/camera/CameraRegions.java"
|
||||
line="17"
|
||||
column="5"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="Assert"
|
||||
message="Assertions are unreliable in Dalvik and unimplemented in ART. Use `BuildConfig.DEBUG` conditional checks instead."
|
||||
errorLine1=" assert (x >= 0 && x <= 1);"
|
||||
errorLine2=" ~~~~~~">
|
||||
<location
|
||||
file="src/main/java/io/flutter/plugins/camera/CameraRegions.java"
|
||||
line="50"
|
||||
column="5"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="Assert"
|
||||
message="Assertions are unreliable in Dalvik and unimplemented in ART. Use `BuildConfig.DEBUG` conditional checks instead."
|
||||
errorLine1=" assert (y >= 0 && y <= 1);"
|
||||
errorLine2=" ~~~~~~">
|
||||
<location
|
||||
file="src/main/java/io/flutter/plugins/camera/CameraRegions.java"
|
||||
line="51"
|
||||
column="5"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="SwitchIntDef"
|
||||
message="Switch statement on an `int` with known associated constant missing case `Configuration.ORIENTATION_SQUARE`, `Configuration.ORIENTATION_UNDEFINED`"
|
||||
errorLine1=" switch (orientation) {"
|
||||
errorLine2=" ~~~~~~">
|
||||
<location
|
||||
file="src/main/java/io/flutter/plugins/camera/DeviceOrientationManager.java"
|
||||
line="143"
|
||||
column="5"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="SwitchIntDef"
|
||||
message="Switch statement on an `int` with known associated constant missing case `Configuration.ORIENTATION_SQUARE`, `Configuration.ORIENTATION_UNDEFINED`"
|
||||
errorLine1=" switch (orientation) {"
|
||||
errorLine2=" ~~~~~~">
|
||||
<location
|
||||
file="src/main/java/io/flutter/plugins/camera/features/sensororientation/DeviceOrientationManager.java"
|
||||
line="264"
|
||||
column="5"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="ObsoleteSdkInt"
|
||||
message="Unnecessary; SDK_INT is never < 21"
|
||||
errorLine1=" if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/io/flutter/plugins/camera/CameraPlugin.java"
|
||||
line="102"
|
||||
column="9"/>
|
||||
</issue>
|
||||
|
||||
</issues>
|
1
packages/camera/camera_android/android/settings.gradle
Normal file
@ -0,0 +1 @@
|
||||
rootProject.name = 'camera_android'
|
@ -0,0 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="io.flutter.plugins.camera">
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||
</manifest>
|
@ -0,0 +1,183 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.camera;
|
||||
|
||||
import android.hardware.camera2.CameraCaptureSession;
|
||||
import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
|
||||
import android.hardware.camera2.CaptureRequest;
|
||||
import android.hardware.camera2.CaptureResult;
|
||||
import android.hardware.camera2.TotalCaptureResult;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import io.flutter.plugins.camera.types.CameraCaptureProperties;
|
||||
import io.flutter.plugins.camera.types.CaptureTimeoutsWrapper;
|
||||
|
||||
/**
|
||||
* A callback object for tracking the progress of a {@link android.hardware.camera2.CaptureRequest}
|
||||
* submitted to the camera device.
|
||||
*/
|
||||
class CameraCaptureCallback extends CaptureCallback {
|
||||
private static final String TAG = "CameraCaptureCallback";
|
||||
private final CameraCaptureStateListener cameraStateListener;
|
||||
private CameraState cameraState;
|
||||
private final CaptureTimeoutsWrapper captureTimeouts;
|
||||
private final CameraCaptureProperties captureProps;
|
||||
|
||||
private CameraCaptureCallback(
|
||||
@NonNull CameraCaptureStateListener cameraStateListener,
|
||||
@NonNull CaptureTimeoutsWrapper captureTimeouts,
|
||||
@NonNull CameraCaptureProperties captureProps) {
|
||||
cameraState = CameraState.STATE_PREVIEW;
|
||||
this.cameraStateListener = cameraStateListener;
|
||||
this.captureTimeouts = captureTimeouts;
|
||||
this.captureProps = captureProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of the {@link CameraCaptureCallback} class.
|
||||
*
|
||||
* @param cameraStateListener instance which will be called when the camera state changes.
|
||||
* @param captureTimeouts specifying the different timeout counters that should be taken into
|
||||
* account.
|
||||
* @return a configured instance of the {@link CameraCaptureCallback} class.
|
||||
*/
|
||||
public static CameraCaptureCallback create(
|
||||
@NonNull CameraCaptureStateListener cameraStateListener,
|
||||
@NonNull CaptureTimeoutsWrapper captureTimeouts,
|
||||
@NonNull CameraCaptureProperties captureProps) {
|
||||
return new CameraCaptureCallback(cameraStateListener, captureTimeouts, captureProps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current {@link CameraState}.
|
||||
*
|
||||
* @return the current {@link CameraState}.
|
||||
*/
|
||||
public CameraState getCameraState() {
|
||||
return cameraState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link CameraState}.
|
||||
*
|
||||
* @param state the camera is currently in.
|
||||
*/
|
||||
public void setCameraState(@NonNull CameraState state) {
|
||||
cameraState = state;
|
||||
}
|
||||
|
||||
private void process(CaptureResult result) {
|
||||
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
|
||||
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
|
||||
|
||||
// Update capture properties
|
||||
if (result instanceof TotalCaptureResult) {
|
||||
Float lensAperture = result.get(CaptureResult.LENS_APERTURE);
|
||||
Long sensorExposureTime = result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
|
||||
Integer sensorSensitivity = result.get(CaptureResult.SENSOR_SENSITIVITY);
|
||||
this.captureProps.setLastLensAperture(lensAperture);
|
||||
this.captureProps.setLastSensorExposureTime(sensorExposureTime);
|
||||
this.captureProps.setLastSensorSensitivity(sensorSensitivity);
|
||||
}
|
||||
|
||||
if (cameraState != CameraState.STATE_PREVIEW) {
|
||||
Log.d(
|
||||
TAG,
|
||||
"CameraCaptureCallback | state: "
|
||||
+ cameraState
|
||||
+ " | afState: "
|
||||
+ afState
|
||||
+ " | aeState: "
|
||||
+ aeState);
|
||||
}
|
||||
|
||||
switch (cameraState) {
|
||||
case STATE_PREVIEW:
|
||||
{
|
||||
// We have nothing to do when the camera preview is working normally.
|
||||
break;
|
||||
}
|
||||
case STATE_WAITING_FOCUS:
|
||||
{
|
||||
if (afState == null) {
|
||||
return;
|
||||
} else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
|
||||
|| afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
|
||||
handleWaitingFocusState(aeState);
|
||||
} else if (captureTimeouts.getPreCaptureFocusing().getIsExpired()) {
|
||||
Log.w(TAG, "Focus timeout, moving on with capture");
|
||||
handleWaitingFocusState(aeState);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case STATE_WAITING_PRECAPTURE_START:
|
||||
{
|
||||
// CONTROL_AE_STATE can be null on some devices
|
||||
if (aeState == null
|
||||
|| aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED
|
||||
|| aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE
|
||||
|| aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) {
|
||||
setCameraState(CameraState.STATE_WAITING_PRECAPTURE_DONE);
|
||||
} else if (captureTimeouts.getPreCaptureMetering().getIsExpired()) {
|
||||
Log.w(TAG, "Metering timeout waiting for pre-capture to start, moving on with capture");
|
||||
|
||||
setCameraState(CameraState.STATE_WAITING_PRECAPTURE_DONE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STATE_WAITING_PRECAPTURE_DONE:
|
||||
{
|
||||
// CONTROL_AE_STATE can be null on some devices
|
||||
if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
|
||||
cameraStateListener.onConverged();
|
||||
} else if (captureTimeouts.getPreCaptureMetering().getIsExpired()) {
|
||||
Log.w(
|
||||
TAG, "Metering timeout waiting for pre-capture to finish, moving on with capture");
|
||||
cameraStateListener.onConverged();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleWaitingFocusState(Integer aeState) {
|
||||
// CONTROL_AE_STATE can be null on some devices
|
||||
if (aeState == null || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) {
|
||||
cameraStateListener.onConverged();
|
||||
} else {
|
||||
cameraStateListener.onPrecapture();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCaptureProgressed(
|
||||
@NonNull CameraCaptureSession session,
|
||||
@NonNull CaptureRequest request,
|
||||
@NonNull CaptureResult partialResult) {
|
||||
process(partialResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCaptureCompleted(
|
||||
@NonNull CameraCaptureSession session,
|
||||
@NonNull CaptureRequest request,
|
||||
@NonNull TotalCaptureResult result) {
|
||||
process(result);
|
||||
}
|
||||
|
||||
/** An interface that describes the different state changes implementers can be informed about. */
|
||||
interface CameraCaptureStateListener {
|
||||
|
||||
/** Called when the {@link android.hardware.camera2.CaptureRequest} has been converged. */
|
||||
void onConverged();
|
||||
|
||||
/**
|
||||
* Called when the {@link android.hardware.camera2.CaptureRequest} enters the pre-capture state.
|
||||
*/
|
||||
void onPrecapture();
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.camera;
|
||||
|
||||
import android.Manifest;
|
||||
import android.Manifest.permission;
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
final class CameraPermissions {
|
||||
interface PermissionsRegistry {
|
||||
@SuppressWarnings("deprecation")
|
||||
void addListener(
|
||||
io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener handler);
|
||||
}
|
||||
|
||||
interface ResultCallback {
|
||||
void onResult(String errorCode, String errorDescription);
|
||||
}
|
||||
|
||||
/**
|
||||
* Camera access permission errors handled when camera is created. See {@code MethodChannelCamera}
|
||||
* in {@code camera/camera_platform_interface} for details.
|
||||
*/
|
||||
private static final String CAMERA_PERMISSIONS_REQUEST_ONGOING =
|
||||
"CameraPermissionsRequestOngoing";
|
||||
|
||||
private static final String CAMERA_PERMISSIONS_REQUEST_ONGOING_MESSAGE =
|
||||
"Another request is ongoing and multiple requests cannot be handled at once.";
|
||||
private static final String CAMERA_ACCESS_DENIED = "CameraAccessDenied";
|
||||
private static final String CAMERA_ACCESS_DENIED_MESSAGE = "Camera access permission was denied.";
|
||||
private static final String AUDIO_ACCESS_DENIED = "AudioAccessDenied";
|
||||
private static final String AUDIO_ACCESS_DENIED_MESSAGE = "Audio access permission was denied.";
|
||||
|
||||
private static final int CAMERA_REQUEST_ID = 9796;
|
||||
@VisibleForTesting boolean ongoing = false;
|
||||
|
||||
void requestPermissions(
|
||||
Activity activity,
|
||||
PermissionsRegistry permissionsRegistry,
|
||||
boolean enableAudio,
|
||||
ResultCallback callback) {
|
||||
if (ongoing) {
|
||||
callback.onResult(
|
||||
CAMERA_PERMISSIONS_REQUEST_ONGOING, CAMERA_PERMISSIONS_REQUEST_ONGOING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (!hasCameraPermission(activity) || (enableAudio && !hasAudioPermission(activity))) {
|
||||
permissionsRegistry.addListener(
|
||||
new CameraRequestPermissionsListener(
|
||||
(String errorCode, String errorDescription) -> {
|
||||
ongoing = false;
|
||||
callback.onResult(errorCode, errorDescription);
|
||||
}));
|
||||
ongoing = true;
|
||||
ActivityCompat.requestPermissions(
|
||||
activity,
|
||||
enableAudio
|
||||
? new String[] {Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO}
|
||||
: new String[] {Manifest.permission.CAMERA},
|
||||
CAMERA_REQUEST_ID);
|
||||
} else {
|
||||
// Permissions already exist. Call the callback with success.
|
||||
callback.onResult(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasCameraPermission(Activity activity) {
|
||||
return ContextCompat.checkSelfPermission(activity, permission.CAMERA)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
private boolean hasAudioPermission(Activity activity) {
|
||||
return ContextCompat.checkSelfPermission(activity, permission.RECORD_AUDIO)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@SuppressWarnings("deprecation")
|
||||
static final class CameraRequestPermissionsListener
|
||||
implements io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener {
|
||||
|
||||
// There's no way to unregister permission listeners in the v1 embedding, so we'll be called
|
||||
// duplicate times in cases where the user denies and then grants a permission. Keep track of if
|
||||
// we've responded before and bail out of handling the callback manually if this is a repeat
|
||||
// call.
|
||||
boolean alreadyCalled = false;
|
||||
|
||||
final ResultCallback callback;
|
||||
|
||||
@VisibleForTesting
|
||||
CameraRequestPermissionsListener(ResultCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onRequestPermissionsResult(int id, String[] permissions, int[] grantResults) {
|
||||
if (alreadyCalled || id != CAMERA_REQUEST_ID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
alreadyCalled = true;
|
||||
// grantResults could be empty if the permissions request with the user is interrupted
|
||||
// https://developer.android.com/reference/android/app/Activity#onRequestPermissionsResult(int,%20java.lang.String[],%20int[])
|
||||
if (grantResults.length == 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
||||
callback.onResult(CAMERA_ACCESS_DENIED, CAMERA_ACCESS_DENIED_MESSAGE);
|
||||
} else if (grantResults.length > 1 && grantResults[1] != PackageManager.PERMISSION_GRANTED) {
|
||||
callback.onResult(AUDIO_ACCESS_DENIED, AUDIO_ACCESS_DENIED_MESSAGE);
|
||||
} else {
|
||||
callback.onResult(null, null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.camera;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin;
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
|
||||
import io.flutter.plugin.common.BinaryMessenger;
|
||||
import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry;
|
||||
import io.flutter.view.TextureRegistry;
|
||||
|
||||
/**
|
||||
* Platform implementation of the camera_plugin.
|
||||
*
|
||||
* <p>Instantiate this in an add to app scenario to gracefully handle activity and context changes.
|
||||
* See {@code io.flutter.plugins.camera.MainActivity} for an example.
|
||||
*
|
||||
* <p>Call {@link #registerWith(io.flutter.plugin.common.PluginRegistry.Registrar)} to register an
|
||||
* implementation of this that uses the stable {@code io.flutter.plugin.common} package.
|
||||
*/
|
||||
public final class CameraPlugin implements FlutterPlugin, ActivityAware {
|
||||
|
||||
private static final String TAG = "CameraPlugin";
|
||||
private @Nullable FlutterPluginBinding flutterPluginBinding;
|
||||
private @Nullable MethodCallHandlerImpl methodCallHandler;
|
||||
|
||||
/**
|
||||
* Initialize this within the {@code #configureFlutterEngine} of a Flutter activity or fragment.
|
||||
*
|
||||
* <p>See {@code io.flutter.plugins.camera.MainActivity} for an example.
|
||||
*/
|
||||
public CameraPlugin() {}
|
||||
|
||||
/**
|
||||
* Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common}
|
||||
* package.
|
||||
*
|
||||
* <p>Calling this automatically initializes the plugin. However plugins initialized this way
|
||||
* won't react to changes in activity or context, unlike {@link CameraPlugin}.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) {
|
||||
CameraPlugin plugin = new CameraPlugin();
|
||||
plugin.maybeStartListening(
|
||||
registrar.activity(),
|
||||
registrar.messenger(),
|
||||
registrar::addRequestPermissionsResultListener,
|
||||
registrar.view());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
|
||||
this.flutterPluginBinding = binding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
|
||||
this.flutterPluginBinding = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
|
||||
maybeStartListening(
|
||||
binding.getActivity(),
|
||||
flutterPluginBinding.getBinaryMessenger(),
|
||||
binding::addRequestPermissionsResultListener,
|
||||
flutterPluginBinding.getTextureRegistry());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromActivity() {
|
||||
// Could be on too low of an SDK to have started listening originally.
|
||||
if (methodCallHandler != null) {
|
||||
methodCallHandler.stopListening();
|
||||
methodCallHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
|
||||
onAttachedToActivity(binding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromActivityForConfigChanges() {
|
||||
onDetachedFromActivity();
|
||||
}
|
||||
|
||||
private void maybeStartListening(
|
||||
Activity activity,
|
||||
BinaryMessenger messenger,
|
||||
PermissionsRegistry permissionsRegistry,
|
||||
TextureRegistry textureRegistry) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
// If the sdk is less than 21 (min sdk for Camera2) we don't register the plugin.
|
||||
return;
|
||||
}
|
||||
|
||||
methodCallHandler =
|
||||
new MethodCallHandlerImpl(
|
||||
activity, messenger, new CameraPermissions(), permissionsRegistry, textureRegistry);
|
||||
}
|
||||
}
|
@ -0,0 +1,386 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.camera;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.camera2.CameraAccessException;
|
||||
import android.hardware.camera2.CameraCharacteristics;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.util.Range;
|
||||
import android.util.Rational;
|
||||
import android.util.Size;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
/** An interface allowing access to the different characteristics of the device's camera. */
|
||||
public interface CameraProperties {
|
||||
|
||||
/**
|
||||
* Returns the name (or identifier) of the camera device.
|
||||
*
|
||||
* @return String The name of the camera device.
|
||||
*/
|
||||
String getCameraName();
|
||||
|
||||
/**
|
||||
* Returns the list of frame rate ranges for @see android.control.aeTargetFpsRange supported by
|
||||
* this camera device.
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#CONTROL_AE_TARGET_FPS_RANGE key.
|
||||
*
|
||||
* @return android.util.Range<Integer>[] List of frame rate ranges supported by this camera
|
||||
* device.
|
||||
*/
|
||||
Range<Integer>[] getControlAutoExposureAvailableTargetFpsRanges();
|
||||
|
||||
/**
|
||||
* Returns the maximum and minimum exposure compensation values for @see
|
||||
* android.control.aeExposureCompensation, in counts of @see android.control.aeCompensationStep,
|
||||
* that are supported by this camera device.
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#CONTROL_AE_COMPENSATION_RANGE key.
|
||||
*
|
||||
* @return android.util.Range<Integer> Maximum and minimum exposure compensation supported by this
|
||||
* camera device.
|
||||
*/
|
||||
Range<Integer> getControlAutoExposureCompensationRange();
|
||||
|
||||
/**
|
||||
* Returns the smallest step by which the exposure compensation can be changed.
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP key.
|
||||
*
|
||||
* @return double Smallest step by which the exposure compensation can be changed.
|
||||
*/
|
||||
double getControlAutoExposureCompensationStep();
|
||||
|
||||
/**
|
||||
* Returns a list of auto-focus modes for @see android.control.afMode that are supported by this
|
||||
* camera device.
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#CONTROL_AF_AVAILABLE_MODES key.
|
||||
*
|
||||
* @return int[] List of auto-focus modes supported by this camera device.
|
||||
*/
|
||||
int[] getControlAutoFocusAvailableModes();
|
||||
|
||||
/**
|
||||
* Returns the maximum number of metering regions that can be used by the auto-exposure routine.
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#CONTROL_MAX_REGIONS_AE key.
|
||||
*
|
||||
* @return Integer Maximum number of metering regions that can be used by the auto-exposure
|
||||
* routine.
|
||||
*/
|
||||
Integer getControlMaxRegionsAutoExposure();
|
||||
|
||||
/**
|
||||
* Returns the maximum number of metering regions that can be used by the auto-focus routine.
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#CONTROL_MAX_REGIONS_AF key.
|
||||
*
|
||||
* @return Integer Maximum number of metering regions that can be used by the auto-focus routine.
|
||||
*/
|
||||
Integer getControlMaxRegionsAutoFocus();
|
||||
|
||||
/**
|
||||
* Returns a list of distortion correction modes for @see android.distortionCorrection.mode that
|
||||
* are supported by this camera device.
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#DISTORTION_CORRECTION_AVAILABLE_MODES key.
|
||||
*
|
||||
* @return int[] List of distortion correction modes supported by this camera device.
|
||||
*/
|
||||
@RequiresApi(api = VERSION_CODES.P)
|
||||
int[] getDistortionCorrectionAvailableModes();
|
||||
|
||||
/**
|
||||
* Returns whether this camera device has a flash unit.
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#FLASH_INFO_AVAILABLE key.
|
||||
*
|
||||
* @return Boolean Whether this camera device has a flash unit.
|
||||
*/
|
||||
Boolean getFlashInfoAvailable();
|
||||
|
||||
/**
|
||||
* Returns the direction the camera faces relative to device screen.
|
||||
*
|
||||
* <p><string>Possible values:</string>
|
||||
*
|
||||
* <ul>
|
||||
* <li>@see android.hardware.camera2.CameraMetadata.LENS_FACING_FRONT
|
||||
* <li>@see android.hardware.camera2.CameraMetadata.LENS_FACING_BACK
|
||||
* <li>@see android.hardware.camera2.CameraMetadata.LENS_FACING_EXTERNAL
|
||||
* </ul>
|
||||
*
|
||||
* <p>By default maps to the @see android.hardware.camera2.CameraCharacteristics.LENS_FACING key.
|
||||
*
|
||||
* @return int Direction the camera faces relative to device screen.
|
||||
*/
|
||||
int getLensFacing();
|
||||
|
||||
/**
|
||||
* Returns the shortest distance from front most surface of the lens that can be brought into
|
||||
* sharp focus.
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE key.
|
||||
*
|
||||
* @return Float Shortest distance from front most surface of the lens that can be brought into
|
||||
* sharp focus.
|
||||
*/
|
||||
Float getLensInfoMinimumFocusDistance();
|
||||
|
||||
/**
|
||||
* Returns the maximum ratio between both active area width and crop region width, and active area
|
||||
* height and crop region height, for @see android.scaler.cropRegion.
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM key.
|
||||
*
|
||||
* @return Float Maximum ratio between both active area width and crop region width, and active
|
||||
* area height and crop region height.
|
||||
*/
|
||||
Float getScalerAvailableMaxDigitalZoom();
|
||||
|
||||
/**
|
||||
* Returns the minimum ratio between the default camera zoom setting and all of the available
|
||||
* zoom.
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE key's lower value.
|
||||
*
|
||||
* @return Float Minimum ratio between the default zoom ratio and the minimum possible zoom.
|
||||
*/
|
||||
@RequiresApi(api = VERSION_CODES.R)
|
||||
Float getScalerMinZoomRatio();
|
||||
|
||||
/**
|
||||
* Returns the maximum ratio between the default camera zoom setting and all of the available
|
||||
* zoom.
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE key's upper value.
|
||||
*
|
||||
* @return Float Maximum ratio between the default zoom ratio and the maximum possible zoom.
|
||||
*/
|
||||
@RequiresApi(api = VERSION_CODES.R)
|
||||
Float getScalerMaxZoomRatio();
|
||||
|
||||
/**
|
||||
* Returns the area of the image sensor which corresponds to active pixels after any geometric
|
||||
* distortion correction has been applied.
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE key.
|
||||
*
|
||||
* @return android.graphics.Rect area of the image sensor which corresponds to active pixels after
|
||||
* any geometric distortion correction has been applied.
|
||||
*/
|
||||
Rect getSensorInfoActiveArraySize();
|
||||
|
||||
/**
|
||||
* Returns the dimensions of the full pixel array, possibly including black calibration pixels.
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE key.
|
||||
*
|
||||
* @return android.util.Size Dimensions of the full pixel array, possibly including black
|
||||
* calibration pixels.
|
||||
*/
|
||||
Size getSensorInfoPixelArraySize();
|
||||
|
||||
/**
|
||||
* Returns the area of the image sensor which corresponds to active pixels prior to the
|
||||
* application of any geometric distortion correction.
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
|
||||
* key.
|
||||
*
|
||||
* @return android.graphics.Rect Area of the image sensor which corresponds to active pixels prior
|
||||
* to the application of any geometric distortion correction.
|
||||
*/
|
||||
@RequiresApi(api = VERSION_CODES.M)
|
||||
Rect getSensorInfoPreCorrectionActiveArraySize();
|
||||
|
||||
/**
|
||||
* Returns the clockwise angle through which the output image needs to be rotated to be upright on
|
||||
* the device screen in its native orientation.
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#SENSOR_ORIENTATION key.
|
||||
*
|
||||
* @return int Clockwise angle through which the output image needs to be rotated to be upright on
|
||||
* the device screen in its native orientation.
|
||||
*/
|
||||
int getSensorOrientation();
|
||||
|
||||
/**
|
||||
* Returns a level which generally classifies the overall set of the camera device functionality.
|
||||
*
|
||||
* <p><strong>Possible values:</strong>
|
||||
*
|
||||
* <ul>
|
||||
* <li>@see android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
|
||||
* <li>@see android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
|
||||
* <li>@see android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
|
||||
* <li>@see android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEVEL_3
|
||||
* <li>@see android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL
|
||||
* </ul>
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL key.
|
||||
*
|
||||
* @return int Level which generally classifies the overall set of the camera device
|
||||
* functionality.
|
||||
*/
|
||||
int getHardwareLevel();
|
||||
|
||||
/**
|
||||
* Returns a list of noise reduction modes for @see android.noiseReduction.mode that are supported
|
||||
* by this camera device.
|
||||
*
|
||||
* <p>By default maps to the @see
|
||||
* android.hardware.camera2.CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES
|
||||
* key.
|
||||
*
|
||||
* @return int[] List of noise reduction modes that are supported by this camera device.
|
||||
*/
|
||||
int[] getAvailableNoiseReductionModes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the @see CameraProperties interface using the @see
|
||||
* android.hardware.camera2.CameraCharacteristics class to access the different characteristics.
|
||||
*/
|
||||
class CameraPropertiesImpl implements CameraProperties {
|
||||
private final CameraCharacteristics cameraCharacteristics;
|
||||
private final String cameraName;
|
||||
|
||||
public CameraPropertiesImpl(String cameraName, CameraManager cameraManager)
|
||||
throws CameraAccessException {
|
||||
this.cameraName = cameraName;
|
||||
this.cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCameraName() {
|
||||
return cameraName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range<Integer>[] getControlAutoExposureAvailableTargetFpsRanges() {
|
||||
return cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range<Integer> getControlAutoExposureCompensationRange() {
|
||||
return cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getControlAutoExposureCompensationStep() {
|
||||
Rational rational =
|
||||
cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP);
|
||||
|
||||
return rational == null ? 0.0 : rational.doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getControlAutoFocusAvailableModes() {
|
||||
return cameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getControlMaxRegionsAutoExposure() {
|
||||
return cameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getControlMaxRegionsAutoFocus() {
|
||||
return cameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF);
|
||||
}
|
||||
|
||||
@RequiresApi(api = VERSION_CODES.P)
|
||||
@Override
|
||||
public int[] getDistortionCorrectionAvailableModes() {
|
||||
return cameraCharacteristics.get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getFlashInfoAvailable() {
|
||||
return cameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLensFacing() {
|
||||
return cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float getLensInfoMinimumFocusDistance() {
|
||||
return cameraCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float getScalerAvailableMaxDigitalZoom() {
|
||||
return cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
|
||||
}
|
||||
|
||||
@RequiresApi(api = VERSION_CODES.R)
|
||||
@Override
|
||||
public Float getScalerMaxZoomRatio() {
|
||||
return cameraCharacteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE).getUpper();
|
||||
}
|
||||
|
||||
@RequiresApi(api = VERSION_CODES.R)
|
||||
@Override
|
||||
public Float getScalerMinZoomRatio() {
|
||||
return cameraCharacteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE).getLower();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rect getSensorInfoActiveArraySize() {
|
||||
return cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Size getSensorInfoPixelArraySize() {
|
||||
return cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE);
|
||||
}
|
||||
|
||||
@RequiresApi(api = VERSION_CODES.M)
|
||||
@Override
|
||||
public Rect getSensorInfoPreCorrectionActiveArraySize() {
|
||||
return cameraCharacteristics.get(
|
||||
CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSensorOrientation() {
|
||||
return cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHardwareLevel() {
|
||||
return cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getAvailableNoiseReductionModes() {
|
||||
return cameraCharacteristics.get(
|
||||
CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES);
|
||||
}
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.camera;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.hardware.camera2.CaptureRequest;
|
||||
import android.hardware.camera2.params.MeteringRectangle;
|
||||
import android.os.Build;
|
||||
import android.util.Size;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Utility class offering functions to calculate values regarding the camera boundaries.
|
||||
*
|
||||
* <p>The functions are used to calculate focus and exposure settings.
|
||||
*/
|
||||
public final class CameraRegionUtils {
|
||||
|
||||
/**
|
||||
* Obtains the boundaries for the currently active camera, that can be used for calculating
|
||||
* MeteringRectangle instances required for setting focus or exposure settings.
|
||||
*
|
||||
* @param cameraProperties - Collection of the characteristics for the current camera device.
|
||||
* @param requestBuilder - The request builder for the current capture request.
|
||||
* @return The boundaries for the current camera device.
|
||||
*/
|
||||
public static Size getCameraBoundaries(
|
||||
@NonNull CameraProperties cameraProperties, @NonNull CaptureRequest.Builder requestBuilder) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
|
||||
&& supportsDistortionCorrection(cameraProperties)) {
|
||||
// Get the current distortion correction mode.
|
||||
Integer distortionCorrectionMode =
|
||||
requestBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE);
|
||||
|
||||
// Return the correct boundaries depending on the mode.
|
||||
android.graphics.Rect rect;
|
||||
if (distortionCorrectionMode == null
|
||||
|| distortionCorrectionMode == CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) {
|
||||
rect = cameraProperties.getSensorInfoPreCorrectionActiveArraySize();
|
||||
} else {
|
||||
rect = cameraProperties.getSensorInfoActiveArraySize();
|
||||
}
|
||||
|
||||
return SizeFactory.create(rect.width(), rect.height());
|
||||
} else {
|
||||
// No distortion correction support.
|
||||
return cameraProperties.getSensorInfoPixelArraySize();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a point into a {@link MeteringRectangle} with the supplied coordinates as the center
|
||||
* point.
|
||||
*
|
||||
* <p>Since the Camera API (due to cross-platform constraints) only accepts a point when
|
||||
* configuring a specific focus or exposure area and Android requires a rectangle to configure
|
||||
* these settings there is a need to convert the point into a rectangle. This method will create
|
||||
* the required rectangle with an arbitrarily size that is a 10th of the current viewport and the
|
||||
* coordinates as the center point.
|
||||
*
|
||||
* @param boundaries - The camera boundaries to calculate the metering rectangle for.
|
||||
* @param x x - 1 >= coordinate >= 0.
|
||||
* @param y y - 1 >= coordinate >= 0.
|
||||
* @return The dimensions of the metering rectangle based on the supplied coordinates and
|
||||
* boundaries.
|
||||
*/
|
||||
public static MeteringRectangle convertPointToMeteringRectangle(
|
||||
@NonNull Size boundaries,
|
||||
double x,
|
||||
double y,
|
||||
@NonNull PlatformChannel.DeviceOrientation orientation) {
|
||||
assert (boundaries.getWidth() > 0 && boundaries.getHeight() > 0);
|
||||
assert (x >= 0 && x <= 1);
|
||||
assert (y >= 0 && y <= 1);
|
||||
// Rotate the coordinates to match the device orientation.
|
||||
double oldX = x, oldY = y;
|
||||
switch (orientation) {
|
||||
case PORTRAIT_UP: // 90 ccw.
|
||||
y = 1 - oldX;
|
||||
x = oldY;
|
||||
break;
|
||||
case PORTRAIT_DOWN: // 90 cw.
|
||||
x = 1 - oldY;
|
||||
y = oldX;
|
||||
break;
|
||||
case LANDSCAPE_LEFT:
|
||||
// No rotation required.
|
||||
break;
|
||||
case LANDSCAPE_RIGHT: // 180.
|
||||
x = 1 - x;
|
||||
y = 1 - y;
|
||||
break;
|
||||
}
|
||||
// Interpolate the target coordinate.
|
||||
int targetX = (int) Math.round(x * ((double) (boundaries.getWidth() - 1)));
|
||||
int targetY = (int) Math.round(y * ((double) (boundaries.getHeight() - 1)));
|
||||
// Determine the dimensions of the metering rectangle (10th of the viewport).
|
||||
int targetWidth = (int) Math.round(((double) boundaries.getWidth()) / 10d);
|
||||
int targetHeight = (int) Math.round(((double) boundaries.getHeight()) / 10d);
|
||||
// Adjust target coordinate to represent top-left corner of metering rectangle.
|
||||
targetX -= targetWidth / 2;
|
||||
targetY -= targetHeight / 2;
|
||||
// Adjust target coordinate as to not fall out of bounds.
|
||||
if (targetX < 0) {
|
||||
targetX = 0;
|
||||
}
|
||||
if (targetY < 0) {
|
||||
targetY = 0;
|
||||
}
|
||||
int maxTargetX = boundaries.getWidth() - 1 - targetWidth;
|
||||
int maxTargetY = boundaries.getHeight() - 1 - targetHeight;
|
||||
if (targetX > maxTargetX) {
|
||||
targetX = maxTargetX;
|
||||
}
|
||||
if (targetY > maxTargetY) {
|
||||
targetY = maxTargetY;
|
||||
}
|
||||
// Build the metering rectangle.
|
||||
return MeteringRectangleFactory.create(targetX, targetY, targetWidth, targetHeight, 1);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
private static boolean supportsDistortionCorrection(CameraProperties cameraProperties) {
|
||||
int[] availableDistortionCorrectionModes =
|
||||
cameraProperties.getDistortionCorrectionAvailableModes();
|
||||
if (availableDistortionCorrectionModes == null) {
|
||||
availableDistortionCorrectionModes = new int[0];
|
||||
}
|
||||
long nonOffModesSupported =
|
||||
Arrays.stream(availableDistortionCorrectionModes)
|
||||
.filter((value) -> value != CaptureRequest.DISTORTION_CORRECTION_MODE_OFF)
|
||||
.count();
|
||||
return nonOffModesSupported > 0;
|
||||
}
|
||||
|
||||
/** Factory class that assists in creating a {@link MeteringRectangle} instance. */
|
||||
static class MeteringRectangleFactory {
|
||||
/**
|
||||
* Creates a new instance of the {@link MeteringRectangle} class.
|
||||
*
|
||||
* <p>This method is visible for testing purposes only and should never be used outside this *
|
||||
* class.
|
||||
*
|
||||
* @param x coordinate >= 0.
|
||||
* @param y coordinate >= 0.
|
||||
* @param width width >= 0.
|
||||
* @param height height >= 0.
|
||||
* @param meteringWeight weight between {@value MeteringRectangle#METERING_WEIGHT_MIN} and
|
||||
* {@value MeteringRectangle#METERING_WEIGHT_MAX} inclusively.
|
||||
* @return new instance of the {@link MeteringRectangle} class.
|
||||
* @throws IllegalArgumentException if any of the parameters were negative.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static MeteringRectangle create(
|
||||
int x, int y, int width, int height, int meteringWeight) {
|
||||
return new MeteringRectangle(x, y, width, height, meteringWeight);
|
||||
}
|
||||
}
|
||||
|
||||
/** Factory class that assists in creating a {@link Size} instance. */
|
||||
static class SizeFactory {
|
||||
/**
|
||||
* Creates a new instance of the {@link Size} class.
|
||||
*
|
||||
* <p>This method is visible for testing purposes only and should never be used outside this *
|
||||
* class.
|
||||
*
|
||||
* @param width width >= 0.
|
||||
* @param height height >= 0.
|
||||
* @return new instance of the {@link Size} class.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static Size create(int width, int height) {
|
||||
return new Size(width, height);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.camera;
|
||||
|
||||
/**
|
||||
* These are the states that the camera can be in. The camera can only take one photo at a time so
|
||||
* this state describes the state of the camera itself. The camera works like a pipeline where we
|
||||
* feed it requests through. It can only process one tasks at a time.
|
||||
*/
|
||||
public enum CameraState {
|
||||
/** Idle, showing preview and not capturing anything. */
|
||||
STATE_PREVIEW,
|
||||
|
||||
/** Starting and waiting for autofocus to complete. */
|
||||
STATE_WAITING_FOCUS,
|
||||
|
||||
/** Start performing autoexposure. */
|
||||
STATE_WAITING_PRECAPTURE_START,
|
||||
|
||||
/** waiting for autoexposure to complete. */
|
||||
STATE_WAITING_PRECAPTURE_DONE,
|
||||
|
||||
/** Capturing an image. */
|
||||
STATE_CAPTURING,
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.camera;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.hardware.camera2.CameraAccessException;
|
||||
import android.hardware.camera2.CameraCharacteristics;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.hardware.camera2.CameraMetadata;
|
||||
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** Provides various utilities for camera. */
|
||||
public final class CameraUtils {
|
||||
|
||||
private CameraUtils() {}
|
||||
|
||||
/**
|
||||
* Gets the {@link CameraManager} singleton.
|
||||
*
|
||||
* @param context The context to get the {@link CameraManager} singleton from.
|
||||
* @return The {@link CameraManager} singleton.
|
||||
*/
|
||||
static CameraManager getCameraManager(Context context) {
|
||||
return (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the {@link PlatformChannel.DeviceOrientation} to a string value.
|
||||
*
|
||||
* @param orientation The orientation to serialize.
|
||||
* @return The serialized orientation.
|
||||
* @throws UnsupportedOperationException when the provided orientation not have a corresponding
|
||||
* string value.
|
||||
*/
|
||||
static String serializeDeviceOrientation(PlatformChannel.DeviceOrientation orientation) {
|
||||
if (orientation == null)
|
||||
throw new UnsupportedOperationException("Could not serialize null device orientation.");
|
||||
switch (orientation) {
|
||||
case PORTRAIT_UP:
|
||||
return "portraitUp";
|
||||
case PORTRAIT_DOWN:
|
||||
return "portraitDown";
|
||||
case LANDSCAPE_LEFT:
|
||||
return "landscapeLeft";
|
||||
case LANDSCAPE_RIGHT:
|
||||
return "landscapeRight";
|
||||
default:
|
||||
throw new UnsupportedOperationException(
|
||||
"Could not serialize device orientation: " + orientation.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a string value to its corresponding {@link PlatformChannel.DeviceOrientation}
|
||||
* value.
|
||||
*
|
||||
* @param orientation The string value to deserialize.
|
||||
* @return The deserialized orientation.
|
||||
* @throws UnsupportedOperationException when the provided string value does not have a
|
||||
* corresponding {@link PlatformChannel.DeviceOrientation}.
|
||||
*/
|
||||
static PlatformChannel.DeviceOrientation deserializeDeviceOrientation(String orientation) {
|
||||
if (orientation == null)
|
||||
throw new UnsupportedOperationException("Could not deserialize null device orientation.");
|
||||
switch (orientation) {
|
||||
case "portraitUp":
|
||||
return PlatformChannel.DeviceOrientation.PORTRAIT_UP;
|
||||
case "portraitDown":
|
||||
return PlatformChannel.DeviceOrientation.PORTRAIT_DOWN;
|
||||
case "landscapeLeft":
|
||||
return PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT;
|
||||
case "landscapeRight":
|
||||
return PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT;
|
||||
default:
|
||||
throw new UnsupportedOperationException(
|
||||
"Could not deserialize device orientation: " + orientation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the available cameras for the device.
|
||||
*
|
||||
* @param activity The current Android activity.
|
||||
* @return A map of all the available cameras, with their name as their key.
|
||||
* @throws CameraAccessException when the camera could not be accessed.
|
||||
*/
|
||||
public static List<Map<String, Object>> getAvailableCameras(Activity activity)
|
||||
throws CameraAccessException {
|
||||
CameraManager cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
|
||||
String[] cameraNames = cameraManager.getCameraIdList();
|
||||
List<Map<String, Object>> cameras = new ArrayList<>();
|
||||
for (String cameraName : cameraNames) {
|
||||
int cameraId;
|
||||
try {
|
||||
cameraId = Integer.parseInt(cameraName, 10);
|
||||
} catch (NumberFormatException e) {
|
||||
cameraId = -1;
|
||||
}
|
||||
if (cameraId < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
HashMap<String, Object> details = new HashMap<>();
|
||||
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraName);
|
||||
details.put("name", cameraName);
|
||||
int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
|
||||
details.put("sensorOrientation", sensorOrientation);
|
||||
|
||||
int lensFacing = characteristics.get(CameraCharacteristics.LENS_FACING);
|
||||
switch (lensFacing) {
|
||||
case CameraMetadata.LENS_FACING_FRONT:
|
||||
details.put("lensFacing", "front");
|
||||
break;
|
||||
case CameraMetadata.LENS_FACING_BACK:
|
||||
details.put("lensFacing", "back");
|
||||
break;
|
||||
case CameraMetadata.LENS_FACING_EXTERNAL:
|
||||
details.put("lensFacing", "external");
|
||||
break;
|
||||
}
|
||||
cameras.add(details);
|
||||
}
|
||||
return cameras;
|
||||
}
|
||||
}
|
@ -0,0 +1,206 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.camera;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
|
||||
import io.flutter.plugin.common.BinaryMessenger;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import io.flutter.plugins.camera.features.autofocus.FocusMode;
|
||||
import io.flutter.plugins.camera.features.exposurelock.ExposureMode;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** Utility class that facilitates communication to the Flutter client */
|
||||
public class DartMessenger {
|
||||
@NonNull private final Handler handler;
|
||||
@Nullable private MethodChannel cameraChannel;
|
||||
@Nullable private MethodChannel deviceChannel;
|
||||
|
||||
/** Specifies the different device related message types. */
|
||||
enum DeviceEventType {
|
||||
/** Indicates the device's orientation has changed. */
|
||||
ORIENTATION_CHANGED("orientation_changed");
|
||||
private final String method;
|
||||
|
||||
DeviceEventType(String method) {
|
||||
this.method = method;
|
||||
}
|
||||
}
|
||||
|
||||
/** Specifies the different camera related message types. */
|
||||
enum CameraEventType {
|
||||
/** Indicates that an error occurred while interacting with the camera. */
|
||||
ERROR("error"),
|
||||
/** Indicates that the camera is closing. */
|
||||
CLOSING("camera_closing"),
|
||||
/** Indicates that the camera is initialized. */
|
||||
INITIALIZED("initialized");
|
||||
|
||||
private final String method;
|
||||
|
||||
/**
|
||||
* Converts the supplied method name to the matching {@link CameraEventType}.
|
||||
*
|
||||
* @param method name to be converted into a {@link CameraEventType}.
|
||||
*/
|
||||
CameraEventType(String method) {
|
||||
this.method = method;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of the {@link DartMessenger} class.
|
||||
*
|
||||
* @param messenger is the {@link BinaryMessenger} that is used to communicate with Flutter.
|
||||
* @param cameraId identifies the camera which is the source of the communication.
|
||||
* @param handler the handler used to manage the thread's message queue. This should always be a
|
||||
* handler managing the main thread since communication with Flutter should always happen on
|
||||
* the main thread. The handler is mainly supplied so it will be easier test this class.
|
||||
*/
|
||||
DartMessenger(BinaryMessenger messenger, long cameraId, @NonNull Handler handler) {
|
||||
cameraChannel =
|
||||
new MethodChannel(messenger, "plugins.flutter.io/camera_android/camera" + cameraId);
|
||||
deviceChannel = new MethodChannel(messenger, "plugins.flutter.io/camera_android/fromPlatform");
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the Flutter client informing the orientation of the device has been changed.
|
||||
*
|
||||
* @param orientation specifies the new orientation of the device.
|
||||
*/
|
||||
public void sendDeviceOrientationChangeEvent(PlatformChannel.DeviceOrientation orientation) {
|
||||
assert (orientation != null);
|
||||
this.send(
|
||||
DeviceEventType.ORIENTATION_CHANGED,
|
||||
new HashMap<String, Object>() {
|
||||
{
|
||||
put("orientation", CameraUtils.serializeDeviceOrientation(orientation));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the Flutter client informing that the camera has been initialized.
|
||||
*
|
||||
* @param previewWidth describes the preview width that is supported by the camera.
|
||||
* @param previewHeight describes the preview height that is supported by the camera.
|
||||
* @param exposureMode describes the current exposure mode that is set on the camera.
|
||||
* @param focusMode describes the current focus mode that is set on the camera.
|
||||
* @param exposurePointSupported indicates if the camera supports setting an exposure point.
|
||||
* @param focusPointSupported indicates if the camera supports setting a focus point.
|
||||
*/
|
||||
void sendCameraInitializedEvent(
|
||||
Integer previewWidth,
|
||||
Integer previewHeight,
|
||||
ExposureMode exposureMode,
|
||||
FocusMode focusMode,
|
||||
Boolean exposurePointSupported,
|
||||
Boolean focusPointSupported) {
|
||||
assert (previewWidth != null);
|
||||
assert (previewHeight != null);
|
||||
assert (exposureMode != null);
|
||||
assert (focusMode != null);
|
||||
assert (exposurePointSupported != null);
|
||||
assert (focusPointSupported != null);
|
||||
this.send(
|
||||
CameraEventType.INITIALIZED,
|
||||
new HashMap<String, Object>() {
|
||||
{
|
||||
put("previewWidth", previewWidth.doubleValue());
|
||||
put("previewHeight", previewHeight.doubleValue());
|
||||
put("exposureMode", exposureMode.toString());
|
||||
put("focusMode", focusMode.toString());
|
||||
put("exposurePointSupported", exposurePointSupported);
|
||||
put("focusPointSupported", focusPointSupported);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Sends a message to the Flutter client informing that the camera is closing. */
|
||||
void sendCameraClosingEvent() {
|
||||
send(CameraEventType.CLOSING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the Flutter client informing that an error occurred while interacting with
|
||||
* the camera.
|
||||
*
|
||||
* @param description contains details regarding the error that occurred.
|
||||
*/
|
||||
void sendCameraErrorEvent(@Nullable String description) {
|
||||
this.send(
|
||||
CameraEventType.ERROR,
|
||||
new HashMap<String, Object>() {
|
||||
{
|
||||
if (!TextUtils.isEmpty(description)) put("description", description);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void send(CameraEventType eventType) {
|
||||
send(eventType, new HashMap<>());
|
||||
}
|
||||
|
||||
private void send(CameraEventType eventType, Map<String, Object> args) {
|
||||
if (cameraChannel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
handler.post(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cameraChannel.invokeMethod(eventType.method, args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void send(DeviceEventType eventType) {
|
||||
send(eventType, new HashMap<>());
|
||||
}
|
||||
|
||||
private void send(DeviceEventType eventType, Map<String, Object> args) {
|
||||
if (deviceChannel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
handler.post(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
deviceChannel.invokeMethod(eventType.method, args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a success payload to a {@link MethodChannel.Result} on the main thread.
|
||||
*
|
||||
* @param payload The payload to send.
|
||||
*/
|
||||
public void finish(MethodChannel.Result result, Object payload) {
|
||||
handler.post(() -> result.success(payload));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an error payload to a {@link MethodChannel.Result} on the main thread.
|
||||
*
|
||||
* @param errorCode error code.
|
||||
* @param errorMessage error message.
|
||||
* @param errorDetails error details.
|
||||
*/
|
||||
public void error(
|
||||
MethodChannel.Result result,
|
||||
String errorCode,
|
||||
@Nullable String errorMessage,
|
||||
@Nullable Object errorDetails) {
|
||||
handler.post(() -> result.error(errorCode, errorMessage, errorDetails));
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.camera;
|
||||
|
||||
import android.media.Image;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/** Saves a JPEG {@link Image} into the specified {@link File}. */
|
||||
public class ImageSaver implements Runnable {
|
||||
|
||||
/** The JPEG image */
|
||||
private final Image image;
|
||||
|
||||
/** The file we save the image into. */
|
||||
private final File file;
|
||||
|
||||
/** Used to report the status of the save action. */
|
||||
private final Callback callback;
|
||||
|
||||
/**
|
||||
* Creates an instance of the ImageSaver runnable
|
||||
*
|
||||
* @param image - The image to save
|
||||
* @param file - The file to save the image to
|
||||
* @param callback - The callback that is run on completion, or when an error is encountered.
|
||||
*/
|
||||
ImageSaver(@NonNull Image image, @NonNull File file, @NonNull Callback callback) {
|
||||
this.image = image;
|
||||
this.file = file;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
|
||||
byte[] bytes = new byte[buffer.remaining()];
|
||||
buffer.get(bytes);
|
||||
FileOutputStream output = null;
|
||||
try {
|
||||
output = FileOutputStreamFactory.create(file);
|
||||
output.write(bytes);
|
||||
|
||||
callback.onComplete(file.getAbsolutePath());
|
||||
|
||||
} catch (IOException e) {
|
||||
callback.onError("IOError", "Failed saving image");
|
||||
} finally {
|
||||
image.close();
|
||||
if (null != output) {
|
||||
try {
|
||||
output.close();
|
||||
} catch (IOException e) {
|
||||
callback.onError("cameraAccess", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface for the callback that is passed to ImageSaver, for detecting completion or
|
||||
* failure of the image saving task.
|
||||
*/
|
||||
public interface Callback {
|
||||
/**
|
||||
* Called when the image file has been saved successfully.
|
||||
*
|
||||
* @param absolutePath - The absolute path of the file that was saved.
|
||||
*/
|
||||
void onComplete(String absolutePath);
|
||||
|
||||
/**
|
||||
* Called when an error is encountered while saving the image file.
|
||||
*
|
||||
* @param errorCode - The error code.
|
||||
* @param errorMessage - The human readable error message.
|
||||
*/
|
||||
void onError(String errorCode, String errorMessage);
|
||||
}
|
||||
|
||||
/** Factory class that assists in creating a {@link FileOutputStream} instance. */
|
||||
static class FileOutputStreamFactory {
|
||||
/**
|
||||
* Creates a new instance of the {@link FileOutputStream} class.
|
||||
*
|
||||
* <p>This method is visible for testing purposes only and should never be used outside this *
|
||||
* class.
|
||||
*
|
||||
* @param file - The file to create the output stream for
|
||||
* @return new instance of the {@link FileOutputStream} class.
|
||||
* @throws FileNotFoundException when the supplied file could not be found.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static FileOutputStream create(File file) throws FileNotFoundException {
|
||||
return new FileOutputStream(file);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,417 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.camera;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.hardware.camera2.CameraAccessException;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
|
||||
import io.flutter.plugin.common.BinaryMessenger;
|
||||
import io.flutter.plugin.common.EventChannel;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import io.flutter.plugin.common.MethodChannel.Result;
|
||||
import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry;
|
||||
import io.flutter.plugins.camera.features.CameraFeatureFactoryImpl;
|
||||
import io.flutter.plugins.camera.features.Point;
|
||||
import io.flutter.plugins.camera.features.autofocus.FocusMode;
|
||||
import io.flutter.plugins.camera.features.exposurelock.ExposureMode;
|
||||
import io.flutter.plugins.camera.features.flash.FlashMode;
|
||||
import io.flutter.plugins.camera.features.resolution.ResolutionPreset;
|
||||
import io.flutter.view.TextureRegistry;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler {
|
||||
private final Activity activity;
|
||||
private final BinaryMessenger messenger;
|
||||
private final CameraPermissions cameraPermissions;
|
||||
private final PermissionsRegistry permissionsRegistry;
|
||||
private final TextureRegistry textureRegistry;
|
||||
private final MethodChannel methodChannel;
|
||||
private final EventChannel imageStreamChannel;
|
||||
private @Nullable Camera camera;
|
||||
|
||||
MethodCallHandlerImpl(
|
||||
Activity activity,
|
||||
BinaryMessenger messenger,
|
||||
CameraPermissions cameraPermissions,
|
||||
PermissionsRegistry permissionsAdder,
|
||||
TextureRegistry textureRegistry) {
|
||||
this.activity = activity;
|
||||
this.messenger = messenger;
|
||||
this.cameraPermissions = cameraPermissions;
|
||||
this.permissionsRegistry = permissionsAdder;
|
||||
this.textureRegistry = textureRegistry;
|
||||
|
||||
methodChannel = new MethodChannel(messenger, "plugins.flutter.io/camera_android");
|
||||
imageStreamChannel =
|
||||
new EventChannel(messenger, "plugins.flutter.io/camera_android/imageStream");
|
||||
methodChannel.setMethodCallHandler(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) {
|
||||
switch (call.method) {
|
||||
case "availableCameras":
|
||||
try {
|
||||
result.success(CameraUtils.getAvailableCameras(activity));
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
case "create":
|
||||
{
|
||||
if (camera != null) {
|
||||
camera.close();
|
||||
}
|
||||
|
||||
cameraPermissions.requestPermissions(
|
||||
activity,
|
||||
permissionsRegistry,
|
||||
call.argument("enableAudio"),
|
||||
(String errCode, String errDesc) -> {
|
||||
if (errCode == null) {
|
||||
try {
|
||||
instantiateCamera(call, result);
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
} else {
|
||||
result.error(errCode, errDesc, null);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "initialize":
|
||||
{
|
||||
if (camera != null) {
|
||||
try {
|
||||
camera.open(call.argument("imageFormatGroup"));
|
||||
result.success(null);
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
} else {
|
||||
result.error(
|
||||
"cameraNotFound",
|
||||
"Camera not found. Please call the 'create' method before calling 'initialize'.",
|
||||
null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "takePicture":
|
||||
{
|
||||
camera.takePicture(result);
|
||||
break;
|
||||
}
|
||||
case "prepareForVideoRecording":
|
||||
{
|
||||
// This optimization is not required for Android.
|
||||
result.success(null);
|
||||
break;
|
||||
}
|
||||
case "startVideoRecording":
|
||||
{
|
||||
camera.startVideoRecording(
|
||||
result,
|
||||
Objects.equals(call.argument("enableStream"), true) ? imageStreamChannel : null);
|
||||
break;
|
||||
}
|
||||
case "stopVideoRecording":
|
||||
{
|
||||
camera.stopVideoRecording(result);
|
||||
break;
|
||||
}
|
||||
case "pauseVideoRecording":
|
||||
{
|
||||
camera.pauseVideoRecording(result);
|
||||
break;
|
||||
}
|
||||
case "resumeVideoRecording":
|
||||
{
|
||||
camera.resumeVideoRecording(result);
|
||||
break;
|
||||
}
|
||||
case "setFlashMode":
|
||||
{
|
||||
String modeStr = call.argument("mode");
|
||||
FlashMode mode = FlashMode.getValueForString(modeStr);
|
||||
if (mode == null) {
|
||||
result.error("setFlashModeFailed", "Unknown flash mode " + modeStr, null);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
camera.setFlashMode(result, mode);
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "setExposureMode":
|
||||
{
|
||||
String modeStr = call.argument("mode");
|
||||
ExposureMode mode = ExposureMode.getValueForString(modeStr);
|
||||
if (mode == null) {
|
||||
result.error("setExposureModeFailed", "Unknown exposure mode " + modeStr, null);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
camera.setExposureMode(result, mode);
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "setExposurePoint":
|
||||
{
|
||||
Boolean reset = call.argument("reset");
|
||||
Double x = null;
|
||||
Double y = null;
|
||||
if (reset == null || !reset) {
|
||||
x = call.argument("x");
|
||||
y = call.argument("y");
|
||||
}
|
||||
try {
|
||||
camera.setExposurePoint(result, new Point(x, y));
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "getMinExposureOffset":
|
||||
{
|
||||
try {
|
||||
result.success(camera.getMinExposureOffset());
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "getMaxExposureOffset":
|
||||
{
|
||||
try {
|
||||
result.success(camera.getMaxExposureOffset());
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "getExposureOffsetStepSize":
|
||||
{
|
||||
try {
|
||||
result.success(camera.getExposureOffsetStepSize());
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "setExposureOffset":
|
||||
{
|
||||
try {
|
||||
camera.setExposureOffset(result, call.argument("offset"));
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "setFocusMode":
|
||||
{
|
||||
String modeStr = call.argument("mode");
|
||||
FocusMode mode = FocusMode.getValueForString(modeStr);
|
||||
if (mode == null) {
|
||||
result.error("setFocusModeFailed", "Unknown focus mode " + modeStr, null);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
camera.setFocusMode(result, mode);
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "setFocusPoint":
|
||||
{
|
||||
Boolean reset = call.argument("reset");
|
||||
Double x = null;
|
||||
Double y = null;
|
||||
if (reset == null || !reset) {
|
||||
x = call.argument("x");
|
||||
y = call.argument("y");
|
||||
}
|
||||
try {
|
||||
camera.setFocusPoint(result, new Point(x, y));
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "startImageStream":
|
||||
{
|
||||
try {
|
||||
camera.startPreviewWithImageStream(imageStreamChannel);
|
||||
result.success(null);
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "stopImageStream":
|
||||
{
|
||||
try {
|
||||
camera.startPreview();
|
||||
result.success(null);
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "getMaxZoomLevel":
|
||||
{
|
||||
assert camera != null;
|
||||
|
||||
try {
|
||||
float maxZoomLevel = camera.getMaxZoomLevel();
|
||||
result.success(maxZoomLevel);
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "getMinZoomLevel":
|
||||
{
|
||||
assert camera != null;
|
||||
|
||||
try {
|
||||
float minZoomLevel = camera.getMinZoomLevel();
|
||||
result.success(minZoomLevel);
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "setZoomLevel":
|
||||
{
|
||||
assert camera != null;
|
||||
|
||||
Double zoom = call.argument("zoom");
|
||||
|
||||
if (zoom == null) {
|
||||
result.error(
|
||||
"ZOOM_ERROR", "setZoomLevel is called without specifying a zoom level.", null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
camera.setZoomLevel(result, zoom.floatValue());
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "lockCaptureOrientation":
|
||||
{
|
||||
PlatformChannel.DeviceOrientation orientation =
|
||||
CameraUtils.deserializeDeviceOrientation(call.argument("orientation"));
|
||||
|
||||
try {
|
||||
camera.lockCaptureOrientation(orientation);
|
||||
result.success(null);
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "unlockCaptureOrientation":
|
||||
{
|
||||
try {
|
||||
camera.unlockCaptureOrientation();
|
||||
result.success(null);
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "pausePreview":
|
||||
{
|
||||
try {
|
||||
camera.pausePreview();
|
||||
result.success(null);
|
||||
} catch (Exception e) {
|
||||
handleException(e, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "resumePreview":
|
||||
{
|
||||
camera.resumePreview();
|
||||
result.success(null);
|
||||
break;
|
||||
}
|
||||
case "dispose":
|
||||
{
|
||||
if (camera != null) {
|
||||
camera.dispose();
|
||||
}
|
||||
result.success(null);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
result.notImplemented();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void stopListening() {
|
||||
methodChannel.setMethodCallHandler(null);
|
||||
}
|
||||
|
||||
private void instantiateCamera(MethodCall call, Result result) throws CameraAccessException {
|
||||
String cameraName = call.argument("cameraName");
|
||||
String preset = call.argument("resolutionPreset");
|
||||
boolean enableAudio = call.argument("enableAudio");
|
||||
|
||||
TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture =
|
||||
textureRegistry.createSurfaceTexture();
|
||||
DartMessenger dartMessenger =
|
||||
new DartMessenger(
|
||||
messenger, flutterSurfaceTexture.id(), new Handler(Looper.getMainLooper()));
|
||||
CameraProperties cameraProperties =
|
||||
new CameraPropertiesImpl(cameraName, CameraUtils.getCameraManager(activity));
|
||||
ResolutionPreset resolutionPreset = ResolutionPreset.valueOf(preset);
|
||||
|
||||
camera =
|
||||
new Camera(
|
||||
activity,
|
||||
flutterSurfaceTexture,
|
||||
new CameraFeatureFactoryImpl(),
|
||||
dartMessenger,
|
||||
cameraProperties,
|
||||
resolutionPreset,
|
||||
enableAudio);
|
||||
|
||||
Map<String, Object> reply = new HashMap<>();
|
||||
reply.put("cameraId", flutterSurfaceTexture.id());
|
||||
result.success(reply);
|
||||
}
|
||||
|
||||
// We move catching CameraAccessException out of onMethodCall because it causes a crash
|
||||
// on plugin registration for sdks incompatible with Camera2 (< 21). We want this plugin to
|
||||
// to be able to compile with <21 sdks for apps that want the camera and support earlier version.
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private void handleException(Exception exception, Result result) {
|
||||
if (exception instanceof CameraAccessException) {
|
||||
result.error("CameraAccess", exception.getMessage(), null);
|
||||
return;
|
||||
}
|
||||
|
||||
// CameraAccessException can not be cast to a RuntimeException.
|
||||
throw (RuntimeException) exception;
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.camera.features;
|
||||
|
||||
import android.hardware.camera2.CaptureRequest;
|
||||
import androidx.annotation.NonNull;
|
||||
import io.flutter.plugins.camera.CameraProperties;
|
||||
|
||||
/**
|
||||
* An interface describing a feature in the camera. This holds a setting value of type T and must
|
||||
* implement a means to check if this setting is supported by the current camera properties. It also
|
||||
* must implement a builder update method which will update a given capture request builder for this
|
||||
* feature's current setting value.
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
public abstract class CameraFeature<T> {
|
||||
|
||||
protected final CameraProperties cameraProperties;
|
||||
|
||||
protected CameraFeature(@NonNull CameraProperties cameraProperties) {
|
||||
this.cameraProperties = cameraProperties;
|
||||
}
|
||||
|
||||
/** Debug name for this feature. */
|
||||
public abstract String getDebugName();
|
||||
|
||||
/**
|
||||
* Gets the current value of this feature's setting.
|
||||
*
|
||||
* @return <T> Current value of this feature's setting.
|
||||
*/
|
||||
public abstract T getValue();
|
||||
|
||||
/**
|
||||
* Sets a new value for this feature's setting.
|
||||
*
|
||||
* @param value New value for this feature's setting.
|
||||
*/
|
||||
public abstract void setValue(T value);
|
||||
|
||||
/**
|
||||
* Returns whether or not this feature is supported.
|
||||
*
|
||||
* <p>When the feature is not supported any {@see #value} is simply ignored by the camera plugin.
|
||||
*
|
||||
* @return boolean Whether or not this feature is supported.
|
||||
*/
|
||||
public abstract boolean checkIsSupported();
|
||||
|
||||
/**
|
||||
* Updates the setting in a provided {@see android.hardware.camera2.CaptureRequest.Builder}.
|
||||
*
|
||||
* @param requestBuilder A {@see android.hardware.camera2.CaptureRequest.Builder} instance used to
|
||||
* configure the settings and outputs needed to capture a single image from the camera device.
|
||||
*/
|
||||
public abstract void updateBuilder(CaptureRequest.Builder requestBuilder);
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.camera.features;
|
||||
|
||||
import android.app.Activity;
|
||||
import androidx.annotation.NonNull;
|
||||
import io.flutter.plugins.camera.CameraProperties;
|
||||
import io.flutter.plugins.camera.DartMessenger;
|
||||
import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature;
|
||||
import io.flutter.plugins.camera.features.exposurelock.ExposureLockFeature;
|
||||
import io.flutter.plugins.camera.features.exposureoffset.ExposureOffsetFeature;
|
||||
import io.flutter.plugins.camera.features.exposurepoint.ExposurePointFeature;
|
||||
import io.flutter.plugins.camera.features.flash.FlashFeature;
|
||||
import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature;
|
||||
import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature;
|
||||
import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature;
|
||||
import io.flutter.plugins.camera.features.resolution.ResolutionFeature;
|
||||
import io.flutter.plugins.camera.features.resolution.ResolutionPreset;
|
||||
import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature;
|
||||
import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature;
|
||||
|
||||
/**
|
||||
* Factory for creating the supported feature implementation controlling different aspects of the
|
||||
* {@link android.hardware.camera2.CaptureRequest}.
|
||||
*/
|
||||
public interface CameraFeatureFactory {
|
||||
|
||||
/**
|
||||
* Creates a new instance of the auto focus feature.
|
||||
*
|
||||
* @param cameraProperties instance of the CameraProperties class containing information about the
|
||||
* cameras features.
|
||||
* @param recordingVideo indicates if the camera is currently recording.
|
||||
* @return newly created instance of the AutoFocusFeature class.
|
||||
*/
|
||||
AutoFocusFeature createAutoFocusFeature(
|
||||
@NonNull CameraProperties cameraProperties, boolean recordingVideo);
|
||||
|
||||
/**
|
||||
* Creates a new instance of the exposure lock feature.
|
||||
*
|
||||
* @param cameraProperties instance of the CameraProperties class containing information about the
|
||||
* cameras features.
|
||||
* @return newly created instance of the ExposureLockFeature class.
|
||||
*/
|
||||
ExposureLockFeature createExposureLockFeature(@NonNull CameraProperties cameraProperties);
|
||||
|
||||
/**
|
||||
* Creates a new instance of the exposure offset feature.
|
||||
*
|
||||
* @param cameraProperties instance of the CameraProperties class containing information about the
|
||||
* cameras features.
|
||||
* @return newly created instance of the ExposureOffsetFeature class.
|
||||
*/
|
||||
ExposureOffsetFeature createExposureOffsetFeature(@NonNull CameraProperties cameraProperties);
|
||||
|
||||
/**
|
||||
* Creates a new instance of the flash feature.
|
||||
*
|
||||
* @param cameraProperties instance of the CameraProperties class containing information about the
|
||||
* cameras features.
|
||||
* @return newly created instance of the FlashFeature class.
|
||||
*/
|
||||
FlashFeature createFlashFeature(@NonNull CameraProperties cameraProperties);
|
||||
|
||||
/**
|
||||
* Creates a new instance of the resolution feature.
|
||||
*
|
||||
* @param cameraProperties instance of the CameraProperties class containing information about the
|
||||
* cameras features.
|
||||
* @param initialSetting initial resolution preset.
|
||||
* @param cameraName the name of the camera which can be used to identify the camera device.
|
||||
* @return newly created instance of the ResolutionFeature class.
|
||||
*/
|
||||
ResolutionFeature createResolutionFeature(
|
||||
@NonNull CameraProperties cameraProperties,
|
||||
ResolutionPreset initialSetting,
|
||||
String cameraName);
|
||||
|
||||
/**
|
||||
* Creates a new instance of the focus point feature.
|
||||
*
|
||||
* @param cameraProperties instance of the CameraProperties class containing information about the
|
||||
* cameras features.
|
||||
* @param sensorOrientationFeature instance of the SensorOrientationFeature class containing
|
||||
* information about the sensor and device orientation.
|
||||
* @return newly created instance of the FocusPointFeature class.
|
||||
*/
|
||||
FocusPointFeature createFocusPointFeature(
|
||||
@NonNull CameraProperties cameraProperties,
|
||||
@NonNull SensorOrientationFeature sensorOrientationFeature);
|
||||
|
||||
/**
|
||||
* Creates a new instance of the FPS range feature.
|
||||
*
|
||||
* @param cameraProperties instance of the CameraProperties class containing information about the
|
||||
* cameras features.
|
||||
* @return newly created instance of the FpsRangeFeature class.
|
||||
*/
|
||||
FpsRangeFeature createFpsRangeFeature(@NonNull CameraProperties cameraProperties);
|
||||
|
||||
/**
|
||||
* Creates a new instance of the sensor orientation feature.
|
||||
*
|
||||
* @param cameraProperties instance of the CameraProperties class containing information about the
|
||||
* cameras features.
|
||||
* @param activity current activity associated with the camera plugin.
|
||||
* @param dartMessenger instance of the DartMessenger class, used to send state updates back to
|
||||
* Dart.
|
||||
* @return newly created instance of the SensorOrientationFeature class.
|
||||
*/
|
||||
SensorOrientationFeature createSensorOrientationFeature(
|
||||
@NonNull CameraProperties cameraProperties,
|
||||
@NonNull Activity activity,
|
||||
@NonNull DartMessenger dartMessenger);
|
||||
|
||||
/**
|
||||
* Creates a new instance of the zoom level feature.
|
||||
*
|
||||
* @param cameraProperties instance of the CameraProperties class containing information about the
|
||||
* cameras features.
|
||||
* @return newly created instance of the ZoomLevelFeature class.
|
||||
*/
|
||||
ZoomLevelFeature createZoomLevelFeature(@NonNull CameraProperties cameraProperties);
|
||||
|
||||
/**
|
||||
* Creates a new instance of the exposure point feature.
|
||||
*
|
||||
* @param cameraProperties instance of the CameraProperties class containing information about the
|
||||
* cameras features.
|
||||
* @param sensorOrientationFeature instance of the SensorOrientationFeature class containing
|
||||
* information about the sensor and device orientation.
|
||||
* @return newly created instance of the ExposurePointFeature class.
|
||||
*/
|
||||
ExposurePointFeature createExposurePointFeature(
|
||||
@NonNull CameraProperties cameraProperties,
|
||||
@NonNull SensorOrientationFeature sensorOrientationFeature);
|
||||
|
||||
/**
|
||||
* Creates a new instance of the noise reduction feature.
|
||||
*
|
||||
* @param cameraProperties instance of the CameraProperties class containing information about the
|
||||
* cameras features.
|
||||
* @return newly created instance of the NoiseReductionFeature class.
|
||||
*/
|
||||
NoiseReductionFeature createNoiseReductionFeature(@NonNull CameraProperties cameraProperties);
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.camera.features;
|
||||
|
||||
import android.app.Activity;
|
||||
import androidx.annotation.NonNull;
|
||||
import io.flutter.plugins.camera.CameraProperties;
|
||||
import io.flutter.plugins.camera.DartMessenger;
|
||||
import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature;
|
||||
import io.flutter.plugins.camera.features.exposurelock.ExposureLockFeature;
|
||||
import io.flutter.plugins.camera.features.exposureoffset.ExposureOffsetFeature;
|
||||
import io.flutter.plugins.camera.features.exposurepoint.ExposurePointFeature;
|
||||
import io.flutter.plugins.camera.features.flash.FlashFeature;
|
||||
import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature;
|
||||
import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature;
|
||||
import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature;
|
||||
import io.flutter.plugins.camera.features.resolution.ResolutionFeature;
|
||||
import io.flutter.plugins.camera.features.resolution.ResolutionPreset;
|
||||
import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature;
|
||||
import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link CameraFeatureFactory} interface creating the supported feature
|
||||
* implementation controlling different aspects of the {@link
|
||||
* android.hardware.camera2.CaptureRequest}.
|
||||
*/
|
||||
public class CameraFeatureFactoryImpl implements CameraFeatureFactory {
|
||||
|
||||
@Override
|
||||
public AutoFocusFeature createAutoFocusFeature(
|
||||
@NonNull CameraProperties cameraProperties, boolean recordingVideo) {
|
||||
return new AutoFocusFeature(cameraProperties, recordingVideo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExposureLockFeature createExposureLockFeature(@NonNull CameraProperties cameraProperties) {
|
||||
return new ExposureLockFeature(cameraProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExposureOffsetFeature createExposureOffsetFeature(
|
||||
@NonNull CameraProperties cameraProperties) {
|
||||
return new ExposureOffsetFeature(cameraProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlashFeature createFlashFeature(@NonNull CameraProperties cameraProperties) {
|
||||
return new FlashFeature(cameraProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResolutionFeature createResolutionFeature(
|
||||
@NonNull CameraProperties cameraProperties,
|
||||
ResolutionPreset initialSetting,
|
||||
String cameraName) {
|
||||
return new ResolutionFeature(cameraProperties, initialSetting, cameraName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FocusPointFeature createFocusPointFeature(
|
||||
@NonNull CameraProperties cameraProperties,
|
||||
@NonNull SensorOrientationFeature sensorOrientationFeature) {
|
||||
return new FocusPointFeature(cameraProperties, sensorOrientationFeature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FpsRangeFeature createFpsRangeFeature(@NonNull CameraProperties cameraProperties) {
|
||||
return new FpsRangeFeature(cameraProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SensorOrientationFeature createSensorOrientationFeature(
|
||||
@NonNull CameraProperties cameraProperties,
|
||||
@NonNull Activity activity,
|
||||
@NonNull DartMessenger dartMessenger) {
|
||||
return new SensorOrientationFeature(cameraProperties, activity, dartMessenger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZoomLevelFeature createZoomLevelFeature(@NonNull CameraProperties cameraProperties) {
|
||||
return new ZoomLevelFeature(cameraProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExposurePointFeature createExposurePointFeature(
|
||||
@NonNull CameraProperties cameraProperties,
|
||||
@NonNull SensorOrientationFeature sensorOrientationFeature) {
|
||||
return new ExposurePointFeature(cameraProperties, sensorOrientationFeature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NoiseReductionFeature createNoiseReductionFeature(
|
||||
@NonNull CameraProperties cameraProperties) {
|
||||
return new NoiseReductionFeature(cameraProperties);
|
||||
}
|
||||
}
|
@ -0,0 +1,285 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.camera.features;
|
||||
|
||||
import android.app.Activity;
|
||||
import io.flutter.plugins.camera.CameraProperties;
|
||||
import io.flutter.plugins.camera.DartMessenger;
|
||||
import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature;
|
||||
import io.flutter.plugins.camera.features.exposurelock.ExposureLockFeature;
|
||||
import io.flutter.plugins.camera.features.exposureoffset.ExposureOffsetFeature;
|
||||
import io.flutter.plugins.camera.features.exposurepoint.ExposurePointFeature;
|
||||
import io.flutter.plugins.camera.features.flash.FlashFeature;
|
||||
import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature;
|
||||
import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature;
|
||||
import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature;
|
||||
import io.flutter.plugins.camera.features.resolution.ResolutionFeature;
|
||||
import io.flutter.plugins.camera.features.resolution.ResolutionPreset;
|
||||
import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature;
|
||||
import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* These are all of our available features in the camera. Used in the Camera to access all features
|
||||
* in a simpler way.
|
||||
*/
|
||||
public class CameraFeatures {
|
||||
private static final String AUTO_FOCUS = "AUTO_FOCUS";
|
||||
private static final String EXPOSURE_LOCK = "EXPOSURE_LOCK";
|
||||
private static final String EXPOSURE_OFFSET = "EXPOSURE_OFFSET";
|
||||
private static final String EXPOSURE_POINT = "EXPOSURE_POINT";
|
||||
private static final String FLASH = "FLASH";
|
||||
private static final String FOCUS_POINT = "FOCUS_POINT";
|
||||
private static final String FPS_RANGE = "FPS_RANGE";
|
||||
private static final String NOISE_REDUCTION = "NOISE_REDUCTION";
|
||||
private static final String REGION_BOUNDARIES = "REGION_BOUNDARIES";
|
||||
private static final String RESOLUTION = "RESOLUTION";
|
||||
private static final String SENSOR_ORIENTATION = "SENSOR_ORIENTATION";
|
||||
private static final String ZOOM_LEVEL = "ZOOM_LEVEL";
|
||||
|
||||
public static CameraFeatures init(
|
||||
CameraFeatureFactory cameraFeatureFactory,
|
||||
CameraProperties cameraProperties,
|
||||
Activity activity,
|
||||
DartMessenger dartMessenger,
|
||||
ResolutionPreset resolutionPreset) {
|
||||
CameraFeatures cameraFeatures = new CameraFeatures();
|
||||
cameraFeatures.setAutoFocus(
|
||||
cameraFeatureFactory.createAutoFocusFeature(cameraProperties, false));
|
||||
cameraFeatures.setExposureLock(
|
||||
cameraFeatureFactory.createExposureLockFeature(cameraProperties));
|
||||
cameraFeatures.setExposureOffset(
|
||||
cameraFeatureFactory.createExposureOffsetFeature(cameraProperties));
|
||||
SensorOrientationFeature sensorOrientationFeature =
|
||||
cameraFeatureFactory.createSensorOrientationFeature(
|
||||
cameraProperties, activity, dartMessenger);
|
||||
cameraFeatures.setSensorOrientation(sensorOrientationFeature);
|
||||
cameraFeatures.setExposurePoint(
|
||||
cameraFeatureFactory.createExposurePointFeature(
|
||||
cameraProperties, sensorOrientationFeature));
|
||||
cameraFeatures.setFlash(cameraFeatureFactory.createFlashFeature(cameraProperties));
|
||||
cameraFeatures.setFocusPoint(
|
||||
cameraFeatureFactory.createFocusPointFeature(cameraProperties, sensorOrientationFeature));
|
||||
cameraFeatures.setFpsRange(cameraFeatureFactory.createFpsRangeFeature(cameraProperties));
|
||||
cameraFeatures.setNoiseReduction(
|
||||
cameraFeatureFactory.createNoiseReductionFeature(cameraProperties));
|
||||
cameraFeatures.setResolution(
|
||||
cameraFeatureFactory.createResolutionFeature(
|
||||
cameraProperties, resolutionPreset, cameraProperties.getCameraName()));
|
||||
cameraFeatures.setZoomLevel(cameraFeatureFactory.createZoomLevelFeature(cameraProperties));
|
||||
return cameraFeatures;
|
||||
}
|
||||
|
||||
private Map<String, CameraFeature> featureMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Gets a collection of all features that have been set.
|
||||
*
|
||||
* @return A collection of all features that have been set.
|
||||
*/
|
||||
public Collection<CameraFeature> getAllFeatures() {
|
||||
return this.featureMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the auto focus feature if it has been set.
|
||||
*
|
||||
* @return the auto focus feature.
|
||||
*/
|
||||
public AutoFocusFeature getAutoFocus() {
|
||||
return (AutoFocusFeature) featureMap.get(AUTO_FOCUS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the instance of the auto focus feature.
|
||||
*
|
||||
* @param autoFocus the {@link AutoFocusFeature} instance to set.
|
||||
*/
|
||||
public void setAutoFocus(AutoFocusFeature autoFocus) {
|
||||
this.featureMap.put(AUTO_FOCUS, autoFocus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the exposure lock feature if it has been set.
|
||||
*
|
||||
* @return the exposure lock feature.
|
||||
*/
|
||||
public ExposureLockFeature getExposureLock() {
|
||||
return (ExposureLockFeature) featureMap.get(EXPOSURE_LOCK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the instance of the exposure lock feature.
|
||||
*
|
||||
* @param exposureLock the {@link ExposureLockFeature} instance to set.
|
||||
*/
|
||||
public void setExposureLock(ExposureLockFeature exposureLock) {
|
||||
this.featureMap.put(EXPOSURE_LOCK, exposureLock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the exposure offset feature if it has been set.
|
||||
*
|
||||
* @return the exposure offset feature.
|
||||
*/
|
||||
public ExposureOffsetFeature getExposureOffset() {
|
||||
return (ExposureOffsetFeature) featureMap.get(EXPOSURE_OFFSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the instance of the exposure offset feature.
|
||||
*
|
||||
* @param exposureOffset the {@link ExposureOffsetFeature} instance to set.
|
||||
*/
|
||||
public void setExposureOffset(ExposureOffsetFeature exposureOffset) {
|
||||
this.featureMap.put(EXPOSURE_OFFSET, exposureOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the exposure point feature if it has been set.
|
||||
*
|
||||
* @return the exposure point feature.
|
||||
*/
|
||||
public ExposurePointFeature getExposurePoint() {
|
||||
return (ExposurePointFeature) featureMap.get(EXPOSURE_POINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the instance of the exposure point feature.
|
||||
*
|
||||
* @param exposurePoint the {@link ExposurePointFeature} instance to set.
|
||||
*/
|
||||
public void setExposurePoint(ExposurePointFeature exposurePoint) {
|
||||
this.featureMap.put(EXPOSURE_POINT, exposurePoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the flash feature if it has been set.
|
||||
*
|
||||
* @return the flash feature.
|
||||
*/
|
||||
public FlashFeature getFlash() {
|
||||
return (FlashFeature) featureMap.get(FLASH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the instance of the flash feature.
|
||||
*
|
||||
* @param flash the {@link FlashFeature} instance to set.
|
||||
*/
|
||||
public void setFlash(FlashFeature flash) {
|
||||
this.featureMap.put(FLASH, flash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the focus point feature if it has been set.
|
||||
*
|
||||
* @return the focus point feature.
|
||||
*/
|
||||
public FocusPointFeature getFocusPoint() {
|
||||
return (FocusPointFeature) featureMap.get(FOCUS_POINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the instance of the focus point feature.
|
||||
*
|
||||
* @param focusPoint the {@link FocusPointFeature} instance to set.
|
||||
*/
|
||||
public void setFocusPoint(FocusPointFeature focusPoint) {
|
||||
this.featureMap.put(FOCUS_POINT, focusPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fps range feature if it has been set.
|
||||
*
|
||||
* @return the fps range feature.
|
||||
*/
|
||||
public FpsRangeFeature getFpsRange() {
|
||||
return (FpsRangeFeature) featureMap.get(FPS_RANGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the instance of the fps range feature.
|
||||
*
|
||||
* @param fpsRange the {@link FpsRangeFeature} instance to set.
|
||||
*/
|
||||
public void setFpsRange(FpsRangeFeature fpsRange) {
|
||||
this.featureMap.put(FPS_RANGE, fpsRange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the noise reduction feature if it has been set.
|
||||
*
|
||||
* @return the noise reduction feature.
|
||||
*/
|
||||
public NoiseReductionFeature getNoiseReduction() {
|
||||
return (NoiseReductionFeature) featureMap.get(NOISE_REDUCTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the instance of the noise reduction feature.
|
||||
*
|
||||
* @param noiseReduction the {@link NoiseReductionFeature} instance to set.
|
||||
*/
|
||||
public void setNoiseReduction(NoiseReductionFeature noiseReduction) {
|
||||
this.featureMap.put(NOISE_REDUCTION, noiseReduction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the resolution feature if it has been set.
|
||||
*
|
||||
* @return the resolution feature.
|
||||
*/
|
||||
public ResolutionFeature getResolution() {
|
||||
return (ResolutionFeature) featureMap.get(RESOLUTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the instance of the resolution feature.
|
||||
*
|
||||
* @param resolution the {@link ResolutionFeature} instance to set.
|
||||
*/
|
||||
public void setResolution(ResolutionFeature resolution) {
|
||||
this.featureMap.put(RESOLUTION, resolution);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sensor orientation feature if it has been set.
|
||||
*
|
||||
* @return the sensor orientation feature.
|
||||
*/
|
||||
public SensorOrientationFeature getSensorOrientation() {
|
||||
return (SensorOrientationFeature) featureMap.get(SENSOR_ORIENTATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the instance of the sensor orientation feature.
|
||||
*
|
||||
* @param sensorOrientation the {@link SensorOrientationFeature} instance to set.
|
||||
*/
|
||||
public void setSensorOrientation(SensorOrientationFeature sensorOrientation) {
|
||||
this.featureMap.put(SENSOR_ORIENTATION, sensorOrientation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the zoom level feature if it has been set.
|
||||
*
|
||||
* @return the zoom level feature.
|
||||
*/
|
||||
public ZoomLevelFeature getZoomLevel() {
|
||||
return (ZoomLevelFeature) featureMap.get(ZOOM_LEVEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the instance of the zoom level feature.
|
||||
*
|
||||
* @param zoomLevel the {@link ZoomLevelFeature} instance to set.
|
||||
*/
|
||||
public void setZoomLevel(ZoomLevelFeature zoomLevel) {
|
||||
this.featureMap.put(ZOOM_LEVEL, zoomLevel);
|
||||
}
|
||||
}
|