@@ -30,6 +30,26 @@ const Duration _kDuration = Duration(milliseconds: 300);
3030
3131class CameraPickerState extends State <CameraPicker >
3232 with WidgetsBindingObserver {
33+ /// The controller for the current camera.
34+ /// 当前相机实例的控制器
35+ CameraController get controller => innerController! ;
36+ CameraController ? innerController;
37+
38+ /// Whether the access to the camera or the audio session
39+ /// has been denied by the platform.
40+ bool accessDenied = false ;
41+
42+ /// Available cameras.
43+ /// 可用的相机实例
44+ late List <CameraDescription > cameras;
45+
46+ /// Whether the controller is handling method calls.
47+ /// 相机控制器是否在处理方法调用
48+ bool isControllerBusy = false ;
49+
50+ /// A [Completer] lock to keep the initialization only runs once at a time.
51+ Completer <void >? initializeLock;
52+
3353 /// The [Duration] for record detection. (200ms)
3454 /// 检测是否开始录制的时长 (200毫秒)
3555 final Duration recordDetectDuration = const Duration (milliseconds: 200 );
@@ -47,19 +67,6 @@ class CameraPickerState extends State<CameraPicker>
4767 final ValueNotifier <bool > isFocusPointDisplays = ValueNotifier <bool >(false );
4868 final ValueNotifier <bool > isFocusPointFadeOut = ValueNotifier <bool >(false );
4969
50- /// The controller for the current camera.
51- /// 当前相机实例的控制器
52- CameraController get controller => innerController! ;
53- CameraController ? innerController;
54-
55- /// Available cameras.
56- /// 可用的相机实例
57- late List <CameraDescription > cameras;
58-
59- /// Whether the controller is handling method calls.
60- /// 相机控制器是否在处理方法调用
61- bool isControllerBusy = false ;
62-
6370 /// Current exposure offset.
6471 /// 当前曝光值
6572 final ValueNotifier <double > currentExposureOffset = ValueNotifier <double >(0 );
@@ -253,8 +260,8 @@ class CameraPickerState extends State<CameraPicker>
253260 @override
254261 void didChangeAppLifecycleState (AppLifecycleState state) {
255262 final CameraController ? c = innerController;
256- if (state == AppLifecycleState .resumed) {
257- initCameras (currentCamera);
263+ if (state == AppLifecycleState .resumed && ! accessDenied ) {
264+ initCameras (cameraDescription : currentCamera);
258265 } else if (c == null || ! c.value.isInitialized) {
259266 // App state changed before we got the chance to initialize.
260267 return ;
@@ -319,34 +326,43 @@ class CameraPickerState extends State<CameraPicker>
319326
320327 /// Initialize cameras instances.
321328 /// 初始化相机实例
322- Future <void > initCameras ([CameraDescription ? cameraDescription]) async {
323- // Save the current controller to a local variable.
324- final CameraController ? c = innerController;
325- // Dispose at last to avoid disposed usage with assertions.
326- if (c != null ) {
327- innerController = null ;
328- await c.dispose ();
329- }
330- // Then request a new frame to unbind the controller from elements.
331- safeSetState (() {
332- maxAvailableZoom = 1 ;
333- minAvailableZoom = 1 ;
334- currentZoom = 1 ;
335- baseZoom = 1 ;
336- // Meanwhile, cancel the existed exposure point and mode display.
337- exposurePointDisplayTimer? .cancel ();
338- exposureModeDisplayTimer? .cancel ();
339- exposureFadeOutTimer? .cancel ();
340- isFocusPointDisplays.value = false ;
341- isFocusPointFadeOut.value = false ;
342- lastExposurePoint.value = null ;
343- currentExposureOffset.value = 0 ;
344- currentExposureSliderOffset.value = 0 ;
345- lockedCaptureOrientation = pickerConfig.lockCaptureOrientation;
346- });
347- // **IMPORTANT**: Push methods into a post frame callback, which ensures the
348- // controller has already unbind from widgets.
349- ambiguate (WidgetsBinding .instance)? .addPostFrameCallback ((_) async {
329+ Future <void > initCameras ({
330+ CameraDescription ? cameraDescription,
331+ bool ignoreLocks = false ,
332+ }) {
333+ if (initializeLock != null && ! ignoreLocks) {
334+ return initializeLock! .future;
335+ }
336+ final lock = ignoreLocks ? initializeLock! : Completer <void >();
337+ if (ignoreLocks) {
338+ initializeLock = lock;
339+ }
340+ Future (() async {
341+ // Save the current controller to a local variable.
342+ final CameraController ? c = innerController;
343+ // Dispose at last to avoid disposed usage with assertions.
344+ if (c != null ) {
345+ innerController = null ;
346+ await c.dispose ();
347+ }
348+ // Then request a new frame to unbind the controller from elements.
349+ safeSetState (() {
350+ maxAvailableZoom = 1 ;
351+ minAvailableZoom = 1 ;
352+ currentZoom = 1 ;
353+ baseZoom = 1 ;
354+ // Meanwhile, cancel the existed exposure point and mode display.
355+ exposurePointDisplayTimer? .cancel ();
356+ exposureModeDisplayTimer? .cancel ();
357+ exposureFadeOutTimer? .cancel ();
358+ isFocusPointDisplays.value = false ;
359+ isFocusPointFadeOut.value = false ;
360+ lastExposurePoint.value = null ;
361+ currentExposureOffset.value = 0 ;
362+ currentExposureSliderOffset.value = 0 ;
363+ lockedCaptureOrientation = pickerConfig.lockCaptureOrientation;
364+ });
365+ await Future .microtask (() {});
350366 // When the [cameraDescription] is null, which means this is the first
351367 // time initializing cameras, so available cameras should be fetched.
352368 if (cameraDescription == null ) {
@@ -388,12 +404,13 @@ class CameraPickerState extends State<CameraPicker>
388404 enableAudio: enableAudio,
389405 imageFormatGroup: pickerConfig.imageFormatGroup,
390406 );
391-
392407 try {
393408 final Stopwatch stopwatch = Stopwatch ()..start ();
394409 await newController.initialize ();
395410 stopwatch.stop ();
396- realDebugPrint ("${stopwatch .elapsed } for controller's initialization." );
411+ realDebugPrint (
412+ "${stopwatch .elapsed } for controller's initialization." ,
413+ );
397414 // Call recording preparation first.
398415 if (shouldPrepareForVideoRecording) {
399416 stopwatch
@@ -474,18 +491,33 @@ class CameraPickerState extends State<CameraPicker>
474491 stopwatch.stop ();
475492 realDebugPrint ("${stopwatch .elapsed } for config's update." );
476493 innerController = newController;
494+ lock.complete ();
477495 } catch (e, s) {
478- handleErrorWithHandler (e, s, pickerConfig.onError);
479- if (! retriedAfterInvalidInitialize) {
480- retriedAfterInvalidInitialize = true ;
481- Future .delayed (Duration .zero, initCameras);
496+ accessDenied = e is CameraException && e.code.contains ('Access' );
497+ if (! accessDenied) {
498+ if (! retriedAfterInvalidInitialize) {
499+ retriedAfterInvalidInitialize = true ;
500+ Future .delayed (Duration .zero, () {
501+ initCameras (
502+ cameraDescription: cameraDescription,
503+ ignoreLocks: true ,
504+ );
505+ });
506+ } else {
507+ retriedAfterInvalidInitialize = false ;
508+ lock.completeError (e, s);
509+ }
482510 } else {
483- retriedAfterInvalidInitialize = false ;
511+ lock. completeError (e, s) ;
484512 }
485- } finally {
486- safeSetState (() {});
487513 }
488514 });
515+ return lock.future.catchError ((e, s) {
516+ handleErrorWithHandler (e, s, pickerConfig.onError);
517+ }).whenComplete (() {
518+ initializeLock = null ;
519+ safeSetState (() {});
520+ });
489521 }
490522
491523 /// Starts to listen on accelerometer events.
@@ -569,7 +601,7 @@ class CameraPickerState extends State<CameraPicker>
569601 if (currentCameraIndex == cameras.length) {
570602 currentCameraIndex = 0 ;
571603 }
572- initCameras (currentCamera);
604+ initCameras (cameraDescription : currentCamera);
573605 }
574606
575607 /// Obtain the next camera description for semantics.
0 commit comments