From a565e5c0211b4bebe3648f886f0eb97a05f95d2c Mon Sep 17 00:00:00 2001 From: Furkan Sarihan Date: Thu, 12 Jan 2023 01:58:14 +0300 Subject: [PATCH 1/3] fix framerate on ios, add file limit option on ios --- ios/Classes/SwiftVideoCompressPlugin.swift | 10 ++++++++-- lib/src/video_compress/video_compressor.dart | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ios/Classes/SwiftVideoCompressPlugin.swift b/ios/Classes/SwiftVideoCompressPlugin.swift index 1e727ff4..b4701af8 100644 --- a/ios/Classes/SwiftVideoCompressPlugin.swift +++ b/ios/Classes/SwiftVideoCompressPlugin.swift @@ -42,8 +42,9 @@ public class SwiftVideoCompressPlugin: NSObject, FlutterPlugin { let duration = args!["duration"] as? Double let includeAudio = args!["includeAudio"] as? Bool let frameRate = args!["frameRate"] as? Int + let fileLengthLimit = args!["fileLengthLimit"] as? Int compressVideo(path, quality, deleteOrigin, startTime, duration, includeAudio, - frameRate, result) + frameRate, fileLengthLimit, result) case "cancelCompression": cancelCompression(result) case "deleteAllCache": @@ -175,7 +176,7 @@ public class SwiftVideoCompressPlugin: NSObject, FlutterPlugin { } private func compressVideo(_ path: String,_ quality: NSNumber,_ deleteOrigin: Bool,_ startTime: Double?, - _ duration: Double?,_ includeAudio: Bool?,_ frameRate: Int?, + _ duration: Double?,_ includeAudio: Bool?,_ frameRate: Int?,_ fileLengthLimit: Int?, _ result: @escaping FlutterResult) { let sourceVideoUrl = Utility.getPathUrl(path) let sourceVideoType = "mp4" @@ -207,9 +208,14 @@ public class SwiftVideoCompressPlugin: NSObject, FlutterPlugin { exporter.outputURL = compressionUrl exporter.outputFileType = AVFileType.mp4 exporter.shouldOptimizeForNetworkUse = true + exporter.canPerformMultiplePassesOverSourceMediaData = true + if fileLengthLimit != nil { + exporter.fileLengthLimit = Int64(fileLengthLimit!) + } if frameRate != nil { let videoComposition = AVMutableVideoComposition(propertiesOf: sourceVideoAsset) + videoComposition.sourceTrackIDForFrameTiming = kCMPersistentTrackID_Invalid; videoComposition.frameDuration = CMTimeMake(value: 1, timescale: Int32(frameRate!)) exporter.videoComposition = videoComposition } diff --git a/lib/src/video_compress/video_compressor.dart b/lib/src/video_compress/video_compressor.dart index 3b7b38e3..17b3ee9a 100644 --- a/lib/src/video_compress/video_compressor.dart +++ b/lib/src/video_compress/video_compressor.dart @@ -128,6 +128,7 @@ extension Compress on IVideoCompress { int? duration, bool? includeAudio, int frameRate = 30, + int? fileLengthLimit, }) async { if (isCompressing) { throw StateError('''VideoCompress Error: @@ -150,6 +151,7 @@ extension Compress on IVideoCompress { 'duration': duration, 'includeAudio': includeAudio, 'frameRate': frameRate, + 'fileLengthLimit': fileLengthLimit, }); // ignore: invalid_use_of_protected_member From 447f6f733c68771ef657a0bd0be3f0a57a00c321 Mon Sep 17 00:00:00 2001 From: Furkan Sarihan Date: Thu, 12 Jan 2023 03:44:00 +0300 Subject: [PATCH 2/3] fix framerate android, custom bitrate on android --- .../video_compress/VideoCompressPlugin.kt | 41 ++++++++++++++----- lib/src/video_compress/video_compressor.dart | 5 +++ 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/android/src/main/kotlin/com/example/video_compress/VideoCompressPlugin.kt b/android/src/main/kotlin/com/example/video_compress/VideoCompressPlugin.kt index 2861da1d..7ceafd46 100644 --- a/android/src/main/kotlin/com/example/video_compress/VideoCompressPlugin.kt +++ b/android/src/main/kotlin/com/example/video_compress/VideoCompressPlugin.kt @@ -83,28 +83,35 @@ class VideoCompressPlugin : MethodCallHandler, FlutterPlugin { val duration = call.argument("duration") val includeAudio = call.argument("includeAudio") ?: true val frameRate = if (call.argument("frameRate")==null) 30 else call.argument("frameRate") + val bitrate = if (call.argument("bitrate")==null) DefaultVideoStrategy.BITRATE_UNKNOWN else call.argument("bitrate") val tempDir: String = context.getExternalFilesDir("video_compress")!!.absolutePath val out = SimpleDateFormat("yyyy-MM-dd hh-mm-ss").format(Date()) val destPath: String = tempDir + File.separator + "VID_" + out + path.hashCode() + ".mp4" var videoTrackStrategy: TrackStrategy = DefaultVideoStrategy.atMost(340).build(); - val audioTrackStrategy: TrackStrategy when (quality) { - 0 -> { - videoTrackStrategy = DefaultVideoStrategy.atMost(720).build() + videoTrackStrategy = DefaultVideoStrategy + .atMost(720) + .bitRate(bitrate!!.toLong()) + .frameRate(frameRate!!).build() } 1 -> { - videoTrackStrategy = DefaultVideoStrategy.atMost(360).build() + videoTrackStrategy = DefaultVideoStrategy + .atMost(360) + .bitRate(bitrate!!.toLong()) + .frameRate(frameRate!!).build() } 2 -> { - videoTrackStrategy = DefaultVideoStrategy.atMost(640).build() + videoTrackStrategy = DefaultVideoStrategy + .atMost(640) + .bitRate(bitrate!!.toLong()) + .frameRate(frameRate!!).build() } 3 -> { - assert(value = frameRate != null) videoTrackStrategy = DefaultVideoStrategy.Builder() .keyFrameInterval(3f) @@ -113,20 +120,32 @@ class VideoCompressPlugin : MethodCallHandler, FlutterPlugin { .build() } 4 -> { - videoTrackStrategy = DefaultVideoStrategy.atMost(480, 640).build() + videoTrackStrategy = DefaultVideoStrategy + .atMost(480, 640) + .bitRate(bitrate!!.toLong()) + .frameRate(frameRate!!).build() } 5 -> { - videoTrackStrategy = DefaultVideoStrategy.atMost(540, 960).build() + videoTrackStrategy = DefaultVideoStrategy + .atMost(540, 960) + .bitRate(bitrate!!.toLong()) + .frameRate(frameRate!!).build() } 6 -> { - videoTrackStrategy = DefaultVideoStrategy.atMost(720, 1280).build() + videoTrackStrategy = DefaultVideoStrategy + .atMost(720, 1280) + .bitRate(bitrate!!.toLong()) + .frameRate(frameRate!!).build() } 7 -> { - videoTrackStrategy = DefaultVideoStrategy.atMost(1080, 1920).build() + videoTrackStrategy = DefaultVideoStrategy + .atMost(1080, 1920) + .bitRate(bitrate!!.toLong()) + .frameRate(frameRate!!).build() } } - audioTrackStrategy = if (includeAudio) { + val audioTrackStrategy: TrackStrategy = if (includeAudio) { val sampleRate = DefaultAudioStrategy.SAMPLE_RATE_AS_INPUT val channels = DefaultAudioStrategy.CHANNELS_AS_INPUT diff --git a/lib/src/video_compress/video_compressor.dart b/lib/src/video_compress/video_compressor.dart index 17b3ee9a..85f2acb7 100644 --- a/lib/src/video_compress/video_compressor.dart +++ b/lib/src/video_compress/video_compressor.dart @@ -112,6 +112,9 @@ extension Compress on IVideoCompress { /// determine whether to delete his source file by [deleteOrigin] /// optional parameters [startTime] [duration] [includeAudio] [frameRate] /// + /// [fileLengthLimit] works only on ios + /// [bitrate] works only on android + /// /// ## example /// ```dart /// final info = await _flutterVideoCompress.compressVideo( @@ -129,6 +132,7 @@ extension Compress on IVideoCompress { bool? includeAudio, int frameRate = 30, int? fileLengthLimit, + int? bitrate, }) async { if (isCompressing) { throw StateError('''VideoCompress Error: @@ -152,6 +156,7 @@ extension Compress on IVideoCompress { 'includeAudio': includeAudio, 'frameRate': frameRate, 'fileLengthLimit': fileLengthLimit, + 'bitrate': bitrate, }); // ignore: invalid_use_of_protected_member From 3f00884e2160d58ae197d50cad7544122b87f6a3 Mon Sep 17 00:00:00 2001 From: Furkan Sarihan Date: Thu, 12 Jan 2023 04:15:54 +0300 Subject: [PATCH 3/3] better api --- lib/src/video_compress/video_compressor.dart | 29 +++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/src/video_compress/video_compressor.dart b/lib/src/video_compress/video_compressor.dart index 85f2acb7..fdf6bb1b 100644 --- a/lib/src/video_compress/video_compressor.dart +++ b/lib/src/video_compress/video_compressor.dart @@ -5,6 +5,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; + import 'package:video_compress/src/progress_callback/compress_mixin.dart'; import 'package:video_compress/video_compress.dart'; @@ -131,8 +132,8 @@ extension Compress on IVideoCompress { int? duration, bool? includeAudio, int frameRate = 30, - int? fileLengthLimit, - int? bitrate, + IosOptions? iosOptions, + AndroidOptions? androidOptions, }) async { if (isCompressing) { throw StateError('''VideoCompress Error: @@ -155,8 +156,9 @@ extension Compress on IVideoCompress { 'duration': duration, 'includeAudio': includeAudio, 'frameRate': frameRate, - 'fileLengthLimit': fileLengthLimit, - 'bitrate': bitrate, + if (iosOptions?.fileLengthLimit != null) + 'fileLengthLimit': iosOptions!.fileLengthLimit, + if (androidOptions?.bitrate != null) 'bitrate': androidOptions!.bitrate, }); // ignore: invalid_use_of_protected_member @@ -188,3 +190,22 @@ extension Compress on IVideoCompress { }); } } + +class IosOptions { + /// The file length that the output of the session must not exceed. + /// https://developer.apple.com/documentation/avfoundation/avassetexportsession/1622333-filelengthlimit + final int? fileLengthLimit; + IosOptions({ + this.fileLengthLimit, + }); +} + +class AndroidOptions { + /// Desired bit rate (bits per second). Can optionally be + /// null, in which case the strategy will try to estimate the bitrate. + /// https://natario1.github.io/Transcoder/docs/track-strategies#other-options + final int? bitrate; + AndroidOptions({ + this.bitrate, + }); +}