debug_camera.dart 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041
  1. import 'dart:async';
  2. import 'package:camera/camera.dart';
  3. import 'package:flutter/foundation.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter/scheduler.dart';
  6. class CameraTestPage extends StatefulWidget {
  7. /// Default Constructor
  8. const CameraTestPage({Key? key, required this.cameras}) : super(key: key);
  9. final List<CameraDescription> cameras;
  10. @override
  11. State<CameraTestPage> createState() {
  12. return _CameraTestPageState();
  13. }
  14. }
  15. /// Returns a suitable camera icon for [direction].
  16. IconData getCameraLensIcon(CameraLensDirection direction) {
  17. switch (direction) {
  18. case CameraLensDirection.back:
  19. return Icons.camera_rear;
  20. case CameraLensDirection.front:
  21. return Icons.camera_front;
  22. case CameraLensDirection.external:
  23. return Icons.camera;
  24. }
  25. // This enum is from a different package, so a new value could be added at
  26. // any time. The example should keep working if that happens.
  27. // ignore: dead_code
  28. return Icons.camera;
  29. }
  30. void _logError(String code, String? message) {
  31. // ignore: avoid_print
  32. print('Error: $code${message == null ? '' : '\nError Message: $message'}');
  33. }
  34. class _CameraTestPageState extends State<CameraTestPage>
  35. with WidgetsBindingObserver, TickerProviderStateMixin {
  36. CameraController? controller;
  37. XFile? imageFile;
  38. XFile? videoFile;
  39. // VideoPlayerController? videoController;
  40. VoidCallback? videoPlayerListener;
  41. bool enableAudio = true;
  42. double _minAvailableExposureOffset = 0.0;
  43. double _maxAvailableExposureOffset = 0.0;
  44. double _currentExposureOffset = 0.0;
  45. late AnimationController _flashModeControlRowAnimationController;
  46. late Animation<double> _flashModeControlRowAnimation;
  47. late AnimationController _exposureModeControlRowAnimationController;
  48. late Animation<double> _exposureModeControlRowAnimation;
  49. late AnimationController _focusModeControlRowAnimationController;
  50. late Animation<double> _focusModeControlRowAnimation;
  51. double _minAvailableZoom = 1.0;
  52. double _maxAvailableZoom = 1.0;
  53. double _currentScale = 1.0;
  54. double _baseScale = 1.0;
  55. // Counting pointers (number of user fingers on screen)
  56. int _pointers = 0;
  57. @override
  58. void initState() {
  59. super.initState();
  60. WidgetsBinding.instance.addObserver(this);
  61. _flashModeControlRowAnimationController = AnimationController(
  62. duration: const Duration(milliseconds: 300),
  63. vsync: this,
  64. );
  65. _flashModeControlRowAnimation = CurvedAnimation(
  66. parent: _flashModeControlRowAnimationController,
  67. curve: Curves.easeInCubic,
  68. );
  69. _exposureModeControlRowAnimationController = AnimationController(
  70. duration: const Duration(milliseconds: 300),
  71. vsync: this,
  72. );
  73. _exposureModeControlRowAnimation = CurvedAnimation(
  74. parent: _exposureModeControlRowAnimationController,
  75. curve: Curves.easeInCubic,
  76. );
  77. _focusModeControlRowAnimationController = AnimationController(
  78. duration: const Duration(milliseconds: 300),
  79. vsync: this,
  80. );
  81. _focusModeControlRowAnimation = CurvedAnimation(
  82. parent: _focusModeControlRowAnimationController,
  83. curve: Curves.easeInCubic,
  84. );
  85. }
  86. @override
  87. void dispose() {
  88. WidgetsBinding.instance.removeObserver(this);
  89. _flashModeControlRowAnimationController.dispose();
  90. _exposureModeControlRowAnimationController.dispose();
  91. super.dispose();
  92. }
  93. // #docregion AppLifecycle
  94. @override
  95. void didChangeAppLifecycleState(AppLifecycleState state) {
  96. final CameraController? cameraController = controller;
  97. // App state changed before we got the chance to initialize.
  98. if (cameraController == null || !cameraController.value.isInitialized) {
  99. return;
  100. }
  101. if (state == AppLifecycleState.inactive) {
  102. cameraController.dispose();
  103. } else if (state == AppLifecycleState.resumed) {
  104. onNewCameraSelected(cameraController.description);
  105. }
  106. }
  107. // #enddocregion AppLifecycle
  108. @override
  109. Widget build(BuildContext context) {
  110. return Column(
  111. children: <Widget>[
  112. Expanded(
  113. child: Container(
  114. decoration: BoxDecoration(
  115. color: Colors.black,
  116. border: Border.all(
  117. color: controller != null && controller!.value.isRecordingVideo
  118. ? Colors.redAccent
  119. : Colors.grey,
  120. width: 3.0,
  121. ),
  122. ),
  123. child: Padding(
  124. padding: const EdgeInsets.all(1.0),
  125. child: Center(
  126. child: _cameraPreviewWidget(),
  127. ),
  128. ),
  129. ),
  130. ),
  131. _captureControlRowWidget(),
  132. _modeControlRowWidget(),
  133. Padding(
  134. padding: const EdgeInsets.all(5.0),
  135. child: Row(
  136. children: <Widget>[
  137. _cameraTogglesRowWidget(),
  138. // _thumbnailWidget(),
  139. ],
  140. ),
  141. ),
  142. ],
  143. );
  144. }
  145. /// Display the preview from the camera (or a message if the preview is not available).
  146. Widget _cameraPreviewWidget() {
  147. final CameraController? cameraController = controller;
  148. if (cameraController == null || !cameraController.value.isInitialized) {
  149. return const Text(
  150. 'Tap a camera',
  151. style: TextStyle(
  152. color: Colors.white,
  153. fontSize: 24.0,
  154. fontWeight: FontWeight.w900,
  155. ),
  156. );
  157. } else {
  158. return Listener(
  159. onPointerDown: (_) => _pointers++,
  160. onPointerUp: (_) => _pointers--,
  161. child: CameraPreview(
  162. controller!,
  163. child: LayoutBuilder(
  164. builder: (BuildContext context, BoxConstraints constraints) {
  165. return GestureDetector(
  166. behavior: HitTestBehavior.opaque,
  167. onScaleStart: _handleScaleStart,
  168. onScaleUpdate: _handleScaleUpdate,
  169. onTapDown: (TapDownDetails details) =>
  170. onViewFinderTap(details, constraints),
  171. );
  172. }),
  173. ),
  174. );
  175. }
  176. }
  177. void _handleScaleStart(ScaleStartDetails details) {
  178. _baseScale = _currentScale;
  179. }
  180. Future<void> _handleScaleUpdate(ScaleUpdateDetails details) async {
  181. // When there are not exactly two fingers on screen don't scale
  182. if (controller == null || _pointers != 2) {
  183. return;
  184. }
  185. _currentScale = (_baseScale * details.scale)
  186. .clamp(_minAvailableZoom, _maxAvailableZoom);
  187. await controller!.setZoomLevel(_currentScale);
  188. }
  189. /// Display the thumbnail of the captured image or video.
  190. // Widget _thumbnailWidget() {
  191. // final VideoPlayerController? localVideoController = videoController;
  192. // return Expanded(
  193. // child: Align(
  194. // alignment: Alignment.centerRight,
  195. // child: Row(
  196. // mainAxisSize: MainAxisSize.min,
  197. // children: <Widget>[
  198. // if (localVideoController == null && imageFile == null)
  199. // Container()
  200. // else
  201. // SizedBox(
  202. // width: 64.0,
  203. // height: 64.0,
  204. // child: (localVideoController == null)
  205. // ? (
  206. // // The captured image on the web contains a network-accessible URL
  207. // // pointing to a location within the browser. It may be displayed
  208. // // either with Image.network or Image.memory after loading the image
  209. // // bytes to memory.
  210. // kIsWeb
  211. // ? Image.network(imageFile!.path)
  212. // : Image.file(File(imageFile!.path)))
  213. // : Container(
  214. // decoration: BoxDecoration(
  215. // border: Border.all(color: Colors.pink)),
  216. // child: Center(
  217. // child: AspectRatio(
  218. // aspectRatio:
  219. // localVideoController.value.size != null
  220. // ? localVideoController.value.aspectRatio
  221. // : 1.0,
  222. // child: VideoPlayer(localVideoController)),
  223. // ),
  224. // ),
  225. // ),
  226. // ],
  227. // ),
  228. // ),
  229. // );
  230. // }
  231. /// Display a bar with buttons to change the flash and exposure modes
  232. Widget _modeControlRowWidget() {
  233. return Column(
  234. children: <Widget>[
  235. Row(
  236. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  237. children: <Widget>[
  238. IconButton(
  239. icon: const Icon(Icons.flash_on),
  240. color: Colors.blue,
  241. onPressed: controller != null ? onFlashModeButtonPressed : null,
  242. ),
  243. // The exposure and focus mode are currently not supported on the web.
  244. ...!kIsWeb
  245. ? <Widget>[
  246. IconButton(
  247. icon: const Icon(Icons.exposure),
  248. color: Colors.blue,
  249. onPressed: controller != null
  250. ? onExposureModeButtonPressed
  251. : null,
  252. ),
  253. IconButton(
  254. icon: const Icon(Icons.filter_center_focus),
  255. color: Colors.blue,
  256. onPressed:
  257. controller != null ? onFocusModeButtonPressed : null,
  258. )
  259. ]
  260. : <Widget>[],
  261. IconButton(
  262. icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute),
  263. color: Colors.blue,
  264. onPressed: controller != null ? onAudioModeButtonPressed : null,
  265. ),
  266. IconButton(
  267. icon: Icon(controller?.value.isCaptureOrientationLocked ?? false
  268. ? Icons.screen_lock_rotation
  269. : Icons.screen_rotation),
  270. color: Colors.blue,
  271. onPressed: controller != null
  272. ? onCaptureOrientationLockButtonPressed
  273. : null,
  274. ),
  275. ],
  276. ),
  277. _flashModeControlRowWidget(),
  278. _exposureModeControlRowWidget(),
  279. _focusModeControlRowWidget(),
  280. ],
  281. );
  282. }
  283. Widget _flashModeControlRowWidget() {
  284. return SizeTransition(
  285. sizeFactor: _flashModeControlRowAnimation,
  286. child: ClipRect(
  287. child: Row(
  288. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  289. children: <Widget>[
  290. IconButton(
  291. icon: const Icon(Icons.flash_off),
  292. color: controller?.value.flashMode == FlashMode.off
  293. ? Colors.orange
  294. : Colors.blue,
  295. onPressed: controller != null
  296. ? () => onSetFlashModeButtonPressed(FlashMode.off)
  297. : null,
  298. ),
  299. IconButton(
  300. icon: const Icon(Icons.flash_auto),
  301. color: controller?.value.flashMode == FlashMode.auto
  302. ? Colors.orange
  303. : Colors.blue,
  304. onPressed: controller != null
  305. ? () => onSetFlashModeButtonPressed(FlashMode.auto)
  306. : null,
  307. ),
  308. IconButton(
  309. icon: const Icon(Icons.flash_on),
  310. color: controller?.value.flashMode == FlashMode.always
  311. ? Colors.orange
  312. : Colors.blue,
  313. onPressed: controller != null
  314. ? () => onSetFlashModeButtonPressed(FlashMode.always)
  315. : null,
  316. ),
  317. IconButton(
  318. icon: const Icon(Icons.highlight),
  319. color: controller?.value.flashMode == FlashMode.torch
  320. ? Colors.orange
  321. : Colors.blue,
  322. onPressed: controller != null
  323. ? () => onSetFlashModeButtonPressed(FlashMode.torch)
  324. : null,
  325. ),
  326. ],
  327. ),
  328. ),
  329. );
  330. }
  331. Widget _exposureModeControlRowWidget() {
  332. final ButtonStyle styleAuto = TextButton.styleFrom(
  333. // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724
  334. // ignore: deprecated_member_use
  335. primary: controller?.value.exposureMode == ExposureMode.auto
  336. ? Colors.orange
  337. : Colors.blue,
  338. );
  339. final ButtonStyle styleLocked = TextButton.styleFrom(
  340. // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724
  341. // ignore: deprecated_member_use
  342. primary: controller?.value.exposureMode == ExposureMode.locked
  343. ? Colors.orange
  344. : Colors.blue,
  345. );
  346. return SizeTransition(
  347. sizeFactor: _exposureModeControlRowAnimation,
  348. child: ClipRect(
  349. child: Container(
  350. color: Colors.grey.shade50,
  351. child: Column(
  352. children: <Widget>[
  353. const Center(
  354. child: Text('Exposure Mode'),
  355. ),
  356. Row(
  357. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  358. children: <Widget>[
  359. TextButton(
  360. style: styleAuto,
  361. onPressed: controller != null
  362. ? () =>
  363. onSetExposureModeButtonPressed(ExposureMode.auto)
  364. : null,
  365. onLongPress: () {
  366. if (controller != null) {
  367. controller!.setExposurePoint(null);
  368. showInSnackBar('Resetting exposure point');
  369. }
  370. },
  371. child: const Text('AUTO'),
  372. ),
  373. TextButton(
  374. style: styleLocked,
  375. onPressed: controller != null
  376. ? () =>
  377. onSetExposureModeButtonPressed(ExposureMode.locked)
  378. : null,
  379. child: const Text('LOCKED'),
  380. ),
  381. TextButton(
  382. style: styleLocked,
  383. onPressed: controller != null
  384. ? () => controller!.setExposureOffset(0.0)
  385. : null,
  386. child: const Text('RESET OFFSET'),
  387. ),
  388. ],
  389. ),
  390. const Center(
  391. child: Text('Exposure Offset'),
  392. ),
  393. Row(
  394. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  395. children: <Widget>[
  396. Text(_minAvailableExposureOffset.toString()),
  397. Slider(
  398. value: _currentExposureOffset,
  399. min: _minAvailableExposureOffset,
  400. max: _maxAvailableExposureOffset,
  401. label: _currentExposureOffset.toString(),
  402. onChanged: _minAvailableExposureOffset ==
  403. _maxAvailableExposureOffset
  404. ? null
  405. : setExposureOffset,
  406. ),
  407. Text(_maxAvailableExposureOffset.toString()),
  408. ],
  409. ),
  410. ],
  411. ),
  412. ),
  413. ),
  414. );
  415. }
  416. Widget _focusModeControlRowWidget() {
  417. final ButtonStyle styleAuto = TextButton.styleFrom(
  418. // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724
  419. // ignore: deprecated_member_use
  420. primary: controller?.value.focusMode == FocusMode.auto
  421. ? Colors.orange
  422. : Colors.blue,
  423. );
  424. final ButtonStyle styleLocked = TextButton.styleFrom(
  425. // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724
  426. // ignore: deprecated_member_use
  427. primary: controller?.value.focusMode == FocusMode.locked
  428. ? Colors.orange
  429. : Colors.blue,
  430. );
  431. return SizeTransition(
  432. sizeFactor: _focusModeControlRowAnimation,
  433. child: ClipRect(
  434. child: Container(
  435. color: Colors.grey.shade50,
  436. child: Column(
  437. children: <Widget>[
  438. const Center(
  439. child: Text('Focus Mode'),
  440. ),
  441. Row(
  442. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  443. children: <Widget>[
  444. TextButton(
  445. style: styleAuto,
  446. onPressed: controller != null
  447. ? () => onSetFocusModeButtonPressed(FocusMode.auto)
  448. : null,
  449. onLongPress: () {
  450. if (controller != null) {
  451. controller!.setFocusPoint(null);
  452. }
  453. showInSnackBar('Resetting focus point');
  454. },
  455. child: const Text('AUTO'),
  456. ),
  457. TextButton(
  458. style: styleLocked,
  459. onPressed: controller != null
  460. ? () => onSetFocusModeButtonPressed(FocusMode.locked)
  461. : null,
  462. child: const Text('LOCKED'),
  463. ),
  464. ],
  465. ),
  466. ],
  467. ),
  468. ),
  469. ),
  470. );
  471. }
  472. /// Display the control bar with buttons to take pictures and record videos.
  473. Widget _captureControlRowWidget() {
  474. final CameraController? cameraController = controller;
  475. return Row(
  476. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  477. children: <Widget>[
  478. IconButton(
  479. icon: const Icon(Icons.camera_alt),
  480. color: Colors.blue,
  481. onPressed: cameraController != null &&
  482. cameraController.value.isInitialized &&
  483. !cameraController.value.isRecordingVideo
  484. ? onTakePictureButtonPressed
  485. : null,
  486. ),
  487. IconButton(
  488. icon: const Icon(Icons.videocam),
  489. color: Colors.blue,
  490. onPressed: cameraController != null &&
  491. cameraController.value.isInitialized &&
  492. !cameraController.value.isRecordingVideo
  493. ? onVideoRecordButtonPressed
  494. : null,
  495. ),
  496. IconButton(
  497. icon: cameraController != null &&
  498. cameraController.value.isRecordingPaused
  499. ? const Icon(Icons.play_arrow)
  500. : const Icon(Icons.pause),
  501. color: Colors.blue,
  502. onPressed: cameraController != null &&
  503. cameraController.value.isInitialized &&
  504. cameraController.value.isRecordingVideo
  505. ? (cameraController.value.isRecordingPaused)
  506. ? onResumeButtonPressed
  507. : onPauseButtonPressed
  508. : null,
  509. ),
  510. IconButton(
  511. icon: const Icon(Icons.stop),
  512. color: Colors.red,
  513. onPressed: cameraController != null &&
  514. cameraController.value.isInitialized &&
  515. cameraController.value.isRecordingVideo
  516. ? onStopButtonPressed
  517. : null,
  518. ),
  519. IconButton(
  520. icon: const Icon(Icons.pause_presentation),
  521. color:
  522. cameraController != null && cameraController.value.isPreviewPaused
  523. ? Colors.red
  524. : Colors.blue,
  525. onPressed:
  526. cameraController == null ? null : onPausePreviewButtonPressed,
  527. ),
  528. ],
  529. );
  530. }
  531. /// Display a row of toggle to select the camera (or a message if no camera is available).
  532. Widget _cameraTogglesRowWidget() {
  533. final List<Widget> toggles = <Widget>[];
  534. void onChanged(CameraDescription? description) {
  535. if (description == null) {
  536. return;
  537. }
  538. onNewCameraSelected(description);
  539. }
  540. if (widget.cameras.isEmpty) {
  541. SchedulerBinding.instance.addPostFrameCallback((_) async {
  542. showInSnackBar('No camera found.');
  543. });
  544. return const Text('None');
  545. } else {
  546. for (final CameraDescription cameraDescription in widget.cameras) {
  547. toggles.add(
  548. SizedBox(
  549. width: 90.0,
  550. child: RadioListTile<CameraDescription>(
  551. title: Icon(getCameraLensIcon(cameraDescription.lensDirection)),
  552. groupValue: controller?.description,
  553. value: cameraDescription,
  554. onChanged:
  555. controller != null && controller!.value.isRecordingVideo
  556. ? null
  557. : onChanged,
  558. ),
  559. ),
  560. );
  561. }
  562. }
  563. return Row(children: toggles);
  564. }
  565. String timestamp() => DateTime.now().millisecondsSinceEpoch.toString();
  566. void showInSnackBar(String message) {
  567. ScaffoldMessenger.of(context)
  568. .showSnackBar(SnackBar(content: Text(message)));
  569. }
  570. void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) {
  571. if (controller == null) {
  572. return;
  573. }
  574. final CameraController cameraController = controller!;
  575. final Offset offset = Offset(
  576. details.localPosition.dx / constraints.maxWidth,
  577. details.localPosition.dy / constraints.maxHeight,
  578. );
  579. cameraController.setExposurePoint(offset);
  580. cameraController.setFocusPoint(offset);
  581. }
  582. Future<void> onNewCameraSelected(CameraDescription cameraDescription) async {
  583. final CameraController? oldController = controller;
  584. if (oldController != null) {
  585. // `controller` needs to be set to null before getting disposed,
  586. // to avoid a race condition when we use the controller that is being
  587. // disposed. This happens when camera permission dialog shows up,
  588. // which triggers `didChangeAppLifecycleState`, which disposes and
  589. // re-creates the controller.
  590. controller = null;
  591. await oldController.dispose();
  592. }
  593. final CameraController cameraController = CameraController(
  594. cameraDescription,
  595. kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium,
  596. enableAudio: enableAudio,
  597. imageFormatGroup: ImageFormatGroup.jpeg,
  598. );
  599. controller = cameraController;
  600. // If the controller is updated then update the UI.
  601. cameraController.addListener(() {
  602. if (mounted) {
  603. setState(() {});
  604. }
  605. if (cameraController.value.hasError) {
  606. showInSnackBar(
  607. 'Camera error ${cameraController.value.errorDescription}');
  608. }
  609. });
  610. try {
  611. await cameraController.initialize();
  612. await Future.wait(<Future<Object?>>[
  613. // The exposure mode is currently not supported on the web.
  614. ...!kIsWeb
  615. ? <Future<Object?>>[
  616. cameraController.getMinExposureOffset().then(
  617. (double value) => _minAvailableExposureOffset = value),
  618. cameraController
  619. .getMaxExposureOffset()
  620. .then((double value) => _maxAvailableExposureOffset = value)
  621. ]
  622. : <Future<Object?>>[],
  623. cameraController
  624. .getMaxZoomLevel()
  625. .then((double value) => _maxAvailableZoom = value),
  626. cameraController
  627. .getMinZoomLevel()
  628. .then((double value) => _minAvailableZoom = value),
  629. ]);
  630. } on CameraException catch (e) {
  631. switch (e.code) {
  632. case 'CameraAccessDenied':
  633. showInSnackBar('You have denied camera access.');
  634. break;
  635. case 'CameraAccessDeniedWithoutPrompt':
  636. // iOS only
  637. showInSnackBar('Please go to Settings app to enable camera access.');
  638. break;
  639. case 'CameraAccessRestricted':
  640. // iOS only
  641. showInSnackBar('Camera access is restricted.');
  642. break;
  643. case 'AudioAccessDenied':
  644. showInSnackBar('You have denied audio access.');
  645. break;
  646. case 'AudioAccessDeniedWithoutPrompt':
  647. // iOS only
  648. showInSnackBar('Please go to Settings app to enable audio access.');
  649. break;
  650. case 'AudioAccessRestricted':
  651. // iOS only
  652. showInSnackBar('Audio access is restricted.');
  653. break;
  654. default:
  655. _showCameraException(e);
  656. break;
  657. }
  658. }
  659. if (mounted) {
  660. setState(() {});
  661. }
  662. }
  663. void onTakePictureButtonPressed() {
  664. takePicture().then((XFile? file) {
  665. if (mounted) {
  666. setState(() {
  667. imageFile = file;
  668. // videoController?.dispose();
  669. // videoController = null;
  670. });
  671. if (file != null) {
  672. showInSnackBar('Picture saved to ${file.path}');
  673. }
  674. }
  675. });
  676. }
  677. void onFlashModeButtonPressed() {
  678. if (_flashModeControlRowAnimationController.value == 1) {
  679. _flashModeControlRowAnimationController.reverse();
  680. } else {
  681. _flashModeControlRowAnimationController.forward();
  682. _exposureModeControlRowAnimationController.reverse();
  683. _focusModeControlRowAnimationController.reverse();
  684. }
  685. }
  686. void onExposureModeButtonPressed() {
  687. if (_exposureModeControlRowAnimationController.value == 1) {
  688. _exposureModeControlRowAnimationController.reverse();
  689. } else {
  690. _exposureModeControlRowAnimationController.forward();
  691. _flashModeControlRowAnimationController.reverse();
  692. _focusModeControlRowAnimationController.reverse();
  693. }
  694. }
  695. void onFocusModeButtonPressed() {
  696. if (_focusModeControlRowAnimationController.value == 1) {
  697. _focusModeControlRowAnimationController.reverse();
  698. } else {
  699. _focusModeControlRowAnimationController.forward();
  700. _flashModeControlRowAnimationController.reverse();
  701. _exposureModeControlRowAnimationController.reverse();
  702. }
  703. }
  704. void onAudioModeButtonPressed() {
  705. enableAudio = !enableAudio;
  706. if (controller != null) {
  707. onNewCameraSelected(controller!.description);
  708. }
  709. }
  710. Future<void> onCaptureOrientationLockButtonPressed() async {
  711. try {
  712. if (controller != null) {
  713. final CameraController cameraController = controller!;
  714. if (cameraController.value.isCaptureOrientationLocked) {
  715. await cameraController.unlockCaptureOrientation();
  716. showInSnackBar('Capture orientation unlocked');
  717. } else {
  718. await cameraController.lockCaptureOrientation();
  719. showInSnackBar(
  720. 'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}');
  721. }
  722. }
  723. } on CameraException catch (e) {
  724. _showCameraException(e);
  725. }
  726. }
  727. void onSetFlashModeButtonPressed(FlashMode mode) {
  728. setFlashMode(mode).then((_) {
  729. if (mounted) {
  730. setState(() {});
  731. }
  732. showInSnackBar('Flash mode set to ${mode.toString().split('.').last}');
  733. });
  734. }
  735. void onSetExposureModeButtonPressed(ExposureMode mode) {
  736. setExposureMode(mode).then((_) {
  737. if (mounted) {
  738. setState(() {});
  739. }
  740. showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}');
  741. });
  742. }
  743. void onSetFocusModeButtonPressed(FocusMode mode) {
  744. setFocusMode(mode).then((_) {
  745. if (mounted) {
  746. setState(() {});
  747. }
  748. showInSnackBar('Focus mode set to ${mode.toString().split('.').last}');
  749. });
  750. }
  751. void onVideoRecordButtonPressed() {
  752. startVideoRecording().then((_) {
  753. if (mounted) {
  754. setState(() {});
  755. }
  756. });
  757. }
  758. void onStopButtonPressed() {
  759. stopVideoRecording().then((XFile? file) {
  760. if (mounted) {
  761. setState(() {});
  762. }
  763. if (file != null) {
  764. showInSnackBar('Video recorded to ${file.path}');
  765. videoFile = file;
  766. // _startVideoPlayer();
  767. }
  768. });
  769. }
  770. Future<void> onPausePreviewButtonPressed() async {
  771. final CameraController? cameraController = controller;
  772. if (cameraController == null || !cameraController.value.isInitialized) {
  773. showInSnackBar('Error: select a camera first.');
  774. return;
  775. }
  776. if (cameraController.value.isPreviewPaused) {
  777. await cameraController.resumePreview();
  778. } else {
  779. await cameraController.pausePreview();
  780. }
  781. if (mounted) {
  782. setState(() {});
  783. }
  784. }
  785. void onPauseButtonPressed() {
  786. pauseVideoRecording().then((_) {
  787. if (mounted) {
  788. setState(() {});
  789. }
  790. showInSnackBar('Video recording paused');
  791. });
  792. }
  793. void onResumeButtonPressed() {
  794. resumeVideoRecording().then((_) {
  795. if (mounted) {
  796. setState(() {});
  797. }
  798. showInSnackBar('Video recording resumed');
  799. });
  800. }
  801. Future<void> startVideoRecording() async {
  802. final CameraController? cameraController = controller;
  803. if (cameraController == null || !cameraController.value.isInitialized) {
  804. showInSnackBar('Error: select a camera first.');
  805. return;
  806. }
  807. if (cameraController.value.isRecordingVideo) {
  808. // A recording is already started, do nothing.
  809. return;
  810. }
  811. try {
  812. await cameraController.startVideoRecording();
  813. } on CameraException catch (e) {
  814. _showCameraException(e);
  815. return;
  816. }
  817. }
  818. Future<XFile?> stopVideoRecording() async {
  819. final CameraController? cameraController = controller;
  820. if (cameraController == null || !cameraController.value.isRecordingVideo) {
  821. return null;
  822. }
  823. try {
  824. return cameraController.stopVideoRecording();
  825. } on CameraException catch (e) {
  826. _showCameraException(e);
  827. return null;
  828. }
  829. }
  830. Future<void> pauseVideoRecording() async {
  831. final CameraController? cameraController = controller;
  832. if (cameraController == null || !cameraController.value.isRecordingVideo) {
  833. return;
  834. }
  835. try {
  836. await cameraController.pauseVideoRecording();
  837. } on CameraException catch (e) {
  838. _showCameraException(e);
  839. rethrow;
  840. }
  841. }
  842. Future<void> resumeVideoRecording() async {
  843. final CameraController? cameraController = controller;
  844. if (cameraController == null || !cameraController.value.isRecordingVideo) {
  845. return;
  846. }
  847. try {
  848. await cameraController.resumeVideoRecording();
  849. } on CameraException catch (e) {
  850. _showCameraException(e);
  851. rethrow;
  852. }
  853. }
  854. Future<void> setFlashMode(FlashMode mode) async {
  855. if (controller == null) {
  856. return;
  857. }
  858. try {
  859. await controller!.setFlashMode(mode);
  860. } on CameraException catch (e) {
  861. _showCameraException(e);
  862. rethrow;
  863. }
  864. }
  865. Future<void> setExposureMode(ExposureMode mode) async {
  866. if (controller == null) {
  867. return;
  868. }
  869. try {
  870. await controller!.setExposureMode(mode);
  871. } on CameraException catch (e) {
  872. _showCameraException(e);
  873. rethrow;
  874. }
  875. }
  876. Future<void> setExposureOffset(double offset) async {
  877. if (controller == null) {
  878. return;
  879. }
  880. setState(() {
  881. _currentExposureOffset = offset;
  882. });
  883. try {
  884. offset = await controller!.setExposureOffset(offset);
  885. } on CameraException catch (e) {
  886. _showCameraException(e);
  887. rethrow;
  888. }
  889. }
  890. Future<void> setFocusMode(FocusMode mode) async {
  891. if (controller == null) {
  892. return;
  893. }
  894. try {
  895. await controller!.setFocusMode(mode);
  896. } on CameraException catch (e) {
  897. _showCameraException(e);
  898. rethrow;
  899. }
  900. }
  901. // Future<void> _startVideoPlayer() async {
  902. // if (videoFile == null) {
  903. // return;
  904. // }
  905. // final VideoPlayerController vController = kIsWeb
  906. // ? VideoPlayerController.network(videoFile!.path)
  907. // : VideoPlayerController.file(File(videoFile!.path));
  908. // videoPlayerListener = () {
  909. // if (videoController != null && videoController!.value.size != null) {
  910. // // Refreshing the state to update video player with the correct ratio.
  911. // if (mounted) {
  912. // setState(() {});
  913. // }
  914. // videoController!.removeListener(videoPlayerListener!);
  915. // }
  916. // };
  917. // vController.addListener(videoPlayerListener!);
  918. // await vController.setLooping(true);
  919. // await vController.initialize();
  920. // await videoController?.dispose();
  921. // if (mounted) {
  922. // setState(() {
  923. // imageFile = null;
  924. // videoController = vController;
  925. // });
  926. // }
  927. // await vController.play();
  928. // }
  929. Future<XFile?> takePicture() async {
  930. final CameraController? cameraController = controller;
  931. if (cameraController == null || !cameraController.value.isInitialized) {
  932. showInSnackBar('Error: select a camera first.');
  933. return null;
  934. }
  935. if (cameraController.value.isTakingPicture) {
  936. // A capture is already pending, do nothing.
  937. return null;
  938. }
  939. try {
  940. final XFile file = await cameraController.takePicture();
  941. return file;
  942. } on CameraException catch (e) {
  943. _showCameraException(e);
  944. return null;
  945. }
  946. }
  947. void _showCameraException(CameraException e) {
  948. _logError(e.code, e.description);
  949. showInSnackBar('Error: ${e.code}\n${e.description}');
  950. }
  951. }