Compare commits

...

7 Commits

Author SHA1 Message Date
Rui Rodrigues
694d9cfa8c add log warning that either onInitReady or onVideoChunkReady are not valid
- however I am not seeing the log when running from yarn
2024-07-16 10:20:13 +01:00
Rui Rodrigues
91767e71c8 default RecordVideoOptions.fileType to mp4
- mov does not support fragmented mp4
2024-07-16 09:50:44 +01:00
Rui Rodrigues
9f2c7906e5 add filePath to define recording directory
- add CaptureError.createRecordingDirectoryError
- stub RCTViewManager to be able to compile CameraViewManager
2024-07-16 09:50:21 +01:00
Rui Rodrigues
621bfe333c format identation 2024-07-16 08:17:06 +01:00
Rui Rodrigues
20f8fa2937 test onInitReady and onVideoChunkReady 2024-07-16 08:07:21 +01:00
Rui Rodrigues
b03f9ea423 add onInitReady to video-camera react native side 2024-07-16 08:04:05 +01:00
Rui Rodrigues
98d90a6442 remove zero padding from chunks file names
- rename index to chunkIndex
2024-07-16 07:59:52 +01:00
13 changed files with 202 additions and 154 deletions

View File

@ -11,7 +11,7 @@ import AVFoundation
// MARK: - CameraView + AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate // MARK: - CameraView + AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate
extension 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 // Type-safety
let callback = Callback(jsCallback) let callback = Callback(jsCallback)
@ -21,6 +21,7 @@ extension CameraView: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAud
// Start Recording with success and error callbacks // Start Recording with success and error callbacks
cameraSession.startRecording( cameraSession.startRecording(
options: options, options: options,
filePath: filePath,
onVideoRecorded: { video in onVideoRecorded: { video in
callback.resolve(video.toJSValue()) callback.resolve(video.toJSValue())
}, },

View File

@ -342,6 +342,7 @@ public final class CameraView: UIView, CameraSessionDelegate {
ReactLogger.log(level: .info, message: "Chunk ready: \(chunk)") ReactLogger.log(level: .info, message: "Chunk ready: \(chunk)")
guard let onVideoChunkReady, let onInitReady else { guard let onVideoChunkReady, let onInitReady else {
ReactLogger.log(level: .warning, message: "Either onInitReady or onVideoChunkReady are not valid!")
return return
} }

View File

@ -64,7 +64,8 @@ RCT_EXPORT_VIEW_PROPERTY(onCodeScanned, RCTDirectEventBlock);
// Camera View Functions // Camera View Functions
RCT_EXTERN_METHOD(startRecording RCT_EXTERN_METHOD(startRecording
: (nonnull NSNumber*)node options : (nonnull NSNumber*)node options
: (NSDictionary*)options onRecordCallback : (NSDictionary*)options filePath
: (NSString*)filePath onRecordCallback
: (RCTResponseSenderBlock)onRecordCallback); : (RCTResponseSenderBlock)onRecordCallback);
RCT_EXTERN_METHOD(pauseRecording RCT_EXTERN_METHOD(pauseRecording
: (nonnull NSNumber*)node resolve : (nonnull NSNumber*)node resolve

View File

@ -43,9 +43,9 @@ final class CameraViewManager: RCTViewManager {
// This means that any errors that occur in this function have to be delegated through // 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. // the callback, but I'd prefer for them to throw for the original function instead.
@objc @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) let component = getCameraView(withTag: node)
component.startRecording(options: options, callback: onRecordCallback) component.startRecording(options: options, filePath: filePath as String, callback: onRecordCallback)
} }
@objc @objc

View File

@ -176,6 +176,7 @@ enum CaptureError {
case noRecordingInProgress case noRecordingInProgress
case fileError case fileError
case createTempFileError(message: String? = nil) case createTempFileError(message: String? = nil)
case createRecordingDirectoryError(message: String? = nil)
case createRecorderError(message: String? = nil) case createRecorderError(message: String? = nil)
case videoNotEnabled case videoNotEnabled
case photoNotEnabled case photoNotEnabled
@ -193,6 +194,8 @@ enum CaptureError {
return "file-io-error" return "file-io-error"
case .createTempFileError: case .createTempFileError:
return "create-temp-file-error" return "create-temp-file-error"
case .createRecordingDirectoryError:
return "create-recording-directory-error"
case .createRecorderError: case .createRecorderError:
return "create-recorder-error" return "create-recorder-error"
case .videoNotEnabled: case .videoNotEnabled:
@ -218,6 +221,8 @@ enum CaptureError {
return "An unexpected File IO error occured!" return "An unexpected File IO error occured!"
case let .createTempFileError(message: message): case let .createTempFileError(message: message):
return "Failed to create a temporary file! \(message ?? "(no additional 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): case let .createRecorderError(message: message):
return "Failed to create the AVAssetWriter (Recorder)! \(message ?? "(no additional message)")" return "Failed to create the AVAssetWriter (Recorder)! \(message ?? "(no additional message)")"
case .videoNotEnabled: case .videoNotEnabled:

View File

@ -15,6 +15,7 @@ extension CameraSession {
Starts a video + audio recording with a custom Asset Writer. Starts a video + audio recording with a custom Asset Writer.
*/ */
func startRecording(options: RecordVideoOptions, func startRecording(options: RecordVideoOptions,
filePath: String,
onVideoRecorded: @escaping (_ video: Video) -> Void, onVideoRecorded: @escaping (_ video: Video) -> Void,
onError: @escaping (_ error: CameraError) -> Void) { onError: @escaping (_ error: CameraError) -> Void) {
// Run on Camera Queue // Run on Camera Queue
@ -70,7 +71,7 @@ extension CameraSession {
} else { } else {
if status == .completed { if status == .completed {
// Recording was successfully saved // Recording was successfully saved
let video = Video(path: recordingSession.url.absoluteString, let video = Video(path: recordingSession.outputDiretory.absoluteString,
duration: recordingSession.duration, duration: recordingSession.duration,
size: recordingSession.size ?? CGSize.zero) size: recordingSession.size ?? CGSize.zero)
onVideoRecorded(video) onVideoRecorded(video)
@ -81,21 +82,20 @@ extension CameraSession {
} }
} }
// Create temporary file if !FileManager.default.fileExists(atPath: filePath) {
let errorPointer = ErrorPointer(nilLiteral: ()) do {
let fileExtension = options.fileType.descriptor ?? "mov" try FileManager.default.createDirectory(atPath: filePath, withIntermediateDirectories: true)
guard let tempFilePath = RCTTempFilePath(fileExtension, errorPointer) else { } catch {
let message = errorPointer?.pointee?.description onError(.capture(.createRecordingDirectoryError(message: error.localizedDescription)))
onError(.capture(.createTempFileError(message: message))) return
return }
} }
ReactLogger.log(level: .info, message: "Will record to temporary file: \(tempFilePath)") ReactLogger.log(level: .info, message: "Will record to temporary file: \(filePath)")
let tempURL = URL(string: "file://\(tempFilePath)")!
do { do {
// Create RecordingSession for the temp file // Create RecordingSession for the temp file
let recordingSession = try RecordingSession(url: tempURL, let recordingSession = try RecordingSession(outputDiretory: filePath,
fileType: options.fileType, fileType: options.fileType,
onChunkReady: onChunkReady, onChunkReady: onChunkReady,
completion: onFinish) completion: onFinish)

View File

@ -25,10 +25,10 @@ class ChunkedRecorder: NSObject {
let outputURL: URL let outputURL: URL
let onChunkReady: ((Chunk) -> Void) let onChunkReady: ((Chunk) -> Void)
private var index: UInt64 = 0 private var chunkIndex: UInt64 = 0
init(url: URL, onChunkReady: @escaping ((Chunk) -> Void)) throws { init(outputURL: URL, onChunkReady: @escaping ((Chunk) -> Void)) throws {
outputURL = url self.outputURL = outputURL
self.onChunkReady = onChunkReady self.onChunkReady = onChunkReady
guard FileManager.default.fileExists(atPath: outputURL.path) else { guard FileManager.default.fileExists(atPath: outputURL.path) else {
throw CameraError.unknown(message: "output directory does not exist at: \(outputURL.path)", cause: nil) 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) { private func saveSegment(_ data: Data) {
defer { let name = "\(chunkIndex).mp4"
index += 1
}
let name = String(format: "%06d.mp4", index)
let url = outputURL.appendingPathComponent(name) let url = outputURL.appendingPathComponent(name)
save(data: data, url: url) 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) { private func save(data: Data, url: URL) {

View File

@ -49,8 +49,7 @@ class RecordingSession {
/** /**
Gets the file URL of the recorded video. Gets the file URL of the recorded video.
*/ */
var url: URL { var outputDiretory: URL {
// FIXME:
return recorder.outputURL return recorder.outputURL
} }
@ -72,14 +71,15 @@ class RecordingSession {
return (lastWrittenTimestamp - startTimestamp).seconds return (lastWrittenTimestamp - startTimestamp).seconds
} }
init(url: URL, init(outputDiretory: String,
fileType: AVFileType, fileType: AVFileType,
onChunkReady: @escaping ((ChunkedRecorder.Chunk) -> Void), onChunkReady: @escaping ((ChunkedRecorder.Chunk) -> Void),
completion: @escaping (RecordingSession, AVAssetWriter.Status, Error?) -> Void) throws { completion: @escaping (RecordingSession, AVAssetWriter.Status, Error?) -> Void) throws {
completionHandler = completion completionHandler = completion
do { 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 = AVAssetWriter(contentType: UTType(fileType.rawValue)!)
assetWriter.shouldOptimizeForNetworkUse = false assetWriter.shouldOptimizeForNetworkUse = false
assetWriter.outputFileTypeProfile = .mpeg4AppleHLS assetWriter.outputFileTypeProfile = .mpeg4AppleHLS

View File

@ -10,18 +10,18 @@ import UIKit
enum RCTLogLevel: String { enum RCTLogLevel: String {
case trace case trace
case info case info
case warning case warning
case error case error
} }
enum RCTLogSource { enum RCTLogSource {
case native case native
} }
func RCTDefaultLogFunction(_ level: RCTLogLevel, _ source: RCTLogSource, _ file: String, _ line: NSNumber, _ message: String) { func RCTDefaultLogFunction(_ level: RCTLogLevel, _ source: RCTLogSource, _ file: String, _ line: NSNumber, _ message: String) {
print(level.rawValue, "-", message) print(level.rawValue, "-", message)
} }
typealias RCTDirectEventBlock = (Any?) -> Void typealias RCTDirectEventBlock = (Any?) -> Void
@ -30,53 +30,73 @@ typealias RCTPromiseRejectBlock = (String, String, NSError?) -> Void
typealias RCTResponseSenderBlock = (Any) -> Void typealias RCTResponseSenderBlock = (Any) -> Void
func NSNull() -> [String: String] { func NSNull() -> [String: String] {
return [:] return [:]
} }
func makeReactError(_ cameraError: CameraError, cause: NSError?) -> [String: Any] { func makeReactError(_ cameraError: CameraError, cause: NSError?) -> [String: Any] {
var causeDictionary: [String: Any]? var causeDictionary: [String: Any]?
if let cause = cause { if let cause = cause {
causeDictionary = [ causeDictionary = [
"cause": "\(cause.domain): \(cause.code) \(cause.description)", "cause": "\(cause.domain): \(cause.code) \(cause.description)",
"userInfo": cause.userInfo "userInfo": cause.userInfo
]
}
return [
"error": "\(cameraError.code): \(cameraError.message)",
"extra": [
"code": cameraError.code,
"message": cameraError.message,
"cause": causeDictionary ?? NSNull(),
]
] ]
}
return [
"error": "\(cameraError.code): \(cameraError.message)",
"extra": [
"code": cameraError.code,
"message": cameraError.message,
"cause": causeDictionary ?? NSNull(),
]
]
} }
func makeReactError(_ cameraError: CameraError) -> [String: Any] { func makeReactError(_ cameraError: CameraError) -> [String: Any] {
return makeReactError(cameraError, cause: nil) return makeReactError(cameraError, cause: nil)
} }
class RCTFPSGraph: UIView { class RCTFPSGraph: UIView {
convenience init(frame: CGRect, color: UIColor) { convenience init(frame: CGRect, color: UIColor) {
self.init(frame: frame) self.init(frame: frame)
} }
func onTick(_ tick: CFTimeInterval) {
func onTick(_ tick: CFTimeInterval) { }
}
} }
func RCTTempFilePath(_ ext: String, _ error: ErrorPointer) -> String? { func RCTTempFilePath(_ ext: String, _ error: ErrorPointer) -> String? {
let directory = NSTemporaryDirectory().appending("ReactNative") let directory = NSTemporaryDirectory().appending("ReactNative")
let fm = FileManager.default let fm = FileManager.default
if fm.fileExists(atPath: directory) { if fm.fileExists(atPath: directory) {
try! fm.removeItem(atPath: directory) try! fm.removeItem(atPath: directory)
} }
if !fm.fileExists(atPath: directory) { if !fm.fileExists(atPath: directory) {
try! fm.createDirectory(atPath: directory, withIntermediateDirectories: true) try! fm.createDirectory(atPath: directory, withIntermediateDirectories: true)
} }
return directory return directory
.appending("/").appending(UUID().uuidString) .appending("/").appending(UUID().uuidString)
.appending(".").appending(ext) .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()
} }

View File

@ -10,96 +10,108 @@ import UIKit
import AVFoundation import AVFoundation
class ViewController: UIViewController { class ViewController: UIViewController {
@IBOutlet weak var recordButton: UIButton!
let cameraView = CameraView()
let filePath: String = {
NSTemporaryDirectory() + "TestRecorder"
}()
override func viewDidLoad() {
super.viewDidLoad()
@IBOutlet weak var recordButton: UIButton! try? FileManager.default.removeItem(atPath: filePath)
let cameraView = CameraView() cameraView.translatesAutoresizingMaskIntoConstraints = false;
view.insertSubview(cameraView, at: 0)
override func viewDidLoad() { NSLayoutConstraint.activate([
super.viewDidLoad() cameraView.topAnchor.constraint(equalTo: view.topAnchor),
cameraView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
cameraView.translatesAutoresizingMaskIntoConstraints = false; cameraView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
view.insertSubview(cameraView, at: 0) cameraView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
NSLayoutConstraint.activate([ ])
cameraView.topAnchor.constraint(equalTo: view.topAnchor),
cameraView.leadingAnchor.constraint(equalTo: view.leadingAnchor), recordButton.isHidden = true
cameraView.trailingAnchor.constraint(equalTo: view.trailingAnchor), cameraView.onInitialized = { _ in
cameraView.bottomAnchor.constraint(equalTo: view.bottomAnchor), DispatchQueue.main.async {
]) self.recordButton.isHidden = false
}
recordButton.isHidden = true }
cameraView.onInitialized = { _ in cameraView.onInitReady = { json in
DispatchQueue.main.async { print("onInitReady:", json ?? "nil")
self.recordButton.isHidden = false }
} cameraView.onVideoChunkReady = { json in
} print("onVideoChunkReady:", json ?? "nil")
}
Task { @MainActor in
await requestAuthorizations() Task { @MainActor in
await requestAuthorizations()
cameraView.photo = true
cameraView.video = true cameraView.photo = true
cameraView.audio = false cameraView.video = true
cameraView.isActive = true cameraView.audio = false
cameraView.cameraId = getCameraDeviceId() as NSString? cameraView.isActive = true
cameraView.didSetProps([]) 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)
} }
button.setTitle("Stop", for: .normal)
button.configuration = .bordered()
} }
}
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()
}
}
} }

View File

@ -10,7 +10,7 @@ import AVFoundation
import Foundation import Foundation
struct RecordVideoOptions { struct RecordVideoOptions {
var fileType: AVFileType = .mov var fileType: AVFileType = .mp4
var flash: Torch = .off var flash: Torch = .off
var codec: AVVideoCodecType? var codec: AVVideoCodecType?
/** /**

View File

@ -7,6 +7,9 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* 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 */; }; B3AF8E862C410FB700CC198C /* ReactStubs.m in Sources */ = {isa = PBXBuildFile; fileRef = B3AF8E852C410FB700CC198C /* ReactStubs.m */; };
B3AF8E882C41159300CC198C /* ChunkedRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3AF8E872C41159300CC198C /* ChunkedRecorder.swift */; }; B3AF8E882C41159300CC198C /* ChunkedRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3AF8E872C41159300CC198C /* ChunkedRecorder.swift */; };
B3AF8E892C41159300CC198C /* 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 */, B3EF9F502C3FC31E00832EE7 /* AVCaptureVideoDataOutput+recommendedVideoSettings.swift in Sources */,
B3EF9F512C3FC31E00832EE7 /* AVCaptureDevice+minFocusDistance.swift in Sources */, B3EF9F512C3FC31E00832EE7 /* AVCaptureDevice+minFocusDistance.swift in Sources */,
B3EF9F5B2C3FC33000832EE7 /* AVCaptureDevice.DeviceType+physicalDeviceDescriptor.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 */, B3EF9F522C3FC31E00832EE7 /* AVCaptureDevice+physicalDevices.swift in Sources */,
B3EF9F532C3FC31E00832EE7 /* AVCaptureDevice+neutralZoom.swift in Sources */, B3EF9F532C3FC31E00832EE7 /* AVCaptureDevice+neutralZoom.swift in Sources */,
B3EF9F542C3FC31E00832EE7 /* AVCaptureDevice.Format+dimensions.swift in Sources */, B3EF9F542C3FC31E00832EE7 /* AVCaptureDevice.Format+dimensions.swift in Sources */,
@ -709,6 +714,7 @@
B3EF9F362C3FC05600832EE7 /* ResizeMode.swift in Sources */, B3EF9F362C3FC05600832EE7 /* ResizeMode.swift in Sources */,
B3EF9F312C3FBFD500832EE7 /* AVAssetWriter.Status+descriptor.swift in Sources */, B3EF9F312C3FBFD500832EE7 /* AVAssetWriter.Status+descriptor.swift in Sources */,
B3EF9F292C3FBF2500832EE7 /* Torch.swift in Sources */, B3EF9F292C3FBF2500832EE7 /* Torch.swift in Sources */,
B31481782C46558C00084A26 /* CameraView+TakePhoto.swift in Sources */,
B3EF9F2C2C3FBF4A00832EE7 /* EnumParserError.swift in Sources */, B3EF9F2C2C3FBF4A00832EE7 /* EnumParserError.swift in Sources */,
B3EF9F272C3FBEF800832EE7 /* PixelFormat.swift in Sources */, B3EF9F272C3FBEF800832EE7 /* PixelFormat.swift in Sources */,
B3EF9F652C3FC43C00832EE7 /* CameraSession+Audio.swift in Sources */, B3EF9F652C3FC43C00832EE7 /* CameraSession+Audio.swift in Sources */,

View File

@ -26,6 +26,9 @@ interface OnErrorEvent {
message: string message: string
cause?: ErrorWithCause cause?: ErrorWithCause
} }
interface OnInitReadyEvent {
filepath: string
}
interface OnVideoChunkReadyEvent { interface OnVideoChunkReadyEvent {
filepath: string filepath: string
index: number index: number
@ -39,6 +42,7 @@ type NativeCameraViewProps = Omit<CameraProps, 'device' | 'onInitialized' | 'onE
onCodeScanned?: (event: NativeSyntheticEvent<OnCodeScannedEvent>) => void onCodeScanned?: (event: NativeSyntheticEvent<OnCodeScannedEvent>) => void
onStarted?: (event: NativeSyntheticEvent<void>) => void onStarted?: (event: NativeSyntheticEvent<void>) => void
onStopped?: (event: NativeSyntheticEvent<void>) => void onStopped?: (event: NativeSyntheticEvent<void>) => void
onInitReady?: (event: NativeSyntheticEvent<OnInitReadyEvent>) => void
onVideoChunkReady?: (event: NativeSyntheticEvent<OnVideoChunkReadyEvent>) => void onVideoChunkReady?: (event: NativeSyntheticEvent<OnVideoChunkReadyEvent>) => void
onViewReady: () => void onViewReady: () => void
} }