Skip to content

Commit dd66f34

Browse files
authored
[camera_web] Add createCamera implementation (flutter#4182)
1 parent 758c55e commit dd66f34

File tree

5 files changed

+318
-12
lines changed

5 files changed

+318
-12
lines changed

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

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'dart:html';
6+
import 'dart:ui';
67

78
import 'package:camera_platform_interface/camera_platform_interface.dart';
89
import 'package:camera_web/src/camera_settings.dart';
@@ -204,6 +205,100 @@ void main() {
204205
);
205206
});
206207
});
208+
209+
group('mapFacingModeToCameraType', () {
210+
testWidgets(
211+
'returns user '
212+
'when the facing mode is user', (tester) async {
213+
expect(
214+
settings.mapFacingModeToCameraType('user'),
215+
equals(CameraType.user),
216+
);
217+
});
218+
219+
testWidgets(
220+
'returns environment '
221+
'when the facing mode is environment', (tester) async {
222+
expect(
223+
settings.mapFacingModeToCameraType('environment'),
224+
equals(CameraType.environment),
225+
);
226+
});
227+
228+
testWidgets(
229+
'returns user '
230+
'when the facing mode is left', (tester) async {
231+
expect(
232+
settings.mapFacingModeToCameraType('left'),
233+
equals(CameraType.user),
234+
);
235+
});
236+
237+
testWidgets(
238+
'returns user '
239+
'when the facing mode is right', (tester) async {
240+
expect(
241+
settings.mapFacingModeToCameraType('right'),
242+
equals(CameraType.user),
243+
);
244+
});
245+
});
246+
247+
group('mapResolutionPresetToSize', () {
248+
testWidgets(
249+
'returns 3840x2160 '
250+
'when the resolution preset is max', (tester) async {
251+
expect(
252+
settings.mapResolutionPresetToSize(ResolutionPreset.max),
253+
equals(Size(3840, 2160)),
254+
);
255+
});
256+
257+
testWidgets(
258+
'returns 3840x2160 '
259+
'when the resolution preset is ultraHigh', (tester) async {
260+
expect(
261+
settings.mapResolutionPresetToSize(ResolutionPreset.ultraHigh),
262+
equals(Size(3840, 2160)),
263+
);
264+
});
265+
266+
testWidgets(
267+
'returns 1920x1080 '
268+
'when the resolution preset is veryHigh', (tester) async {
269+
expect(
270+
settings.mapResolutionPresetToSize(ResolutionPreset.veryHigh),
271+
equals(Size(1920, 1080)),
272+
);
273+
});
274+
275+
testWidgets(
276+
'returns 1280x720 '
277+
'when the resolution preset is high', (tester) async {
278+
expect(
279+
settings.mapResolutionPresetToSize(ResolutionPreset.high),
280+
equals(Size(1280, 720)),
281+
);
282+
});
283+
284+
testWidgets(
285+
'returns 720x480 '
286+
'when the resolution preset is medium', (tester) async {
287+
expect(
288+
settings.mapResolutionPresetToSize(ResolutionPreset.medium),
289+
equals(Size(720, 480)),
290+
);
291+
});
292+
293+
testWidgets(
294+
'returns 320x240 '
295+
'when the resolution preset is low', (tester) async {
296+
expect(
297+
settings.mapResolutionPresetToSize(ResolutionPreset.low),
298+
equals(Size(320, 240)),
299+
);
300+
});
301+
});
207302
});
208303
}
209304

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

Lines changed: 134 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
// found in the LICENSE file.
44

55
import 'dart:html';
6+
import 'dart:ui';
67

78
import 'package:camera_platform_interface/camera_platform_interface.dart';
89
import 'package:camera_web/camera_web.dart';
10+
import 'package:camera_web/src/camera.dart';
911
import 'package:camera_web/src/camera_settings.dart';
1012
import 'package:camera_web/src/types/types.dart';
1113
import 'package:flutter/services.dart';
@@ -296,18 +298,140 @@ void main() {
296298
});
297299
});
298300

299-
testWidgets('createCamera throws UnimplementedError', (tester) async {
300-
expect(
301-
() => CameraPlatform.instance.createCamera(
302-
CameraDescription(
301+
group('createCamera', () {
302+
testWidgets(
303+
'throws CameraException '
304+
'with missingMetadata error '
305+
'if there is no metadata '
306+
'for the given camera description', (tester) async {
307+
expect(
308+
() => CameraPlatform.instance.createCamera(
309+
CameraDescription(
310+
name: 'name',
311+
lensDirection: CameraLensDirection.back,
312+
sensorOrientation: 0,
313+
),
314+
ResolutionPreset.ultraHigh,
315+
),
316+
throwsA(
317+
isA<CameraException>().having(
318+
(e) => e.code,
319+
'code',
320+
CameraErrorCodes.missingMetadata,
321+
),
322+
),
323+
);
324+
});
325+
326+
group('creates a camera', () {
327+
const ultraHighResolutionSize = Size(3840, 2160);
328+
const maxResolutionSize = Size(3840, 2160);
329+
330+
late CameraDescription cameraDescription;
331+
late CameraMetadata cameraMetadata;
332+
333+
setUp(() {
334+
cameraDescription = CameraDescription(
303335
name: 'name',
304-
lensDirection: CameraLensDirection.external,
336+
lensDirection: CameraLensDirection.front,
305337
sensorOrientation: 0,
306-
),
307-
ResolutionPreset.medium,
308-
),
309-
throwsUnimplementedError,
310-
);
338+
);
339+
340+
cameraMetadata = CameraMetadata(
341+
deviceId: 'deviceId',
342+
facingMode: 'user',
343+
);
344+
345+
// Add metadata for the camera description.
346+
(CameraPlatform.instance as CameraPlugin)
347+
.camerasMetadata[cameraDescription] = cameraMetadata;
348+
349+
when(
350+
() => cameraSettings.mapFacingModeToCameraType('user'),
351+
).thenReturn(CameraType.user);
352+
});
353+
354+
testWidgets('with appropriate options', (tester) async {
355+
when(
356+
() => cameraSettings
357+
.mapResolutionPresetToSize(ResolutionPreset.ultraHigh),
358+
).thenReturn(ultraHighResolutionSize);
359+
360+
final cameraId = await CameraPlatform.instance.createCamera(
361+
cameraDescription,
362+
ResolutionPreset.ultraHigh,
363+
enableAudio: true,
364+
);
365+
366+
expect(
367+
(CameraPlatform.instance as CameraPlugin).cameras[cameraId],
368+
isA<Camera>()
369+
.having(
370+
(camera) => camera.textureId,
371+
'textureId',
372+
cameraId,
373+
)
374+
.having(
375+
(camera) => camera.window,
376+
'window',
377+
window,
378+
)
379+
.having(
380+
(camera) => camera.options,
381+
'options',
382+
CameraOptions(
383+
audio: AudioConstraints(enabled: true),
384+
video: VideoConstraints(
385+
facingMode: FacingModeConstraint(CameraType.user),
386+
width: VideoSizeConstraint(
387+
ideal: ultraHighResolutionSize.width.toInt(),
388+
),
389+
height: VideoSizeConstraint(
390+
ideal: ultraHighResolutionSize.height.toInt(),
391+
),
392+
deviceId: cameraMetadata.deviceId,
393+
),
394+
),
395+
),
396+
);
397+
});
398+
399+
testWidgets(
400+
'with a max resolution preset '
401+
'and enabled audio set to false '
402+
'when no options are specified', (tester) async {
403+
when(
404+
() =>
405+
cameraSettings.mapResolutionPresetToSize(ResolutionPreset.max),
406+
).thenReturn(maxResolutionSize);
407+
408+
final cameraId = await CameraPlatform.instance.createCamera(
409+
cameraDescription,
410+
null,
411+
);
412+
413+
expect(
414+
(CameraPlatform.instance as CameraPlugin).cameras[cameraId],
415+
isA<Camera>().having(
416+
(camera) => camera.options,
417+
'options',
418+
CameraOptions(
419+
audio: AudioConstraints(enabled: false),
420+
video: VideoConstraints(
421+
facingMode: FacingModeConstraint(CameraType.user),
422+
width: VideoSizeConstraint(
423+
ideal: maxResolutionSize.width.toInt(),
424+
),
425+
height: VideoSizeConstraint(
426+
ideal: maxResolutionSize.height.toInt(),
427+
),
428+
deviceId: cameraMetadata.deviceId,
429+
),
430+
),
431+
),
432+
);
433+
});
434+
});
311435
});
312436

313437
testWidgets('initializeCamera throws UnimplementedError', (tester) async {

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'dart:html' as html;
6+
import 'dart:ui';
67

78
import 'package:camera_platform_interface/camera_platform_interface.dart';
89
import 'package:camera_web/src/types/types.dart';
@@ -105,4 +106,38 @@ class CameraSettings {
105106
return CameraLensDirection.external;
106107
}
107108
}
109+
110+
/// Maps the given [facingMode] to [CameraType].
111+
///
112+
/// See [CameraMetadata.facingMode] for more details.
113+
CameraType mapFacingModeToCameraType(String facingMode) {
114+
switch (facingMode) {
115+
case 'user':
116+
return CameraType.user;
117+
case 'environment':
118+
return CameraType.environment;
119+
case 'left':
120+
case 'right':
121+
default:
122+
return CameraType.user;
123+
}
124+
}
125+
126+
/// Maps the given [resolutionPreset] to [Size].
127+
Size mapResolutionPresetToSize(ResolutionPreset resolutionPreset) {
128+
switch (resolutionPreset) {
129+
case ResolutionPreset.max:
130+
case ResolutionPreset.ultraHigh:
131+
return Size(3840, 2160);
132+
case ResolutionPreset.veryHigh:
133+
return Size(1920, 1080);
134+
case ResolutionPreset.high:
135+
return Size(1280, 720);
136+
case ResolutionPreset.medium:
137+
return Size(720, 480);
138+
case ResolutionPreset.low:
139+
default:
140+
return Size(320, 240);
141+
}
142+
}
108143
}

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

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'dart:html' as html;
77
import 'dart:math';
88

99
import 'package:camera_platform_interface/camera_platform_interface.dart';
10+
import 'package:camera_web/src/camera.dart';
1011
import 'package:camera_web/src/camera_settings.dart';
1112
import 'package:camera_web/src/types/types.dart';
1213
import 'package:flutter/material.dart';
@@ -31,6 +32,11 @@ class CameraPlugin extends CameraPlatform {
3132

3233
final CameraSettings _cameraSettings;
3334

35+
/// The cameras managed by the [CameraPlugin].
36+
@visibleForTesting
37+
final cameras = <int, Camera>{};
38+
var _textureCounter = 1;
39+
3440
/// Metadata associated with each camera description.
3541
/// Populated in [availableCameras].
3642
@visibleForTesting
@@ -130,8 +136,51 @@ class CameraPlugin extends CameraPlatform {
130136
CameraDescription cameraDescription,
131137
ResolutionPreset? resolutionPreset, {
132138
bool enableAudio = false,
133-
}) {
134-
throw UnimplementedError('createCamera() is not implemented.');
139+
}) async {
140+
if (!camerasMetadata.containsKey(cameraDescription)) {
141+
throw CameraException(
142+
CameraErrorCodes.missingMetadata,
143+
'Missing camera metadata. Make sure to call `availableCameras` before creating a camera.',
144+
);
145+
}
146+
147+
final textureId = _textureCounter++;
148+
149+
final cameraMetadata = camerasMetadata[cameraDescription]!;
150+
151+
final cameraType = cameraMetadata.facingMode != null
152+
? _cameraSettings.mapFacingModeToCameraType(cameraMetadata.facingMode!)
153+
: null;
154+
155+
// Use the highest resolution possible
156+
// if the resolution preset is not specified.
157+
final videoSize = _cameraSettings
158+
.mapResolutionPresetToSize(resolutionPreset ?? ResolutionPreset.max);
159+
160+
// Create a camera with the given audio and video constraints.
161+
// Sensor orientation is currently not supported.
162+
final camera = Camera(
163+
textureId: textureId,
164+
window: window,
165+
options: CameraOptions(
166+
audio: AudioConstraints(enabled: enableAudio),
167+
video: VideoConstraints(
168+
facingMode:
169+
cameraType != null ? FacingModeConstraint(cameraType) : null,
170+
width: VideoSizeConstraint(
171+
ideal: videoSize.width.toInt(),
172+
),
173+
height: VideoSizeConstraint(
174+
ideal: videoSize.height.toInt(),
175+
),
176+
deviceId: cameraMetadata.deviceId,
177+
),
178+
),
179+
);
180+
181+
cameras[textureId] = camera;
182+
183+
return textureId;
135184
}
136185

137186
@override

0 commit comments

Comments
 (0)