Skip to content

Commit 82e0137

Browse files
committed
feat: add video compression like whatsapp by compressionMethod for ios
1 parent 6f6fb2d commit 82e0137

File tree

1 file changed

+255
-81
lines changed

1 file changed

+255
-81
lines changed

ios/Video/VideoCompressor.swift

Lines changed: 255 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class VideoCompressor: RCTEventEmitter, URLSessionTaskDelegate {
7070

7171
@objc(compress:withOptions:withResolver:withRejecter:)
7272
func compress(fileUrl: String, options: [String: Any], resolve:@escaping RCTPromiseResolveBlock, reject:@escaping RCTPromiseRejectBlock) -> Void {
73-
compressVideo(url: URL(string: fileUrl)!, bitRate: options["bitrate"] as! Float?,
73+
compressVideo(url: URL(string: fileUrl)!, options:options,
7474
onProgress: { progress in
7575
print("Progress", progress)
7676
if(self.hasListener){
@@ -176,101 +176,275 @@ func makeValidUri(filePath: String) -> String {
176176
override func startObserving() -> Void {
177177
hasListener = true
178178
}
179+
180+
func getfileSize(forURL url: Any) -> Double {
181+
var fileURL: URL?
182+
var fileSize: Double = 0.0
183+
if (url is URL) || (url is String)
184+
{
185+
if (url is URL) {
186+
fileURL = url as? URL
187+
}
188+
else {
189+
fileURL = URL(fileURLWithPath: url as! String)
190+
}
191+
var fileSizeValue = 0.0
192+
try? fileSizeValue = (fileURL?.resourceValues(forKeys: [URLResourceKey.fileSizeKey]).allValues.first?.value as! Double?)!
193+
if fileSizeValue > 0.0 {
194+
fileSize = (Double(fileSizeValue) / (1024 * 1024))
195+
}
196+
}
197+
return fileSize
198+
}
179199

180200

181-
func compressVideo(url: URL, bitRate: Float?, onProgress: @escaping (Float) -> Void, onCompletion: @escaping (URL) -> Void, onFailure: @escaping (Error) -> Void){
182-
var tmpURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
183-
.appendingPathComponent(ProcessInfo().globallyUniqueString)
184-
.appendingPathExtension("mp4")
185-
tmpURL = URL(string:makeValidUri(filePath: tmpURL.absoluteString))!
201+
func compressVideo(url: URL, options: [String: Any], onProgress: @escaping (Float) -> Void, onCompletion: @escaping (URL) -> Void, onFailure: @escaping (Error) -> Void){
186202

187-
var _bitRate=bitRate;
188-
let asset = AVAsset(url: url)
189-
guard asset.tracks.count >= 1 else {
190-
let error = CompressionError(message: "Invalid video URL, no track found")
191-
onFailure(error)
192-
return
193-
}
194-
var videoTrackIndex: Int = 0;
195-
let trackLength = asset.tracks.count;
196-
if(trackLength==2)
203+
let fileSize=self.getfileSize(forURL: url);
204+
205+
if(fileSize>16)
197206
{
198-
if(asset.tracks[0].mediaType.rawValue=="soun")
207+
if(options["compressionMethod"] as! String=="auto")
199208
{
200-
videoTrackIndex=1;
209+
autoCompressionHelper(url: url, options:options) { progress in
210+
onProgress(progress)
211+
} onCompletion: { outputURL in
212+
onCompletion(outputURL)
213+
} onFailure: { error in
214+
onFailure(error)
215+
}
201216
}
202-
}
203-
let track = asset.tracks[videoTrackIndex];
204-
let exporter = NextLevelSessionExporter(withAsset: asset)
205-
exporter.outputURL = tmpURL
206-
exporter.outputFileType = AVFileType.mp4
207-
208-
let videoSize = track.naturalSize.applying(track.preferredTransform);
209-
var width = Float(abs(videoSize.width))
210-
var height = Float(abs(videoSize.height))
211-
let isPortrait = height > width
212-
let maxSize = Float(1920);
213-
if(isPortrait && height > maxSize){
214-
width = (maxSize/height)*width
215-
height = maxSize
216-
}else if(width > maxSize){
217-
height = (maxSize/width)*height
218-
width = maxSize
217+
else
218+
{
219+
manualCompressionHelper(url: url, bitRate: options["bitrate"] as! Float?) { progress in
220+
onProgress(progress)
221+
} onCompletion: { outputURL in
222+
onCompletion(outputURL)
223+
} onFailure: { error in
224+
onFailure(error)
225+
}
226+
}
227+
228+
229+
219230
}
220231
else
221232
{
222-
_bitRate=bitRate ?? Float(abs(track.estimatedDataRate))*0.8
233+
onCompletion(url)
223234
}
224-
225-
let videoBitRate = _bitRate ?? height*width*1.5
226-
227-
let compressionDict: [String: Any] = [
228-
AVVideoAverageBitRateKey: videoBitRate,
229-
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
230-
]
231-
exporter.optimizeForNetworkUse = true;
232-
exporter.videoOutputConfiguration = [
233-
AVVideoCodecKey: AVVideoCodecType.h264,
234-
AVVideoWidthKey: width,
235-
AVVideoHeightKey: height,
236-
AVVideoScalingModeKey: AVVideoScalingModeResizeAspectFill,
237-
AVVideoCompressionPropertiesKey: compressionDict
238-
]
239-
exporter.audioOutputConfiguration = [
240-
AVFormatIDKey: kAudioFormatMPEG4AAC,
241-
AVEncoderBitRateKey: NSNumber(integerLiteral: 128000),
242-
AVNumberOfChannelsKey: NSNumber(integerLiteral: 2),
243-
AVSampleRateKey: NSNumber(value: Float(44100))
244-
]
245235

236+
237+
238+
}
239+
240+
241+
func makeVideoBitrate(originalHeight:Int,originalWidth:Int,originalBitrate:Int,height:Int,width:Int)->Int {
242+
let compressFactor:Float = 0.8
243+
let minCompressFactor:Float = 0.8
244+
let maxBitrate:Int = 1669000
246245

247-
exporter.export(progressHandler: { (progress) in
248-
let _progress:Float=progress*100;
249-
if(Int(_progress)==self.videoCompressionCounter)
246+
var remeasuredBitrate:Int = originalBitrate / (min(originalHeight/height,originalWidth/width))
247+
remeasuredBitrate = remeasuredBitrate*Int(compressFactor)
248+
let minBitrate:Int = self.getVideoBitrateWithFactor(f: minCompressFactor) / (1280 * 720 / (width * height))
249+
if (originalBitrate < minBitrate) {
250+
return remeasuredBitrate;
251+
}
252+
if (remeasuredBitrate > maxBitrate) {
253+
return maxBitrate;
254+
}
255+
return max(remeasuredBitrate, minBitrate);
256+
}
257+
func getVideoBitrateWithFactor(f:Float)->Int {
258+
return Int(f * 2000 * 1000 * 1.13);
259+
}
260+
261+
func autoCompressionHelper(url: URL, options: [String: Any], onProgress: @escaping (Float) -> Void, onCompletion: @escaping (URL) -> Void, onFailure: @escaping (Error) -> Void){
262+
var bitRate=options["bitrate"] as! Float?;
263+
var tmpURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
264+
.appendingPathComponent(ProcessInfo().globallyUniqueString)
265+
.appendingPathExtension("mp4")
266+
tmpURL = URL(string:makeValidUri(filePath: tmpURL.absoluteString))!
267+
268+
let maxSize = 640.0;
269+
var _bitRate=bitRate;
270+
let asset = AVAsset(url: url)
271+
guard asset.tracks.count >= 1 else {
272+
let error = CompressionError(message: "Invalid video URL, no track found")
273+
onFailure(error)
274+
return
275+
}
276+
var videoTrackIndex: Int = 0;
277+
let trackLength = asset.tracks.count;
278+
if(trackLength==2)
250279
{
251-
self.videoCompressionCounter=Int(_progress)+self.videoCompressionThreshold
252-
onProgress(progress)
280+
if(asset.tracks[0].mediaType.rawValue=="soun")
281+
{
282+
videoTrackIndex=1;
283+
}
253284
}
285+
let track = asset.tracks[videoTrackIndex];
286+
let exporter = NextLevelSessionExporter(withAsset: asset)
287+
exporter.outputURL = tmpURL
288+
exporter.outputFileType = AVFileType.mp4
254289

255-
}, completionHandler: { result in
256-
self.videoCompressionCounter=0;
257-
switch result {
258-
case .success(let status):
259-
switch status {
260-
case .completed:
261-
onCompletion(exporter.outputURL!)
262-
break
263-
default:
264-
let error = CompressionError(message: "Compression didn't complete")
290+
let videoSize = track.naturalSize.applying(track.preferredTransform);
291+
var actualWidth = Float(abs(videoSize.width))
292+
var actualHeight = Float(abs(videoSize.height))
293+
294+
let bitrate=Float(abs(track.estimatedDataRate));
295+
let scale:Float = actualWidth > actualHeight ? (Float(maxSize) / actualWidth) : (Float(maxSize) / actualHeight);
296+
let resultWidth:Float = round(actualWidth * scale / 2) * 2;
297+
let resultHeight:Float = round(actualHeight * scale / 2) * 2;
298+
299+
let videoBitRate:Int = self.makeVideoBitrate(
300+
originalHeight: Int(actualHeight), originalWidth: Int(actualWidth),
301+
originalBitrate: Int(bitrate),
302+
height: Int(resultHeight), width: Int(resultWidth)
303+
);
304+
305+
306+
let compressionDict: [String: Any] = [
307+
AVVideoAverageBitRateKey: videoBitRate,
308+
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
309+
]
310+
exporter.optimizeForNetworkUse = true;
311+
exporter.videoOutputConfiguration = [
312+
AVVideoCodecKey: AVVideoCodecType.h264,
313+
AVVideoWidthKey: resultWidth,
314+
AVVideoHeightKey: resultHeight,
315+
AVVideoScalingModeKey: AVVideoScalingModeResizeAspectFill,
316+
AVVideoCompressionPropertiesKey: compressionDict
317+
]
318+
exporter.audioOutputConfiguration = [
319+
AVFormatIDKey: kAudioFormatMPEG4AAC,
320+
AVEncoderBitRateKey: NSNumber(integerLiteral: 128000),
321+
AVNumberOfChannelsKey: NSNumber(integerLiteral: 2),
322+
AVSampleRateKey: NSNumber(value: Float(44100))
323+
]
324+
325+
326+
exporter.export(progressHandler: { (progress) in
327+
let _progress:Float=progress*100;
328+
if(Int(_progress)==self.videoCompressionCounter)
329+
{
330+
self.videoCompressionCounter=Int(_progress)+self.videoCompressionThreshold
331+
onProgress(progress)
332+
}
333+
334+
}, completionHandler: { result in
335+
self.videoCompressionCounter=0;
336+
switch result {
337+
case .success(let status):
338+
switch status {
339+
case .completed:
340+
onCompletion(exporter.outputURL!)
341+
break
342+
default:
343+
let error = CompressionError(message: "Compression didn't complete")
344+
onFailure(error)
345+
break
346+
}
347+
break
348+
case .failure(let error):
349+
onFailure(error)
350+
break
351+
}
352+
})
353+
}
354+
355+
356+
func manualCompressionHelper(url: URL, bitRate: Float?, onProgress: @escaping (Float) -> Void, onCompletion: @escaping (URL) -> Void, onFailure: @escaping (Error) -> Void){
357+
var tmpURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
358+
.appendingPathComponent(ProcessInfo().globallyUniqueString)
359+
.appendingPathExtension("mp4")
360+
tmpURL = URL(string:makeValidUri(filePath: tmpURL.absoluteString))!
361+
362+
var _bitRate=bitRate;
363+
let asset = AVAsset(url: url)
364+
guard asset.tracks.count >= 1 else {
365+
let error = CompressionError(message: "Invalid video URL, no track found")
265366
onFailure(error)
266-
break
367+
return
368+
}
369+
var videoTrackIndex: Int = 0;
370+
let trackLength = asset.tracks.count;
371+
if(trackLength==2)
372+
{
373+
if(asset.tracks[0].mediaType.rawValue=="soun")
374+
{
375+
videoTrackIndex=1;
376+
}
267377
}
268-
break
269-
case .failure(let error):
270-
onFailure(error)
271-
break
378+
let track = asset.tracks[videoTrackIndex];
379+
let exporter = NextLevelSessionExporter(withAsset: asset)
380+
exporter.outputURL = tmpURL
381+
exporter.outputFileType = AVFileType.mp4
382+
383+
let videoSize = track.naturalSize.applying(track.preferredTransform);
384+
var width = Float(abs(videoSize.width))
385+
var height = Float(abs(videoSize.height))
386+
let isPortrait = height > width
387+
let maxSize = Float(1920);
388+
if(isPortrait && height > maxSize){
389+
width = (maxSize/height)*width
390+
height = maxSize
391+
}else if(width > maxSize){
392+
height = (maxSize/width)*height
393+
width = maxSize
394+
}
395+
else
396+
{
397+
_bitRate=bitRate ?? Float(abs(track.estimatedDataRate))*0.8
398+
}
399+
400+
let videoBitRate = _bitRate ?? height*width*1.5
401+
402+
let compressionDict: [String: Any] = [
403+
AVVideoAverageBitRateKey: videoBitRate,
404+
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
405+
]
406+
exporter.optimizeForNetworkUse = true;
407+
exporter.videoOutputConfiguration = [
408+
AVVideoCodecKey: AVVideoCodecType.h264,
409+
AVVideoWidthKey: width,
410+
AVVideoHeightKey: height,
411+
AVVideoScalingModeKey: AVVideoScalingModeResizeAspectFill,
412+
AVVideoCompressionPropertiesKey: compressionDict
413+
]
414+
exporter.audioOutputConfiguration = [
415+
AVFormatIDKey: kAudioFormatMPEG4AAC,
416+
AVEncoderBitRateKey: NSNumber(integerLiteral: 128000),
417+
AVNumberOfChannelsKey: NSNumber(integerLiteral: 2),
418+
AVSampleRateKey: NSNumber(value: Float(44100))
419+
]
420+
421+
422+
exporter.export(progressHandler: { (progress) in
423+
let _progress:Float=progress*100;
424+
if(Int(_progress)==self.videoCompressionCounter)
425+
{
426+
self.videoCompressionCounter=Int(_progress)+self.videoCompressionThreshold
427+
onProgress(progress)
428+
}
429+
430+
}, completionHandler: { result in
431+
self.videoCompressionCounter=0;
432+
switch result {
433+
case .success(let status):
434+
switch status {
435+
case .completed:
436+
onCompletion(exporter.outputURL!)
437+
break
438+
default:
439+
let error = CompressionError(message: "Compression didn't complete")
440+
onFailure(error)
441+
break
442+
}
443+
break
444+
case .failure(let error):
445+
onFailure(error)
446+
break
447+
}
448+
})
272449
}
273-
})
274-
}
275-
276-
}
450+
}

0 commit comments

Comments
 (0)