Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
delete irrelevant guards
  • Loading branch information
camsim99 committed Jan 6, 2026
commit d0ef96fa978bcdc2c6bcbcf16c7a78c228eb2a60
Original file line number Diff line number Diff line change
Expand Up @@ -1123,48 +1123,6 @@ class AndroidCameraCameraX extends CameraPlatform {
return;
}

dynamic Function(CameraImageData)? streamCallback = options.streamCallback;
if (!_previewIsPaused) {
// The plugin binds the preview use case to the camera lifecycle when
// createCamera is called, but camera use cases can become limited
// when video recording and displaying a preview concurrently. This logic
// will prioritize attempting to continue displaying the preview,
// stream images, and record video if specified and supported. Otherwise,
// the preview must be paused in order to allow those concurrently. See
// https://developer.android.com/media/camera/camerax/architecture#combine-use-cases
// for more information on supported concurrent camera use cases.
final camera2CameraInfo = Camera2CameraInfo.from(cameraInfo: cameraInfo!);
final cameraInfoSupportedHardwareLevel =
(await camera2CameraInfo.getCameraCharacteristic(
CameraCharacteristics.infoSupportedHardwareLevel,
))!
as InfoSupportedHardwareLevel;

// Handle limited level device restrictions:
final cameraSupportsConcurrentImageCapture =
cameraInfoSupportedHardwareLevel != InfoSupportedHardwareLevel.legacy;
if (!cameraSupportsConcurrentImageCapture) {
// Concurrent preview + video recording + image capture is not supported
// unless the camera device is cameraSupportsHardwareLevelLimited or
// better.
await _unbindUseCaseFromLifecycle(imageCapture!);
}

// Handle level 3 device restrictions:
final cameraSupportsHardwareLevel3 =
cameraInfoSupportedHardwareLevel == InfoSupportedHardwareLevel.level3;
if (!cameraSupportsHardwareLevel3 || streamCallback == null) {
// Concurrent preview + video recording + image streaming is not supported
// unless the camera device is cameraSupportsHardwareLevel3 or better.
streamCallback = null;
await _unbindUseCaseFromLifecycle(imageAnalysis!);
} else {
// If image streaming concurrently with video recording, image capture
// is unsupported.
await _unbindUseCaseFromLifecycle(imageCapture!);
}
}

await _bindUseCaseToLifecycle(videoCapture!, options.cameraId);

// Set target rotation to default CameraX rotation only if capture
Expand Down Expand Up @@ -1194,8 +1152,8 @@ class AndroidCameraCameraX extends CameraPlatform {

recording = await pendingRecording!.start(_videoRecordingEventListener);

if (streamCallback != null) {
onStreamedFrameAvailable(options.cameraId).listen(streamCallback);
if (options.streamCallback != null) {
onStreamedFrameAvailable(options.cameraId).listen(options.streamCallback);
}

// Wait for video recording to start.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6609,231 +6609,6 @@ void main() {
},
);

test(
'startVideoCapturing does not unbind ImageCapture or ImageAnalysis use cases when preview is paused',
() async {
// Set up mocks and constants.
final camera = AndroidCameraCameraX();
final mockPendingRecording = MockPendingRecording();
final mockRecording = MockRecording();
final mockCamera = MockCamera();
final mockCameraInfo = MockCameraInfo();
final mockCamera2CameraInfo = MockCamera2CameraInfo();

// Set directly for test versus calling createCamera.
camera.processCameraProvider = MockProcessCameraProvider();
camera.recorder = MockRecorder();
camera.videoCapture = MockVideoCapture();
camera.cameraSelector = MockCameraSelector();
camera.cameraInfo = MockCameraInfo();
camera.imageAnalysis = MockImageAnalysis();
camera.imageCapture = MockImageCapture();
camera.preview = MockPreview();
camera.enableRecordingAudio = false;

// Ignore setting target rotation for this test; tested seprately.
camera.captureOrientationLocked = true;

// Tell plugin to create detached Observer when camera info updated.
const outputPath = '/temp/REC123.mp4';
PigeonOverrides.analyzer_new =
({required void Function(Analyzer, ImageProxy) analyze}) {
return Analyzer.pigeon_detached(analyze: analyze);
};
GenericsPigeonOverrides.observerNew =
<T>({required void Function(Observer<T>, T) onChanged}) {
return Observer<T>.detached(onChanged: onChanged);
};
PigeonOverrides.camera2CameraInfo_from =
({required dynamic cameraInfo}) => mockCamera2CameraInfo;
PigeonOverrides.systemServicesManager_new =
({
required void Function(SystemServicesManager, String) onCameraError,
}) {
final mockSystemServicesManager = MockSystemServicesManager();
when(
mockSystemServicesManager.getTempFilePath(
camera.videoPrefix,
'.mp4',
),
).thenAnswer((_) async => outputPath);
return mockSystemServicesManager;
};
PigeonOverrides.videoRecordEventListener_new =
({
required void Function(VideoRecordEventListener, VideoRecordEvent)
onEvent,
}) {
return VideoRecordEventListener.pigeon_detached(onEvent: onEvent);
};
PigeonOverrides.cameraCharacteristics_infoSupportedHardwareLevel =
MockCameraCharacteristicsKey();

const cameraId = 97;

// Mock method calls.
when(
camera.recorder!.prepareRecording(outputPath),
).thenAnswer((_) async => mockPendingRecording);
when(
mockPendingRecording.withAudioEnabled(!camera.enableRecordingAudio),
).thenAnswer((_) async => mockPendingRecording);
when(
mockPendingRecording.asPersistentRecording(),
).thenAnswer((_) async => mockPendingRecording);
when(
mockPendingRecording.start(any),
).thenAnswer((_) async => mockRecording);
when(
camera.processCameraProvider!.isBound(camera.videoCapture!),
).thenAnswer((_) async => false);
when(
camera.processCameraProvider!.bindToLifecycle(
camera.cameraSelector!,
<UseCase>[camera.videoCapture!],
),
).thenAnswer((_) async => mockCamera);
when(
mockCamera.getCameraInfo(),
).thenAnswer((_) => Future<CameraInfo>.value(mockCameraInfo));
when(
mockCameraInfo.getCameraState(),
).thenAnswer((_) async => MockLiveCameraState());

await camera.pausePreview(cameraId);

// Simulate video recording being started so startVideoRecording completes.
AndroidCameraCameraX.videoRecordingEventStreamController.add(
VideoRecordEventStart.pigeon_detached(),
);

await camera.startVideoCapturing(const VideoCaptureOptions(cameraId));

verifyNever(
camera.processCameraProvider!.unbind(<UseCase>[camera.imageCapture!]),
);
verifyNever(
camera.processCameraProvider!.unbind(<UseCase>[camera.imageAnalysis!]),
);
},
);

test(
'startVideoCapturing unbinds ImageCapture and ImageAnalysis use cases when running on a legacy hardware device',
() async {
// Set up mocks and constants.
final camera = AndroidCameraCameraX();
final mockPendingRecording = MockPendingRecording();
final mockRecording = MockRecording();
final mockCamera = MockCamera();
final mockCameraInfo = MockCameraInfo();
final mockCamera2CameraInfo = MockCamera2CameraInfo();

// Set directly for test versus calling createCamera.
camera.processCameraProvider = MockProcessCameraProvider();
camera.recorder = MockRecorder();
camera.videoCapture = MockVideoCapture();
camera.cameraSelector = MockCameraSelector();
camera.cameraInfo = MockCameraInfo();
camera.imageAnalysis = MockImageAnalysis();
camera.imageCapture = MockImageCapture();
camera.preview = MockPreview();
camera.enableRecordingAudio = true;

// Ignore setting target rotation for this test; tested seprately.
camera.captureOrientationLocked = true;

// Tell plugin to create detached Observer when camera info updated.
const outputPath = '/temp/REC123.mp4';
PigeonOverrides.analyzer_new =
({required void Function(Analyzer, ImageProxy) analyze}) {
return Analyzer.pigeon_detached(analyze: analyze);
};
GenericsPigeonOverrides.observerNew =
<T>({required void Function(Observer<T>, T) onChanged}) {
return Observer<T>.detached(onChanged: onChanged);
};
PigeonOverrides.camera2CameraInfo_from =
({required dynamic cameraInfo}) => mockCamera2CameraInfo;
PigeonOverrides.systemServicesManager_new =
({
required void Function(SystemServicesManager, String) onCameraError,
}) {
final mockSystemServicesManager = MockSystemServicesManager();
when(
mockSystemServicesManager.getTempFilePath(
camera.videoPrefix,
'.mp4',
),
).thenAnswer((_) async => outputPath);
return mockSystemServicesManager;
};
PigeonOverrides.videoRecordEventListener_new =
({
required void Function(VideoRecordEventListener, VideoRecordEvent)
onEvent,
}) {
return VideoRecordEventListener.pigeon_detached(onEvent: onEvent);
};
PigeonOverrides.cameraCharacteristics_infoSupportedHardwareLevel =
MockCameraCharacteristicsKey();

const cameraId = 44;

// Mock method calls.
when(
camera.recorder!.prepareRecording(outputPath),
).thenAnswer((_) async => mockPendingRecording);
when(
mockPendingRecording.withAudioEnabled(!camera.enableRecordingAudio),
).thenAnswer((_) async => mockPendingRecording);
when(
mockPendingRecording.asPersistentRecording(),
).thenAnswer((_) async => mockPendingRecording);
when(
mockPendingRecording.start(any),
).thenAnswer((_) async => mockRecording);
when(
camera.processCameraProvider!.isBound(camera.videoCapture!),
).thenAnswer((_) async => false);
when(
camera.processCameraProvider!.isBound(camera.imageCapture!),
).thenAnswer((_) async => true);
when(
camera.processCameraProvider!.isBound(camera.imageAnalysis!),
).thenAnswer((_) async => true);
when(
camera.processCameraProvider!.bindToLifecycle(
camera.cameraSelector!,
<UseCase>[camera.videoCapture!],
),
).thenAnswer((_) async => mockCamera);
when(
mockCamera.getCameraInfo(),
).thenAnswer((_) => Future<CameraInfo>.value(mockCameraInfo));
when(
mockCameraInfo.getCameraState(),
).thenAnswer((_) async => MockLiveCameraState());
when(
mockCamera2CameraInfo.getCameraCharacteristic(any),
).thenAnswer((_) async => InfoSupportedHardwareLevel.legacy);

// Simulate video recording being started so startVideoRecording completes.
AndroidCameraCameraX.videoRecordingEventStreamController.add(
VideoRecordEventStart.pigeon_detached(),
);

await camera.startVideoCapturing(const VideoCaptureOptions(cameraId));

verify(
camera.processCameraProvider!.unbind(<UseCase>[camera.imageCapture!]),
);
verify(
camera.processCameraProvider!.unbind(<UseCase>[camera.imageAnalysis!]),
);
},
);

test(
'prepareForVideoRecording does not make any calls involving starting video recording',
() async {
Expand Down