Skip to content

Flutter Camera plugin memory issue #20943

@ghost

Description

Hi, I'm using camera plugin to capture video and my code basically all the same from plugin example code except I added animationController so that I can stop recording after 15 seconds.
The problem is when I run on real Iphone device, app will crush. It happens in some cases. Right after start recording, in progress, right after finish recording.
I checked Xcode device console and it says video recording processes were called on main thread not background.
Is this plugin error or I should do some modifications?
Here is screenshot for Xcode. Even 100MB, app crash.
2018-08-22 22 09 39

Code:

import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
import 'package:path_provider/path_provider.dart';
import 'package:video_player/video_player.dart';
import 'countdown.dart';
import 'video_edit.dart';

class CaptureVideo extends StatefulWidget {
  @override
  CaptureVideoState createState() {
    return new CaptureVideoState();
  }
}

class CaptureVideoState extends State<CaptureVideo>
    with TickerProviderStateMixin {
  List<CameraDescription> _cameraDescriptions;
  CameraController _cameraController;
  VideoPlayerController _videoController;
  AnimationController _animationController;
  String imagePath;
  String videoPath;
  VoidCallback videoPlayerListener;
  bool _isSelfie = false;
  bool _isRecorded = false;
  bool _isRecording = false;
  bool _isReady = false;

  @override
  void initState() {
    super.initState();
    _setupCameras();
  }

  Future<void> _setupCameras() async {
    try {
      _cameraDescriptions = await availableCameras();
      _cameraController =
          new CameraController(_cameraDescriptions[0], ResolutionPreset.high);
      await _cameraController.initialize();
    } on CameraException catch (_) {
      // do something on error.
    }
    if (!mounted) return;
    setState(() {
      _isReady = true;
    });
    _animationController = new AnimationController(
      vsync: this,
      duration: new Duration(seconds: 15),
    );
  }

  @override
  void deactivate() {
    _videoController?.setVolume(0.0);
    _videoController?.removeListener(videoPlayerListener);
    super.deactivate();
  }

  @override
  void dispose() {
    _cameraController?.dispose();
    _animationController?.dispose();
    _videoController?.dispose();
    super.dispose();
  }

  String timestamp() => new DateTime.now().millisecondsSinceEpoch.toString();

  Future _changeDirection(CameraDescription cameraDescription) async {
    await _cameraController.dispose();
      _cameraController =
          new CameraController(cameraDescription, ResolutionPreset.high);

      _cameraController.addListener(() {
        if (mounted) setState(() {});
        if (_cameraController.value.hasError) {
          print(_cameraController.value.errorDescription);
        }
 

      try {
        await _cameraController.initialize();
      } on CameraException catch (e) {
        print(e);
      }

      if (mounted) {
        setState(() {});
      }
    });
  }

  _toSelfie() {
    _changeDirection(_cameraDescriptions[1]);
  }

  _toForward() {
    _changeDirection(_cameraDescriptions[0]);
  }

  void onVideoRecordButtonPressed() {
    startVideoRecording().then((String filePath) {
      if (mounted) setState(() {});
      if (filePath != null) print('Saving video to $filePath');
    });
  }

  void onStopButtonPressed() {
    stopVideoRecording().then((_) {
      if (mounted) setState(() {});
      print('Video recorded to: $videoPath');
    });
  }

  Future<String> startVideoRecording() async {
    if (!_cameraController.value.isInitialized) {
      print('Error: select a camera first.');
      return null;
    }

    final Directory extDir = await getTemporaryDirectory();
    final String dirPath = '${extDir.path}/Movies/flutter_test';
    await new Directory(dirPath).create(recursive: true);
    final String filePath = '$dirPath/${timestamp()}.mp4';

    if (_cameraController.value.isRecordingVideo) {
      // A recording is already started, do nothing.
      return null;
    }

    try {
      videoPath = filePath;
      await _cameraController.startVideoRecording(filePath);
      _animationController.forward(from: 0.0).then((_) {
        onStopButtonPressed();
        setState(() {
          _isRecorded = true;
        });
      });
    } on CameraException catch (e) {
      print(e);
      return null;
    }
    return filePath;
  }

  Future<void> stopVideoRecording() async {
    if (!_cameraController.value.isRecordingVideo) {
      return null;
    }

    try {
      await _cameraController.stopVideoRecording();
      _animationController.stop();
      setState(() {
        _isRecorded = true;
      });
    } on CameraException catch (e) {
      print(e);
      return null;
    }

    await _startVideoPlayer();
  }

  Future<void> _startVideoPlayer() async {
    final VideoPlayerController vcontroller =
        new VideoPlayerController.file(new File(videoPath));
    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(() {
        imagePath = null;
        _videoController = vcontroller;
      });
    }
    await vcontroller.play();
  }

  _startVideoRecordingCoountDown() {
    onVideoRecordButtonPressed();
  }

  _stopVideoRecordingCountDown() {
    onStopButtonPressed();
    setState(() {
      _animationController.stop();
      _isRecorded = true;
    });
  }

  Widget _recordingView() {
    return new Stack(
      children: <Widget>[
        new AspectRatio(
          aspectRatio: _cameraController.value.aspectRatio,
          child: new CameraPreview(_cameraController),
        ),
        !_isRecording
            ? new Align(
                alignment: Alignment.topLeft,
                child: new Container(
                  child: new FloatingActionButton(
                    heroTag: 'start',
                    backgroundColor: Colors.black.withOpacity(0.001),
                    elevation: 50.0,
                    child: new Center(child: new Icon(Icons.clear, size: 28.0)),
                    onPressed: () {
                      Navigator.of(context).pop();
                    },
                  ),
                ),
              )
            : new Container(),
        new Align(
          alignment: new Alignment(0.0, 1.0),
          child: new Container(
            margin: const EdgeInsets.only(bottom: 10.0),
            child: new FloatingActionButton(
              elevation: 30.0,
              backgroundColor: Colors.white,
              foregroundColor: Colors.amber,
              child: _animationController.isAnimating
                  ? new Countdown(
                      animation: new StepTween(
                        begin: 16,
                        end: 0,
                      ).animate(_animationController),
                    )
                  : new Icon(Icons.play_arrow),
              onPressed: () {
                setState(() {
                  _isRecording = true;
                });
                !_animationController.isAnimating
                    ? _startVideoRecordingCoountDown()
                    : _stopVideoRecordingCountDown();
              },
            ),
          ),
        ),
        !_isRecording
            ? new Align(
                alignment: Alignment.bottomRight,
                child: new Container(
                  margin: const EdgeInsets.only(right: 15.0, bottom: 10.0),
                  child: new FloatingActionButton(
                    elevation: 50.0,
                    backgroundColor: Colors.black.withOpacity(0.001),
                    child: new Icon(Icons.cached, size: 35.0),
                    onPressed: () {
                      setState(() {
                        _isSelfie ? _toForward() : _toSelfie();
                        _isSelfie ? _isSelfie = false : _isSelfie = true;
                      });
                    },
                  ),
                ),
              )
            : new Container(),
      ],
      fit: StackFit.expand,
    );
  }

  @override
  Widget build(BuildContext context) {
    if (!_isReady) {
      return new Container(color: Colors.black);
    }
    return new Scaffold(
      resizeToAvoidBottomPadding: false,
      backgroundColor: Colors.black,
      body: !_isRecorded ? _recordingView() : VideoEdit(_videoController),
    );
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    c: performanceRelates to speed or footprint issues (see "perf:" labels)packageflutter/packages repository. See also p: labels.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions