From c4f084d69d7b07005132cf4efd5c997b4cb049ea Mon Sep 17 00:00:00 2001 From: Rick Hohler Date: Wed, 28 Jan 2026 21:06:50 -0600 Subject: [PATCH 1/5] [camera_avfoundation] Handle startWriting errors (Fixes #180048) Handling the return value of startWriting to prevent silent failures and ensure errors are propagated. --- .../ios/RunnerTests/SampleBufferTests.swift | 27 +++++++++++++++++++ .../camera_avfoundation/DefaultCamera.swift | 11 ++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift index 86130035d14a..ab2baf287563 100644 --- a/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift +++ b/packages/camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift @@ -359,4 +359,31 @@ final class CameraSampleBufferTests: XCTestCase { AVAudioSession.sharedInstance().category == .playAndRecord, "Category should be PlayAndRecord.") } + func testStartVideoRecordingReportsErrorWhenStartWritingFails() { + let (camera, writerMock, _, _) = createCamera() + + // Configure the mock to return false for startWriting + writerMock.startWritingStub = { + return false + } + + // Configure a mock error + let mockError = NSError( + domain: "test", code: 123, userInfo: [NSLocalizedDescriptionKey: "Mock write error"]) + writerMock.error = mockError + + let expectation = self.expectation(description: "Completion handler called with error") + + camera.startVideoRecording( + completion: { error in + XCTAssertNotNil(error) + XCTAssertEqual(error?.code, "IOError") + XCTAssertEqual(error?.message, "Unable to start writing") + XCTAssertEqual(error?.details as? String, "Mock write error") + XCTAssertFalse(camera.isRecording) + expectation.fulfill() + }, messengerForStreaming: nil) + + waitForExpectations(timeout: 1.0, handler: nil) + } } diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift index 15a001d63ef0..799550d60d36 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift @@ -97,7 +97,7 @@ final class DefaultCamera: NSObject, Camera { private var latestPixelBuffer: CVPixelBuffer? private var videoRecordingPath: String? - private var isRecording = false + private(set) var isRecording = false private var isRecordingPaused = false private var isFirstVideoSample = false private var videoIsDisconnected = false @@ -512,7 +512,14 @@ final class DefaultCamera: NSObject, Camera { // didOutputSampleBuffer had chance to call startWriting and lag at start of video // https://github.com/flutter/flutter/issues/132016 // https://github.com/flutter/flutter/issues/151319 - let _ = videoWriter?.startWriting() + if videoWriter?.startWriting() == false { + completion( + FlutterError( + code: "IOError", + message: "Unable to start writing", + details: videoWriter?.error?.localizedDescription)) + return + } isFirstVideoSample = true isRecording = true isRecordingPaused = false From 9584474a3f7b2a1407f11df5c55b632dcc2c0a7b Mon Sep 17 00:00:00 2001 From: Rick Hohler Date: Thu, 29 Jan 2026 11:37:58 -0600 Subject: [PATCH 2/5] Apply review suggestion --- .../Sources/camera_avfoundation/DefaultCamera.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift index 799550d60d36..85fff282d38e 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift @@ -512,7 +512,7 @@ final class DefaultCamera: NSObject, Camera { // didOutputSampleBuffer had chance to call startWriting and lag at start of video // https://github.com/flutter/flutter/issues/132016 // https://github.com/flutter/flutter/issues/151319 - if videoWriter?.startWriting() == false { + guard let videoWriter = videoWriter, videoWriter.startWriting() else { completion( FlutterError( code: "IOError", From 3f20e4dfdeef268c517becbbfa9fb0e925f40968 Mon Sep 17 00:00:00 2001 From: Rick Hohler Date: Thu, 29 Jan 2026 16:46:19 -0600 Subject: [PATCH 3/5] Bump version and update changelog --- packages/camera/camera_avfoundation/CHANGELOG.md | 4 ++++ packages/camera/camera_avfoundation/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera_avfoundation/CHANGELOG.md b/packages/camera/camera_avfoundation/CHANGELOG.md index d008c309612c..e913461816f4 100644 --- a/packages/camera/camera_avfoundation/CHANGELOG.md +++ b/packages/camera/camera_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.22+10 + +* Fixes error handling in `startWriting`. + ## 0.9.22+9 * Migrates `FLTSavePhotoDelegate`, `FLTWritableData`, and `FLTImageStreamHandler` classes to Swift. diff --git a/packages/camera/camera_avfoundation/pubspec.yaml b/packages/camera/camera_avfoundation/pubspec.yaml index 4f53f2d6d57f..bc6d1928fb4d 100644 --- a/packages/camera/camera_avfoundation/pubspec.yaml +++ b/packages/camera/camera_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_avfoundation description: iOS implementation of the camera plugin. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.9.22+9 +version: 0.9.22+10 environment: sdk: ^3.9.0 From fdc19b49621329e599679eb7843882f3dbb87130 Mon Sep 17 00:00:00 2001 From: Rick Hohler Date: Thu, 29 Jan 2026 16:57:27 -0600 Subject: [PATCH 4/5] Implement missing startWriting error check and update isRecording visibility --- .../Sources/camera_avfoundation/DefaultCamera.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift index 85fff282d38e..356d2302e2eb 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift @@ -512,6 +512,7 @@ final class DefaultCamera: NSObject, Camera { // didOutputSampleBuffer had chance to call startWriting and lag at start of video // https://github.com/flutter/flutter/issues/132016 // https://github.com/flutter/flutter/issues/151319 +<<<<<<< Updated upstream guard let videoWriter = videoWriter, videoWriter.startWriting() else { completion( FlutterError( @@ -519,6 +520,16 @@ final class DefaultCamera: NSObject, Camera { message: "Unable to start writing", details: videoWriter?.error?.localizedDescription)) return +======= + if videoWriter?.startWriting() == false { + let error = videoWriter?.error + completion( + FlutterError( + code: "IOError", + message: "Unable to start writing: \(error?.localizedDescription ?? "unknown error")", + details: nil)) + return +>>>>>>> Stashed changes } isFirstVideoSample = true isRecording = true From e558196662be196fdacbd7488d7a9e0c1104ba97 Mon Sep 17 00:00:00 2001 From: Rick Hohler Date: Thu, 29 Jan 2026 16:58:41 -0600 Subject: [PATCH 5/5] Resolve conflict and update isRecording visibility --- .../Sources/camera_avfoundation/DefaultCamera.swift | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift index 356d2302e2eb..85fff282d38e 100644 --- a/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift +++ b/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/DefaultCamera.swift @@ -512,7 +512,6 @@ final class DefaultCamera: NSObject, Camera { // didOutputSampleBuffer had chance to call startWriting and lag at start of video // https://github.com/flutter/flutter/issues/132016 // https://github.com/flutter/flutter/issues/151319 -<<<<<<< Updated upstream guard let videoWriter = videoWriter, videoWriter.startWriting() else { completion( FlutterError( @@ -520,16 +519,6 @@ final class DefaultCamera: NSObject, Camera { message: "Unable to start writing", details: videoWriter?.error?.localizedDescription)) return -======= - if videoWriter?.startWriting() == false { - let error = videoWriter?.error - completion( - FlutterError( - code: "IOError", - message: "Unable to start writing: \(error?.localizedDescription ?? "unknown error")", - details: nil)) - return ->>>>>>> Stashed changes } isFirstVideoSample = true isRecording = true