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

[camera_web] Add onCameraClosing implementation #4259

Merged
merged 8 commits into from
Aug 26, 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.
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 @@ -5,6 +5,7 @@
import 'dart:html';
import 'dart:ui';

import 'package:async/async.dart';
import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:camera_web/src/camera.dart';
import 'package:camera_web/src/camera_service.dart';
Expand Down Expand Up @@ -843,10 +844,81 @@ void main() {

await camera.initialize();

camera.dispose();
await camera.dispose();

expect(camera.videoElement.srcObject, isNull);
});
});

group('events', () {
group('onEnded', () {
testWidgets(
'emits the default video track '
'when it emits an ended event', (tester) async {
final camera = Camera(
textureId: textureId,
cameraService: cameraService,
);

final streamQueue = StreamQueue(camera.onEnded);

await camera.initialize();

final videoTracks = camera.stream!.getVideoTracks();
final defaultVideoTrack = videoTracks.first;

defaultVideoTrack.dispatchEvent(Event('ended'));

expect(
await streamQueue.next,
equals(defaultVideoTrack),
);

await streamQueue.cancel();
});

testWidgets(
'emits the default video track '
'when the camera is stopped', (tester) async {
final camera = Camera(
textureId: textureId,
cameraService: cameraService,
);

final streamQueue = StreamQueue(camera.onEnded);

await camera.initialize();

final videoTracks = camera.stream!.getVideoTracks();
final defaultVideoTrack = videoTracks.first;

camera.stop();

expect(
await streamQueue.next,
equals(defaultVideoTrack),
);

await streamQueue.cancel();
});

testWidgets(
'no longer emits the default video track '
'when the camera is disposed', (tester) async {
final camera = Camera(
textureId: textureId,
cameraService: cameraService,
);

await camera.initialize();
await camera.dispose();

expect(
camera.onEndedStreamController.isClosed,
isTrue,
);
});
});
});
});
}
195 changes: 137 additions & 58 deletions packages/camera/camera_web/example/integration_test/camera_web_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -563,24 +563,34 @@ void main() {
late Camera camera;
late VideoElement videoElement;

late StreamController<Event> errorStreamController, abortStreamController;
late StreamController<MediaStreamTrack> endedStreamController;

setUp(() {
camera = MockCamera();
videoElement = MockVideoElement();

when(() => camera.videoElement).thenReturn(videoElement);
when(() => videoElement.onError)
.thenAnswer((_) => FakeElementStream(Stream.empty()));
when(() => videoElement.onAbort)
.thenAnswer((_) => FakeElementStream(Stream.empty()));
});
errorStreamController = StreamController<Event>();
abortStreamController = StreamController<Event>();
endedStreamController = StreamController<MediaStreamTrack>();

testWidgets('initializes and plays the camera', (tester) async {
when(camera.getVideoSize).thenAnswer(
(_) => Future.value(Size(10, 10)),
);
when(camera.initialize).thenAnswer((_) => Future.value());
when(camera.play).thenAnswer((_) => Future.value());

when(() => camera.videoElement).thenReturn(videoElement);
when(() => videoElement.onError)
.thenAnswer((_) => FakeElementStream(errorStreamController.stream));
when(() => videoElement.onAbort)
.thenAnswer((_) => FakeElementStream(abortStreamController.stream));

when(() => camera.onEnded)
.thenAnswer((_) => endedStreamController.stream);
});

testWidgets('initializes and plays the camera', (tester) async {
// Save the camera in the camera plugin.
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;

Expand All @@ -590,6 +600,32 @@ void main() {
verify(camera.play).called(1);
});

testWidgets('starts listening to the camera video error and abort events',
(tester) async {
// Save the camera in the camera plugin.
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;

expect(errorStreamController.hasListener, isFalse);
expect(abortStreamController.hasListener, isFalse);

await CameraPlatform.instance.initializeCamera(cameraId);

expect(errorStreamController.hasListener, isTrue);
expect(abortStreamController.hasListener, isTrue);
});

testWidgets('starts listening to the camera ended events',
(tester) async {
// Save the camera in the camera plugin.
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;

expect(endedStreamController.hasListener, isFalse);

await CameraPlatform.instance.initializeCamera(cameraId);

expect(endedStreamController.hasListener, isTrue);
});

group('throws PlatformException', () {
testWidgets(
'with notFound error '
Expand Down Expand Up @@ -1610,6 +1646,37 @@ void main() {
});

group('dispose', () {
late Camera camera;
late VideoElement videoElement;

late StreamController<Event> errorStreamController, abortStreamController;
late StreamController<MediaStreamTrack> endedStreamController;

setUp(() {
camera = MockCamera();
videoElement = MockVideoElement();

errorStreamController = StreamController<Event>();
abortStreamController = StreamController<Event>();
endedStreamController = StreamController<MediaStreamTrack>();

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

when(() => camera.videoElement).thenReturn(videoElement);
when(() => videoElement.onError)
.thenAnswer((_) => FakeElementStream(errorStreamController.stream));
when(() => videoElement.onAbort)
.thenAnswer((_) => FakeElementStream(abortStreamController.stream));

when(() => camera.onEnded)
.thenAnswer((_) => endedStreamController.stream);
});

testWidgets('disposes the correct camera', (tester) async {
const firstCameraId = 0;
const secondCameraId = 1;
Expand Down Expand Up @@ -1642,38 +1709,26 @@ void main() {
);
});

testWidgets('cancels camera video and abort error subscriptions',
testWidgets('cancels the camera video error and abort subscriptions',
(tester) async {
final camera = MockCamera();
final videoElement = MockVideoElement();

final errorStreamController = StreamController<Event>();
final abortStreamController = StreamController<Event>();
// Save the camera in the camera plugin.
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;

when(camera.getVideoSize).thenAnswer(
(_) => Future.value(Size(10, 10)),
);
when(camera.initialize).thenAnswer((_) => Future.value());
when(camera.play).thenAnswer((_) => Future.value());
await CameraPlatform.instance.initializeCamera(cameraId);
await CameraPlatform.instance.dispose(cameraId);

when(() => camera.videoElement).thenReturn(videoElement);
when(() => videoElement.onError)
.thenAnswer((_) => FakeElementStream(errorStreamController.stream));
when(() => videoElement.onAbort)
.thenAnswer((_) => FakeElementStream(abortStreamController.stream));
expect(errorStreamController.hasListener, isFalse);
expect(abortStreamController.hasListener, isFalse);
});

testWidgets('cancels the camera ended subscriptions', (tester) async {
// Save the camera in the camera plugin.
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;

await CameraPlatform.instance.initializeCamera(cameraId);

expect(errorStreamController.hasListener, isTrue);
expect(abortStreamController.hasListener, isTrue);

await CameraPlatform.instance.dispose(cameraId);

expect(errorStreamController.hasListener, isFalse);
expect(abortStreamController.hasListener, isFalse);
expect(endedStreamController.hasListener, isFalse);
});

group('throws PlatformException', () {
Expand Down Expand Up @@ -1749,6 +1804,36 @@ void main() {
});

group('events', () {
late Camera camera;
late VideoElement videoElement;

late StreamController<Event> errorStreamController, abortStreamController;
late StreamController<MediaStreamTrack> endedStreamController;

setUp(() {
camera = MockCamera();
videoElement = MockVideoElement();

errorStreamController = StreamController<Event>();
abortStreamController = StreamController<Event>();
endedStreamController = StreamController<MediaStreamTrack>();

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

when(() => camera.videoElement).thenReturn(videoElement);
when(() => videoElement.onError)
.thenAnswer((_) => FakeElementStream(errorStreamController.stream));
when(() => videoElement.onAbort)
.thenAnswer((_) => FakeElementStream(abortStreamController.stream));

when(() => camera.onEnded)
.thenAnswer((_) => endedStreamController.stream);
});

testWidgets(
'onCameraInitialized emits a CameraInitializedEvent '
'on initializeCamera', (tester) async {
Expand Down Expand Up @@ -1805,46 +1890,40 @@ void main() {
);
});

testWidgets('onCameraClosing throws UnimplementedError', (tester) async {
expect(
() => CameraPlatform.instance.onCameraClosing(cameraId),
throwsUnimplementedError,
);
});
testWidgets(
'onCameraClosing emits a CameraClosingEvent '
'on the camera ended event', (tester) async {
// Save the camera in the camera plugin.
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;

group('onCameraError', () {
late Camera camera;
late VideoElement videoElement;
final Stream<CameraClosingEvent> eventStream =
CameraPlatform.instance.onCameraClosing(cameraId);

late StreamController<Event> errorStreamController,
abortStreamController;
final streamQueue = StreamQueue(eventStream);

setUp(() {
camera = MockCamera();
videoElement = MockVideoElement();
await CameraPlatform.instance.initializeCamera(cameraId);

errorStreamController = StreamController<Event>();
abortStreamController = StreamController<Event>();
endedStreamController.add(MockMediaStreamTrack());

when(camera.getVideoSize).thenAnswer(
(_) => Future.value(Size(10, 10)),
);
when(camera.initialize).thenAnswer((_) => Future.value());
when(camera.play).thenAnswer((_) => Future.value());
expect(
await streamQueue.next,
equals(
CameraClosingEvent(cameraId),
),
);

when(() => camera.videoElement).thenReturn(videoElement);
when(() => videoElement.onError).thenAnswer(
(_) => FakeElementStream(errorStreamController.stream));
when(() => videoElement.onAbort).thenAnswer(
(_) => FakeElementStream(abortStreamController.stream));
await streamQueue.cancel();
});

group('onCameraError', () {
setUp(() {
// Save the camera in the camera plugin.
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
});

testWidgets(
'emits a CameraErrorEvent '
'on initialize video error '
'on the camera video error event '
'with a message', (tester) async {
final Stream<CameraErrorEvent> eventStream =
CameraPlatform.instance.onCameraError(cameraId);
Expand Down Expand Up @@ -1879,7 +1958,7 @@ void main() {

testWidgets(
'emits a CameraErrorEvent '
'on initialize video error '
'on the camera video error event '
'with no message', (tester) async {
final Stream<CameraErrorEvent> eventStream =
CameraPlatform.instance.onCameraError(cameraId);
Expand Down Expand Up @@ -1910,7 +1989,7 @@ void main() {

testWidgets(
'emits a CameraErrorEvent '
'on initialize abort error', (tester) async {
'on the camera video abort event', (tester) async {
final Stream<CameraErrorEvent> eventStream =
CameraPlatform.instance.onCameraError(cameraId);

Expand Down
Loading