Simplify cancellation and fix memory leak

This commit is contained in:
Sami Samhuri 2025-01-19 16:17:50 -08:00
parent 865e524be6
commit 49d41080bb
No known key found for this signature in database
2 changed files with 25 additions and 34 deletions

View file

@ -34,7 +34,7 @@ When you're integrating this into an app with Xcode then go to your project's Pa
When you're integrating this using SPM on its own then add this to the list of dependencies your Package.swift file:
```swift
.package(url: "https://github.com/samsonjs/SJSAssetExportSession.git", .upToNextMajor(from: "0.3.5"))
.package(url: "https://github.com/samsonjs/SJSAssetExportSession.git", .upToNextMajor(from: "0.3.7"))
```
and then add `"SJSAssetExportSession"` to the list of dependencies in your target as well.

View file

@ -41,13 +41,12 @@ actor SampleWriter {
// MARK: Internal state
private let reader: AVAssetReader
private let writer: AVAssetWriter
private var reader: AVAssetReader?
private var writer: AVAssetWriter?
private var audioOutput: AVAssetReaderAudioMixOutput?
private var audioInput: AVAssetWriterInput?
private var videoOutput: AVAssetReaderVideoCompositionOutput?
private var videoInput: AVAssetWriterInput?
private var isCancelled = false
nonisolated init(
asset: sending AVAsset,
@ -107,34 +106,39 @@ actor SampleWriter {
}
func writeSamples() async throws {
guard let reader, let writer else { throw CancellationError() }
try Task.checkCancellation()
// Clear all of these properties otherwise when we get cancelled then we leak a bunch of
// pixel buffers.
defer {
if Task.isCancelled {
reader.cancelReading()
writer.cancelWriting()
}
self.reader = nil
self.writer = nil
audioInput = nil
audioOutput = nil
videoInput = nil
videoOutput = nil
}
progressContinuation.yield(0.0)
writer.startWriting()
writer.startSession(atSourceTime: timeRange.start)
reader.startReading()
try Task.checkCancellation()
startEncodingAudioTracks()
startEncodingVideoTracks()
while reader.status == .reading, writer.status == .writing {
guard !Task.isCancelled else {
// Flag so that we stop writing samples
isCancelled = true
throw CancellationError()
}
try await Task.sleep(for: .milliseconds(10))
}
guard !isCancelled, reader.status != .cancelled, writer.status != .cancelled else {
log.debug("Cancelled before writing samples")
reader.cancelReading()
writer.cancelWriting()
throw CancellationError()
}
guard writer.status != .failed else {
reader.cancelReading()
throw Error.writeFailure(writer.error)
@ -169,7 +173,7 @@ actor SampleWriter {
let audioOutput = AVAssetReaderAudioMixOutput(audioTracks: audioTracks, audioSettings: nil)
audioOutput.alwaysCopiesSampleData = false
audioOutput.audioMix = audioMix
guard reader.canAdd(audioOutput) else {
guard let reader, reader.canAdd(audioOutput) else {
throw Error.setupFailure(.cannotAddAudioOutput)
}
reader.add(audioOutput)
@ -177,7 +181,7 @@ actor SampleWriter {
let audioInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioOutputSettings)
audioInput.expectsMediaDataInRealTime = false
guard writer.canAdd(audioInput) else {
guard let writer, writer.canAdd(audioInput) else {
throw Error.setupFailure(.cannotAddAudioInput)
}
writer.add(audioInput)
@ -193,7 +197,7 @@ actor SampleWriter {
)
videoOutput.alwaysCopiesSampleData = false
videoOutput.videoComposition = videoComposition
guard reader.canAdd(videoOutput) else {
guard let reader, reader.canAdd(videoOutput) else {
throw Error.setupFailure(.cannotAddVideoOutput)
}
reader.add(videoOutput)
@ -201,7 +205,7 @@ actor SampleWriter {
let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoOutputSettings)
videoInput.expectsMediaDataInRealTime = false
guard writer.canAdd(videoInput) else {
guard let writer, writer.canAdd(videoInput) else {
throw Error.setupFailure(.cannotAddVideoInput)
}
writer.add(videoInput)
@ -234,13 +238,6 @@ actor SampleWriter {
}
private func writeAllReadySamples() {
guard !isCancelled else {
log.debug("Cancelled while writing samples")
reader.cancelReading()
writer.cancelWriting()
return
}
if let audioInput, let audioOutput {
let hasMoreAudio = writeReadySamples(output: audioOutput, input: audioInput)
if !hasMoreAudio { log.debug("Finished encoding audio") }
@ -252,13 +249,7 @@ actor SampleWriter {
private func writeReadySamples(output: AVAssetReaderOutput, input: AVAssetWriterInput) -> Bool {
while input.isReadyForMoreMediaData {
guard !isCancelled else {
log.debug("Cancelled while writing samples")
reader.cancelReading()
writer.cancelWriting()
return false
}
guard reader.status == .reading && writer.status == .writing,
guard reader?.status == .reading && writer?.status == .writing,
let sampleBuffer = output.copyNextSampleBuffer() else {
input.markAsFinished()
return false