Skip to content

Commit 49d4108

Browse files
committed
Simplify cancellation and fix memory leak
1 parent 865e524 commit 49d4108

File tree

2 files changed

+25
-34
lines changed

2 files changed

+25
-34
lines changed

Readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ When you're integrating this into an app with Xcode then go to your project's Pa
3434
When you're integrating this using SPM on its own then add this to the list of dependencies your Package.swift file:
3535

3636
```swift
37-
.package(url: "https://github.com/samsonjs/SJSAssetExportSession.git", .upToNextMajor(from: "0.3.5"))
37+
.package(url: "https://github.com/samsonjs/SJSAssetExportSession.git", .upToNextMajor(from: "0.3.7"))
3838
```
3939

4040
and then add `"SJSAssetExportSession"` to the list of dependencies in your target as well.

Sources/SJSAssetExportSession/SampleWriter.swift

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,12 @@ actor SampleWriter {
4141

4242
// MARK: Internal state
4343

44-
private let reader: AVAssetReader
45-
private let writer: AVAssetWriter
44+
private var reader: AVAssetReader?
45+
private var writer: AVAssetWriter?
4646
private var audioOutput: AVAssetReaderAudioMixOutput?
4747
private var audioInput: AVAssetWriterInput?
4848
private var videoOutput: AVAssetReaderVideoCompositionOutput?
4949
private var videoInput: AVAssetWriterInput?
50-
private var isCancelled = false
5150

5251
nonisolated init(
5352
asset: sending AVAsset,
@@ -107,34 +106,39 @@ actor SampleWriter {
107106
}
108107

109108
func writeSamples() async throws {
109+
guard let reader, let writer else { throw CancellationError() }
110110
try Task.checkCancellation()
111111

112+
// Clear all of these properties otherwise when we get cancelled then we leak a bunch of
113+
// pixel buffers.
114+
defer {
115+
if Task.isCancelled {
116+
reader.cancelReading()
117+
writer.cancelWriting()
118+
}
119+
self.reader = nil
120+
self.writer = nil
121+
audioInput = nil
122+
audioOutput = nil
123+
videoInput = nil
124+
videoOutput = nil
125+
}
126+
112127
progressContinuation.yield(0.0)
113128

114129
writer.startWriting()
115130
writer.startSession(atSourceTime: timeRange.start)
116131
reader.startReading()
132+
117133
try Task.checkCancellation()
118134

119135
startEncodingAudioTracks()
120136
startEncodingVideoTracks()
121137

122138
while reader.status == .reading, writer.status == .writing {
123-
guard !Task.isCancelled else {
124-
// Flag so that we stop writing samples
125-
isCancelled = true
126-
throw CancellationError()
127-
}
128-
129139
try await Task.sleep(for: .milliseconds(10))
130140
}
131141

132-
guard !isCancelled, reader.status != .cancelled, writer.status != .cancelled else {
133-
log.debug("Cancelled before writing samples")
134-
reader.cancelReading()
135-
writer.cancelWriting()
136-
throw CancellationError()
137-
}
138142
guard writer.status != .failed else {
139143
reader.cancelReading()
140144
throw Error.writeFailure(writer.error)
@@ -169,15 +173,15 @@ actor SampleWriter {
169173
let audioOutput = AVAssetReaderAudioMixOutput(audioTracks: audioTracks, audioSettings: nil)
170174
audioOutput.alwaysCopiesSampleData = false
171175
audioOutput.audioMix = audioMix
172-
guard reader.canAdd(audioOutput) else {
176+
guard let reader, reader.canAdd(audioOutput) else {
173177
throw Error.setupFailure(.cannotAddAudioOutput)
174178
}
175179
reader.add(audioOutput)
176180
self.audioOutput = audioOutput
177181

178182
let audioInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioOutputSettings)
179183
audioInput.expectsMediaDataInRealTime = false
180-
guard writer.canAdd(audioInput) else {
184+
guard let writer, writer.canAdd(audioInput) else {
181185
throw Error.setupFailure(.cannotAddAudioInput)
182186
}
183187
writer.add(audioInput)
@@ -193,15 +197,15 @@ actor SampleWriter {
193197
)
194198
videoOutput.alwaysCopiesSampleData = false
195199
videoOutput.videoComposition = videoComposition
196-
guard reader.canAdd(videoOutput) else {
200+
guard let reader, reader.canAdd(videoOutput) else {
197201
throw Error.setupFailure(.cannotAddVideoOutput)
198202
}
199203
reader.add(videoOutput)
200204
self.videoOutput = videoOutput
201205

202206
let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoOutputSettings)
203207
videoInput.expectsMediaDataInRealTime = false
204-
guard writer.canAdd(videoInput) else {
208+
guard let writer, writer.canAdd(videoInput) else {
205209
throw Error.setupFailure(.cannotAddVideoInput)
206210
}
207211
writer.add(videoInput)
@@ -234,13 +238,6 @@ actor SampleWriter {
234238
}
235239

236240
private func writeAllReadySamples() {
237-
guard !isCancelled else {
238-
log.debug("Cancelled while writing samples")
239-
reader.cancelReading()
240-
writer.cancelWriting()
241-
return
242-
}
243-
244241
if let audioInput, let audioOutput {
245242
let hasMoreAudio = writeReadySamples(output: audioOutput, input: audioInput)
246243
if !hasMoreAudio { log.debug("Finished encoding audio") }
@@ -252,13 +249,7 @@ actor SampleWriter {
252249

253250
private func writeReadySamples(output: AVAssetReaderOutput, input: AVAssetWriterInput) -> Bool {
254251
while input.isReadyForMoreMediaData {
255-
guard !isCancelled else {
256-
log.debug("Cancelled while writing samples")
257-
reader.cancelReading()
258-
writer.cancelWriting()
259-
return false
260-
}
261-
guard reader.status == .reading && writer.status == .writing,
252+
guard reader?.status == .reading && writer?.status == .writing,
262253
let sampleBuffer = output.copyNextSampleBuffer() else {
263254
input.markAsFinished()
264255
return false

0 commit comments

Comments
 (0)