Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[camera_web] Do not flip the video on the back camera #4281

Merged
merged 3 commits into from
Aug 30, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
112 changes: 110 additions & 2 deletions packages/camera/camera_web/example/integration_test/camera_test.dart
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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);
}
Comment on lines +192 to +196
Copy link
Member

Choose a reason for hiding this comment

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

This will probably need to be applied per-frame (somehow) when recording video.


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
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ class CameraPlugin extends CameraPlatform {
);
});

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

cameraEventStreamController.add(
CameraInitializedEvent(
Expand Down