Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e1e074e0f | |||
| b269e9c493 |
@@ -38,11 +38,27 @@ extension CameraSession {
|
|||||||
// Callback for when new chunks are ready
|
// Callback for when new chunks are ready
|
||||||
let onChunkReady: (ChunkedRecorder.Chunk) -> Void = { chunk in
|
let onChunkReady: (ChunkedRecorder.Chunk) -> Void = { chunk in
|
||||||
guard let delegate = self.delegate else {
|
guard let delegate = self.delegate else {
|
||||||
|
ReactLogger.log(level: .warning, message: "Chunk ready but delegate is nil, dropping chunk: \(chunk)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
delegate.onVideoChunkReady(chunk: chunk)
|
delegate.onVideoChunkReady(chunk: chunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Callback for when a chunk write fails (e.g. init file write failure)
|
||||||
|
let onChunkError: (Error) -> Void = { error in
|
||||||
|
ReactLogger.log(level: .error, message: "Chunk write error, stopping recording: \(error.localizedDescription)")
|
||||||
|
// Stop recording immediately
|
||||||
|
if let session = self.recordingSession {
|
||||||
|
session.stop(clock: self.captureSession.clock)
|
||||||
|
}
|
||||||
|
// Surface error to RN
|
||||||
|
if let cameraError = error as? CameraError {
|
||||||
|
onError(cameraError)
|
||||||
|
} else {
|
||||||
|
onError(.capture(.fileError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Callback for when the recording ends
|
// Callback for when the recording ends
|
||||||
let onFinish = { (recordingSession: RecordingSession, status: AVAssetWriter.Status, error: Error?) in
|
let onFinish = { (recordingSession: RecordingSession, status: AVAssetWriter.Status, error: Error?) in
|
||||||
defer {
|
defer {
|
||||||
@@ -98,6 +114,7 @@ extension CameraSession {
|
|||||||
let recordingSession = try RecordingSession(outputDiretory: filePath,
|
let recordingSession = try RecordingSession(outputDiretory: filePath,
|
||||||
fileType: options.fileType,
|
fileType: options.fileType,
|
||||||
onChunkReady: onChunkReady,
|
onChunkReady: onChunkReady,
|
||||||
|
onChunkError: onChunkError,
|
||||||
completion: onFinish)
|
completion: onFinish)
|
||||||
|
|
||||||
// Init Audio + Activate Audio Session (optional)
|
// Init Audio + Activate Audio Session (optional)
|
||||||
|
|||||||
@@ -24,12 +24,14 @@ class ChunkedRecorder: NSObject {
|
|||||||
|
|
||||||
let outputURL: URL
|
let outputURL: URL
|
||||||
let onChunkReady: ((Chunk) -> Void)
|
let onChunkReady: ((Chunk) -> Void)
|
||||||
|
let onError: ((Error) -> Void)?
|
||||||
|
|
||||||
private var chunkIndex: UInt64 = 0
|
private var chunkIndex: UInt64 = 0
|
||||||
|
|
||||||
init(outputURL: URL, onChunkReady: @escaping ((Chunk) -> Void)) throws {
|
init(outputURL: URL, onChunkReady: @escaping ((Chunk) -> Void), onError: ((Error) -> Void)? = nil) throws {
|
||||||
self.outputURL = outputURL
|
self.outputURL = outputURL
|
||||||
self.onChunkReady = onChunkReady
|
self.onChunkReady = onChunkReady
|
||||||
|
self.onError = onError
|
||||||
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)
|
||||||
}
|
}
|
||||||
@@ -56,14 +58,19 @@ extension ChunkedRecorder: AVAssetWriterDelegate {
|
|||||||
|
|
||||||
private func saveInitSegment(_ data: Data) {
|
private func saveInitSegment(_ data: Data) {
|
||||||
let url = outputURL.appendingPathComponent("init.mp4")
|
let url = outputURL.appendingPathComponent("init.mp4")
|
||||||
save(data: data, url: url)
|
do {
|
||||||
|
try data.write(to: url)
|
||||||
onChunkReady(url: url, type: .initialization)
|
onChunkReady(url: url, type: .initialization)
|
||||||
|
} catch {
|
||||||
|
ReactLogger.log(level: .error, message: "Failed to write init file \(url): \(error.localizedDescription)")
|
||||||
|
onError?(CameraError.capture(.fileError))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func saveSegment(_ data: Data, report: AVAssetSegmentReport?) {
|
private func saveSegment(_ data: Data, report: AVAssetSegmentReport?) {
|
||||||
let name = "\(chunkIndex).mp4"
|
let name = "\(chunkIndex).mp4"
|
||||||
let url = outputURL.appendingPathComponent(name)
|
let url = outputURL.appendingPathComponent(name)
|
||||||
save(data: data, url: url)
|
if save(data: data, url: url) {
|
||||||
let duration = report?
|
let duration = report?
|
||||||
.trackReports
|
.trackReports
|
||||||
.filter { $0.mediaType == .video }
|
.filter { $0.mediaType == .video }
|
||||||
@@ -72,12 +79,15 @@ extension ChunkedRecorder: AVAssetWriterDelegate {
|
|||||||
onChunkReady(url: url, type: .data(index: chunkIndex, duration: duration))
|
onChunkReady(url: url, type: .data(index: chunkIndex, duration: duration))
|
||||||
chunkIndex += 1
|
chunkIndex += 1
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func save(data: Data, url: URL) {
|
private func save(data: Data, url: URL) -> Bool {
|
||||||
do {
|
do {
|
||||||
try data.write(to: url)
|
try data.write(to: url)
|
||||||
|
return true
|
||||||
} catch {
|
} catch {
|
||||||
ReactLogger.log(level: .error, message: "Unable to write \(url): \(error.localizedDescription)")
|
ReactLogger.log(level: .error, message: "Unable to write \(url): \(error.localizedDescription)")
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,12 +74,13 @@ class RecordingSession {
|
|||||||
init(outputDiretory: String,
|
init(outputDiretory: String,
|
||||||
fileType: AVFileType,
|
fileType: AVFileType,
|
||||||
onChunkReady: @escaping ((ChunkedRecorder.Chunk) -> Void),
|
onChunkReady: @escaping ((ChunkedRecorder.Chunk) -> Void),
|
||||||
|
onChunkError: ((Error) -> Void)? = nil,
|
||||||
completion: @escaping (RecordingSession, AVAssetWriter.Status, Error?) -> Void) throws {
|
completion: @escaping (RecordingSession, AVAssetWriter.Status, Error?) -> Void) throws {
|
||||||
completionHandler = completion
|
completionHandler = completion
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let outputURL = URL(fileURLWithPath: outputDiretory)
|
let outputURL = URL(fileURLWithPath: outputDiretory)
|
||||||
recorder = try ChunkedRecorder(outputURL: outputURL, onChunkReady: onChunkReady)
|
recorder = try ChunkedRecorder(outputURL: outputURL, onChunkReady: onChunkReady, onError: onChunkError)
|
||||||
assetWriter = AVAssetWriter(contentType: UTType(fileType.rawValue)!)
|
assetWriter = AVAssetWriter(contentType: UTType(fileType.rawValue)!)
|
||||||
assetWriter.shouldOptimizeForNetworkUse = false
|
assetWriter.shouldOptimizeForNetworkUse = false
|
||||||
assetWriter.outputFileTypeProfile = .mpeg4AppleHLS
|
assetWriter.outputFileTypeProfile = .mpeg4AppleHLS
|
||||||
|
|||||||
Reference in New Issue
Block a user