Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

import 'dart:html';
import 'dart:ui';

import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:camera_web/src/camera_settings.dart';
Expand Down Expand Up @@ -204,6 +205,100 @@ void main() {
);
});
});

group('mapFacingModeToCameraType', () {
testWidgets(
'returns user '
'when the facing mode is user', (tester) async {
expect(
settings.mapFacingModeToCameraType('user'),
equals(CameraType.user),
);
});

testWidgets(
'returns environment '
'when the facing mode is environment', (tester) async {
expect(
settings.mapFacingModeToCameraType('environment'),
equals(CameraType.environment),
);
});

testWidgets(
'returns user '
'when the facing mode is left', (tester) async {
expect(
settings.mapFacingModeToCameraType('left'),
equals(CameraType.user),
);
});

testWidgets(
'returns user '
'when the facing mode is right', (tester) async {
expect(
settings.mapFacingModeToCameraType('right'),
equals(CameraType.user),
);
});
});

group('mapResolutionPresetToSize', () {
testWidgets(
'returns 3840x2160 '
'when the resolution preset is max', (tester) async {
expect(
settings.mapResolutionPresetToSize(ResolutionPreset.max),
equals(Size(3840, 2160)),
);
});

testWidgets(
'returns 3840x2160 '
'when the resolution preset is ultraHigh', (tester) async {
expect(
settings.mapResolutionPresetToSize(ResolutionPreset.ultraHigh),
equals(Size(3840, 2160)),
);
});

testWidgets(
'returns 1920x1080 '
'when the resolution preset is veryHigh', (tester) async {
expect(
settings.mapResolutionPresetToSize(ResolutionPreset.veryHigh),
equals(Size(1920, 1080)),
);
});

testWidgets(
'returns 1280x720 '
'when the resolution preset is high', (tester) async {
expect(
settings.mapResolutionPresetToSize(ResolutionPreset.high),
equals(Size(1280, 720)),
);
});

testWidgets(
'returns 720x480 '
'when the resolution preset is medium', (tester) async {
expect(
settings.mapResolutionPresetToSize(ResolutionPreset.medium),
equals(Size(720, 480)),
);
});

testWidgets(
'returns 320x240 '
'when the resolution preset is low', (tester) async {
expect(
settings.mapResolutionPresetToSize(ResolutionPreset.low),
equals(Size(320, 240)),
);
});
});
});
}

Expand Down
144 changes: 134 additions & 10 deletions packages/camera/camera_web/example/integration_test/camera_web_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
// found in the LICENSE file.

import 'dart:html';
import 'dart:ui';

import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:camera_web/camera_web.dart';
import 'package:camera_web/src/camera.dart';
import 'package:camera_web/src/camera_settings.dart';
import 'package:camera_web/src/types/types.dart';
import 'package:flutter/services.dart';
Expand Down Expand Up @@ -296,18 +298,140 @@ void main() {
});
});

testWidgets('createCamera throws UnimplementedError', (tester) async {
expect(
() => CameraPlatform.instance.createCamera(
CameraDescription(
group('createCamera', () {
testWidgets(
'throws CameraException '
'with missingMetadata error '
'if there is no metadata '
'for the given camera description', (tester) async {
expect(
() => CameraPlatform.instance.createCamera(
CameraDescription(
name: 'name',
lensDirection: CameraLensDirection.back,
sensorOrientation: 0,
),
ResolutionPreset.ultraHigh,
),
throwsA(
isA<CameraException>().having(
(e) => e.code,
'code',
CameraErrorCodes.missingMetadata,
),
),
);
});

group('creates a camera', () {
const ultraHighResolutionSize = Size(3840, 2160);
const maxResolutionSize = Size(3840, 2160);

late CameraDescription cameraDescription;
late CameraMetadata cameraMetadata;

setUp(() {
cameraDescription = CameraDescription(
name: 'name',
lensDirection: CameraLensDirection.external,
lensDirection: CameraLensDirection.front,
sensorOrientation: 0,
),
ResolutionPreset.medium,
),
throwsUnimplementedError,
);
);

cameraMetadata = CameraMetadata(
deviceId: 'deviceId',
facingMode: 'user',
);

// Add metadata for the camera description.
(CameraPlatform.instance as CameraPlugin)
.camerasMetadata[cameraDescription] = cameraMetadata;

when(
() => cameraSettings.mapFacingModeToCameraType('user'),
).thenReturn(CameraType.user);
});

testWidgets('with appropriate options', (tester) async {
when(
() => cameraSettings
.mapResolutionPresetToSize(ResolutionPreset.ultraHigh),
).thenReturn(ultraHighResolutionSize);

final cameraId = await CameraPlatform.instance.createCamera(
cameraDescription,
ResolutionPreset.ultraHigh,
enableAudio: true,
);

expect(
(CameraPlatform.instance as CameraPlugin).cameras[cameraId],
isA<Camera>()
.having(
(camera) => camera.textureId,
'textureId',
cameraId,
)
.having(
(camera) => camera.window,
'window',
window,
)
.having(
(camera) => camera.options,
'options',
CameraOptions(
audio: AudioConstraints(enabled: true),
video: VideoConstraints(
facingMode: FacingModeConstraint(CameraType.user),
width: VideoSizeConstraint(
ideal: ultraHighResolutionSize.width.toInt(),
),
height: VideoSizeConstraint(
ideal: ultraHighResolutionSize.height.toInt(),
),
deviceId: cameraMetadata.deviceId,
),
),
),
);
});

testWidgets(
'with a max resolution preset '
'and enabled audio set to false '
'when no options are specified', (tester) async {
when(
() =>
cameraSettings.mapResolutionPresetToSize(ResolutionPreset.max),
).thenReturn(maxResolutionSize);

final cameraId = await CameraPlatform.instance.createCamera(
cameraDescription,
null,
);

expect(
(CameraPlatform.instance as CameraPlugin).cameras[cameraId],
isA<Camera>().having(
(camera) => camera.options,
'options',
CameraOptions(
audio: AudioConstraints(enabled: false),
video: VideoConstraints(
facingMode: FacingModeConstraint(CameraType.user),
width: VideoSizeConstraint(
ideal: maxResolutionSize.width.toInt(),
),
height: VideoSizeConstraint(
ideal: maxResolutionSize.height.toInt(),
),
deviceId: cameraMetadata.deviceId,
),
),
),
);
});
});
});

testWidgets('initializeCamera throws UnimplementedError', (tester) async {
Expand Down
35 changes: 35 additions & 0 deletions packages/camera/camera_web/lib/src/camera_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

import 'dart:html' as html;
import 'dart:ui';

import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:camera_web/src/types/types.dart';
Expand Down Expand Up @@ -105,4 +106,38 @@ class CameraSettings {
return CameraLensDirection.external;
}
}

/// Maps the given [facingMode] to [CameraType].
///
/// See [CameraMetadata.facingMode] for more details.
CameraType mapFacingModeToCameraType(String facingMode) {
switch (facingMode) {
case 'user':
return CameraType.user;
case 'environment':
return CameraType.environment;
case 'left':
case 'right':
default:
return CameraType.user;
}
}

/// Maps the given [resolutionPreset] to [Size].
Size mapResolutionPresetToSize(ResolutionPreset resolutionPreset) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this take in to account the screen orientation? I don't remember if we had to flip the w/h on the photobooth when the app was rotated? Do you remember @felangel?

Copy link
Contributor Author

@bselwe bselwe Jul 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I can see that photobooth records the screen orientation when the photo is being captured. Then, the photo is displayed inside the AspectRatio widget with an aspect ratio based on that orientation but not sure if that has anything to do with flipping the w/h. 🤔

switch (resolutionPreset) {
case ResolutionPreset.max:
case ResolutionPreset.ultraHigh:
return Size(3840, 2160);
case ResolutionPreset.veryHigh:
return Size(1920, 1080);
case ResolutionPreset.high:
return Size(1280, 720);
case ResolutionPreset.medium:
return Size(720, 480);
case ResolutionPreset.low:
default:
return Size(320, 240);
}
}
}
53 changes: 51 additions & 2 deletions packages/camera/camera_web/lib/src/camera_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'dart:html' as html;
import 'dart:math';

import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:camera_web/src/camera.dart';
import 'package:camera_web/src/camera_settings.dart';
import 'package:camera_web/src/types/types.dart';
import 'package:flutter/material.dart';
Expand All @@ -31,6 +32,11 @@ class CameraPlugin extends CameraPlatform {

final CameraSettings _cameraSettings;

/// The cameras managed by the [CameraPlugin].
@visibleForTesting
final cameras = <int, Camera>{};
var _textureCounter = 1;

/// Metadata associated with each camera description.
/// Populated in [availableCameras].
@visibleForTesting
Expand Down Expand Up @@ -130,8 +136,51 @@ class CameraPlugin extends CameraPlatform {
CameraDescription cameraDescription,
ResolutionPreset? resolutionPreset, {
bool enableAudio = false,
}) {
throw UnimplementedError('createCamera() is not implemented.');
}) async {
if (!camerasMetadata.containsKey(cameraDescription)) {
throw CameraException(
CameraErrorCodes.missingMetadata,
'Missing camera metadata. Make sure to call `availableCameras` before creating a camera.',
);
}

final textureId = _textureCounter++;

final cameraMetadata = camerasMetadata[cameraDescription]!;

final cameraType = cameraMetadata.facingMode != null
? _cameraSettings.mapFacingModeToCameraType(cameraMetadata.facingMode!)
: null;

// Use the highest resolution possible
// if the resolution preset is not specified.
final videoSize = _cameraSettings
.mapResolutionPresetToSize(resolutionPreset ?? ResolutionPreset.max);

// Create a camera with the given audio and video constraints.
// Sensor orientation is currently not supported.
final camera = Camera(
textureId: textureId,
window: window,
options: CameraOptions(
audio: AudioConstraints(enabled: enableAudio),
video: VideoConstraints(
facingMode:
cameraType != null ? FacingModeConstraint(cameraType) : null,
width: VideoSizeConstraint(
ideal: videoSize.width.toInt(),
),
height: VideoSizeConstraint(
ideal: videoSize.height.toInt(),
),
deviceId: cameraMetadata.deviceId,
),
),
);

cameras[textureId] = camera;

return textureId;
}

@override
Expand Down
Loading