Skip to content

Commit 622f2db

Browse files
[camera_web] Support getting cameras while a camera is being used.
1 parent 83c272c commit 622f2db

File tree

2 files changed

+91
-75
lines changed

2 files changed

+91
-75
lines changed

packages/camera/camera_web/example/integration_test/camera_web_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ void main() {
6464
when(
6565
() => cameraService.getMediaStreamForOptions(
6666
any(),
67-
cameraId: any(named: 'cameraId'),
67+
cameraId: any(),
6868
),
6969
).thenAnswer(
7070
(_) async => videoElement.captureStream(),

packages/camera/camera_web/lib/src/camera_web.dart

Lines changed: 90 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class CameraPlugin extends CameraPlatform {
4343
@visibleForTesting
4444
final Map<int, Camera> cameras = <int, Camera>{};
4545
int _textureCounter = 1;
46+
List<CameraDescription>? _availableCameras;
4647

4748
/// Metadata associated with each camera description.
4849
/// Populated in [availableCameras].
@@ -81,48 +82,60 @@ class CameraPlugin extends CameraPlatform {
8182
@visibleForTesting
8283
html.Window? window = html.window;
8384

84-
@override
85-
Future<List<CameraDescription>> availableCameras() async {
86-
try {
87-
final html.MediaDevices? mediaDevices = window?.navigator.mediaDevices;
88-
final List<CameraDescription> cameras = <CameraDescription>[];
85+
Future<List<html.MediaDeviceInfo>> _videoInputDevices() async {
86+
final html.MediaDevices? mediaDevices = window?.navigator.mediaDevices;
8987

90-
// Throw a not supported exception if the current browser window
91-
// does not support any media devices.
92-
if (mediaDevices == null) {
93-
throw PlatformException(
94-
code: CameraErrorCode.notSupported.toString(),
95-
message: 'The camera is not supported on this device.',
96-
);
97-
}
88+
// Throw a not supported exception if the current browser window
89+
// does not support any media devices.
90+
if (mediaDevices == null) {
91+
throw PlatformException(
92+
code: CameraErrorCode.notSupported.toString(),
93+
message: 'The camera is not supported on this device.',
94+
);
95+
}
9896

99-
// Request video permissions only.
100-
final html.MediaStream cameraStream =
101-
await _cameraService.getMediaStreamForOptions(const CameraOptions());
97+
// Request available media devices.
98+
final List<dynamic> devices = await mediaDevices.enumerateDevices();
10299

103-
// Release the camera stream used to request video permissions.
104-
cameraStream
105-
.getVideoTracks()
106-
.forEach((html.MediaStreamTrack videoTrack) => videoTrack.stop());
100+
// Filter video input devices.
101+
final Iterable<html.MediaDeviceInfo> videoInputDevices = devices
102+
.whereType<html.MediaDeviceInfo>()
103+
.where((html.MediaDeviceInfo device) =>
104+
device.kind == MediaDeviceKind.videoInput)
107105

108-
// Request available media devices.
109-
final List<dynamic> devices = await mediaDevices.enumerateDevices();
106+
/// The device id property is currently not supported on Internet Explorer:
107+
/// https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/deviceId#browser_compatibility
108+
.where(
109+
(html.MediaDeviceInfo device) =>
110+
device.deviceId != null && device.deviceId!.isNotEmpty,
111+
);
110112

111-
// Filter video input devices.
112-
final Iterable<html.MediaDeviceInfo> videoInputDevices = devices
113-
.whereType<html.MediaDeviceInfo>()
114-
.where((html.MediaDeviceInfo device) =>
115-
device.kind == MediaDeviceKind.videoInput)
113+
return videoInputDevices.toList();
114+
}
116115

117-
/// The device id property is currently not supported on Internet Explorer:
118-
/// https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/deviceId#browser_compatibility
119-
.where(
120-
(html.MediaDeviceInfo device) =>
121-
device.deviceId != null && device.deviceId!.isNotEmpty,
122-
);
116+
@override
117+
Future<List<CameraDescription>> availableCameras() async {
118+
if (_availableCameras != null) {
119+
return _availableCameras!;
120+
}
121+
try {
122+
final List<CameraDescription> cameraDescriptions = <CameraDescription>[];
123+
124+
final bool hasPermission = cameras.isNotEmpty;
125+
if (!hasPermission) {
126+
// Request video permissions only.
127+
final html.MediaStream cameraStream = await _cameraService
128+
.getMediaStreamForOptions(const CameraOptions());
129+
130+
// Release the camera stream used to request video permissions.
131+
cameraStream
132+
.getVideoTracks()
133+
.forEach((html.MediaStreamTrack videoTrack) => videoTrack.stop());
134+
}
123135

124136
// Map video input devices to camera descriptions.
125-
for (final html.MediaDeviceInfo videoInputDevice in videoInputDevices) {
137+
for (final html.MediaDeviceInfo videoInputDevice
138+
in await _videoInputDevices()) {
126139
// Get the video stream for the current video input device
127140
// to later use for the available video tracks.
128141
final html.MediaStream videoStream = await _getVideoStreamForDevice(
@@ -134,54 +147,57 @@ class CameraPlugin extends CameraPlatform {
134147
final List<html.MediaStreamTrack> videoTracks =
135148
videoStream.getVideoTracks();
136149

137-
if (videoTracks.isNotEmpty) {
138-
// Get the facing mode from the first available video track.
139-
final String? facingMode =
140-
_cameraService.getFacingModeForVideoTrack(videoTracks.first);
141-
142-
// Get the lens direction based on the facing mode.
143-
// Fallback to the external lens direction
144-
// if the facing mode is not available.
145-
final CameraLensDirection lensDirection = facingMode != null
146-
? mapFacingModeToLensDirection(facingMode)
147-
: CameraLensDirection.external;
148-
149-
// Create a camera description.
150-
//
151-
// The name is a camera label which might be empty
152-
// if no permissions to media devices have been granted.
153-
//
154-
// MediaDeviceInfo.label:
155-
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/label
156-
//
157-
// Sensor orientation is currently not supported.
158-
final String cameraLabel = videoInputDevice.label ?? '';
159-
final CameraDescription camera = CameraDescription(
160-
name: cameraLabel,
161-
lensDirection: lensDirection,
162-
sensorOrientation: 0,
163-
);
164-
165-
final CameraMetadata cameraMetadata = CameraMetadata(
166-
deviceId: videoInputDevice.deviceId!,
167-
facingMode: facingMode,
168-
);
169-
170-
cameras.add(camera);
171-
172-
camerasMetadata[camera] = cameraMetadata;
150+
if (videoTracks.isEmpty) {
151+
// Ignore as no video tracks exist in the current video input device.
152+
continue;
153+
}
154+
155+
// Get the facing mode from the first available video track.
156+
final String? facingMode =
157+
_cameraService.getFacingModeForVideoTrack(videoTracks.first);
158+
159+
// Get the lens direction based on the facing mode.
160+
// Fallback to the external lens direction
161+
// if the facing mode is not available.
162+
final CameraLensDirection lensDirection = facingMode != null
163+
? mapFacingModeToLensDirection(facingMode)
164+
: CameraLensDirection.external;
165+
166+
// Create a camera description.
167+
//
168+
// The name is a camera label which might be empty
169+
// if no permissions to media devices have been granted.
170+
//
171+
// MediaDeviceInfo.label:
172+
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/label
173+
//
174+
// Sensor orientation is currently not supported.
175+
final String cameraLabel = videoInputDevice.label ?? '';
176+
final CameraDescription camera = CameraDescription(
177+
name: cameraLabel,
178+
lensDirection: lensDirection,
179+
sensorOrientation: 0,
180+
);
173181

182+
final CameraMetadata cameraMetadata = CameraMetadata(
183+
deviceId: videoInputDevice.deviceId!,
184+
facingMode: facingMode,
185+
);
186+
187+
cameraDescriptions.add(camera);
188+
189+
camerasMetadata[camera] = cameraMetadata;
190+
191+
if (!hasPermission) {
174192
// Release the camera stream of the current video input device.
175193
for (final html.MediaStreamTrack videoTrack in videoTracks) {
176194
videoTrack.stop();
177195
}
178-
} else {
179-
// Ignore as no video tracks exist in the current video input device.
180-
continue;
181196
}
182197
}
183198

184-
return cameras;
199+
_availableCameras = cameraDescriptions;
200+
return cameraDescriptions;
185201
} on html.DomException catch (e) {
186202
throw CameraException(e.name, e.message);
187203
} on PlatformException catch (e) {

0 commit comments

Comments
 (0)