Skip to content

[camera] Proposal to take a picture while VideoRecording is in Progress #96676

Open

Description

@leandroveronezi

I need to take pictures while recording videos. Images could be preview frames.

In the current version, calling the takePicture function during a recording does not return an image.

Activity

  1. as1605 commented on Jan 15, 2022

    @as1605

    Agreed, the behaviour when trying to takePicture while VideoRecording is erratic, on some platforms it works, on some it crashes without giving any helpful error message.
    See

  2. maheshj01 commented on Jan 17, 2022

    @maheshj01
    Member

    Hi @leandroveronezi, Thanks for filing the issue. I tried to capture a image while the video recording is in progress and I see the app crashing. Heres a slightly modified code sample taken from the official example.

    modification is done on line 518 to take picture regardless of recording state.

    IconButton(
    icon: const Icon(Icons.camera_alt),
    color: Colors.blue,
    onPressed:
    cameraController != null && cameraController.value.isInitialized
    // !cameraController.value.isRecordingVideo
    ? onTakePictureButtonPressed
    : null,
    ),

    modified code sample
    import 'dart:async';
    import 'dart:io';
    
    import 'package:camera/camera.dart';
    import 'package:flutter/foundation.dart';
    import 'package:flutter/material.dart';
    import 'package:video_player/video_player.dart';
    
    class CameraExampleHome extends StatefulWidget {
      @override
      _CameraExampleHomeState createState() {
        return _CameraExampleHomeState();
      }
    }
    
    /// Returns a suitable camera icon for [direction].
    IconData getCameraLensIcon(CameraLensDirection direction) {
      switch (direction) {
        case CameraLensDirection.back:
          return Icons.camera_rear;
        case CameraLensDirection.front:
          return Icons.camera_front;
        case CameraLensDirection.external:
          return Icons.camera;
        default:
          throw ArgumentError('Unknown lens direction');
      }
    }
    
    void logError(String code, String? message) {
      if (message != null) {
        print('Error: $code\nError Message: $message');
      } else {
        print('Error: $code');
      }
    }
    
    class _CameraExampleHomeState extends State<CameraExampleHome>
        with WidgetsBindingObserver, TickerProviderStateMixin {
      CameraController? controller;
      XFile? imageFile;
      XFile? videoFile;
      VideoPlayerController? videoController;
      VoidCallback? videoPlayerListener;
      bool enableAudio = true;
      double _minAvailableExposureOffset = 0.0;
      double _maxAvailableExposureOffset = 0.0;
      double _currentExposureOffset = 0.0;
      late AnimationController _flashModeControlRowAnimationController;
      late Animation<double> _flashModeControlRowAnimation;
      late AnimationController _exposureModeControlRowAnimationController;
      late Animation<double> _exposureModeControlRowAnimation;
      late AnimationController _focusModeControlRowAnimationController;
      late Animation<double> _focusModeControlRowAnimation;
      double _minAvailableZoom = 1.0;
      double _maxAvailableZoom = 1.0;
      double _currentScale = 1.0;
      double _baseScale = 1.0;
    
      // Counting pointers (number of user fingers on screen)
      int _pointers = 0;
    
      @override
      void initState() {
        super.initState();
        _ambiguate(WidgetsBinding.instance)?.addObserver(this);
    
        _flashModeControlRowAnimationController = AnimationController(
          duration: const Duration(milliseconds: 300),
          vsync: this,
        );
        _flashModeControlRowAnimation = CurvedAnimation(
          parent: _flashModeControlRowAnimationController,
          curve: Curves.easeInCubic,
        );
        _exposureModeControlRowAnimationController = AnimationController(
          duration: const Duration(milliseconds: 300),
          vsync: this,
        );
        _exposureModeControlRowAnimation = CurvedAnimation(
          parent: _exposureModeControlRowAnimationController,
          curve: Curves.easeInCubic,
        );
        _focusModeControlRowAnimationController = AnimationController(
          duration: const Duration(milliseconds: 300),
          vsync: this,
        );
        _focusModeControlRowAnimation = CurvedAnimation(
          parent: _focusModeControlRowAnimationController,
          curve: Curves.easeInCubic,
        );
      }
    
      @override
      void dispose() {
        _ambiguate(WidgetsBinding.instance)?.removeObserver(this);
        _flashModeControlRowAnimationController.dispose();
        _exposureModeControlRowAnimationController.dispose();
        super.dispose();
      }
    
      @override
      void didChangeAppLifecycleState(AppLifecycleState state) {
        final CameraController? cameraController = controller;
    
        // App state changed before we got the chance to initialize.
        if (cameraController == null || !cameraController.value.isInitialized) {
          return;
        }
    
        if (state == AppLifecycleState.inactive) {
          cameraController.dispose();
        } else if (state == AppLifecycleState.resumed) {
          onNewCameraSelected(cameraController.description);
        }
      }
    
      final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          key: _scaffoldKey,
          appBar: AppBar(
            title: const Text('Camera example'),
          ),
          body: Column(
            children: <Widget>[
              Expanded(
                child: Container(
                  child: Padding(
                    padding: const EdgeInsets.all(1.0),
                    child: Center(
                      child: _cameraPreviewWidget(),
                    ),
                  ),
                  decoration: BoxDecoration(
                    color: Colors.black,
                    border: Border.all(
                      color:
                          controller != null && controller!.value.isRecordingVideo
                              ? Colors.redAccent
                              : Colors.grey,
                      width: 3.0,
                    ),
                  ),
                ),
              ),
              _captureControlRowWidget(),
              _modeControlRowWidget(),
              Padding(
                padding: const EdgeInsets.all(5.0),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.start,
                  children: <Widget>[
                    _cameraTogglesRowWidget(),
                    _thumbnailWidget(),
                  ],
                ),
              ),
            ],
          ),
        );
      }
    
      /// Display the preview from the camera (or a message if the preview is not available).
      Widget _cameraPreviewWidget() {
        final CameraController? cameraController = controller;
    
        if (cameraController == null || !cameraController.value.isInitialized) {
          return const Text(
            'Tap a camera',
            style: TextStyle(
              color: Colors.white,
              fontSize: 24.0,
              fontWeight: FontWeight.w900,
            ),
          );
        } else {
          return Listener(
            onPointerDown: (_) => _pointers++,
            onPointerUp: (_) => _pointers--,
            child: CameraPreview(
              controller!,
              child: LayoutBuilder(
                  builder: (BuildContext context, BoxConstraints constraints) {
                return GestureDetector(
                  behavior: HitTestBehavior.opaque,
                  onScaleStart: _handleScaleStart,
                  onScaleUpdate: _handleScaleUpdate,
                  onTapDown: (details) => onViewFinderTap(details, constraints),
                );
              }),
            ),
          );
        }
      }
    
      void _handleScaleStart(ScaleStartDetails details) {
        _baseScale = _currentScale;
      }
    
      Future<void> _handleScaleUpdate(ScaleUpdateDetails details) async {
        // When there are not exactly two fingers on screen don't scale
        if (controller == null || _pointers != 2) {
          return;
        }
    
        _currentScale = (_baseScale * details.scale)
            .clamp(_minAvailableZoom, _maxAvailableZoom);
    
        await controller!.setZoomLevel(_currentScale);
      }
    
      /// Display the thumbnail of the captured image or video.
      Widget _thumbnailWidget() {
        final VideoPlayerController? localVideoController = videoController;
    
        return Expanded(
          child: Align(
            alignment: Alignment.centerRight,
            child: Row(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                localVideoController == null && imageFile == null
                    ? Container()
                    : SizedBox(
                        child: (localVideoController == null)
                            ? (
                                // The captured image on the web contains a network-accessible URL
                                // pointing to a location within the browser. It may be displayed
                                // either with Image.network or Image.memory after loading the image
                                // bytes to memory.
                                kIsWeb
                                    ? Image.network(imageFile!.path)
                                    : Image.file(File(imageFile!.path)))
                            : Container(
                                child: Center(
                                  child: AspectRatio(
                                      aspectRatio:
                                          localVideoController.value.size != null
                                              ? localVideoController
                                                  .value.aspectRatio
                                              : 1.0,
                                      child: VideoPlayer(localVideoController)),
                                ),
                                decoration: BoxDecoration(
                                    border: Border.all(color: Colors.pink)),
                              ),
                        width: 64.0,
                        height: 64.0,
                      ),
              ],
            ),
          ),
        );
      }
    
      /// Display a bar with buttons to change the flash and exposure modes
      Widget _modeControlRowWidget() {
        return Column(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              mainAxisSize: MainAxisSize.max,
              children: <Widget>[
                IconButton(
                  icon: Icon(Icons.flash_on),
                  color: Colors.blue,
                  onPressed: controller != null ? onFlashModeButtonPressed : null,
                ),
                // The exposure and focus mode are currently not supported on the web.
                ...(!kIsWeb
                    ? [
                        IconButton(
                          icon: Icon(Icons.exposure),
                          color: Colors.blue,
                          onPressed: controller != null
                              ? onExposureModeButtonPressed
                              : null,
                        ),
                        IconButton(
                          icon: Icon(Icons.filter_center_focus),
                          color: Colors.blue,
                          onPressed:
                              controller != null ? onFocusModeButtonPressed : null,
                        )
                      ]
                    : []),
                IconButton(
                  icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute),
                  color: Colors.blue,
                  onPressed: controller != null ? onAudioModeButtonPressed : null,
                ),
                IconButton(
                  icon: Icon(controller?.value.isCaptureOrientationLocked ?? false
                      ? Icons.screen_lock_rotation
                      : Icons.screen_rotation),
                  color: Colors.blue,
                  onPressed: controller != null
                      ? onCaptureOrientationLockButtonPressed
                      : null,
                ),
              ],
            ),
            _flashModeControlRowWidget(),
            _exposureModeControlRowWidget(),
            _focusModeControlRowWidget(),
          ],
        );
      }
    
      Widget _flashModeControlRowWidget() {
        return SizeTransition(
          sizeFactor: _flashModeControlRowAnimation,
          child: ClipRect(
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              mainAxisSize: MainAxisSize.max,
              children: [
                IconButton(
                  icon: Icon(Icons.flash_off),
                  color: controller?.value.flashMode == FlashMode.off
                      ? Colors.orange
                      : Colors.blue,
                  onPressed: controller != null
                      ? () => onSetFlashModeButtonPressed(FlashMode.off)
                      : null,
                ),
                IconButton(
                  icon: Icon(Icons.flash_auto),
                  color: controller?.value.flashMode == FlashMode.auto
                      ? Colors.orange
                      : Colors.blue,
                  onPressed: controller != null
                      ? () => onSetFlashModeButtonPressed(FlashMode.auto)
                      : null,
                ),
                IconButton(
                  icon: Icon(Icons.flash_on),
                  color: controller?.value.flashMode == FlashMode.always
                      ? Colors.orange
                      : Colors.blue,
                  onPressed: controller != null
                      ? () => onSetFlashModeButtonPressed(FlashMode.always)
                      : null,
                ),
                IconButton(
                  icon: Icon(Icons.highlight),
                  color: controller?.value.flashMode == FlashMode.torch
                      ? Colors.orange
                      : Colors.blue,
                  onPressed: controller != null
                      ? () => onSetFlashModeButtonPressed(FlashMode.torch)
                      : null,
                ),
              ],
            ),
          ),
        );
      }
    
      Widget _exposureModeControlRowWidget() {
        final ButtonStyle styleAuto = TextButton.styleFrom(
          primary: controller?.value.exposureMode == ExposureMode.auto
              ? Colors.orange
              : Colors.blue,
        );
        final ButtonStyle styleLocked = TextButton.styleFrom(
          primary: controller?.value.exposureMode == ExposureMode.locked
              ? Colors.orange
              : Colors.blue,
        );
    
        return SizeTransition(
          sizeFactor: _exposureModeControlRowAnimation,
          child: ClipRect(
            child: Container(
              color: Colors.grey.shade50,
              child: Column(
                children: [
                  Center(
                    child: Text("Exposure Mode"),
                  ),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    mainAxisSize: MainAxisSize.max,
                    children: [
                      TextButton(
                        child: Text('AUTO'),
                        style: styleAuto,
                        onPressed: controller != null
                            ? () =>
                                onSetExposureModeButtonPressed(ExposureMode.auto)
                            : null,
                        onLongPress: () {
                          if (controller != null) {
                            controller!.setExposurePoint(null);
                            showInSnackBar('Resetting exposure point');
                          }
                        },
                      ),
                      TextButton(
                        child: Text('LOCKED'),
                        style: styleLocked,
                        onPressed: controller != null
                            ? () =>
                                onSetExposureModeButtonPressed(ExposureMode.locked)
                            : null,
                      ),
                      TextButton(
                        child: Text('RESET OFFSET'),
                        style: styleLocked,
                        onPressed: controller != null
                            ? () => controller!.setExposureOffset(0.0)
                            : null,
                      ),
                    ],
                  ),
                  Center(
                    child: Text("Exposure Offset"),
                  ),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    mainAxisSize: MainAxisSize.max,
                    children: [
                      Text(_minAvailableExposureOffset.toString()),
                      Slider(
                        value: _currentExposureOffset,
                        min: _minAvailableExposureOffset,
                        max: _maxAvailableExposureOffset,
                        label: _currentExposureOffset.toString(),
                        onChanged: _minAvailableExposureOffset ==
                                _maxAvailableExposureOffset
                            ? null
                            : setExposureOffset,
                      ),
                      Text(_maxAvailableExposureOffset.toString()),
                    ],
                  ),
                ],
              ),
            ),
          ),
        );
      }
    
      Widget _focusModeControlRowWidget() {
        final ButtonStyle styleAuto = TextButton.styleFrom(
          primary: controller?.value.focusMode == FocusMode.auto
              ? Colors.orange
              : Colors.blue,
        );
        final ButtonStyle styleLocked = TextButton.styleFrom(
          primary: controller?.value.focusMode == FocusMode.locked
              ? Colors.orange
              : Colors.blue,
        );
    
        return SizeTransition(
          sizeFactor: _focusModeControlRowAnimation,
          child: ClipRect(
            child: Container(
              color: Colors.grey.shade50,
              child: Column(
                children: [
                  Center(
                    child: Text("Focus Mode"),
                  ),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    mainAxisSize: MainAxisSize.max,
                    children: [
                      TextButton(
                        child: Text('AUTO'),
                        style: styleAuto,
                        onPressed: controller != null
                            ? () => onSetFocusModeButtonPressed(FocusMode.auto)
                            : null,
                        onLongPress: () {
                          if (controller != null) controller!.setFocusPoint(null);
                          showInSnackBar('Resetting focus point');
                        },
                      ),
                      TextButton(
                        child: Text('LOCKED'),
                        style: styleLocked,
                        onPressed: controller != null
                            ? () => onSetFocusModeButtonPressed(FocusMode.locked)
                            : null,
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
        );
      }
    
      /// Display the control bar with buttons to take pictures and record videos.
      Widget _captureControlRowWidget() {
        final CameraController? cameraController = controller;
    
        return Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          mainAxisSize: MainAxisSize.max,
          children: <Widget>[
            IconButton(
              icon: const Icon(Icons.camera_alt),
              color: Colors.blue,
              onPressed:
                  cameraController != null && cameraController.value.isInitialized
                      // !cameraController.value.isRecordingVideo
                      ? onTakePictureButtonPressed
                      : null,
            ),
            IconButton(
              icon: const Icon(Icons.videocam),
              color: Colors.blue,
              onPressed: cameraController != null &&
                      cameraController.value.isInitialized &&
                      !cameraController.value.isRecordingVideo
                  ? onVideoRecordButtonPressed
                  : null,
            ),
            IconButton(
              icon: cameraController != null &&
                      cameraController.value.isRecordingPaused
                  ? Icon(Icons.play_arrow)
                  : Icon(Icons.pause),
              color: Colors.blue,
              onPressed: cameraController != null &&
                      cameraController.value.isInitialized &&
                      cameraController.value.isRecordingVideo
                  ? (cameraController.value.isRecordingPaused)
                      ? onResumeButtonPressed
                      : onPauseButtonPressed
                  : null,
            ),
            IconButton(
              icon: const Icon(Icons.stop),
              color: Colors.red,
              onPressed: cameraController != null &&
                      cameraController.value.isInitialized &&
                      cameraController.value.isRecordingVideo
                  ? onStopButtonPressed
                  : null,
            ),
            IconButton(
              icon: const Icon(Icons.pause_presentation),
              color:
                  cameraController != null && cameraController.value.isPreviewPaused
                      ? Colors.red
                      : Colors.blue,
              onPressed:
                  cameraController == null ? null : onPausePreviewButtonPressed,
            ),
          ],
        );
      }
    
      /// Display a row of toggle to select the camera (or a message if no camera is available).
      Widget _cameraTogglesRowWidget() {
        final List<Widget> toggles = <Widget>[];
    
        final onChanged = (CameraDescription? description) {
          if (description == null) {
            return;
          }
    
          onNewCameraSelected(description);
        };
    
        if (cameras.isEmpty) {
          return const Text('No camera found');
        } else {
          for (CameraDescription cameraDescription in cameras) {
            toggles.add(
              SizedBox(
                width: 90.0,
                child: RadioListTile<CameraDescription>(
                  title: Icon(getCameraLensIcon(cameraDescription.lensDirection)),
                  groupValue: controller?.description,
                  value: cameraDescription,
                  onChanged:
                      controller != null && controller!.value.isRecordingVideo
                          ? null
                          : onChanged,
                ),
              ),
            );
          }
        }
    
        return Row(children: toggles);
      }
    
      String timestamp() => DateTime.now().millisecondsSinceEpoch.toString();
    
      void showInSnackBar(String message) {
        // ignore: deprecated_member_use
        _scaffoldKey.currentState?.showSnackBar(SnackBar(content: Text(message)));
      }
    
      void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) {
        if (controller == null) {
          return;
        }
    
        final CameraController cameraController = controller!;
    
        final offset = Offset(
          details.localPosition.dx / constraints.maxWidth,
          details.localPosition.dy / constraints.maxHeight,
        );
        cameraController.setExposurePoint(offset);
        cameraController.setFocusPoint(offset);
      }
    
      void onNewCameraSelected(CameraDescription cameraDescription) async {
        if (controller != null) {
          await controller!.dispose();
        }
    
        final CameraController cameraController = CameraController(
          cameraDescription,
          kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium,
          enableAudio: enableAudio,
          imageFormatGroup: ImageFormatGroup.jpeg,
        );
    
        controller = cameraController;
    
        // If the controller is updated then update the UI.
        cameraController.addListener(() {
          if (mounted) setState(() {});
          if (cameraController.value.hasError) {
            showInSnackBar(
                'Camera error ${cameraController.value.errorDescription}');
          }
        });
    
        try {
          await cameraController.initialize();
          await Future.wait([
            // The exposure mode is currently not supported on the web.
            ...(!kIsWeb
                ? [
                    cameraController
                        .getMinExposureOffset()
                        .then((value) => _minAvailableExposureOffset = value),
                    cameraController
                        .getMaxExposureOffset()
                        .then((value) => _maxAvailableExposureOffset = value)
                  ]
                : []),
            cameraController
                .getMaxZoomLevel()
                .then((value) => _maxAvailableZoom = value),
            cameraController
                .getMinZoomLevel()
                .then((value) => _minAvailableZoom = value),
          ]);
        } on CameraException catch (e) {
          _showCameraException(e);
        }
    
        if (mounted) {
          setState(() {});
        }
      }
    
      void onTakePictureButtonPressed() {
        takePicture().then((XFile? file) {
          if (mounted) {
            setState(() {
              imageFile = file;
              videoController?.dispose();
              videoController = null;
            });
            if (file != null) showInSnackBar('Picture saved to ${file.path}');
          }
        });
      }
    
      void onFlashModeButtonPressed() {
        if (_flashModeControlRowAnimationController.value == 1) {
          _flashModeControlRowAnimationController.reverse();
        } else {
          _flashModeControlRowAnimationController.forward();
          _exposureModeControlRowAnimationController.reverse();
          _focusModeControlRowAnimationController.reverse();
        }
      }
    
      void onExposureModeButtonPressed() {
        if (_exposureModeControlRowAnimationController.value == 1) {
          _exposureModeControlRowAnimationController.reverse();
        } else {
          _exposureModeControlRowAnimationController.forward();
          _flashModeControlRowAnimationController.reverse();
          _focusModeControlRowAnimationController.reverse();
        }
      }
    
      void onFocusModeButtonPressed() {
        if (_focusModeControlRowAnimationController.value == 1) {
          _focusModeControlRowAnimationController.reverse();
        } else {
          _focusModeControlRowAnimationController.forward();
          _flashModeControlRowAnimationController.reverse();
          _exposureModeControlRowAnimationController.reverse();
        }
      }
    
      void onAudioModeButtonPressed() {
        enableAudio = !enableAudio;
        if (controller != null) {
          onNewCameraSelected(controller!.description);
        }
      }
    
      void onCaptureOrientationLockButtonPressed() async {
        try {
          if (controller != null) {
            final CameraController cameraController = controller!;
            if (cameraController.value.isCaptureOrientationLocked) {
              await cameraController.unlockCaptureOrientation();
              showInSnackBar('Capture orientation unlocked');
            } else {
              await cameraController.lockCaptureOrientation();
              showInSnackBar(
                  'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}');
            }
          }
        } on CameraException catch (e) {
          _showCameraException(e);
        }
      }
    
      void onSetFlashModeButtonPressed(FlashMode mode) {
        setFlashMode(mode).then((_) {
          if (mounted) setState(() {});
          showInSnackBar('Flash mode set to ${mode.toString().split('.').last}');
        });
      }
    
      void onSetExposureModeButtonPressed(ExposureMode mode) {
        setExposureMode(mode).then((_) {
          if (mounted) setState(() {});
          showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}');
        });
      }
    
      void onSetFocusModeButtonPressed(FocusMode mode) {
        setFocusMode(mode).then((_) {
          if (mounted) setState(() {});
          showInSnackBar('Focus mode set to ${mode.toString().split('.').last}');
        });
      }
    
      void onVideoRecordButtonPressed() {
        startVideoRecording().then((_) {
          if (mounted) setState(() {});
        });
      }
    
      void onStopButtonPressed() {
        stopVideoRecording().then((file) {
          if (mounted) setState(() {});
          if (file != null) {
            showInSnackBar('Video recorded to ${file.path}');
            videoFile = file;
            _startVideoPlayer();
          }
        });
      }
    
      Future<void> onPausePreviewButtonPressed() async {
        final CameraController? cameraController = controller;
    
        if (cameraController == null || !cameraController.value.isInitialized) {
          showInSnackBar('Error: select a camera first.');
          return;
        }
    
        if (cameraController.value.isPreviewPaused) {
          await cameraController.resumePreview();
        } else {
          await cameraController.pausePreview();
        }
    
        if (mounted) setState(() {});
      }
    
      void onPauseButtonPressed() {
        pauseVideoRecording().then((_) {
          if (mounted) setState(() {});
          showInSnackBar('Video recording paused');
        });
      }
    
      void onResumeButtonPressed() {
        resumeVideoRecording().then((_) {
          if (mounted) setState(() {});
          showInSnackBar('Video recording resumed');
        });
      }
    
      Future<void> startVideoRecording() async {
        final CameraController? cameraController = controller;
    
        if (cameraController == null || !cameraController.value.isInitialized) {
          showInSnackBar('Error: select a camera first.');
          return;
        }
    
        if (cameraController.value.isRecordingVideo) {
          // A recording is already started, do nothing.
          return;
        }
    
        try {
          await cameraController.startVideoRecording();
        } on CameraException catch (e) {
          _showCameraException(e);
          return;
        }
      }
    
      Future<XFile?> stopVideoRecording() async {
        final CameraController? cameraController = controller;
    
        if (cameraController == null || !cameraController.value.isRecordingVideo) {
          return null;
        }
    
        try {
          return cameraController.stopVideoRecording();
        } on CameraException catch (e) {
          _showCameraException(e);
          return null;
        }
      }
    
      Future<void> pauseVideoRecording() async {
        final CameraController? cameraController = controller;
    
        if (cameraController == null || !cameraController.value.isRecordingVideo) {
          return null;
        }
    
        try {
          await cameraController.pauseVideoRecording();
        } on CameraException catch (e) {
          _showCameraException(e);
          rethrow;
        }
      }
    
      Future<void> resumeVideoRecording() async {
        final CameraController? cameraController = controller;
    
        if (cameraController == null || !cameraController.value.isRecordingVideo) {
          return null;
        }
    
        try {
          await cameraController.resumeVideoRecording();
        } on CameraException catch (e) {
          _showCameraException(e);
          rethrow;
        }
      }
    
      Future<void> setFlashMode(FlashMode mode) async {
        if (controller == null) {
          return;
        }
    
        try {
          await controller!.setFlashMode(mode);
        } on CameraException catch (e) {
          _showCameraException(e);
          rethrow;
        }
      }
    
      Future<void> setExposureMode(ExposureMode mode) async {
        if (controller == null) {
          return;
        }
    
        try {
          await controller!.setExposureMode(mode);
        } on CameraException catch (e) {
          _showCameraException(e);
          rethrow;
        }
      }
    
      Future<void> setExposureOffset(double offset) async {
        if (controller == null) {
          return;
        }
    
        setState(() {
          _currentExposureOffset = offset;
        });
        try {
          offset = await controller!.setExposureOffset(offset);
        } on CameraException catch (e) {
          _showCameraException(e);
          rethrow;
        }
      }
    
      Future<void> setFocusMode(FocusMode mode) async {
        if (controller == null) {
          return;
        }
    
        try {
          await controller!.setFocusMode(mode);
        } on CameraException catch (e) {
          _showCameraException(e);
          rethrow;
        }
      }
    
      Future<void> _startVideoPlayer() async {
        if (videoFile == null) {
          return;
        }
    
        final VideoPlayerController vController = kIsWeb
            ? VideoPlayerController.network(videoFile!.path)
            : VideoPlayerController.file(File(videoFile!.path));
    
        videoPlayerListener = () {
          if (videoController != null && videoController!.value.size != null) {
            // Refreshing the state to update video player with the correct ratio.
            if (mounted) setState(() {});
            videoController!.removeListener(videoPlayerListener!);
          }
        };
        vController.addListener(videoPlayerListener!);
        await vController.setLooping(true);
        await vController.initialize();
        await videoController?.dispose();
        if (mounted) {
          setState(() {
            imageFile = null;
            videoController = vController;
          });
        }
        await vController.play();
      }
    
      Future<XFile?> takePicture() async {
        final CameraController? cameraController = controller;
        if (cameraController == null || !cameraController.value.isInitialized) {
          showInSnackBar('Error: select a camera first.');
          return null;
        }
    
        if (cameraController.value.isTakingPicture) {
          // A capture is already pending, do nothing.
          return null;
        }
    
        try {
          XFile file = await cameraController.takePicture();
          return file;
        } on CameraException catch (e) {
          _showCameraException(e);
          return null;
        }
      }
    
      void _showCameraException(CameraException e) {
        logError(e.code, e.description);
        showInSnackBar('Error: ${e.code}\n${e.description}');
      }
    }
    
    class CameraApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: CameraExampleHome(),
        );
      }
    }
    
    List<CameraDescription> cameras = [];
    
    Future<void> main() async {
      // Fetch the available cameras before initializing the app.
      try {
        WidgetsFlutterBinding.ensureInitialized();
        cameras = await availableCameras();
      } on CameraException catch (e) {
        logError(e.code, e.description);
      }
      runApp(CameraApp());
    }
    
    /// This allows a value of type T or T? to be treated as a value of type T?.
    ///
    /// We use this so that APIs that have become non-nullable can still be used
    /// with `!` and `?` on the stable branch.
    // TODO(ianh): Remove this once we roll stable in late 2021.
    T? _ambiguate<T>(T? value) => value;
    
    logs
    Launching lib/main.dart on Redmi K20 Pro in debug mode...
    Warning: Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
    Warning: Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
    Warning: Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
    Warning: Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
    Warning: Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
    Warning: Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
    Warning: Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
    Warning: Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
    Warning: Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
    Warning: Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
    ✓  Built build/app/outputs/flutter-apk/app-debug.apk.
    W/FlutterActivityAndFragmentDelegate( 5947): A splash screen was provided to Flutter, but this is deprecated. See flutter.dev/go/android-splash-migration for migration steps.
    Connecting to VM Service at ws://127.0.0.1:50025/22vvyUmwXvc=/ws
    I/CameraManagerGlobal( 5947): Connecting to camera service
    W/CameraManagerGlobal( 5947): [soar.cts] ignore the status update of camera: 100
    W/CameraManagerGlobal( 5947): [soar.cts] ignore the status update of camera: 101
    W/CameraManagerGlobal( 5947): [soar.cts] ignore the status update of camera: 120
    W/CameraManagerGlobal( 5947): [soar.cts] ignore the status update of camera: 20
    W/CameraManagerGlobal( 5947): [soar.cts] ignore the status update of camera: 21
    W/CameraManagerGlobal( 5947): [soar.cts] ignore the status update of camera: 60
    W/CameraManagerGlobal( 5947): [soar.cts] ignore the status update of camera: 61
    W/CameraManagerGlobal( 5947): [soar.cts] ignore the status update of camera: 62
    W/CameraManagerGlobal( 5947): [soar.cts] ignore the status update of camera: 63
    W/CameraManagerGlobal( 5947): ignore the torch status update of camera: 100
    W/CameraManagerGlobal( 5947): ignore the torch status update of camera: 120
    W/CameraManagerGlobal( 5947): ignore the torch status update of camera: 20
    W/CameraManagerGlobal( 5947): ignore the torch status update of camera: 21
    W/CameraManagerGlobal( 5947): ignore the torch status update of camera: 60
    W/CameraManagerGlobal( 5947): ignore the torch status update of camera: 61
    W/CameraManagerGlobal( 5947): ignore the torch status update of camera: 62
    W/CameraManagerGlobal( 5947): ignore the torch status update of camera: 63
    I/le.routes_issu( 5947): ProcessProfilingInfo new_methods=766 is saved saved_to_disk=1 resolve_classes_delay=8000
    D/MIUIInput( 5947): [MotionEvent] ViewRootImpl { action=ACTION_DOWN, id[0]=0, pointerCount=1, eventTime=453982281, downTime=453982281 } moveCount:0
    W/MirrorManager( 5947): this model don't Support
    D/MIUIInput( 5947): [MotionEvent] ViewRootImpl { action=ACTION_UP, id[0]=0, pointerCount=1, eventTime=453982372, downTime=453982281 } moveCount:0
    E/libc    ( 5947): Access denied finding property "persist.vendor.camera.privapp.list"
    W/le.routes_issue( 5947): type=1400 audit(0.0:837462): avc: denied { read } for name="u:object_r:vendor_persist_camera_prop:s0" dev="tmpfs" ino=28899 scontext=u:r:untrusted_app:s0:c217,c259,c512,c768 tcontext=u:object_r:vendor_persist_camera_prop:s0 tclass=file permissive=0 app=com.example.routes_issue
    I/Camera  ( 5947): startPreview
    I/Camera  ( 5947): CameraCaptureSession onConfigured
    I/Camera  ( 5947): Updating builder settings
    D/Camera  ( 5947): Updating builder with feature: ExposureLockFeature
    D/Camera  ( 5947): Updating builder with feature: ExposurePointFeature
    D/Camera  ( 5947): Updating builder with feature: ZoomLevelFeature
    D/Camera  ( 5947): Updating builder with feature: AutoFocusFeature
    D/Camera  ( 5947): Updating builder with feature: NoiseReductionFeature
    I/Camera  ( 5947): updateNoiseReduction | currentSetting: fast
    D/Camera  ( 5947): Updating builder with feature: FocusPointFeature
    D/Camera  ( 5947): Updating builder with feature: ResolutionFeature
    D/Camera  ( 5947): Updating builder with feature: SensorOrientationFeature
    D/Camera  ( 5947): Updating builder with feature: FlashFeature
    D/Camera  ( 5947): Updating builder with feature: ExposureOffsetFeature
    D/Camera  ( 5947): Updating builder with feature: FpsRangeFeature
    I/Camera  ( 5947): refreshPreviewCaptureSession
    D/MIUIInput( 5947): [MotionEvent] ViewRootImpl { action=ACTION_DOWN, id[0]=0, pointerCount=1, eventTime=453984083, downTime=453984083 } moveCount:0
    D/MIUIInput( 5947): [MotionEvent] ViewRootImpl { action=ACTION_UP, id[0]=0, pointerCount=1, eventTime=453984183, downTime=453984083 } moveCount:0
    I/Camera  ( 5947): prepareMediaRecorder
    I/Camera  ( 5947): closeCaptureSession
    W/BpBinder( 5947): PerfMonitor binderTransact :  time=280ms interface=android.hardware.camera2.ICameraDeviceUser code=14
    W/BpBinder( 5947): PerfMonitor binderTransact :  time=203ms interface=android.hardware.camera2.ICameraDeviceUser code=6
    I/Camera  ( 5947): CameraCaptureSession onConfigured
    I/Camera  ( 5947): Updating builder settings
    D/Camera  ( 5947): Updating builder with feature: ExposureLockFeature
    D/Camera  ( 5947): Updating builder with feature: ExposurePointFeature
    D/Camera  ( 5947): Updating builder with feature: ZoomLevelFeature
    D/Camera  ( 5947): Updating builder with feature: AutoFocusFeature
    D/Camera  ( 5947): Updating builder with feature: NoiseReductionFeature
    I/Camera  ( 5947): updateNoiseReduction | currentSetting: fast
    D/Camera  ( 5947): Updating builder with feature: FocusPointFeature
    D/Camera  ( 5947): Updating builder with feature: ResolutionFeature
    D/Camera  ( 5947): Updating builder with feature: SensorOrientationFeature
    D/Camera  ( 5947): Updating builder with feature: FlashFeature
    D/Camera  ( 5947): Updating builder with feature: ExposureOffsetFeature
    D/Camera  ( 5947): Updating builder with feature: FpsRangeFeature
    I/Camera  ( 5947): refreshPreviewCaptureSession
    I/Camera  ( 5947): CameraCaptureSession onClosed
    D/MIUIInput( 5947): [MotionEvent] ViewRootImpl { action=ACTION_DOWN, id[0]=0, pointerCount=1, eventTime=453986823, downTime=453986823 } moveCount:0
    D/MIUIInput( 5947): [MotionEvent] ViewRootImpl { action=ACTION_UP, id[0]=0, pointerCount=1, eventTime=453986948, downTime=453986823 } moveCount:0
    I/Camera  ( 5947): runPictureAutoFocus
    I/Camera  ( 5947): lockAutoFocus
    D/CameraCaptureCallback( 5947): CameraCaptureCallback | state: STATE_WAITING_FOCUS | afState: 2 | aeState: 2
    D/CameraCaptureCallback( 5947): CameraCaptureCallback | state: STATE_WAITING_FOCUS | afState: 2 | aeState: 2
    D/CameraCaptureCallback( 5947): CameraCaptureCallback | state: STATE_WAITING_FOCUS | afState: 2 | aeState: 2
    D/CameraCaptureCallback( 5947): CameraCaptureCallback | state: STATE_WAITING_FOCUS | afState: 2 | aeState: 2
    D/CameraCaptureCallback( 5947): CameraCaptureCallback | state: STATE_WAITING_FOCUS | afState: 2 | aeState: 2
    D/CameraCaptureCallback( 5947): CameraCaptureCallback | state: STATE_WAITING_FOCUS | afState: 2 | aeState: 2
    D/CameraCaptureCallback( 5947): CameraCaptureCallback | state: STATE_WAITING_FOCUS | afState: 2 | aeState: 2
    D/CameraCaptureCallback( 5947): CameraCaptureCallback | state: STATE_WAITING_FOCUS | afState: 2 | aeState: 2
    D/CameraCaptureCallback( 5947): CameraCaptureCallback | state: STATE_WAITING_FOCUS | afState: 4 | aeState: 2
    I/Camera  ( 5947): captureStillPicture
    D/Camera  ( 5947): Updating builder with feature: ExposureLockFeature
    D/Camera  ( 5947): Updating builder with feature: ExposurePointFeature
    D/Camera  ( 5947): Updating builder with feature: ZoomLevelFeature
    D/Camera  ( 5947): Updating builder with feature: AutoFocusFeature
    D/Camera  ( 5947): Updating builder with feature: NoiseReductionFeature
    I/Camera  ( 5947): updateNoiseReduction | currentSetting: fast
    D/Camera  ( 5947): Updating builder with feature: FocusPointFeature
    D/Camera  ( 5947): Updating builder with feature: ResolutionFeature
    D/Camera  ( 5947): Updating builder with feature: SensorOrientationFeature
    D/Camera  ( 5947): Updating builder with feature: FlashFeature
    D/Camera  ( 5947): Updating builder with feature: ExposureOffsetFeature
    D/Camera  ( 5947): Updating builder with feature: FpsRangeFeature
    I/Camera  ( 5947): sending capture request
    E/AndroidRuntime( 5947): FATAL EXCEPTION: CameraBackground
    E/AndroidRuntime( 5947): Process: com.example.routes_issue, PID: 5947
    E/AndroidRuntime( 5947): java.lang.IllegalArgumentException: CaptureRequest contains unconfigured Input/Output Surface!
    E/AndroidRuntime( 5947): 	at android.hardware.camera2.CaptureRequest.convertSurfaceToStreamId(CaptureRequest.java:738)
    E/AndroidRuntime( 5947): 	at android.hardware.camera2.impl.CameraDeviceImpl.submitCaptureRequest(CameraDeviceImpl.java:1215)
    E/AndroidRuntime( 5947): 	at android.hardware.camera2.impl.CameraDeviceImpl.capture(CameraDeviceImpl.java:1087)
    E/AndroidRuntime( 5947): 	at android.hardware.camera2.impl.CameraCaptureSessionImpl.capture(CameraCaptureSessionImpl.java:188)
    E/AndroidRuntime( 5947): 	at io.flutter.plugins.camera.Camera.takePictureAfterPrecapture(Camera.java:589)
    E/AndroidRuntime( 5947): 	at io.flutter.plugins.camera.Camera.onConverged(Camera.java:172)
    E/AndroidRuntime( 5947): 	at io.flutter.plugins.camera.CameraCaptureCallback.handleWaitingFocusState(CameraCaptureCallback.java:150)
    E/AndroidRuntime( 5947): 	at io.flutter.plugins.camera.CameraCaptureCallback.process(CameraCaptureCallback.java:108)
    E/AndroidRuntime( 5947): 	at io.flutter.plugins.camera.CameraCaptureCallback.onCaptureProgressed(CameraCaptureCallback.java:161)
    E/AndroidRuntime( 5947): 	at android.hardware.camera2.impl.CameraCaptureSessionImpl$1.lambda$onCaptureProgressed$2$CameraCaptureSessionImpl$1(CameraCaptureSessionImpl.java:679)
    E/AndroidRuntime( 5947): 	at android.hardware.camera2.impl.-$$Lambda$CameraCaptureSessionImpl$1$7mSdNTTAoYA0D3ITDxzDJKGykz0.run(Unknown Source:8)
    E/AndroidRuntime( 5947): 	at android.os.Handler.handleCallback(Handler.java:938)
    E/AndroidRuntime( 5947): 	at android.os.Handler.dispatchMessage(Handler.java:99)
    E/AndroidRuntime( 5947): 	at android.os.Looper.loop(Looper.java:236)
    E/AndroidRuntime( 5947): 	at android.os.HandlerThread.run(HandlerThread.java:67)
    I/Camera  ( 5947): dispose
    I/Camera  ( 5947): close
    I/Camera  ( 5947): closeCaptureSession
    I/Process ( 5947): Sending signal. PID: 5947 SIG: 9
    Lost connection to device.
    Exited (sigterm)
    
    flutter doctor -v
    [✓] Flutter (Channel stable, 2.8.1, on macOS 12.1 21C52 darwin-arm, locale en-GB)
        • Flutter version 2.8.1 at /Users/mahesh/Documents/flutter
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision 77d935af4d (3 weeks ago), 2021-12-16 08:37:33 -0800
        • Engine revision 890a5fca2e
        • Dart version 2.15.1
    
    [✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
        • Android SDK at /Users/mahesh/Library/Android/sdk
        • Platform android-31, build-tools 31.0.0
        • ANDROID_HOME = /Users/mahesh/Library/Android/sdk
        • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
        • Java version OpenJDK Runtime Environment (build 11.0.8+10-b944.6916264)
        • All Android licenses accepted.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 13.1)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • CocoaPods version 1.10.2
    
    [✓] Chrome - develop for the web
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] Android Studio (version 4.2)
        • Android Studio at /Applications/Android Studio.app/Contents
        • Flutter plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/9212-flutter
        • Dart plugin can be installed from:
          🔨 https://plugins.jetbrains.com/plugin/6351-dart
        • Java version OpenJDK Runtime Environment (build 11.0.8+10-b944.6916264)
    
    [✓] IntelliJ IDEA Community Edition (version 2021.2.1)
        • IntelliJ at /Applications/IntelliJ IDEA CE.app
        • Flutter plugin version 60.1.4
        • Dart plugin version 212.5080.8
    
    [✓] VS Code (version 1.63.2)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.32.0
    
    [✓] Connected device (4 available)
        • Redmi K20 Pro (mobile)                     • android-arm64  • Android 11 (API 30)
        • iPhone 13 Pro (mobile) • 0FA2FB93-2A89-4FDB-8293-DF66188F1E54 • ios            • com.apple.CoreSimulator.SimRuntime.iOS-15-0
          (simulator)
        • macOS (desktop)        • macos                                • darwin-arm64   • macOS 12.1 21C52 darwin-arm
        • Chrome (web)           • chrome                               • web-javascript • Google Chrome 96.0.4664.110
    
    • No issues found!
    

    Labeling it as a valid feature request.

  3. added
    c: proposalA detailed proposal for a change to Flutter
    c: new featureNothing broken; request for a new capability
    and removed
    in triagePresently being triaged by the triage team
    on Jan 17, 2022
  4. changed the title [-][camera] Proposal to add takePicture while VideoRecording[/-] [+][camera] Proposal to take a picture while VideoRecording is in Progress[/+] on Jan 17, 2022
  5. quyenpham6841 commented on Jun 27, 2024

    @quyenpham6841

    Any update :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Issues that are less important to the Flutter projectc: new featureNothing broken; request for a new capabilityc: proposalA detailed proposal for a change to Flutterp: cameraThe camera pluginpackageflutter/packages repository. See also p: labels.team-ecosystemOwned by Ecosystem teamtriaged-ecosystemTriaged by Ecosystem team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions