Compare commits
	
		
			7 Commits
		
	
	
		
			0a43d7a160
			...
			694d9cfa8c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 694d9cfa8c | ||
|  | 91767e71c8 | ||
|  | 9f2c7906e5 | ||
|  | 621bfe333c | ||
|  | 20f8fa2937 | ||
|  | b03f9ea423 | ||
|  | 98d90a6442 | 
| @@ -11,7 +11,7 @@ import AVFoundation | ||||
| // MARK: - CameraView + AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate | ||||
|  | ||||
| extension CameraView: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate { | ||||
|   func startRecording(options: NSDictionary, callback jsCallback: @escaping RCTResponseSenderBlock) { | ||||
|   func startRecording(options: NSDictionary, filePath: String, callback jsCallback: @escaping RCTResponseSenderBlock) { | ||||
|     // Type-safety | ||||
|     let callback = Callback(jsCallback) | ||||
|  | ||||
| @@ -21,6 +21,7 @@ extension CameraView: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAud | ||||
|       // Start Recording with success and error callbacks | ||||
|       cameraSession.startRecording( | ||||
|         options: options, | ||||
|         filePath: filePath, | ||||
|         onVideoRecorded: { video in | ||||
|           callback.resolve(video.toJSValue()) | ||||
|         }, | ||||
|   | ||||
| @@ -342,6 +342,7 @@ public final class CameraView: UIView, CameraSessionDelegate { | ||||
|     ReactLogger.log(level: .info, message: "Chunk ready: \(chunk)") | ||||
|      | ||||
|     guard let onVideoChunkReady, let onInitReady else { | ||||
|       ReactLogger.log(level: .warning, message: "Either onInitReady or onVideoChunkReady are not valid!") | ||||
|       return | ||||
|     } | ||||
|      | ||||
|   | ||||
| @@ -64,7 +64,8 @@ RCT_EXPORT_VIEW_PROPERTY(onCodeScanned, RCTDirectEventBlock); | ||||
| // Camera View Functions | ||||
| RCT_EXTERN_METHOD(startRecording | ||||
|                   : (nonnull NSNumber*)node options | ||||
|                   : (NSDictionary*)options onRecordCallback | ||||
|                   : (NSDictionary*)options filePath | ||||
|                   : (NSString*)filePath onRecordCallback | ||||
|                   : (RCTResponseSenderBlock)onRecordCallback); | ||||
| RCT_EXTERN_METHOD(pauseRecording | ||||
|                   : (nonnull NSNumber*)node resolve | ||||
|   | ||||
| @@ -43,9 +43,9 @@ final class CameraViewManager: RCTViewManager { | ||||
|   //       This means that any errors that occur in this function have to be delegated through | ||||
|   //       the callback, but I'd prefer for them to throw for the original function instead. | ||||
|   @objc | ||||
|   final func startRecording(_ node: NSNumber, options: NSDictionary, onRecordCallback: @escaping RCTResponseSenderBlock) { | ||||
|   final func startRecording(_ node: NSNumber, options: NSDictionary, filePath: NSString, onRecordCallback: @escaping RCTResponseSenderBlock) { | ||||
|     let component = getCameraView(withTag: node) | ||||
|     component.startRecording(options: options, callback: onRecordCallback) | ||||
|     component.startRecording(options: options, filePath: filePath as String, callback: onRecordCallback) | ||||
|   } | ||||
|  | ||||
|   @objc | ||||
|   | ||||
| @@ -176,6 +176,7 @@ enum CaptureError { | ||||
|   case noRecordingInProgress | ||||
|   case fileError | ||||
|   case createTempFileError(message: String? = nil) | ||||
|   case createRecordingDirectoryError(message: String? = nil) | ||||
|   case createRecorderError(message: String? = nil) | ||||
|   case videoNotEnabled | ||||
|   case photoNotEnabled | ||||
| @@ -193,6 +194,8 @@ enum CaptureError { | ||||
|       return "file-io-error" | ||||
|     case .createTempFileError: | ||||
|       return "create-temp-file-error" | ||||
|     case .createRecordingDirectoryError: | ||||
|       return "create-recording-directory-error" | ||||
|     case .createRecorderError: | ||||
|       return "create-recorder-error" | ||||
|     case .videoNotEnabled: | ||||
| @@ -218,6 +221,8 @@ enum CaptureError { | ||||
|       return "An unexpected File IO error occured!" | ||||
|     case let .createTempFileError(message: message): | ||||
|       return "Failed to create a temporary file! \(message ?? "(no additional message)")" | ||||
|     case let .createRecordingDirectoryError(message: message): | ||||
|       return "Failed to create a recording directory! \(message ?? "(no additional message)")" | ||||
|     case let .createRecorderError(message: message): | ||||
|       return "Failed to create the AVAssetWriter (Recorder)! \(message ?? "(no additional message)")" | ||||
|     case .videoNotEnabled: | ||||
|   | ||||
| @@ -15,6 +15,7 @@ extension CameraSession { | ||||
|    Starts a video + audio recording with a custom Asset Writer. | ||||
|    */ | ||||
|   func startRecording(options: RecordVideoOptions, | ||||
|                       filePath: String, | ||||
|                       onVideoRecorded: @escaping (_ video: Video) -> Void, | ||||
|                       onError: @escaping (_ error: CameraError) -> Void) { | ||||
|     // Run on Camera Queue | ||||
| @@ -70,7 +71,7 @@ extension CameraSession { | ||||
|         } else { | ||||
|           if status == .completed { | ||||
|             // Recording was successfully saved | ||||
|             let video = Video(path: recordingSession.url.absoluteString, | ||||
|             let video = Video(path: recordingSession.outputDiretory.absoluteString, | ||||
|                               duration: recordingSession.duration, | ||||
|                               size: recordingSession.size ?? CGSize.zero) | ||||
|             onVideoRecorded(video) | ||||
| @@ -81,21 +82,20 @@ extension CameraSession { | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       // Create temporary file | ||||
|       let errorPointer = ErrorPointer(nilLiteral: ()) | ||||
|       let fileExtension = options.fileType.descriptor ?? "mov" | ||||
|       guard let tempFilePath = RCTTempFilePath(fileExtension, errorPointer) else { | ||||
|         let message = errorPointer?.pointee?.description | ||||
|         onError(.capture(.createTempFileError(message: message))) | ||||
|         return | ||||
|       if !FileManager.default.fileExists(atPath: filePath) { | ||||
|         do { | ||||
|           try FileManager.default.createDirectory(atPath: filePath, withIntermediateDirectories: true) | ||||
|         } catch { | ||||
|           onError(.capture(.createRecordingDirectoryError(message: error.localizedDescription))) | ||||
|           return | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       ReactLogger.log(level: .info, message: "Will record to temporary file: \(tempFilePath)") | ||||
|       let tempURL = URL(string: "file://\(tempFilePath)")! | ||||
|       ReactLogger.log(level: .info, message: "Will record to temporary file: \(filePath)") | ||||
|  | ||||
|       do { | ||||
|         // Create RecordingSession for the temp file | ||||
|         let recordingSession = try RecordingSession(url: tempURL, | ||||
|         let recordingSession = try RecordingSession(outputDiretory: filePath, | ||||
|                                                     fileType: options.fileType, | ||||
|                                                     onChunkReady: onChunkReady, | ||||
|                                                     completion: onFinish) | ||||
|   | ||||
| @@ -25,10 +25,10 @@ class ChunkedRecorder: NSObject { | ||||
|   let outputURL: URL | ||||
|   let onChunkReady: ((Chunk) -> Void) | ||||
|    | ||||
|   private var index: UInt64 = 0 | ||||
|   private var chunkIndex: UInt64 = 0 | ||||
|    | ||||
|   init(url: URL, onChunkReady: @escaping ((Chunk) -> Void)) throws { | ||||
|     outputURL = url | ||||
|   init(outputURL: URL, onChunkReady: @escaping ((Chunk) -> Void)) throws { | ||||
|     self.outputURL = outputURL | ||||
|     self.onChunkReady = onChunkReady | ||||
|     guard FileManager.default.fileExists(atPath: outputURL.path) else { | ||||
|       throw CameraError.unknown(message: "output directory does not exist at: \(outputURL.path)", cause: nil) | ||||
| @@ -61,13 +61,11 @@ extension ChunkedRecorder: AVAssetWriterDelegate { | ||||
|   } | ||||
|    | ||||
|   private func saveSegment(_ data: Data) { | ||||
|     defer { | ||||
|       index += 1 | ||||
|     } | ||||
|     let name = String(format: "%06d.mp4", index) | ||||
|     let name = "\(chunkIndex).mp4" | ||||
|     let url = outputURL.appendingPathComponent(name) | ||||
|     save(data: data, url: url) | ||||
|     onChunkReady(url: url, type: .data(index: index)) | ||||
|     onChunkReady(url: url, type: .data(index: chunkIndex)) | ||||
|     chunkIndex += 1 | ||||
|   } | ||||
|    | ||||
|   private func save(data: Data, url: URL) { | ||||
|   | ||||
| @@ -49,8 +49,7 @@ class RecordingSession { | ||||
|   /** | ||||
|    Gets the file URL of the recorded video. | ||||
|    */ | ||||
|   var url: URL { | ||||
|     // FIXME: | ||||
|   var outputDiretory: URL { | ||||
|     return recorder.outputURL | ||||
|   } | ||||
|  | ||||
| @@ -72,14 +71,15 @@ class RecordingSession { | ||||
|     return (lastWrittenTimestamp - startTimestamp).seconds | ||||
|   } | ||||
|  | ||||
|   init(url: URL, | ||||
|   init(outputDiretory: String, | ||||
|        fileType: AVFileType, | ||||
|        onChunkReady: @escaping ((ChunkedRecorder.Chunk) -> Void), | ||||
|        completion: @escaping (RecordingSession, AVAssetWriter.Status, Error?) -> Void) throws { | ||||
|     completionHandler = completion | ||||
|  | ||||
|     do { | ||||
|       recorder = try ChunkedRecorder(url: url.deletingLastPathComponent(), onChunkReady: onChunkReady) | ||||
|       let outputURL = URL(fileURLWithPath: outputDiretory) | ||||
|       recorder = try ChunkedRecorder(outputURL: outputURL, onChunkReady: onChunkReady) | ||||
|       assetWriter = AVAssetWriter(contentType: UTType(fileType.rawValue)!) | ||||
|       assetWriter.shouldOptimizeForNetworkUse = false | ||||
|       assetWriter.outputFileTypeProfile = .mpeg4AppleHLS | ||||
|   | ||||
| @@ -10,18 +10,18 @@ import UIKit | ||||
|  | ||||
|  | ||||
| enum RCTLogLevel: String { | ||||
|     case trace | ||||
|     case info | ||||
|     case warning | ||||
|     case error | ||||
|   case trace | ||||
|   case info | ||||
|   case warning | ||||
|   case error | ||||
| } | ||||
|  | ||||
| enum RCTLogSource { | ||||
|     case native | ||||
|   case native | ||||
| } | ||||
|  | ||||
| func RCTDefaultLogFunction(_ level: RCTLogLevel, _ source: RCTLogSource, _ file: String, _ line: NSNumber, _ message: String) { | ||||
|     print(level.rawValue, "-", message) | ||||
|   print(level.rawValue, "-", message) | ||||
| } | ||||
|  | ||||
| typealias RCTDirectEventBlock = (Any?) -> Void | ||||
| @@ -30,53 +30,73 @@ typealias RCTPromiseRejectBlock = (String, String, NSError?) -> Void | ||||
| typealias RCTResponseSenderBlock = (Any) -> Void | ||||
|  | ||||
| func NSNull() -> [String: String] { | ||||
|     return [:] | ||||
|   return [:] | ||||
| } | ||||
|  | ||||
|  | ||||
| func makeReactError(_ cameraError: CameraError, cause: NSError?) -> [String: Any] { | ||||
|     var causeDictionary: [String: Any]? | ||||
|     if let cause = cause { | ||||
|         causeDictionary = [ | ||||
|             "cause": "\(cause.domain): \(cause.code) \(cause.description)", | ||||
|             "userInfo": cause.userInfo | ||||
|         ] | ||||
|     } | ||||
|     return [ | ||||
|         "error": "\(cameraError.code): \(cameraError.message)", | ||||
|         "extra": [ | ||||
|             "code": cameraError.code, | ||||
|             "message": cameraError.message, | ||||
|             "cause": causeDictionary ?? NSNull(), | ||||
|         ] | ||||
|   var causeDictionary: [String: Any]? | ||||
|   if let cause = cause { | ||||
|     causeDictionary = [ | ||||
|       "cause": "\(cause.domain): \(cause.code) \(cause.description)", | ||||
|       "userInfo": cause.userInfo | ||||
|     ] | ||||
|   } | ||||
|   return [ | ||||
|     "error": "\(cameraError.code): \(cameraError.message)", | ||||
|     "extra": [ | ||||
|       "code": cameraError.code, | ||||
|       "message": cameraError.message, | ||||
|       "cause": causeDictionary ?? NSNull(), | ||||
|     ] | ||||
|   ] | ||||
| } | ||||
|  | ||||
| func makeReactError(_ cameraError: CameraError) -> [String: Any] { | ||||
|     return makeReactError(cameraError, cause: nil) | ||||
|   return makeReactError(cameraError, cause: nil) | ||||
| } | ||||
|  | ||||
|  | ||||
| class RCTFPSGraph: UIView { | ||||
|     convenience init(frame: CGRect, color: UIColor) { | ||||
|         self.init(frame: frame) | ||||
|     } | ||||
|   convenience init(frame: CGRect, color: UIColor) { | ||||
|     self.init(frame: frame) | ||||
|   } | ||||
|    | ||||
|     func onTick(_ tick: CFTimeInterval) { | ||||
|   func onTick(_ tick: CFTimeInterval) { | ||||
|      | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| func RCTTempFilePath(_ ext: String, _ error: ErrorPointer) -> String? { | ||||
|     let directory = NSTemporaryDirectory().appending("ReactNative") | ||||
|     let fm = FileManager.default | ||||
|     if fm.fileExists(atPath: directory) { | ||||
|         try! fm.removeItem(atPath: directory) | ||||
|     } | ||||
|     if !fm.fileExists(atPath: directory) { | ||||
|         try! fm.createDirectory(atPath: directory, withIntermediateDirectories: true) | ||||
|     } | ||||
|     return directory | ||||
|         .appending("/").appending(UUID().uuidString) | ||||
|         .appending(".").appending(ext) | ||||
|   let directory = NSTemporaryDirectory().appending("ReactNative") | ||||
|   let fm = FileManager.default | ||||
|   if fm.fileExists(atPath: directory) { | ||||
|     try! fm.removeItem(atPath: directory) | ||||
|   } | ||||
|   if !fm.fileExists(atPath: directory) { | ||||
|     try! fm.createDirectory(atPath: directory, withIntermediateDirectories: true) | ||||
|   } | ||||
|   return directory | ||||
|     .appending("/").appending(UUID().uuidString) | ||||
|     .appending(".").appending(ext) | ||||
| } | ||||
|  | ||||
|  | ||||
| class RCTViewManager: NSObject { | ||||
|    | ||||
|   var methodQueue: DispatchQueue! { nil } | ||||
|   class func requiresMainQueueSetup() -> Bool { false } | ||||
|   func view() -> UIView! { nil } | ||||
|    | ||||
|   struct Bridge { | ||||
|     let uiManager = UIManager() | ||||
|   } | ||||
|    | ||||
|   struct UIManager { | ||||
|     func view(forReactTag: NSNumber) -> UIView! { | ||||
|       nil | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   let bridge: Bridge = Bridge() | ||||
| } | ||||
|   | ||||
| @@ -11,95 +11,107 @@ import AVFoundation | ||||
|  | ||||
| class ViewController: UIViewController { | ||||
|    | ||||
|     @IBOutlet weak var recordButton: UIButton! | ||||
|   @IBOutlet weak var recordButton: UIButton! | ||||
|    | ||||
|     let cameraView = CameraView() | ||||
|   let cameraView = CameraView() | ||||
|   let filePath: String = { | ||||
|     NSTemporaryDirectory() + "TestRecorder" | ||||
|   }() | ||||
|    | ||||
|     override func viewDidLoad() { | ||||
|         super.viewDidLoad() | ||||
|   override func viewDidLoad() { | ||||
|     super.viewDidLoad() | ||||
|      | ||||
|         cameraView.translatesAutoresizingMaskIntoConstraints = false; | ||||
|         view.insertSubview(cameraView, at: 0) | ||||
|         NSLayoutConstraint.activate([ | ||||
|             cameraView.topAnchor.constraint(equalTo: view.topAnchor), | ||||
|             cameraView.leadingAnchor.constraint(equalTo: view.leadingAnchor), | ||||
|             cameraView.trailingAnchor.constraint(equalTo: view.trailingAnchor), | ||||
|             cameraView.bottomAnchor.constraint(equalTo: view.bottomAnchor), | ||||
|         ]) | ||||
|     try? FileManager.default.removeItem(atPath: filePath) | ||||
|      | ||||
|         recordButton.isHidden = true | ||||
|         cameraView.onInitialized = { _ in | ||||
|             DispatchQueue.main.async { | ||||
|                 self.recordButton.isHidden = false | ||||
|             } | ||||
|     cameraView.translatesAutoresizingMaskIntoConstraints = false; | ||||
|     view.insertSubview(cameraView, at: 0) | ||||
|     NSLayoutConstraint.activate([ | ||||
|       cameraView.topAnchor.constraint(equalTo: view.topAnchor), | ||||
|       cameraView.leadingAnchor.constraint(equalTo: view.leadingAnchor), | ||||
|       cameraView.trailingAnchor.constraint(equalTo: view.trailingAnchor), | ||||
|       cameraView.bottomAnchor.constraint(equalTo: view.bottomAnchor), | ||||
|     ]) | ||||
|      | ||||
|     recordButton.isHidden = true | ||||
|     cameraView.onInitialized = { _ in | ||||
|       DispatchQueue.main.async { | ||||
|         self.recordButton.isHidden = false | ||||
|       } | ||||
|     } | ||||
|     cameraView.onInitReady = { json in | ||||
|       print("onInitReady:", json ?? "nil") | ||||
|     } | ||||
|     cameraView.onVideoChunkReady = { json in | ||||
|       print("onVideoChunkReady:", json ?? "nil") | ||||
|     } | ||||
|      | ||||
|     Task { @MainActor in | ||||
|       await requestAuthorizations() | ||||
|        | ||||
|       cameraView.photo = true | ||||
|       cameraView.video = true | ||||
|       cameraView.audio = false | ||||
|       cameraView.isActive = true | ||||
|       cameraView.cameraId = getCameraDeviceId() as NSString? | ||||
|       cameraView.didSetProps([]) | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   func isAuthorized(for mediaType: AVMediaType) async -> Bool { | ||||
|     let status = AVCaptureDevice.authorizationStatus(for: mediaType) | ||||
|     var isAuthorized = status == .authorized | ||||
|     if status == .notDetermined { | ||||
|       isAuthorized = await AVCaptureDevice.requestAccess(for: mediaType) | ||||
|     } | ||||
|     return isAuthorized | ||||
|   } | ||||
|    | ||||
|    | ||||
|   func requestAuthorizations() async { | ||||
|     guard await isAuthorized(for: .video) else { return } | ||||
|     guard await isAuthorized(for: .audio) else { return } | ||||
|     // Set up the capture session. | ||||
|   } | ||||
|    | ||||
|   private func getCameraDeviceId() -> String? { | ||||
|     let deviceTypes: [AVCaptureDevice.DeviceType] = [ | ||||
|       .builtInWideAngleCamera | ||||
|     ] | ||||
|     let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: deviceTypes, mediaType: .video, position: .back) | ||||
|      | ||||
|     let device = discoverySession.devices.first | ||||
|      | ||||
|     return device?.uniqueID | ||||
|   } | ||||
|    | ||||
|   @IBAction | ||||
|   func toggleRecord(_ button: UIButton) { | ||||
|     if button.title(for: .normal) == "Stop" { | ||||
|        | ||||
|       cameraView.stopRecording(promise: Promise( | ||||
|         resolver: { result in | ||||
|           print("result") | ||||
|         }, rejecter: { code, message, cause in | ||||
|           print("error") | ||||
|         })) | ||||
|        | ||||
|       button.setTitle("Record", for: .normal) | ||||
|       button.configuration = .filled() | ||||
|        | ||||
|     } else { | ||||
|       cameraView.startRecording( | ||||
|         options: [ | ||||
|           "fileType": "mp4", | ||||
|           "videoCodec": "h265", | ||||
|         ], | ||||
|         filePath: filePath) { callback in | ||||
|           print("callback", callback) | ||||
|         } | ||||
|        | ||||
|         Task { @MainActor in | ||||
|             await requestAuthorizations() | ||||
|              | ||||
|             cameraView.photo = true | ||||
|             cameraView.video = true | ||||
|             cameraView.audio = false | ||||
|             cameraView.isActive = true | ||||
|             cameraView.cameraId = getCameraDeviceId() as NSString? | ||||
|             cameraView.didSetProps([]) | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     func isAuthorized(for mediaType: AVMediaType) async -> Bool { | ||||
|         let status = AVCaptureDevice.authorizationStatus(for: mediaType) | ||||
|         var isAuthorized = status == .authorized | ||||
|         if status == .notDetermined { | ||||
|             isAuthorized = await AVCaptureDevice.requestAccess(for: mediaType) | ||||
|         } | ||||
|         return isAuthorized | ||||
|     } | ||||
|  | ||||
|  | ||||
|     func requestAuthorizations() async { | ||||
|         guard await isAuthorized(for: .video) else { return } | ||||
|         guard await isAuthorized(for: .audio) else { return } | ||||
|         // Set up the capture session. | ||||
|     } | ||||
|      | ||||
|     private func getCameraDeviceId() -> String? { | ||||
|         let deviceTypes: [AVCaptureDevice.DeviceType] = [ | ||||
|             .builtInWideAngleCamera | ||||
|         ] | ||||
|         let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: deviceTypes, mediaType: .video, position: .back) | ||||
|          | ||||
|         let device = discoverySession.devices.first | ||||
|          | ||||
|         return device?.uniqueID | ||||
|     } | ||||
|      | ||||
|     @IBAction | ||||
|     func toggleRecord(_ button: UIButton) { | ||||
|         if button.title(for: .normal) == "Stop" { | ||||
|              | ||||
|             cameraView.stopRecording(promise: Promise( | ||||
|                 resolver: { result in | ||||
|                     print("result") | ||||
|                 }, rejecter: { code, message, cause in | ||||
|                     print("error") | ||||
|                 })) | ||||
|              | ||||
|             button.setTitle("Record", for: .normal) | ||||
|             button.configuration = .filled() | ||||
|              | ||||
|         } else { | ||||
|             cameraView.startRecording( | ||||
|                 options: [ | ||||
|                     "fileType": "mp4", | ||||
|                     "videoCodec": "h265", | ||||
|                 ]) { callback in | ||||
|                     print("callback", callback) | ||||
|                 } | ||||
|              | ||||
|             button.setTitle("Stop", for: .normal) | ||||
|             button.configuration = .bordered() | ||||
|         } | ||||
|       button.setTitle("Stop", for: .normal) | ||||
|       button.configuration = .bordered() | ||||
|     } | ||||
|   } | ||||
|    | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import AVFoundation | ||||
| import Foundation | ||||
|  | ||||
| struct RecordVideoOptions { | ||||
|   var fileType: AVFileType = .mov | ||||
|   var fileType: AVFileType = .mp4 | ||||
|   var flash: Torch = .off | ||||
|   var codec: AVVideoCodecType? | ||||
|   /** | ||||
|   | ||||
| @@ -7,6 +7,9 @@ | ||||
| 	objects = { | ||||
|  | ||||
| /* Begin PBXBuildFile section */ | ||||
| 		B31481772C46547B00084A26 /* CameraViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887518125E0102000DB86D6 /* CameraViewManager.swift */; }; | ||||
| 		B31481782C46558C00084A26 /* CameraView+TakePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887517125E0102000DB86D6 /* CameraView+TakePhoto.swift */; }; | ||||
| 		B31481792C46559700084A26 /* CameraView+Focus.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8A1AEC52AD7F08E00169C0D /* CameraView+Focus.swift */; }; | ||||
| 		B3AF8E862C410FB700CC198C /* ReactStubs.m in Sources */ = {isa = PBXBuildFile; fileRef = B3AF8E852C410FB700CC198C /* ReactStubs.m */; }; | ||||
| 		B3AF8E882C41159300CC198C /* ChunkedRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3AF8E872C41159300CC198C /* ChunkedRecorder.swift */; }; | ||||
| 		B3AF8E892C41159300CC198C /* ChunkedRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3AF8E872C41159300CC198C /* ChunkedRecorder.swift */; }; | ||||
| @@ -691,6 +694,8 @@ | ||||
| 				B3EF9F502C3FC31E00832EE7 /* AVCaptureVideoDataOutput+recommendedVideoSettings.swift in Sources */, | ||||
| 				B3EF9F512C3FC31E00832EE7 /* AVCaptureDevice+minFocusDistance.swift in Sources */, | ||||
| 				B3EF9F5B2C3FC33000832EE7 /* AVCaptureDevice.DeviceType+physicalDeviceDescriptor.swift in Sources */, | ||||
| 				B31481792C46559700084A26 /* CameraView+Focus.swift in Sources */, | ||||
| 				B31481772C46547B00084A26 /* CameraViewManager.swift in Sources */, | ||||
| 				B3EF9F522C3FC31E00832EE7 /* AVCaptureDevice+physicalDevices.swift in Sources */, | ||||
| 				B3EF9F532C3FC31E00832EE7 /* AVCaptureDevice+neutralZoom.swift in Sources */, | ||||
| 				B3EF9F542C3FC31E00832EE7 /* AVCaptureDevice.Format+dimensions.swift in Sources */, | ||||
| @@ -709,6 +714,7 @@ | ||||
| 				B3EF9F362C3FC05600832EE7 /* ResizeMode.swift in Sources */, | ||||
| 				B3EF9F312C3FBFD500832EE7 /* AVAssetWriter.Status+descriptor.swift in Sources */, | ||||
| 				B3EF9F292C3FBF2500832EE7 /* Torch.swift in Sources */, | ||||
| 				B31481782C46558C00084A26 /* CameraView+TakePhoto.swift in Sources */, | ||||
| 				B3EF9F2C2C3FBF4A00832EE7 /* EnumParserError.swift in Sources */, | ||||
| 				B3EF9F272C3FBEF800832EE7 /* PixelFormat.swift in Sources */, | ||||
| 				B3EF9F652C3FC43C00832EE7 /* CameraSession+Audio.swift in Sources */, | ||||
|   | ||||
| @@ -26,6 +26,9 @@ interface OnErrorEvent { | ||||
|   message: string | ||||
|   cause?: ErrorWithCause | ||||
| } | ||||
| interface OnInitReadyEvent { | ||||
|   filepath: string | ||||
| } | ||||
| interface OnVideoChunkReadyEvent { | ||||
|   filepath: string | ||||
|   index: number | ||||
| @@ -39,6 +42,7 @@ type NativeCameraViewProps = Omit<CameraProps, 'device' | 'onInitialized' | 'onE | ||||
|   onCodeScanned?: (event: NativeSyntheticEvent<OnCodeScannedEvent>) => void | ||||
|   onStarted?: (event: NativeSyntheticEvent<void>) => void | ||||
|   onStopped?: (event: NativeSyntheticEvent<void>) => void | ||||
|   onInitReady?: (event: NativeSyntheticEvent<OnInitReadyEvent>) => void | ||||
|   onVideoChunkReady?: (event: NativeSyntheticEvent<OnVideoChunkReadyEvent>) => void | ||||
|   onViewReady: () => void | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user