Skip to content

Commit f0acd13

Browse files
bselwefotiDim
authored andcommitted
[camera_web] Add onCameraClosing implementation (flutter#4259)
1 parent 9f32254 commit f0acd13

File tree

4 files changed

+267
-63
lines changed

4 files changed

+267
-63
lines changed

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

+73-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'dart:html';
66
import 'dart:ui';
77

8+
import 'package:async/async.dart';
89
import 'package:camera_platform_interface/camera_platform_interface.dart';
910
import 'package:camera_web/src/camera.dart';
1011
import 'package:camera_web/src/camera_service.dart';
@@ -843,10 +844,81 @@ void main() {
843844

844845
await camera.initialize();
845846

846-
camera.dispose();
847+
await camera.dispose();
847848

848849
expect(camera.videoElement.srcObject, isNull);
849850
});
850851
});
852+
853+
group('events', () {
854+
group('onEnded', () {
855+
testWidgets(
856+
'emits the default video track '
857+
'when it emits an ended event', (tester) async {
858+
final camera = Camera(
859+
textureId: textureId,
860+
cameraService: cameraService,
861+
);
862+
863+
final streamQueue = StreamQueue(camera.onEnded);
864+
865+
await camera.initialize();
866+
867+
final videoTracks = camera.stream!.getVideoTracks();
868+
final defaultVideoTrack = videoTracks.first;
869+
870+
defaultVideoTrack.dispatchEvent(Event('ended'));
871+
872+
expect(
873+
await streamQueue.next,
874+
equals(defaultVideoTrack),
875+
);
876+
877+
await streamQueue.cancel();
878+
});
879+
880+
testWidgets(
881+
'emits the default video track '
882+
'when the camera is stopped', (tester) async {
883+
final camera = Camera(
884+
textureId: textureId,
885+
cameraService: cameraService,
886+
);
887+
888+
final streamQueue = StreamQueue(camera.onEnded);
889+
890+
await camera.initialize();
891+
892+
final videoTracks = camera.stream!.getVideoTracks();
893+
final defaultVideoTrack = videoTracks.first;
894+
895+
camera.stop();
896+
897+
expect(
898+
await streamQueue.next,
899+
equals(defaultVideoTrack),
900+
);
901+
902+
await streamQueue.cancel();
903+
});
904+
905+
testWidgets(
906+
'no longer emits the default video track '
907+
'when the camera is disposed', (tester) async {
908+
final camera = Camera(
909+
textureId: textureId,
910+
cameraService: cameraService,
911+
);
912+
913+
await camera.initialize();
914+
await camera.dispose();
915+
916+
expect(
917+
camera.onEndedStreamController.isClosed,
918+
isTrue,
919+
);
920+
});
921+
});
922+
});
851923
});
852924
}

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

+137-58
Original file line numberDiff line numberDiff line change
@@ -563,24 +563,34 @@ void main() {
563563
late Camera camera;
564564
late VideoElement videoElement;
565565

566+
late StreamController<Event> errorStreamController, abortStreamController;
567+
late StreamController<MediaStreamTrack> endedStreamController;
568+
566569
setUp(() {
567570
camera = MockCamera();
568571
videoElement = MockVideoElement();
569572

570-
when(() => camera.videoElement).thenReturn(videoElement);
571-
when(() => videoElement.onError)
572-
.thenAnswer((_) => FakeElementStream(Stream.empty()));
573-
when(() => videoElement.onAbort)
574-
.thenAnswer((_) => FakeElementStream(Stream.empty()));
575-
});
573+
errorStreamController = StreamController<Event>();
574+
abortStreamController = StreamController<Event>();
575+
endedStreamController = StreamController<MediaStreamTrack>();
576576

577-
testWidgets('initializes and plays the camera', (tester) async {
578577
when(camera.getVideoSize).thenAnswer(
579578
(_) => Future.value(Size(10, 10)),
580579
);
581580
when(camera.initialize).thenAnswer((_) => Future.value());
582581
when(camera.play).thenAnswer((_) => Future.value());
583582

583+
when(() => camera.videoElement).thenReturn(videoElement);
584+
when(() => videoElement.onError)
585+
.thenAnswer((_) => FakeElementStream(errorStreamController.stream));
586+
when(() => videoElement.onAbort)
587+
.thenAnswer((_) => FakeElementStream(abortStreamController.stream));
588+
589+
when(() => camera.onEnded)
590+
.thenAnswer((_) => endedStreamController.stream);
591+
});
592+
593+
testWidgets('initializes and plays the camera', (tester) async {
584594
// Save the camera in the camera plugin.
585595
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
586596

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

603+
testWidgets('starts listening to the camera video error and abort events',
604+
(tester) async {
605+
// Save the camera in the camera plugin.
606+
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
607+
608+
expect(errorStreamController.hasListener, isFalse);
609+
expect(abortStreamController.hasListener, isFalse);
610+
611+
await CameraPlatform.instance.initializeCamera(cameraId);
612+
613+
expect(errorStreamController.hasListener, isTrue);
614+
expect(abortStreamController.hasListener, isTrue);
615+
});
616+
617+
testWidgets('starts listening to the camera ended events',
618+
(tester) async {
619+
// Save the camera in the camera plugin.
620+
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
621+
622+
expect(endedStreamController.hasListener, isFalse);
623+
624+
await CameraPlatform.instance.initializeCamera(cameraId);
625+
626+
expect(endedStreamController.hasListener, isTrue);
627+
});
628+
593629
group('throws PlatformException', () {
594630
testWidgets(
595631
'with notFound error '
@@ -1610,6 +1646,37 @@ void main() {
16101646
});
16111647

16121648
group('dispose', () {
1649+
late Camera camera;
1650+
late VideoElement videoElement;
1651+
1652+
late StreamController<Event> errorStreamController, abortStreamController;
1653+
late StreamController<MediaStreamTrack> endedStreamController;
1654+
1655+
setUp(() {
1656+
camera = MockCamera();
1657+
videoElement = MockVideoElement();
1658+
1659+
errorStreamController = StreamController<Event>();
1660+
abortStreamController = StreamController<Event>();
1661+
endedStreamController = StreamController<MediaStreamTrack>();
1662+
1663+
when(camera.getVideoSize).thenAnswer(
1664+
(_) => Future.value(Size(10, 10)),
1665+
);
1666+
when(camera.initialize).thenAnswer((_) => Future.value());
1667+
when(camera.play).thenAnswer((_) => Future.value());
1668+
when(camera.dispose).thenAnswer((_) => Future.value());
1669+
1670+
when(() => camera.videoElement).thenReturn(videoElement);
1671+
when(() => videoElement.onError)
1672+
.thenAnswer((_) => FakeElementStream(errorStreamController.stream));
1673+
when(() => videoElement.onAbort)
1674+
.thenAnswer((_) => FakeElementStream(abortStreamController.stream));
1675+
1676+
when(() => camera.onEnded)
1677+
.thenAnswer((_) => endedStreamController.stream);
1678+
});
1679+
16131680
testWidgets('disposes the correct camera', (tester) async {
16141681
const firstCameraId = 0;
16151682
const secondCameraId = 1;
@@ -1642,38 +1709,26 @@ void main() {
16421709
);
16431710
});
16441711

1645-
testWidgets('cancels camera video and abort error subscriptions',
1712+
testWidgets('cancels the camera video error and abort subscriptions',
16461713
(tester) async {
1647-
final camera = MockCamera();
1648-
final videoElement = MockVideoElement();
1649-
1650-
final errorStreamController = StreamController<Event>();
1651-
final abortStreamController = StreamController<Event>();
1714+
// Save the camera in the camera plugin.
1715+
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
16521716

1653-
when(camera.getVideoSize).thenAnswer(
1654-
(_) => Future.value(Size(10, 10)),
1655-
);
1656-
when(camera.initialize).thenAnswer((_) => Future.value());
1657-
when(camera.play).thenAnswer((_) => Future.value());
1717+
await CameraPlatform.instance.initializeCamera(cameraId);
1718+
await CameraPlatform.instance.dispose(cameraId);
16581719

1659-
when(() => camera.videoElement).thenReturn(videoElement);
1660-
when(() => videoElement.onError)
1661-
.thenAnswer((_) => FakeElementStream(errorStreamController.stream));
1662-
when(() => videoElement.onAbort)
1663-
.thenAnswer((_) => FakeElementStream(abortStreamController.stream));
1720+
expect(errorStreamController.hasListener, isFalse);
1721+
expect(abortStreamController.hasListener, isFalse);
1722+
});
16641723

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

16681728
await CameraPlatform.instance.initializeCamera(cameraId);
1669-
1670-
expect(errorStreamController.hasListener, isTrue);
1671-
expect(abortStreamController.hasListener, isTrue);
1672-
16731729
await CameraPlatform.instance.dispose(cameraId);
16741730

1675-
expect(errorStreamController.hasListener, isFalse);
1676-
expect(abortStreamController.hasListener, isFalse);
1731+
expect(endedStreamController.hasListener, isFalse);
16771732
});
16781733

16791734
group('throws PlatformException', () {
@@ -1749,6 +1804,36 @@ void main() {
17491804
});
17501805

17511806
group('events', () {
1807+
late Camera camera;
1808+
late VideoElement videoElement;
1809+
1810+
late StreamController<Event> errorStreamController, abortStreamController;
1811+
late StreamController<MediaStreamTrack> endedStreamController;
1812+
1813+
setUp(() {
1814+
camera = MockCamera();
1815+
videoElement = MockVideoElement();
1816+
1817+
errorStreamController = StreamController<Event>();
1818+
abortStreamController = StreamController<Event>();
1819+
endedStreamController = StreamController<MediaStreamTrack>();
1820+
1821+
when(camera.getVideoSize).thenAnswer(
1822+
(_) => Future.value(Size(10, 10)),
1823+
);
1824+
when(camera.initialize).thenAnswer((_) => Future.value());
1825+
when(camera.play).thenAnswer((_) => Future.value());
1826+
1827+
when(() => camera.videoElement).thenReturn(videoElement);
1828+
when(() => videoElement.onError)
1829+
.thenAnswer((_) => FakeElementStream(errorStreamController.stream));
1830+
when(() => videoElement.onAbort)
1831+
.thenAnswer((_) => FakeElementStream(abortStreamController.stream));
1832+
1833+
when(() => camera.onEnded)
1834+
.thenAnswer((_) => endedStreamController.stream);
1835+
});
1836+
17521837
testWidgets(
17531838
'onCameraInitialized emits a CameraInitializedEvent '
17541839
'on initializeCamera', (tester) async {
@@ -1805,46 +1890,40 @@ void main() {
18051890
);
18061891
});
18071892

1808-
testWidgets('onCameraClosing throws UnimplementedError', (tester) async {
1809-
expect(
1810-
() => CameraPlatform.instance.onCameraClosing(cameraId),
1811-
throwsUnimplementedError,
1812-
);
1813-
});
1893+
testWidgets(
1894+
'onCameraClosing emits a CameraClosingEvent '
1895+
'on the camera ended event', (tester) async {
1896+
// Save the camera in the camera plugin.
1897+
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
18141898

1815-
group('onCameraError', () {
1816-
late Camera camera;
1817-
late VideoElement videoElement;
1899+
final Stream<CameraClosingEvent> eventStream =
1900+
CameraPlatform.instance.onCameraClosing(cameraId);
18181901

1819-
late StreamController<Event> errorStreamController,
1820-
abortStreamController;
1902+
final streamQueue = StreamQueue(eventStream);
18211903

1822-
setUp(() {
1823-
camera = MockCamera();
1824-
videoElement = MockVideoElement();
1904+
await CameraPlatform.instance.initializeCamera(cameraId);
18251905

1826-
errorStreamController = StreamController<Event>();
1827-
abortStreamController = StreamController<Event>();
1906+
endedStreamController.add(MockMediaStreamTrack());
18281907

1829-
when(camera.getVideoSize).thenAnswer(
1830-
(_) => Future.value(Size(10, 10)),
1831-
);
1832-
when(camera.initialize).thenAnswer((_) => Future.value());
1833-
when(camera.play).thenAnswer((_) => Future.value());
1908+
expect(
1909+
await streamQueue.next,
1910+
equals(
1911+
CameraClosingEvent(cameraId),
1912+
),
1913+
);
18341914

1835-
when(() => camera.videoElement).thenReturn(videoElement);
1836-
when(() => videoElement.onError).thenAnswer(
1837-
(_) => FakeElementStream(errorStreamController.stream));
1838-
when(() => videoElement.onAbort).thenAnswer(
1839-
(_) => FakeElementStream(abortStreamController.stream));
1915+
await streamQueue.cancel();
1916+
});
18401917

1918+
group('onCameraError', () {
1919+
setUp(() {
18411920
// Save the camera in the camera plugin.
18421921
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
18431922
});
18441923

18451924
testWidgets(
18461925
'emits a CameraErrorEvent '
1847-
'on initialize video error '
1926+
'on the camera video error event '
18481927
'with a message', (tester) async {
18491928
final Stream<CameraErrorEvent> eventStream =
18501929
CameraPlatform.instance.onCameraError(cameraId);
@@ -1879,7 +1958,7 @@ void main() {
18791958

18801959
testWidgets(
18811960
'emits a CameraErrorEvent '
1882-
'on initialize video error '
1961+
'on the camera video error event '
18831962
'with no message', (tester) async {
18841963
final Stream<CameraErrorEvent> eventStream =
18851964
CameraPlatform.instance.onCameraError(cameraId);
@@ -1910,7 +1989,7 @@ void main() {
19101989

19111990
testWidgets(
19121991
'emits a CameraErrorEvent '
1913-
'on initialize abort error', (tester) async {
1992+
'on the camera video abort event', (tester) async {
19141993
final Stream<CameraErrorEvent> eventStream =
19151994
CameraPlatform.instance.onCameraError(cameraId);
19161995

0 commit comments

Comments
 (0)