mirror of
https://github.com/samsonjs/SJSAssetExportSession.git
synced 2026-03-25 08:45:50 +00:00
Simplify cancellation and fix memory leak
This commit is contained in:
parent
865e524be6
commit
49d41080bb
2 changed files with 25 additions and 34 deletions
|
|
@ -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:
|
When you're integrating this using SPM on its own then add this to the list of dependencies your Package.swift file:
|
||||||
|
|
||||||
```swift
|
```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.
|
and then add `"SJSAssetExportSession"` to the list of dependencies in your target as well.
|
||||||
|
|
|
||||||
|
|
@ -41,13 +41,12 @@ actor SampleWriter {
|
||||||
|
|
||||||
// MARK: Internal state
|
// MARK: Internal state
|
||||||
|
|
||||||
private let reader: AVAssetReader
|
private var reader: AVAssetReader?
|
||||||
private let writer: AVAssetWriter
|
private var writer: AVAssetWriter?
|
||||||
private var audioOutput: AVAssetReaderAudioMixOutput?
|
private var audioOutput: AVAssetReaderAudioMixOutput?
|
||||||
private var audioInput: AVAssetWriterInput?
|
private var audioInput: AVAssetWriterInput?
|
||||||
private var videoOutput: AVAssetReaderVideoCompositionOutput?
|
private var videoOutput: AVAssetReaderVideoCompositionOutput?
|
||||||
private var videoInput: AVAssetWriterInput?
|
private var videoInput: AVAssetWriterInput?
|
||||||
private var isCancelled = false
|
|
||||||
|
|
||||||
nonisolated init(
|
nonisolated init(
|
||||||
asset: sending AVAsset,
|
asset: sending AVAsset,
|
||||||
|
|
@ -107,34 +106,39 @@ actor SampleWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeSamples() async throws {
|
func writeSamples() async throws {
|
||||||
|
guard let reader, let writer else { throw CancellationError() }
|
||||||
try Task.checkCancellation()
|
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)
|
progressContinuation.yield(0.0)
|
||||||
|
|
||||||
writer.startWriting()
|
writer.startWriting()
|
||||||
writer.startSession(atSourceTime: timeRange.start)
|
writer.startSession(atSourceTime: timeRange.start)
|
||||||
reader.startReading()
|
reader.startReading()
|
||||||
|
|
||||||
try Task.checkCancellation()
|
try Task.checkCancellation()
|
||||||
|
|
||||||
startEncodingAudioTracks()
|
startEncodingAudioTracks()
|
||||||
startEncodingVideoTracks()
|
startEncodingVideoTracks()
|
||||||
|
|
||||||
while reader.status == .reading, writer.status == .writing {
|
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))
|
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 {
|
guard writer.status != .failed else {
|
||||||
reader.cancelReading()
|
reader.cancelReading()
|
||||||
throw Error.writeFailure(writer.error)
|
throw Error.writeFailure(writer.error)
|
||||||
|
|
@ -169,7 +173,7 @@ actor SampleWriter {
|
||||||
let audioOutput = AVAssetReaderAudioMixOutput(audioTracks: audioTracks, audioSettings: nil)
|
let audioOutput = AVAssetReaderAudioMixOutput(audioTracks: audioTracks, audioSettings: nil)
|
||||||
audioOutput.alwaysCopiesSampleData = false
|
audioOutput.alwaysCopiesSampleData = false
|
||||||
audioOutput.audioMix = audioMix
|
audioOutput.audioMix = audioMix
|
||||||
guard reader.canAdd(audioOutput) else {
|
guard let reader, reader.canAdd(audioOutput) else {
|
||||||
throw Error.setupFailure(.cannotAddAudioOutput)
|
throw Error.setupFailure(.cannotAddAudioOutput)
|
||||||
}
|
}
|
||||||
reader.add(audioOutput)
|
reader.add(audioOutput)
|
||||||
|
|
@ -177,7 +181,7 @@ actor SampleWriter {
|
||||||
|
|
||||||
let audioInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioOutputSettings)
|
let audioInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioOutputSettings)
|
||||||
audioInput.expectsMediaDataInRealTime = false
|
audioInput.expectsMediaDataInRealTime = false
|
||||||
guard writer.canAdd(audioInput) else {
|
guard let writer, writer.canAdd(audioInput) else {
|
||||||
throw Error.setupFailure(.cannotAddAudioInput)
|
throw Error.setupFailure(.cannotAddAudioInput)
|
||||||
}
|
}
|
||||||
writer.add(audioInput)
|
writer.add(audioInput)
|
||||||
|
|
@ -193,7 +197,7 @@ actor SampleWriter {
|
||||||
)
|
)
|
||||||
videoOutput.alwaysCopiesSampleData = false
|
videoOutput.alwaysCopiesSampleData = false
|
||||||
videoOutput.videoComposition = videoComposition
|
videoOutput.videoComposition = videoComposition
|
||||||
guard reader.canAdd(videoOutput) else {
|
guard let reader, reader.canAdd(videoOutput) else {
|
||||||
throw Error.setupFailure(.cannotAddVideoOutput)
|
throw Error.setupFailure(.cannotAddVideoOutput)
|
||||||
}
|
}
|
||||||
reader.add(videoOutput)
|
reader.add(videoOutput)
|
||||||
|
|
@ -201,7 +205,7 @@ actor SampleWriter {
|
||||||
|
|
||||||
let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoOutputSettings)
|
let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoOutputSettings)
|
||||||
videoInput.expectsMediaDataInRealTime = false
|
videoInput.expectsMediaDataInRealTime = false
|
||||||
guard writer.canAdd(videoInput) else {
|
guard let writer, writer.canAdd(videoInput) else {
|
||||||
throw Error.setupFailure(.cannotAddVideoInput)
|
throw Error.setupFailure(.cannotAddVideoInput)
|
||||||
}
|
}
|
||||||
writer.add(videoInput)
|
writer.add(videoInput)
|
||||||
|
|
@ -234,13 +238,6 @@ actor SampleWriter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func writeAllReadySamples() {
|
private func writeAllReadySamples() {
|
||||||
guard !isCancelled else {
|
|
||||||
log.debug("Cancelled while writing samples")
|
|
||||||
reader.cancelReading()
|
|
||||||
writer.cancelWriting()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if let audioInput, let audioOutput {
|
if let audioInput, let audioOutput {
|
||||||
let hasMoreAudio = writeReadySamples(output: audioOutput, input: audioInput)
|
let hasMoreAudio = writeReadySamples(output: audioOutput, input: audioInput)
|
||||||
if !hasMoreAudio { log.debug("Finished encoding audio") }
|
if !hasMoreAudio { log.debug("Finished encoding audio") }
|
||||||
|
|
@ -252,13 +249,7 @@ actor SampleWriter {
|
||||||
|
|
||||||
private func writeReadySamples(output: AVAssetReaderOutput, input: AVAssetWriterInput) -> Bool {
|
private func writeReadySamples(output: AVAssetReaderOutput, input: AVAssetWriterInput) -> Bool {
|
||||||
while input.isReadyForMoreMediaData {
|
while input.isReadyForMoreMediaData {
|
||||||
guard !isCancelled else {
|
guard reader?.status == .reading && writer?.status == .writing,
|
||||||
log.debug("Cancelled while writing samples")
|
|
||||||
reader.cancelReading()
|
|
||||||
writer.cancelWriting()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
guard reader.status == .reading && writer.status == .writing,
|
|
||||||
let sampleBuffer = output.copyNextSampleBuffer() else {
|
let sampleBuffer = output.copyNextSampleBuffer() else {
|
||||||
input.markAsFinished()
|
input.markAsFinished()
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue