@@ -16,6 +16,12 @@ enum BufferType {
16
16
case video
17
17
}
18
18
19
+ // MARK: - RecordingSessionError
20
+
21
+ enum RecordingSessionError : Error {
22
+ case failedToStartSession
23
+ }
24
+
19
25
// MARK: - RecordingSession
20
26
21
27
class RecordingSession {
@@ -59,6 +65,9 @@ class RecordingSession {
59
65
}
60
66
}
61
67
68
+ /**
69
+ Initializes an AssetWriter for video frames (CMSampleBuffers).
70
+ */
62
71
func initializeVideoWriter( withSettings settings: [ String : Any ] , isVideoMirrored: Bool ) {
63
72
guard !settings. isEmpty else {
64
73
ReactLogger . log ( level: . error, message: " Tried to initialize Video Writer with empty settings! " , alsoLogToJS: true )
@@ -83,6 +92,9 @@ class RecordingSession {
83
92
ReactLogger . log ( level: . info, message: " Initialized Video AssetWriter. " )
84
93
}
85
94
95
+ /**
96
+ Initializes an AssetWriter for audio frames (CMSampleBuffers).
97
+ */
86
98
func initializeAudioWriter( withSettings settings: [ String : Any ] ) {
87
99
guard !settings. isEmpty else {
88
100
ReactLogger . log ( level: . error, message: " Tried to initialize Audio Writer with empty settings! " , alsoLogToJS: true )
@@ -99,15 +111,34 @@ class RecordingSession {
99
111
ReactLogger . log ( level: . info, message: " Initialized Audio AssetWriter. " )
100
112
}
101
113
102
- func start( ) {
103
- assetWriter. startWriting ( )
114
+ /**
115
+ Start the Asset Writer(s). If the AssetWriter failed to start, an error will be thrown.
116
+ */
117
+ func start( ) throws {
118
+ ReactLogger . log ( level: . info, message: " Starting Asset Writer(s)... " )
119
+
120
+ let success = assetWriter. startWriting ( )
121
+ if !success {
122
+ ReactLogger . log ( level: . error, message: " Failed to start Asset Writer(s)! " )
123
+ throw RecordingSessionError . failedToStartSession
124
+ }
125
+
104
126
initialTimestamp = CMTime ( seconds: CACurrentMediaTime ( ) , preferredTimescale: 1_000_000_000 )
105
127
assetWriter. startSession ( atSourceTime: initialTimestamp!)
106
128
ReactLogger . log ( level: . info, message: " Started RecordingSession at \( initialTimestamp!. seconds) seconds. " )
107
129
}
108
130
131
+ /**
132
+ Appends a new CMSampleBuffer to the Asset Writer. Use bufferType to specify if this is a video or audio frame.
133
+ The timestamp parameter represents the presentation timestamp of the buffer, which should be synchronized across video and audio frames.
134
+ */
109
135
func appendBuffer( _ buffer: CMSampleBuffer , type bufferType: BufferType , timestamp: CMTime ) {
136
+ guard assetWriter. status == . writing else {
137
+ ReactLogger . log ( level: . error, message: " Frame arrived, but AssetWriter status is \( assetWriter. status. descriptor) ! " )
138
+ return
139
+ }
110
140
if !CMSampleBufferDataIsReady( buffer) {
141
+ ReactLogger . log ( level: . error, message: " Frame arrived, but sample buffer is not ready! " )
111
142
return
112
143
}
113
144
guard let initialTimestamp = initialTimestamp else {
@@ -138,7 +169,7 @@ class RecordingSession {
138
169
bufferAdaptor. append ( imageBuffer, withPresentationTime: timestamp)
139
170
if !hasWrittenFirstVideoFrame {
140
171
hasWrittenFirstVideoFrame = true
141
- ReactLogger . log ( level: . warning, message: " VideoWriter: First frame arrived \( ( timestamp - initialTimestamp ) . seconds) seconds late. " )
172
+ ReactLogger . log ( level: . warning, message: " VideoWriter: First frame arrived \( ( initialTimestamp - timestamp ) . seconds) seconds late. " )
142
173
}
143
174
case . audio:
144
175
guard let audioWriter = audioWriter else {
@@ -156,16 +187,25 @@ class RecordingSession {
156
187
}
157
188
158
189
if assetWriter. status == . failed {
159
- // TODO: Should I call the completion handler or is this instance still valid?
160
190
ReactLogger . log ( level: . error,
161
191
message: " AssetWriter failed to write buffer! Error: \( assetWriter. error? . localizedDescription ?? " none " ) " ,
162
192
alsoLogToJS: true )
193
+ finish ( )
163
194
}
164
195
}
165
196
197
+ /**
198
+ Marks the AssetWriters as finished and stops writing frames. The callback will be invoked either with an error or the status "success".
199
+ */
166
200
func finish( ) {
167
201
ReactLogger . log ( level: . info, message: " Finishing Recording with AssetWriter status \" \( assetWriter. status. descriptor) \" ... " )
168
- if assetWriter. status == . writing {
202
+
203
+ if !hasWrittenFirstVideoFrame {
204
+ let error = NSError ( domain: " capture/aborted " ,
205
+ code: 1 ,
206
+ userInfo: [ NSLocalizedDescriptionKey: " Stopped Recording Session too early, no frames have been recorded! " ] )
207
+ completionHandler ( . failed, error)
208
+ } else if assetWriter. status == . writing {
169
209
bufferAdaptor? . assetWriterInput. markAsFinished ( )
170
210
audioWriter? . markAsFinished ( )
171
211
assetWriter. finishWriting {
0 commit comments