diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 0e5385db44e0..62b5f1f9bd4c 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.4 + +* Add web support by endorsing `package:camera_web`. + ## 0.9.3+1 * Remove iOS 9 availability check around ultra high capture sessions. diff --git a/packages/camera/camera/README.md b/packages/camera/camera/README.md index aa34273fb92c..24566e76bbfc 100644 --- a/packages/camera/camera/README.md +++ b/packages/camera/camera/README.md @@ -2,7 +2,7 @@ [![pub package](https://img.shields.io/pub/v/camera.svg)](https://pub.dev/packages/camera) -A Flutter plugin for iOS and Android allowing access to the device cameras. +A Flutter plugin for iOS, Android and Web allowing access to the device cameras. *Note*: This plugin is still under development, and some APIs might not be available yet. We are working on a refactor which can be followed here: [issue](https://github.com/flutter/flutter/issues/31225) @@ -47,6 +47,11 @@ minSdkVersion 21 It's important to note that the `MediaRecorder` class is not working properly on emulators, as stated in the documentation: https://developer.android.com/reference/android/media/MediaRecorder. Specifically, when recording a video with sound enabled and trying to play it back, the duration won't be correct and you will only see the first frame. +### Web integration + +For web integration details, see the +[`camera_web` package](https://pub.dev/packages/camera_web). + ### Handling Lifecycle states As of version [0.5.0](https://github.com/flutter/plugins/blob/master/packages/camera/CHANGELOG.md#050) of the camera plugin, lifecycle changes are no longer handled by the plugin. This means developers are now responsible to control camera resources when the lifecycle state is updated. Failure to do so might lead to unexpected behavior (for example as described in issue [#39109](https://github.com/flutter/flutter/issues/39109)). Handling lifecycle changes can be done by overriding the `didChangeAppLifecycleState` method like so: diff --git a/packages/camera/camera/example/lib/main.dart b/packages/camera/camera/example/lib/main.dart index c0e90eefa3ab..a3a5d1d46391 100644 --- a/packages/camera/camera/example/lib/main.dart +++ b/packages/camera/camera/example/lib/main.dart @@ -8,6 +8,7 @@ import 'dart:async'; import 'dart:io'; import 'package:camera/camera.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; @@ -231,7 +232,14 @@ class _CameraExampleHomeState extends State ? Container() : SizedBox( child: (localVideoController == null) - ? Image.file(File(imageFile!.path)) + ? ( + // The captured image on the web contains a network-accessible URL + // pointing to a location within the browser. It may be displayed + // either with Image.network or Image.memory after loading the image + // bytes to memory. + kIsWeb + ? Image.network(imageFile!.path) + : Image.file(File(imageFile!.path))) : Container( child: Center( child: AspectRatio( @@ -267,17 +275,24 @@ class _CameraExampleHomeState extends State color: Colors.blue, onPressed: controller != null ? onFlashModeButtonPressed : null, ), - IconButton( - icon: Icon(Icons.exposure), - color: Colors.blue, - onPressed: - controller != null ? onExposureModeButtonPressed : null, - ), - IconButton( - icon: Icon(Icons.filter_center_focus), - color: Colors.blue, - onPressed: controller != null ? onFocusModeButtonPressed : null, - ), + // The exposure and focus mode are currently not supported on the web. + ...(!kIsWeb + ? [ + IconButton( + icon: Icon(Icons.exposure), + color: Colors.blue, + onPressed: controller != null + ? onExposureModeButtonPressed + : null, + ), + IconButton( + icon: Icon(Icons.filter_center_focus), + color: Colors.blue, + onPressed: + controller != null ? onFocusModeButtonPressed : null, + ) + ] + : []), IconButton( icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute), color: Colors.blue, @@ -616,7 +631,7 @@ class _CameraExampleHomeState extends State final CameraController cameraController = CameraController( cameraDescription, - ResolutionPreset.medium, + kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, enableAudio: enableAudio, imageFormatGroup: ImageFormatGroup.jpeg, ); @@ -635,12 +650,17 @@ class _CameraExampleHomeState extends State try { await cameraController.initialize(); await Future.wait([ - cameraController - .getMinExposureOffset() - .then((value) => _minAvailableExposureOffset = value), - cameraController - .getMaxExposureOffset() - .then((value) => _maxAvailableExposureOffset = value), + // The exposure mode is currently not supported on the web. + ...(!kIsWeb + ? [ + cameraController + .getMinExposureOffset() + .then((value) => _minAvailableExposureOffset = value), + cameraController + .getMaxExposureOffset() + .then((value) => _maxAvailableExposureOffset = value) + ] + : []), cameraController .getMaxZoomLevel() .then((value) => _maxAvailableZoom = value), @@ -708,16 +728,20 @@ class _CameraExampleHomeState extends State } void onCaptureOrientationLockButtonPressed() async { - if (controller != null) { - final CameraController cameraController = controller!; - if (cameraController.value.isCaptureOrientationLocked) { - await cameraController.unlockCaptureOrientation(); - showInSnackBar('Capture orientation unlocked'); - } else { - await cameraController.lockCaptureOrientation(); - showInSnackBar( - 'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}'); + try { + if (controller != null) { + final CameraController cameraController = controller!; + if (cameraController.value.isCaptureOrientationLocked) { + await cameraController.unlockCaptureOrientation(); + showInSnackBar('Capture orientation unlocked'); + } else { + await cameraController.lockCaptureOrientation(); + showInSnackBar( + 'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}'); + } } + } on CameraException catch (e) { + _showCameraException(e); } } @@ -916,8 +940,10 @@ class _CameraExampleHomeState extends State return; } - final VideoPlayerController vController = - VideoPlayerController.file(File(videoFile!.path)); + final VideoPlayerController vController = kIsWeb + ? VideoPlayerController.network(videoFile!.path) + : VideoPlayerController.file(File(videoFile!.path)); + videoPlayerListener = () { if (videoController != null && videoController!.value.size != null) { // Refreshing the state to update video player with the correct ratio. diff --git a/packages/camera/camera/example/web/favicon.png b/packages/camera/camera/example/web/favicon.png new file mode 100644 index 000000000000..8aaa46ac1ae2 Binary files /dev/null and b/packages/camera/camera/example/web/favicon.png differ diff --git a/packages/camera/camera/example/web/icons/Icon-192.png b/packages/camera/camera/example/web/icons/Icon-192.png new file mode 100644 index 000000000000..b749bfef0747 Binary files /dev/null and b/packages/camera/camera/example/web/icons/Icon-192.png differ diff --git a/packages/camera/camera/example/web/icons/Icon-512.png b/packages/camera/camera/example/web/icons/Icon-512.png new file mode 100644 index 000000000000..88cfd48dff11 Binary files /dev/null and b/packages/camera/camera/example/web/icons/Icon-512.png differ diff --git a/packages/camera/camera/example/web/index.html b/packages/camera/camera/example/web/index.html new file mode 100644 index 000000000000..2a3117d29362 --- /dev/null +++ b/packages/camera/camera/example/web/index.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + Camera Web Example + + + + + + + + + + \ No newline at end of file diff --git a/packages/camera/camera/example/web/manifest.json b/packages/camera/camera/example/web/manifest.json new file mode 100644 index 000000000000..5fe0e048afe6 --- /dev/null +++ b/packages/camera/camera/example/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "camera example", + "short_name": "camera", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "An example of the camera on the web.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/packages/camera/camera/lib/src/camera_preview.dart b/packages/camera/camera/lib/src/camera_preview.dart index 6a15896bfa47..5faa69f3cb9d 100644 --- a/packages/camera/camera/lib/src/camera_preview.dart +++ b/packages/camera/camera/lib/src/camera_preview.dart @@ -43,7 +43,7 @@ class CameraPreview extends StatelessWidget { } Widget _wrapInRotatedBox({required Widget child}) { - if (defaultTargetPlatform != TargetPlatform.android) { + if (kIsWeb || defaultTargetPlatform != TargetPlatform.android) { return child; } diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index 7efc7930cd54..b8894d58ac3a 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -1,10 +1,10 @@ name: camera description: A Flutter plugin for getting information about and controlling the - camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video, + camera on Android, iOS and Web. Supports previewing the camera feed, capturing images, capturing video, and streaming image buffers to dart. repository: https://github.com/flutter/plugins/tree/master/packages/camera/camera issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.3+1 +version: 0.9.4 environment: sdk: ">=2.14.0 <3.0.0" @@ -18,9 +18,12 @@ flutter: pluginClass: CameraPlugin ios: pluginClass: CameraPlugin + web: + default_package: camera_web dependencies: camera_platform_interface: ^2.1.0 + camera_web: ^0.2.1 flutter: sdk: flutter pedantic: ^1.10.0 diff --git a/packages/camera/camera/test/camera_preview_test.dart b/packages/camera/camera/test/camera_preview_test.dart index 14afddaea070..32718f4d5169 100644 --- a/packages/camera/camera/test/camera_preview_test.dart +++ b/packages/camera/camera/test/camera_preview_test.dart @@ -221,7 +221,7 @@ void main() { debugDefaultTargetPlatformOverride = null; }); - }); + }, skip: kIsWeb); testWidgets('when not on Android there should not be a rotated box', (WidgetTester tester) async {