-
Notifications
You must be signed in to change notification settings - Fork 92
Race condition in the default captureControlBuilder #80
Copy link
Copy link
Open
Description
At least on iOS, when hitting the capture button rapidly twice, it will try to take the picture twice which results in
CameraException (CameraException(No camera is streaming images, stopImageStream was called when no camera is streaming images.))
since the camera is off after the first picture has been taken. This is, because the button isn't immediately disabled when clicking it, leading to such race condition.
Anyone who doesn't want their app to crash needs to implement their own custom capture button:
import 'dart:async';
import 'dart:io';
import 'package:access_control/constants/ui_value_keys.dart';
import 'package:access_control/l10n/app_localizations.dart';
import 'package:access_control/ui/commons/trailing_app_bar_button.dart';
import 'package:access_control/ui/style.dart';
import 'package:access_control/util/debug.dart';
import 'package:access_control/xl10n.dart';
import 'package:face_camera/face_camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
// ignore:unused_element
const String _tag = "ui/screens/home_screen";
class FacecamScreen extends StatefulWidget {
const FacecamScreen({
super.key,
required this.materialTitle,
required this.onCapture,
});
final String materialTitle;
final void Function(File? image) onCapture;
@override
State<FacecamScreen> createState() => _FacecamScreenState();
}
class _FacecamScreenState extends State<FacecamScreen> {
late File? _capturedImage = null;
late bool _isCapturing = false;
late FaceCameraController _controller = FaceCameraController(
enableAudio: false,
autoCapture: false,
defaultCameraLens: CameraLens.front,
performanceMode: FaceDetectorMode.accurate,
onCapture: (image) {
setState(() {
this._isCapturing = false;
this._capturedImage = image;
});
},
);
@override
void initState() {
setState(() => unawaited(this._controller.initialize()));
super.initState();
}
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final AppLocalizations l10n = AppLocalizations.of(context)!;
return PlatformScaffold(
appBar: PlatformAppBar(
material: MaterialAppBarData().onPushedRoute(
title: widget.materialTitle,
),
cupertino: CupertinoNavigationBarData().onPushedRoute(),
leading: this._capturedImage != null
? TrailingAppBarButton(
key: const ValueKey(UiValueKeys.facecamDelete),
text: pl10n(context, PlatformI10nKey.delete),
onPressed: () {
unawaited(this._capturedImage!.delete());
setState(() {
this._capturedImage = null;
});
},
color: theme.colorScheme.error,
)
: null,
trailingActions: this._capturedImage != null
? [
TrailingAppBarButton(
key: const ValueKey(UiValueKeys.facecamSave),
text: pl10n(context, PlatformI10nKey.save),
onPressed: () {
widget.onCapture(this._capturedImage);
Navigator.of(context).pop();
},
),
]
: null,
),
body: this._capturedImage != null
? ColoredBox(
color: theme.primaryColor,
child: SizedBox.expand(child: Image.file(this._capturedImage!)),
)
: Stack(
children: [
SmartFaceCamera(
controller: this._controller,
indicatorShape: IndicatorShape.circle,
showCaptureControl:
false, // We use our own button to fix issues
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: EdgeInsetsGeometry.only(bottom: 70),
child: _captureControlWidget(),
),
),
],
),
);
}
Widget _captureControlWidget() {
return IconButton(
icon: CircleAvatar(
radius: 35,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Icon(Icons.camera_alt, size: 35),
),
),
onPressed: () {
if (this._isCapturing) return;
setState(() {
this._isCapturing = true;
});
final Face? face = this._controller.value.detectedFace?.face;
if (this._controller.enableControls &&
face != null &&
face.headEulerAngleY! <= 10 &&
face.headEulerAngleY! >= -10) {
this._controller.captureImage();
} else {
setState(() {
this._isCapturing = false;
});
}
},
);
}
@override
void dispose() {
printd(_tag, "dispose screen");
super.dispose();
}
}Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels