fix: Move Audio Input initialization shortly before startRecording (#159)
				
					
				
			* rename * Update AVAudioSession+updateCategory.swift * fix bootstrap script * Update CameraView+AVAudioSession.swift * move audio input adding lower * Activate AudioSession only when starting recording * format * Deactivate Audio Session * remove audio input before deactivating audio session * Update CameraView+AVAudioSession.swift * log time * Update CameraView+AVAudioSession.swift * measure time with `measureElapsedTime` * Update project.pbxproj * only log in debug builds * bootstrap with bridge (RNN new API) * Mark two funcs as `@inlinable` * format * Update ReactLogger.swift * Make audioWriter optional (allow videos without sound) * only log frame drop reason in DEBUG * Make audio writing entirely optional * format * Use function name as label for measureElapsedTime * Update MeasureElapsedTime.swift * Update MeasureElapsedTime.swift * Mark AudioWriter as finished * set `automaticallyConfiguresApplicationAudioSession` once * Add JS console logging * log to JS console for a few logs * Update AVAudioSession+updateCategory.swift * format * Update JSConsoleHelper.mm * catch log errors * Update ReactLogger.swift * fix docs * Update RecordingSession.swift * Immediatelly add audio input * Update CameraView+AVCaptureSession.swift * Update CameraView+AVCaptureSession.swift * Update ReactLogger.swift * immediatelly set audio session * extract * format * Update TROUBLESHOOTING.mdx * hmm * Update AVAudioSession+updateCategory.swift * Create secondary `AVCaptureSession` for audio * Configure once, start stop on demand * format * fix audio notification interruptions * docs
This commit is contained in:
		| @@ -11,7 +11,7 @@ import Foundation | ||||
|  | ||||
| // MARK: - BufferType | ||||
|  | ||||
| enum BufferType { | ||||
| enum BufferType: String { | ||||
|   case audio | ||||
|   case video | ||||
| } | ||||
| @@ -20,12 +20,11 @@ enum BufferType { | ||||
|  | ||||
| class RecordingSession { | ||||
|   private let assetWriter: AVAssetWriter | ||||
|   private let audioWriter: AVAssetWriterInput | ||||
|   private let videoWriter: AVAssetWriterInput | ||||
|   private let bufferAdaptor: AVAssetWriterInputPixelBufferAdaptor | ||||
|   private var audioWriter: AVAssetWriterInput? | ||||
|   private var bufferAdaptor: AVAssetWriterInputPixelBufferAdaptor? | ||||
|   private let completionHandler: (AVAssetWriter.Status, Error?) -> Void | ||||
|  | ||||
|   private let initialTimestamp: CMTime | ||||
|   private var initialTimestamp: CMTime? | ||||
|   private var latestTimestamp: CMTime? | ||||
|   private var hasWrittenFirstVideoFrame = false | ||||
|  | ||||
| @@ -34,7 +33,8 @@ class RecordingSession { | ||||
|   } | ||||
|  | ||||
|   var duration: Double { | ||||
|     guard let latestTimestamp = latestTimestamp else { | ||||
|     guard let latestTimestamp = latestTimestamp, | ||||
|           let initialTimestamp = initialTimestamp else { | ||||
|       return 0.0 | ||||
|     } | ||||
|     return (latestTimestamp - initialTimestamp).seconds | ||||
| @@ -42,61 +42,98 @@ class RecordingSession { | ||||
|  | ||||
|   init(url: URL, | ||||
|        fileType: AVFileType, | ||||
|        videoSettings: [String: Any], | ||||
|        audioSettings: [String: Any], | ||||
|        isVideoMirrored: Bool, | ||||
|        completion: @escaping (AVAssetWriter.Status, Error?) -> Void) throws { | ||||
|     completionHandler = completion | ||||
|  | ||||
|     do { | ||||
|       assetWriter = try AVAssetWriter(outputURL: url, fileType: fileType) | ||||
|       audioWriter = AVAssetWriterInput(mediaType: .audio, outputSettings: audioSettings) | ||||
|       videoWriter = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings) | ||||
|       completionHandler = completion | ||||
|     } catch let error as NSError { | ||||
|       throw CameraError.capture(.createRecorderError(message: error.description)) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|     audioWriter.expectsMediaDataInRealTime = true | ||||
|   deinit { | ||||
|     if assetWriter.status == .writing { | ||||
|       ReactLogger.log(level: .info, message: "Cancelling AssetWriter...", alsoLogToJS: true) | ||||
|       assetWriter.cancelWriting() | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   func initializeVideoWriter(withSettings settings: [String: Any], isVideoMirrored: Bool) { | ||||
|     guard !settings.isEmpty else { | ||||
|       ReactLogger.log(level: .error, message: "Tried to initialize Video Writer with empty settings!", alsoLogToJS: true) | ||||
|       return | ||||
|     } | ||||
|     guard bufferAdaptor == nil else { | ||||
|       ReactLogger.log(level: .error, message: "Tried to add Video Writer twice!", alsoLogToJS: true) | ||||
|       return | ||||
|     } | ||||
|  | ||||
|     let videoWriter = AVAssetWriterInput(mediaType: .video, outputSettings: settings) | ||||
|     videoWriter.expectsMediaDataInRealTime = true | ||||
|  | ||||
|     if isVideoMirrored { | ||||
|       videoWriter.transform = CGAffineTransform(rotationAngle: -(.pi / 2)) | ||||
|     } else { | ||||
|       videoWriter.transform = CGAffineTransform(rotationAngle: .pi / 2) | ||||
|     } | ||||
|  | ||||
|     bufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoWriter, withVideoSettings: videoSettings) | ||||
|  | ||||
|     assetWriter.add(videoWriter) | ||||
|     assetWriter.add(audioWriter) | ||||
|  | ||||
|     assetWriter.startWriting() | ||||
|     initialTimestamp = CMTime(seconds: CACurrentMediaTime(), preferredTimescale: 1_000_000_000) | ||||
|     assetWriter.startSession(atSourceTime: initialTimestamp) | ||||
|     ReactLogger.log(level: .info, message: "Initialized Video and Audio AssetWriter.") | ||||
|     bufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoWriter, withVideoSettings: settings) | ||||
|     ReactLogger.log(level: .info, message: "Initialized Video AssetWriter.") | ||||
|   } | ||||
|  | ||||
|   deinit { | ||||
|     if assetWriter.status == .writing { | ||||
|       ReactLogger.log(level: .info, message: "Cancelling AssetWriter...") | ||||
|       assetWriter.cancelWriting() | ||||
|   func initializeAudioWriter(withSettings settings: [String: Any]) { | ||||
|     guard !settings.isEmpty else { | ||||
|       ReactLogger.log(level: .error, message: "Tried to initialize Audio Writer with empty settings!", alsoLogToJS: true) | ||||
|       return | ||||
|     } | ||||
|     guard audioWriter == nil else { | ||||
|       ReactLogger.log(level: .error, message: "Tried to add Audio Writer twice!", alsoLogToJS: true) | ||||
|       return | ||||
|     } | ||||
|  | ||||
|     audioWriter = AVAssetWriterInput(mediaType: .audio, outputSettings: settings) | ||||
|     audioWriter!.expectsMediaDataInRealTime = true | ||||
|     assetWriter.add(audioWriter!) | ||||
|     ReactLogger.log(level: .info, message: "Initialized Audio AssetWriter.") | ||||
|   } | ||||
|  | ||||
|   func start() { | ||||
|     assetWriter.startWriting() | ||||
|     initialTimestamp = CMTime(seconds: CACurrentMediaTime(), preferredTimescale: 1_000_000_000) | ||||
|     assetWriter.startSession(atSourceTime: initialTimestamp!) | ||||
|     ReactLogger.log(level: .info, message: "Started RecordingSession at \(initialTimestamp!.seconds) seconds.") | ||||
|   } | ||||
|  | ||||
|   func appendBuffer(_ buffer: CMSampleBuffer, type bufferType: BufferType) { | ||||
|     if !CMSampleBufferDataIsReady(buffer) { | ||||
|       return | ||||
|     } | ||||
|     guard let initialTimestamp = initialTimestamp else { | ||||
|       ReactLogger.log(level: .error, | ||||
|                       message: "A \(bufferType.rawValue) frame arrived, but initialTimestamp was nil. Is this RecordingSession running?", | ||||
|                       alsoLogToJS: true) | ||||
|       return | ||||
|     } | ||||
|  | ||||
|     let timestamp = CMSampleBufferGetPresentationTimeStamp(buffer) | ||||
|     latestTimestamp = timestamp | ||||
|  | ||||
|     switch bufferType { | ||||
|     case .video: | ||||
|       if !videoWriter.isReadyForMoreMediaData { | ||||
|         ReactLogger.log(level: .warning, message: "The Video AVAssetWriterInput was not ready for more data! Is your frame rate too high?") | ||||
|       guard let bufferAdaptor = bufferAdaptor else { | ||||
|         ReactLogger.log(level: .error, message: "Video Frame arrived but VideoWriter was nil!", alsoLogToJS: true) | ||||
|         return | ||||
|       } | ||||
|       if !bufferAdaptor.assetWriterInput.isReadyForMoreMediaData { | ||||
|         ReactLogger.log(level: .warning, | ||||
|                         message: "The Video AVAssetWriterInput was not ready for more data! Is your frame rate too high?", | ||||
|                         alsoLogToJS: true) | ||||
|         return | ||||
|       } | ||||
|       guard let imageBuffer = CMSampleBufferGetImageBuffer(buffer) else { | ||||
|         ReactLogger.log(level: .error, message: "Failed to get the CVImageBuffer!") | ||||
|         ReactLogger.log(level: .error, message: "Failed to get the CVImageBuffer!", alsoLogToJS: true) | ||||
|         return | ||||
|       } | ||||
|       bufferAdaptor.append(imageBuffer, withPresentationTime: timestamp) | ||||
| @@ -105,6 +142,10 @@ class RecordingSession { | ||||
|         ReactLogger.log(level: .warning, message: "VideoWriter: First frame arrived \((timestamp - initialTimestamp).seconds) seconds late.") | ||||
|       } | ||||
|     case .audio: | ||||
|       guard let audioWriter = audioWriter else { | ||||
|         ReactLogger.log(level: .error, message: "Audio Frame arrived but AudioWriter was nil!", alsoLogToJS: true) | ||||
|         return | ||||
|       } | ||||
|       if !audioWriter.isReadyForMoreMediaData { | ||||
|         return | ||||
|       } | ||||
| @@ -117,14 +158,17 @@ class RecordingSession { | ||||
|  | ||||
|     if assetWriter.status == .failed { | ||||
|       // TODO: Should I call the completion handler or is this instance still valid? | ||||
|       ReactLogger.log(level: .error, message: "AssetWriter failed to write buffer! Error: \(assetWriter.error?.localizedDescription ?? "none")") | ||||
|       ReactLogger.log(level: .error, | ||||
|                       message: "AssetWriter failed to write buffer! Error: \(assetWriter.error?.localizedDescription ?? "none")", | ||||
|                       alsoLogToJS: true) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   func finish() { | ||||
|     ReactLogger.log(level: .info, message: "Finishing Recording with AssetWriter status \"\(assetWriter.status.descriptor)\"...") | ||||
|     if assetWriter.status == .writing { | ||||
|       videoWriter.markAsFinished() | ||||
|       bufferAdaptor?.assetWriterInput.markAsFinished() | ||||
|       audioWriter?.markAsFinished() | ||||
|       assetWriter.finishWriting { | ||||
|         self.completionHandler(self.assetWriter.status, self.assetWriter.error) | ||||
|       } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user