[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 https://github.com/flutter/flutter/issues/130005.
This commit is contained in:
Camille Simon
2023-07-18 13:38:30 -07:00
committed by GitHub
parent 1fd191e3cb
commit 5af829020c
4 changed files with 58 additions and 11 deletions

View File

@ -1,3 +1,7 @@
## 0.5.0+11
* Fixes issue with image data not being emitted after relistening to stream returned by `onStreamedFrameAvailable`.
## 0.5.0+10
* Implements off, auto, and always flash mode configurations for image capture.

View File

@ -605,12 +605,6 @@ class AndroidCameraCameraX extends CameraPlatform {
/// Configures the [imageAnalysis] instance for image streaming and binds it
/// to camera lifecycle controlled by the [processCameraProvider].
Future<void> _configureAndBindImageAnalysisToLifecycle() async {
if (imageAnalysis != null &&
await processCameraProvider!.isBound(imageAnalysis!)) {
// imageAnalysis already configured and bound to lifecycle.
return;
}
// Create Analyzer that can read image data for image streaming.
final WeakReference<AndroidCameraCameraX> weakThis =
WeakReference<AndroidCameraCameraX>(this);
@ -648,9 +642,14 @@ class AndroidCameraCameraX extends CameraPlatform {
// TODO(camsim99): Support resolution configuration.
// Defaults to YUV_420_888 image format.
imageAnalysis = createImageAnalysis(null);
imageAnalysis ??= createImageAnalysis(null);
unawaited(imageAnalysis!.setAnalyzer(analyzer));
if (await processCameraProvider!.isBound(imageAnalysis!)) {
// No need to bind imageAnalysis to lifecycle again.
return;
}
// TODO(camsim99): Reset live camera state observers here when
// https://github.com/flutter/packages/pull/3419 lands.
camera = await processCameraProvider!

View File

@ -2,7 +2,7 @@ name: camera_android_camerax
description: Android implementation of the camera plugin using the CameraX library.
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.5.0+10
version: 0.5.0+11
environment:
sdk: ">=2.19.0 <4.0.0"

View File

@ -940,7 +940,47 @@ void main() {
});
test(
'onStreamedFrameAvaiable returns stream that responds expectedly to being listened to',
'onStreamedFrameAvailable emits CameraImageData when listened to after cancelation',
() async {
final FakeAndroidCameraCameraX camera =
FakeAndroidCameraCameraX(shouldCreateDetachedObjectForTesting: true);
final MockProcessCameraProvider mockProcessCameraProvider =
MockProcessCameraProvider();
final MockCamera mockCamera = MockCamera();
const int cameraId = 22;
camera.processCameraProvider = mockProcessCameraProvider;
camera.cameraSelector = MockCameraSelector();
when(mockProcessCameraProvider.bindToLifecycle(any, any))
.thenAnswer((_) => Future<Camera>.value(mockCamera));
when(mockCamera.getCameraInfo())
.thenAnswer((_) => Future<CameraInfo>.value(MockCameraInfo()));
final CameraImageData mockCameraImageData = MockCameraImageData();
final Stream<CameraImageData> imageStream =
camera.onStreamedFrameAvailable(cameraId);
// Listen to image stream.
final StreamSubscription<CameraImageData> imageStreamSubscription =
imageStream.listen((CameraImageData data) {});
// Cancel subscription to image stream.
await imageStreamSubscription.cancel();
final Stream<CameraImageData> imageStream2 =
camera.onStreamedFrameAvailable(cameraId);
// Listen to image stream again.
final StreamQueue<CameraImageData> streamQueue =
StreamQueue<CameraImageData>(imageStream2);
camera.cameraImageDataStreamController!.add(mockCameraImageData);
expect(await streamQueue.next, equals(mockCameraImageData));
await streamQueue.cancel();
});
test(
'onStreamedFrameAvailable returns stream that responds expectedly to being listened to',
() async {
final FakeAndroidCameraCameraX camera =
FakeAndroidCameraCameraX(shouldCreateDetachedObjectForTesting: true);
@ -963,6 +1003,8 @@ void main() {
camera.processCameraProvider = mockProcessCameraProvider;
camera.cameraSelector = mockCameraSelector;
when(mockProcessCameraProvider.isBound(camera.mockImageAnalysis))
.thenAnswer((_) async => Future<bool>.value(false));
when(mockProcessCameraProvider.bindToLifecycle(
mockCameraSelector, <UseCase>[camera.mockImageAnalysis]))
.thenAnswer((_) async => mockCamera);
@ -989,7 +1031,9 @@ void main() {
final Analyzer capturedAnalyzer =
verify(camera.mockImageAnalysis.setAnalyzer(captureAny)).captured.single
as Analyzer;
verify(mockProcessCameraProvider.bindToLifecycle(
await untilCalled(
mockProcessCameraProvider.isBound(camera.mockImageAnalysis));
await untilCalled(mockProcessCameraProvider.bindToLifecycle(
mockCameraSelector, <UseCase>[camera.mockImageAnalysis]));
await capturedAnalyzer.analyze(mockImageProxy);
@ -1011,7 +1055,7 @@ void main() {
});
test(
'onStreamedFrameAvaiable returns stream that responds expectedly to being canceled',
'onStreamedFrameAvailable returns stream that responds expectedly to being canceled',
() async {
final FakeAndroidCameraCameraX camera =
FakeAndroidCameraCameraX(shouldCreateDetachedObjectForTesting: true);