-
-
Notifications
You must be signed in to change notification settings - Fork 287
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Camera state management #990
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,7 @@ import 'package:google_ml_barcode_scanner/google_ml_barcode_scanner.dart'; | |
import 'package:provider/provider.dart'; | ||
import 'package:smooth_app/data_models/continuous_scan_model.dart'; | ||
import 'package:smooth_app/main.dart'; | ||
import 'package:smooth_app/pages/scan/scanner_overlay.dart'; | ||
import 'package:smooth_app/pages/scan/scanner_state_manager.dart'; | ||
|
||
class MLKitScannerPage extends StatefulWidget { | ||
const MLKitScannerPage({Key? key}) : super(key: key); | ||
|
@@ -19,20 +19,23 @@ class MLKitScannerPage extends StatefulWidget { | |
|
||
class MLKitScannerPageState extends State<MLKitScannerPage> { | ||
BarcodeScanner? barcodeScanner = GoogleMlKit.vision.barcodeScanner(); | ||
CameraLensDirection cameraLensDirection = CameraLensDirection.back; | ||
late ContinuousScanModel _model; | ||
CameraController? _controller; | ||
int _cameraIndex = 0; | ||
CameraLensDirection cameraLensDirection = CameraLensDirection.back; | ||
bool isBusy = false; | ||
bool imageStreamActive = false; | ||
//Used when rebuilding to stop the camera | ||
bool stoppingCamera = false; | ||
|
||
@override | ||
void initState() { | ||
super.initState(); | ||
|
||
//Find the most relevant camera to use if none of these criteria are met, | ||
//the default value of [_cameraIndex] will be used to select the first | ||
//camera in the global cameras list. | ||
// Find the most relevant camera to use if none of these criteria are met, | ||
// the default value of [_cameraIndex] will be used to select the first | ||
// camera in the global cameras list. | ||
// if non matching is found we fall back to the first in the list | ||
// initValue of [_cameraIndex] | ||
if (cameras.any( | ||
(CameraDescription element) => | ||
element.lensDirection == cameraLensDirection && | ||
|
@@ -58,17 +61,28 @@ class MLKitScannerPageState extends State<MLKitScannerPage> { | |
|
||
@override | ||
void dispose() { | ||
_stopImageStream().then( | ||
(_) => _controller?.dispose(), | ||
); | ||
_controller?.dispose(); | ||
super.dispose(); | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
_model = context.watch<ContinuousScanModel>(); | ||
if (_controller == null || _controller!.value.isInitialized == false) { | ||
return const Center(child: CircularProgressIndicator()); | ||
|
||
return LifeCycleManager( | ||
onResume: _startLiveFeed, | ||
onStop: _stopImageStream, | ||
child: _buildScannerWidget(), | ||
); | ||
} | ||
|
||
Widget _buildScannerWidget() { | ||
// Showing the black scanner background + the icon when the scanner is | ||
// loading or stopped | ||
if (_controller == null || | ||
_controller!.value.isInitialized == false || | ||
stoppingCamera) { | ||
return Container(); | ||
} | ||
|
||
final Size size = MediaQuery.of(context).size; | ||
|
@@ -84,55 +98,62 @@ class MLKitScannerPageState extends State<MLKitScannerPage> { | |
scale = 1 / scale; | ||
} | ||
|
||
return Scaffold( | ||
body: ScannerOverlay( | ||
restartCamera: _resumeImageStream, | ||
stopCamera: _stopImageStream, | ||
model: _model, | ||
scannerWidget: Transform.scale( | ||
scale: scale, | ||
child: Center( | ||
child: CameraPreview( | ||
_controller!, | ||
), | ||
), | ||
return Transform.scale( | ||
scale: scale, | ||
child: Center( | ||
key: ValueKey<bool>(stoppingCamera), | ||
child: CameraPreview( | ||
_controller!, | ||
), | ||
), | ||
); | ||
} | ||
|
||
Future<void> _startLiveFeed() async { | ||
stoppingCamera = false; | ||
final CameraDescription camera = cameras[_cameraIndex]; | ||
_controller = CameraController( | ||
|
||
final CameraController cameraController = CameraController( | ||
camera, | ||
ResolutionPreset.high, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. have we tested other resolutions ? does it influence the scanning a lot ? it could alleviate some of the resource concerns There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No not tested in any way, just took high that we have a "high" resolution, we fill nearly the full screen with the preview but not max so that we don't spend too much performance. After all it's just the preview not how much we get from the camera which very likely needs more performance. |
||
enableAudio: false, | ||
); | ||
_controller!.setFocusMode(FocusMode.auto); | ||
_controller!.lockCaptureOrientation(DeviceOrientation.portraitUp); | ||
cameraController.setFocusMode(FocusMode.auto); | ||
cameraController.lockCaptureOrientation(DeviceOrientation.portraitUp); | ||
|
||
_controller!.initialize().then((_) { | ||
if (!mounted) { | ||
return; | ||
_controller = cameraController; | ||
|
||
// If the controller is updated then update the UI. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. out of curiousity, when does the listener fire, in other words, what does "controller is updated" mean? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When I am right this is used to update the page from showing a loading indicator to display the camera preview. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok we should confirm that, because it's rerendering the widget. |
||
cameraController.addListener(() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we keep adding this listener every time There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We only call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we also call |
||
if (mounted) { | ||
setState(() {}); | ||
} | ||
if (cameraController.value.hasError) { | ||
debugPrint(cameraController.value.errorDescription); | ||
} | ||
_controller!.startImageStream(_processCameraImage); | ||
imageStreamActive = true; | ||
setState(() {}); | ||
}); | ||
} | ||
|
||
void _resumeImageStream() { | ||
if (_controller != null && !imageStreamActive) { | ||
_controller!.startImageStream(_processCameraImage); | ||
imageStreamActive = true; | ||
try { | ||
await cameraController.initialize(); | ||
_controller?.startImageStream(_processCameraImage); | ||
} on CameraException catch (e) { | ||
if (kDebugMode) { | ||
// TODO(M123): Show error message | ||
debugPrint(e.toString()); | ||
} | ||
} | ||
|
||
if (mounted) { | ||
setState(() {}); | ||
} | ||
} | ||
|
||
Future<void> _stopImageStream() async { | ||
if (_controller != null) { | ||
await _controller!.stopImageStream(); | ||
imageStreamActive = false; | ||
stoppingCamera = true; | ||
if (mounted) { | ||
setState(() {}); | ||
} | ||
await _controller?.dispose(); | ||
} | ||
|
||
//Convert the [CameraImage] to a [InputImage] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
out of curiosity when is
_startLiveFeed
called?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In initState after checking which camera to use.