react-native-compressor 1.8.16 → 1.8.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +103 -10
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/com/reactnativecompressor/Audio/AudioCompressor.kt +58 -27
  4. package/android/src/main/java/com/reactnativecompressor/Audio/AudioHelper.kt +49 -0
  5. package/android/src/main/java/com/reactnativecompressor/Audio/AudioMain.kt +2 -6
  6. package/android/src/main/java/com/reactnativecompressor/CompressorModule.kt +5 -0
  7. package/android/src/main/java/com/reactnativecompressor/Utils/HttpCallManager.kt +41 -0
  8. package/android/src/main/java/com/reactnativecompressor/Utils/Uploader.kt +70 -33
  9. package/android/src/main/java/com/reactnativecompressor/Utils/createVideoThumbnail.kt +0 -2
  10. package/android/src/oldarch/CompressorSpec.kt +1 -0
  11. package/ios/Audio/AudioHelper.swift +49 -0
  12. package/ios/Audio/AudioMain.swift +46 -82
  13. package/ios/Audio/AudioOptions.swift +37 -0
  14. package/ios/Audio/FormatConverter/FormatConverter+Compressed.swift +326 -0
  15. package/ios/Audio/FormatConverter/FormatConverter+PCM.swift +239 -0
  16. package/ios/Audio/FormatConverter/FormatConverter+Utilities.swift +103 -0
  17. package/ios/Audio/FormatConverter/FormatConverter.swift +264 -0
  18. package/ios/Compressor.mm +4 -1
  19. package/ios/Compressor.xcodeproj/xcuserdata/apple.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  20. package/ios/CompressorManager.swift +5 -0
  21. package/ios/Utils/Uploader.swift +18 -1
  22. package/ios/Utils/UrlTaskManager.swift +58 -0
  23. package/ios/Video/NextLevelSessionExporter.swift +694 -0
  24. package/ios/Video/VideoMain.swift +1 -2
  25. package/lib/commonjs/Audio/index.js +1 -3
  26. package/lib/commonjs/Audio/index.js.map +1 -1
  27. package/lib/commonjs/Spec/NativeCompressor.js.map +1 -1
  28. package/lib/commonjs/index.js +6 -0
  29. package/lib/commonjs/index.js.map +1 -1
  30. package/lib/commonjs/utils/Uploader.js +13 -2
  31. package/lib/commonjs/utils/Uploader.js.map +1 -1
  32. package/lib/commonjs/utils/index.js +0 -2
  33. package/lib/commonjs/utils/index.js.map +1 -1
  34. package/lib/module/Audio/index.js +1 -3
  35. package/lib/module/Audio/index.js.map +1 -1
  36. package/lib/module/Spec/NativeCompressor.js.map +1 -1
  37. package/lib/module/index.js +2 -2
  38. package/lib/module/index.js.map +1 -1
  39. package/lib/module/utils/Uploader.js +11 -1
  40. package/lib/module/utils/Uploader.js.map +1 -1
  41. package/lib/module/utils/index.js +0 -3
  42. package/lib/module/utils/index.js.map +1 -1
  43. package/lib/typescript/Audio/index.d.ts.map +1 -1
  44. package/lib/typescript/Spec/NativeCompressor.d.ts +1 -0
  45. package/lib/typescript/Spec/NativeCompressor.d.ts.map +1 -1
  46. package/lib/typescript/index.d.ts +3 -3
  47. package/lib/typescript/index.d.ts.map +1 -1
  48. package/lib/typescript/utils/Uploader.d.ts +3 -1
  49. package/lib/typescript/utils/Uploader.d.ts.map +1 -1
  50. package/lib/typescript/utils/index.d.ts +4 -1
  51. package/lib/typescript/utils/index.d.ts.map +1 -1
  52. package/package.json +1 -1
  53. package/react-native-compressor.podspec +0 -1
  54. package/src/Audio/index.tsx +1 -3
  55. package/src/Spec/NativeCompressor.ts +2 -0
  56. package/src/index.tsx +2 -0
  57. package/src/utils/Uploader.tsx +18 -1
  58. package/src/utils/index.tsx +5 -2
@@ -0,0 +1,49 @@
1
+ //
2
+ // AudioHelper.swift
3
+ // react-native-compressor
4
+ //
5
+ // Created by Numan on 12/11/2023.
6
+ //
7
+
8
+ import AVFoundation
9
+
10
+ class AudioHelper {
11
+
12
+ static func getAudioBitrate(path: String) -> Int {
13
+ let audioURL = URL(fileURLWithPath: path)
14
+ let avAsset = AVURLAsset(url: audioURL)
15
+ let keys: Set<URLResourceKey> = [.totalFileSizeKey, .fileSizeKey]
16
+ let resourceValues = try? audioURL.resourceValues(forKeys: keys)
17
+ let fileSize = resourceValues?.fileSize ?? resourceValues?.totalFileSize
18
+
19
+ // Calculate bitrate in kbps
20
+ if let fileSize = fileSize, avAsset.duration.seconds > 0 {
21
+ return Int(Double(fileSize) * 8 / avAsset.duration.seconds)
22
+ }
23
+ return 0
24
+ }
25
+
26
+ static func getDestinationBitrateByQuality(path: String, quality: String) -> Int {
27
+ let originalBitrate = getAudioBitrate(path: path)
28
+ var destinationBitrate = originalBitrate
29
+ print("source bitrate: \(originalBitrate)")
30
+
31
+ // Calculate the percentage of the original bitrate relative to the range 64000 to 320000
32
+ let percentage = Double(originalBitrate - 64000) / Double(320000 - 64000)
33
+
34
+ switch quality.lowercased() {
35
+ case "low":
36
+ destinationBitrate = max(64000, Int(Double(originalBitrate) * 0.3))
37
+ case "medium":
38
+ // Set destination bitrate to 60% of the original bitrate
39
+ destinationBitrate = Int(Double(originalBitrate) * 0.5)
40
+ case "high":
41
+ // Set destination bitrate to 80% of the original bitrate
42
+ destinationBitrate = min(320000,Int(Double(originalBitrate) * 0.7))
43
+ default:
44
+ print("Invalid quality level. Please enter 'low', 'medium', or 'high'.")
45
+ }
46
+
47
+ return destinationBitrate
48
+ }
49
+ }
@@ -5,16 +5,21 @@
5
5
  // Created by Numan on 10/09/2023.
6
6
  //
7
7
 
8
- import Foundation
8
+
9
9
  import AVFoundation
10
+
11
+
12
+
10
13
  let AlAsset_Library_Scheme = "assets-library"
11
14
  class AudioMain{
12
15
  static func compress_audio(_ fileUrl: String, optionMap: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
13
16
  do {
14
17
  var fileUrl = fileUrl
18
+
15
19
  if fileUrl.hasPrefix("file://") {
16
20
  fileUrl = fileUrl.replacingOccurrences(of: "file://", with: "")
17
21
  }
22
+
18
23
  let fileManager = FileManager.default
19
24
  var isDir: ObjCBool = false
20
25
  if !fileManager.fileExists(atPath: fileUrl, isDirectory: &isDir) || isDir.boolValue {
@@ -23,93 +28,52 @@ class AudioMain{
23
28
  return
24
29
  }
25
30
 
26
- let assetOptions: [String: Any] = [AVURLAssetPreferPreciseDurationAndTimingKey: true]
27
- let asset = AVURLAsset(url: URL(fileURLWithPath: fileUrl), options: assetOptions)
28
- let quality = optionMap["quality"] as? String ?? ""
29
- let qualityConstant = getAudioQualityConstant(quality)
30
- auto_compress_helper(asset, qualityConstant: qualityConstant) { mp3Path, finished in
31
- if finished {
32
- resolve("file://\(mp3Path ?? "")")
33
- } else {
34
- reject("Error", "Something went wrong", nil)
35
- }
36
- }
37
- } catch {
38
- reject(error.localizedDescription, error.localizedDescription, nil)
39
- }
40
- }
41
-
42
- static func getAudioQualityConstant(_ quality: String) -> String {
43
- let audioQualityArray = ["low", "medium", "high"]
44
- if let index = audioQualityArray.firstIndex(of: quality) {
45
- switch index {
46
- case 0:
47
- return AVAssetExportPresetLowQuality
48
- case 1:
49
- return AVAssetExportPresetMediumQuality
50
- case 2:
51
- return AVAssetExportPresetHighestQuality
52
- default:
53
- return AVAssetExportPresetMediumQuality
31
+ let audioOptions = AudioOptions.fromDictionary((optionMap as! [String : Any]))
32
+ let outputMp3Path = "file://\(Utils.generateCacheFilePath("m4a"))"
33
+
34
+ let oldUfileUrlRL = URL(string: fileUrl)!
35
+ let newURL = URL(string: outputMp3Path)!
36
+
37
+ var options = FormatConverter.Options()
38
+
39
+ if(audioOptions.samplerate != -1){
40
+ options.sampleRate = Double(audioOptions.samplerate)
54
41
  }
55
- }
56
- return AVAssetExportPresetMediumQuality
57
- }
58
-
59
- static func auto_compress_helper(_ avAsset: AVURLAsset, qualityConstant: String, complete completeCallback: @escaping (_ mp3Path: String?, _ finished: Bool) -> Void) {
60
- var path: String
61
- if avAsset.url.scheme == AlAsset_Library_Scheme {
62
- path = avAsset.url.query ?? ""
63
- if path.isEmpty {
64
- completeCallback(nil, false)
65
- return
42
+
43
+ if(audioOptions.bitrate != -1){
44
+ options.bitRate = UInt32(audioOptions.bitrate)
66
45
  }
67
- } else {
68
- path = avAsset.url.path
69
- if path.isEmpty || !FileManager.default.fileExists(atPath: path) {
70
- completeCallback(nil, false)
71
- return
46
+ else
47
+ {
48
+ let bitrate = AudioHelper.getDestinationBitrateByQuality(path: fileUrl,quality: audioOptions.quality)
49
+ print("output bitrate: \(bitrate)")
50
+ options.bitRate = UInt32(bitrate)
72
51
  }
73
- }
74
-
75
- let mp3Path = Utils.generateCacheFilePath("m4a")
76
-
77
- if FileManager.default.fileExists(atPath: mp3Path) {
78
- completeCallback(mp3Path, true)
79
- return
80
- }
81
-
82
- let compatiblePresets = AVAssetExportSession.exportPresets(compatibleWith: avAsset)
83
-
84
- if compatiblePresets.contains(qualityConstant) {
85
- let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetAppleM4A)
86
52
 
87
- let mp3Url = URL(fileURLWithPath: mp3Path)
88
- exportSession?.outputURL = mp3Url
89
- exportSession?.shouldOptimizeForNetworkUse = true
90
- exportSession?.outputFileType = .m4a
91
53
 
92
- exportSession?.exportAsynchronously(completionHandler: {
93
- var finished = false
94
- switch exportSession?.status {
95
- case .failed:
96
- print("AVAssetExportSessionStatusFailed, error: \(exportSession?.error?.localizedDescription ?? "").")
97
- case .cancelled:
98
- print("AVAssetExportSessionStatusCancelled.")
99
- case .completed:
100
- print("AVAssetExportSessionStatusCompleted.")
101
- finished = true
102
- case .unknown:
103
- print("AVAssetExportSessionStatusUnknown")
104
- case .waiting:
105
- print("AVAssetExportSessionStatusWaiting")
106
- case .exporting:
107
- print("AVAssetExportSessionStatusExporting")
108
- default:
109
- break
54
+ if(audioOptions.channels != -1){
55
+ options.channels = UInt32(audioOptions.channels)
56
+ }
57
+
58
+ options.format = .m4a
59
+ options.eraseFile = false
60
+ options.bitDepthRule = .any
61
+
62
+ let converter = FormatConverter(inputURL: oldUfileUrlRL, outputURL: newURL, options: options)
63
+ converter.start { error in
64
+ // check to see if error isn't nil, otherwise you're good
65
+ if((error) != nil)
66
+ {
67
+ print("error=> \(error?.localizedDescription)")
68
+ reject(error?.localizedDescription,error?.localizedDescription, error)
69
+ return
110
70
  }
111
- completeCallback(mp3Path, finished)
112
- })
71
+
72
+ resolve(newURL.absoluteString)
73
+ }
74
+
75
+ } catch {
76
+ reject(error.localizedDescription, error.localizedDescription, nil)
113
77
  }
114
78
  }
115
79
  }
@@ -0,0 +1,37 @@
1
+ class AudioOptions: NSObject {
2
+ static func fromDictionary(_ dictionary: [String: Any]?) -> AudioOptions {
3
+ let options = AudioOptions()
4
+
5
+ guard let dictionary = dictionary else {
6
+ return options
7
+ }
8
+
9
+ for (key, value) in dictionary {
10
+ switch key {
11
+ case "quality":
12
+ options.quality = (value as? String) ?? "medium"
13
+ case "bitrate":
14
+ options.bitrate = (value as? Int) ?? -1
15
+ case "samplerate":
16
+ options.samplerate = (value as? Int) ?? -1
17
+ case "channels":
18
+ options.channels = (value as? Int) ?? -1
19
+
20
+ default:
21
+ break
22
+ }
23
+ }
24
+
25
+ return options
26
+ }
27
+
28
+ var quality: String = "medium"
29
+ var bitrate: Int = -1
30
+ var samplerate: Int = -1
31
+ var channels: Int = -1
32
+
33
+
34
+ override init() {
35
+ super.init()
36
+ }
37
+ }
@@ -0,0 +1,326 @@
1
+ // Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/AudioKit/
2
+
3
+ import AVFoundation
4
+
5
+ // MARK: - internal helper functions
6
+
7
+
8
+ public extension AVURLAsset {
9
+ /// Audio format for the file in the URL asset
10
+ var audioFormat: AVAudioFormat? {
11
+ // pull the input format out of the audio file...
12
+ if let source = try? AVAudioFile(forReading: url) {
13
+ return source.fileFormat
14
+
15
+ // if that fails it might be a video, so check the tracks for audio
16
+ } else {
17
+ let audioTracks = tracks.filter { $0.mediaType == .audio }
18
+
19
+ guard !audioTracks.isEmpty else { return nil }
20
+
21
+ let formatDescriptions = audioTracks.compactMap {
22
+ $0.formatDescriptions as? [CMFormatDescription]
23
+ }.reduce([], +)
24
+
25
+ let audioFormats: [AVAudioFormat] = formatDescriptions.compactMap {
26
+ AVAudioFormat(cmAudioFormatDescription: $0)
27
+ }
28
+ return audioFormats.first
29
+ }
30
+ }
31
+ }
32
+
33
+
34
+ extension FormatConverter {
35
+ /// Example of the most simplistic AVFoundation conversion.
36
+ /// With this approach you can't really specify any settings other than the limited presets.
37
+ /// No sample rate conversion in this. This isn't used in the public methods but is here
38
+ /// for example.
39
+ ///
40
+ /// see `AVAssetExportSession`:
41
+ /// *Prior to initializing an instance of AVAssetExportSession, you can invoke
42
+ /// +allExportPresets to obtain the complete list of presets available. Use
43
+ /// +exportPresetsCompatibleWithAsset: to obtain a list of presets that are compatible
44
+ /// with a specific AVAsset.*
45
+ ///
46
+ /// This is no longer used in this class as it's not possible to convert sample rate or other
47
+ /// required options. It will use the next function instead
48
+ func convertCompressed(presetName: String, completionHandler: FormatConverterCallback? = nil) {
49
+ guard let inputURL = inputURL else {
50
+ completionHandler?(Self.createError(message: "Input file can't be nil."))
51
+ return
52
+ }
53
+ guard let outputURL = outputURL else {
54
+ completionHandler?(Self.createError(message: "Output file can't be nil."))
55
+ return
56
+ }
57
+
58
+ let asset = AVURLAsset(url: inputURL)
59
+ guard let session = AVAssetExportSession(asset: asset,
60
+ presetName: presetName) else { return }
61
+
62
+ session.determineCompatibleFileTypes { list in
63
+
64
+ guard let outputFileType: AVFileType = list.first else {
65
+ let error = Self.createError(message: "Unable to determine a compatible file type from \(inputURL.path)")
66
+ completionHandler?(error)
67
+ return
68
+ }
69
+
70
+ // session.progress could be sent out via a delegate for this session
71
+ session.outputURL = outputURL
72
+ session.outputFileType = outputFileType
73
+ session.exportAsynchronously {
74
+ completionHandler?(session.error)
75
+ }
76
+ }
77
+ }
78
+
79
+ /// Convert to compressed first creating a tmp file to PCM to allow more flexible conversion
80
+ /// options to work.
81
+ func convertCompressed(completionHandler: FormatConverterCallback? = nil) {
82
+ guard let inputURL = inputURL else {
83
+ completionHandler?(Self.createError(message: "Input file can't be nil."))
84
+ return
85
+ }
86
+ guard let outputURL = outputURL else {
87
+ completionHandler?(Self.createError(message: "Output file can't be nil."))
88
+ return
89
+ }
90
+
91
+ guard let options = options else {
92
+ completionHandler?(Self.createError(message: "Options can't be nil."))
93
+ return
94
+ }
95
+
96
+ let tempName = outputURL.deletingPathExtension().lastPathComponent + "_TEMP.wav"
97
+ let tempFile = outputURL.deletingLastPathComponent().appendingPathComponent(tempName)
98
+
99
+ var tempOptions = FormatConverter.Options()
100
+ tempOptions.bitDepthRule = .lessThanOrEqual
101
+ tempOptions.bitDepth = 24
102
+ tempOptions.sampleRate = options.sampleRate
103
+ tempOptions.channels = options.channels
104
+ tempOptions.format = .wav
105
+
106
+ let tempConverter = FormatConverter(inputURL: inputURL,
107
+ outputURL: tempFile,
108
+ options: tempOptions)
109
+
110
+ tempConverter.start { error in
111
+ if let error = error {
112
+ completionHandler?(Self.createError(message: "Failed to convert input to PCM: \(error.localizedDescription)"))
113
+ return
114
+ }
115
+
116
+ self.inputURL = tempFile
117
+
118
+ self.convertPCMToCompressed { error in
119
+ try? FileManager.default.removeItem(at: tempFile)
120
+ completionHandler?(error)
121
+ }
122
+ }
123
+ }
124
+
125
+ /// The AVFoundation way. *This doesn't currently handle compressed input - only compressed output.*
126
+ func convertPCMToCompressed(completionHandler: FormatConverterCallback? = nil) {
127
+ guard let inputURL = inputURL else {
128
+ completionHandler?(Self.createError(message: "Input file can't be nil."))
129
+ return
130
+ }
131
+ guard let outputURL = outputURL else {
132
+ completionHandler?(Self.createError(message: "Output file can't be nil."))
133
+ return
134
+ }
135
+
136
+ guard let options = options, let outputFormat = options.format else {
137
+ completionHandler?(Self.createError(message: "Options can't be nil."))
138
+ return
139
+ }
140
+
141
+ // verify outputFormat
142
+ guard FormatConverter.outputFormats.contains(outputFormat) else {
143
+ completionHandler?(Self.createError(message: "The output file format isn't able to be produced by this class."))
144
+ return
145
+ }
146
+
147
+ let asset = AVURLAsset(url: inputURL)
148
+ do {
149
+ self.reader = try AVAssetReader(asset: asset)
150
+
151
+ } catch let err as NSError {
152
+ completionHandler?(err)
153
+ return
154
+ }
155
+
156
+ guard let reader = reader else {
157
+ completionHandler?(Self.createError(message: "Unable to setup the AVAssetReader."))
158
+ return
159
+ }
160
+
161
+ guard let inputFormat = asset.audioFormat else {
162
+ completionHandler?(Self.createError(message: "Unable to read the input file format."))
163
+ return
164
+ }
165
+
166
+ var format: AVFileType
167
+ var formatKey: AudioFormatID
168
+
169
+ switch outputFormat {
170
+ case .m4a, .mp4:
171
+ format = .m4a
172
+ formatKey = kAudioFormatMPEG4AAC
173
+ case .aif:
174
+ format = .aiff
175
+ formatKey = kAudioFormatLinearPCM
176
+ case .caf:
177
+ format = .caf
178
+ formatKey = kAudioFormatLinearPCM
179
+ case .wav:
180
+ format = .wav
181
+ formatKey = kAudioFormatLinearPCM
182
+ default:
183
+ print("Unsupported output format: \(outputFormat)")
184
+ return
185
+ }
186
+
187
+ do {
188
+ self.writer = try AVAssetWriter(outputURL: outputURL, fileType: format)
189
+ } catch let err as NSError {
190
+ completionHandler?(err)
191
+ return
192
+ }
193
+
194
+ guard let writer = writer else {
195
+ completionHandler?(Self.createError(message: "Unable to setup the AVAssetWriter."))
196
+ return
197
+ }
198
+
199
+ // 1. chosen option. 2. same as input file. 3. 16 bit
200
+ // optional in case of compressed audio. That said, the other conversion methods are actually used in
201
+ // that case
202
+ let bitDepth = (options.bitDepth ?? inputFormat.settings[AVLinearPCMBitDepthKey] ?? 16) as Any
203
+ var isFloat = false
204
+ if let intDepth = bitDepth as? Int {
205
+ isFloat = intDepth == 32
206
+ }
207
+
208
+ var sampleRate = options.sampleRate ?? inputFormat.sampleRate
209
+ let channels = options.channels ?? inputFormat.channelCount
210
+
211
+ if sampleRate == 0 {
212
+ print("Sample rate can't be 0 - assigning to default format of 48k. inputFormat is", inputFormat)
213
+ sampleRate = 48000
214
+ }
215
+ var outputSettings: [String: Any]?
216
+
217
+ // Note: AVAssetReaderOutput does not currently support compressed audio
218
+ if formatKey == kAudioFormatMPEG4AAC {
219
+ if sampleRate > 48000 {
220
+ sampleRate = 48000
221
+ }
222
+ // mono should be 1/2 the shown bitrate
223
+ let perChannel = channels == 1 ? 2 : 1
224
+
225
+ // reset these for m4a:
226
+ outputSettings = [
227
+ AVFormatIDKey: formatKey,
228
+ AVSampleRateKey: sampleRate,
229
+ AVNumberOfChannelsKey: channels,
230
+ AVEncoderBitRateKey: Int(options.bitRate) / perChannel,
231
+ AVEncoderBitRateStrategyKey: AVAudioBitRateStrategy_Constant,
232
+ ]
233
+ } else {
234
+ outputSettings = [
235
+ AVFormatIDKey: formatKey,
236
+ AVSampleRateKey: sampleRate,
237
+ AVNumberOfChannelsKey: channels,
238
+ AVLinearPCMBitDepthKey: bitDepth,
239
+ AVLinearPCMIsFloatKey: isFloat,
240
+ AVLinearPCMIsBigEndianKey: format != .wav,
241
+ AVLinearPCMIsNonInterleaved: !(options.isInterleaved ?? inputFormat.isInterleaved),
242
+ ]
243
+ }
244
+
245
+ let hint = asset.audioFormat?.formatDescription
246
+
247
+ let writerInput = AVAssetWriterInput(mediaType: .audio, outputSettings: outputSettings, sourceFormatHint: hint)
248
+ writer.add(writerInput)
249
+
250
+ guard let track = asset.tracks(withMediaType: .audio).first else {
251
+ completionProxy(error: Self.createError(message: "No audio was found in the input file."),
252
+ completionHandler: completionHandler)
253
+ return
254
+ }
255
+
256
+ let readerOutput = AVAssetReaderTrackOutput(track: track, outputSettings: nil)
257
+ guard reader.canAdd(readerOutput) else {
258
+ completionProxy(error: Self.createError(message: "Unable to add reader output."),
259
+ completionHandler: completionHandler)
260
+ return
261
+ }
262
+ reader.add(readerOutput)
263
+
264
+ if !writer.startWriting() {
265
+ print("Failed to start writing. Error:", writer.error?.localizedDescription)
266
+ completionProxy(error: writer.error,
267
+ completionHandler: completionHandler)
268
+ return
269
+ }
270
+
271
+ writer.startSession(atSourceTime: CMTime.zero)
272
+
273
+ if !reader.startReading() {
274
+ print("Failed to start reading. Error:", reader.error?.localizedDescription)
275
+ completionProxy(error: reader.error,
276
+ completionHandler: completionHandler)
277
+ return
278
+ }
279
+
280
+ let queue = DispatchQueue(label: "com.audiodesigndesk.ADD.FormatConverter.convertAsset")
281
+
282
+ // session.progress could be sent out via a delegate for this session
283
+ writerInput.requestMediaDataWhenReady(on: queue, using: {
284
+ var processing = true // safety flag to prevent runaway loops if errors
285
+
286
+ while writerInput.isReadyForMoreMediaData, processing {
287
+ if reader.status == .reading,
288
+ let buffer = readerOutput.copyNextSampleBuffer()
289
+ {
290
+ writerInput.append(buffer)
291
+
292
+ } else {
293
+ writerInput.markAsFinished()
294
+
295
+ switch reader.status {
296
+ case .failed:
297
+ print("Conversion failed with error", reader.error)
298
+ writer.cancelWriting()
299
+ self.completionProxy(error: reader.error, completionHandler: completionHandler)
300
+ case .cancelled:
301
+ print("Conversion cancelled")
302
+ self.completionProxy(error: Self.createError(message: "Process canceled"),
303
+ completionHandler: completionHandler)
304
+ case .completed:
305
+ writer.finishWriting {
306
+ switch writer.status {
307
+ case .failed:
308
+ print("Conversion failed at finishWriting")
309
+ self.completionProxy(error: writer.error,
310
+ completionHandler: completionHandler)
311
+ default:
312
+ // no errors
313
+ completionHandler?(nil)
314
+ }
315
+ }
316
+ default:
317
+ break
318
+ }
319
+ processing = false
320
+ }
321
+ }
322
+ }) // requestMediaDataWhenReady
323
+ }
324
+ }
325
+
326
+