diff --git a/lib/src/states/camera_picker_state.dart b/lib/src/states/camera_picker_state.dart index a4b368d..6a73f2c 100644 --- a/lib/src/states/camera_picker_state.dart +++ b/lib/src/states/camera_picker_state.dart @@ -201,6 +201,13 @@ class CameraPickerState extends State CameraPickerTextDelegate get textDelegate => Constants.textDelegate; + /// If controller methods were failed to called for camera descriptions, + /// it will be recorded as invalid and never gets called again. + /// + /// 如果相机实例的某个方法调用失败,该方法会被记录并且不会再被调用。 + final invalidControllerMethods = >{}; + bool retriedAfterInvalidInitialize = false; + @override void initState() { super.initState(); @@ -265,6 +272,31 @@ class CameraPickerState extends State return scale; } + /// Wraps [CameraController] methods with invalid controls. + /// Returns the [fallback] value if invalid and [T] is non-void. + /// + /// 对于 [CameraController] 的方法增加是否无效的控制。 + /// 如果 [T] 是非 void 且方法无效,返回 [fallback]。 + Future wrapControllerMethod( + String key, + Future Function() method, { + CameraDescription? description, + VoidCallback? onError, + T? fallback, + }) async { + description ??= currentCamera; + if (invalidControllerMethods[description]!.contains(key)) { + return fallback!; + } + try { + return await method(); + } catch (e) { + invalidControllerMethods[description]!.add(key); + onError?.call(); + rethrow; + } + } + /// Initialize cameras instances. /// 初始化相机实例 Future initCameras([CameraDescription? cameraDescription]) async { @@ -325,8 +357,10 @@ class CameraPickerState extends State index = currentCameraIndex; } // Initialize the controller with the given resolution preset. + final description = cameraDescription ?? cameras[index]; + invalidControllerMethods[description] ??= {}; final CameraController newController = CameraController( - cameraDescription ?? cameras[index], + description, pickerConfig.resolutionPreset, enableAudio: enableAudio, imageFormatGroup: pickerConfig.imageFormatGroup, @@ -352,44 +386,79 @@ class CameraPickerState extends State ..start(); await Future.wait( >[ - newController - .getExposureOffsetStepSize() - .then((double value) => exposureStep = value) - .catchError((_) => exposureStep), - newController - .getMaxExposureOffset() - .then((double value) => maxAvailableExposureOffset = value) - .catchError((_) => maxAvailableExposureOffset), - newController - .getMinExposureOffset() - .then((double value) => minAvailableExposureOffset = value) - .catchError((_) => minAvailableExposureOffset), - newController - .getMaxZoomLevel() - .then((double value) => maxAvailableZoom = value) - .catchError((_) => maxAvailableZoom), - newController - .getMinZoomLevel() - .then((double value) => minAvailableZoom = value) - .catchError((_) => minAvailableZoom), + wrapControllerMethod( + 'getExposureOffsetStepSize', + () => newController.getExposureOffsetStepSize(), + description: description, + fallback: exposureStep, + ).then((value) => exposureStep = value), + wrapControllerMethod( + 'getMaxExposureOffset', + () => newController.getMaxExposureOffset(), + description: description, + fallback: maxAvailableExposureOffset, + ).then((value) => maxAvailableExposureOffset = value), + wrapControllerMethod( + 'getMinExposureOffset', + () => newController.getMinExposureOffset(), + description: description, + fallback: minAvailableExposureOffset, + ).then((value) => minAvailableExposureOffset = value), + wrapControllerMethod( + 'getMaxZoomLevel', + () => newController.getMaxZoomLevel(), + description: description, + fallback: maxAvailableZoom, + ).then((value) => maxAvailableZoom = value), + wrapControllerMethod( + 'getMinZoomLevel', + () => newController.getMinZoomLevel(), + description: description, + fallback: minAvailableZoom, + ).then((value) => minAvailableZoom = value), + wrapControllerMethod( + 'getMinZoomLevel', + () => newController.getMinZoomLevel(), + description: description, + fallback: minAvailableZoom, + ).then((value) => minAvailableZoom = value), if (pickerConfig.lockCaptureOrientation != null) - newController - .lockCaptureOrientation(pickerConfig.lockCaptureOrientation) - .catchError((_) {}), - if (pickerConfig.preferredFlashMode != FlashMode.auto) - newController - .setFlashMode(pickerConfig.preferredFlashMode) - .catchError((_) { - validFlashModes[currentCamera] - ?.remove(pickerConfig.preferredFlashMode); - }), + wrapControllerMethod( + 'lockCaptureOrientation', + () => newController.lockCaptureOrientation( + pickerConfig.lockCaptureOrientation, + ), + description: description, + ), + // Do not set flash modes for the front camera. + if (description.lensDirection != CameraLensDirection.front && + pickerConfig.preferredFlashMode != FlashMode.auto) + wrapControllerMethod( + 'setFlashMode', + () => newController.setFlashMode( + pickerConfig.preferredFlashMode, + ), + description: description, + onError: () { + validFlashModes[description]?.remove( + pickerConfig.preferredFlashMode, + ); + }, + ), ], + eagerError: false, ); stopwatch.stop(); realDebugPrint("${stopwatch.elapsed} for config's update."); innerController = newController; } catch (e, s) { handleErrorWithHandler(e, pickerConfig.onError, s: s); + if (!retriedAfterInvalidInitialize) { + retriedAfterInvalidInitialize = true; + Future.delayed(Duration.zero, initCameras); + } else { + retriedAfterInvalidInitialize = false; + } } finally { safeSetState(() {}); } @@ -582,9 +651,16 @@ class CameraPickerState extends State isFocusPointFadeOut.value = false; try { await Future.wait(>[ + wrapControllerMethod( + 'setExposureOffset', + () => controller.setExposureOffset(0), + ), controller.setExposureOffset(0), if (controller.value.exposureMode == ExposureMode.locked) - controller.setExposureMode(ExposureMode.auto), + wrapControllerMethod( + 'setExposureMode', + () => controller.setExposureMode(ExposureMode.auto), + ), ]); final Offset newPoint = lastExposurePoint.value!.scale( 1 / constraints.maxWidth, @@ -604,6 +680,7 @@ class CameraPickerState extends State /// Update the exposure offset using the exposure controller. /// 使用曝光控制器更新曝光值 Future updateExposureOffset(double value) async { + final previousSliderOffsetValue = currentExposureSliderOffset.value; currentExposureSliderOffset.value = value; // Normalize the new exposure value if exposures have steps. if (exposureStep > 0) { @@ -621,18 +698,26 @@ class CameraPickerState extends State value > maxAvailableExposureOffset) { return; } + final previousOffsetValue = currentExposureOffset.value; currentExposureOffset.value = value; + bool hasError = false; try { realDebugPrint('Updating the exposure offset value: $value'); // Use [CameraPlatform] explicitly to reduce channel calls. - await CameraPlatform.instance.setExposureOffset( - controller.cameraId, - value, + await wrapControllerMethod( + 'setExposureOffset', + () => CameraPlatform.instance.setExposureOffset( + controller.cameraId, + value, + ), ); } catch (e, s) { handleErrorWithHandler(e, pickerConfig.onError, s: s); + hasError = true; + currentExposureSliderOffset.value = previousSliderOffsetValue; + currentExposureOffset.value = previousOffsetValue; } - if (!isFocusPointDisplays.value) { + if (!hasError && !isFocusPointDisplays.value) { isFocusPointDisplays.value = true; } restartExposurePointDisplayTimer(); @@ -693,11 +778,17 @@ class CameraPickerState extends State final ExposureMode previousExposureMode = controller.value.exposureMode; try { await Future.wait(>[ - controller.setFocusMode(FocusMode.locked).catchError((e, s) { + wrapControllerMethod( + 'setFocusMode', + () => controller.setFocusMode(FocusMode.locked), + ).catchError((e, s) { handleErrorWithHandler(e, pickerConfig.onError, s: s); }), if (previousExposureMode != ExposureMode.locked) - controller.setExposureMode(ExposureMode.locked).catchError((e, s) { + wrapControllerMethod( + 'setExposureMode', + () => controller.setExposureMode(ExposureMode.locked), + ).catchError((e, s) { handleErrorWithHandler(e, pickerConfig.onError, s: s); }), ]); @@ -719,9 +810,15 @@ class CameraPickerState extends State return; } await Future.wait(>[ - controller.setFocusMode(FocusMode.auto), + wrapControllerMethod( + 'setFocusMode', + () => controller.setFocusMode(FocusMode.auto), + ), if (previousExposureMode != ExposureMode.locked) - controller.setExposureMode(previousExposureMode), + wrapControllerMethod( + 'setExposureMode', + () => controller.setExposureMode(previousExposureMode), + ), ]); await controller.resumePreview(); } catch (e, s) {