Skip to content

Commit 42b9909

Browse files
authored
[camera] Add web support (flutter#4240)
* feat: add web to the example app * docs: update README and point users to camera_web for more web-specific info.
1 parent 1fe19f1 commit 42b9909

File tree

11 files changed

+135
-35
lines changed

11 files changed

+135
-35
lines changed

packages/camera/camera/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.9.4
2+
3+
* Add web support by endorsing `package:camera_web`.
4+
15
## 0.9.3+1
26

37
* Remove iOS 9 availability check around ultra high capture sessions.

packages/camera/camera/README.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![pub package](https://img.shields.io/pub/v/camera.svg)](https://pub.dev/packages/camera)
44

5-
A Flutter plugin for iOS and Android allowing access to the device cameras.
5+
A Flutter plugin for iOS, Android and Web allowing access to the device cameras.
66

77
*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)
88

@@ -47,6 +47,11 @@ minSdkVersion 21
4747

4848
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.
4949

50+
### Web integration
51+
52+
For web integration details, see the
53+
[`camera_web` package](https://pub.dev/packages/camera_web).
54+
5055
### Handling Lifecycle states
5156

5257
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:

packages/camera/camera/example/lib/main.dart

+56-30
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'dart:async';
88
import 'dart:io';
99

1010
import 'package:camera/camera.dart';
11+
import 'package:flutter/foundation.dart';
1112
import 'package:flutter/material.dart';
1213
import 'package:video_player/video_player.dart';
1314

@@ -231,7 +232,14 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
231232
? Container()
232233
: SizedBox(
233234
child: (localVideoController == null)
234-
? Image.file(File(imageFile!.path))
235+
? (
236+
// The captured image on the web contains a network-accessible URL
237+
// pointing to a location within the browser. It may be displayed
238+
// either with Image.network or Image.memory after loading the image
239+
// bytes to memory.
240+
kIsWeb
241+
? Image.network(imageFile!.path)
242+
: Image.file(File(imageFile!.path)))
235243
: Container(
236244
child: Center(
237245
child: AspectRatio(
@@ -267,17 +275,24 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
267275
color: Colors.blue,
268276
onPressed: controller != null ? onFlashModeButtonPressed : null,
269277
),
270-
IconButton(
271-
icon: Icon(Icons.exposure),
272-
color: Colors.blue,
273-
onPressed:
274-
controller != null ? onExposureModeButtonPressed : null,
275-
),
276-
IconButton(
277-
icon: Icon(Icons.filter_center_focus),
278-
color: Colors.blue,
279-
onPressed: controller != null ? onFocusModeButtonPressed : null,
280-
),
278+
// The exposure and focus mode are currently not supported on the web.
279+
...(!kIsWeb
280+
? [
281+
IconButton(
282+
icon: Icon(Icons.exposure),
283+
color: Colors.blue,
284+
onPressed: controller != null
285+
? onExposureModeButtonPressed
286+
: null,
287+
),
288+
IconButton(
289+
icon: Icon(Icons.filter_center_focus),
290+
color: Colors.blue,
291+
onPressed:
292+
controller != null ? onFocusModeButtonPressed : null,
293+
)
294+
]
295+
: []),
281296
IconButton(
282297
icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute),
283298
color: Colors.blue,
@@ -616,7 +631,7 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
616631

617632
final CameraController cameraController = CameraController(
618633
cameraDescription,
619-
ResolutionPreset.medium,
634+
kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium,
620635
enableAudio: enableAudio,
621636
imageFormatGroup: ImageFormatGroup.jpeg,
622637
);
@@ -635,12 +650,17 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
635650
try {
636651
await cameraController.initialize();
637652
await Future.wait([
638-
cameraController
639-
.getMinExposureOffset()
640-
.then((value) => _minAvailableExposureOffset = value),
641-
cameraController
642-
.getMaxExposureOffset()
643-
.then((value) => _maxAvailableExposureOffset = value),
653+
// The exposure mode is currently not supported on the web.
654+
...(!kIsWeb
655+
? [
656+
cameraController
657+
.getMinExposureOffset()
658+
.then((value) => _minAvailableExposureOffset = value),
659+
cameraController
660+
.getMaxExposureOffset()
661+
.then((value) => _maxAvailableExposureOffset = value)
662+
]
663+
: []),
644664
cameraController
645665
.getMaxZoomLevel()
646666
.then((value) => _maxAvailableZoom = value),
@@ -708,16 +728,20 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
708728
}
709729

710730
void onCaptureOrientationLockButtonPressed() async {
711-
if (controller != null) {
712-
final CameraController cameraController = controller!;
713-
if (cameraController.value.isCaptureOrientationLocked) {
714-
await cameraController.unlockCaptureOrientation();
715-
showInSnackBar('Capture orientation unlocked');
716-
} else {
717-
await cameraController.lockCaptureOrientation();
718-
showInSnackBar(
719-
'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}');
731+
try {
732+
if (controller != null) {
733+
final CameraController cameraController = controller!;
734+
if (cameraController.value.isCaptureOrientationLocked) {
735+
await cameraController.unlockCaptureOrientation();
736+
showInSnackBar('Capture orientation unlocked');
737+
} else {
738+
await cameraController.lockCaptureOrientation();
739+
showInSnackBar(
740+
'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}');
741+
}
720742
}
743+
} on CameraException catch (e) {
744+
_showCameraException(e);
721745
}
722746
}
723747

@@ -916,8 +940,10 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
916940
return;
917941
}
918942

919-
final VideoPlayerController vController =
920-
VideoPlayerController.file(File(videoFile!.path));
943+
final VideoPlayerController vController = kIsWeb
944+
? VideoPlayerController.network(videoFile!.path)
945+
: VideoPlayerController.file(File(videoFile!.path));
946+
921947
videoPlayerListener = () {
922948
if (videoController != null && videoController!.value.size != null) {
923949
// Refreshing the state to update video player with the correct ratio.
917 Bytes
Loading
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!DOCTYPE html>
2+
<!-- Copyright 2013 The Flutter Authors. All rights reserved.
3+
Use of this source code is governed by a BSD-style license that can be
4+
found in the LICENSE file. -->
5+
<html>
6+
7+
<head>
8+
<meta charset="UTF-8">
9+
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
10+
<meta name="description" content="An example of the camera on the web.">
11+
12+
<!-- iOS meta tags & icons -->
13+
<meta name="apple-mobile-web-app-capable" content="yes">
14+
<meta name="apple-mobile-web-app-status-bar-style" content="black">
15+
<meta name="apple-mobile-web-app-title" content="example">
16+
<link rel="apple-touch-icon" href="icons/Icon-192.png">
17+
18+
<!-- Favicon -->
19+
<link rel="shortcut icon" type="image/png" href="favicon.png" />
20+
21+
<title>Camera Web Example</title>
22+
<link rel="manifest" href="manifest.json">
23+
</head>
24+
25+
<body>
26+
<!-- This script installs service_worker.js to provide PWA functionality to
27+
application. For more information, see:
28+
https://developers.google.com/web/fundamentals/primers/service-workers -->
29+
<script>
30+
if ('serviceWorker' in navigator) {
31+
window.addEventListener('load', function () {
32+
navigator.serviceWorker.register('flutter_service_worker.js');
33+
});
34+
}
35+
</script>
36+
<script src="main.dart.js" type="application/javascript"></script>
37+
</body>
38+
39+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "camera example",
3+
"short_name": "camera",
4+
"start_url": ".",
5+
"display": "standalone",
6+
"background_color": "#0175C2",
7+
"theme_color": "#0175C2",
8+
"description": "An example of the camera on the web.",
9+
"orientation": "portrait-primary",
10+
"prefer_related_applications": false,
11+
"icons": [
12+
{
13+
"src": "icons/Icon-192.png",
14+
"sizes": "192x192",
15+
"type": "image/png"
16+
},
17+
{
18+
"src": "icons/Icon-512.png",
19+
"sizes": "512x512",
20+
"type": "image/png"
21+
}
22+
]
23+
}

packages/camera/camera/lib/src/camera_preview.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class CameraPreview extends StatelessWidget {
4343
}
4444

4545
Widget _wrapInRotatedBox({required Widget child}) {
46-
if (defaultTargetPlatform != TargetPlatform.android) {
46+
if (kIsWeb || defaultTargetPlatform != TargetPlatform.android) {
4747
return child;
4848
}
4949

packages/camera/camera/pubspec.yaml

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
name: camera
22
description: A Flutter plugin for getting information about and controlling the
3-
camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video,
3+
camera on Android, iOS and Web. Supports previewing the camera feed, capturing images, capturing video,
44
and streaming image buffers to dart.
55
repository: https://github.com/flutter/plugins/tree/master/packages/camera/camera
66
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
7-
version: 0.9.3+1
7+
version: 0.9.4
88

99
environment:
1010
sdk: ">=2.14.0 <3.0.0"
@@ -18,9 +18,12 @@ flutter:
1818
pluginClass: CameraPlugin
1919
ios:
2020
pluginClass: CameraPlugin
21+
web:
22+
default_package: camera_web
2123

2224
dependencies:
2325
camera_platform_interface: ^2.1.0
26+
camera_web: ^0.2.1
2427
flutter:
2528
sdk: flutter
2629
pedantic: ^1.10.0

packages/camera/camera/test/camera_preview_test.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ void main() {
221221

222222
debugDefaultTargetPlatformOverride = null;
223223
});
224-
});
224+
}, skip: kIsWeb);
225225

226226
testWidgets('when not on Android there should not be a rotated box',
227227
(WidgetTester tester) async {

0 commit comments

Comments
 (0)