Skip to content

Commit 5af8290

Browse files
authored
[camerax] Fixes relistening to onStreamedFrameAvailable's stream behavior (#4511)
Removes incorrect assumption causing image stream to stop emitting data after subscription to stream is canceled and then the stream is listened to again. Fixes flutter/flutter#130005.
1 parent 1fd191e commit 5af8290

File tree

4 files changed

+58
-11
lines changed

4 files changed

+58
-11
lines changed

packages/camera/camera_android_camerax/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.5.0+11
2+
3+
* Fixes issue with image data not being emitted after relistening to stream returned by `onStreamedFrameAvailable`.
4+
15
## 0.5.0+10
26

37
* Implements off, auto, and always flash mode configurations for image capture.

packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -605,12 +605,6 @@ class AndroidCameraCameraX extends CameraPlatform {
605605
/// Configures the [imageAnalysis] instance for image streaming and binds it
606606
/// to camera lifecycle controlled by the [processCameraProvider].
607607
Future<void> _configureAndBindImageAnalysisToLifecycle() async {
608-
if (imageAnalysis != null &&
609-
await processCameraProvider!.isBound(imageAnalysis!)) {
610-
// imageAnalysis already configured and bound to lifecycle.
611-
return;
612-
}
613-
614608
// Create Analyzer that can read image data for image streaming.
615609
final WeakReference<AndroidCameraCameraX> weakThis =
616610
WeakReference<AndroidCameraCameraX>(this);
@@ -648,9 +642,14 @@ class AndroidCameraCameraX extends CameraPlatform {
648642

649643
// TODO(camsim99): Support resolution configuration.
650644
// Defaults to YUV_420_888 image format.
651-
imageAnalysis = createImageAnalysis(null);
645+
imageAnalysis ??= createImageAnalysis(null);
652646
unawaited(imageAnalysis!.setAnalyzer(analyzer));
653647

648+
if (await processCameraProvider!.isBound(imageAnalysis!)) {
649+
// No need to bind imageAnalysis to lifecycle again.
650+
return;
651+
}
652+
654653
// TODO(camsim99): Reset live camera state observers here when
655654
// https://github.com/flutter/packages/pull/3419 lands.
656655
camera = await processCameraProvider!

packages/camera/camera_android_camerax/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: camera_android_camerax
22
description: Android implementation of the camera plugin using the CameraX library.
33
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
5-
version: 0.5.0+10
5+
version: 0.5.0+11
66

77
environment:
88
sdk: ">=2.19.0 <4.0.0"

packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -940,7 +940,47 @@ void main() {
940940
});
941941

942942
test(
943-
'onStreamedFrameAvaiable returns stream that responds expectedly to being listened to',
943+
'onStreamedFrameAvailable emits CameraImageData when listened to after cancelation',
944+
() async {
945+
final FakeAndroidCameraCameraX camera =
946+
FakeAndroidCameraCameraX(shouldCreateDetachedObjectForTesting: true);
947+
final MockProcessCameraProvider mockProcessCameraProvider =
948+
MockProcessCameraProvider();
949+
final MockCamera mockCamera = MockCamera();
950+
const int cameraId = 22;
951+
952+
camera.processCameraProvider = mockProcessCameraProvider;
953+
camera.cameraSelector = MockCameraSelector();
954+
955+
when(mockProcessCameraProvider.bindToLifecycle(any, any))
956+
.thenAnswer((_) => Future<Camera>.value(mockCamera));
957+
when(mockCamera.getCameraInfo())
958+
.thenAnswer((_) => Future<CameraInfo>.value(MockCameraInfo()));
959+
960+
final CameraImageData mockCameraImageData = MockCameraImageData();
961+
final Stream<CameraImageData> imageStream =
962+
camera.onStreamedFrameAvailable(cameraId);
963+
964+
// Listen to image stream.
965+
final StreamSubscription<CameraImageData> imageStreamSubscription =
966+
imageStream.listen((CameraImageData data) {});
967+
968+
// Cancel subscription to image stream.
969+
await imageStreamSubscription.cancel();
970+
final Stream<CameraImageData> imageStream2 =
971+
camera.onStreamedFrameAvailable(cameraId);
972+
973+
// Listen to image stream again.
974+
final StreamQueue<CameraImageData> streamQueue =
975+
StreamQueue<CameraImageData>(imageStream2);
976+
camera.cameraImageDataStreamController!.add(mockCameraImageData);
977+
978+
expect(await streamQueue.next, equals(mockCameraImageData));
979+
await streamQueue.cancel();
980+
});
981+
982+
test(
983+
'onStreamedFrameAvailable returns stream that responds expectedly to being listened to',
944984
() async {
945985
final FakeAndroidCameraCameraX camera =
946986
FakeAndroidCameraCameraX(shouldCreateDetachedObjectForTesting: true);
@@ -963,6 +1003,8 @@ void main() {
9631003
camera.processCameraProvider = mockProcessCameraProvider;
9641004
camera.cameraSelector = mockCameraSelector;
9651005

1006+
when(mockProcessCameraProvider.isBound(camera.mockImageAnalysis))
1007+
.thenAnswer((_) async => Future<bool>.value(false));
9661008
when(mockProcessCameraProvider.bindToLifecycle(
9671009
mockCameraSelector, <UseCase>[camera.mockImageAnalysis]))
9681010
.thenAnswer((_) async => mockCamera);
@@ -989,7 +1031,9 @@ void main() {
9891031
final Analyzer capturedAnalyzer =
9901032
verify(camera.mockImageAnalysis.setAnalyzer(captureAny)).captured.single
9911033
as Analyzer;
992-
verify(mockProcessCameraProvider.bindToLifecycle(
1034+
await untilCalled(
1035+
mockProcessCameraProvider.isBound(camera.mockImageAnalysis));
1036+
await untilCalled(mockProcessCameraProvider.bindToLifecycle(
9931037
mockCameraSelector, <UseCase>[camera.mockImageAnalysis]));
9941038

9951039
await capturedAnalyzer.analyze(mockImageProxy);
@@ -1011,7 +1055,7 @@ void main() {
10111055
});
10121056

10131057
test(
1014-
'onStreamedFrameAvaiable returns stream that responds expectedly to being canceled',
1058+
'onStreamedFrameAvailable returns stream that responds expectedly to being canceled',
10151059
() async {
10161060
final FakeAndroidCameraCameraX camera =
10171061
FakeAndroidCameraCameraX(shouldCreateDetachedObjectForTesting: true);

0 commit comments

Comments
 (0)