Compare commits
3 Commits
d9a1287b68
...
0a43d7a160
Author | SHA1 | Date | |
---|---|---|---|
|
0a43d7a160 | ||
|
a2ce4df663 | ||
|
89ecb35616 |
@ -62,6 +62,8 @@ public final class CameraView: UIView, CameraSessionDelegate {
|
||||
@objc var onStarted: RCTDirectEventBlock?
|
||||
@objc var onStopped: RCTDirectEventBlock?
|
||||
@objc var onViewReady: RCTDirectEventBlock?
|
||||
@objc var onInitReady: RCTDirectEventBlock?
|
||||
@objc var onVideoChunkReady: RCTDirectEventBlock?
|
||||
@objc var onCodeScanned: RCTDirectEventBlock?
|
||||
// zoom
|
||||
@objc var enableZoomGesture = false {
|
||||
@ -336,6 +338,26 @@ public final class CameraView: UIView, CameraSessionDelegate {
|
||||
#endif
|
||||
}
|
||||
|
||||
func onVideoChunkReady(chunk: ChunkedRecorder.Chunk) {
|
||||
ReactLogger.log(level: .info, message: "Chunk ready: \(chunk)")
|
||||
|
||||
guard let onVideoChunkReady, let onInitReady else {
|
||||
return
|
||||
}
|
||||
|
||||
switch chunk.type {
|
||||
case .initialization:
|
||||
onInitReady([
|
||||
"filepath": chunk.url.path,
|
||||
])
|
||||
case .data(index: let index):
|
||||
onVideoChunkReady([
|
||||
"filepath": chunk.url.path,
|
||||
"index": index,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
func onCodeScanned(codes: [CameraSession.Code], scannerFrame: CameraSession.CodeScannerFrame) {
|
||||
guard let onCodeScanned = onCodeScanned else {
|
||||
return
|
||||
|
@ -55,6 +55,8 @@ RCT_EXPORT_VIEW_PROPERTY(onInitialized, RCTDirectEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onStarted, RCTDirectEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onStopped, RCTDirectEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onViewReady, RCTDirectEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onInitReady, RCTDirectEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onVideoChunkReady, RCTDirectEventBlock);
|
||||
// Code Scanner
|
||||
RCT_EXPORT_VIEW_PROPERTY(codeScannerOptions, NSDictionary);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onCodeScanned, RCTDirectEventBlock);
|
||||
|
@ -34,6 +34,14 @@ extension CameraSession {
|
||||
|
||||
let enableAudio = self.configuration?.audio != .disabled
|
||||
|
||||
// Callback for when new chunks are ready
|
||||
let onChunkReady: (ChunkedRecorder.Chunk) -> Void = { chunk in
|
||||
guard let delegate = self.delegate else {
|
||||
return
|
||||
}
|
||||
delegate.onVideoChunkReady(chunk: chunk)
|
||||
}
|
||||
|
||||
// Callback for when the recording ends
|
||||
let onFinish = { (recordingSession: RecordingSession, status: AVAssetWriter.Status, error: Error?) in
|
||||
defer {
|
||||
@ -89,6 +97,7 @@ extension CameraSession {
|
||||
// Create RecordingSession for the temp file
|
||||
let recordingSession = try RecordingSession(url: tempURL,
|
||||
fileType: options.fileType,
|
||||
onChunkReady: onChunkReady,
|
||||
completion: onFinish)
|
||||
|
||||
// Init Audio + Activate Audio Session (optional)
|
||||
|
@ -33,6 +33,10 @@ protocol CameraSessionDelegate: AnyObject {
|
||||
Called for every frame (if video or frameProcessor is enabled)
|
||||
*/
|
||||
func onFrame(sampleBuffer: CMSampleBuffer)
|
||||
/**
|
||||
Called whenever a new video chunk is available
|
||||
*/
|
||||
func onVideoChunkReady(chunk: ChunkedRecorder.Chunk)
|
||||
/**
|
||||
Called whenever a QR/Barcode has been scanned. Only if the CodeScanner Output is enabled
|
||||
*/
|
||||
|
@ -12,14 +12,24 @@ import AVFoundation
|
||||
|
||||
class ChunkedRecorder: NSObject {
|
||||
|
||||
enum ChunkType {
|
||||
case initialization
|
||||
case data(index: UInt64)
|
||||
}
|
||||
|
||||
struct Chunk {
|
||||
let url: URL
|
||||
let type: ChunkType
|
||||
}
|
||||
|
||||
let outputURL: URL
|
||||
let onChunkReady: ((Chunk) -> Void)
|
||||
|
||||
private var initSegment: Data?
|
||||
private var index: Int = 0
|
||||
private var index: UInt64 = 0
|
||||
|
||||
init(url: URL) throws {
|
||||
init(url: URL, onChunkReady: @escaping ((Chunk) -> Void)) throws {
|
||||
outputURL = url
|
||||
|
||||
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)
|
||||
}
|
||||
@ -45,26 +55,31 @@ extension ChunkedRecorder: AVAssetWriterDelegate {
|
||||
}
|
||||
|
||||
private func saveInitSegment(_ data: Data) {
|
||||
initSegment = data
|
||||
let url = outputURL.appendingPathComponent("init.mp4")
|
||||
save(data: data, url: url)
|
||||
onChunkReady(url: url, type: .initialization)
|
||||
}
|
||||
|
||||
private func saveSegment(_ data: Data) {
|
||||
guard let initSegment else {
|
||||
print("missing init segment")
|
||||
return
|
||||
}
|
||||
|
||||
let file = String(format: "%06d.mp4", index)
|
||||
defer {
|
||||
index += 1
|
||||
let url = outputURL.appendingPathComponent(file)
|
||||
}
|
||||
let name = String(format: "%06d.mp4", index)
|
||||
let url = outputURL.appendingPathComponent(name)
|
||||
save(data: data, url: url)
|
||||
onChunkReady(url: url, type: .data(index: index))
|
||||
}
|
||||
|
||||
private func save(data: Data, url: URL) {
|
||||
do {
|
||||
let outputData = initSegment + data
|
||||
try outputData.write(to: url)
|
||||
print("writing", data.count, "to", url)
|
||||
try data.write(to: url)
|
||||
} catch {
|
||||
print("Error--->", error)
|
||||
ReactLogger.log(level: .error, message: "Unable to write \(url): \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
private func onChunkReady(url: URL, type: ChunkType) {
|
||||
onChunkReady(Chunk(url: url, type: type))
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -74,11 +74,12 @@ class RecordingSession {
|
||||
|
||||
init(url: URL,
|
||||
fileType: AVFileType,
|
||||
onChunkReady: @escaping ((ChunkedRecorder.Chunk) -> Void),
|
||||
completion: @escaping (RecordingSession, AVAssetWriter.Status, Error?) -> Void) throws {
|
||||
completionHandler = completion
|
||||
|
||||
do {
|
||||
recorder = try ChunkedRecorder(url: url.deletingLastPathComponent())
|
||||
recorder = try ChunkedRecorder(url: url.deletingLastPathComponent(), onChunkReady: onChunkReady)
|
||||
assetWriter = AVAssetWriter(contentType: UTType(fileType.rawValue)!)
|
||||
assetWriter.shouldOptimizeForNetworkUse = false
|
||||
assetWriter.outputFileTypeProfile = .mpeg4AppleHLS
|
||||
|
Loading…
Reference in New Issue
Block a user