Skip to content

Commit

Permalink
[camera_web] Do not flip the video on the back camera (flutter#4281)
Browse files Browse the repository at this point in the history
* feat: do not flip the video on the back camera

* test: do not flip the video on the back camera tests

* feat: update getVideoSize usage
  • Loading branch information
bselwe committed Aug 30, 2021
1 parent 32ca776 commit 18129d6
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 20 deletions.
112 changes: 110 additions & 2 deletions packages/camera/camera_web/example/integration_test/camera_test.dart
Expand Up @@ -85,11 +85,17 @@ void main() {
'creates a video element '
'with correct properties', (tester) async {
const audioConstraints = AudioConstraints(enabled: true);
final videoConstraints = VideoConstraints(
facingMode: FacingModeConstraint(
CameraType.user,
),
);

final camera = Camera(
textureId: textureId,
options: CameraOptions(
audio: audioConstraints,
video: videoConstraints,
),
cameraService: cameraService,
);
Expand All @@ -108,6 +114,27 @@ void main() {
expect(camera.videoElement.style.width, equals('100%'));
expect(camera.videoElement.style.height, equals('100%'));
expect(camera.videoElement.style.objectFit, equals('cover'));
});

testWidgets(
'flips the video element horizontally '
'for a back camera', (tester) async {
final videoConstraints = VideoConstraints(
facingMode: FacingModeConstraint(
CameraType.environment,
),
);

final camera = Camera(
textureId: textureId,
options: CameraOptions(
video: videoConstraints,
),
cameraService: cameraService,
);

await camera.initialize();

expect(camera.videoElement.style.transform, equals('scaleX(-1)'));
});

Expand Down Expand Up @@ -376,7 +403,7 @@ void main() {
await camera.initialize();

expect(
await camera.getVideoSize(),
camera.getVideoSize(),
equals(videoSize),
);
});
Expand All @@ -396,7 +423,7 @@ void main() {
await camera.initialize();

expect(
await camera.getVideoSize(),
camera.getVideoSize(),
equals(Size.zero),
);
});
Expand Down Expand Up @@ -819,6 +846,87 @@ void main() {
});
});

group('getLensDirection', () {
testWidgets(
'returns a lens direction '
'based on the first video track settings', (tester) async {
final videoElement = MockVideoElement();

final camera = Camera(
textureId: textureId,
cameraService: cameraService,
)..videoElement = videoElement;

final firstVideoTrack = MockMediaStreamTrack();

when(() => videoElement.srcObject).thenReturn(
FakeMediaStream([
firstVideoTrack,
MockMediaStreamTrack(),
]),
);

when(firstVideoTrack.getSettings)
.thenReturn({'facingMode': 'environment'});

when(() => cameraService.mapFacingModeToLensDirection('environment'))
.thenReturn(CameraLensDirection.external);

expect(
camera.getLensDirection(),
equals(CameraLensDirection.external),
);
});

testWidgets(
'returns null '
'if the first video track is missing the facing mode',
(tester) async {
final videoElement = MockVideoElement();

final camera = Camera(
textureId: textureId,
cameraService: cameraService,
)..videoElement = videoElement;

final firstVideoTrack = MockMediaStreamTrack();

when(() => videoElement.srcObject).thenReturn(
FakeMediaStream([
firstVideoTrack,
MockMediaStreamTrack(),
]),
);

when(firstVideoTrack.getSettings).thenReturn({});

expect(
camera.getLensDirection(),
isNull,
);
});

testWidgets(
'returns null '
'if the camera is missing video tracks', (tester) async {
// Create a video stream with no video tracks.
final videoElement = VideoElement();
mediaStream = videoElement.captureStream();

final camera = Camera(
textureId: textureId,
cameraService: cameraService,
);

await camera.initialize();

expect(
camera.getLensDirection(),
isNull,
);
});
});

group('getViewType', () {
testWidgets('returns a correct view type', (tester) async {
final camera = Camera(
Expand Down
Expand Up @@ -574,9 +574,7 @@ void main() {
abortStreamController = StreamController<Event>();
endedStreamController = StreamController<MediaStreamTrack>();

when(camera.getVideoSize).thenAnswer(
(_) => Future.value(Size(10, 10)),
);
when(camera.getVideoSize).thenReturn(Size(10, 10));
when(camera.initialize).thenAnswer((_) => Future.value());
when(camera.play).thenAnswer((_) => Future.value());

Expand Down Expand Up @@ -1660,9 +1658,7 @@ void main() {
abortStreamController = StreamController<Event>();
endedStreamController = StreamController<MediaStreamTrack>();

when(camera.getVideoSize).thenAnswer(
(_) => Future.value(Size(10, 10)),
);
when(camera.getVideoSize).thenReturn(Size(10, 10));
when(camera.initialize).thenAnswer((_) => Future.value());
when(camera.play).thenAnswer((_) => Future.value());
when(camera.dispose).thenAnswer((_) => Future.value());
Expand Down Expand Up @@ -1818,9 +1814,7 @@ void main() {
abortStreamController = StreamController<Event>();
endedStreamController = StreamController<MediaStreamTrack>();

when(camera.getVideoSize).thenAnswer(
(_) => Future.value(Size(10, 10)),
);
when(camera.getVideoSize).thenReturn(Size(10, 10));
when(camera.initialize).thenAnswer((_) => Future.value());
when(camera.play).thenAnswer((_) => Future.value());

Expand Down
52 changes: 44 additions & 8 deletions packages/camera/camera_web/lib/src/camera.dart
Expand Up @@ -106,7 +106,6 @@ class Camera {
);

videoElement = html.VideoElement();
_applyDefaultVideoStyles(videoElement);

divElement = html.DivElement()
..style.setProperty('object-fit', 'cover')
Expand All @@ -123,6 +122,8 @@ class Camera {
..srcObject = stream
..setAttribute('playsinline', '');

_applyDefaultVideoStyles(videoElement);

final videoTracks = stream!.getVideoTracks();

if (videoTracks.isNotEmpty) {
Expand All @@ -149,7 +150,7 @@ class Camera {
}

/// Pauses the camera stream on the current frame.
void pause() async {
void pause() {
videoElement.pause();
}

Expand Down Expand Up @@ -185,11 +186,17 @@ class Camera {
final videoWidth = videoElement.videoWidth;
final videoHeight = videoElement.videoHeight;
final canvas = html.CanvasElement(width: videoWidth, height: videoHeight);
final isBackCamera = getLensDirection() == CameraLensDirection.back;

// Flip the picture horizontally if it is not taken from a back camera.
if (!isBackCamera) {
canvas.context2D
..translate(videoWidth, 0)
..scale(-1, 1);
}

canvas.context2D
..translate(videoWidth, 0)
..scale(-1, 1)
..drawImageScaled(videoElement, 0, 0, videoWidth, videoHeight);
.drawImageScaled(videoElement, 0, 0, videoWidth, videoHeight);

final blob = await canvas.toBlob('image/jpeg');

Expand All @@ -204,7 +211,7 @@ class Camera {
///
/// Returns [Size.zero] if the camera is missing a video track or
/// the video track does not include the width or height setting.
Future<Size> getVideoSize() async {
Size getVideoSize() {
final videoTracks = videoElement.srcObject?.getVideoTracks() ?? [];

if (videoTracks.isEmpty) {
Expand Down Expand Up @@ -332,6 +339,29 @@ class Camera {
});
}

/// Returns a lens direction of this camera.
///
/// Returns null if the camera is missing a video track or
/// the video track does not include the facing mode setting.
CameraLensDirection? getLensDirection() {
final videoTracks = videoElement.srcObject?.getVideoTracks() ?? [];

if (videoTracks.isEmpty) {
return null;
}

final defaultVideoTrack = videoTracks.first;
final defaultVideoTrackSettings = defaultVideoTrack.getSettings();

final facingMode = defaultVideoTrackSettings['facingMode'];

if (facingMode != null) {
return _cameraService.mapFacingModeToLensDirection(facingMode);
} else {
return null;
}
}

/// Returns the registered view type of the camera.
String getViewType() => _getViewType(textureId);

Expand All @@ -354,12 +384,18 @@ class Camera {

/// Applies default styles to the video [element].
void _applyDefaultVideoStyles(html.VideoElement element) {
final isBackCamera = getLensDirection() == CameraLensDirection.back;

// Flip the video horizontally if it is not taken from a back camera.
if (!isBackCamera) {
element.style.transform = 'scaleX(-1)';
}

element.style
..transformOrigin = 'center'
..pointerEvents = 'none'
..width = '100%'
..height = '100%'
..objectFit = 'cover'
..transform = 'scaleX(-1)';
..objectFit = 'cover';
}
}
2 changes: 1 addition & 1 deletion packages/camera/camera_web/lib/src/camera_web.dart
Expand Up @@ -285,7 +285,7 @@ class CameraPlugin extends CameraPlatform {
);
});

final cameraSize = await camera.getVideoSize();
final cameraSize = camera.getVideoSize();

cameraEventStreamController.add(
CameraInitializedEvent(
Expand Down

0 comments on commit 18129d6

Please sign in to comment.