Skip to content

[camera_avfoundation] Errors during video recording causes inaccessible recording files to be saved to NSDocumentDirectory #166933

Open

Description

@misos1
  1. It pretends like a file is created in some temporary directory (where files will be deleted at some point) by using a function with name getTemporaryFilePathWithExtension. But that function actually uses NSDocumentDirectory and the path can look like "/var/mobile/Containers/Data/Application/{some_id}/Documents/camera/videos" which looks like a persistent directory specific for the app. So it is a good idea to delete generated videos on the dart side at some point (maybe good to at least document this?), otherwise it will eventually fill the entire storage and one would need to delete and reinstall the app. Problem is that the recorded file is obtained by calling stopVideoRecordingWithCompletion and if some error happens before this or during a call to it then it is not possible to obtain that file on the dart side. Currently a workaround could be to use some plugin like path_provider and use hard coded path generation in a similar way as camera_avfoundation does (for example to clear that folder in case of error), which is prone to problems if something changes. Maybe camera_avfoundation should really rather use some temporary folder or prevent these leaks by some other means.

  2. Also when stopVideoRecordingWithCompletion fails it updates internal state in camera_avfoundation about recording to false but on the dart side in camera this is not updated. So trying to record something in this state will throw "Unhandled Exception: CameraException(A video recording is already started., startVideoRecording was called when a recording is already started.)" and on the other hand trying to stop recording throws "CameraException(Error -1008, Video is not recording!)".

Activity

  1. darshankawar commented on Apr 11, 2025

    @darshankawar
    Member

    @misos1 Thanks for the report.
    Please elaborate the case by providing a runnable reproducible code sample that shows the reported behavior along with flutter doctor -v.

  2. added
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Apr 11, 2025
  3. misos1 commented on Apr 16, 2025

    @misos1
    Author

    One possible way to make AVAssetWriter fail is to move the app into the background. There is sample code which does that. Of course this specific case has a simple workaround but there are also other causes for AVAssetWriter to fail. The point of this issue is that in general AVAssetWriter and so stopVideoRecordingWithCompletion can fail and when it fails it does not delete the partially written video file.

    Run it in release mode. Wait some 10 seconds after each step. It outputs messages into the terminal (I could not see them when using flutter run --release, so in that case run it from within xcode or make it display text in the widget).

    1. start video recording
    2. move the app into the background
    3. move the app into the foreground
    4. stop video recording
    5. it should print into terminal "files from camera" and there should be listed leaked files

    This code sample should show that file is not deleted when stopVideoRecordingWithCompletion fails and that there is also mismatch in "isRecording" state between camera and camera_avfoundation (when you try to record another video after provided steps). Now you can also test with enabled handling of lifecycle states by uncommenting the line with WidgetsBinding.instance.addObserver(this) and it will still be leaking files. But this then shows a little different situation.

    Again the point of this code sample is to just make stopVideoRecordingWithCompletion fail and show its behaviour in that case. This is not because of unexpected behavior by not handling lifecycle states. There are other causes when stopVideoRecordingWithCompletion may fail (but they may be harder to trigger, btw implementation of didChangeAppLifecycleState is not quite correct here - #167349).

    Code sample
    import "dart:io";
    import "package:flutter/material.dart";
    import "package:camera/camera.dart";
    import "package:path_provider/path_provider.dart";
    
    void main()
    {
    	runApp(MaterialApp(theme: ThemeData.light(), home: MyHomePage()));
    }
    
    class MyHomePage extends StatefulWidget
    {
    	const MyHomePage({super.key});
    	@override State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver
    {
    	CameraController? camera_cont;
    	bool recording = false;
    
    	@override void initState()
    	{
    		super.initState();
    		init_camera();
    		//WidgetsBinding.instance.addObserver(this);
    	}
    
    	@override void didChangeAppLifecycleState(AppLifecycleState state)
    	{
    		print(state);
    		if(state == AppLifecycleState.resumed)init_camera();
    		if(state == AppLifecycleState.inactive && camera_cont != null)
    		{
    			camera_cont!.dispose();
    			setState(() => camera_cont = null);
    		}
    	}
    
    	void init_camera() async
    	{
    		final cameras = await availableCameras();
    		if(camera_cont != null)return;
    		camera_cont = CameraController(cameras.firstWhere((camera) => camera.lensDirection == CameraLensDirection.back), fps: 60, ResolutionPreset.max, enableAudio: true);
    		await camera_cont!.initialize();
    		setState(() => recording = false);
    		print_files();
    	}
    
    	void print_files() async
    	{
    		var files;
    		try
    		{
    			files = Directory("${(await getApplicationDocumentsDirectory()).path}/camera/videos").listSync();
    		}
    		catch(e)
    		{
    			files = "camera folder probably does not exist yet";
    		}
    		print("files from camera: $files");
    	}
    
    	void record() async
    	{
    		await camera_cont!.startVideoRecording();
    		setState(() => recording = true);
    	}
    
    	void stop() async
    	{
    		try
    		{
    			final f = await camera_cont!.stopVideoRecording();
    			File(f.path).deleteSync();
    		}
    		catch(e)
    		{
    			print(e);
    		}
    		print_files();
    		setState(() => recording = false);
    	}
    
    	@override Widget build(BuildContext context)
    	{
    		if(camera_cont == null)return CircularProgressIndicator();
    		return Scaffold(
    			body: Column(
    				children: [
    					CameraPreview(camera_cont!),
    					if(recording)FilledButton(onPressed: stop, child: const Text("Stop"))
    					else FilledButton(onPressed: record, child: const Text("Record"))
    				],
    			)
    		);
    	}
    }
    Doctor output
    [✓] Flutter (Channel stable, 3.27.2, on macOS 15.3.1 24D70 darwin-x64)
        • Flutter version 3.27.2 on channel stable at /usr/local/Caskroom/flutter/3.22.1/flutter
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision 68415ad1d9 (3 months ago), 2025-01-13 10:22:03 -0800
        • Engine revision e672b006cb
        • Dart version 3.6.1
        • DevTools version 2.40.2
    
    [✓] Xcode - develop for iOS and macOS (Xcode 16.2)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • Build 16C5032a
        • CocoaPods version 1.15.2
    
    [✓] Connected device
        • ios            • iOS 15.8.2 19H384
  4. removed
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Apr 16, 2025
  5. darshankawar commented on Apr 17, 2025

    @darshankawar
    Member

    Thanks for the update. Can you also share the relevant log from devtools that probably shows the leak while following the steps ? Also check if the leak occurs in profile mode or not.

  6. added
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Apr 17, 2025
  7. misos1 commented on Apr 17, 2025

    @misos1
    Author

    Yes with flutter run --profile it also occurs and I can also see messages from the sample code. I can see the same messages also in devtools "Logging" section. Just to show how it looks when performing the mentioned steps (as I forgot to include that in the previous comment):

    flutter: files from camera: []
    // all steps were performed here
    flutter: CameraException(IOError, AVAssetWriter could not finish writing!)
    flutter: files from camera: [File: '/var/mobile/Containers/Data/Application/{some_id}/Documents/camera/videos/REC_0BFCF0CA-2B8B-41B3-8FEC-F989F7F71702.mp4']
    
  8. removed
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Apr 17, 2025
  9. danagbemava-nc commented on Apr 22, 2025

    @danagbemava-nc
    Member

    Hi @misos1, I just tested this several times on an iPhone 16 pro max and I can't seem to reproduce the issue. Is there any other steps we can use to reproduce this?

  10. added
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Apr 22, 2025
  11. misos1 commented on Apr 22, 2025

    @misos1
    Author

    Ensure it is run in release or profile mode, you wait at least 10 seconds after each step, by moving into the background I mean clicking on the home button so the home screen is visible, ensure you really can see messages from print. Try to uncomment the line with WidgetsBinding.instance.addObserver.

  12. removed
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Apr 22, 2025
  13. darshankawar commented on Apr 28, 2025

    @darshankawar
    Member

    Thanks for the update. I tried the given code sample on iPhone 15 device running iOS 18.3.1 and followed the steps as mentioned running in profile mode with latest stable version, tried to wait 10 seconds after each step, ie, move the app to background, wait, then move the app to foreground, wait and then stop the recording, but I didn't see the reported log in the console, but based on the report, I'll keep the issue open for team's tracking.

  14. added
    platform-iosiOS applications specifically
    packageflutter/packages repository. See also p: labels.
    perf: memoryPerformance issues related to memory
    team-iosOwned by iOS platform team
    and removed
    in triagePresently being triaged by the triage team
    on Apr 28, 2025
  15. vashworth commented on May 15, 2025

    @vashworth
    Contributor

    2. Also when stopVideoRecordingWithCompletion fails it updates internal state in camera_avfoundation about recording to false but on the dart side in camera this is not updated. So trying to record something in this state will throw "Unhandled Exception: CameraException(A video recording is already started., startVideoRecording was called when a recording is already started.)" and on the other hand trying to stop recording throws "CameraException(Error -1008, Video is not recording!)".

    @misos1 Can you file a separate issue for this?

  16. added
    P2Important issues not at the top of the work list
    triaged-iosTriaged by iOS platform team
    on May 15, 2025
  17. changed the title [-][camera_avfoundation] camera may leak files if error happens after startVideoRecordingWithCompletion[/-] [+][camera_avfoundation] Errors during video recording causes inaccessible recording files to be saved to `NSDocumentDirectory`[/+] on May 15, 2025
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

    P2Important issues not at the top of the work listp: cameraThe camera pluginpackageflutter/packages repository. See also p: labels.perf: memoryPerformance issues related to memoryplatform-iosiOS applications specificallyteam-iosOwned by iOS platform teamtriaged-iosTriaged by iOS platform team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions