| 
									
										
										
										
											2024-07-12 16:51:09 +01:00
										 |  |  | // | 
					
						
							|  |  |  | //  ChunkedRecorder.swift | 
					
						
							|  |  |  | //  VisionCamera | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | //  Created by Rafael Bastos on 12/07/2024. | 
					
						
							|  |  |  | //  Copyright © 2024 mrousavy. All rights reserved. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import Foundation | 
					
						
							|  |  |  | import AVFoundation | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ChunkedRecorder: NSObject { | 
					
						
							| 
									
										
										
										
											2024-07-28 16:37:20 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-15 08:46:41 +01:00
										 |  |  |   enum ChunkType { | 
					
						
							|  |  |  |     case initialization | 
					
						
							| 
									
										
										
										
											2024-07-16 10:46:24 +01:00
										 |  |  |     case data(index: UInt64, duration: CMTime?) | 
					
						
							| 
									
										
										
										
											2024-07-15 08:46:41 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   struct Chunk { | 
					
						
							|  |  |  |     let url: URL | 
					
						
							|  |  |  |     let type: ChunkType | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2024-07-28 16:37:20 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-12 16:51:09 +01:00
										 |  |  |   let outputURL: URL | 
					
						
							| 
									
										
										
										
											2024-07-15 08:46:41 +01:00
										 |  |  |   let onChunkReady: ((Chunk) -> Void) | 
					
						
							| 
									
										
										
										
											2024-07-28 16:37:20 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-16 07:59:52 +01:00
										 |  |  |   private var chunkIndex: UInt64 = 0 | 
					
						
							| 
									
										
										
										
											2024-07-28 16:37:20 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-16 09:50:21 +01:00
										 |  |  |   init(outputURL: URL, onChunkReady: @escaping ((Chunk) -> Void)) throws { | 
					
						
							|  |  |  |     self.outputURL = outputURL | 
					
						
							| 
									
										
										
										
											2024-07-15 08:46:41 +01:00
										 |  |  |     self.onChunkReady = onChunkReady | 
					
						
							| 
									
										
										
										
											2024-07-12 16:51:09 +01:00
										 |  |  |     guard FileManager.default.fileExists(atPath: outputURL.path) else { | 
					
						
							|  |  |  |       throw CameraError.unknown(message: "output directory does not exist at: \(outputURL.path)", cause: nil) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2024-07-28 16:37:20 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-12 16:51:09 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | extension ChunkedRecorder: AVAssetWriterDelegate { | 
					
						
							| 
									
										
										
										
											2024-07-28 16:37:20 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |   func assetWriter(_ writer: AVAssetWriter, | 
					
						
							| 
									
										
										
										
											2024-07-12 16:51:09 +01:00
										 |  |  |                    didOutputSegmentData segmentData: Data, | 
					
						
							|  |  |  |                    segmentType: AVAssetSegmentType, | 
					
						
							|  |  |  |                    segmentReport: AVAssetSegmentReport?) { | 
					
						
							| 
									
										
										
										
											2024-07-28 16:37:20 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-12 16:51:09 +01:00
										 |  |  |     switch segmentType { | 
					
						
							|  |  |  |     case .initialization: | 
					
						
							|  |  |  |       saveInitSegment(segmentData) | 
					
						
							|  |  |  |     case .separable: | 
					
						
							| 
									
										
										
										
											2024-07-16 10:46:24 +01:00
										 |  |  |       saveSegment(segmentData, report: segmentReport) | 
					
						
							| 
									
										
										
										
											2024-07-12 16:51:09 +01:00
										 |  |  |     @unknown default: | 
					
						
							|  |  |  |       fatalError("Unknown AVAssetSegmentType!") | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2024-07-28 16:37:20 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-15 08:46:41 +01:00
										 |  |  |   private func saveInitSegment(_ data: Data) { | 
					
						
							|  |  |  |     let url = outputURL.appendingPathComponent("init.mp4") | 
					
						
							|  |  |  |     save(data: data, url: url) | 
					
						
							|  |  |  |     onChunkReady(url: url, type: .initialization) | 
					
						
							| 
									
										
										
										
											2024-07-12 16:51:09 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2024-07-28 16:37:20 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-16 10:46:24 +01:00
										 |  |  |   private func saveSegment(_ data: Data, report: AVAssetSegmentReport?) { | 
					
						
							| 
									
										
										
										
											2024-07-16 07:59:52 +01:00
										 |  |  |     let name = "\(chunkIndex).mp4" | 
					
						
							| 
									
										
										
										
											2024-07-15 08:46:41 +01:00
										 |  |  |     let url = outputURL.appendingPathComponent(name) | 
					
						
							|  |  |  |     save(data: data, url: url) | 
					
						
							| 
									
										
										
										
											2024-07-16 10:46:24 +01:00
										 |  |  |     let duration = report? | 
					
						
							|  |  |  |       .trackReports | 
					
						
							|  |  |  |       .filter { $0.mediaType == .video } | 
					
						
							|  |  |  |       .first? | 
					
						
							|  |  |  |       .duration | 
					
						
							|  |  |  |     onChunkReady(url: url, type: .data(index: chunkIndex, duration: duration)) | 
					
						
							| 
									
										
										
										
											2024-07-16 07:59:52 +01:00
										 |  |  |     chunkIndex += 1 | 
					
						
							| 
									
										
										
										
											2024-07-15 08:46:41 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2024-07-28 16:37:20 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-15 08:46:41 +01:00
										 |  |  |   private func save(data: Data, url: URL) { | 
					
						
							| 
									
										
										
										
											2024-07-12 16:51:09 +01:00
										 |  |  |     do { | 
					
						
							| 
									
										
										
										
											2024-07-15 08:46:41 +01:00
										 |  |  |       try data.write(to: url) | 
					
						
							| 
									
										
										
										
											2024-07-12 16:51:09 +01:00
										 |  |  |     } catch { | 
					
						
							| 
									
										
										
										
											2024-07-15 08:46:41 +01:00
										 |  |  |       ReactLogger.log(level: .error, message: "Unable to write \(url): \(error.localizedDescription)") | 
					
						
							| 
									
										
										
										
											2024-07-12 16:51:09 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2024-07-28 16:37:20 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-15 08:46:41 +01:00
										 |  |  |   private func onChunkReady(url: URL, type: ChunkType) { | 
					
						
							|  |  |  |     onChunkReady(Chunk(url: url, type: type)) | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2024-07-28 16:37:20 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-12 16:51:09 +01:00
										 |  |  | } |