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

Commit ebf4d59

Browse files
authored
[camera_web] Add support for pausing and resuming the camera preview (#4239)
* chore: update camera_platform_interface to 2.1.0 * feat: add pause to Camera * test: add Camera pause test * feat: add pausePreview and resumePreview implementation * test: add pausePreview and resumePreview tests
1 parent c8570fc commit ebf4d59

File tree

5 files changed

+210
-1
lines changed

5 files changed

+210
-1
lines changed

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,24 @@ void main() {
214214
});
215215
});
216216

217+
group('pause', () {
218+
testWidgets('pauses the camera stream', (tester) async {
219+
final camera = Camera(
220+
textureId: textureId,
221+
cameraService: cameraService,
222+
);
223+
224+
await camera.initialize();
225+
await camera.play();
226+
227+
expect(camera.videoElement.paused, isFalse);
228+
229+
camera.pause();
230+
231+
expect(camera.videoElement.paused, isTrue);
232+
});
233+
});
234+
217235
group('stop', () {
218236
testWidgets('resets the camera stream', (tester) async {
219237
final camera = Camera(

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

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,6 +1459,135 @@ void main() {
14591459
});
14601460
});
14611461

1462+
group('pausePreview', () {
1463+
testWidgets('calls pause on the camera', (tester) async {
1464+
final camera = MockCamera();
1465+
1466+
// Save the camera in the camera plugin.
1467+
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
1468+
1469+
await CameraPlatform.instance.pausePreview(cameraId);
1470+
1471+
verify(camera.pause).called(1);
1472+
});
1473+
1474+
group('throws PlatformException', () {
1475+
testWidgets(
1476+
'with notFound error '
1477+
'if the camera does not exist', (tester) async {
1478+
expect(
1479+
() async => await CameraPlatform.instance.pausePreview(cameraId),
1480+
throwsA(
1481+
isA<PlatformException>().having(
1482+
(e) => e.code,
1483+
'code',
1484+
CameraErrorCode.notFound.toString(),
1485+
),
1486+
),
1487+
);
1488+
});
1489+
1490+
testWidgets('when pause throws DomException', (tester) async {
1491+
final camera = MockCamera();
1492+
final exception = FakeDomException(DomException.NOT_SUPPORTED);
1493+
1494+
when(camera.pause).thenThrow(exception);
1495+
1496+
// Save the camera in the camera plugin.
1497+
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
1498+
1499+
expect(
1500+
() async => await CameraPlatform.instance.pausePreview(cameraId),
1501+
throwsA(
1502+
isA<PlatformException>().having(
1503+
(e) => e.code,
1504+
'code',
1505+
exception.name,
1506+
),
1507+
),
1508+
);
1509+
});
1510+
});
1511+
});
1512+
1513+
group('resumePreview', () {
1514+
testWidgets('calls play on the camera', (tester) async {
1515+
final camera = MockCamera();
1516+
1517+
when(camera.play).thenAnswer((_) async => {});
1518+
1519+
// Save the camera in the camera plugin.
1520+
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
1521+
1522+
await CameraPlatform.instance.resumePreview(cameraId);
1523+
1524+
verify(camera.play).called(1);
1525+
});
1526+
1527+
group('throws PlatformException', () {
1528+
testWidgets(
1529+
'with notFound error '
1530+
'if the camera does not exist', (tester) async {
1531+
expect(
1532+
() async => await CameraPlatform.instance.resumePreview(cameraId),
1533+
throwsA(
1534+
isA<PlatformException>().having(
1535+
(e) => e.code,
1536+
'code',
1537+
CameraErrorCode.notFound.toString(),
1538+
),
1539+
),
1540+
);
1541+
});
1542+
1543+
testWidgets('when play throws DomException', (tester) async {
1544+
final camera = MockCamera();
1545+
final exception = FakeDomException(DomException.NOT_SUPPORTED);
1546+
1547+
when(camera.play).thenThrow(exception);
1548+
1549+
// Save the camera in the camera plugin.
1550+
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
1551+
1552+
expect(
1553+
() async => await CameraPlatform.instance.resumePreview(cameraId),
1554+
throwsA(
1555+
isA<PlatformException>().having(
1556+
(e) => e.code,
1557+
'code',
1558+
exception.name,
1559+
),
1560+
),
1561+
);
1562+
});
1563+
1564+
testWidgets('when play throws CameraWebException', (tester) async {
1565+
final camera = MockCamera();
1566+
final exception = CameraWebException(
1567+
cameraId,
1568+
CameraErrorCode.unknown,
1569+
'description',
1570+
);
1571+
1572+
when(camera.play).thenThrow(exception);
1573+
1574+
// Save the camera in the camera plugin.
1575+
(CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera;
1576+
1577+
expect(
1578+
() async => await CameraPlatform.instance.resumePreview(cameraId),
1579+
throwsA(
1580+
isA<PlatformException>().having(
1581+
(e) => e.code,
1582+
'code',
1583+
exception.code.toString(),
1584+
),
1585+
),
1586+
);
1587+
});
1588+
});
1589+
});
1590+
14621591
testWidgets(
14631592
'buildPreview returns an HtmlElementView '
14641593
'with an appropriate view type', (tester) async {
@@ -1993,6 +2122,42 @@ void main() {
19932122

19942123
await streamQueue.cancel();
19952124
});
2125+
2126+
testWidgets(
2127+
'emits a CameraErrorEvent '
2128+
'on resumePreview error', (tester) async {
2129+
final exception = CameraWebException(
2130+
cameraId,
2131+
CameraErrorCode.unknown,
2132+
'description',
2133+
);
2134+
2135+
when(camera.play).thenThrow(exception);
2136+
2137+
final Stream<CameraErrorEvent> eventStream =
2138+
CameraPlatform.instance.onCameraError(cameraId);
2139+
2140+
final streamQueue = StreamQueue(eventStream);
2141+
2142+
expect(
2143+
() async => await CameraPlatform.instance.resumePreview(cameraId),
2144+
throwsA(
2145+
isA<PlatformException>(),
2146+
),
2147+
);
2148+
2149+
expect(
2150+
await streamQueue.next,
2151+
equals(
2152+
CameraErrorEvent(
2153+
cameraId,
2154+
'Error code: ${exception.code}, error message: ${exception.description}',
2155+
),
2156+
),
2157+
);
2158+
2159+
await streamQueue.cancel();
2160+
});
19962161
});
19972162

19982163
testWidgets('onVideoRecordedEvent throws UnimplementedError',

packages/camera/camera_web/lib/src/camera.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ class Camera {
119119
await videoElement.play();
120120
}
121121

122+
/// Pauses the camera stream on the current frame.
123+
void pause() async {
124+
videoElement.pause();
125+
}
126+
122127
/// Stops the camera stream and resets the camera source.
123128
void stop() {
124129
final tracks = videoElement.srcObject?.getTracks();

packages/camera/camera_web/lib/src/camera_web.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,27 @@ class CameraPlugin extends CameraPlatform {
517517
}
518518
}
519519

520+
@override
521+
Future<void> pausePreview(int cameraId) async {
522+
try {
523+
getCamera(cameraId).pause();
524+
} on html.DomException catch (e) {
525+
throw PlatformException(code: e.name, message: e.message);
526+
}
527+
}
528+
529+
@override
530+
Future<void> resumePreview(int cameraId) async {
531+
try {
532+
await getCamera(cameraId).play();
533+
} on html.DomException catch (e) {
534+
throw PlatformException(code: e.name, message: e.message);
535+
} on CameraWebException catch (e) {
536+
_addCameraErrorEvent(e);
537+
throw PlatformException(code: e.code.toString(), message: e.description);
538+
}
539+
}
540+
520541
@override
521542
Widget buildPreview(int cameraId) {
522543
return HtmlElementView(

packages/camera/camera_web/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ flutter:
2121
fileName: camera_web.dart
2222

2323
dependencies:
24-
camera_platform_interface: ^2.0.1
24+
camera_platform_interface: ^2.1.0
2525
flutter:
2626
sdk: flutter
2727
flutter_web_plugins:

0 commit comments

Comments
 (0)