@@ -30,6 +30,26 @@ const Duration _kDuration = Duration(milliseconds: 300);
30
30
31
31
class CameraPickerState extends State <CameraPicker >
32
32
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
+
33
53
/// The [Duration] for record detection. (200ms)
34
54
/// 检测是否开始录制的时长 (200毫秒)
35
55
final Duration recordDetectDuration = const Duration (milliseconds: 200 );
@@ -47,19 +67,6 @@ class CameraPickerState extends State<CameraPicker>
47
67
final ValueNotifier <bool > isFocusPointDisplays = ValueNotifier <bool >(false );
48
68
final ValueNotifier <bool > isFocusPointFadeOut = ValueNotifier <bool >(false );
49
69
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
-
63
70
/// Current exposure offset.
64
71
/// 当前曝光值
65
72
final ValueNotifier <double > currentExposureOffset = ValueNotifier <double >(0 );
@@ -253,8 +260,8 @@ class CameraPickerState extends State<CameraPicker>
253
260
@override
254
261
void didChangeAppLifecycleState (AppLifecycleState state) {
255
262
final CameraController ? c = innerController;
256
- if (state == AppLifecycleState .resumed) {
257
- initCameras (currentCamera);
263
+ if (state == AppLifecycleState .resumed && ! accessDenied ) {
264
+ initCameras (cameraDescription : currentCamera);
258
265
} else if (c == null || ! c.value.isInitialized) {
259
266
// App state changed before we got the chance to initialize.
260
267
return ;
@@ -319,34 +326,43 @@ class CameraPickerState extends State<CameraPicker>
319
326
320
327
/// Initialize cameras instances.
321
328
/// 初始化相机实例
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 (() {});
350
366
// When the [cameraDescription] is null, which means this is the first
351
367
// time initializing cameras, so available cameras should be fetched.
352
368
if (cameraDescription == null ) {
@@ -388,12 +404,13 @@ class CameraPickerState extends State<CameraPicker>
388
404
enableAudio: enableAudio,
389
405
imageFormatGroup: pickerConfig.imageFormatGroup,
390
406
);
391
-
392
407
try {
393
408
final Stopwatch stopwatch = Stopwatch ()..start ();
394
409
await newController.initialize ();
395
410
stopwatch.stop ();
396
- realDebugPrint ("${stopwatch .elapsed } for controller's initialization." );
411
+ realDebugPrint (
412
+ "${stopwatch .elapsed } for controller's initialization." ,
413
+ );
397
414
// Call recording preparation first.
398
415
if (shouldPrepareForVideoRecording) {
399
416
stopwatch
@@ -474,18 +491,33 @@ class CameraPickerState extends State<CameraPicker>
474
491
stopwatch.stop ();
475
492
realDebugPrint ("${stopwatch .elapsed } for config's update." );
476
493
innerController = newController;
494
+ lock.complete ();
477
495
} 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
+ }
482
510
} else {
483
- retriedAfterInvalidInitialize = false ;
511
+ lock. completeError (e, s) ;
484
512
}
485
- } finally {
486
- safeSetState (() {});
487
513
}
488
514
});
515
+ return lock.future.catchError ((e, s) {
516
+ handleErrorWithHandler (e, s, pickerConfig.onError);
517
+ }).whenComplete (() {
518
+ initializeLock = null ;
519
+ safeSetState (() {});
520
+ });
489
521
}
490
522
491
523
/// Starts to listen on accelerometer events.
@@ -569,7 +601,7 @@ class CameraPickerState extends State<CameraPicker>
569
601
if (currentCameraIndex == cameras.length) {
570
602
currentCameraIndex = 0 ;
571
603
}
572
- initCameras (currentCamera);
604
+ initCameras (cameraDescription : currentCamera);
573
605
}
574
606
575
607
/// Obtain the next camera description for semantics.
0 commit comments