feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,2 @@
// @generated by expo-module-scripts
module.exports = require('expo-module-scripts/eslintrc.base.js');

View File

@@ -0,0 +1,545 @@
# Changelog
## Unpublished
### 🛠 Breaking changes
### 🎉 New features
### 🐛 Bug fixes
### 💡 Others
## 15.0.16 — 2024-09-13
_This version does not introduce any user-facing changes._
## 15.0.15 — 2024-08-24
### 🎉 New features
- On `Android`, add support for setting the aspect ratio of the camera. ([#29822](https://github.com/expo/expo/pull/29822) by [@alanjhughes](https://github.com/alanjhughes))
- Support mirroring the output of the front facing camera. ([#30430](https://github.com/expo/expo/pull/30430) by [@alanjhughes](https://github.com/alanjhughes))
- On `Android`, support mirroring video when using front facing camera. Control mirroring with a prop. Deprecate `mirror` option on `takePictureAsync` and `recordAsync`. ([#30548](https://github.com/expo/expo/pull/30548) by [@alanjhughes](https://github.com/alanjhughes))
- Support pausing and resuming the preview. ([#30666](https://github.com/expo/expo/pull/30666) by [@alanjhughes](https://github.com/alanjhughes))
- Add `active` prop to stop and start the camera session. Useful with navigation where the camera is can still be active on a previous screen. ([#30802](https://github.com/expo/expo/pull/30802) by [@alanjhughes](https://github.com/alanjhughes))
### 🐛 Bug fixes
- On `iOS`, correctly stop the session when the `CameraView` is removed. ([#30580](https://github.com/expo/expo/pull/30580) by [@alanjhughes](https://github.com/alanjhughes))
- Fix rendering order of child views. ([#30759](https://github.com/expo/expo/pull/30759) by [@alanjhughes](https://github.com/alanjhughes))
- Set the previews `scaleType` when the aspect ratio is set. ([#30831](https://github.com/expo/expo/pull/30831) by [@alanjhughes](https://github.com/alanjhughes))
- On Android, fix selecting `pictureSize`. ([#31093](https://github.com/expo/expo/pull/31093) by [@alanjhughes](https://github.com/alanjhughes))
## 15.0.14 — 2024-07-16
_This version does not introduce any user-facing changes._
## 15.0.13 — 2024-07-03
### 🐛 Bug fixes
- On `iOS`, fix calling `takePicture` from the simulator. ([#30103](https://github.com/expo/expo/pull/30103) by [@alanjhughes](https://github.com/alanjhughes))
- On `iOS`, fix touch interactions when using gesture handler. ([#30338](https://github.com/expo/expo/pull/30338) by [@alanjhughes](https://github.com/alanjhughes))
## 15.0.12 — 2024-06-20
### 🐛 Bug fixes
- Prevent shutter sound when device volume is muted. ([#29638](https://github.com/expo/expo/pull/29638) by [@frederikocmr](https://github.com/frederikocmr))
- On `Android`, correct image orientation when `exif` is set to true in `takePictureAsync`. ([#29712](https://github.com/expo/expo/pull/29712) by [@alanjhughes](https://github.com/alanjhughes))
## 15.0.11 — 2024-06-13
### 🐛 Bug fixes
- Fix return type of scanFromURLAsync. ([#29547](https://github.com/expo/expo/pull/29547) by [@janicduplessis](https://github.com/janicduplessis))
- On `web`, fix missing function "getCapabilities" in Firefox. ([#28947](https://github.com/expo/expo/pull/28947) by [@miso-belica](https://github.com/miso-belica))
- Allow starting the camera with the torch enabled. ([#29217](https://github.com/expo/expo/pull/29217) by [@alanjhughes](https://github.com/alanjhughes))
- On `iOS`, return the correct orientation in the exif data. ([#29681](https://github.com/expo/expo/pull/29681) by [@alanjhughes](https://github.com/alanjhughes))
### 💡 Others
- Make the returned `type` in `BarCodeScanningResult` consistent. ([#29421](https://github.com/expo/expo/pull/29421)) by [@alanjhughes](https://github.com/alanjhughes))
## 15.0.10 — 2024-05-29
### 🐛 Bug fixes
- On `iOS`, prevent a crash when rendering the view on a simulator. ([#28911](https://github.com/expo/expo/pull/28911) by [@alanjhughes](https://github.com/alanjhughes))
- On `iOS`, fix incorrect orientation when taking pictures in landscape. ([#28917](https://github.com/expo/expo/pull/28917) by [@alanjhughes](https://github.com/alanjhughes))
- On `iOS`, set previewLayer on scanner to get correct dimensions. ([#28931](https://github.com/expo/expo/pull/28931) by [@alanjhughes](https://github.com/alanjhughes))
- On `Android`, correctly handle orientation when landscape pictures are rendered. ([#28929](https://github.com/expo/expo/pull/28929) by [@alanjhughes](https://github.com/alanjhughes))
## 15.0.8 — 2024-05-13
### 🐛 Bug fixes
- On `iOS`, fix dead frames when switching from picture to video. ([#28783](https://github.com/expo/expo/pull/28783) by [@alanjhughes](https://github.com/alanjhughes))
## 15.0.7 — 2024-05-13
### 💡 Others
- Remove unused property `interval` from `BarcodeSettings`. ([#28760](https://github.com/expo/expo/pull/28760) by [@alanjhughes](https://github.com/alanjhughes))
## 15.0.6 — 2024-05-10
### 🐛 Bug fixes
- Fix documention for `maxDuration` in `CameraRecordingOptions`. ([#28749](https://github.com/expo/expo/pull/28749) by [@alanjhughes](https://github.com/alanjhughes))
## 15.0.5 — 2024-05-09
### 🐛 Bug fixes
- On `iOS`, fix `ean13` barcodes not returning data. ([#28674](https://github.com/expo/expo/pull/28674) by [@alanjhughes](https://github.com/alanjhughes))
## 15.0.4 — 2024-05-07
### 🎉 New features
- Add `autoFocus` prop to allow setting the device focus mode. ([#28650](https://github.com/expo/expo/pull/28650) by [@alanjhughes](https://github.com/alanjhughes))
## 15.0.3 — 2024-04-29
### 🎉 New features
- Support scanning barcodes from a provided image URL. ([#28445](https://github.com/expo/expo/pull/28445) by [@alanjhughes](https://github.com/alanjhughes))
## 15.0.2 — 2024-04-24
### 🐛 Bug fixes
- On `iOS`, fixed regression where recording a video captures dark frames. Reduced frequency of camera initialization. ([#28427](https://github.com/expo/expo/pull/28427) by [@alanjhughes](https://github.com/alanjhughes))
## 15.0.1 — 2024-04-23
_This version does not introduce any user-facing changes._
## 15.0.0 — 2024-04-18
### 🎉 New features
- Add ability to disable permissions in config plugin by passing `false` instead of permission messages. ([#28107](https://github.com/expo/expo/pull/28107) by [@EvanBacon](https://github.com/EvanBacon))
- Add `pictureSize` prop to `CameraView` component. ([#27664](https://github.com/expo/expo/pull/27664) by [@alanjhughes](https://github.com/alanjhughes))
- Allow user to remove `NSMicrophoneUsageDescription` and ignore the `mute` prop if they don't intend to use video. ([#28156](https://github.com/expo/expo/pull/28156) by [@alanjhughes](https://github.com/alanjhughes))
- Add `animateShutter` prop to provide feedback when a picture is taken. Also added shutter sound on android. ([#28211](https://github.com/expo/expo/pull/28211) by [@alanjhughes](https://github.com/alanjhughes))
### 🐛 Bug fixes
- Allow users using xcode 14 to still build when including camera. ([#27873](https://github.com/expo/expo/pull/27873) by [@alanjhughes](https://github.com/alanjhughes))
- Fix an issue where the permission functions were being imported from the wrong file. ([#27988](https://github.com/expo/expo/pull/27988) by [@alanjhughes](https://github.com/alanjhughes))
- Fix an issue on `iOS` where the barcode types did not match the typescript representation. Also enabled scanning `upc_a` codes on `iOS`. ([#28233](https://github.com/expo/expo/pull/28233) by [@alanjhughes](https://github.com/alanjhughes))
### 💡 Others
- drop unused web `name` property. ([#27437](https://github.com/expo/expo/pull/27437) by [@EvanBacon](https://github.com/EvanBacon))
- On `Android`, requesting audio permissions was meant to be optional in the config plugin. ([#27365](https://github.com/expo/expo/pull/27365) by [@alanjhughes](https://github.com/alanjhughes))
- Prevent unnecessary configuration changes wherever possible. ([#27919](https://github.com/expo/expo/pull/27919) by [@alanjhughes](https://github.com/alanjhughes))
- On `Android`, only recreate camera after certain props have changed. ([#27952](https://github.com/expo/expo/pull/27952) by [@alanjhughes](https://github.com/alanjhughes))
- Removed deprecated backward compatible Gradle settings. ([#28083](https://github.com/expo/expo/pull/28083) by [@kudo](https://github.com/kudo))
- Promote `next` package to stable. ([#28226](https://github.com/expo/expo/pull/28226) by [@alanjhughes](https://github.com/alanjhughes))
## 14.1.1 - 2024-03-13
_This version does not introduce any user-facing changes._
## 14.1.0 - 2024-03-13
### 🐛 Bug fixes
- On `Android`, fix empty qualities being passed to QualitySelector ([#27126](https://github.com/expo/expo/pull/27126) by [@leonhh](https://github.com/leonhh))
- On `web`, prevent creating a webworker when rendering on the server ([#27222](https://github.com/expo/expo/pull/27222) by [@marklawlor](https://github.com/marklawlor))
- On `iOS`, fix method call on an optional variable. ([#27235](https://github.com/expo/expo/pull/27235) by [@alanjhughes](https://github.com/alanjhughes))
- Fix scanned frame bounds when scanning a barcode. ([#27207](https://github.com/expo/expo/pull/27207) by [@tamagokun](https://github.com/tamagokun))
- Fix incorrect prop name `flash` being passed to native. ([#27394](https://github.com/expo/expo/pull/27394) by [@alanjhughes](https://github.com/alanjhughes))
- Ensure `mute` prop is passed to native so it is correctly initialiased even when not provided from JS. ([#27546](https://github.com/expo/expo/pull/27546) by [@alanjhughes](https://github.com/alanjhughes))
- On `iOS`, fix camera orientation on initial render. ([#27545](https://github.com/expo/expo/pull/27545) by [@alanjhughes](https://github.com/alanjhughes))
- On `iOS`, fix an issue where the configuration can be interuppted when the dev menu is presented on intial launch. ([#27572](https://github.com/expo/expo/pull/27572) by [@alanjhughes](https://github.com/alanjhughes))
- On `iOS`, fix `getAvailablePictureSizes` in the legacy package. ([#27642](https://github.com/expo/expo/pull/27642) by [@alanjhughes](https://github.com/alanjhughes))
## 14.0.6 - 2024-03-07
### 🐛 Bug fixes
- On `iOS`, fix the orientation value in `onResponsiveOrientationChanged` when `exif` is set to true. ([#27314](https://github.com/expo/expo/pull/27314) by [@alanjhughes](https://github.com/alanjhughes))
## 14.0.5 - 2024-02-16
### 🎉 New features
- `BarCodeAnalyzer` now passes an additional `raw` field to its `onComplete` callback, corresponding to the barcode value as it was encoded in the barcode without parsing. Will always be undefined on iOS. ([#25391](https://github.com/expo/expo/pull/25391) by [@ajacquierbret](https://github.com/ajacquierbret))
### 🐛 Bug fixes
- Set a higher resolution for barcode scans to allow scanning of high resolution barcodes. ([#26886](https://github.com/expo/expo/pull/26886)) by [@byudaniel](https://github.com/byudaniel)) ([#26886](https://github.com/expo/expo/pull/26886) by [@byudaniel](https://github.com/byudaniel))
- Fix barcode types casing errors. ([#26888](https://github.com/expo/expo/pull/26888) by [@byudaniel](https://github.com/byudaniel))
- On `Android`, fix the camera not being released when the view is destroyed. ([#27086](https://github.com/expo/expo/pull/27086) by [@alanjhughes](https://github.com/alanjhughes))
### 💡 Others
- Make the casing of `Barcode` consistent. ([#26900](https://github.com/expo/expo/pull/26900) by [@alanjhughes](https://github.com/alanjhughes))
## 14.0.4 - 2024-02-06
_This version does not introduce any user-facing changes._
## 14.0.3 - 2024-01-26
### 🐛 Bug fixes
- On `iOS`, barcode types were not converted correctly causing the scanner to not start immediately. ([#26704](https://github.com/expo/expo/pull/26704) by [@alanjhughes](https://github.com/alanjhughes))
- On `iOS`, fix `maxDuration` timescale on videos. ([#26882](https://github.com/expo/expo/pull/26882) by [@alanjhughes](https://github.com/alanjhughes))
## 14.0.2 - 2024-01-23
### 🐛 Bug fixes
- Fix naming of web files. ([#26505](https://github.com/expo/expo/pull/26505) by [@alanjhughes](https://github.com/alanjhughes))
## 14.0.1 - 2023-12-19
_This version does not introduce any user-facing changes._
## 14.0.0 — 2023-12-12
### 🎉 New features
- Methods `stopRecording`, `pausePreview` and `resumePreview` have been updated to return promises. ([#25737](https://github.com/expo/expo/pull/25737) by [@lukmccall](https://github.com/lukmccall))
### 💡 Others
- [iOS] Replace legacy `FileSystem` interfaces usage with core `FileSystemUtilities`. ([#25495](https://github.com/expo/expo/pull/25495) by [@alanhughes](https://github.com/alanjhughes))
## 13.9.0 — 2023-11-14
### 🛠 Breaking changes
- Bumped iOS deployment target to 13.4. ([#25063](https://github.com/expo/expo/pull/25063) by [@gabrieldonadel](https://github.com/gabrieldonadel))
- On `Android` bump `compileSdkVersion` and `targetSdkVersion` to `34`. ([#24708](https://github.com/expo/expo/pull/24708) by [@alanjhughes](https://github.com/alanjhughes))
### 🎉 New features
- [iOS] Rewrote Objective-C classes to Swift. ([#22604](https://github.com/expo/expo/pull/22604) by [@alanjhughes](https://github.com/alanjhughes))
### 🐛 Bug fixes
- [iOS] Fix a regression from ([#22604](https://github.com/expo/expo/pull/22604) that prevented the barcode scanner from starting.([#25053](https://github.com/expo/expo/pull/25053) by [@alanjhughes](https://github.com/alanjhughes))
### 💡 Others
- Use `pointerEvent` style instead of prop. ([#24931](https://github.com/expo/expo/pull/24931) by [@EvanBacon](https://github.com/EvanBacon))
## 13.8.0 — 2023-10-17
### 🛠 Breaking changes
- Dropped support for Android SDK 21 and 22. ([#24201](https://github.com/expo/expo/pull/24201) by [@behenate](https://github.com/behenate))
- Mark the `ratio` param of `getAvailablePictureSizes` as required because omitting it causes a crash on Android. On iOS, the param has no effect. ([#24234](https://github.com/expo/expo/pull/24234) by [@vonovak](https://github.com/vonovak))
### 💡 Others
- Ship untranspiled JSX to support custom handling of `jsx` and `createElement`. ([#24889](https://github.com/expo/expo/pull/24889) by [@EvanBacon](https://github.com/EvanBacon))
## 13.7.0 — 2023-09-15
_This version does not introduce any user-facing changes._
## 13.4.4 — 2023-09-11
### 🐛 Bug fixes
- Remove @koale/useworker. ([#23967](https://github.com/expo/expo/pull/23967) by [@marklawlor](https://github.com/marklawlor))
## 13.6.0 — 2023-09-04
### 🎉 New features
- Added support for React Native 0.73. ([#24018](https://github.com/expo/expo/pull/24018) by [@kudo](https://github.com/kudo))
### 🐛 Bug fixes
- Fixed flash is not enabled during recordings. ([#23776](https://github.com/expo/expo/pull/23776) by [@tszheichoi](https://github.com/tszheichoi))
- On iOS, fix dead frames when starting a video recording. ([#22037](https://github.com/expo/expo/pull/22037) by [@alanjhughes](https://github.com/alanjhughes))
- Remove @koale/useworker. ([#23967](https://github.com/expo/expo/pull/23967) by [@marklawlor](https://github.com/marklawlor))
## 13.5.1 — 2023-08-02
_This version does not introduce any user-facing changes._
## 13.5.0 — 2023-07-28
### 🐛 Bug fixes
- Fixed issue with checking camera/microphone permissions in Firefox. ([#22855](https://github.com/expo/expo/pull/22855) by [@loganrosen](https://github.com/loganrosen))
## 13.4.2 - 2023-07-04
### 🐛 Bug fixes
- Fix crash when onBarCodeScanned or onFacesDetected callback is removed. ([#23223](https://github.com/expo/expo/pull/23223) by [@thespacemanatee](https://github.com/thespacemanatee))
## 13.4.1 — 2023-06-28
### 🐛 Bug fixes
- Resolved an issue on Android where recording a video, even with the mute: true option, would still result in an audio permission exception. Furthermore, the mute flag was incorrectly referred to as muteValue, causing it to be consistently ignored ([#23145](https://github.com/expo/expo/pull/23145) by [@hirbod](https://github.com/hirbod))
## 13.4.0 — 2023-06-13
- Fixed `Expo camera - Cannot take landscape photos if screen orientation is locked'` on iOS. ([#21938](https://github.com/expo/expo/issues/21938) by [@chalenascholl](https://github.com/chalenascholl)) ([#21956](https://github.com/expo/expo/pull/21956) by [@chalenascholl](https://github.com/chalenascholl))
### 🐛 Bug fixes
- Fixed Android build warnings for Gradle version 8. ([#22537](https://github.com/expo/expo/pull/22537), [#22609](https://github.com/expo/expo/pull/22609) by [@kudo](https://github.com/kudo))
## 13.3.0 — 2023-05-08
_This version does not introduce any user-facing changes._
## 13.2.1 — 2023-02-09
_This version does not introduce any user-facing changes._
## 13.2.0 — 2023-02-03
### 🐛 Bug fixes
- Fix path where simulator saves photos ([#20872](https://github.com/expo/expo/pull/20872) by [@pettomartino](https://github.com/pettomartino))
- Fixed `Cannot set prop 'barCodeScannerSettings' on view 'class expo.modules.camera.ExpoCameraView'` on Android. ([#21033](https://github.com/expo/expo/pull/21033) by [@lukmccall](https://github.com/lukmccall))
### 💡 Others
- On Android bump `compileSdkVersion` and `targetSdkVersion` to `33`. ([#20721](https://github.com/expo/expo/pull/20721) by [@lukmccall](https://github.com/lukmccall))
## 13.1.0 - 2022-11-23
### 🐛 Bug fixes
- Fix import issue on case-sensitive file systems ([#20141](https://github.com/expo/expo/pull/20141) by [@hirbod](https://github.com/hirbod))
### 💡 Others
- Use correct type for `videoStabilizationMode` option. ([#20130](https://github.com/expo/expo/pull/20130) by [@simek](https://github.com/simek))
## 13.0.0 — 2022-10-25
### 🐛 Bug fixes
- Added `bounds` property to the `BarCodeScanningResult`. ([#19519](https://github.com/expo/expo/pull/19519) by [@lukmccall](https://github.com/lukmccall))
## 13.0.0-beta.1 — 2022-10-06
### 🛠 Breaking changes
- [plugin] Upgrade minimum runtime requirement to Node 14 (LTS). ([#18204](https://github.com/expo/expo/pull/18204) by [@EvanBacon](https://github.com/EvanBacon))
- Bumped iOS deployment target to 13.0 and deprecated support for iOS 12. ([#18873](https://github.com/expo/expo/pull/18873) by [@tsapeta](https://github.com/tsapeta))
### 🎉 New features
- On iOS and Android, added new `additionalExif` parameter to `takePictureAsync()` method so that users can add extra information to the photos, such as GPS coordinates. ([#18469](https://github.com/expo/expo/pull/18469) by [@alexyangjie](https://github.com/alexyangjie))
- Native module for camera view is now written in Swift using the new API. ([#18703](https://github.com/expo/expo/pull/18703) by [@tsapeta](https://github.com/tsapeta))
### 🐛 Bug fixes
- Fix error when calling `takePictureAsync()` on Android emulator. ([#18704](https://github.com/expo/expo/pull/18704)) by [@keith-kurak](https://github.com/keith-kurak))
- Add `cornerPoints` to `onBarCodeScanned` on Android. ([#19357](https://github.com/expo/expo/pull/19357) by [@igoro00](https://github.com/igoro00))
- Fix error where `takePictureAsync()` saved the photo to a global cache directory that was inaccessible in Expo Go. ([#19205](https://github.com/expo/expo/pull/19205) by [@aleqsio](https://github.com/aleqsio))
### 💡 Others
- Drop `@expo/config-plugins` dependency in favor of peer dependency on `expo`. ([#18595](https://github.com/expo/expo/pull/18595) by [@EvanBacon](https://github.com/EvanBacon))
- Refactored inline Android emulator checks to use enhanced checking in `EmulatorUtilities.isRunningOnEmulator()`. ([#16177](https://github.com/expo/expo/pull/16177)) by [@kbrandwijk](https://github.com/kbrandwijk), [@keith-kurak](https://github.com/keith-kurak))
## 12.3.0 — 2022-07-07
### 🐛 Bug fixes
- On Web prevent the QR worker to be immediately cleaned up after finishing it's job to allow reusing it later (e.g. do not re-download every script upon repetitive worker launch). ([#15369](https://github.com/expo/expo/pull/15369) by [@jer-sen](https://github.com/jer-sen) and [#17833](https://github.com/expo/expo/pull/17833) by [@bbarthec](https://github.com/bbarthec))
- Fix bug on Android that would only allow you to scan one bar code. ([#17655](https://github.com/expo/expo/pull/17655) by [@witheroux](https://github.com/witheroux))
## 12.2.0 — 2022-04-18
### 🎉 New features
- Update `useWebQRScanner` to allow scanning QR codes with inverted colors (light foreground and dark background). ([#16106](https://github.com/expo/expo/pull/16106) by [@rissois](https://github.com/rissois))
### 🐛 Bug fixes
- Fix crash on Android when app is restored from background by check for null value of `pendingFaceDetectorSettings`. ([#16543](https://github.com/expo/expo/pull/16543) by [@giautm](https://github.com/giautm))
### 💡 Others
- Updated `@expo/config-plugins` from `4.0.2` to `4.0.14` ([#15621](https://github.com/expo/expo/pull/15621) by [@EvanBacon](https://github.com/EvanBacon))
- Replace `CapturedPicture` type with `CameraCapturedPicture` in events callback to avoid duplicated types. ([#15936](https://github.com/expo/expo/pull/15936) by [@Simek](https://github.com/Simek))
### ⚠️ Notices
- On Android bump `compileSdkVersion` to `31`, `targetSdkVersion` to `31` and `Java` version to `11`. ([#16941](https://github.com/expo/expo/pull/16941) by [@bbarthec](https://github.com/bbarthec))
## 12.1.2 - 2022-02-04
### 🐛 Bug fixes
- Fix null pointer exception when barcode scanner or face detector are not installed. ([#16167](https://github.com/expo/expo/pull/16167) by [@tsapeta](https://github.com/tsapeta))
## 12.1.1 - 2022-02-01
### 🐛 Bug fixes
- Fix `Plugin with id 'maven' not found` build error from Android Gradle 7. ([#16080](https://github.com/expo/expo/pull/16080) by [@kudo](https://github.com/kudo))
## 12.1.0 — 2021-12-03
### 🐛 Bug fixes
- Fix Gradle error when running Gradle from outside of the project directory. ([#15109](https://github.com/expo/expo/pull/15109) by [@kudo](https://github.com/kudo))
### 💡 Others
- Rewrite module to Kotlin. ([#14717](https://github.com/expo/expo/pull/14717) by [@mstach60161](https://github.com/mstach60161))
- [plugin] Use more specific gradle variable name. ([#14966](https://github.com/expo/expo/pull/14966) by [@EvanBacon](https://github.com/EvanBacon))
## 12.0.1 — 2021-10-01
_This version does not introduce any user-facing changes._
## 12.0.0 — 2021-09-28
### 🛠 Breaking changes
- Deprecate `getPermissionsAsync` and `requestPermissionsAsync` methods, use specific permission requesters. ([#13855](https://github.com/expo/expo/pull/13855) by [@bycedric](https://github.com/bycedric))
- Dropped support for iOS 11.0 ([#14383](https://github.com/expo/expo/pull/14383) by [@cruzach](https://github.com/cruzach))
### 🎉 New features
- Add `useCameraPermissions` and `useMicrophonePermissions` hooks from modules factory. ([#13855](https://github.com/expo/expo/pull/13855) by [@bycedric](https://github.com/bycedric))
- [plugin] Add monorepo support to Android config plugin for Gradle import. ([#14521](https://github.com/expo/expo/pull/14521) by [@EvanBacon](https://github.com/EvanBacon))
### 🐛 Bug fixes
- Fix QR code scanner in expo web by updating `@koale/useworker` to `^4.0.2` ([#14138](https://github.com/expo/expo/pull/13341) by [@fguitton](https://github.com/fguitton))
- Update video codec validation to properly reject an invalid codec option. ([#13341](https://github.com/expo/expo/pull/13341) by [@ajsmth](https://github.com/ajsmth))
- Add `get/requestMicrophonePermissionsAsync()` and `get/requestCameraPermissionsAsync()` methods to named exports. ([#13621](https://github.com/expo/expo/pull/13621) by [@ajsmth](https://github.com/ajsmth))
- Fix regression in video quality option of recordAsync() ([#13659](https://github.com/expo/expo/pull/13659) by [@ajsmth](https://github.com/ajsmth))
- Update permission validation to check for only camera permissions in `initWithModuleRegistry()` ([#13690](https://github.com/expo/expo/pull/13690) by [@ajsmth](https://github.com/ajsmth))
- Fix building errors from use_frameworks! in Podfile. ([#14523](https://github.com/expo/expo/pull/14523) by [@kudo](https://github.com/kudo))
### 💡 Others
- Migrated from `@unimodules/core` to `expo-modules-core`. ([#13750](https://github.com/expo/expo/pull/13750) by [@tsapeta](https://github.com/tsapeta))
- Updated `@expo/config-plugins` ([#14443](https://github.com/expo/expo/pull/14443) by [@EvanBacon](https://github.com/EvanBacon))
## 11.1.1 — 2021-06-16
_This version does not introduce any user-facing changes._
## 11.1.0 — 2021-06-07
### 🎉 New features
- On iOS added new `codec` parameter in `recordAsync()` method and new method `getAvailableVideoCodecsAsync()` that queries the device for available video codecs. ([#12772](https://github.com/expo/expo/pull/12772) by [@ajsmth](https://github.com/ajsmth))
- Added new `requestCameraPermissionsAsync()`, `requestMicrophonePermissionsAsync()`, `getCameraPermissionsAsync()` and `getMicrophonePermissionsAsync()` methods which gives more fine-grained control over requested permissions. ([#12860](https://github.com/expo/expo/pull/12772) by [@ajsmth](https://github.com/ajsmth))
### 💡 Others
- Migrated interfaces from their own packages to `expo-modules-core`. ([#12868](https://github.com/expo/expo/pull/12868), [#12912](https://github.com/expo/expo/pull/12912), [#12918](https://github.com/expo/expo/pull/12918) by [@tsapeta](https://github.com/tsapeta))
## 11.0.3 — 2021-05-03
### 🐛 Bug fixes
- Enable kotlin in all modules. ([#12716](https://github.com/expo/expo/pull/12716) by [@wschurman](https://github.com/wschurman))
- Add `unimodules-permissions-interface` dependency. ([#12739](https://github.com/expo/expo/pull/12739) by [@ajsmth](https://github.com/ajsmth))
## 11.0.2 — 2021-04-13
_This version does not introduce any user-facing changes._
## 11.0.1 — 2021-04-01
### 🐛 Bug fixes
- Fix typing on `Camera.Constants`. ([#12343](https://github.com/expo/expo/pull/12343) by [@HBiede](https://github.com/HBiede))
## 11.0.0 — 2021-03-10
### 🛠 Breaking changes
- Remove deprecated `barCodeTypes` prop in favor of `barCodeScannerSettings.barCodeTypes`. ([#11904](https://github.com/expo/expo/pull/11904) by [@EvanBacon](https://github.com/EvanBacon))
### 🎉 New features
- Remove lodash. ([#11900](https://github.com/expo/expo/pull/11900) by [@EvanBacon](https://github.com/EvanBacon))
- Add requestPermissionsAsync and getPermissionsAsync for web. ([#11694](https://github.com/expo/expo/pull/11694) by [@IjzerenHein](https://github.com/IjzerenHein))
- Converted plugin to TypeScript. ([#11715](https://github.com/expo/expo/pull/11715) by [@EvanBacon](https://github.com/EvanBacon))
- Updated Android build configuration to target Android 11 (added support for Android SDK 30). ([#11647](https://github.com/expo/expo/pull/11647) by [@bbarthec](https://github.com/bbarthec))
### 🐛 Bug fixes
- Remove peerDependencies and unimodulePeerDependencies from Expo modules. ([#11980](https://github.com/expo/expo/pull/11980) by [@brentvatne](https://github.com/brentvatne))
## 10.0.0 — 2021-01-15
### ⚠️ Notices
- The package is now shipped with prebuilt binaries on iOS. You can read more about it on [expo.fyi/prebuilt-modules](https://expo.fyi/prebuilt-modules). ([#11224](https://github.com/expo/expo/pull/11224) by [@tsapeta](https://github.com/tsapeta))
### 🛠 Breaking changes
- Dropped support for iOS 10.0 ([#11344](https://github.com/expo/expo/pull/11344) by [@tsapeta](https://github.com/tsapeta))
### 🎉 New features
- Created config plugins ([#11538](https://github.com/expo/expo/pull/11538) by [@EvanBacon](https://github.com/EvanBacon))
### 🐛 Bug fixes
- Removed `fbjs` dependency ([#11396](https://github.com/expo/expo/pull/11396) by [@cruzach](https://github.com/cruzach))
## 9.1.1 — 2020-12-14
_This version does not introduce any user-facing changes._
## 9.1.0 — 2020-11-17
### 🎉 New features
- Added support for video poster to show while the camera is loading on web. ([#9930](https://github.com/expo/expo/pull/9930) by [@liorJuice](https://github.com/liorJuice))
## 9.0.0 — 2020-08-18
### 🛠 Breaking changes
- Fix bug where `barCodeTypes` needed to be defined on web. ([#9630](https://github.com/expo/expo/pull/9630) by [@EvanBacon](https://github.com/EvanBacon))
- Fix bug where camera would sometimes not start on web desktop. ([#9630](https://github.com/expo/expo/pull/9630) by [@EvanBacon](https://github.com/EvanBacon))
- Deleted `CaptureOptions` in favor of `CameraPictureOptions` ([#9558](https://github.com/expo/expo/pull/9558) by [@EvanBacon](https://github.com/EvanBacon))
- Added camera permissions declarations to `AndroidManifest.xml` on Android. ([#9224](https://github.com/expo/expo/pull/9224) by [@bycedric](https://github.com/bycedric))
### 🎉 New features
- Added support for QR scanning on web. ([#4166](https://github.com/expo/expo/pull/4166) by [@EvanBacon](https://github.com/EvanBacon))
- Remove `fbjs` dependency
- Delete `prop-types` in favor of TypeScript. ([#8680](https://github.com/expo/expo/pull/8680) by [@EvanBacon](https://github.com/EvanBacon))
- [camera] Directly import `createElement` from `react-native-web` for RNW v12 support. ([#8773](https://github.com/expo/expo/pull/8773) by [@EvanBacon](https://github.com/EvanBacon))
### 🐛 Bug fixes
- Fix QR scanning on Android and iOS. ([#9741](https://github.com/expo/expo/pull/9741) by [@EvanBacon](https://github.com/EvanBacon))
- [web] Fix bug where swapping cameras caused screen to flicker ([#9558](https://github.com/expo/expo/pull/9558) by [@EvanBacon](https://github.com/EvanBacon))
- [web] Fix bug where swapping cameras doesn't persist camera settings ([#9558](https://github.com/expo/expo/pull/9558) by [@EvanBacon](https://github.com/EvanBacon))
## 8.3.1 — 2020-05-29
_This version does not introduce any user-facing changes._
## 8.3.0 — 2020-05-27
### 🛠 Breaking changes
- The base64 output will no longer contain newline and special character (`\n`, `\r`) on Android. ([#7841](https://github.com/expo/expo/pull/7841) by [@jarvisluong](https://github.com/jarvisluong))
### 🎉 New features
- Added exports for TypeScript definitions: CameraType, ImageType, ImageParameters, ImageSize, CaptureOptions, CapturedPicture ([#8457](https://github.com/expo/expo/pull/8457) by [@jarvisluong](https://github.com/jarvisluong))

View File

@@ -0,0 +1,77 @@
<p>
<a href="https://docs.expo.dev/versions/latest/sdk/camera/">
<img
src="../../.github/resources/expo-camera.svg"
alt="expo-camera"
height="64" />
</a>
</p>
A React component that renders a preview for the device's either front or back camera. Camera's parameters like zoom, auto focus, white balance and flash mode are adjustable. With expo-camera, one can also take photos and record videos that are saved to the app's cache. Morever, the component is also capable of detecting faces and bar codes appearing on the preview.
# API documentation
- [Documentation for the main branch](https://github.com/expo/expo/blob/main/docs/pages/versions/unversioned/sdk/camera.mdx)
- [Documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/camera/)
# Installation in managed Expo projects
For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/camera/).
# Installation in bare React Native projects
For bare React Native projects, you must ensure that you have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before continuing.
### Add the package to your npm dependencies
```
npx expo install expo-camera
```
### Configure for iOS
Add `NSCameraUsageDescription` and `NSMicrophoneUsageDescription` keys to your `Info.plist`:
```xml
<key>NSCameraUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to use the camera</string>
<key>NSMicrophoneUsageDescription</key>
<string>Allow $(PRODUCT_NAME) to use the microphone</string>
```
Run `npx pod-install` after installing the npm package.
### Configure for Android
This package automatically adds the `CAMERA` permission to your app. If you want to record videos with audio, you have to include the `RECORD_AUDIO`.
```xml
<!-- Added permissions -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- Optional permissions -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
```
Adjust the `android/build.gradle` to add a new `maven` block after all other repositories as described below:
```gradle
allprojects {
repositories {
// * Your other repositories here *
// * Add a new maven block after other repositories / blocks *
maven {
// expo-camera bundles a custom com.google.android:cameraview
url "$rootDir/../node_modules/expo-camera/android/maven"
}
}
}
```
The sourcecode for `cameraview` can be found at [`expo/cameraview`](https://github.com/expo/cameraview).
# Contributing
Contributions are very welcome! Please refer to guidelines described in the [contributing guide](https://github.com/expo/expo#contributing).

View File

@@ -0,0 +1,44 @@
apply plugin: 'com.android.library'
group = 'host.exp.exponent'
version = '15.0.16'
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
apply from: expoModulesCorePlugin
applyKotlinExpoModulesCorePlugin()
useCoreDependencies()
useDefaultAndroidSdkVersions()
useExpoPublishing()
android {
namespace "expo.modules.camera"
defaultConfig {
versionCode 32
versionName "15.0.16"
}
}
repositories {
maven {
url "$projectDir/maven"
}
}
dependencies {
def camerax_version = "1.4.0-beta02"
api "androidx.exifinterface:exifinterface:1.3.7"
api "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
implementation "androidx.camera:camera-video:${camerax_version}"
implementation "androidx.camera:camera-view:${camerax_version}"
implementation "androidx.camera:camera-extensions:${camerax_version}"
implementation "com.google.mlkit:barcode-scanning:17.2.0"
implementation "androidx.camera:camera-mlkit-vision:${camerax_version}"
api 'com.google.android:cameraview:1.0.0'
}

View File

@@ -0,0 +1 @@
4c23883e5472108b7a31944c4ba58eea

View File

@@ -0,0 +1 @@
3874291cb50122dd79eba6d49524e0be58bb8f6f

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.android</groupId>
<artifactId>cameraview</artifactId>
<version>1.0.0</version>
<packaging>aar</packaging>
<dependencies>
<dependency>
<groupId>com.android.support</groupId>
<artifactId>support-annotations</artifactId>
<version>25.3.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.android.support</groupId>
<artifactId>support-v4</artifactId>
<version>25.3.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.android.support</groupId>
<artifactId>appcompat-v7</artifactId>
<version>25.3.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1 @@
e8661f352c640fe9d420e153a0aa095d

View File

@@ -0,0 +1 @@
cd83ff235770e2d944b18f70f881c29b4f935599

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>com.google.android</groupId>
<artifactId>cameraview</artifactId>
<versioning>
<release>1.0.0</release>
<versions>
<version>1.0.0</version>
</versions>
<lastUpdated>20180605124508</lastUpdated>
</versioning>
</metadata>

View File

@@ -0,0 +1 @@
bea89333cd34959f72e685c639ed8e42

View File

@@ -0,0 +1 @@
b9227e1e2d046cd0fb25f555bc5d16ee240c27f5

View File

@@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
</manifest>

View File

@@ -0,0 +1,15 @@
package expo.modules.camera
import expo.modules.kotlin.exception.CodedException
class CameraExceptions {
class ImageCaptureFailed : CodedException(message = "Failed to capture image")
class VideoRecordingFailed(cause: String?) : CodedException("Video recording failed: $cause")
class ImageRetrievalException(url: String) :
CodedException("Could not get the image from given url: '$url'")
class UnsupportedAspectRatioException(aspectRatio: String) :
CodedException("Unsupported aspect ratio: '$aspectRatio'")
}

View File

@@ -0,0 +1,42 @@
package expo.modules.camera
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import expo.modules.camera.records.CameraType
import java.io.ByteArrayOutputStream
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
object CameraViewHelper {
// Utilities
@JvmStatic
fun getCorrectCameraRotation(rotation: Int, facing: CameraType) =
if (facing == CameraType.FRONT) {
(rotation - 90 + 360) % 360
} else {
(-rotation + 90 + 360) % 360
}
fun generateSimulatorPhoto(width: Int, height: Int): ByteArray {
val fakePhotoBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(fakePhotoBitmap)
val background = Paint().apply {
color = Color.BLACK
}
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), background)
val textPaint = Paint().apply {
color = Color.YELLOW
textSize = 35f
}
val calendar = Calendar.getInstance()
val simpleDateFormat = SimpleDateFormat("dd.MM.yy HH:mm:ss", Locale.US)
canvas.drawText(simpleDateFormat.format(calendar.time), width * 0.1f, height * 0.9f, textPaint)
val stream = ByteArrayOutputStream()
fakePhotoBitmap.compress(Bitmap.CompressFormat.PNG, 90, stream)
return stream.toByteArray()
}
}

View File

@@ -0,0 +1,263 @@
package expo.modules.camera
import android.Manifest
import android.graphics.Bitmap
import android.util.Log
import expo.modules.camera.analyzers.BarCodeScannerResultSerializer
import expo.modules.camera.analyzers.MLKitBarCodeScanner
import expo.modules.camera.records.BarcodeSettings
import expo.modules.camera.records.BarcodeType
import expo.modules.camera.records.CameraMode
import expo.modules.camera.records.CameraRatio
import expo.modules.camera.records.CameraType
import expo.modules.camera.records.FlashMode
import expo.modules.camera.records.FocusMode
import expo.modules.camera.records.VideoQuality
import expo.modules.camera.tasks.ResolveTakenPicture
import expo.modules.core.errors.ModuleDestroyedException
import expo.modules.core.utilities.EmulatorUtilities
import expo.modules.interfaces.imageloader.ImageLoaderInterface
import expo.modules.interfaces.permissions.Permissions
import expo.modules.kotlin.Promise
import expo.modules.kotlin.exception.Exceptions
import expo.modules.kotlin.functions.Queues
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import java.io.File
val cameraEvents = arrayOf(
"onCameraReady",
"onMountError",
"onBarcodeScanned",
"onFacesDetected",
"onFaceDetectionError",
"onPictureSaved"
)
class CameraViewModule : Module() {
private val moduleScope = CoroutineScope(Dispatchers.Main)
override fun definition() = ModuleDefinition {
Name("ExpoCamera")
Events("onModernBarcodeScanned")
AsyncFunction("requestCameraPermissionsAsync") { promise: Promise ->
Permissions.askForPermissionsWithPermissionsManager(
permissionsManager,
promise,
Manifest.permission.CAMERA
)
}
AsyncFunction("requestMicrophonePermissionsAsync") { promise: Promise ->
Permissions.askForPermissionsWithPermissionsManager(
permissionsManager,
promise,
Manifest.permission.RECORD_AUDIO
)
}
AsyncFunction("getCameraPermissionsAsync") { promise: Promise ->
Permissions.getPermissionsWithPermissionsManager(
permissionsManager,
promise,
Manifest.permission.CAMERA
)
}
AsyncFunction("getMicrophonePermissionsAsync") { promise: Promise ->
Permissions.getPermissionsWithPermissionsManager(
permissionsManager,
promise,
Manifest.permission.RECORD_AUDIO
)
}
AsyncFunction("scanFromURLAsync") { url: String, barcodeTypes: List<BarcodeType>, promise: Promise ->
appContext.imageLoader?.loadImageForManipulationFromURL(
url,
object : ImageLoaderInterface.ResultListener {
override fun onSuccess(bitmap: Bitmap) {
val scanner = MLKitBarCodeScanner()
val formats = barcodeTypes.map { it.mapToBarcode() }
scanner.setSettings(formats)
moduleScope.launch {
val barcodes = scanner.scan(bitmap)
.filter { formats.contains(it.type) }
.map { BarCodeScannerResultSerializer.toBundle(it, 1.0f) }
promise.resolve(barcodes)
}
}
override fun onFailure(cause: Throwable?) {
promise.reject(CameraExceptions.ImageRetrievalException(url))
}
}
)
}
OnDestroy {
try {
moduleScope.cancel(ModuleDestroyedException())
} catch (e: IllegalStateException) {
Log.e(TAG, "The scope does not have a job in it")
}
}
View(ExpoCameraView::class) {
Events(cameraEvents)
Prop("facing") { view, facing: CameraType? ->
facing?.let {
if (view.lensFacing != facing) {
view.lensFacing = it
}
}
}
Prop("flashMode") { view, flashMode: FlashMode? ->
flashMode?.let {
view.setCameraFlashMode(it)
}
}
Prop("enableTorch") { view, enabled: Boolean? ->
view.enableTorch = enabled ?: false
}
Prop("animateShutter") { view, animate: Boolean? ->
view.animateShutter = animate ?: true
}
Prop("zoom") { view, zoom: Float? ->
zoom?.let {
view.camera?.cameraControl?.setLinearZoom(it)
}
}
Prop("mode") { view, mode: CameraMode? ->
mode?.let {
if (view.cameraMode != mode) {
view.cameraMode = it
}
}
}
Prop("mute") { view, muted: Boolean? ->
muted?.let {
if (it != view.mute) {
view.mute = it
}
}
}
Prop("videoQuality") { view, quality: VideoQuality? ->
quality?.let {
view.videoQuality = it
}
}
Prop("barcodeScannerSettings") { view, settings: BarcodeSettings? ->
if (settings == null) {
return@Prop
}
view.setBarcodeScannerSettings(settings)
}
Prop("barcodeScannerEnabled") { view, enabled: Boolean? ->
enabled?.let {
view.setShouldScanBarcodes(enabled)
}
}
Prop("pictureSize") { view, pictureSize: String? ->
pictureSize?.let {
if (view.pictureSize != pictureSize) {
view.pictureSize = it
}
}
}
Prop("autoFocus") { view, autoFocus: FocusMode? ->
view.autoFocus = autoFocus ?: FocusMode.OFF
}
Prop("ratio") { view, ratio: CameraRatio? ->
if (view.ratio != ratio) {
view.ratio = ratio
}
}
Prop("mirror") { view, mirror: Boolean? ->
mirror?.let {
view.mirror = it
return@Prop
}
view.mirror = false
}
OnViewDidUpdateProps { view ->
view.createCamera()
}
AsyncFunction("takePicture") { view: ExpoCameraView, options: PictureOptions, promise: Promise ->
if (!EmulatorUtilities.isRunningOnEmulator()) {
view.takePicture(options, promise, cacheDirectory)
} else {
val image = CameraViewHelper.generateSimulatorPhoto(view.width, view.height)
moduleScope.launch {
ResolveTakenPicture(image, promise, options, false, cacheDirectory) { response ->
view.onPictureSaved(response)
}.resolve()
}
}
}.runOnQueue(Queues.MAIN)
AsyncFunction("getAvailablePictureSizes") { view: ExpoCameraView ->
return@AsyncFunction view.getAvailablePictureSizes()
}
AsyncFunction("record") { view: ExpoCameraView, options: RecordingOptions, promise: Promise ->
if (!view.mute && !permissionsManager.hasGrantedPermissions(Manifest.permission.RECORD_AUDIO)) {
throw Exceptions.MissingPermissions(Manifest.permission.RECORD_AUDIO)
}
view.record(options, promise, cacheDirectory)
}.runOnQueue(Queues.MAIN)
AsyncFunction("stopRecording") { view: ExpoCameraView ->
view.activeRecording?.close()
}.runOnQueue(Queues.MAIN)
AsyncFunction("resumePreview") { view: ExpoCameraView ->
view.resumePreview()
}
AsyncFunction("pausePreview") { view: ExpoCameraView ->
view.pausePreview()
}
OnViewDestroys { view ->
view.orientationEventListener.disable()
view.cancelCoroutineScope()
view.releaseCamera()
}
}
}
private val cacheDirectory: File
get() = appContext.cacheDirectory
private val permissionsManager: Permissions
get() = appContext.permissions ?: throw Exceptions.PermissionsModuleNotFound()
companion object {
internal val TAG = CameraViewModule::class.java.simpleName
}
}

View File

@@ -0,0 +1,655 @@
package expo.modules.camera
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Color
import android.graphics.ImageFormat
import android.graphics.SurfaceTexture
import android.graphics.drawable.ColorDrawable
import android.hardware.camera2.CameraCharacteristics
import android.media.AudioManager
import android.media.MediaActionSound
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.util.Size
import android.view.OrientationEventListener
import android.view.Surface
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.camera2.interop.ExperimentalCamera2Interop
import androidx.camera.core.Camera
import androidx.camera.core.CameraInfo
import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraState
import androidx.camera.core.DisplayOrientedMeteringPointFactory
import androidx.camera.core.FocusMeteringAction
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.ImageProxy
import androidx.camera.core.MirrorMode
import androidx.camera.core.Preview
import androidx.camera.core.UseCaseGroup
import androidx.camera.core.resolutionselector.ResolutionSelector
import androidx.camera.core.resolutionselector.ResolutionStrategy
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.video.FallbackStrategy
import androidx.camera.video.FileOutputOptions
import androidx.camera.video.QualitySelector
import androidx.camera.video.Recorder
import androidx.camera.video.Recording
import androidx.camera.video.VideoCapture
import androidx.camera.video.VideoRecordEvent
import androidx.camera.view.PreviewView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import expo.modules.camera.analyzers.BarcodeAnalyzer
import expo.modules.camera.analyzers.toByteArray
import expo.modules.camera.common.BarcodeScannedEvent
import expo.modules.camera.common.CameraMountErrorEvent
import expo.modules.camera.common.PictureSavedEvent
import expo.modules.camera.records.BarcodeSettings
import expo.modules.camera.records.BarcodeType
import expo.modules.camera.records.CameraMode
import expo.modules.camera.records.CameraRatio
import expo.modules.camera.records.CameraType
import expo.modules.camera.records.FlashMode
import expo.modules.camera.records.FocusMode
import expo.modules.camera.records.VideoQuality
import expo.modules.camera.tasks.ResolveTakenPicture
import expo.modules.camera.utils.FileSystemUtils
import expo.modules.camera.utils.mapX
import expo.modules.camera.utils.mapY
import expo.modules.core.errors.ModuleDestroyedException
import expo.modules.interfaces.barcodescanner.BarCodeScannerResult
import expo.modules.interfaces.barcodescanner.BarCodeScannerResult.BoundingBox
import expo.modules.interfaces.camera.CameraViewInterface
import expo.modules.kotlin.AppContext
import expo.modules.kotlin.Promise
import expo.modules.kotlin.exception.Exceptions
import expo.modules.kotlin.viewevent.EventDispatcher
import expo.modules.kotlin.views.ExpoView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import java.io.File
import kotlin.math.roundToInt
import kotlin.properties.Delegates
const val ANIMATION_FAST_MILLIS = 50L
const val ANIMATION_SLOW_MILLIS = 100L
@SuppressLint("ViewConstructor")
class ExpoCameraView(
context: Context,
appContext: AppContext
) : ExpoView(context, appContext),
CameraViewInterface {
private val currentActivity
get() = appContext.currentActivity as? AppCompatActivity
?: throw Exceptions.MissingActivity()
val orientationEventListener by lazy {
object : OrientationEventListener(currentActivity) {
override fun onOrientationChanged(orientation: Int) {
if (orientation == ORIENTATION_UNKNOWN) {
return
}
val rotation = when (orientation) {
in 45 until 135 -> Surface.ROTATION_270
in 135 until 225 -> Surface.ROTATION_180
in 225 until 315 -> Surface.ROTATION_90
else -> Surface.ROTATION_0
}
imageAnalysisUseCase?.targetRotation = rotation
imageCaptureUseCase?.targetRotation = rotation
}
}
}
var camera: Camera? = null
var activeRecording: Recording? = null
private var cameraProvider: ProcessCameraProvider? = null
private val providerFuture = ProcessCameraProvider.getInstance(context)
private var imageCaptureUseCase: ImageCapture? = null
private var imageAnalysisUseCase: ImageAnalysis? = null
private var recorder: Recorder? = null
private var barcodeFormats: List<BarcodeType> = emptyList()
private var previewView = PreviewView(context).apply {
elevation = 0f
}
private val scope = CoroutineScope(Dispatchers.Main)
private var shouldCreateCamera = false
private var previewPaused = false
var lensFacing = CameraType.BACK
set(value) {
field = value
shouldCreateCamera = true
}
var cameraMode: CameraMode = CameraMode.PICTURE
set(value) {
field = value
shouldCreateCamera = true
}
var autoFocus: FocusMode = FocusMode.OFF
set(value) {
field = value
camera?.cameraControl?.let {
if (field == FocusMode.OFF) {
it.cancelFocusAndMetering()
} else {
startFocusMetering()
}
}
}
var videoQuality: VideoQuality = VideoQuality.VIDEO1080P
set(value) {
field = value
shouldCreateCamera = true
}
var ratio: CameraRatio? = null
set(value) {
field = value
shouldCreateCamera = true
}
var pictureSize: String = ""
set(value) {
field = value
shouldCreateCamera = true
}
var mirror: Boolean = false
set(value) {
field = value
shouldCreateCamera = true
}
var mute: Boolean = false
var animateShutter: Boolean = true
var enableTorch: Boolean by Delegates.observable(false) { _, _, newValue ->
setTorchEnabled(newValue)
}
private val onCameraReady by EventDispatcher<Unit>()
private val onMountError by EventDispatcher<CameraMountErrorEvent>()
private val onBarcodeScanned by EventDispatcher<BarcodeScannedEvent>(
/**
* We want every distinct barcode to be reported to the JS listener.
* If we return some static value as a coalescing key there may be two barcode events
* containing two different barcodes waiting to be transmitted to JS
* that would get coalesced (because both of them would have the same coalescing key).
* So let's differentiate them with a hash of the contents (mod short's max value).
*/
coalescingKey = { event -> (event.data.hashCode() % Short.MAX_VALUE).toShort() }
)
private val onPictureSaved by EventDispatcher<PictureSavedEvent>(
coalescingKey = { event ->
val uriHash = event.data.getString("uri")?.hashCode() ?: -1
(uriHash % Short.MAX_VALUE).toShort()
}
)
// Scanning-related properties
private var shouldScanBarcodes = false
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
measureChild(previewView, widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(
ViewGroup.resolveSize(previewView.measuredWidth, widthMeasureSpec),
ViewGroup.resolveSize(previewView.measuredHeight, heightMeasureSpec)
)
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
if (!changed) {
return
}
val width = right - left
val height = bottom - top
previewView.layout(0, 0, width, height)
}
override fun onViewAdded(child: View?) {
super.onViewAdded(child)
if (child == previewView) {
return
}
child?.bringToFront()
removeView(previewView)
addView(previewView, 0)
}
fun takePicture(options: PictureOptions, promise: Promise, cacheDirectory: File) {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val volume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
imageCaptureUseCase?.takePicture(
ContextCompat.getMainExecutor(context),
object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureStarted() {
if (volume != 0) {
MediaActionSound().play(MediaActionSound.SHUTTER_CLICK)
}
if (!animateShutter) {
return
}
rootView.postDelayed({
rootView.foreground = ColorDrawable(Color.WHITE)
rootView.postDelayed(
{ rootView.foreground = null },
ANIMATION_FAST_MILLIS
)
}, ANIMATION_SLOW_MILLIS)
}
override fun onCaptureSuccess(image: ImageProxy) {
val data = image.planes.toByteArray()
if (options.fastMode) {
promise.resolve(null)
}
cacheDirectory.let {
scope.launch {
val shouldMirror = mirror && lensFacing == CameraType.FRONT
ResolveTakenPicture(data, promise, options, shouldMirror, it) { response: Bundle ->
onPictureSaved(response)
}.resolve()
}
}
image.close()
}
override fun onError(exception: ImageCaptureException) {
promise.reject(CameraExceptions.ImageCaptureFailed())
}
}
)
}
fun setCameraFlashMode(mode: FlashMode) {
if (imageCaptureUseCase?.flashMode != mode.mapToLens()) {
imageCaptureUseCase?.flashMode = mode.mapToLens()
}
}
private fun setTorchEnabled(enabled: Boolean) {
if (camera?.cameraInfo?.hasFlashUnit() == true) {
camera?.cameraControl?.enableTorch(enabled)
}
}
fun record(options: RecordingOptions, promise: Promise, cacheDirectory: File) {
val file = FileSystemUtils.generateOutputFile(cacheDirectory, "Camera", ".mp4")
val fileOutputOptions = FileOutputOptions.Builder(file)
.setFileSizeLimit(options.maxFileSize.toLong())
.setDurationLimitMillis(options.maxDuration.toLong() * 1000)
.build()
recorder?.let {
if (!mute && ActivityCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
promise.reject(Exceptions.MissingPermissions(Manifest.permission.RECORD_AUDIO))
return
}
activeRecording = it.prepareRecording(context, fileOutputOptions)
.apply {
if (!mute) {
withAudioEnabled()
}
}
.start(ContextCompat.getMainExecutor(context)) { event ->
when (event) {
is VideoRecordEvent.Finalize -> {
when (event.error) {
VideoRecordEvent.Finalize.ERROR_FILE_SIZE_LIMIT_REACHED,
VideoRecordEvent.Finalize.ERROR_DURATION_LIMIT_REACHED,
VideoRecordEvent.Finalize.ERROR_NONE -> {
promise.resolve(
Bundle().apply {
putString("uri", event.outputResults.outputUri.toString())
}
)
}
else -> promise.reject(
CameraExceptions.VideoRecordingFailed(
event.cause?.message
?: "Video recording Failed: ${event.cause?.message ?: "Unknown error"}"
)
)
}
}
}
}
}
?: promise.reject("E_RECORDING_FAILED", "Starting video recording failed - could not create video file.", null)
}
@SuppressLint("UnsafeOptInUsageError")
fun createCamera() {
if (!shouldCreateCamera || previewPaused) {
return
}
shouldCreateCamera = false
providerFuture.addListener(
{
val cameraProvider: ProcessCameraProvider = providerFuture.get()
previewView.scaleType = if (ratio == CameraRatio.FOUR_THREE || ratio == CameraRatio.SIXTEEN_NINE) {
PreviewView.ScaleType.FIT_CENTER
} else {
PreviewView.ScaleType.FILL_CENTER
}
val resolutionSelector = buildResolutionSelector()
val preview = Preview.Builder()
.setResolutionSelector(resolutionSelector)
.build()
.also {
it.surfaceProvider = previewView.surfaceProvider
}
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(lensFacing.mapToCharacteristic())
.build()
imageCaptureUseCase = ImageCapture.Builder()
.setResolutionSelector(resolutionSelector)
.build()
val videoCapture = createVideoCapture()
imageAnalysisUseCase = createImageAnalyzer()
val useCases = UseCaseGroup.Builder().apply {
addUseCase(preview)
if (cameraMode == CameraMode.PICTURE) {
imageCaptureUseCase?.let {
addUseCase(it)
}
imageAnalysisUseCase?.let {
addUseCase(it)
}
} else {
addUseCase(videoCapture)
}
}.build()
try {
cameraProvider.unbindAll()
camera = cameraProvider.bindToLifecycle(currentActivity, cameraSelector, useCases)
camera?.let {
observeCameraState(it.cameraInfo)
}
this.cameraProvider = cameraProvider
} catch (e: Exception) {
onMountError(
CameraMountErrorEvent("Camera component could not be rendered - is there any other instance running?")
)
}
},
ContextCompat.getMainExecutor(context)
)
}
private fun createImageAnalyzer(): ImageAnalysis =
ImageAnalysis.Builder()
.setResolutionSelector(
ResolutionSelector.Builder()
.setResolutionStrategy(ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY)
.build()
)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also { analyzer ->
if (shouldScanBarcodes) {
analyzer.setAnalyzer(
ContextCompat.getMainExecutor(context),
BarcodeAnalyzer(lensFacing, barcodeFormats) {
onBarcodeScanned(it)
}
)
}
}
private fun buildResolutionSelector(): ResolutionSelector {
val strategy = if (pictureSize.isNotEmpty()) {
val size = Size.parseSize(pictureSize)
ResolutionStrategy(size, ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER)
} else {
ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY
}
return if (ratio == CameraRatio.ONE_ONE) {
ResolutionSelector.Builder().setResolutionFilter { supportedSizes, _ ->
return@setResolutionFilter supportedSizes.filter {
it.width == it.height
}
}.setResolutionStrategy(strategy).build()
} else {
ResolutionSelector.Builder().apply {
ratio?.let {
setAspectRatioStrategy(it.mapToStrategy())
}
setResolutionStrategy(strategy)
}.build()
}
}
private fun createVideoCapture(): VideoCapture<Recorder> {
val preferredQuality = videoQuality.mapToQuality()
val fallbackStrategy = FallbackStrategy.higherQualityOrLowerThan(preferredQuality)
val qualitySelector = QualitySelector.from(preferredQuality, fallbackStrategy)
val recorder = Recorder.Builder()
.setExecutor(ContextCompat.getMainExecutor(context))
.setQualitySelector(qualitySelector)
.build()
.also {
this.recorder = it
}
return VideoCapture.Builder(recorder).apply {
if (mirror) {
setMirrorMode(MirrorMode.MIRROR_MODE_ON_FRONT_ONLY)
}
setVideoStabilizationEnabled(true)
}.build()
}
private fun startFocusMetering() {
camera?.let {
val meteringPointFactory = DisplayOrientedMeteringPointFactory(
previewView.display,
it.cameraInfo,
previewView.width.toFloat(),
previewView.height.toFloat()
)
val action = FocusMeteringAction.Builder(meteringPointFactory.createPoint(1f, 1f), FocusMeteringAction.FLAG_AF)
.build()
it.cameraControl.startFocusAndMetering(action)
}
}
private fun observeCameraState(cameraInfo: CameraInfo) {
cameraInfo.cameraState.observe(currentActivity) {
when (it.type) {
CameraState.Type.OPEN -> {
onCameraReady(Unit)
setTorchEnabled(enableTorch)
}
else -> {}
}
}
}
@OptIn(ExperimentalCamera2Interop::class)
fun getAvailablePictureSizes(): List<String> {
return camera?.cameraInfo?.let { cameraInfo ->
val info = Camera2CameraInfo.from(cameraInfo).getCameraCharacteristic(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
info?.getOutputSizes(ImageFormat.JPEG)?.map { it.toString() }
} ?: emptyList()
}
fun resumePreview() {
shouldCreateCamera = true
previewPaused = false
createCamera()
}
fun pausePreview() {
previewPaused = true
cameraProvider?.unbindAll()
}
fun setShouldScanBarcodes(shouldScanBarcodes: Boolean) {
this.shouldScanBarcodes = shouldScanBarcodes
shouldCreateCamera = true
}
fun setBarcodeScannerSettings(settings: BarcodeSettings?) {
barcodeFormats = settings?.barcodeTypes ?: emptyList()
}
private fun getDeviceOrientation() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
appContext.currentActivity?.display?.rotation ?: 0
} else {
(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.rotation
}
fun releaseCamera() = appContext.mainQueue.launch {
cameraProvider?.unbindAll()
}
private fun transformBarcodeScannerResultToViewCoordinates(barcode: BarCodeScannerResult) {
val cornerPoints = barcode.cornerPoints
val previewWidth = previewView.width
val previewHeight = previewView.height
val facingFront = lensFacing == CameraType.FRONT
val portrait = getDeviceOrientation() % 2 == 0
val landscape = getDeviceOrientation() % 2 != 0
if (facingFront && portrait) {
cornerPoints.mapY { barcode.referenceImageHeight - cornerPoints[it] }
}
if (facingFront && landscape) {
cornerPoints.mapX { barcode.referenceImageWidth - cornerPoints[it] }
}
cornerPoints.mapX {
(cornerPoints[it] * previewWidth / barcode.referenceImageWidth.toFloat())
.roundToInt()
}
cornerPoints.mapY {
(cornerPoints[it] * previewHeight / barcode.referenceImageHeight.toFloat())
.roundToInt()
}
barcode.cornerPoints = cornerPoints
barcode.referenceImageHeight = height
barcode.referenceImageWidth = width
}
private fun getCornerPointsAndBoundingBox(cornerPoints: List<Int>, boundingBox: BoundingBox): Pair<ArrayList<Bundle>, Bundle> {
val density = previewView.resources.displayMetrics.density
val convertedCornerPoints = ArrayList<Bundle>()
for (i in cornerPoints.indices step 2) {
val y = cornerPoints[i].toFloat() / density
val x = cornerPoints[i + 1].toFloat() / density
convertedCornerPoints.add(
Bundle().apply {
putFloat("x", x)
putFloat("y", y)
}
)
}
val boundingBoxBundle = Bundle().apply {
putParcelable(
"origin",
Bundle().apply {
putFloat("x", boundingBox.x.toFloat() / density)
putFloat("y", boundingBox.y.toFloat() / density)
}
)
putParcelable(
"size",
Bundle().apply {
putFloat("width", boundingBox.width.toFloat() / density)
putFloat("height", boundingBox.height.toFloat() / density)
}
)
}
return convertedCornerPoints to boundingBoxBundle
}
private fun onBarcodeScanned(barcode: BarCodeScannerResult) {
if (shouldScanBarcodes) {
transformBarcodeScannerResultToViewCoordinates(barcode)
val (cornerPoints, boundingBox) = getCornerPointsAndBoundingBox(barcode.cornerPoints, barcode.boundingBox)
onBarcodeScanned(
BarcodeScannedEvent(
target = id,
data = barcode.value,
raw = barcode.raw,
type = BarcodeType.mapFormatToString(barcode.type),
cornerPoints = cornerPoints,
boundingBox = boundingBox
)
)
}
}
override fun setPreviewTexture(surfaceTexture: SurfaceTexture?) = Unit
override fun getPreviewSizeAsArray() = intArrayOf(previewView.width, previewView.height)
init {
orientationEventListener.enable()
previewView.setOnHierarchyChangeListener(object : OnHierarchyChangeListener {
override fun onChildViewRemoved(parent: View?, child: View?) = Unit
override fun onChildViewAdded(parent: View?, child: View?) {
parent?.measure(
MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)
)
parent?.layout(0, 0, parent.measuredWidth, parent.measuredHeight)
}
})
addView(
previewView,
ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
)
}
fun onPictureSaved(response: Bundle) {
onPictureSaved(PictureSavedEvent(response.getInt("id"), response.getBundle("data")!!))
}
fun cancelCoroutineScope() = try {
scope.cancel(ModuleDestroyedException())
} catch (e: Exception) {
Log.e(CameraViewModule.TAG, "The scope does not have a job in it")
}
}

View File

@@ -0,0 +1,23 @@
package expo.modules.camera
import expo.modules.camera.records.VideoQuality
import expo.modules.kotlin.records.Field
import expo.modules.kotlin.records.Record
data class PictureOptions(
@Field val quality: Double = 1.0,
@Field val base64: Boolean = false,
@Field val exif: Boolean = false,
@Field val additionalExif: Map<String, Any>? = null,
@Field val mirror: Boolean = false,
@Field val skipProcessing: Boolean = false,
@Field val fastMode: Boolean = false,
@Field val id: Int? = null,
@Field val maxDownsampling: Int = 1
) : Record
data class RecordingOptions(
@Field val maxDuration: Int = 0,
@Field val maxFileSize: Int = 0,
@Field val quality: VideoQuality?
) : Record

View File

@@ -0,0 +1,76 @@
package expo.modules.camera.analyzers
import android.util.Log
import androidx.annotation.OptIn
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.common.InputImage
import expo.modules.camera.CameraViewHelper
import expo.modules.camera.records.BarcodeType
import expo.modules.camera.records.CameraType
import expo.modules.interfaces.barcodescanner.BarCodeScannerResult
import java.nio.ByteBuffer
@OptIn(ExperimentalGetImage::class)
class BarcodeAnalyzer(private val lensFacing: CameraType, formats: List<BarcodeType>, val onComplete: (BarCodeScannerResult) -> Unit) : ImageAnalysis.Analyzer {
private val barcodeFormats = if (formats.isEmpty()) {
0
} else {
formats.map { it.mapToBarcode() }.reduce { acc, it ->
acc or it
}
}
private var barcodeScannerOptions =
BarcodeScannerOptions.Builder()
.setBarcodeFormats(barcodeFormats)
.build()
private var barcodeScanner = BarcodeScanning.getClient(barcodeScannerOptions)
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image
if (mediaImage != null) {
val rotation = CameraViewHelper.getCorrectCameraRotation(imageProxy.imageInfo.rotationDegrees, lensFacing)
val image = InputImage.fromMediaImage(mediaImage, rotation)
barcodeScanner.process(image)
.addOnSuccessListener { barcodes ->
if (barcodes.isEmpty()) {
return@addOnSuccessListener
}
val barcode = barcodes.first()
val raw = barcode.rawValue ?: barcode.rawBytes?.let { String(it) }
val cornerPoints = mutableListOf<Int>()
barcode.cornerPoints?.let { points ->
for (point in points) {
cornerPoints.addAll(listOf(point.x, point.y))
}
}
onComplete(BarCodeScannerResult(barcode.format, barcode.displayValue, raw, cornerPoints, image.width, image.height))
}
.addOnFailureListener {
Log.d("SCANNER", it.cause?.message ?: "Barcode scanning failed")
}
.addOnCompleteListener {
imageProxy.close()
}
}
}
}
private fun ByteBuffer.toByteArray(): ByteArray {
rewind()
val data = ByteArray(remaining())
get(data)
return data
}
fun Array<ImageProxy.PlaneProxy>.toByteArray() = this.fold(mutableListOf<Byte>()) { acc, plane ->
acc.addAll(plane.buffer.toByteArray().toList())
acc
}.toByteArray()

View File

@@ -0,0 +1,48 @@
package expo.modules.camera.analyzers
import android.os.Bundle
import android.util.Pair
import expo.modules.interfaces.barcodescanner.BarCodeScannerResult
object BarCodeScannerResultSerializer {
fun toBundle(result: BarCodeScannerResult, density: Float) =
Bundle().apply {
putString("data", result.value)
putString("raw", result.raw)
putInt("type", result.type)
val cornerPointsAndBoundingBox = getCornerPointsAndBoundingBox(result.cornerPoints, result.boundingBox, density)
putParcelableArrayList("cornerPoints", cornerPointsAndBoundingBox.first)
putBundle("bounds", cornerPointsAndBoundingBox.second)
}
private fun getCornerPointsAndBoundingBox(
cornerPoints: List<Int>,
boundingBox: BarCodeScannerResult.BoundingBox,
density: Float
): Pair<ArrayList<Bundle>, Bundle> {
val convertedCornerPoints = ArrayList<Bundle>()
for (i in cornerPoints.indices step 2) {
val x = cornerPoints[i].toFloat() / density
val y = cornerPoints[i + 1].toFloat() / density
convertedCornerPoints.add(getPoint(x, y))
}
val boundingBoxBundle = Bundle().apply {
putParcelable("origin", getPoint(boundingBox.x.toFloat() / density, boundingBox.y.toFloat() / density))
putParcelable("size", getSize(boundingBox.width.toFloat() / density, boundingBox.height.toFloat() / density))
}
return Pair(convertedCornerPoints, boundingBoxBundle)
}
private fun getSize(width: Float, height: Float) =
Bundle().apply {
putFloat("width", width)
putFloat("height", height)
}
private fun getPoint(x: Float, y: Float) =
Bundle().apply {
putFloat("x", x)
putFloat("y", y)
}
}

View File

@@ -0,0 +1,102 @@
package expo.modules.camera.analyzers
import android.graphics.Bitmap
import android.util.Log
import com.google.android.gms.tasks.Task
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
import expo.modules.interfaces.barcodescanner.BarCodeScannerResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class MLKitBarCodeScanner {
private var barCodeTypes: List<Int>? = null
private var barcodeScannerOptions =
BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS)
.build()
private var barcodeScanner = BarcodeScanning.getClient(barcodeScannerOptions)
suspend fun scan(bitmap: Bitmap): List<BarCodeScannerResult> = withContext(Dispatchers.IO) {
val inputImage = InputImage.fromBitmap(bitmap, 0)
try {
val result: List<Barcode> = barcodeScanner.process(inputImage).await()
val results = mutableListOf<BarCodeScannerResult>()
if (result.isEmpty()) {
return@withContext results
}
for (barcode in result) {
val raw = barcode.rawValue ?: barcode.rawBytes?.let { String(it) }
val value = if (barcode.valueType == Barcode.TYPE_CONTACT_INFO) {
raw
} else {
barcode.displayValue
}
val cornerPoints = mutableListOf<Int>()
barcode.cornerPoints?.let { points ->
for (point in points) {
cornerPoints.addAll(listOf(point.x, point.y))
}
}
results.add(BarCodeScannerResult(barcode.format, value, raw, cornerPoints, inputImage.height, inputImage.width))
}
return@withContext results
} catch (e: Exception) {
Log.e(TAG, "Failed to detect barcode: " + e.message)
return@withContext emptyList()
}
}
fun setSettings(formats: List<Int>) {
if (areNewAndOldBarCodeTypesEqual(formats)) {
return
}
val barcodeFormats = formats.reduce { acc, it ->
acc or it
}
barCodeTypes = formats
barcodeScannerOptions = BarcodeScannerOptions.Builder()
.setBarcodeFormats(barcodeFormats)
.build()
barcodeScanner = BarcodeScanning.getClient(barcodeScannerOptions)
}
private fun areNewAndOldBarCodeTypesEqual(newBarCodeTypes: List<Int>): Boolean {
barCodeTypes?.run {
// create distinct-values sets
val prevTypesSet = toHashSet()
val nextTypesSet = newBarCodeTypes.toHashSet()
// sets sizes are equal -> possible content equality
if (prevTypesSet.size == nextTypesSet.size) {
prevTypesSet.removeAll(nextTypesSet)
// every element from new set was in previous one -> sets are equal
return prevTypesSet.isEmpty()
}
}
return false
}
companion object {
private val TAG = MLKitBarCodeScanner::class.java.simpleName
}
}
suspend fun <T> Task<T>.await(): T = suspendCancellableCoroutine { continuation ->
addOnSuccessListener { result ->
continuation.resume(result)
}
addOnFailureListener { exception ->
continuation.resumeWithException(exception)
}
addOnCanceledListener {
continuation.cancel()
}
}

View File

@@ -0,0 +1,23 @@
package expo.modules.camera.common
import android.os.Bundle
import expo.modules.kotlin.records.Field
import expo.modules.kotlin.records.Record
class BarcodeScannedEvent(
@Field val target: Int,
@Field val data: String,
@Field val raw: String,
@Field val type: String,
@Field val cornerPoints: ArrayList<Bundle>,
@Field val boundingBox: Bundle
) : Record
class CameraMountErrorEvent(
@Field val message: String
) : Record
class PictureSavedEvent(
@Field val id: Int,
@Field val data: Bundle
) : Record

View File

@@ -0,0 +1,7 @@
package expo.modules.camera.legacy
import expo.modules.kotlin.exception.CodedException
class CameraExceptions {
class CameraIsNotRunning : CodedException(message = "Camera is not running")
}

View File

@@ -0,0 +1,128 @@
package expo.modules.camera.legacy
import androidx.exifinterface.media.ExifInterface
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.media.CamcorderProfile
import android.os.Bundle
import com.google.android.cameraview.CameraView
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*
object CameraViewHelper {
// Utilities
@JvmStatic
fun getCorrectCameraRotation(rotation: Int, facing: Int) =
if (facing == CameraView.FACING_FRONT) {
(rotation - 90 + 360) % 360
} else {
(-rotation + 90 + 360) % 360
}
@JvmStatic
fun getCamcorderProfile(cameraId: Int, quality: Int): CamcorderProfile {
var profile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH)
when (quality) {
VIDEO_2160P -> profile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_2160P)
VIDEO_1080P -> profile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_1080P)
VIDEO_720P -> profile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_720P)
VIDEO_480P -> profile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_480P)
VIDEO_4x3 -> {
profile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_480P)
profile.videoFrameWidth = 640
}
}
return profile
}
@JvmStatic
fun getExifData(exifInterface: ExifInterface): Bundle {
val exifMap = Bundle()
for ((type, name) in exifTags) {
if (exifInterface.getAttribute(name) != null) {
when (type) {
"string" -> exifMap.putString(name, exifInterface.getAttribute(name))
"int" -> exifMap.putInt(name, exifInterface.getAttributeInt(name, 0))
"double" -> exifMap.putDouble(name, exifInterface.getAttributeDouble(name, 0.0))
}
}
}
exifInterface.latLong?.let {
exifMap.putDouble(ExifInterface.TAG_GPS_LATITUDE, it[0])
exifMap.putDouble(ExifInterface.TAG_GPS_LONGITUDE, it[1])
exifMap.putDouble(ExifInterface.TAG_GPS_ALTITUDE, exifInterface.getAltitude(0.0))
}
return exifMap
}
@JvmStatic
@Throws(IllegalArgumentException::class)
fun setExifData(baseExif: ExifInterface, exifMap: Map<String, Any>) {
for ((_, name) in exifTags) {
exifMap[name]?.let {
// Convert possible type to string before putting into baseExif
when (it) {
is String -> baseExif.setAttribute(name, it)
is Number -> baseExif.setAttribute(name, it.toDouble().toBigDecimal().toPlainString())
is Boolean -> baseExif.setAttribute(name, it.toString())
}
}
}
if (exifMap.containsKey(ExifInterface.TAG_GPS_LATITUDE) &&
exifMap.containsKey(ExifInterface.TAG_GPS_LONGITUDE) &&
exifMap[ExifInterface.TAG_GPS_LATITUDE] is Number &&
exifMap[ExifInterface.TAG_GPS_LONGITUDE] is Number
) {
baseExif.setLatLong(
exifMap[ExifInterface.TAG_GPS_LATITUDE] as Double,
exifMap[ExifInterface.TAG_GPS_LONGITUDE] as Double
)
}
if (exifMap.containsKey(ExifInterface.TAG_GPS_ALTITUDE) &&
exifMap[ExifInterface.TAG_GPS_ALTITUDE] is Number
) {
baseExif.setAltitude(exifMap[ExifInterface.TAG_GPS_ALTITUDE] as Double)
}
}
@JvmStatic
@Throws(IOException::class)
fun addExifData(baseExif: ExifInterface, additionalExif: ExifInterface) {
for (tagInfo in exifTags) {
val name = tagInfo[1]
additionalExif.getAttribute(name)?.let {
baseExif.setAttribute(name, it)
}
}
baseExif.saveAttributes()
}
fun generateSimulatorPhoto(width: Int, height: Int): ByteArray {
val fakePhotoBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(fakePhotoBitmap)
val background = Paint().apply {
color = Color.BLACK
}
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), background)
val textPaint = Paint().apply {
color = Color.YELLOW
textSize = 35f
}
val calendar = Calendar.getInstance()
val simpleDateFormat = SimpleDateFormat("dd.MM.yy HH:mm:ss", Locale.US)
canvas.drawText(simpleDateFormat.format(calendar.time), width * 0.1f, height * 0.9f, textPaint)
val stream = ByteArrayOutputStream()
fakePhotoBitmap.compress(Bitmap.CompressFormat.PNG, 90, stream)
val fakePhotoByteArray = stream.toByteArray()
return fakePhotoByteArray
}
}

View File

@@ -0,0 +1,265 @@
package expo.modules.camera.legacy
import android.Manifest
import com.google.android.cameraview.AspectRatio
import com.google.android.cameraview.Size
import expo.modules.camera.legacy.tasks.ResolveTakenPictureAsyncTask
import expo.modules.core.interfaces.services.UIManager
import expo.modules.core.utilities.EmulatorUtilities
import expo.modules.interfaces.barcodescanner.BarCodeScannerSettings
import expo.modules.interfaces.permissions.Permissions
import expo.modules.kotlin.Promise
import expo.modules.kotlin.exception.Exceptions
import expo.modules.kotlin.functions.Queues
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import java.io.File
class CameraViewLegacyModule : Module() {
override fun definition() = ModuleDefinition {
Name("ExpoCameraLegacy")
Constants(
"Type" to mapOf(
"front" to com.google.android.cameraview.Constants.FACING_FRONT,
"back" to com.google.android.cameraview.Constants.FACING_BACK
),
"FlashMode" to mapOf(
"off" to com.google.android.cameraview.Constants.FLASH_OFF,
"on" to com.google.android.cameraview.Constants.FLASH_ON,
"auto" to com.google.android.cameraview.Constants.FLASH_AUTO,
"torch" to com.google.android.cameraview.Constants.FLASH_TORCH
),
"AutoFocus" to mapOf(
"on" to true,
"off" to false
),
"WhiteBalance" to mapOf(
"auto" to com.google.android.cameraview.Constants.WB_AUTO,
"cloudy" to com.google.android.cameraview.Constants.WB_CLOUDY,
"sunny" to com.google.android.cameraview.Constants.WB_SUNNY,
"shadow" to com.google.android.cameraview.Constants.WB_SHADOW,
"fluorescent" to com.google.android.cameraview.Constants.WB_FLUORESCENT,
"incandescent" to com.google.android.cameraview.Constants.WB_INCANDESCENT
),
"VideoQuality" to mapOf(
"2160p" to VIDEO_2160P,
"1080p" to VIDEO_1080P,
"720p" to VIDEO_720P,
"480p" to VIDEO_480P,
"4:3" to VIDEO_4x3
)
)
AsyncFunction("pausePreview") { viewTag: Int ->
val view = findView(viewTag)
if (view.cameraView.isCameraOpened) {
view.cameraView.pausePreview()
}
}.runOnQueue(Queues.MAIN)
AsyncFunction("resumePreview") { viewTag: Int ->
val view = findView(viewTag)
if (view.cameraView.isCameraOpened) {
view.cameraView.resumePreview()
}
}.runOnQueue(Queues.MAIN)
AsyncFunction("takePicture") { options: PictureOptions, viewTag: Int, promise: Promise ->
val view = findView(viewTag)
if (!EmulatorUtilities.isRunningOnEmulator()) {
if (!view.cameraView.isCameraOpened) {
throw CameraExceptions.CameraIsNotRunning()
}
view.takePicture(options, promise, cacheDirectory)
} else {
val image = CameraViewHelper.generateSimulatorPhoto(view.width, view.height)
ResolveTakenPictureAsyncTask(image, promise, options, cacheDirectory, view).execute()
}
}.runOnQueue(Queues.MAIN)
AsyncFunction("record") { options: RecordingOptions, viewTag: Int, promise: Promise ->
if (!options.mute && !permissionsManager.hasGrantedPermissions(Manifest.permission.RECORD_AUDIO)) {
throw Exceptions.MissingPermissions(Manifest.permission.RECORD_AUDIO)
}
val view = findView(viewTag)
if (!view.cameraView.isCameraOpened) {
throw CameraExceptions.CameraIsNotRunning()
}
view.record(options, promise, cacheDirectory)
}.runOnQueue(Queues.MAIN)
AsyncFunction("stopRecording") { viewTag: Int ->
val view = findView(viewTag)
if (view.cameraView.isCameraOpened) {
view.cameraView.stopRecording()
}
}.runOnQueue(Queues.MAIN)
AsyncFunction("getSupportedRatios") { viewTag: Int ->
val view = findView(viewTag)
if (!view.cameraView.isCameraOpened) {
throw CameraExceptions.CameraIsNotRunning()
}
return@AsyncFunction view.cameraView.supportedAspectRatios.map { it.toString() }
}.runOnQueue(Queues.MAIN)
AsyncFunction("getAvailablePictureSizes") { ratio: String, viewTag: Int ->
val view = findView(viewTag)
if (!view.cameraView.isCameraOpened) {
throw CameraExceptions.CameraIsNotRunning()
}
val sizes = view.cameraView.getAvailablePictureSizes(AspectRatio.parse(ratio))
return@AsyncFunction sizes.map { it.toString() }
}.runOnQueue(Queues.MAIN)
AsyncFunction("requestPermissionsAsync") { promise: Promise ->
Permissions.askForPermissionsWithPermissionsManager(
permissionsManager,
promise,
Manifest.permission.CAMERA
)
}
AsyncFunction("requestCameraPermissionsAsync") { promise: Promise ->
Permissions.askForPermissionsWithPermissionsManager(
permissionsManager,
promise,
Manifest.permission.CAMERA
)
}
AsyncFunction("requestMicrophonePermissionsAsync") { promise: Promise ->
Permissions.askForPermissionsWithPermissionsManager(
permissionsManager,
promise,
Manifest.permission.RECORD_AUDIO
)
}
AsyncFunction("getPermissionsAsync") { promise: Promise ->
Permissions.getPermissionsWithPermissionsManager(
permissionsManager,
promise,
Manifest.permission.CAMERA
)
}
AsyncFunction("getCameraPermissionsAsync") { promise: Promise ->
Permissions.getPermissionsWithPermissionsManager(
permissionsManager,
promise,
Manifest.permission.CAMERA
)
}
AsyncFunction("getMicrophonePermissionsAsync") { promise: Promise ->
Permissions.getPermissionsWithPermissionsManager(
permissionsManager,
promise,
Manifest.permission.RECORD_AUDIO
)
}
View(ExpoCameraView::class) {
Events(
"onCameraReady",
"onMountError",
"onBarCodeScanned",
"onFacesDetected",
"onFaceDetectionError",
"onPictureSaved"
)
OnViewDestroys<ExpoCameraView> { view ->
val uiManager = appContext.legacyModule<UIManager>()
uiManager?.unregisterLifecycleEventListener(view)
view.cameraView.stop()
}
Prop("type") { view: ExpoCameraView, type: Int ->
view.cameraView.facing = type
}
Prop("ratio") { view: ExpoCameraView, ratio: String? ->
if (ratio == null) {
return@Prop
}
view.cameraView.setAspectRatio(AspectRatio.parse(ratio))
}
Prop("flashMode") { view: ExpoCameraView, torchMode: Int ->
view.cameraView.flash = torchMode
}
Prop("autoFocus") { view: ExpoCameraView, autoFocus: Boolean ->
view.cameraView.autoFocus = autoFocus
}
Prop("focusDepth") { view: ExpoCameraView, depth: Float ->
view.cameraView.focusDepth = depth
}
Prop("zoom") { view: ExpoCameraView, zoom: Float ->
view.cameraView.zoom = zoom
}
Prop("whiteBalance") { view: ExpoCameraView, whiteBalance: Int ->
view.cameraView.whiteBalance = whiteBalance
}
Prop("pictureSize") { view: ExpoCameraView, size: String? ->
if (size == null) {
return@Prop
}
view.cameraView.pictureSize = Size.parse(size)
}
Prop("barCodeScannerSettings") { view: ExpoCameraView, settings: Map<String, Any?>? ->
if (settings == null) {
return@Prop
}
view.setBarCodeScannerSettings(BarCodeScannerSettings(settings))
}
Prop("useCamera2Api") { view: ExpoCameraView, useCamera2Api: Boolean ->
view.cameraView.setUsingCamera2Api(useCamera2Api)
}
Prop("barCodeScannerEnabled") { view: ExpoCameraView, barCodeScannerEnabled: Boolean? ->
view.setShouldScanBarCodes(barCodeScannerEnabled ?: false)
}
Prop("faceDetectorEnabled") { view: ExpoCameraView, faceDetectorEnabled: Boolean? ->
view.setShouldDetectFaces(faceDetectorEnabled ?: false)
}
Prop("faceDetectorSettings") { view: ExpoCameraView, settings: Map<String, Any>? ->
view.setFaceDetectorSettings(settings)
}
}
}
private val cacheDirectory: File
get() = appContext.cacheDirectory
private val permissionsManager: Permissions
get() = appContext.permissions ?: throw Exceptions.PermissionsModuleNotFound()
private fun findView(viewTag: Int): ExpoCameraView {
return appContext.findView(viewTag)
?: throw Exceptions.ViewNotFound(ExpoCameraView::class, viewTag)
}
}

View File

@@ -0,0 +1,15 @@
package expo.modules.camera.legacy
import android.os.Bundle
import expo.modules.kotlin.records.Field
import expo.modules.kotlin.records.Record
data class FaceDetectionErrorEvent(
@Field val isOperational: Boolean
) : Record
data class FacesDetectedEvent(
@Field val type: String,
@Field val faces: List<Bundle>,
@Field val target: Int
) : Record

View File

@@ -0,0 +1,145 @@
package expo.modules.camera.legacy
import androidx.exifinterface.media.ExifInterface
const val VIDEO_2160P = 0
const val VIDEO_1080P = 1
const val VIDEO_720P = 2
const val VIDEO_480P = 3
const val VIDEO_4x3 = 4
val exifTags = arrayOf(
arrayOf("string", ExifInterface.TAG_ARTIST),
arrayOf("int", ExifInterface.TAG_BITS_PER_SAMPLE),
arrayOf("int", ExifInterface.TAG_COMPRESSION),
arrayOf("string", ExifInterface.TAG_COPYRIGHT),
arrayOf("string", ExifInterface.TAG_DATETIME),
arrayOf("string", ExifInterface.TAG_IMAGE_DESCRIPTION),
arrayOf("int", ExifInterface.TAG_IMAGE_LENGTH),
arrayOf("int", ExifInterface.TAG_IMAGE_WIDTH),
arrayOf("int", ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT),
arrayOf("int", ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH),
arrayOf("string", ExifInterface.TAG_MAKE),
arrayOf("string", ExifInterface.TAG_MODEL),
arrayOf("int", ExifInterface.TAG_ORIENTATION),
arrayOf("int", ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION),
arrayOf("int", ExifInterface.TAG_PLANAR_CONFIGURATION),
arrayOf("double", ExifInterface.TAG_PRIMARY_CHROMATICITIES),
arrayOf("double", ExifInterface.TAG_REFERENCE_BLACK_WHITE),
arrayOf("int", ExifInterface.TAG_RESOLUTION_UNIT),
arrayOf("int", ExifInterface.TAG_ROWS_PER_STRIP),
arrayOf("int", ExifInterface.TAG_SAMPLES_PER_PIXEL),
arrayOf("string", ExifInterface.TAG_SOFTWARE),
arrayOf("int", ExifInterface.TAG_STRIP_BYTE_COUNTS),
arrayOf("int", ExifInterface.TAG_STRIP_OFFSETS),
arrayOf("int", ExifInterface.TAG_TRANSFER_FUNCTION),
arrayOf("double", ExifInterface.TAG_WHITE_POINT),
arrayOf("double", ExifInterface.TAG_X_RESOLUTION),
arrayOf("double", ExifInterface.TAG_Y_CB_CR_COEFFICIENTS),
arrayOf("int", ExifInterface.TAG_Y_CB_CR_POSITIONING),
arrayOf("int", ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING),
arrayOf("double", ExifInterface.TAG_Y_RESOLUTION),
arrayOf("double", ExifInterface.TAG_APERTURE_VALUE),
arrayOf("double", ExifInterface.TAG_BRIGHTNESS_VALUE),
arrayOf("string", ExifInterface.TAG_CFA_PATTERN),
arrayOf("int", ExifInterface.TAG_COLOR_SPACE),
arrayOf("string", ExifInterface.TAG_COMPONENTS_CONFIGURATION),
arrayOf("double", ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL),
arrayOf("int", ExifInterface.TAG_CONTRAST),
arrayOf("int", ExifInterface.TAG_CUSTOM_RENDERED),
arrayOf("string", ExifInterface.TAG_DATETIME_DIGITIZED),
arrayOf("string", ExifInterface.TAG_DATETIME_ORIGINAL),
arrayOf("string", ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION),
arrayOf("double", ExifInterface.TAG_DIGITAL_ZOOM_RATIO),
arrayOf("string", ExifInterface.TAG_EXIF_VERSION),
arrayOf("double", ExifInterface.TAG_EXPOSURE_BIAS_VALUE),
arrayOf("double", ExifInterface.TAG_EXPOSURE_INDEX),
arrayOf("int", ExifInterface.TAG_EXPOSURE_MODE),
arrayOf("int", ExifInterface.TAG_EXPOSURE_PROGRAM),
arrayOf("double", ExifInterface.TAG_EXPOSURE_TIME),
arrayOf("double", ExifInterface.TAG_F_NUMBER),
arrayOf("string", ExifInterface.TAG_FILE_SOURCE),
arrayOf("int", ExifInterface.TAG_FLASH),
arrayOf("double", ExifInterface.TAG_FLASH_ENERGY),
arrayOf("string", ExifInterface.TAG_FLASHPIX_VERSION),
arrayOf("double", ExifInterface.TAG_FOCAL_LENGTH),
arrayOf("int", ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM),
arrayOf("int", ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT),
arrayOf("double", ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION),
arrayOf("double", ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION),
arrayOf("int", ExifInterface.TAG_GAIN_CONTROL),
arrayOf("int", ExifInterface.TAG_ISO_SPEED_RATINGS),
arrayOf("string", ExifInterface.TAG_IMAGE_UNIQUE_ID),
arrayOf("int", ExifInterface.TAG_LIGHT_SOURCE),
arrayOf("string", ExifInterface.TAG_MAKER_NOTE),
arrayOf("double", ExifInterface.TAG_MAX_APERTURE_VALUE),
arrayOf("int", ExifInterface.TAG_METERING_MODE),
arrayOf("int", ExifInterface.TAG_NEW_SUBFILE_TYPE),
arrayOf("string", ExifInterface.TAG_OECF),
arrayOf("int", ExifInterface.TAG_PIXEL_X_DIMENSION),
arrayOf("int", ExifInterface.TAG_PIXEL_Y_DIMENSION),
arrayOf("string", ExifInterface.TAG_RELATED_SOUND_FILE),
arrayOf("int", ExifInterface.TAG_SATURATION),
arrayOf("int", ExifInterface.TAG_SCENE_CAPTURE_TYPE),
arrayOf("string", ExifInterface.TAG_SCENE_TYPE),
arrayOf("int", ExifInterface.TAG_SENSING_METHOD),
arrayOf("int", ExifInterface.TAG_SHARPNESS),
arrayOf("double", ExifInterface.TAG_SHUTTER_SPEED_VALUE),
arrayOf("string", ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE),
arrayOf("string", ExifInterface.TAG_SPECTRAL_SENSITIVITY),
arrayOf("int", ExifInterface.TAG_SUBFILE_TYPE),
arrayOf("string", ExifInterface.TAG_SUBSEC_TIME),
arrayOf("string", ExifInterface.TAG_SUBSEC_TIME_DIGITIZED),
arrayOf("string", ExifInterface.TAG_SUBSEC_TIME_ORIGINAL),
arrayOf("int", ExifInterface.TAG_SUBJECT_AREA),
arrayOf("double", ExifInterface.TAG_SUBJECT_DISTANCE),
arrayOf("int", ExifInterface.TAG_SUBJECT_DISTANCE_RANGE),
arrayOf("int", ExifInterface.TAG_SUBJECT_LOCATION),
arrayOf("string", ExifInterface.TAG_USER_COMMENT),
arrayOf("int", ExifInterface.TAG_WHITE_BALANCE),
arrayOf("double", ExifInterface.TAG_GPS_ALTITUDE),
arrayOf("int", ExifInterface.TAG_GPS_ALTITUDE_REF),
arrayOf("string", ExifInterface.TAG_GPS_AREA_INFORMATION),
arrayOf("double", ExifInterface.TAG_GPS_DOP),
arrayOf("string", ExifInterface.TAG_GPS_DATESTAMP),
arrayOf("double", ExifInterface.TAG_GPS_DEST_BEARING),
arrayOf("string", ExifInterface.TAG_GPS_DEST_BEARING_REF),
arrayOf("double", ExifInterface.TAG_GPS_DEST_DISTANCE),
arrayOf("string", ExifInterface.TAG_GPS_DEST_DISTANCE_REF),
arrayOf("double", ExifInterface.TAG_GPS_DEST_LATITUDE),
arrayOf("string", ExifInterface.TAG_GPS_DEST_LATITUDE_REF),
arrayOf("double", ExifInterface.TAG_GPS_DEST_LONGITUDE),
arrayOf("string", ExifInterface.TAG_GPS_DEST_LONGITUDE_REF),
arrayOf("int", ExifInterface.TAG_GPS_DIFFERENTIAL),
arrayOf("string", ExifInterface.TAG_GPS_H_POSITIONING_ERROR),
arrayOf("double", ExifInterface.TAG_GPS_IMG_DIRECTION),
arrayOf("string", ExifInterface.TAG_GPS_IMG_DIRECTION_REF),
arrayOf("double", ExifInterface.TAG_GPS_LATITUDE),
arrayOf("string", ExifInterface.TAG_GPS_LATITUDE_REF),
arrayOf("double", ExifInterface.TAG_GPS_LONGITUDE),
arrayOf("string", ExifInterface.TAG_GPS_LONGITUDE_REF),
arrayOf("string", ExifInterface.TAG_GPS_MAP_DATUM),
arrayOf("string", ExifInterface.TAG_GPS_MEASURE_MODE),
arrayOf("string", ExifInterface.TAG_GPS_PROCESSING_METHOD),
arrayOf("string", ExifInterface.TAG_GPS_SATELLITES),
arrayOf("double", ExifInterface.TAG_GPS_SPEED),
arrayOf("string", ExifInterface.TAG_GPS_SPEED_REF),
arrayOf("string", ExifInterface.TAG_GPS_STATUS),
arrayOf("string", ExifInterface.TAG_GPS_TIMESTAMP),
arrayOf("double", ExifInterface.TAG_GPS_TRACK),
arrayOf("string", ExifInterface.TAG_GPS_TRACK_REF),
arrayOf("string", ExifInterface.TAG_GPS_VERSION_ID),
arrayOf("string", ExifInterface.TAG_INTEROPERABILITY_INDEX),
arrayOf("int", ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH),
arrayOf("int", ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH),
arrayOf("int", ExifInterface.TAG_DNG_VERSION),
arrayOf("int", ExifInterface.TAG_DEFAULT_CROP_SIZE),
arrayOf("int", ExifInterface.TAG_ORF_PREVIEW_IMAGE_START),
arrayOf("int", ExifInterface.TAG_ORF_PREVIEW_IMAGE_LENGTH),
arrayOf("int", ExifInterface.TAG_ORF_ASPECT_FRAME),
arrayOf("int", ExifInterface.TAG_RW2_SENSOR_BOTTOM_BORDER),
arrayOf("int", ExifInterface.TAG_RW2_SENSOR_LEFT_BORDER),
arrayOf("int", ExifInterface.TAG_RW2_SENSOR_RIGHT_BORDER),
arrayOf("int", ExifInterface.TAG_RW2_SENSOR_TOP_BORDER),
arrayOf("int", ExifInterface.TAG_RW2_ISO)
)

View File

@@ -0,0 +1,435 @@
package expo.modules.camera.legacy
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.graphics.SurfaceTexture
import android.net.Uri
import android.os.Bundle
import android.view.View
import com.google.android.cameraview.CameraView
import expo.modules.camera.legacy.CameraViewHelper.getCamcorderProfile
import expo.modules.camera.legacy.CameraViewHelper.getCorrectCameraRotation
import expo.modules.camera.legacy.tasks.BarCodeScannerAsyncTask
import expo.modules.camera.legacy.tasks.BarCodeScannerAsyncTaskDelegate
import expo.modules.camera.legacy.tasks.FaceDetectorAsyncTaskDelegate
import expo.modules.camera.legacy.tasks.FaceDetectorTask
import expo.modules.camera.legacy.tasks.PictureSavedDelegate
import expo.modules.camera.legacy.tasks.ResolveTakenPictureAsyncTask
import expo.modules.camera.legacy.utils.FileSystemUtils
import expo.modules.camera.legacy.utils.ImageDimensions
import expo.modules.core.interfaces.LifecycleEventListener
import expo.modules.core.interfaces.services.UIManager
import expo.modules.core.utilities.EmulatorUtilities
import expo.modules.interfaces.barcodescanner.BarCodeScannerInterface
import expo.modules.interfaces.barcodescanner.BarCodeScannerProviderInterface
import expo.modules.interfaces.barcodescanner.BarCodeScannerResult
import expo.modules.interfaces.barcodescanner.BarCodeScannerSettings
import expo.modules.interfaces.camera.CameraViewInterface
import expo.modules.interfaces.facedetector.FaceDetectorInterface
import expo.modules.interfaces.facedetector.FaceDetectorProviderInterface
import expo.modules.kotlin.AppContext
import expo.modules.kotlin.Promise
import expo.modules.kotlin.views.ExpoView
import java.io.File
import java.io.IOException
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue
import expo.modules.camera.utils.mapX
import expo.modules.camera.utils.mapY
import kotlin.math.roundToInt
import android.view.WindowManager
import expo.modules.camera.common.BarcodeScannedEvent
import expo.modules.camera.common.CameraMountErrorEvent
import expo.modules.camera.common.PictureSavedEvent
import expo.modules.camera.records.BarcodeType
import expo.modules.interfaces.barcodescanner.BarCodeScannerResult.BoundingBox
import expo.modules.kotlin.viewevent.EventDispatcher
@SuppressLint("ViewConstructor")
class ExpoCameraView(
context: Context,
appContext: AppContext
) : ExpoView(context, appContext),
LifecycleEventListener,
BarCodeScannerAsyncTaskDelegate,
FaceDetectorAsyncTaskDelegate,
PictureSavedDelegate,
CameraViewInterface {
internal val cameraView = CameraView(context, true)
private val pictureTakenPromises: Queue<Promise> = ConcurrentLinkedQueue()
private val pictureTakenOptions: MutableMap<Promise, PictureOptions> = ConcurrentHashMap()
private val pictureTakenDirectories: MutableMap<Promise, File> = ConcurrentHashMap()
private var videoRecordedPromise: Promise? = null
private var isPaused = false
private var isNew = true
private val onCameraReady by EventDispatcher<Unit>()
private val onMountError by EventDispatcher<CameraMountErrorEvent>()
private val onBarCodeScanned by EventDispatcher<BarcodeScannedEvent>(
/**
* We want every distinct barcode to be reported to the JS listener.
* If we return some static value as a coalescing key there may be two barcode events
* containing two different barcodes waiting to be transmitted to JS
* that would get coalesced (because both of them would have the same coalescing key).
* So let's differentiate them with a hash of the contents (mod short's max value).
*/
coalescingKey = { event -> (event.data.hashCode() % Short.MAX_VALUE).toShort() }
)
private val onFacesDetected by EventDispatcher<FacesDetectedEvent>(
/**
* Should events about detected faces coalesce, the best strategy will be
* to ensure that events with different faces count are always being transmitted.
*/
coalescingKey = { event -> (event.faces.size % Short.MAX_VALUE).toShort() }
)
private val onFaceDetectionError by EventDispatcher<FaceDetectionErrorEvent>()
private val onPictureSaved by EventDispatcher<PictureSavedEvent>(
coalescingKey = { event ->
val uriHash = event.data.getString("uri")?.hashCode() ?: -1
(uriHash % Short.MAX_VALUE).toShort()
}
)
// Concurrency lock for scanners to avoid flooding the runtime
@Volatile
var barCodeScannerTaskLock = false
@Volatile
var faceDetectorTaskLock = false
// Scanning-related properties
private var barCodeScanner: BarCodeScannerInterface? = null
private var faceDetector: FaceDetectorInterface? = null
private var pendingFaceDetectorSettings: Map<String, Any>? = null
private var shouldDetectFaces = false
private var mShouldScanBarCodes = false
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
val width = right - left
val height = bottom - top
cameraView.layout(0, 0, width, height)
cameraView.setBackgroundColor(Color.BLACK)
val preview = cameraView.view ?: return
preview.layout(0, 0, width, height)
}
override fun onViewAdded(child: View) {
// react adds children to containers at the beginning of children list and that moves pre-react added preview to the end of that list
// above would cause preview (TextureView that covers all available space) to be rendered at the top of children stack
// while we need this preview to be rendered last beneath all other children
// child is not preview
if (cameraView === child) {
return
}
// bring to front all non-preview children
val childrenToBeReordered = mutableListOf<View>()
for (i in 0 until this.childCount) {
val childView = getChildAt(i)
if (i == 0 && childView === cameraView) {
// preview is already first in children list - do not reorder anything
return
}
if (childView !== cameraView) {
childrenToBeReordered.add(childView)
}
}
for (childView in childrenToBeReordered) {
bringChildToFront(childView)
}
cameraView.requestLayout()
cameraView.invalidate()
}
fun takePicture(options: PictureOptions, promise: Promise, cacheDirectory: File) {
pictureTakenPromises.add(promise)
pictureTakenOptions[promise] = options
pictureTakenDirectories[promise] = cacheDirectory
try {
cameraView.takePicture()
} catch (e: Exception) {
pictureTakenPromises.remove(promise)
pictureTakenOptions.remove(promise)
pictureTakenDirectories.remove(promise)
throw e
}
}
override fun onPictureSaved(response: Bundle) {
onPictureSaved(PictureSavedEvent(response.getInt("id"), response.getBundle("data")!!))
}
fun record(options: RecordingOptions, promise: Promise, cacheDirectory: File) {
try {
val path = FileSystemUtils.generateOutputPath(cacheDirectory, "Camera", ".mp4")
val profile = getCamcorderProfile(cameraView.cameraId, options.quality)
options.videoBitrate?.let { profile.videoBitRate = it }
if (cameraView.record(path, options.maxDuration * 1000, options.maxFileSize, !options.mute, profile)) {
videoRecordedPromise = promise
} else {
promise.reject("E_RECORDING_FAILED", "Starting video recording failed. Another recording might be in progress.", null)
}
} catch (e: IOException) {
promise.reject("E_RECORDING_FAILED", "Starting video recording failed - could not create video file.", null)
}
}
/**
* Initialize the barcode scanner.
* Supports all iOS codes except [code138, code39mod43, itf14]
* Additionally supports [codabar, code128, maxicode, rss14, rssexpanded, upc_a, upc_ean]
*/
private fun initBarCodeScanner() {
val barCodeScannerProvider = appContext.legacyModule<BarCodeScannerProviderInterface>()
barCodeScanner = barCodeScannerProvider?.createBarCodeDetectorWithContext(context)
}
fun setShouldScanBarCodes(shouldScanBarCodes: Boolean) {
mShouldScanBarCodes = shouldScanBarCodes
cameraView.scanning = mShouldScanBarCodes || shouldDetectFaces
}
fun setBarCodeScannerSettings(settings: BarCodeScannerSettings) {
barCodeScanner?.setSettings(settings)
}
// Even = portrait, odd = landscape
private fun getDeviceOrientation() =
(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.rotation
private fun transformBarCodeScannerResultToViewCoordinates(barCode: BarCodeScannerResult) {
val cornerPoints = barCode.cornerPoints
// For some reason they're swapped, I don't know anymore...
val cameraWidth = barCode.referenceImageHeight
val cameraHeight = barCode.referenceImageWidth
val facingBack = cameraView.facing == CameraView.FACING_BACK
val facingFront = cameraView.facing == CameraView.FACING_FRONT
val portrait = getDeviceOrientation() % 2 == 0
val landscape = getDeviceOrientation() % 2 == 1
if (facingBack && portrait) {
cornerPoints.mapX { cameraWidth - cornerPoints[it] }
}
if (facingBack && landscape) {
cornerPoints.mapY { cameraHeight - cornerPoints[it] }
}
if (facingFront) {
cornerPoints.mapX { cameraWidth - cornerPoints[it] }
cornerPoints.mapY { cameraHeight - cornerPoints[it] }
}
val scaleX = width / cameraWidth.toDouble()
val scaleY = height / cameraHeight.toDouble()
cornerPoints.mapX {
(cornerPoints[it] * scaleX)
.roundToInt()
}
cornerPoints.mapY {
(cornerPoints[it] * scaleY)
.roundToInt()
}
barCode.cornerPoints = cornerPoints
}
private fun getCornerPointsAndBoundingBox(cornerPoints: List<Int>, boundingBox: BoundingBox): Pair<ArrayList<Bundle>, Bundle> {
val density = cameraView.resources.displayMetrics.density
val convertedCornerPoints = ArrayList<Bundle>()
for (i in cornerPoints.indices step 2) {
val y = cornerPoints[i].toFloat() / density
val x = cornerPoints[i + 1].toFloat() / density
convertedCornerPoints.add(
Bundle().apply {
putFloat("x", x)
putFloat("y", y)
}
)
}
val boundingBoxBundle = Bundle().apply {
putParcelable(
"origin",
Bundle().apply {
putFloat("x", boundingBox.x.toFloat() / density)
putFloat("y", boundingBox.y.toFloat() / density)
}
)
putParcelable(
"size",
Bundle().apply {
putFloat("width", boundingBox.width.toFloat() / density)
putFloat("height", boundingBox.height.toFloat() / density)
}
)
}
return convertedCornerPoints to boundingBoxBundle
}
override fun onBarCodeScanned(barCode: BarCodeScannerResult) {
if (mShouldScanBarCodes) {
transformBarCodeScannerResultToViewCoordinates(barCode)
val (cornerPoints, boundingBox) = getCornerPointsAndBoundingBox(barCode.cornerPoints, barCode.boundingBox)
onBarCodeScanned(
BarcodeScannedEvent(
target = id,
data = barCode.value,
raw = barCode.raw,
type = BarcodeType.mapFormatToString(barCode.type),
cornerPoints = cornerPoints,
boundingBox = boundingBox
)
)
}
}
override fun onBarCodeScanningTaskCompleted() {
barCodeScannerTaskLock = false
}
override fun setPreviewTexture(surfaceTexture: SurfaceTexture?) {
cameraView.setPreviewTexture(surfaceTexture)
}
override fun getPreviewSizeAsArray() = intArrayOf(cameraView.previewSize.width, cameraView.previewSize.height)
override fun onHostResume() {
if (hasCameraPermissions()) {
if (isPaused && !cameraView.isCameraOpened || isNew) {
isPaused = false
isNew = false
if (!EmulatorUtilities.isRunningOnEmulator()) {
cameraView.start()
val faceDetectorProvider = appContext.legacyModule<FaceDetectorProviderInterface>()
faceDetector = faceDetectorProvider?.createFaceDetectorWithContext(context)
pendingFaceDetectorSettings?.let {
faceDetector?.setSettings(it)
pendingFaceDetectorSettings = null
}
}
}
} else {
onMountError(CameraMountErrorEvent("Camera permissions not granted - component could not be rendered."))
}
}
override fun onHostPause() {
if (!isPaused && cameraView.isCameraOpened) {
faceDetector?.release()
isPaused = true
cameraView.stop()
}
}
override fun onHostDestroy() {
faceDetector?.release()
cameraView.stop()
}
private fun hasCameraPermissions(): Boolean {
val permissionsManager = appContext.permissions ?: return false
return permissionsManager.hasGrantedPermissions(Manifest.permission.CAMERA)
}
fun setShouldDetectFaces(shouldDetectFaces: Boolean) {
this.shouldDetectFaces = shouldDetectFaces
cameraView.scanning = mShouldScanBarCodes || shouldDetectFaces
}
fun setFaceDetectorSettings(settings: Map<String, Any>?) {
faceDetector?.setSettings(settings) ?: run {
pendingFaceDetectorSettings = settings
}
}
override fun onFacesDetected(faces: List<Bundle>) {
if (shouldDetectFaces) {
onFacesDetected(
FacesDetectedEvent(
"face",
faces,
id
)
)
}
}
override fun onFaceDetectionError(faceDetector: FaceDetectorInterface) {
faceDetectorTaskLock = false
if (shouldDetectFaces) {
onFaceDetectionError(FaceDetectionErrorEvent(true))
}
}
override fun onFaceDetectingTaskCompleted() {
faceDetectorTaskLock = false
}
init {
initBarCodeScanner()
isChildrenDrawingOrderEnabled = true
val uIManager = appContext.legacyModule<UIManager>()
uIManager!!.registerLifecycleEventListener(this)
cameraView.addCallback(object : CameraView.Callback() {
override fun onCameraOpened(cameraView: CameraView) {
onCameraReady(Unit)
}
override fun onMountError(cameraView: CameraView) {
onMountError(
CameraMountErrorEvent("Camera component could not be rendered - is there any other instance running?")
)
}
override fun onPictureTaken(cameraView: CameraView, data: ByteArray) {
val promise = pictureTakenPromises.poll() ?: return
val cacheDirectory = pictureTakenDirectories.remove(promise)
val options = pictureTakenOptions.remove(promise)!!
if (options.fastMode) {
promise.resolve(null)
}
cacheDirectory?.let {
ResolveTakenPictureAsyncTask(data, promise, options, it, this@ExpoCameraView).execute()
}
}
override fun onVideoRecorded(cameraView: CameraView, path: String) {
videoRecordedPromise?.let {
it.resolve(
Bundle().apply {
putString("uri", Uri.fromFile(File(path)).toString())
}
)
videoRecordedPromise = null
}
}
override fun onFramePreview(cameraView: CameraView, data: ByteArray, width: Int, height: Int, rotation: Int) {
val correctRotation = getCorrectCameraRotation(rotation, cameraView.facing)
if (mShouldScanBarCodes && !barCodeScannerTaskLock) {
barCodeScannerTaskLock = true
barCodeScanner?.let { BarCodeScannerAsyncTask(this@ExpoCameraView, it, data, width, height, rotation).execute() }
}
if (shouldDetectFaces && !faceDetectorTaskLock) {
faceDetectorTaskLock = true
val density = cameraView.resources.displayMetrics.density
val dimensions = ImageDimensions(width, height, correctRotation, cameraView.facing)
val scaleX = cameraView.width.toDouble() / (dimensions.width * density)
val scaleY = cameraView.height.toDouble() / (dimensions.height * density)
val task = faceDetector?.let { FaceDetectorTask(this@ExpoCameraView, it, data, width, height, correctRotation, cameraView.facing == CameraView.FACING_FRONT, scaleX, scaleY) }
task?.execute()
}
}
})
addView(cameraView)
}
}

View File

@@ -0,0 +1,35 @@
package expo.modules.camera.legacy
import android.media.CamcorderProfile
import expo.modules.kotlin.records.Field
import expo.modules.kotlin.records.Record
class PictureOptions : Record {
@Field val quality: Double = 1.0
@Field val base64: Boolean = false
@Field val exif: Boolean = false
@Field val additionalExif: Map<String, Any>? = null
@Field val skipProcessing: Boolean = false
@Field val fastMode: Boolean = false
@Field val id: Int? = null
@Field val maxDownsampling: Int = 1
}
class RecordingOptions : Record {
@Field val maxDuration: Int = 0
@Field val maxFileSize: Int = 0
@Field val quality: Int = CamcorderProfile.QUALITY_HIGH
@Field val mute: Boolean = false
@Field val videoBitrate: Int? = null
}

View File

@@ -0,0 +1,29 @@
package expo.modules.camera.legacy.tasks
import android.os.AsyncTask
import expo.modules.interfaces.barcodescanner.BarCodeScannerInterface
import expo.modules.interfaces.barcodescanner.BarCodeScannerResult
class BarCodeScannerAsyncTask(
private val delegate: BarCodeScannerAsyncTaskDelegate,
private val barCodeScanner: BarCodeScannerInterface,
private val imageData: ByteArray,
private val width: Int,
private val height: Int,
private val rotation: Int
) : AsyncTask<Void?, Void?, BarCodeScannerResult?>() {
override fun doInBackground(vararg params: Void?) = if (!isCancelled) {
barCodeScanner.scan(imageData, width, height, rotation)
} else {
null
}
override fun onPostExecute(result: BarCodeScannerResult?) {
super.onPostExecute(result)
result?.let {
delegate.onBarCodeScanned(result)
}
delegate.onBarCodeScanningTaskCompleted()
}
}

View File

@@ -0,0 +1,8 @@
package expo.modules.camera.legacy.tasks
import expo.modules.interfaces.barcodescanner.BarCodeScannerResult
interface BarCodeScannerAsyncTaskDelegate {
fun onBarCodeScanned(barCode: BarCodeScannerResult)
fun onBarCodeScanningTaskCompleted()
}

View File

@@ -0,0 +1,10 @@
package expo.modules.camera.legacy.tasks
import android.os.Bundle
import expo.modules.interfaces.facedetector.FaceDetectorInterface
interface FaceDetectorAsyncTaskDelegate {
fun onFacesDetected(faces: List<Bundle>)
fun onFaceDetectionError(faceDetector: FaceDetectorInterface)
fun onFaceDetectingTaskCompleted()
}

View File

@@ -0,0 +1,34 @@
package expo.modules.camera.legacy.tasks
import expo.modules.interfaces.facedetector.FaceDetectorInterface
class FaceDetectorTask(
private val mDelegate: FaceDetectorAsyncTaskDelegate,
private val mFaceDetector: FaceDetectorInterface,
private val mImageData: ByteArray,
private val mWidth: Int,
private val mHeight: Int,
private val mRotation: Int,
private val mMirrored: Boolean,
private val mScaleX: Double,
private val mScaleY: Double
) {
fun execute() {
mFaceDetector.detectFaces(
mImageData, mWidth, mHeight, mRotation, mMirrored, mScaleX, mScaleY,
{ result ->
result?.let {
mDelegate.onFacesDetected(result)
} ?: run {
mDelegate.onFaceDetectionError(mFaceDetector)
}
mDelegate.onFaceDetectingTaskCompleted()
},
{ error ->
mDelegate.onFaceDetectionError(mFaceDetector)
mDelegate.onFaceDetectingTaskCompleted()
},
{ skippedReason -> mDelegate.onFaceDetectingTaskCompleted() }
)
}
}

View File

@@ -0,0 +1,7 @@
package expo.modules.camera.legacy.tasks
import android.os.Bundle
interface PictureSavedDelegate {
fun onPictureSaved(response: Bundle)
}

View File

@@ -0,0 +1,244 @@
package expo.modules.camera.legacy.tasks
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.net.Uri
import android.os.AsyncTask
import android.os.Bundle
import android.util.Base64
import androidx.exifinterface.media.ExifInterface
import expo.modules.camera.legacy.PictureOptions
import expo.modules.camera.legacy.CameraViewHelper.addExifData
import expo.modules.camera.legacy.CameraViewHelper.getExifData
import expo.modules.camera.legacy.CameraViewHelper.setExifData
import expo.modules.camera.legacy.utils.FileSystemUtils
import expo.modules.kotlin.Promise
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
private const val DIRECTORY_NOT_FOUND_MSG = "Documents directory of the app could not be found."
private const val UNKNOWN_IO_EXCEPTION_MSG = "An unknown I/O exception has occurred."
private const val UNKNOWN_EXCEPTION_MSG = "An unknown exception has occurred."
private const val PARAMETER_EXCEPTION_MSG = "An incompatible parameter has been passed in. "
private const val OUT_OF_MEMORY_EXCEPTION_MSG = "Cannot allocate enough space to process the taken picture."
private const val ERROR_TAG = "E_TAKING_PICTURE_FAILED"
private const val OUT_OF_MEMORY_TAG = "ERR_CAMERA_OUT_OF_MEMORY"
private const val DIRECTORY_NAME = "Camera"
private const val EXTENSION = ".jpg"
private const val BASE64_KEY = "base64"
private const val HEIGHT_KEY = "height"
private const val WIDTH_KEY = "width"
private const val EXIF_KEY = "exif"
private const val DATA_KEY = "data"
private const val URI_KEY = "uri"
private const val ID_KEY = "id"
class ResolveTakenPictureAsyncTask(
private var imageData: ByteArray,
private var promise: Promise,
private var options: PictureOptions,
private val directory: File,
private var pictureSavedDelegate: PictureSavedDelegate
) : AsyncTask<Void?, Void?, Bundle?>() {
private val quality: Int
get() = (options.quality * 100).toInt()
override fun doInBackground(vararg params: Void?): Bundle? {
// handle SkipProcessing
if (options.skipProcessing) {
return handleSkipProcessing()
}
// set, read, and apply EXIF data
try {
ByteArrayInputStream(imageData).use { inputStream ->
val response = Bundle()
val exifInterface = ExifInterface(inputStream)
// If there are additional exif data, insert it here
options.additionalExif?.let {
setExifData(exifInterface, it)
}
// Get orientation of the image from mImageData via inputStream
val orientation = exifInterface.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED
)
val bitmapOptions = BitmapFactory
.Options()
.apply {
inSampleSize = 1
}
var bitmap: Bitmap? = null
var lastError: Error? = null
// If OOM exception was thrown, we try to use downsampling to recover.
while (bitmapOptions.inSampleSize <= options.maxDownsampling) {
try {
bitmap = decodeBitmap(imageData, orientation, options.exif, bitmapOptions)
break
} catch (exception: OutOfMemoryError) {
bitmapOptions.inSampleSize *= 2
lastError = exception
}
}
if (bitmap == null) {
promise.reject(OUT_OF_MEMORY_TAG, OUT_OF_MEMORY_EXCEPTION_MSG, lastError)
return null
}
// Write Exif data to the response if requested
if (options.exif) {
val exifData = getExifData(exifInterface)
response.putBundle(EXIF_KEY, exifData)
}
// Upon rotating, write the image's dimensions to the response
response.apply {
putInt(WIDTH_KEY, bitmap.width)
putInt(HEIGHT_KEY, bitmap.height)
}
// Cache compressed image in imageStream
ByteArrayOutputStream().use { imageStream ->
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, imageStream)
// Write compressed image to file in cache directory
val filePath = writeStreamToFile(imageStream)
// Save Exif data to the image if requested
if (options.exif) {
val exifFromFile = ExifInterface(filePath!!)
addExifData(exifFromFile, exifInterface)
}
val imageFile = File(filePath)
val fileUri = Uri.fromFile(imageFile).toString()
response.putString(URI_KEY, fileUri)
// Write base64-encoded image to the response if requested
if (options.base64) {
response.putString(BASE64_KEY, Base64.encodeToString(imageStream.toByteArray(), Base64.NO_WRAP))
}
}
return response
}
} catch (e: Exception) {
when (e) {
is Resources.NotFoundException -> promise.reject(ERROR_TAG, DIRECTORY_NOT_FOUND_MSG, e)
is IOException -> promise.reject(ERROR_TAG, UNKNOWN_IO_EXCEPTION_MSG, e)
is IllegalArgumentException -> promise.reject(ERROR_TAG, PARAMETER_EXCEPTION_MSG, e)
else -> promise.reject(ERROR_TAG, UNKNOWN_EXCEPTION_MSG, e)
}
e.printStackTrace()
}
// An exception had to occur, promise has already been rejected. Do not try to resolve it again.
return null
}
private fun handleSkipProcessing(): Bundle? {
try {
// save byte array (it's already a JPEG)
ByteArrayOutputStream().use { imageStream ->
imageStream.write(imageData)
// write compressed image to file in cache directory
val filePath = writeStreamToFile(imageStream)
val imageFile = File(filePath)
// handle image uri
val fileUri = Uri.fromFile(imageFile).toString()
// read exif information
val exifInterface = ExifInterface(filePath!!)
return Bundle().apply {
putString(URI_KEY, fileUri)
putInt(WIDTH_KEY, exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, -1))
putInt(HEIGHT_KEY, exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, -1))
// handle exif request
if (options.exif) {
val exifData = getExifData(exifInterface)
putBundle(EXIF_KEY, exifData)
}
// handle base64
if (options.base64) {
putString(BASE64_KEY, Base64.encodeToString(imageData, Base64.NO_WRAP))
}
}
}
} catch (e: Exception) {
if (e is IOException) {
promise.reject(ERROR_TAG, UNKNOWN_IO_EXCEPTION_MSG, e)
} else {
promise.reject(ERROR_TAG, UNKNOWN_EXCEPTION_MSG, e)
}
e.printStackTrace()
}
// error occurred
return null
}
override fun onPostExecute(response: Bundle?) {
super.onPostExecute(response)
// If the response is not null everything went well and we can resolve the promise.
if (response != null) {
if (options.fastMode) {
val wrapper = Bundle()
wrapper.putInt(ID_KEY, requireNotNull(options.id))
wrapper.putBundle(DATA_KEY, response)
pictureSavedDelegate.onPictureSaved(wrapper)
} else {
promise.resolve(response)
}
}
}
// Write stream to file in cache directory
@Throws(Exception::class)
private fun writeStreamToFile(inputStream: ByteArrayOutputStream): String? {
try {
val outputPath = FileSystemUtils.generateOutputPath(directory, DIRECTORY_NAME, EXTENSION)
FileOutputStream(outputPath).use { outputStream ->
inputStream.writeTo(outputStream)
}
return outputPath
} catch (e: IOException) {
e.printStackTrace()
}
return null
}
private fun decodeBitmap(imageData: ByteArray, orientation: Int, exif: Boolean, bitmapOptions: BitmapFactory.Options): Bitmap {
// Rotate the bitmap to the proper orientation if needed
return if (!exif) {
decodeAndRotateBitmap(imageData, getImageRotation(orientation), bitmapOptions)
} else {
BitmapFactory.decodeByteArray(imageData, 0, imageData.size, bitmapOptions)
}
}
private fun decodeAndRotateBitmap(imageData: ByteArray, angle: Int, options: BitmapFactory.Options): Bitmap {
val source = BitmapFactory.decodeByteArray(imageData, 0, imageData.size, options)
val matrix = Matrix()
matrix.postRotate(angle.toFloat())
return Bitmap.createBitmap(source, 0, 0, source.width, source.height, matrix, true)
}
// Get rotation degrees from Exif orientation enum
private fun getImageRotation(orientation: Int) = when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> 90
ExifInterface.ORIENTATION_ROTATE_180 -> 180
ExifInterface.ORIENTATION_ROTATE_270 -> 270
else -> 0
}
}

View File

@@ -0,0 +1,31 @@
package expo.modules.camera.legacy.utils
import java.io.File
import java.io.IOException
import java.util.*
object FileSystemUtils {
@Throws(IOException::class)
fun ensureDirExists(dir: File): File {
if (!(dir.isDirectory || dir.mkdirs())) {
throw IOException("Couldn't create directory '$dir'")
}
return dir
}
@Throws(IOException::class)
fun generateOutputPath(internalDirectory: File, dirName: String, extension: String): String {
val directory = File(internalDirectory.toString() + File.separator + dirName)
ensureDirExists(directory)
val filename = UUID.randomUUID().toString()
return directory.toString() + File.separator + filename + extension
}
@Throws(IOException::class)
fun generateOutputFile(internalDirectory: File, dirName: String, extension: String): File {
val directory = File(internalDirectory.toString() + File.separator + dirName)
ensureDirExists(directory)
val filename = UUID.randomUUID().toString()
return File(directory.toString() + File.separator + filename + extension)
}
}

View File

@@ -0,0 +1,18 @@
package expo.modules.camera.legacy.utils
data class ImageDimensions @JvmOverloads constructor(private val mWidth: Int, private val mHeight: Int, val rotation: Int = 0, val facing: Int = -1) {
private val isLandscape: Boolean
get() = rotation % 180 == 90
val width: Int
get() = if (isLandscape) {
mHeight
} else {
mWidth
}
val height: Int
get() = if (isLandscape) {
mWidth
} else {
mHeight
}
}

View File

@@ -0,0 +1,138 @@
package expo.modules.camera.records
import android.hardware.camera2.CameraMetadata
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture
import androidx.camera.core.resolutionselector.AspectRatioStrategy
import androidx.camera.video.Quality
import com.google.mlkit.vision.barcode.common.Barcode
import expo.modules.camera.CameraExceptions
import expo.modules.kotlin.records.Field
import expo.modules.kotlin.records.Record
import expo.modules.kotlin.types.Enumerable
enum class CameraType(val value: String) : Enumerable {
FRONT("front"),
BACK("back");
fun mapToSelector() = when (this) {
FRONT -> CameraSelector.DEFAULT_FRONT_CAMERA
BACK -> CameraSelector.DEFAULT_BACK_CAMERA
}
fun mapToCharacteristic() = when (this) {
FRONT -> CameraMetadata.LENS_FACING_FRONT
BACK -> CameraMetadata.LENS_FACING_BACK
}
}
enum class CameraRatio(val value: String) : Enumerable {
FOUR_THREE("4:3"),
SIXTEEN_NINE("16:9"),
ONE_ONE("1:1");
fun mapToStrategy() = when (this) {
FOUR_THREE -> AspectRatioStrategy.RATIO_4_3_FALLBACK_AUTO_STRATEGY
SIXTEEN_NINE -> AspectRatioStrategy.RATIO_16_9_FALLBACK_AUTO_STRATEGY
else -> throw CameraExceptions.UnsupportedAspectRatioException(this.value)
}
}
enum class VideoQuality(val value: String) : Enumerable {
VIDEO2160P("2160p"),
VIDEO1080P("1080p"),
VIDEO720P("720p"),
VIDEO480P("480p"),
VIDEO4X3("4:3");
fun mapToQuality(): Quality = when (this) {
VIDEO2160P -> Quality.UHD
VIDEO1080P -> Quality.FHD
VIDEO720P -> Quality.HD
VIDEO480P -> Quality.SD
VIDEO4X3 -> Quality.LOWEST
}
}
enum class FlashMode(val value: String) : Enumerable {
AUTO("auto"),
ON("on"),
OFF("off");
fun mapToLens() = when (this) {
AUTO -> ImageCapture.FLASH_MODE_AUTO
OFF -> ImageCapture.FLASH_MODE_OFF
ON -> ImageCapture.FLASH_MODE_ON
}
}
enum class CameraMode(val value: String) : Enumerable {
PICTURE("picture"),
VIDEO("video")
}
enum class FocusMode(val value: String) : Enumerable {
ON("on"),
OFF("off")
}
data class BarcodeSettings(
@Field val barcodeTypes: List<BarcodeType>
) : Record
enum class BarcodeType(private val value: String) : Enumerable {
AZTEC("aztec"),
EAN13("ean13"),
EAN8("ean8"),
QR("qr"),
PDF417("pdf417"),
UPCE("upc_e"),
DATAMATRIX("datamatrix"),
CODE39("code39"),
CODE93("code93"),
ITF14("itf14"),
CODABAR("codabar"),
CODE128("code128"),
UPCA("upc_a"),
UNKNOWN("unknown");
fun mapToBarcode() = when (this) {
AZTEC -> Barcode.FORMAT_AZTEC
EAN13 -> Barcode.FORMAT_EAN_13
EAN8 -> Barcode.FORMAT_EAN_8
QR -> Barcode.FORMAT_QR_CODE
PDF417 -> Barcode.FORMAT_PDF417
UPCE -> Barcode.FORMAT_UPC_E
DATAMATRIX -> Barcode.FORMAT_DATA_MATRIX
CODE39 -> Barcode.FORMAT_CODE_39
CODE93 -> Barcode.FORMAT_CODE_93
ITF14 -> Barcode.FORMAT_ITF
CODABAR -> Barcode.FORMAT_CODABAR
CODE128 -> Barcode.FORMAT_CODE_128
UPCA -> Barcode.FORMAT_UPC_A
UNKNOWN -> Barcode.FORMAT_UNKNOWN
}
companion object {
fun mapFormatToString(format: Int): String {
val result = when (format) {
Barcode.FORMAT_AZTEC -> AZTEC
Barcode.FORMAT_EAN_13 -> EAN13
Barcode.FORMAT_EAN_8 -> EAN8
Barcode.FORMAT_QR_CODE -> QR
Barcode.FORMAT_PDF417 -> PDF417
Barcode.FORMAT_UPC_E -> UPCE
Barcode.FORMAT_DATA_MATRIX -> DATAMATRIX
Barcode.FORMAT_CODE_39 -> CODE39
Barcode.FORMAT_CODE_93 -> CODE93
Barcode.FORMAT_ITF -> ITF14
Barcode.FORMAT_CODABAR -> CODABAR
Barcode.FORMAT_CODE_128 -> CODE128
Barcode.FORMAT_UPC_A -> UPCA
else -> UNKNOWN
}
return result.value
}
}
}

View File

@@ -0,0 +1,7 @@
package expo.modules.camera.tasks
import android.os.Bundle
fun interface PictureSavedDelegate {
fun onPictureSaved(response: Bundle)
}

View File

@@ -0,0 +1,273 @@
package expo.modules.camera.tasks
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.net.Uri
import android.os.Bundle
import android.util.Base64
import androidx.exifinterface.media.ExifInterface
import expo.modules.camera.PictureOptions
import expo.modules.camera.legacy.CameraViewHelper.addExifData
import expo.modules.camera.legacy.CameraViewHelper.getExifData
import expo.modules.camera.legacy.CameraViewHelper.setExifData
import expo.modules.camera.legacy.utils.FileSystemUtils
import expo.modules.kotlin.Promise
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
private const val DIRECTORY_NOT_FOUND_MSG = "Documents directory of the app could not be found."
private const val UNKNOWN_IO_EXCEPTION_MSG = "An unknown I/O exception has occurred."
private const val UNKNOWN_EXCEPTION_MSG = "An unknown exception has occurred."
private const val PARAMETER_EXCEPTION_MSG = "An incompatible parameter has been passed in. "
private const val OUT_OF_MEMORY_EXCEPTION_MSG = "Cannot allocate enough space to process the taken picture."
private const val ERROR_TAG = "E_TAKING_PICTURE_FAILED"
private const val OUT_OF_MEMORY_TAG = "ERR_CAMERA_OUT_OF_MEMORY"
private const val DIRECTORY_NAME = "Camera"
private const val EXTENSION = ".jpg"
private const val BASE64_KEY = "base64"
private const val HEIGHT_KEY = "height"
private const val WIDTH_KEY = "width"
private const val EXIF_KEY = "exif"
private const val DATA_KEY = "data"
private const val URI_KEY = "uri"
private const val ID_KEY = "id"
fun getMirroredOrientation(orientation: Int): Int {
return when (orientation) {
ExifInterface.ORIENTATION_NORMAL -> ExifInterface.ORIENTATION_FLIP_HORIZONTAL
ExifInterface.ORIENTATION_ROTATE_90 -> ExifInterface.ORIENTATION_TRANSPOSE
ExifInterface.ORIENTATION_ROTATE_180 -> ExifInterface.ORIENTATION_FLIP_VERTICAL
ExifInterface.ORIENTATION_ROTATE_270 -> ExifInterface.ORIENTATION_TRANSVERSE
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> ExifInterface.ORIENTATION_NORMAL
ExifInterface.ORIENTATION_TRANSPOSE -> ExifInterface.ORIENTATION_ROTATE_90
ExifInterface.ORIENTATION_FLIP_VERTICAL -> ExifInterface.ORIENTATION_ROTATE_180
ExifInterface.ORIENTATION_TRANSVERSE -> ExifInterface.ORIENTATION_ROTATE_270
else -> ExifInterface.ORIENTATION_UNDEFINED
}
}
class ResolveTakenPicture(
private var imageData: ByteArray,
private var promise: Promise,
private var options: PictureOptions,
private var mirror: Boolean,
private val directory: File,
private var pictureSavedDelegate: PictureSavedDelegate
) {
private val quality: Int
get() = (options.quality * 100).toInt()
suspend fun resolve() = withContext(Dispatchers.IO) {
val bundle = processImage()
onComplete(bundle)
}
private fun processImage(): Bundle? {
// handle SkipProcessing
if (options.skipProcessing) {
return skipProcessing()
}
// set, read, and apply EXIF data
try {
ByteArrayInputStream(imageData).use { inputStream ->
val response = Bundle()
val exifInterface = ExifInterface(inputStream)
// If there are additional exif data, insert it here
options.additionalExif?.let {
setExifData(exifInterface, it)
}
// Get orientation of the image from mImageData via inputStream
val orientation = exifInterface.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL
)
if (mirror) {
exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION, getMirroredOrientation(orientation).toString())
}
val bitmapOptions = BitmapFactory
.Options()
.apply {
inSampleSize = 1
}
var bitmap: Bitmap? = null
var lastError: Error? = null
// If OOM exception was thrown, we try to use downsampling to recover.
while (bitmapOptions.inSampleSize <= options.maxDownsampling) {
try {
bitmap = decodeBitmap(imageData, orientation, options, bitmapOptions)
break
} catch (exception: OutOfMemoryError) {
bitmapOptions.inSampleSize *= 2
lastError = exception
}
}
if (bitmap == null) {
promise.reject(OUT_OF_MEMORY_TAG, OUT_OF_MEMORY_EXCEPTION_MSG, lastError)
return null
}
// Write Exif data to the response if requested
if (options.exif) {
val exifData = getExifData(exifInterface)
response.putBundle(EXIF_KEY, exifData)
}
// Upon rotating, write the image's dimensions to the response
response.apply {
putInt(WIDTH_KEY, bitmap.width)
putInt(HEIGHT_KEY, bitmap.height)
}
// Cache compressed image in imageStream
ByteArrayOutputStream().use { imageStream ->
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, imageStream)
// Write compressed image to file in cache directory
val filePath = writeStreamToFile(imageStream)
bitmap.recycle()
// Save Exif data to the image if requested
if (options.exif) {
val exifFromFile = ExifInterface(filePath!!)
addExifData(exifFromFile, exifInterface)
}
val imageFile = File(filePath)
val fileUri = Uri.fromFile(imageFile).toString()
response.putString(URI_KEY, fileUri)
// Write base64-encoded image to the response if requested
if (options.base64) {
response.putString(BASE64_KEY, Base64.encodeToString(imageStream.toByteArray(), Base64.NO_WRAP))
}
}
return response
}
} catch (e: Exception) {
when (e) {
is Resources.NotFoundException -> promise.reject(ERROR_TAG, DIRECTORY_NOT_FOUND_MSG, e)
is IOException -> promise.reject(ERROR_TAG, UNKNOWN_IO_EXCEPTION_MSG, e)
is IllegalArgumentException -> promise.reject(ERROR_TAG, PARAMETER_EXCEPTION_MSG, e)
else -> promise.reject(ERROR_TAG, UNKNOWN_EXCEPTION_MSG, e)
}
e.printStackTrace()
}
// An exception had to occur, promise has already been rejected. Do not try to resolve it again.
return null
}
private fun skipProcessing(): Bundle? {
try {
// save byte array (it's already a JPEG)
ByteArrayOutputStream().use { imageStream ->
imageStream.write(imageData)
// write compressed image to file in cache directory
val filePath = writeStreamToFile(imageStream)
val imageFile = filePath?.let { File(it) }
// handle image uri
val fileUri = Uri.fromFile(imageFile).toString()
// read exif information
val exifInterface = ExifInterface(filePath!!)
return Bundle().apply {
putString(URI_KEY, fileUri)
putInt(WIDTH_KEY, exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, -1))
putInt(HEIGHT_KEY, exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, -1))
// handle exif request
if (options.exif) {
val exifData = getExifData(exifInterface)
putBundle(EXIF_KEY, exifData)
}
// handle base64
if (options.base64) {
putString(BASE64_KEY, Base64.encodeToString(imageData, Base64.NO_WRAP))
}
}
}
} catch (e: IOException) {
promise.reject(ERROR_TAG, UNKNOWN_IO_EXCEPTION_MSG, e)
e.printStackTrace()
} catch (e: Exception) {
promise.reject(ERROR_TAG, UNKNOWN_EXCEPTION_MSG, e)
e.printStackTrace()
}
// error occurred
return null
}
private fun onComplete(response: Bundle?) {
if (response == null) {
return
}
if (options.fastMode) {
val wrapper = Bundle()
wrapper.putInt(ID_KEY, requireNotNull(options.id))
wrapper.putBundle(DATA_KEY, response)
pictureSavedDelegate.onPictureSaved(wrapper)
} else {
promise.resolve(response)
}
}
// Write stream to file in cache directory
@Throws(Exception::class)
private fun writeStreamToFile(inputStream: ByteArrayOutputStream): String? {
try {
val outputPath = FileSystemUtils.generateOutputPath(directory, DIRECTORY_NAME, EXTENSION)
FileOutputStream(outputPath).use { outputStream ->
inputStream.writeTo(outputStream)
}
return outputPath
} catch (e: IOException) {
e.printStackTrace()
}
return null
}
private fun decodeBitmap(imageData: ByteArray, orientation: Int, options: PictureOptions, bitmapOptions: BitmapFactory.Options): Bitmap {
// Rotate the bitmap to the proper orientation if needed
return if (!options.exif) {
decodeAndRotateBitmap(imageData, getImageRotation(orientation), bitmapOptions)
} else {
BitmapFactory.decodeByteArray(imageData, 0, imageData.size, bitmapOptions)
}
}
private fun decodeAndRotateBitmap(imageData: ByteArray, angle: Int, options: BitmapFactory.Options): Bitmap {
val source = BitmapFactory.decodeByteArray(imageData, 0, imageData.size, options)
val matrix = Matrix()
matrix.apply {
postRotate(angle.toFloat())
if (mirror) {
postScale(-1f, 1f)
}
}
return Bitmap.createBitmap(source, 0, 0, source.width, source.height, matrix, true)
}
// Get rotation degrees from Exif orientation enum
private fun getImageRotation(orientation: Int) = when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> 90
ExifInterface.ORIENTATION_TRANSPOSE -> 90
ExifInterface.ORIENTATION_ROTATE_180 -> 180
ExifInterface.ORIENTATION_FLIP_VERTICAL -> 180
ExifInterface.ORIENTATION_ROTATE_270 -> 270
ExifInterface.ORIENTATION_TRANSVERSE -> 270
else -> 0
}
}

View File

@@ -0,0 +1,13 @@
package expo.modules.camera.utils
inline fun MutableList<Int>.mapY(block: (it: Int) -> Int) {
for (it in 0 until this.size step 2) {
this[it] = block(it)
}
}
inline fun MutableList<Int>.mapX(block: (it: Int) -> Int) {
for (it in 1 until this.size step 2) {
this[it] = block(it)
}
}

View File

@@ -0,0 +1,23 @@
package expo.modules.camera.utils
import java.io.File
import java.io.IOException
import java.util.*
object FileSystemUtils {
@Throws(IOException::class)
fun ensureDirExists(dir: File): File {
if (!(dir.isDirectory || dir.mkdirs())) {
throw IOException("Couldn't create directory '$dir'")
}
return dir
}
@Throws(IOException::class)
fun generateOutputFile(internalDirectory: File, dirName: String, extension: String): File {
val directory = File(internalDirectory.toString() + File.separator + dirName)
ensureDirExists(directory)
val filename = UUID.randomUUID().toString()
return File(directory.toString() + File.separator + filename + extension)
}
}

View File

@@ -0,0 +1,20 @@
package expo.modules.camera.utils
import expo.modules.camera.records.CameraType
data class ImageDimensions @JvmOverloads constructor(private val mWidth: Int, private val mHeight: Int, val rotation: Int = 0, val facing: CameraType = CameraType.BACK) {
private val isLandscape: Boolean
get() = rotation % 180 == 90
val width: Int
get() = if (isLandscape) {
mHeight
} else {
mWidth
}
val height: Int
get() = if (isLandscape) {
mWidth
} else {
mHeight
}
}

View File

@@ -0,0 +1 @@
module.exports = require('./plugin/build/withCamera');

View File

@@ -0,0 +1,459 @@
import { PermissionResponse, PermissionStatus, PermissionExpiration, PermissionHookOptions } from 'expo-modules-core';
import { Ref } from 'react';
import type { ViewProps } from 'react-native';
export type CameraType = 'front' | 'back';
export type FlashMode = 'off' | 'on' | 'auto';
export type ImageType = 'png' | 'jpg';
export type CameraMode = 'picture' | 'video';
export type CameraRatio = '4:3' | '16:9' | '1:1';
/**
* This option specifies the mode of focus on the device.
* - `on` - Indicates that the device should autofocus once and then lock the focus.
* - `off` - Indicates that the device should automatically focus when needed.
* @default off
*/
export type FocusMode = 'on' | 'off';
/**
* This option specifies what codec to use when recording a video.
* @platform ios
*/
export type VideoCodec = 'avc1' | 'hvc1' | 'jpeg' | 'apcn' | 'ap4h';
/**
* This option specifies the stabilization mode to use when recording a video.
* @platform ios
*/
export type VideoStabilization = 'off' | 'standard' | 'cinematic' | 'auto';
export type VideoQuality = '2160p' | '1080p' | '720p' | '480p' | '4:3';
export type CameraOrientation = 'portrait' | 'portraitUpsideDown' | 'landscapeLeft' | 'landscapeRight';
/**
* @hidden We do not expose related web methods in docs.
* @platform web
*/
export type ImageSize = {
width: number;
height: number;
};
/**
* @hidden We do not expose related web methods in docs.
* @platform web
*/
export type WebCameraSettings = {
autoFocus?: string;
flashMode?: string;
whiteBalance?: string;
exposureCompensation?: number;
colorTemperature?: number;
iso?: number;
brightness?: number;
contrast?: number;
saturation?: number;
sharpness?: number;
focusDistance?: number;
zoom?: number;
};
export type CameraCapturedPicture = {
/**
* Captured image width.
*/
width: number;
/**
* Captured image height.
*/
height: number;
/**
* On web, the value of `uri` is the same as `base64` because file system URLs are not supported in the browser.
*/
uri: string;
/**
* A Base64 representation of the image.
*/
base64?: string;
/**
* On Android and iOS this object may include various fields based on the device and operating system.
* On web, it is a partial representation of the [`MediaTrackSettings`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings) dictionary.
*/
exif?: Partial<MediaTrackSettings> | any;
};
export type CameraPictureOptions = {
/**
* Specify the compression quality from `0` to `1`. `0` means compress for small size, and `1` means compress for maximum quality.
*/
quality?: number;
/**
* Whether to also include the image data in Base64 format.
*/
base64?: boolean;
/**
* Whether to also include the EXIF data for the image.
*/
exif?: boolean;
/**
* Additional EXIF data to be included for the image. Only useful when `exif` option is set to `true`.
* @platform android
* @platform ios
*/
additionalExif?: Record<string, any>;
/**
* A callback invoked when picture is saved. If set, the promise of this method will resolve immediately with no data after picture is captured.
* The data that it should contain will be passed to this callback. If displaying or processing a captured photo right after taking it
* is not your case, this callback lets you skip waiting for it to be saved.
* @param picture
*/
onPictureSaved?: (picture: CameraCapturedPicture) => void;
/**
* If set to `true`, camera skips orientation adjustment and returns an image straight from the device's camera.
* If enabled, `quality` option is discarded (processing pipeline is skipped as a whole).
* Although enabling this option reduces image delivery time significantly, it may cause the image to appear in a wrong orientation
* in the `Image` component (at the time of writing, it does not respect EXIF orientation of the images).
* > **Note**: Enabling `skipProcessing` would cause orientation uncertainty. `Image` component does not respect EXIF
* > stored orientation information, that means obtained image would be displayed wrongly (rotated by 90°, 180° or 270°).
* > Different devices provide different orientations. For example some Sony Xperia or Samsung devices don't provide
* > correctly oriented images by default. To always obtain correctly oriented image disable `skipProcessing` option.
*/
skipProcessing?: boolean;
/**
* @platform web
*/
scale?: number;
/**
* @platform web
*/
imageType?: ImageType;
/**
* @platform web
*/
isImageMirror?: boolean;
/**
* When set to `true`, the output image will be flipped along the vertical axis when using the front camera.
* @default false
* @platform ios
* @platform android
* @deprecated Use `mirror` prop on `CameraView` instead.
*/
mirror?: boolean;
/**
* @hidden
*/
id?: number;
/**
* @hidden
*/
fastMode?: boolean;
/**
* @hidden
*/
maxDownsampling?: number;
};
export type CameraRecordingOptions = {
/**
* Maximum video duration in seconds.
*/
maxDuration?: number;
/**
* Maximum video file size in bytes.
*/
maxFileSize?: number;
/**
* If `true`, the recorded video will be flipped along the vertical axis. iOS flips videos recorded with the front camera by default,
* but you can reverse that back by setting this to `true`. On Android, this is handled in the user's device settings.
* @deprecated Use `mirror` prop on `CameraView` instead.
*/
mirror?: boolean;
/**
* This option specifies what codec to use when recording the video. See [`VideoCodec`](#videocodec) for the possible values.
* @platform ios
*/
codec?: VideoCodec;
};
/**
* @hidden
*/
export type PictureSavedListener = (event: {
nativeEvent: {
data: CameraCapturedPicture;
id: number;
};
}) => void;
/**
* @hidden
*/
export type CameraReadyListener = () => void;
/**
* @hidden
*/
export type ResponsiveOrientationChangedListener = (event: {
nativeEvent: ResponsiveOrientationChanged;
}) => void;
export type ResponsiveOrientationChanged = {
orientation: CameraOrientation;
};
/**
* @hidden
*/
export type MountErrorListener = (event: {
nativeEvent: CameraMountError;
}) => void;
export type CameraMountError = {
message: string;
};
export type Point = {
x: number;
y: number;
};
export type BarcodeSize = {
/**
* The height value.
*/
height: number;
/**
* The width value.
*/
width: number;
};
/**
* These coordinates are represented in the coordinate space of the camera source (e.g. when you
* are using the camera view, these values are adjusted to the dimensions of the view).
*/
export type BarcodePoint = Point;
export type BarcodeBounds = {
/**
* The origin point of the bounding box.
*/
origin: BarcodePoint;
/**
* The size of the bounding box.
*/
size: BarcodeSize;
};
export type BarcodeScanningResult = {
/**
* The barcode type.
*/
type: string;
/**
* The parsed information encoded in the barcode.
*/
data: string;
/**
* The raw information encoded in the barcode.
* May be different from `data` depending on the barcode type.
* @platform android
* @hidden
*/
raw?: string;
/**
* Corner points of the bounding box.
* `cornerPoints` is not always available and may be empty. On iOS, for `code39` and `pdf417`
* you don't get this value.
*/
cornerPoints: BarcodePoint[];
/**
* The [BarcodeBounds](#barcodebounds) object.
* `bounds` in some case will be representing an empty rectangle.
* Moreover, `bounds` doesn't have to bound the whole barcode.
* For some types, they will represent the area used by the scanner.
*/
bounds: BarcodeBounds;
};
export type ScanningResult = Omit<BarcodeScanningResult, 'bounds'>;
export type CameraProps = ViewProps & {
/**
* Camera facing. Use one of `CameraType`. When `front`, use the front-facing camera.
* When `back`, use the back-facing camera.
* @default 'back'
*/
facing?: CameraType;
/**
* Camera flash mode. Use one of `FlashMode` values. When `on`, the flash on your device will
* turn on when taking a picture. When `off`, it won't. Setting it to `auto` will fire flash if required.
* @default 'off'
*/
flash?: FlashMode;
/**
* A value between `0` and `1` being a percentage of device's max zoom. `0` - not zoomed, `1` - maximum zoom.
* @default 0
*/
zoom?: number;
/**
* Used to select image or video output
* @default 'picture'
*/
mode?: CameraMode;
/**
* If present, video will be recorded with no sound.
* @default false
*/
mute?: boolean;
/**
* A boolean that determines whether the camera should mirror the image when using the front camera.
* @default false
*/
mirror?: boolean;
/**
* Indicates the focus mode to use.
* @default off
* @platform ios
*/
autofocus?: FocusMode;
/**
* A boolean that determines whether the camera should be active.
* Useful in situations where the camera may not have unmounted but you still want to stop the camera session.
* @default true
* @platform ios
*/
active?: boolean;
/**
* Specify the quality of the recorded video. Use one of `VideoQuality` possible values:
* for 16:9 resolution `2160p`, `1080p`, `720p`, `480p` : `Android only` and for 4:3 `4:3` (the size is 640x480).
* If the chosen quality is not available for a device, the highest available is chosen.
*/
videoQuality?: VideoQuality;
/**
* A boolean that determines whether the camera shutter animation should be enabled.
* @default true
*/
animateShutter?: boolean;
/**
* A string representing the size of pictures [`takePictureAsync`](#takepictureasync) will take.
* Available sizes can be fetched with [`getAvailablePictureSizes`](#getavailablepicturesizes).
*/
pictureSize?: string;
/**
* A boolean to enable or disable the torch
* @default false
*/
enableTorch?: boolean;
/**
* The video stabilization mode used for a video recording. Use one of [`VideoStabilization.<value>`](#videostabilization).
* You can read more about each stabilization type in [Apple Documentation](https://developer.apple.com/documentation/avfoundation/avcapturevideostabilizationmode).
* @platform ios
*/
videoStabilizationMode?: VideoStabilization;
/**
* @example
* ```tsx
* <CameraView
* barcodeScannerSettings={{
* barcodeTypes: ["qr"],
* }}
* />
* ```
*/
barcodeScannerSettings?: BarcodeSettings;
/**
* A URL for an image to be shown while the camera is loading.
* @platform web
*/
poster?: string;
/**
* Whether to allow responsive orientation of the camera when the screen orientation is locked (i.e. when set to `true`
* landscape photos will be taken if the device is turned that way, even if the app or device orientation is locked to portrait)
* @platform ios
*/
responsiveOrientationWhenOrientationLocked?: boolean;
/**
* A string representing the aspect ratio of the preview. For example, `4:3` and `16:9`.
* Note: Setting the aspect ratio here will change the scaleType of the camera preview from `FILL` to `FIT`.
* Also, when using 1:1, devices only support certain sizes. If you specify an unsupported size, the closest supported ratio will be used.
* @platform android
*/
ratio?: CameraRatio;
/**
* Callback invoked when camera preview has been set.
*/
onCameraReady?: () => void;
/**
* Callback invoked when camera preview could not start.
* @param event Error object that contains a `message`.
*/
onMountError?: (event: CameraMountError) => void;
/**
* Callback that is invoked when a barcode has been successfully scanned. The callback is provided with
* an object of the [`BarcodeScanningResult`](#barcodescanningresult) shape, where the `type`
* refers to the barcode type that was scanned, and the `data` is the information encoded in the barcode
* (in this case of QR codes, this is often a URL). See [`BarcodeType`](#barcodetype) for supported values.
* for supported values.
* @param scanningResult
*/
onBarcodeScanned?: (scanningResult: BarcodeScanningResult) => void;
/**
* Callback invoked when responsive orientation changes. Only applicable if `responsiveOrientationWhenOrientationLocked` is `true`
* @param event result object that contains updated orientation of camera
* @platform ios
*/
onResponsiveOrientationChanged?: (event: ResponsiveOrientationChanged) => void;
};
/**
* @hidden
*/
export interface CameraViewRef {
readonly takePicture: (options: CameraPictureOptions) => Promise<CameraCapturedPicture>;
readonly getAvailablePictureSizes: () => Promise<string[]>;
readonly record: (options?: CameraRecordingOptions) => Promise<{
uri: string;
}>;
readonly stopRecording: () => Promise<void>;
readonly launchModernScanner: () => Promise<void>;
readonly resumePreview: () => Promise<void>;
readonly pausePreview: () => Promise<void>;
}
/**
* @hidden
*/
export type CameraNativeProps = {
pointerEvents?: any;
style?: any;
ref?: Ref<CameraViewRef>;
onCameraReady?: CameraReadyListener;
onMountError?: MountErrorListener;
onBarcodeScanned?: (event: {
nativeEvent: BarcodeScanningResult;
}) => void;
onPictureSaved?: PictureSavedListener;
onResponsiveOrientationChanged?: ResponsiveOrientationChangedListener;
facing?: string;
flashMode?: string;
enableTorch?: boolean;
animateShutter?: boolean;
autoFocus?: FocusMode;
mute?: boolean;
zoom?: number;
ratio?: CameraRatio;
barcodeScannerSettings?: BarcodeSettings;
barcodeScannerEnabled?: boolean;
poster?: string;
responsiveOrientationWhenOrientationLocked?: boolean;
};
export type BarcodeSettings = {
barcodeTypes: BarcodeType[];
};
/**
* @platform ios
*/
export type ScanningOptions = {
/**
* The type of codes to scan for.
*/
barcodeTypes: BarcodeType[];
/**
* Indicates whether people can use a two-finger pinch-to-zoom gesture.
* @default true
*/
isPinchToZoomEnabled?: boolean;
/**
* Guidance text, such as “Slow Down,” appears over the live video.
* @default true
*/
isGuidanceEnabled?: boolean;
/**
* Indicates whether the scanner displays highlights around recognized items.
* @default false
*/
isHighlightingEnabled?: boolean;
};
/**
* The available barcode types that can be scanned.
*/
export type BarcodeType = 'aztec' | 'ean13' | 'ean8' | 'qr' | 'pdf417' | 'upc_e' | 'datamatrix' | 'code39' | 'code93' | 'itf14' | 'codabar' | 'code128' | 'upc_a';
export { PermissionResponse, PermissionStatus, PermissionExpiration, PermissionHookOptions };
//# sourceMappingURL=Camera.types.d.ts.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
import { PermissionStatus, } from 'expo-modules-core';
export { PermissionStatus };
//# sourceMappingURL=Camera.types.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,121 @@
import { Subscription } from 'expo-modules-core';
import * as React from 'react';
import { Ref } from 'react';
import { CameraCapturedPicture, CameraOrientation, CameraPictureOptions, CameraProps, CameraRecordingOptions, CameraViewRef, ScanningOptions, ScanningResult, VideoCodec } from './Camera.types';
export default class CameraView extends React.Component<CameraProps> {
/**
* Property that determines if the current device has the ability to use `DataScannerViewController` (iOS 16+).
*/
static isModernBarcodeScannerAvailable: boolean;
/**
* Check whether the current device has a camera. This is useful for web and simulators cases.
* This isn't influenced by the Permissions API (all platforms), or HTTP usage (in the browser).
* You will still need to check if the native permission has been accepted.
* @platform web
*/
static isAvailableAsync(): Promise<boolean>;
/**
* Queries the device for the available video codecs that can be used in video recording.
* @return A promise that resolves to a list of strings that represents available codecs.
* @platform ios
*/
static getAvailableVideoCodecsAsync(): Promise<VideoCodec[]>;
/**
* Get picture sizes that are supported by the device.
* @return Returns a Promise that resolves to an array of strings representing picture sizes that can be passed to `pictureSize` prop.
* The list varies across Android devices but is the same for every iOS.
*/
getAvailablePictureSizesAsync(): Promise<string[]>;
/**
* Resumes the camera preview.
*/
resumePreview(): Promise<void>;
/**
* Pauses the camera preview. It is not recommended to use `takePictureAsync` when preview is paused.
*/
pausePreview(): Promise<void>;
static ConversionTables: {
type: Record<number | typeof Symbol.iterator | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf" | "codePointAt" | "includes" | "endsWith" | "normalize" | "repeat" | "startsWith" | "anchor" | "big" | "blink" | "bold" | "fixed" | "fontcolor" | "fontsize" | "italics" | "link" | "small" | "strike" | "sub" | "sup" | "padStart" | "padEnd" | "trimEnd" | "trimStart" | "trimLeft" | "trimRight" | "matchAll" | "replaceAll" | "at", string | undefined>;
flash: Record<number | typeof Symbol.iterator | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "valueOf" | "codePointAt" | "includes" | "endsWith" | "normalize" | "repeat" | "startsWith" | "anchor" | "big" | "blink" | "bold" | "fixed" | "fontcolor" | "fontsize" | "italics" | "link" | "small" | "strike" | "sub" | "sup" | "padStart" | "padEnd" | "trimEnd" | "trimStart" | "trimLeft" | "trimRight" | "matchAll" | "replaceAll" | "at", string | undefined>;
};
static defaultProps: CameraProps;
_cameraHandle?: number | null;
_cameraRef: React.RefObject<CameraViewRef>;
_lastEvents: {
[eventName: string]: string;
};
_lastEventsTimes: {
[eventName: string]: Date;
};
/**
* Takes a picture and saves it to app's cache directory. Photos are rotated to match device's orientation
* (if `options.skipProcessing` flag is not enabled) and scaled to match the preview. Anyway on Android it is essential
* to set ratio prop to get a picture with correct dimensions.
* > **Note**: Make sure to wait for the [`onCameraReady`](#oncameraready) callback before calling this method.
* @param options An object in form of `CameraPictureOptions` type.
* @return Returns a Promise that resolves to `CameraCapturedPicture` object, where `uri` is a URI to the local image file on iOS,
* Android, and a base64 string on web (usable as the source for an `Image` element). The `width` and `height` properties specify
* the dimensions of the image. `base64` is included if the `base64` option was truthy, and is a string containing the JPEG data
* of the image in Base64--prepend that with `'data:image/jpg;base64,'` to get a data URI, which you can use as the source
* for an `Image` element for example. `exif` is included if the `exif` option was truthy, and is an object containing EXIF
* data for the image--the names of its properties are EXIF tags and their values are the values for those tags.
*
* > On native platforms, the local image URI is temporary. Use [`FileSystem.copyAsync`](filesystem/#filesystemcopyasyncoptions)
* > to make a permanent copy of the image.
*
* **Note** Avoid calling this method while the preview is paused. On iOS, this will take a picture of the last frame that is currently on screen, on Android, this will throw an error.
*/
takePictureAsync(options?: CameraPictureOptions): Promise<CameraCapturedPicture | undefined>;
/**
* Presents a modal view controller that uses the [`DataScannerViewController`](https://developer.apple.com/documentation/visionkit/scanning_data_with_the_camera) available on iOS 16+.
* @platform ios
*/
static launchScanner(options?: ScanningOptions): Promise<void>;
/**
* Dimiss the scanner presented by `launchScanner`.
* @platform ios
*/
static dismissScanner(): Promise<void>;
/**
* Invokes the `listener` function when a bar code has been successfully scanned. The callback is provided with
* an object of the `ScanningResult` shape, where the `type` refers to the bar code type that was scanned and the `data` is the information encoded in the bar code
* (in this case of QR codes, this is often a URL). See [`BarcodeType`](#barcodetype) for supported values.
* @param listener Invoked with the [ScanningResult](#scanningresult) when a bar code has been successfully scanned.
*
* @platform ios
*/
static onModernBarcodeScanned(listener: (event: ScanningResult) => void): Subscription;
/**
* Starts recording a video that will be saved to cache directory. Videos are rotated to match device's orientation.
* Flipping camera during a recording results in stopping it.
* @param options A map of `CameraRecordingOptions` type.
* @return Returns a Promise that resolves to an object containing video file `uri` property and a `codec` property on iOS.
* The Promise is returned if `stopRecording` was invoked, one of `maxDuration` and `maxFileSize` is reached or camera preview is stopped.
* @platform android
* @platform ios
*/
recordAsync(options?: CameraRecordingOptions): Promise<{
uri: string;
} | undefined>;
/**
* Stops recording if any is in progress.
*/
stopRecording(): void;
_onCameraReady: () => void;
_onMountError: ({ nativeEvent }: {
nativeEvent: {
message: string;
};
}) => void;
_onResponsiveOrientationChanged: ({ nativeEvent, }: {
nativeEvent: {
orientation: CameraOrientation;
};
}) => void;
_onObjectDetected: (callback?: Function) => ({ nativeEvent }: {
nativeEvent: any;
}) => void;
_setReference: (ref: Ref<CameraViewRef>) => void;
render(): JSX.Element;
}
//# sourceMappingURL=CameraView.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"CameraView.d.ts","sourceRoot":"","sources":["../src/CameraView.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+C,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9F,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAE5B,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,WAAW,EACX,sBAAsB,EACtB,aAAa,EACb,eAAe,EACf,cAAc,EACd,UAAU,EACX,MAAM,gBAAgB,CAAC;AAiExB,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC;IAClE;;OAEG;IACH,MAAM,CAAC,+BAA+B,EAAE,OAAO,CAAiD;IAChG;;;;;OAKG;WACU,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IASjD;;;;OAIG;WACU,4BAA4B,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAQlE;;;;OAIG;IACG,6BAA6B,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIxD;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAIpC;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAKnC,MAAM,CAAC,gBAAgB;;;MAAoB;IAE3C,MAAM,CAAC,YAAY,EAAE,WAAW,CAM9B;IAEF,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,iCAAoC;IAC9C,WAAW,EAAE;QAAE,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAM;IAClD,gBAAgB,EAAE;QAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAM;IAGrD;;;;;;;;;;;;;;;;;OAiBG;IACG,gBAAgB,CACpB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC;IAM7C;;;OAGG;WACU,aAAa,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IASpE;;;OAGG;WACU,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5C;;;;;;;OAOG;IACH,MAAM,CAAC,sBAAsB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,YAAY;IAItF;;;;;;;;OAQG;IACG,WAAW,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAKzF;;OAEG;IACH,aAAa;IAIb,cAAc,aAIZ;IAEF,aAAa;qBAAoC;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE;eAIlE;IAEF,+BAA+B;qBAGhB;YAAE,aAAa,iBAAiB,CAAA;SAAE;eAK/C;IAEF,iBAAiB,cACH,QAAQ;qBACa,GAAG;eAgBlC;IAEJ,aAAa,QAAS,IAAI,aAAa,CAAC,UAOtC;IAEF,MAAM;CAkBP"}

View File

@@ -0,0 +1,226 @@
import { Platform, UnavailabilityError, EventEmitter } from 'expo-modules-core';
import * as React from 'react';
import ExpoCamera from './ExpoCamera';
import CameraManager from './ExpoCameraManager';
import { ConversionTables, ensureNativeProps } from './utils/props';
const emitter = new EventEmitter(CameraManager);
const EventThrottleMs = 500;
const _PICTURE_SAVED_CALLBACKS = {};
let _GLOBAL_PICTURE_ID = 1;
function ensurePictureOptions(options) {
if (!options || typeof options !== 'object') {
return {};
}
if (!options.quality) {
options.quality = 1;
}
if (options.mirror) {
console.warn('The `mirror` option is deprecated. Please use the `mirror` prop on the `CameraView` instead.');
}
if (options.onPictureSaved) {
const id = _GLOBAL_PICTURE_ID++;
_PICTURE_SAVED_CALLBACKS[id] = options.onPictureSaved;
options.id = id;
options.fastMode = true;
}
return options;
}
function ensureRecordingOptions(options) {
if (!options || typeof options !== 'object') {
return {};
}
if (options.mirror) {
console.warn('The `mirror` option is deprecated. Please use the `mirror` prop on the `CameraView` instead.');
}
return options;
}
function _onPictureSaved({ nativeEvent, }) {
const { id, data } = nativeEvent;
const callback = _PICTURE_SAVED_CALLBACKS[id];
if (callback) {
callback(data);
delete _PICTURE_SAVED_CALLBACKS[id];
}
}
export default class CameraView extends React.Component {
/**
* Property that determines if the current device has the ability to use `DataScannerViewController` (iOS 16+).
*/
static isModernBarcodeScannerAvailable = CameraManager.isModernBarcodeScannerAvailable;
/**
* Check whether the current device has a camera. This is useful for web and simulators cases.
* This isn't influenced by the Permissions API (all platforms), or HTTP usage (in the browser).
* You will still need to check if the native permission has been accepted.
* @platform web
*/
static async isAvailableAsync() {
if (!CameraManager.isAvailableAsync) {
throw new UnavailabilityError('expo-camera', 'isAvailableAsync');
}
return await CameraManager.isAvailableAsync();
}
// @needsAudit
/**
* Queries the device for the available video codecs that can be used in video recording.
* @return A promise that resolves to a list of strings that represents available codecs.
* @platform ios
*/
static async getAvailableVideoCodecsAsync() {
if (!CameraManager.getAvailableVideoCodecsAsync) {
throw new UnavailabilityError('Camera', 'getAvailableVideoCodecsAsync');
}
return await CameraManager.getAvailableVideoCodecsAsync();
}
/**
* Get picture sizes that are supported by the device.
* @return Returns a Promise that resolves to an array of strings representing picture sizes that can be passed to `pictureSize` prop.
* The list varies across Android devices but is the same for every iOS.
*/
async getAvailablePictureSizesAsync() {
return (await this._cameraRef.current?.getAvailablePictureSizes()) ?? [];
}
/**
* Resumes the camera preview.
*/
async resumePreview() {
return this._cameraRef.current?.resumePreview();
}
/**
* Pauses the camera preview. It is not recommended to use `takePictureAsync` when preview is paused.
*/
async pausePreview() {
return this._cameraRef.current?.pausePreview();
}
// Values under keys from this object will be transformed to native options
static ConversionTables = ConversionTables;
static defaultProps = {
zoom: 0,
facing: 'back',
enableTorch: false,
mode: 'picture',
flash: 'off',
};
_cameraHandle;
_cameraRef = React.createRef();
_lastEvents = {};
_lastEventsTimes = {};
// @needsAudit
/**
* Takes a picture and saves it to app's cache directory. Photos are rotated to match device's orientation
* (if `options.skipProcessing` flag is not enabled) and scaled to match the preview. Anyway on Android it is essential
* to set ratio prop to get a picture with correct dimensions.
* > **Note**: Make sure to wait for the [`onCameraReady`](#oncameraready) callback before calling this method.
* @param options An object in form of `CameraPictureOptions` type.
* @return Returns a Promise that resolves to `CameraCapturedPicture` object, where `uri` is a URI to the local image file on iOS,
* Android, and a base64 string on web (usable as the source for an `Image` element). The `width` and `height` properties specify
* the dimensions of the image. `base64` is included if the `base64` option was truthy, and is a string containing the JPEG data
* of the image in Base64--prepend that with `'data:image/jpg;base64,'` to get a data URI, which you can use as the source
* for an `Image` element for example. `exif` is included if the `exif` option was truthy, and is an object containing EXIF
* data for the image--the names of its properties are EXIF tags and their values are the values for those tags.
*
* > On native platforms, the local image URI is temporary. Use [`FileSystem.copyAsync`](filesystem/#filesystemcopyasyncoptions)
* > to make a permanent copy of the image.
*
* **Note** Avoid calling this method while the preview is paused. On iOS, this will take a picture of the last frame that is currently on screen, on Android, this will throw an error.
*/
async takePictureAsync(options) {
const pictureOptions = ensurePictureOptions(options);
return await this._cameraRef.current?.takePicture(pictureOptions);
}
/**
* Presents a modal view controller that uses the [`DataScannerViewController`](https://developer.apple.com/documentation/visionkit/scanning_data_with_the_camera) available on iOS 16+.
* @platform ios
*/
static async launchScanner(options) {
if (!options) {
options = { barcodeTypes: [] };
}
if (Platform.OS === 'ios' && CameraView.isModernBarcodeScannerAvailable) {
await CameraManager.launchScanner(options);
}
}
/**
* Dimiss the scanner presented by `launchScanner`.
* @platform ios
*/
static async dismissScanner() {
if (Platform.OS === 'ios' && CameraView.isModernBarcodeScannerAvailable) {
await CameraManager.dismissScanner();
}
}
/**
* Invokes the `listener` function when a bar code has been successfully scanned. The callback is provided with
* an object of the `ScanningResult` shape, where the `type` refers to the bar code type that was scanned and the `data` is the information encoded in the bar code
* (in this case of QR codes, this is often a URL). See [`BarcodeType`](#barcodetype) for supported values.
* @param listener Invoked with the [ScanningResult](#scanningresult) when a bar code has been successfully scanned.
*
* @platform ios
*/
static onModernBarcodeScanned(listener) {
return emitter.addListener('onModernBarcodeScanned', listener);
}
/**
* Starts recording a video that will be saved to cache directory. Videos are rotated to match device's orientation.
* Flipping camera during a recording results in stopping it.
* @param options A map of `CameraRecordingOptions` type.
* @return Returns a Promise that resolves to an object containing video file `uri` property and a `codec` property on iOS.
* The Promise is returned if `stopRecording` was invoked, one of `maxDuration` and `maxFileSize` is reached or camera preview is stopped.
* @platform android
* @platform ios
*/
async recordAsync(options) {
const recordingOptions = ensureRecordingOptions(options);
return await this._cameraRef.current?.record(recordingOptions);
}
/**
* Stops recording if any is in progress.
*/
stopRecording() {
this._cameraRef.current?.stopRecording();
}
_onCameraReady = () => {
if (this.props.onCameraReady) {
this.props.onCameraReady();
}
};
_onMountError = ({ nativeEvent }) => {
if (this.props.onMountError) {
this.props.onMountError(nativeEvent);
}
};
_onResponsiveOrientationChanged = ({ nativeEvent, }) => {
if (this.props.onResponsiveOrientationChanged) {
this.props.onResponsiveOrientationChanged(nativeEvent);
}
};
_onObjectDetected = (callback) => ({ nativeEvent }) => {
const { type } = nativeEvent;
if (this._lastEvents[type] &&
this._lastEventsTimes[type] &&
JSON.stringify(nativeEvent) === this._lastEvents[type] &&
new Date().getTime() - this._lastEventsTimes[type].getTime() < EventThrottleMs) {
return;
}
if (callback) {
callback(nativeEvent);
this._lastEventsTimes[type] = new Date();
this._lastEvents[type] = JSON.stringify(nativeEvent);
}
};
_setReference = (ref) => {
if (ref) {
// TODO(Bacon): Unify these - perhaps with hooks?
if (Platform.OS === 'web') {
this._cameraHandle = ref;
}
}
};
render() {
const nativeProps = ensureNativeProps(this.props);
const onBarcodeScanned = this.props.onBarcodeScanned
? this._onObjectDetected(this.props.onBarcodeScanned)
: undefined;
return (<ExpoCamera {...nativeProps} ref={this._cameraRef} onCameraReady={this._onCameraReady} onMountError={this._onMountError} onBarcodeScanned={onBarcodeScanned} onPictureSaved={_onPictureSaved} onResponsiveOrientationChanged={this._onResponsiveOrientationChanged}/>);
}
}
//# sourceMappingURL=CameraView.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
import * as React from 'react';
import { CameraNativeProps } from './Camera.types';
declare const ExpoCamera: React.ComponentType<CameraNativeProps>;
export default ExpoCamera;
//# sourceMappingURL=ExpoCamera.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCamera.d.ts","sourceRoot":"","sources":["../src/ExpoCamera.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,QAAA,MAAM,UAAU,EAAE,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAA0C,CAAC;AAElG,eAAe,UAAU,CAAC"}

View File

@@ -0,0 +1,4 @@
import { requireNativeViewManager } from 'expo-modules-core';
const ExpoCamera = requireNativeViewManager('ExpoCamera');
export default ExpoCamera;
//# sourceMappingURL=ExpoCamera.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCamera.js","sourceRoot":"","sources":["../src/ExpoCamera.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAK7D,MAAM,UAAU,GAA2C,wBAAwB,CAAC,YAAY,CAAC,CAAC;AAElG,eAAe,UAAU,CAAC","sourcesContent":["import { requireNativeViewManager } from 'expo-modules-core';\nimport * as React from 'react';\n\nimport { CameraNativeProps } from './Camera.types';\n\nconst ExpoCamera: React.ComponentType<CameraNativeProps> = requireNativeViewManager('ExpoCamera');\n\nexport default ExpoCamera;\n"]}

View File

@@ -0,0 +1,13 @@
import * as React from 'react';
import { CameraCapturedPicture, CameraNativeProps, CameraPictureOptions } from './legacy/Camera.types';
export interface ExponentCameraRef {
getAvailablePictureSizes: (ratio: string) => Promise<string[]>;
takePicture: (options: CameraPictureOptions) => Promise<CameraCapturedPicture>;
resumePreview: () => Promise<void>;
pausePreview: () => Promise<void>;
}
declare const ExponentCamera: React.ForwardRefExoticComponent<Pick<CameraNativeProps & {
children?: React.ReactNode;
}, "type" | "flashMode" | "children" | "pointerEvents" | "style" | "zoom" | "pictureSize" | "poster" | "responsiveOrientationWhenOrientationLocked" | "ratio" | "onCameraReady" | "onMountError" | "onResponsiveOrientationChanged" | "onPictureSaved" | "autoFocus" | "whiteBalance" | "onBarCodeScanned" | "onFacesDetected" | "onFaceDetectionError" | "focusDepth" | "barCodeScannerSettings" | "faceDetectorSettings" | "barCodeScannerEnabled" | "faceDetectorEnabled" | "useCamera2Api"> & React.RefAttributes<ExponentCameraRef>>;
export default ExponentCamera;
//# sourceMappingURL=ExpoCamera.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCamera.web.d.ts","sourceRoot":"","sources":["../src/ExpoCamera.web.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EAErB,MAAM,uBAAuB,CAAC;AAM/B,MAAM,WAAW,iBAAiB;IAChC,wBAAwB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/D,WAAW,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC/E,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AAED,QAAA,MAAM,cAAc;eAE6C,MAAM,SAAS;ygBA+G/E,CAAC;AAEF,eAAe,cAAc,CAAC"}

View File

@@ -0,0 +1,106 @@
import { CodedError } from 'expo-modules-core';
import * as React from 'react';
import { StyleSheet, View } from 'react-native';
import createElement from 'react-native-web/dist/exports/createElement';
import CameraManager from './ExpoCameraManager.web';
import { CameraType, } from './legacy/Camera.types';
import { capture } from './web/WebCameraUtils';
import { PictureSizes } from './web/WebConstants';
import { useWebCameraStream } from './web/useWebCameraStream';
import { useWebQRScanner } from './web/useWebQRScanner';
const ExponentCamera = React.forwardRef(({ type, poster, ...props }, ref) => {
const video = React.useRef(null);
const native = useWebCameraStream(video, type, props, {
onCameraReady() {
if (props.onCameraReady) {
props.onCameraReady();
}
},
onMountError: props.onMountError,
});
const isQRScannerEnabled = React.useMemo(() => {
return !!(props.barCodeScannerSettings?.barCodeTypes?.includes('qr') && !!props.onBarCodeScanned);
}, [props.barCodeScannerSettings?.barCodeTypes, props.onBarCodeScanned]);
useWebQRScanner(video, {
interval: props.barCodeScannerSettings?.interval,
isEnabled: isQRScannerEnabled,
captureOptions: { scale: 1, isImageMirror: native.type === CameraType.front },
onScanned(event) {
if (props.onBarCodeScanned) {
props.onBarCodeScanned(event);
}
},
// onError: props.onMountError,
});
// const [pause, setPaused]
React.useImperativeHandle(ref, () => ({
async getAvailablePictureSizes(ratio) {
return PictureSizes;
},
async takePicture(options) {
if (!video.current || video.current?.readyState !== video.current?.HAVE_ENOUGH_DATA) {
throw new CodedError('ERR_CAMERA_NOT_READY', 'HTMLVideoElement does not have enough camera data to construct an image yet.');
}
const settings = native.mediaTrackSettings;
if (!settings) {
throw new CodedError('ERR_CAMERA_NOT_READY', 'MediaStream is not ready yet.');
}
return capture(video.current, settings, {
...options,
// This will always be defined, the option gets added to a queue in the upper-level. We should replace the original so it isn't called twice.
onPictureSaved(picture) {
if (options.onPictureSaved) {
options.onPictureSaved(picture);
}
if (props.onPictureSaved) {
props.onPictureSaved({ nativeEvent: { data: picture, id: -1 } });
}
},
});
},
async resumePreview() {
if (video.current) {
video.current.play();
}
},
async pausePreview() {
if (video.current) {
video.current.pause();
}
},
}), [native.mediaTrackSettings, props.onPictureSaved]);
// TODO(Bacon): Create a universal prop, on native the microphone is only used when recording videos.
// Because we don't support recording video in the browser we don't need the user to give microphone permissions.
const isMuted = true;
const style = React.useMemo(() => {
const isFrontFacingCamera = native.type === CameraManager.Type.front;
return [
StyleSheet.absoluteFill,
styles.video,
{
// Flip the camera
transform: isFrontFacingCamera ? [{ scaleX: -1 }] : undefined,
},
];
}, [native.type]);
return (<View pointerEvents="box-none" style={[styles.videoWrapper, props.style]}>
<Video autoPlay playsInline muted={isMuted} poster={poster}
// webkitPlaysinline
pointerEvents={props.pointerEvents} ref={video} style={style}/>
{props.children}
</View>);
});
export default ExponentCamera;
const Video = React.forwardRef((props, ref) => createElement('video', { ...props, ref }));
const styles = StyleSheet.create({
videoWrapper: {
flex: 1,
alignItems: 'stretch',
},
video: {
width: '100%',
height: '100%',
objectFit: 'cover',
},
});
//# sourceMappingURL=ExpoCamera.web.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
declare const _default: any;
export default _default;
//# sourceMappingURL=ExpoCameraManager.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCameraManager.d.ts","sourceRoot":"","sources":["../src/ExpoCameraManager.ts"],"names":[],"mappings":";AAEA,wBAAiD"}

View File

@@ -0,0 +1,3 @@
import { requireNativeModule } from 'expo-modules-core';
export default requireNativeModule('ExpoCamera');
//# sourceMappingURL=ExpoCameraManager.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCameraManager.js","sourceRoot":"","sources":["../src/ExpoCameraManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,eAAe,mBAAmB,CAAC,YAAY,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\n\nexport default requireNativeModule('ExpoCamera');\n"]}

View File

@@ -0,0 +1,41 @@
import { CameraCapturedPicture, CameraPictureOptions, PermissionResponse } from './legacy/Camera.types';
import { ExponentCameraRef } from './legacy/ExpoCamera.web';
declare const _default: {
readonly Type: {
back: string;
front: string;
};
readonly FlashMode: {
on: string;
off: string;
auto: string;
torch: string;
};
readonly AutoFocus: {
on: string;
off: string;
auto: string;
singleShot: string;
};
readonly WhiteBalance: {
auto: string;
continuous: string;
manual: string;
};
readonly VideoQuality: {};
readonly VideoStabilization: {};
isAvailableAsync(): Promise<boolean>;
takePicture(options: CameraPictureOptions, camera: ExponentCameraRef): Promise<CameraCapturedPicture>;
pausePreview(camera: ExponentCameraRef): Promise<void>;
resumePreview(camera: ExponentCameraRef): Promise<void>;
getAvailableCameraTypesAsync(): Promise<string[]>;
getAvailablePictureSizes(ratio: string, camera: ExponentCameraRef): Promise<string[]>;
getPermissionsAsync(): Promise<PermissionResponse>;
requestPermissionsAsync(): Promise<PermissionResponse>;
getCameraPermissionsAsync(): Promise<PermissionResponse>;
requestCameraPermissionsAsync(): Promise<PermissionResponse>;
getMicrophonePermissionsAsync(): Promise<PermissionResponse>;
requestMicrophonePermissionsAsync(): Promise<PermissionResponse>;
};
export default _default;
//# sourceMappingURL=ExpoCameraManager.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCameraManager.web.d.ts","sourceRoot":"","sources":["../src/ExpoCameraManager.web.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EAEpB,kBAAkB,EAEnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;wBA0JhC,QAAQ,OAAO,CAAC;yBAI/B,oBAAoB,UACrB,iBAAiB,GACxB,QAAQ,qBAAqB,CAAC;yBAGN,iBAAiB,GAAG,QAAQ,IAAI,CAAC;0BAGhC,iBAAiB,GAAG,QAAQ,IAAI,CAAC;oCAGvB,QAAQ,MAAM,EAAE,CAAC;oCAYjB,MAAM,UAAU,iBAAiB,GAAG,QAAQ,MAAM,EAAE,CAAC;2BAe9D,QAAQ,kBAAkB,CAAC;+BAGvB,QAAQ,kBAAkB,CAAC;iCAGzB,QAAQ,kBAAkB,CAAC;qCAGvB,QAAQ,kBAAkB,CAAC;qCAG3B,QAAQ,kBAAkB,CAAC;yCAGvB,QAAQ,kBAAkB,CAAC;;AA7FxE,wBA4GE"}

View File

@@ -0,0 +1,213 @@
import { UnavailabilityError } from 'expo-modules-core';
import { CameraType, PermissionStatus, } from './legacy/Camera.types';
import { canGetUserMedia, isBackCameraAvailableAsync, isFrontCameraAvailableAsync, } from './web/WebUserMediaManager';
function getUserMedia(constraints) {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
return navigator.mediaDevices.getUserMedia(constraints);
}
// Some browsers partially implement mediaDevices. We can't just assign an object
// with getUserMedia as it would overwrite existing properties.
// Here, we will just add the getUserMedia property if it's missing.
// First get ahold of the legacy getUserMedia, if present
const getUserMedia =
// TODO: this method is deprecated, migrate to https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
function () {
const error = new Error('Permission unimplemented');
error.code = 0;
error.name = 'NotAllowedError';
throw error;
};
return new Promise((resolve, reject) => {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
function handleGetUserMediaError({ message }) {
// name: NotAllowedError
// code: 0
if (message === 'Permission dismissed') {
return {
status: PermissionStatus.UNDETERMINED,
expires: 'never',
canAskAgain: true,
granted: false,
};
}
else {
// TODO: Bacon: [OSX] The system could deny access to chrome.
// TODO: Bacon: add: { status: 'unimplemented' }
return {
status: PermissionStatus.DENIED,
expires: 'never',
canAskAgain: true,
granted: false,
};
}
}
async function handleRequestPermissionsAsync() {
try {
await getUserMedia({
video: true,
});
return {
status: PermissionStatus.GRANTED,
expires: 'never',
canAskAgain: true,
granted: true,
};
}
catch ({ message }) {
return handleGetUserMediaError({ message });
}
}
async function handlePermissionsQueryAsync(query) {
if (!navigator?.permissions?.query) {
throw new UnavailabilityError('expo-camera', 'navigator.permissions API is not available');
}
try {
const { state } = await navigator.permissions.query({ name: query });
switch (state) {
case 'prompt':
return {
status: PermissionStatus.UNDETERMINED,
expires: 'never',
canAskAgain: true,
granted: false,
};
case 'granted':
return {
status: PermissionStatus.GRANTED,
expires: 'never',
canAskAgain: true,
granted: true,
};
case 'denied':
return {
status: PermissionStatus.DENIED,
expires: 'never',
canAskAgain: true,
granted: false,
};
}
}
catch (e) {
// Firefox doesn't support querying for the camera permission, so return undetermined status
if (e instanceof TypeError) {
return {
status: PermissionStatus.UNDETERMINED,
expires: 'never',
canAskAgain: true,
granted: false,
};
}
throw e;
}
}
export default {
get Type() {
return {
back: 'back',
front: 'front',
};
},
get FlashMode() {
return {
on: 'on',
off: 'off',
auto: 'auto',
torch: 'torch',
};
},
get AutoFocus() {
return {
on: 'on',
off: 'off',
auto: 'auto',
singleShot: 'singleShot',
};
},
get WhiteBalance() {
return {
auto: 'auto',
continuous: 'continuous',
manual: 'manual',
};
},
get VideoQuality() {
return {};
},
get VideoStabilization() {
return {};
},
async isAvailableAsync() {
return canGetUserMedia();
},
async takePicture(options, camera) {
return await camera.takePicture(options);
},
async pausePreview(camera) {
await camera.pausePreview();
},
async resumePreview(camera) {
return await camera.resumePreview();
},
async getAvailableCameraTypesAsync() {
if (!canGetUserMedia() || !navigator.mediaDevices.enumerateDevices)
return [];
const devices = await navigator.mediaDevices.enumerateDevices();
const types = await Promise.all([
(await isFrontCameraAvailableAsync(devices)) && CameraType.front,
(await isBackCameraAvailableAsync()) && CameraType.back,
]);
return types.filter(Boolean);
},
async getAvailablePictureSizes(ratio, camera) {
return await camera.getAvailablePictureSizes(ratio);
},
/* async getSupportedRatios(camera: ExponentCameraRef): Promise<string[]> {
// TODO: Support on web
},
async record(
options?: CameraRecordingOptions,
camera: ExponentCameraRef
): Promise<{ uri: string }> {
// TODO: Support on web
},
async stopRecording(camera: ExponentCameraRef): Promise<void> {
// TODO: Support on web
}, */
async getPermissionsAsync() {
return handlePermissionsQueryAsync('camera');
},
async requestPermissionsAsync() {
return handleRequestPermissionsAsync();
},
async getCameraPermissionsAsync() {
return handlePermissionsQueryAsync('camera');
},
async requestCameraPermissionsAsync() {
return handleRequestPermissionsAsync();
},
async getMicrophonePermissionsAsync() {
return handlePermissionsQueryAsync('microphone');
},
async requestMicrophonePermissionsAsync() {
try {
await getUserMedia({
audio: true,
});
return {
status: PermissionStatus.GRANTED,
expires: 'never',
canAskAgain: true,
granted: true,
};
}
catch ({ message }) {
return handleGetUserMediaError({ message });
}
},
};
//# sourceMappingURL=ExpoCameraManager.web.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,68 @@
import { BarcodeScanningResult, BarcodeType } from './Camera.types';
import { PermissionResponse } from './legacy/Camera.types';
export { default as CameraView } from './CameraView';
/**
* Checks user's permissions for accessing camera.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
declare function getCameraPermissionsAsync(): Promise<PermissionResponse>;
/**
* Asks the user to grant permissions for accessing camera.
* On iOS this will require apps to specify an `NSCameraUsageDescription` entry in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
declare function requestCameraPermissionsAsync(): Promise<PermissionResponse>;
/**
* Check or request permissions to access the camera.
* This uses both `requestCameraPermissionsAsync` and `getCameraPermissionsAsync` to interact with the permissions.
*
* @example
* ```ts
* const [status, requestPermission] = useCameraPermissions();
* ```
*/
export declare const useCameraPermissions: (options?: import("expo-modules-core").PermissionHookOptions<object> | undefined) => [PermissionResponse | null, () => Promise<PermissionResponse>, () => Promise<PermissionResponse>];
/**
* Checks user's permissions for accessing microphone.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
declare function getMicrophonePermissionsAsync(): Promise<PermissionResponse>;
/**
* Asks the user to grant permissions for accessing the microphone.
* On iOS this will require apps to specify an `NSMicrophoneUsageDescription` entry in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
declare function requestMicrophonePermissionsAsync(): Promise<PermissionResponse>;
/**
* Check or request permissions to access the microphone.
* This uses both `requestMicrophonePermissionsAsync` and `getMicrophonePermissionsAsync` to interact with the permissions.
*
* @example
* ```ts
* const [status, requestPermission] = Camera.useMicrophonePermissions();
* ```
*/
export declare const useMicrophonePermissions: (options?: import("expo-modules-core").PermissionHookOptions<object> | undefined) => [PermissionResponse | null, () => Promise<PermissionResponse>, () => Promise<PermissionResponse>];
/**
* Scan bar codes from the image at the given URL.
* @param url URL to get the image from.
* @param barcodeTypes An array of bar code types. Defaults to all supported bar code types on
* the platform.
* > __Note:__ Only QR codes are supported on iOS.
* On android, the barcode should take up the majority of the image for best results.
* @return A possibly empty array of objects of the `BarcodeScanningResult` shape, where the type
* refers to the barcode type that was scanned and the data is the information encoded in the barcode.
*/
export declare function scanFromURLAsync(url: string, barcodeTypes?: BarcodeType[]): Promise<BarcodeScanningResult[]>;
export * from './Camera.types';
/**
* @hidden
*/
export declare const Camera: {
getCameraPermissionsAsync: typeof getCameraPermissionsAsync;
requestCameraPermissionsAsync: typeof requestCameraPermissionsAsync;
getMicrophonePermissionsAsync: typeof getMicrophonePermissionsAsync;
requestMicrophonePermissionsAsync: typeof requestMicrophonePermissionsAsync;
scanFromURLAsync: typeof scanFromURLAsync;
};
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AAGrD;;;GAGG;AACH,iBAAe,yBAAyB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAEtE;AAGD;;;;GAIG;AACH,iBAAe,6BAA6B,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAE1E;AAGD;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,wLAG/B,CAAC;AAGH;;;GAGG;AACH,iBAAe,6BAA6B,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAE1E;AAGD;;;;GAIG;AACH,iBAAe,iCAAiC,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAE9E;AAGD;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,wLAGnC,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,YAAY,GAAE,WAAW,EAAW,GACnC,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAElC;AAED,cAAc,gBAAgB,CAAC;AAE/B;;GAEG;AACH,eAAO,MAAM,MAAM;;;;;;CAMlB,CAAC"}

View File

@@ -0,0 +1,90 @@
import { createPermissionHook } from 'expo-modules-core';
import CameraManager from './ExpoCameraManager';
export { default as CameraView } from './CameraView';
// @needsAudit
/**
* Checks user's permissions for accessing camera.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
async function getCameraPermissionsAsync() {
return CameraManager.getCameraPermissionsAsync();
}
// @needsAudit
/**
* Asks the user to grant permissions for accessing camera.
* On iOS this will require apps to specify an `NSCameraUsageDescription` entry in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
async function requestCameraPermissionsAsync() {
return CameraManager.requestCameraPermissionsAsync();
}
// @needsAudit
/**
* Check or request permissions to access the camera.
* This uses both `requestCameraPermissionsAsync` and `getCameraPermissionsAsync` to interact with the permissions.
*
* @example
* ```ts
* const [status, requestPermission] = useCameraPermissions();
* ```
*/
export const useCameraPermissions = createPermissionHook({
getMethod: getCameraPermissionsAsync,
requestMethod: requestCameraPermissionsAsync,
});
// @needsAudit
/**
* Checks user's permissions for accessing microphone.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
async function getMicrophonePermissionsAsync() {
return CameraManager.getMicrophonePermissionsAsync();
}
// @needsAudit
/**
* Asks the user to grant permissions for accessing the microphone.
* On iOS this will require apps to specify an `NSMicrophoneUsageDescription` entry in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
async function requestMicrophonePermissionsAsync() {
return CameraManager.requestMicrophonePermissionsAsync();
}
// @needsAudit
/**
* Check or request permissions to access the microphone.
* This uses both `requestMicrophonePermissionsAsync` and `getMicrophonePermissionsAsync` to interact with the permissions.
*
* @example
* ```ts
* const [status, requestPermission] = Camera.useMicrophonePermissions();
* ```
*/
export const useMicrophonePermissions = createPermissionHook({
getMethod: getMicrophonePermissionsAsync,
requestMethod: requestMicrophonePermissionsAsync,
});
/**
* Scan bar codes from the image at the given URL.
* @param url URL to get the image from.
* @param barcodeTypes An array of bar code types. Defaults to all supported bar code types on
* the platform.
* > __Note:__ Only QR codes are supported on iOS.
* On android, the barcode should take up the majority of the image for best results.
* @return A possibly empty array of objects of the `BarcodeScanningResult` shape, where the type
* refers to the barcode type that was scanned and the data is the information encoded in the barcode.
*/
export async function scanFromURLAsync(url, barcodeTypes = ['qr']) {
return CameraManager.scanFromURLAsync(url, barcodeTypes);
}
export * from './Camera.types';
/**
* @hidden
*/
export const Camera = {
getCameraPermissionsAsync,
requestCameraPermissionsAsync,
getMicrophonePermissionsAsync,
requestMicrophonePermissionsAsync,
scanFromURLAsync,
};
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGzD,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAGhD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AAErD,cAAc;AACd;;;GAGG;AACH,KAAK,UAAU,yBAAyB;IACtC,OAAO,aAAa,CAAC,yBAAyB,EAAE,CAAC;AACnD,CAAC;AAED,cAAc;AACd;;;;GAIG;AACH,KAAK,UAAU,6BAA6B;IAC1C,OAAO,aAAa,CAAC,6BAA6B,EAAE,CAAC;AACvD,CAAC;AAED,cAAc;AACd;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;IACvD,SAAS,EAAE,yBAAyB;IACpC,aAAa,EAAE,6BAA6B;CAC7C,CAAC,CAAC;AAEH,cAAc;AACd;;;GAGG;AACH,KAAK,UAAU,6BAA6B;IAC1C,OAAO,aAAa,CAAC,6BAA6B,EAAE,CAAC;AACvD,CAAC;AAED,cAAc;AACd;;;;GAIG;AACH,KAAK,UAAU,iCAAiC;IAC9C,OAAO,aAAa,CAAC,iCAAiC,EAAE,CAAC;AAC3D,CAAC;AAED,cAAc;AACd;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,oBAAoB,CAAC;IAC3D,SAAS,EAAE,6BAA6B;IACxC,aAAa,EAAE,iCAAiC;CACjD,CAAC,CAAC;AAEH;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAAW,EACX,eAA8B,CAAC,IAAI,CAAC;IAEpC,OAAO,aAAa,CAAC,gBAAgB,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;AAC3D,CAAC;AAED,cAAc,gBAAgB,CAAC;AAE/B;;GAEG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,yBAAyB;IACzB,6BAA6B;IAC7B,6BAA6B;IAC7B,iCAAiC;IACjC,gBAAgB;CACjB,CAAC","sourcesContent":["import { createPermissionHook } from 'expo-modules-core';\n\nimport { BarcodeScanningResult, BarcodeType } from './Camera.types';\nimport CameraManager from './ExpoCameraManager';\nimport { PermissionResponse } from './legacy/Camera.types';\n\nexport { default as CameraView } from './CameraView';\n\n// @needsAudit\n/**\n * Checks user's permissions for accessing camera.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function getCameraPermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.getCameraPermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Asks the user to grant permissions for accessing camera.\n * On iOS this will require apps to specify an `NSCameraUsageDescription` entry in the **Info.plist**.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function requestCameraPermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.requestCameraPermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Check or request permissions to access the camera.\n * This uses both `requestCameraPermissionsAsync` and `getCameraPermissionsAsync` to interact with the permissions.\n *\n * @example\n * ```ts\n * const [status, requestPermission] = useCameraPermissions();\n * ```\n */\nexport const useCameraPermissions = createPermissionHook({\n getMethod: getCameraPermissionsAsync,\n requestMethod: requestCameraPermissionsAsync,\n});\n\n// @needsAudit\n/**\n * Checks user's permissions for accessing microphone.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function getMicrophonePermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.getMicrophonePermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Asks the user to grant permissions for accessing the microphone.\n * On iOS this will require apps to specify an `NSMicrophoneUsageDescription` entry in the **Info.plist**.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function requestMicrophonePermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.requestMicrophonePermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Check or request permissions to access the microphone.\n * This uses both `requestMicrophonePermissionsAsync` and `getMicrophonePermissionsAsync` to interact with the permissions.\n *\n * @example\n * ```ts\n * const [status, requestPermission] = Camera.useMicrophonePermissions();\n * ```\n */\nexport const useMicrophonePermissions = createPermissionHook({\n getMethod: getMicrophonePermissionsAsync,\n requestMethod: requestMicrophonePermissionsAsync,\n});\n\n/**\n * Scan bar codes from the image at the given URL.\n * @param url URL to get the image from.\n * @param barcodeTypes An array of bar code types. Defaults to all supported bar code types on\n * the platform.\n * > __Note:__ Only QR codes are supported on iOS.\n * On android, the barcode should take up the majority of the image for best results.\n * @return A possibly empty array of objects of the `BarcodeScanningResult` shape, where the type\n * refers to the barcode type that was scanned and the data is the information encoded in the barcode.\n */\nexport async function scanFromURLAsync(\n url: string,\n barcodeTypes: BarcodeType[] = ['qr']\n): Promise<BarcodeScanningResult[]> {\n return CameraManager.scanFromURLAsync(url, barcodeTypes);\n}\n\nexport * from './Camera.types';\n\n/**\n * @hidden\n */\nexport const Camera = {\n getCameraPermissionsAsync,\n requestCameraPermissionsAsync,\n getMicrophonePermissionsAsync,\n requestMicrophonePermissionsAsync,\n scanFromURLAsync,\n};\n"]}

View File

@@ -0,0 +1,164 @@
import * as React from 'react';
import { CameraCapturedPicture, CameraOrientation, CameraPictureOptions, CameraProps, CameraRecordingOptions, CameraType, ConstantsType, PermissionResponse, VideoCodec } from './Camera.types';
export default class Camera extends React.Component<CameraProps> {
/**
* Check whether the current device has a camera. This is useful for web and simulators cases.
* This isn't influenced by the Permissions API (all platforms), or HTTP usage (in the browser).
* You will still need to check if the native permission has been accepted.
* @platform web
*/
static isAvailableAsync(): Promise<boolean>;
/**
* Returns a list of camera types `['front', 'back']`. This is useful for desktop browsers which only have front-facing cameras.
* @platform web
*/
static getAvailableCameraTypesAsync(): Promise<CameraType[]>;
/**
* Queries the device for the available video codecs that can be used in video recording.
* @return A promise that resolves to a list of strings that represents available codecs.
* @platform ios
*/
static getAvailableVideoCodecsAsync(): Promise<VideoCodec[]>;
static Constants: ConstantsType;
static ConversionTables: {
type: Record<"front" | "back", string | number | undefined>;
flashMode: Record<"off" | "on" | "auto" | "torch", string | number | undefined>;
autoFocus: Record<"off" | "on" | "auto" | "singleShot", string | number | boolean | undefined>;
whiteBalance: Record<"auto" | "sunny" | "cloudy" | "shadow" | "incandescent" | "fluorescent" | "continuous" | "manual", string | number | undefined>;
};
static defaultProps: CameraProps;
/**
* @deprecated Use `getCameraPermissionsAsync` or `getMicrophonePermissionsAsync` instead.
* Checks user's permissions for accessing camera.
*/
static getPermissionsAsync(): Promise<PermissionResponse>;
/**
* Asks the user to grant permissions for accessing camera.
* On iOS this will require apps to specify both `NSCameraUsageDescription` and `NSMicrophoneUsageDescription` entries in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
* @deprecated Use `requestCameraPermissionsAsync` or `requestMicrophonePermissionsAsync` instead.
*/
static requestPermissionsAsync(): Promise<PermissionResponse>;
/**
* Checks user's permissions for accessing camera.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
static getCameraPermissionsAsync(): Promise<PermissionResponse>;
/**
* Asks the user to grant permissions for accessing camera.
* On iOS this will require apps to specify an `NSCameraUsageDescription` entry in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
static requestCameraPermissionsAsync(): Promise<PermissionResponse>;
/**
* Check or request permissions to access the camera.
* This uses both `requestCameraPermissionsAsync` and `getCameraPermissionsAsync` to interact with the permissions.
*
* @example
* ```ts
* const [status, requestPermission] = Camera.useCameraPermissions();
* ```
*/
static useCameraPermissions: (options?: import("expo-modules-core").PermissionHookOptions<object> | undefined) => [PermissionResponse | null, () => Promise<PermissionResponse>, () => Promise<PermissionResponse>];
/**
* Checks user's permissions for accessing microphone.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
static getMicrophonePermissionsAsync(): Promise<PermissionResponse>;
/**
* Asks the user to grant permissions for accessing the microphone.
* On iOS this will require apps to specify an `NSMicrophoneUsageDescription` entry in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
static requestMicrophonePermissionsAsync(): Promise<PermissionResponse>;
/**
* Check or request permissions to access the microphone.
* This uses both `requestMicrophonePermissionsAsync` and `getMicrophonePermissionsAsync` to interact with the permissions.
*
* @example
* ```ts
* const [status, requestPermission] = Camera.useMicrophonePermissions();
* ```
*/
static useMicrophonePermissions: (options?: import("expo-modules-core").PermissionHookOptions<object> | undefined) => [PermissionResponse | null, () => Promise<PermissionResponse>, () => Promise<PermissionResponse>];
_cameraHandle?: number | null;
_cameraRef?: React.Component | null;
_lastEvents: {
[eventName: string]: string;
};
_lastEventsTimes: {
[eventName: string]: Date;
};
/**
* Takes a picture and saves it to app's cache directory. Photos are rotated to match device's orientation
* (if `options.skipProcessing` flag is not enabled) and scaled to match the preview. Anyway on Android it is essential
* to set ratio prop to get a picture with correct dimensions.
* > **Note**: Make sure to wait for the [`onCameraReady`](#oncameraready) callback before calling this method.
* @param options An object in form of `CameraPictureOptions` type.
* @return Returns a Promise that resolves to `CameraCapturedPicture` object, where `uri` is a URI to the local image file on iOS,
* Android, and a base64 string on web (usable as the source for an `Image` element). The `width` and `height` properties specify
* the dimensions of the image. `base64` is included if the `base64` option was truthy, and is a string containing the JPEG data
* of the image in Base64--prepend that with `'data:image/jpg;base64,'` to get a data URI, which you can use as the source
* for an `Image` element for example. `exif` is included if the `exif` option was truthy, and is an object containing EXIF
* data for the image--the names of its properties are EXIF tags and their values are the values for those tags.
*
* > On native platforms, the local image URI is temporary. Use [`FileSystem.copyAsync`](filesystem/#filesystemcachedirectory)
* > to make a permanent copy of the image.
*/
takePictureAsync(options?: CameraPictureOptions): Promise<CameraCapturedPicture>;
/**
* Get aspect ratios that are supported by the device and can be passed via `ratio` prop.
* @return Returns a Promise that resolves to an array of strings representing ratios, eg. `['4:3', '1:1']`.
* @platform android
*/
getSupportedRatiosAsync(): Promise<string[]>;
/**
* Get picture sizes that are supported by the device for given `ratio`.
* @param ratio A string representing aspect ratio of sizes to be returned.
* @return Returns a Promise that resolves to an array of strings representing picture sizes that can be passed to `pictureSize` prop.
* The list varies across Android devices but is the same for every iOS.
*/
getAvailablePictureSizesAsync(ratio: string): Promise<string[]>;
/**
* Starts recording a video that will be saved to cache directory. Videos are rotated to match device's orientation.
* Flipping camera during a recording results in stopping it.
* @param options A map of `CameraRecordingOptions` type.
* @return Returns a Promise that resolves to an object containing video file `uri` property and a `codec` property on iOS.
* The Promise is returned if `stopRecording` was invoked, one of `maxDuration` and `maxFileSize` is reached or camera preview is stopped.
* @platform android
* @platform ios
*/
recordAsync(options?: CameraRecordingOptions): Promise<{
uri: string;
}>;
/**
* Stops recording if any is in progress.
*/
stopRecording(): Promise<void>;
/**
* Pauses the camera preview. It is not recommended to use `takePictureAsync` when preview is paused.
*/
pausePreview(): Promise<void>;
/**
* Resumes the camera preview.
*/
resumePreview(): Promise<void>;
_onCameraReady: () => void;
_onMountError: ({ nativeEvent }: {
nativeEvent: {
message: string;
};
}) => void;
_onResponsiveOrientationChanged: ({ nativeEvent, }: {
nativeEvent: {
orientation: CameraOrientation;
};
}) => void;
_onObjectDetected: (callback?: Function) => ({ nativeEvent }: {
nativeEvent: any;
}) => void;
_setReference: (ref?: React.Component) => void;
render(): JSX.Element;
}
export declare const Constants: ConstantsType, getPermissionsAsync: typeof Camera.getPermissionsAsync, requestPermissionsAsync: typeof Camera.requestPermissionsAsync, getCameraPermissionsAsync: typeof Camera.getCameraPermissionsAsync, requestCameraPermissionsAsync: typeof Camera.requestCameraPermissionsAsync, getMicrophonePermissionsAsync: typeof Camera.getMicrophonePermissionsAsync, requestMicrophonePermissionsAsync: typeof Camera.requestMicrophonePermissionsAsync;
//# sourceMappingURL=Camera.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Camera.d.ts","sourceRoot":"","sources":["../../src/legacy/Camera.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,WAAW,EACX,sBAAsB,EACtB,UAAU,EACV,aAAa,EACb,kBAAkB,EAClB,UAAU,EACX,MAAM,gBAAgB,CAAC;AAoDxB,MAAM,CAAC,OAAO,OAAO,MAAO,SAAQ,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC;IAC9D;;;;;OAKG;WACU,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IAQjD;;;OAGG;WACU,4BAA4B,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IASlE;;;;OAIG;WACU,4BAA4B,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAQlE,MAAM,CAAC,SAAS,EAAE,aAAa,CAQ7B;IAGF,MAAM,CAAC,gBAAgB;;;;;MAAoB;IAE3C,MAAM,CAAC,YAAY,EAAE,WAAW,CAS9B;IAGF;;;OAGG;WACU,mBAAmB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAQ/D;;;;;OAKG;WACU,uBAAuB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAQnE;;;OAGG;WACU,yBAAyB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAKrE;;;;OAIG;WACU,6BAA6B,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAKzE;;;;;;;;OAQG;IACH,MAAM,CAAC,oBAAoB,yLAGxB;IAGH;;;OAGG;WACU,6BAA6B,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAKzE;;;;OAIG;WACU,iCAAiC,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAK7E;;;;;;;;OAQG;IACH,MAAM,CAAC,wBAAwB,yLAG5B;IAEH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC;IACpC,WAAW,EAAE;QAAE,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAM;IAClD,gBAAgB,EAAE;QAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAM;IAGrD;;;;;;;;;;;;;;;OAeG;IACG,gBAAgB,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAMtF;;;;OAIG;IACG,uBAAuB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAQlD;;;;;OAKG;IACG,6BAA6B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAOrE;;;;;;;;OAQG;IACG,WAAW,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAS7E;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAQpC;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAQnC;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAQpC,cAAc,aAIZ;IAEF,aAAa;qBAAoC;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE;eAIlE;IAEF,+BAA+B;qBAGhB;YAAE,aAAa,iBAAiB,CAAA;SAAE;eAK/C;IAEF,iBAAiB,cACH,QAAQ;qBACa,GAAG;eAgBlC;IAEJ,aAAa,SAAU,MAAM,SAAS,UAapC;IAEF,MAAM;CAsBP;AAED,eAAO,MACL,SAAS,iBACT,mBAAmB,qCACnB,uBAAuB,yCACvB,yBAAyB,2CACzB,6BAA6B,+CAC7B,6BAA6B,+CAC7B,iCAAiC,iDACzB,CAAC"}

View File

@@ -0,0 +1,325 @@
import { createPermissionHook, Platform, UnavailabilityError } from 'expo-modules-core';
import * as React from 'react';
import { findNodeHandle } from 'react-native';
import ExpoCamera from './ExpoCamera';
import CameraManager from './ExpoCameraManager';
import { ConversionTables, ensureNativeProps } from './utils/props';
const EventThrottleMs = 500;
const _PICTURE_SAVED_CALLBACKS = {};
let _GLOBAL_PICTURE_ID = 1;
function ensurePictureOptions(options) {
const pictureOptions = !options || typeof options !== 'object' ? {} : options;
if (!pictureOptions.quality) {
pictureOptions.quality = 1;
}
if (pictureOptions.onPictureSaved) {
const id = _GLOBAL_PICTURE_ID++;
_PICTURE_SAVED_CALLBACKS[id] = pictureOptions.onPictureSaved;
pictureOptions.id = id;
pictureOptions.fastMode = true;
}
return pictureOptions;
}
function ensureRecordingOptions(options) {
let recordingOptions = options || {};
if (!recordingOptions || typeof recordingOptions !== 'object') {
recordingOptions = {};
}
else if (typeof recordingOptions.quality === 'string') {
recordingOptions.quality = Camera.Constants.VideoQuality[recordingOptions.quality];
}
return recordingOptions;
}
function _onPictureSaved({ nativeEvent, }) {
const { id, data } = nativeEvent;
const callback = _PICTURE_SAVED_CALLBACKS[id];
if (callback) {
callback(data);
delete _PICTURE_SAVED_CALLBACKS[id];
}
}
export default class Camera extends React.Component {
/**
* Check whether the current device has a camera. This is useful for web and simulators cases.
* This isn't influenced by the Permissions API (all platforms), or HTTP usage (in the browser).
* You will still need to check if the native permission has been accepted.
* @platform web
*/
static async isAvailableAsync() {
if (!CameraManager.isAvailableAsync) {
throw new UnavailabilityError('expo-camera', 'isAvailableAsync');
}
return await CameraManager.isAvailableAsync();
}
/**
* Returns a list of camera types `['front', 'back']`. This is useful for desktop browsers which only have front-facing cameras.
* @platform web
*/
static async getAvailableCameraTypesAsync() {
if (!CameraManager.getAvailableCameraTypesAsync) {
throw new UnavailabilityError('expo-camera', 'getAvailableCameraTypesAsync');
}
return await CameraManager.getAvailableCameraTypesAsync();
}
// @needsAudit
/**
* Queries the device for the available video codecs that can be used in video recording.
* @return A promise that resolves to a list of strings that represents available codecs.
* @platform ios
*/
static async getAvailableVideoCodecsAsync() {
if (!CameraManager.getAvailableVideoCodecsAsync) {
throw new UnavailabilityError('Camera', 'getAvailableVideoCodecsAsync');
}
return await CameraManager.getAvailableVideoCodecsAsync();
}
static Constants = {
Type: CameraManager.Type,
FlashMode: CameraManager.FlashMode,
AutoFocus: CameraManager.AutoFocus,
WhiteBalance: CameraManager.WhiteBalance,
VideoQuality: CameraManager.VideoQuality,
VideoStabilization: CameraManager.VideoStabilization || {},
VideoCodec: CameraManager.VideoCodec,
};
// Values under keys from this object will be transformed to native options
static ConversionTables = ConversionTables;
static defaultProps = {
zoom: 0,
ratio: '4:3',
focusDepth: 0,
faceDetectorSettings: {},
type: CameraManager.Type.back,
autoFocus: CameraManager.AutoFocus.on,
flashMode: CameraManager.FlashMode.off,
whiteBalance: CameraManager.WhiteBalance.auto,
};
// @needsAudit
/**
* @deprecated Use `getCameraPermissionsAsync` or `getMicrophonePermissionsAsync` instead.
* Checks user's permissions for accessing camera.
*/
static async getPermissionsAsync() {
console.warn(`"getPermissionsAsync()" is now deprecated. Please use "getCameraPermissionsAsync()" or "getMicrophonePermissionsAsync()" instead.`);
return CameraManager.getPermissionsAsync();
}
// @needsAudit
/**
* Asks the user to grant permissions for accessing camera.
* On iOS this will require apps to specify both `NSCameraUsageDescription` and `NSMicrophoneUsageDescription` entries in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
* @deprecated Use `requestCameraPermissionsAsync` or `requestMicrophonePermissionsAsync` instead.
*/
static async requestPermissionsAsync() {
console.warn(`"requestPermissionsAsync()" is now deprecated. Please use "requestCameraPermissionsAsync()" or "requestMicrophonePermissionsAsync()" instead.`);
return CameraManager.requestPermissionsAsync();
}
// @needsAudit
/**
* Checks user's permissions for accessing camera.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
static async getCameraPermissionsAsync() {
return CameraManager.getCameraPermissionsAsync();
}
// @needsAudit
/**
* Asks the user to grant permissions for accessing camera.
* On iOS this will require apps to specify an `NSCameraUsageDescription` entry in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
static async requestCameraPermissionsAsync() {
return CameraManager.requestCameraPermissionsAsync();
}
// @needsAudit
/**
* Check or request permissions to access the camera.
* This uses both `requestCameraPermissionsAsync` and `getCameraPermissionsAsync` to interact with the permissions.
*
* @example
* ```ts
* const [status, requestPermission] = Camera.useCameraPermissions();
* ```
*/
static useCameraPermissions = createPermissionHook({
getMethod: Camera.getCameraPermissionsAsync,
requestMethod: Camera.requestCameraPermissionsAsync,
});
// @needsAudit
/**
* Checks user's permissions for accessing microphone.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
static async getMicrophonePermissionsAsync() {
return CameraManager.getMicrophonePermissionsAsync();
}
// @needsAudit
/**
* Asks the user to grant permissions for accessing the microphone.
* On iOS this will require apps to specify an `NSMicrophoneUsageDescription` entry in the **Info.plist**.
* @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).
*/
static async requestMicrophonePermissionsAsync() {
return CameraManager.requestMicrophonePermissionsAsync();
}
// @needsAudit
/**
* Check or request permissions to access the microphone.
* This uses both `requestMicrophonePermissionsAsync` and `getMicrophonePermissionsAsync` to interact with the permissions.
*
* @example
* ```ts
* const [status, requestPermission] = Camera.useMicrophonePermissions();
* ```
*/
static useMicrophonePermissions = createPermissionHook({
getMethod: Camera.getMicrophonePermissionsAsync,
requestMethod: Camera.requestMicrophonePermissionsAsync,
});
_cameraHandle;
_cameraRef;
_lastEvents = {};
_lastEventsTimes = {};
// @needsAudit
/**
* Takes a picture and saves it to app's cache directory. Photos are rotated to match device's orientation
* (if `options.skipProcessing` flag is not enabled) and scaled to match the preview. Anyway on Android it is essential
* to set ratio prop to get a picture with correct dimensions.
* > **Note**: Make sure to wait for the [`onCameraReady`](#oncameraready) callback before calling this method.
* @param options An object in form of `CameraPictureOptions` type.
* @return Returns a Promise that resolves to `CameraCapturedPicture` object, where `uri` is a URI to the local image file on iOS,
* Android, and a base64 string on web (usable as the source for an `Image` element). The `width` and `height` properties specify
* the dimensions of the image. `base64` is included if the `base64` option was truthy, and is a string containing the JPEG data
* of the image in Base64--prepend that with `'data:image/jpg;base64,'` to get a data URI, which you can use as the source
* for an `Image` element for example. `exif` is included if the `exif` option was truthy, and is an object containing EXIF
* data for the image--the names of its properties are EXIF tags and their values are the values for those tags.
*
* > On native platforms, the local image URI is temporary. Use [`FileSystem.copyAsync`](filesystem/#filesystemcachedirectory)
* > to make a permanent copy of the image.
*/
async takePictureAsync(options) {
const pictureOptions = ensurePictureOptions(options);
return await CameraManager.takePicture(pictureOptions, this._cameraHandle);
}
/**
* Get aspect ratios that are supported by the device and can be passed via `ratio` prop.
* @return Returns a Promise that resolves to an array of strings representing ratios, eg. `['4:3', '1:1']`.
* @platform android
*/
async getSupportedRatiosAsync() {
if (!CameraManager.getSupportedRatios) {
throw new UnavailabilityError('Camera', 'getSupportedRatiosAsync');
}
return await CameraManager.getSupportedRatios(this._cameraHandle);
}
/**
* Get picture sizes that are supported by the device for given `ratio`.
* @param ratio A string representing aspect ratio of sizes to be returned.
* @return Returns a Promise that resolves to an array of strings representing picture sizes that can be passed to `pictureSize` prop.
* The list varies across Android devices but is the same for every iOS.
*/
async getAvailablePictureSizesAsync(ratio) {
if (!CameraManager.getAvailablePictureSizes) {
throw new UnavailabilityError('Camera', 'getAvailablePictureSizesAsync');
}
return await CameraManager.getAvailablePictureSizes(ratio, this._cameraHandle);
}
/**
* Starts recording a video that will be saved to cache directory. Videos are rotated to match device's orientation.
* Flipping camera during a recording results in stopping it.
* @param options A map of `CameraRecordingOptions` type.
* @return Returns a Promise that resolves to an object containing video file `uri` property and a `codec` property on iOS.
* The Promise is returned if `stopRecording` was invoked, one of `maxDuration` and `maxFileSize` is reached or camera preview is stopped.
* @platform android
* @platform ios
*/
async recordAsync(options) {
if (!CameraManager.record) {
throw new UnavailabilityError('Camera', 'recordAsync');
}
const recordingOptions = ensureRecordingOptions(options);
return await CameraManager.record(recordingOptions, this._cameraHandle);
}
/**
* Stops recording if any is in progress.
*/
async stopRecording() {
if (!CameraManager.stopRecording) {
throw new UnavailabilityError('Camera', 'stopRecording');
}
return await CameraManager.stopRecording(this._cameraHandle);
}
/**
* Pauses the camera preview. It is not recommended to use `takePictureAsync` when preview is paused.
*/
async pausePreview() {
if (!CameraManager.pausePreview) {
throw new UnavailabilityError('Camera', 'pausePreview');
}
return await CameraManager.pausePreview(this._cameraHandle);
}
/**
* Resumes the camera preview.
*/
async resumePreview() {
if (!CameraManager.resumePreview) {
throw new UnavailabilityError('Camera', 'resumePreview');
}
return await CameraManager.resumePreview(this._cameraHandle);
}
_onCameraReady = () => {
if (this.props.onCameraReady) {
this.props.onCameraReady();
}
};
_onMountError = ({ nativeEvent }) => {
if (this.props.onMountError) {
this.props.onMountError(nativeEvent);
}
};
_onResponsiveOrientationChanged = ({ nativeEvent, }) => {
if (this.props.onResponsiveOrientationChanged) {
this.props.onResponsiveOrientationChanged(nativeEvent);
}
};
_onObjectDetected = (callback) => ({ nativeEvent }) => {
const { type } = nativeEvent;
if (this._lastEvents[type] &&
this._lastEventsTimes[type] &&
JSON.stringify(nativeEvent) === this._lastEvents[type] &&
new Date().getTime() - this._lastEventsTimes[type].getTime() < EventThrottleMs) {
return;
}
if (callback) {
callback(nativeEvent);
this._lastEventsTimes[type] = new Date();
this._lastEvents[type] = JSON.stringify(nativeEvent);
}
};
_setReference = (ref) => {
if (ref) {
this._cameraRef = ref;
// TODO(Bacon): Unify these - perhaps with hooks?
if (Platform.OS === 'web') {
this._cameraHandle = ref;
}
else {
this._cameraHandle = findNodeHandle(ref);
}
}
else {
this._cameraRef = null;
this._cameraHandle = null;
}
};
render() {
const nativeProps = ensureNativeProps(this.props);
const onBarCodeScanned = this.props.onBarCodeScanned
? this._onObjectDetected(this.props.onBarCodeScanned)
: undefined;
const onFacesDetected = this._onObjectDetected(this.props.onFacesDetected);
return (<ExpoCamera {...nativeProps} ref={this._setReference} onCameraReady={this._onCameraReady} onMountError={this._onMountError} onBarCodeScanned={onBarCodeScanned} onFacesDetected={onFacesDetected} onPictureSaved={_onPictureSaved} onResponsiveOrientationChanged={this._onResponsiveOrientationChanged}/>);
}
}
export const { Constants, getPermissionsAsync, requestPermissionsAsync, getCameraPermissionsAsync, requestCameraPermissionsAsync, getMicrophonePermissionsAsync, requestMicrophonePermissionsAsync, } = Camera;
//# sourceMappingURL=Camera.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,515 @@
import { PermissionResponse, PermissionStatus, PermissionExpiration, PermissionHookOptions } from 'expo-modules-core';
import type { ViewProps } from 'react-native';
export declare enum CameraType {
front = "front",
back = "back"
}
export declare enum FlashMode {
on = "on",
off = "off",
auto = "auto",
torch = "torch"
}
export declare enum AutoFocus {
on = "on",
off = "off",
/**
* @platform web
*/
auto = "auto",
/**
* @platform web
*/
singleShot = "singleShot"
}
export declare enum WhiteBalance {
auto = "auto",
/**
* @platform android
* @platform ios
*/
sunny = "sunny",
/**
* @platform android
* @platform ios
*/
cloudy = "cloudy",
/**
* @platform android
* @platform ios
*/
shadow = "shadow",
/**
* @platform android
* @platform ios
*/
incandescent = "incandescent",
/**
* @platform android
* @platform ios
*/
fluorescent = "fluorescent",
/**
* @platform web
*/
continuous = "continuous",
/**
* @platform web
*/
manual = "manual"
}
export declare enum ImageType {
png = "png",
jpg = "jpg"
}
/**
* This option specifies what codec to use when recording a video.
* @platform ios
*/
export declare enum VideoCodec {
H264 = "avc1",
HEVC = "hvc1",
JPEG = "jpeg",
AppleProRes422 = "apcn",
AppleProRes4444 = "ap4h"
}
/**
* This option specifies the stabilization mode to use when recording a video.
* @platform ios
*/
export declare enum VideoStabilization {
off = "off",
standard = "standard",
cinematic = "cinematic",
auto = "auto"
}
export declare enum VideoQuality {
'2160p' = "2160p",
'1080p' = "1080p",
'720p' = "720p",
'480p' = "480p",
'4:3' = "4:3"
}
export declare enum CameraOrientation {
portrait = 1,
portraitUpsideDown = 2,
landscapeLeft = 3,
landscapeRight = 4
}
/**
* @hidden We do not expose related web methods in docs.
* @platform web
*/
export type ImageSize = {
width: number;
height: number;
};
/**
* @hidden We do not expose related web methods in docs.
* @platform web
*/
export type WebCameraSettings = {
autoFocus?: string;
flashMode?: string;
whiteBalance?: string;
exposureCompensation?: number;
colorTemperature?: number;
iso?: number;
brightness?: number;
contrast?: number;
saturation?: number;
sharpness?: number;
focusDistance?: number;
zoom?: number;
};
export type CameraCapturedPicture = {
/**
* Captured image width.
*/
width: number;
/**
* Captured image height.
*/
height: number;
/**
* On web, the value of `uri` is the same as `base64` because file system URLs are not supported in the browser.
*/
uri: string;
/**
* A Base64 representation of the image.
*/
base64?: string;
/**
* On Android and iOS this object may include various fields based on the device and operating system.
* On web, it is a partial representation of the [`MediaTrackSettings`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings) dictionary.
*/
exif?: Partial<MediaTrackSettings> | any;
};
export type CameraPictureOptions = {
/**
* Specify the compression quality from `0` to `1`. `0` means compress for small size, and `1` means compress for maximum quality.
*/
quality?: number;
/**
* Whether to also include the image data in Base64 format.
*/
base64?: boolean;
/**
* Whether to also include the EXIF data for the image.
*/
exif?: boolean;
/**
* Additional EXIF data to be included for the image. Only useful when `exif` option is set to `true`.
* @platform android
* @platform ios
*/
additionalExif?: Record<string, any>;
/**
* A callback invoked when picture is saved. If set, the promise of this method will resolve immediately with no data after picture is captured.
* The data that it should contain will be passed to this callback. If displaying or processing a captured photo right after taking it
* is not your case, this callback lets you skip waiting for it to be saved.
* @param picture
*/
onPictureSaved?: (picture: CameraCapturedPicture) => void;
/**
* If set to `true`, camera skips orientation adjustment and returns an image straight from the device's camera.
* If enabled, `quality` option is discarded (processing pipeline is skipped as a whole).
* Although enabling this option reduces image delivery time significantly, it may cause the image to appear in a wrong orientation
* in the `Image` component (at the time of writing, it does not respect EXIF orientation of the images).
* > **Note**: Enabling `skipProcessing` would cause orientation uncertainty. `Image` component does not respect EXIF
* > stored orientation information, that means obtained image would be displayed wrongly (rotated by 90°, 180° or 270°).
* > Different devices provide different orientations. For example some Sony Xperia or Samsung devices don't provide
* > correctly oriented images by default. To always obtain correctly oriented image disable `skipProcessing` option.
*/
skipProcessing?: boolean;
/**
* @platform web
*/
scale?: number;
/**
* @platform web
*/
imageType?: ImageType;
/**
* @platform web
*/
isImageMirror?: boolean;
/**
* @hidden
*/
id?: number;
/**
* @hidden
*/
fastMode?: boolean;
/**
* @hidden
*/
maxDownsampling?: number;
};
export type CameraRecordingOptions = {
/**
* Maximum video duration in seconds.
*/
maxDuration?: number;
/**
* Maximum video file size in bytes.
*/
maxFileSize?: number;
/**
* Specify the quality of recorded video. Use one of [`VideoQuality.<value>`](#videoquality).
* Possible values: for 16:9 resolution `2160p`, `1080p`, `720p`, `480p` : `Android only` and for 4:3 `4:3` (the size is 640x480).
* If the chosen quality is not available for a device, the highest available is chosen.
*/
quality?: number | string;
/**
* If present, video will be recorded with no sound.
*/
mute?: boolean;
/**
* If `true`, the recorded video will be flipped along the vertical axis. iOS flips videos recorded with the front camera by default,
* but you can reverse that back by setting this to `true`. On Android, this is handled in the user's device settings.
* @platform ios
*/
mirror?: boolean;
/**
* Only works if `useCamera2Api` is set to `true`. This option specifies a desired video bitrate. For example, `5*1000*1000` would be 5Mbps.
* @platform android
*/
videoBitrate?: number;
/**
* This option specifies what codec to use when recording the video. See [`VideoCodec`](#videocodec) for the possible values.
* @platform ios
*/
codec?: VideoCodec;
};
/**
* @hidden
*/
export type PictureSavedListener = (event: {
nativeEvent: {
data: CameraCapturedPicture;
id: number;
};
}) => void;
/**
* @hidden
*/
export type CameraReadyListener = () => void;
/**
* @hidden
*/
export type ResponsiveOrientationChangedListener = (event: {
nativeEvent: ResponsiveOrientationChanged;
}) => void;
export type ResponsiveOrientationChanged = {
orientation: CameraOrientation;
};
/**
* @hidden
*/
export type MountErrorListener = (event: {
nativeEvent: CameraMountError;
}) => void;
export type CameraMountError = {
message: string;
};
export type Point = {
x: number;
y: number;
};
export type BarCodeSize = {
/**
* The height value.
*/
height: number;
/**
* The width value.
*/
width: number;
};
/**
* These coordinates are represented in the coordinate space of the camera source (e.g. when you
* are using the camera view, these values are adjusted to the dimensions of the view).
*/
export type BarCodePoint = Point;
export type BarCodeBounds = {
/**
* The origin point of the bounding box.
*/
origin: BarCodePoint;
/**
* The size of the bounding box.
*/
size: BarCodeSize;
};
export type BarCodeScanningResult = {
/**
* The barcode type.
*/
type: string;
/**
* The parsed information encoded in the bar code.
*/
data: string;
/**
* The raw information encoded in the bar code.
* May be different from `data` depending on the barcode type.
* @platform android
* @hidden
*/
raw?: string;
/**
* Corner points of the bounding box.
* `cornerPoints` is not always available and may be empty. On iOS, for `code39` and `pdf417`
* you don't get this value.
*/
cornerPoints: BarCodePoint[];
/**
* The [BarCodeBounds](#barcodebounds) object.
* `bounds` in some case will be representing an empty rectangle.
* Moreover, `bounds` doesn't have to bound the whole barcode.
* For some types, they will represent the area used by the scanner.
*/
bounds: BarCodeBounds;
};
export type FaceDetectionResult = {
/**
* Array of objects representing results of face detection.
* See [`FaceFeature`](facedetector/#facefeature) in FaceDetector documentation for more details.
*/
faces: object[];
};
/**
* @hidden
*/
export type ConstantsType = {
Type: CameraType;
FlashMode: FlashMode;
AutoFocus: AutoFocus;
WhiteBalance: WhiteBalance;
VideoQuality: VideoQuality;
VideoStabilization: VideoStabilization;
VideoCodec: VideoCodec;
};
export type CameraProps = ViewProps & {
/**
* Camera facing. Use one of `CameraType`. When `CameraType.front`, use the front-facing camera.
* When `CameraType.back`, use the back-facing camera.
* @default CameraType.back
*/
type?: number | CameraType;
/**
* Camera flash mode. Use one of [`FlashMode.<value>`](#flashmode-1). When `FlashMode.on`, the flash on your device will
* turn on when taking a picture, when `FlashMode.off`, it won't. Setting to `FlashMode.auto` will fire flash if required,
* `FlashMode.torch` turns on flash during the preview.
* @default FlashMode.off
*/
flashMode?: number | FlashMode;
/**
* Camera white balance. Use one of [`WhiteBalance.<value>`](#whitebalance). If a device does not support any of these values previous one is used.
* @default WhiteBalance.auto
*/
whiteBalance?: number | WhiteBalance;
/**
* State of camera auto focus. Use one of [`AutoFocus.<value>`](#autofocus-1). When `AutoFocus.on`,
* auto focus will be enabled, when `AutoFocus.off`, it won't and focus will lock as it was in the moment of change,
* but it can be adjusted on some devices via `focusDepth` prop.
* @default AutoFocus.on
*/
autoFocus?: boolean | number | AutoFocus;
/**
* A value between `0` and `1` being a percentage of device's max zoom. `0` - not zoomed, `1` - maximum zoom.
* @default 0
*/
zoom?: number;
/**
* A string representing aspect ratio of the preview, eg. `4:3`, `16:9`, `1:1`. To check if a ratio is supported
* by the device use [`getSupportedRatiosAsync`](#getsupportedratiosasync).
* @default 4:3
* @platform android
*/
ratio?: string;
/**
* Distance to plane of the sharpest focus. A value between `0` and `1` where: `0` - infinity focus, `1` - focus as close as possible.
* For Android this is available only for some devices and when `useCamera2Api` is set to `true`.
* @default 0
*/
focusDepth?: number;
/**
* Callback invoked when camera preview has been set.
*/
onCameraReady?: () => void;
/**
* Whether to use Android's Camera2 API. See `Note` at the top of this page.
* @platform android
*/
useCamera2Api?: boolean;
/**
* A string representing the size of pictures [`takePictureAsync`](#takepictureasyncoptions) will take.
* Available sizes can be fetched with [`getAvailablePictureSizesAsync`](#getavailablepicturesizesasyncratio).
*/
pictureSize?: string;
/**
* The video stabilization mode used for a video recording. Use one of [`VideoStabilization.<value>`](#videostabilization).
* You can read more about each stabilization type in [Apple Documentation](https://developer.apple.com/documentation/avfoundation/avcapturevideostabilizationmode).
* @platform ios
*/
videoStabilizationMode?: VideoStabilization;
/**
* Callback invoked when camera preview could not been started.
* @param event Error object that contains a `message`.
*/
onMountError?: (event: CameraMountError) => void;
/**
* Settings exposed by [`BarCodeScanner`](bar-code-scanner) module. Supported settings: **barCodeTypes**.
* @example
* ```tsx
* <Camera
* barCodeScannerSettings={{
* barCodeTypes: [BarCodeScanner.Constants.BarCodeType.qr],
* }}
* />
* ```
*/
barCodeScannerSettings?: BarCodeSettings;
/**
* Callback that is invoked when a bar code has been successfully scanned. The callback is provided with
* an object of the [`BarCodeScanningResult`](#barcodescanningresult) shape, where the `type`
* refers to the bar code type that was scanned and the `data` is the information encoded in the bar code
* (in this case of QR codes, this is often a URL). See [`BarCodeScanner.Constants.BarCodeType`](bar-code-scanner#supported-formats)
* for supported values.
* @param scanningResult
*/
onBarCodeScanned?: (scanningResult: BarCodeScanningResult) => void;
/**
* A settings object passed directly to an underlying module providing face detection features.
* See [`DetectionOptions`](facedetector/#detectionoptions) in FaceDetector documentation for details.
*/
faceDetectorSettings?: object;
/**
* Callback invoked with results of face detection on the preview.
* See [`DetectionResult`](facedetector/#detectionresult) in FaceDetector documentation for more details.
* @param faces
*/
onFacesDetected?: (faces: FaceDetectionResult) => void;
/**
* A URL for an image to be shown while the camera is loading.
* @platform web
*/
poster?: string;
/**
* Whether to allow responsive orientation of the camera when the screen orientation is locked (i.e. when set to `true`
* landscape photos will be taken if the device is turned that way, even if the app or device orientation is locked to portrait)
* @platform ios
*/
responsiveOrientationWhenOrientationLocked?: boolean;
/**
* Callback invoked when responsive orientation changes. Only applicable if `responsiveOrientationWhenOrientationLocked` is `true`
* @param event result object that contains updated orientation of camera
* @platform ios
*/
onResponsiveOrientationChanged?: (event: ResponsiveOrientationChanged) => void;
};
/**
* @hidden
*/
export type CameraNativeProps = {
pointerEvents?: any;
style?: any;
ref?: Function;
onCameraReady?: CameraReadyListener;
onMountError?: MountErrorListener;
onBarCodeScanned?: (event: {
nativeEvent: BarCodeScanningResult;
}) => void;
onFacesDetected?: (event: {
nativeEvent: FaceDetectionResult;
}) => void;
onFaceDetectionError?: (event: {
nativeEvent: Error;
}) => void;
onPictureSaved?: PictureSavedListener;
onResponsiveOrientationChanged?: ResponsiveOrientationChangedListener;
type?: number | string;
flashMode?: number | string;
autoFocus?: string | boolean | number;
focusDepth?: number;
zoom?: number;
whiteBalance?: number | string;
pictureSize?: string;
barCodeScannerSettings?: BarCodeSettings;
faceDetectorSettings?: object;
barCodeScannerEnabled?: boolean;
faceDetectorEnabled?: boolean;
ratio?: string;
useCamera2Api?: boolean;
poster?: string;
responsiveOrientationWhenOrientationLocked?: boolean;
};
export type BarCodeSettings = {
barCodeTypes: string[];
interval?: number;
};
export { PermissionResponse, PermissionStatus, PermissionExpiration, PermissionHookOptions };
//# sourceMappingURL=Camera.types.d.ts.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,109 @@
import { PermissionStatus, } from 'expo-modules-core';
export var CameraType;
(function (CameraType) {
CameraType["front"] = "front";
CameraType["back"] = "back";
})(CameraType || (CameraType = {}));
export var FlashMode;
(function (FlashMode) {
FlashMode["on"] = "on";
FlashMode["off"] = "off";
FlashMode["auto"] = "auto";
FlashMode["torch"] = "torch";
})(FlashMode || (FlashMode = {}));
export var AutoFocus;
(function (AutoFocus) {
AutoFocus["on"] = "on";
AutoFocus["off"] = "off";
/**
* @platform web
*/
AutoFocus["auto"] = "auto";
/**
* @platform web
*/
AutoFocus["singleShot"] = "singleShot";
})(AutoFocus || (AutoFocus = {}));
export var WhiteBalance;
(function (WhiteBalance) {
WhiteBalance["auto"] = "auto";
/**
* @platform android
* @platform ios
*/
WhiteBalance["sunny"] = "sunny";
/**
* @platform android
* @platform ios
*/
WhiteBalance["cloudy"] = "cloudy";
/**
* @platform android
* @platform ios
*/
WhiteBalance["shadow"] = "shadow";
/**
* @platform android
* @platform ios
*/
WhiteBalance["incandescent"] = "incandescent";
/**
* @platform android
* @platform ios
*/
WhiteBalance["fluorescent"] = "fluorescent";
/**
* @platform web
*/
WhiteBalance["continuous"] = "continuous";
/**
* @platform web
*/
WhiteBalance["manual"] = "manual";
})(WhiteBalance || (WhiteBalance = {}));
export var ImageType;
(function (ImageType) {
ImageType["png"] = "png";
ImageType["jpg"] = "jpg";
})(ImageType || (ImageType = {}));
/**
* This option specifies what codec to use when recording a video.
* @platform ios
*/
export var VideoCodec;
(function (VideoCodec) {
VideoCodec["H264"] = "avc1";
VideoCodec["HEVC"] = "hvc1";
VideoCodec["JPEG"] = "jpeg";
VideoCodec["AppleProRes422"] = "apcn";
VideoCodec["AppleProRes4444"] = "ap4h";
})(VideoCodec || (VideoCodec = {}));
/**
* This option specifies the stabilization mode to use when recording a video.
* @platform ios
*/
export var VideoStabilization;
(function (VideoStabilization) {
VideoStabilization["off"] = "off";
VideoStabilization["standard"] = "standard";
VideoStabilization["cinematic"] = "cinematic";
VideoStabilization["auto"] = "auto";
})(VideoStabilization || (VideoStabilization = {}));
// @docsMissing
export var VideoQuality;
(function (VideoQuality) {
VideoQuality["2160p"] = "2160p";
VideoQuality["1080p"] = "1080p";
VideoQuality["720p"] = "720p";
VideoQuality["480p"] = "480p";
VideoQuality["4:3"] = "4:3";
})(VideoQuality || (VideoQuality = {}));
export var CameraOrientation;
(function (CameraOrientation) {
CameraOrientation[CameraOrientation["portrait"] = 1] = "portrait";
CameraOrientation[CameraOrientation["portraitUpsideDown"] = 2] = "portraitUpsideDown";
CameraOrientation[CameraOrientation["landscapeLeft"] = 3] = "landscapeLeft";
CameraOrientation[CameraOrientation["landscapeRight"] = 4] = "landscapeRight";
})(CameraOrientation || (CameraOrientation = {}));
export { PermissionStatus };
//# sourceMappingURL=Camera.types.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
import * as React from 'react';
import { CameraNativeProps } from './Camera.types';
declare const ExponentCamera: React.ComponentType<CameraNativeProps>;
export default ExponentCamera;
//# sourceMappingURL=ExpoCamera.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCamera.d.ts","sourceRoot":"","sources":["../../src/legacy/ExpoCamera.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,aAAa,CAAC,iBAAiB,CACb,CAAC;AAE/C,eAAe,cAAc,CAAC"}

View File

@@ -0,0 +1,4 @@
import { requireNativeViewManager } from 'expo-modules-core';
const ExponentCamera = requireNativeViewManager('ExpoCameraLegacy');
export default ExponentCamera;
//# sourceMappingURL=ExpoCamera.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCamera.js","sourceRoot":"","sources":["../../src/legacy/ExpoCamera.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAK7D,MAAM,cAAc,GAClB,wBAAwB,CAAC,kBAAkB,CAAC,CAAC;AAE/C,eAAe,cAAc,CAAC","sourcesContent":["import { requireNativeViewManager } from 'expo-modules-core';\nimport * as React from 'react';\n\nimport { CameraNativeProps } from './Camera.types';\n\nconst ExponentCamera: React.ComponentType<CameraNativeProps> =\n requireNativeViewManager('ExpoCameraLegacy');\n\nexport default ExponentCamera;\n"]}

View File

@@ -0,0 +1,13 @@
import * as React from 'react';
import { CameraCapturedPicture, CameraNativeProps, CameraPictureOptions } from './Camera.types';
export interface ExponentCameraRef {
getAvailablePictureSizes: (ratio: string) => Promise<string[]>;
takePicture: (options: CameraPictureOptions) => Promise<CameraCapturedPicture>;
resumePreview: () => Promise<void>;
pausePreview: () => Promise<void>;
}
declare const ExponentCamera: React.ForwardRefExoticComponent<Pick<CameraNativeProps & {
children?: React.ReactNode;
}, "type" | "flashMode" | "children" | "pointerEvents" | "style" | "zoom" | "pictureSize" | "poster" | "responsiveOrientationWhenOrientationLocked" | "ratio" | "onCameraReady" | "onMountError" | "onResponsiveOrientationChanged" | "onPictureSaved" | "autoFocus" | "whiteBalance" | "onBarCodeScanned" | "onFacesDetected" | "onFaceDetectionError" | "focusDepth" | "barCodeScannerSettings" | "faceDetectorSettings" | "barCodeScannerEnabled" | "faceDetectorEnabled" | "useCamera2Api"> & React.RefAttributes<ExponentCameraRef>>;
export default ExponentCamera;
//# sourceMappingURL=ExpoCamera.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCamera.web.d.ts","sourceRoot":"","sources":["../../src/legacy/ExpoCamera.web.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EAErB,MAAM,gBAAgB,CAAC;AAOxB,MAAM,WAAW,iBAAiB;IAChC,wBAAwB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/D,WAAW,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC/E,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AAED,QAAA,MAAM,cAAc;eAE0D,MAAM,SAAS;ygBA+G5F,CAAC;AAEF,eAAe,cAAc,CAAC"}

View File

@@ -0,0 +1,108 @@
import { CodedError } from 'expo-modules-core';
import * as React from 'react';
import { StyleSheet, View } from 'react-native';
import createElement from 'react-native-web/dist/exports/createElement';
import { CameraType, } from './Camera.types';
import CameraManager from './ExpoCameraManager.web';
import { capture } from '../web/WebCameraUtils';
import { PictureSizes } from '../web/WebConstants';
import { useWebCameraStream } from '../web/useWebCameraStream';
import { useWebQRScanner } from '../web/useWebQRScanner';
const ExponentCamera = React.forwardRef(({ type, pictureSize, poster, ...props }, ref) => {
const video = React.useRef(null);
const native = useWebCameraStream(video, type, props, {
onCameraReady() {
if (props.onCameraReady) {
props.onCameraReady();
}
},
onMountError: props.onMountError,
});
const isQRScannerEnabled = React.useMemo(() => {
return !!(props.barCodeScannerSettings?.barCodeTypes?.includes('qr') && !!props.onBarCodeScanned);
}, [props.barCodeScannerSettings?.barCodeTypes, props.onBarCodeScanned]);
useWebQRScanner(video, {
interval: props.barCodeScannerSettings?.interval,
isEnabled: isQRScannerEnabled,
captureOptions: { scale: 1, isImageMirror: native.type === CameraType.front },
onScanned(event) {
if (props.onBarCodeScanned) {
props.onBarCodeScanned(event);
}
},
// onError: props.onMountError,
});
// const [pause, setPaused]
React.useImperativeHandle(ref, () => ({
async getAvailablePictureSizes(ratio) {
return PictureSizes;
},
async takePicture(options) {
if (!video.current || video.current?.readyState !== video.current?.HAVE_ENOUGH_DATA) {
throw new CodedError('ERR_CAMERA_NOT_READY', 'HTMLVideoElement does not have enough camera data to construct an image yet.');
}
const settings = native.mediaTrackSettings;
if (!settings) {
throw new CodedError('ERR_CAMERA_NOT_READY', 'MediaStream is not ready yet.');
}
return capture(video.current, settings, {
...options,
// This will always be defined, the option gets added to a queue in the upper-level. We should replace the original so it isn't called twice.
onPictureSaved(picture) {
if (options.onPictureSaved) {
options.onPictureSaved(picture);
}
if (props.onPictureSaved) {
props.onPictureSaved({ nativeEvent: { data: picture, id: -1 } });
}
},
});
},
async resumePreview() {
if (video.current) {
video.current.play();
}
},
async pausePreview() {
if (video.current) {
video.current.pause();
}
},
}), [native.mediaTrackSettings, props.onPictureSaved]);
// TODO(Bacon): Create a universal prop, on native the microphone is only used when recording videos.
// Because we don't support recording video in the browser we don't need the user to give microphone permissions.
const isMuted = true;
const style = React.useMemo(() => {
const isFrontFacingCamera = native.type === CameraManager.Type.front;
return [
StyleSheet.absoluteFill,
styles.video,
{
pointerEvents: props.pointerEvents,
// Flip the camera
transform: isFrontFacingCamera ? [{ scaleX: -1 }] : undefined,
},
];
}, [props.pointerEvents, native.type]);
return (<View style={[styles.videoWrapper, props.style]}>
<Video autoPlay playsInline muted={isMuted} poster={poster}
// webkitPlaysinline
ref={video} style={style}/>
{props.children}
</View>);
});
export default ExponentCamera;
const Video = React.forwardRef((props, ref) => createElement('video', { ...props, ref }));
const styles = StyleSheet.create({
videoWrapper: {
flex: 1,
alignItems: 'stretch',
pointerEvents: 'box-none',
},
video: {
width: '100%',
height: '100%',
objectFit: 'cover',
},
});
//# sourceMappingURL=ExpoCamera.web.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
declare const CameraManager: Record<string, any>;
export default CameraManager;
//# sourceMappingURL=ExpoCameraManager.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCameraManager.d.ts","sourceRoot":"","sources":["../../src/legacy/ExpoCameraManager.ts"],"names":[],"mappings":"AAEA,QAAA,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAA2C,CAAC;AAEnF,eAAe,aAAa,CAAC"}

View File

@@ -0,0 +1,4 @@
import { requireNativeModule } from 'expo-modules-core';
const CameraManager = requireNativeModule('ExpoCameraLegacy');
export default CameraManager;
//# sourceMappingURL=ExpoCameraManager.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCameraManager.js","sourceRoot":"","sources":["../../src/legacy/ExpoCameraManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,aAAa,GAAwB,mBAAmB,CAAC,kBAAkB,CAAC,CAAC;AAEnF,eAAe,aAAa,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\n\nconst CameraManager: Record<string, any> = requireNativeModule('ExpoCameraLegacy');\n\nexport default CameraManager;\n"]}

View File

@@ -0,0 +1,41 @@
import { CameraCapturedPicture, CameraPictureOptions, PermissionResponse } from './Camera.types';
import { ExponentCameraRef } from './ExpoCamera.web';
declare const _default: {
readonly Type: {
back: string;
front: string;
};
readonly FlashMode: {
on: string;
off: string;
auto: string;
torch: string;
};
readonly AutoFocus: {
on: string;
off: string;
auto: string;
singleShot: string;
};
readonly WhiteBalance: {
auto: string;
continuous: string;
manual: string;
};
readonly VideoQuality: {};
readonly VideoStabilization: {};
isAvailableAsync(): Promise<boolean>;
takePicture(options: CameraPictureOptions, camera: ExponentCameraRef): Promise<CameraCapturedPicture>;
pausePreview(camera: ExponentCameraRef): Promise<void>;
resumePreview(camera: ExponentCameraRef): Promise<void>;
getAvailableCameraTypesAsync(): Promise<string[]>;
getAvailablePictureSizes(ratio: string, camera: ExponentCameraRef): Promise<string[]>;
getPermissionsAsync(): Promise<PermissionResponse>;
requestPermissionsAsync(): Promise<PermissionResponse>;
getCameraPermissionsAsync(): Promise<PermissionResponse>;
requestCameraPermissionsAsync(): Promise<PermissionResponse>;
getMicrophonePermissionsAsync(): Promise<PermissionResponse>;
requestMicrophonePermissionsAsync(): Promise<PermissionResponse>;
};
export default _default;
//# sourceMappingURL=ExpoCameraManager.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoCameraManager.web.d.ts","sourceRoot":"","sources":["../../src/legacy/ExpoCameraManager.web.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EAEpB,kBAAkB,EAEnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;wBA0JzB,QAAQ,OAAO,CAAC;yBAI/B,oBAAoB,UACrB,iBAAiB,GACxB,QAAQ,qBAAqB,CAAC;yBAGN,iBAAiB,GAAG,QAAQ,IAAI,CAAC;0BAGhC,iBAAiB,GAAG,QAAQ,IAAI,CAAC;oCAGvB,QAAQ,MAAM,EAAE,CAAC;oCAYjB,MAAM,UAAU,iBAAiB,GAAG,QAAQ,MAAM,EAAE,CAAC;2BAe9D,QAAQ,kBAAkB,CAAC;+BAGvB,QAAQ,kBAAkB,CAAC;iCAGzB,QAAQ,kBAAkB,CAAC;qCAGvB,QAAQ,kBAAkB,CAAC;qCAG3B,QAAQ,kBAAkB,CAAC;yCAGvB,QAAQ,kBAAkB,CAAC;;AA7FxE,wBA4GE"}

View File

@@ -0,0 +1,213 @@
import { UnavailabilityError } from 'expo-modules-core';
import { CameraType, PermissionStatus, } from './Camera.types';
import { canGetUserMedia, isBackCameraAvailableAsync, isFrontCameraAvailableAsync, } from '../web/WebUserMediaManager';
function getUserMedia(constraints) {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
return navigator.mediaDevices.getUserMedia(constraints);
}
// Some browsers partially implement mediaDevices. We can't just assign an object
// with getUserMedia as it would overwrite existing properties.
// Here, we will just add the getUserMedia property if it's missing.
// First get ahold of the legacy getUserMedia, if present
const getUserMedia =
// TODO: this method is deprecated, migrate to https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
function () {
const error = new Error('Permission unimplemented');
error.code = 0;
error.name = 'NotAllowedError';
throw error;
};
return new Promise((resolve, reject) => {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
function handleGetUserMediaError({ message }) {
// name: NotAllowedError
// code: 0
if (message === 'Permission dismissed') {
return {
status: PermissionStatus.UNDETERMINED,
expires: 'never',
canAskAgain: true,
granted: false,
};
}
else {
// TODO: Bacon: [OSX] The system could deny access to chrome.
// TODO: Bacon: add: { status: 'unimplemented' }
return {
status: PermissionStatus.DENIED,
expires: 'never',
canAskAgain: true,
granted: false,
};
}
}
async function handleRequestPermissionsAsync() {
try {
await getUserMedia({
video: true,
});
return {
status: PermissionStatus.GRANTED,
expires: 'never',
canAskAgain: true,
granted: true,
};
}
catch ({ message }) {
return handleGetUserMediaError({ message });
}
}
async function handlePermissionsQueryAsync(query) {
if (!navigator?.permissions?.query) {
throw new UnavailabilityError('expo-camera', 'navigator.permissions API is not available');
}
try {
const { state } = await navigator.permissions.query({ name: query });
switch (state) {
case 'prompt':
return {
status: PermissionStatus.UNDETERMINED,
expires: 'never',
canAskAgain: true,
granted: false,
};
case 'granted':
return {
status: PermissionStatus.GRANTED,
expires: 'never',
canAskAgain: true,
granted: true,
};
case 'denied':
return {
status: PermissionStatus.DENIED,
expires: 'never',
canAskAgain: true,
granted: false,
};
}
}
catch (e) {
// Firefox doesn't support querying for the camera permission, so return undetermined status
if (e instanceof TypeError) {
return {
status: PermissionStatus.UNDETERMINED,
expires: 'never',
canAskAgain: true,
granted: false,
};
}
throw e;
}
}
export default {
get Type() {
return {
back: 'back',
front: 'front',
};
},
get FlashMode() {
return {
on: 'on',
off: 'off',
auto: 'auto',
torch: 'torch',
};
},
get AutoFocus() {
return {
on: 'on',
off: 'off',
auto: 'auto',
singleShot: 'singleShot',
};
},
get WhiteBalance() {
return {
auto: 'auto',
continuous: 'continuous',
manual: 'manual',
};
},
get VideoQuality() {
return {};
},
get VideoStabilization() {
return {};
},
async isAvailableAsync() {
return canGetUserMedia();
},
async takePicture(options, camera) {
return await camera.takePicture(options);
},
async pausePreview(camera) {
await camera.pausePreview();
},
async resumePreview(camera) {
return await camera.resumePreview();
},
async getAvailableCameraTypesAsync() {
if (!canGetUserMedia() || !navigator.mediaDevices.enumerateDevices)
return [];
const devices = await navigator.mediaDevices.enumerateDevices();
const types = await Promise.all([
(await isFrontCameraAvailableAsync(devices)) && CameraType.front,
(await isBackCameraAvailableAsync()) && CameraType.back,
]);
return types.filter(Boolean);
},
async getAvailablePictureSizes(ratio, camera) {
return await camera.getAvailablePictureSizes(ratio);
},
/* async getSupportedRatios(camera: ExponentCameraRef): Promise<string[]> {
// TODO: Support on web
},
async record(
options?: CameraRecordingOptions,
camera: ExponentCameraRef
): Promise<{ uri: string }> {
// TODO: Support on web
},
async stopRecording(camera: ExponentCameraRef): Promise<void> {
// TODO: Support on web
}, */
async getPermissionsAsync() {
return handlePermissionsQueryAsync('camera');
},
async requestPermissionsAsync() {
return handleRequestPermissionsAsync();
},
async getCameraPermissionsAsync() {
return handlePermissionsQueryAsync('camera');
},
async requestCameraPermissionsAsync() {
return handleRequestPermissionsAsync();
},
async getMicrophonePermissionsAsync() {
return handlePermissionsQueryAsync('microphone');
},
async requestMicrophonePermissionsAsync() {
try {
await getUserMedia({
audio: true,
});
return {
status: PermissionStatus.GRANTED,
expires: 'never',
canAskAgain: true,
granted: true,
};
}
catch ({ message }) {
return handleGetUserMediaError({ message });
}
},
};
//# sourceMappingURL=ExpoCameraManager.web.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
export { default as Camera } from './Camera';
export { Constants, getPermissionsAsync, requestPermissionsAsync, getCameraPermissionsAsync, requestCameraPermissionsAsync, getMicrophonePermissionsAsync, requestMicrophonePermissionsAsync, } from './Camera';
export * from './Camera.types';
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/legacy/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,uBAAuB,EACvB,yBAAyB,EACzB,6BAA6B,EAC7B,6BAA6B,EAC7B,iCAAiC,GAClC,MAAM,UAAU,CAAC;AAElB,cAAc,gBAAgB,CAAC"}

View File

@@ -0,0 +1,4 @@
export { default as Camera } from './Camera';
export { Constants, getPermissionsAsync, requestPermissionsAsync, getCameraPermissionsAsync, requestCameraPermissionsAsync, getMicrophonePermissionsAsync, requestMicrophonePermissionsAsync, } from './Camera';
export * from './Camera.types';
//# sourceMappingURL=index.js.map

Some files were not shown because too many files have changed in this diff Show More