├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_template.md │ ├── enhancement_template.md │ ├── help_template.md │ └── other_template.md ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── .idea │ ├── .name │ ├── caches │ │ └── build_file_checksums.ser │ ├── encodings.xml │ ├── gradle.xml │ ├── misc.xml │ ├── modules.xml │ └── runConfigurations.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── example │ └── flutter_video_compress │ ├── FFmpegCommander.kt │ ├── FlutterVideoCompressPlugin.kt │ ├── ThumbnailUtility.kt │ ├── Utility.kt │ └── VideoQuality.kt ├── doc ├── chinese.md ├── images │ ├── logo.svg │ └── preview.gif └── japanese.md ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── flutter_video_compress_example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── main.dart │ └── my-theme.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── flutter_video_compress.iml ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── AvController.swift │ ├── FlutterVideoCompressPlugin.h │ ├── FlutterVideoCompressPlugin.m │ ├── SwiftFlutterVideoCompressPlugin.swift │ └── Utility.swift └── flutter_video_compress.podspec ├── lib ├── android │ └── media_metadata_retriever.dart ├── flutter_video_compress.dart ├── model │ └── media_info.dart └── src │ ├── flutter_video_compress.dart │ ├── observable_builder.dart │ └── video_quality.dart ├── pubspec.yaml └── test └── flutter_video_compress_test.dart /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [TenkaiRuri] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: tenkairuri 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: https://paypal.me/tenkairuri 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🙀 Bug report" 3 | about: Report bugs to improve this library 4 | title: "[Bug]" 5 | labels: bug 6 | assignees: TenkaiRuri 7 | 8 | --- 9 | 10 | ### Description 11 | Provide detailed contextual explanations to help us understand 12 | 13 | ### Platform 14 | IOS|Android|Both|Other 15 | 16 | ### Backtracking step (if has) 17 | 1. open application 18 | 2. touch somewhere 5 times 19 | 3. crash 20 | 21 | ### Code Example (if has) 22 | ```dart 23 | void main() { 24 | 25 | } 26 | ``` 27 | 28 | ### Expected solution 29 | Expect description of the solution 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "😳 enhancement" 3 | about: New features or enhancements you want 4 | title: "[Feature]" 5 | labels: enhancement 6 | assignees: TenkaiRuri 7 | 8 | --- 9 | 10 | ### Description 11 | Provide detailed contextual explanations to help us understand 12 | 13 | ### Platform 14 | IOS|Android|Both|Other 15 | 16 | ### Code Example (if has) 17 | ```dart 18 | void main() { 19 | 20 | } 21 | ``` 22 | 23 | ### Expected solution 24 | Expect description of the solution 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/help_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🤔 Help wanted" 3 | about: Need some help 4 | title: "[help]" 5 | labels: "help wanted" 6 | assignees: TenkaiRuri 7 | 8 | --- 9 | 10 | ### Description 11 | Provide detailed contextual explanations to help us understand 12 | 13 | ### Platform 14 | IOS|Android|Both|Other 15 | 16 | ### Code Example (if has) 17 | ```dart 18 | void main() { 19 | 20 | } 21 | ``` 22 | 23 | ### Expected solution 24 | Expect description of the solution 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🌏 Other" 3 | about: other 4 | title: "" 5 | labels: "" 6 | assignees: TenkaiRuri 7 | 8 | --- 9 | 10 | ### Description 11 | Provide detailed contextual explanations to help us understand -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .atom/ 3 | .idea/ 4 | .idea/* 5 | .vscode/ 6 | .vscode/* 7 | 8 | .packages 9 | .pub/ 10 | .dart_tool/ 11 | pubspec.lock 12 | 13 | Podfile 14 | Podfile.lock 15 | Pods/ 16 | .symlinks/ 17 | **/Flutter/App.framework/ 18 | **/Flutter/Flutter.framework/ 19 | **/Flutter/Generated.xcconfig 20 | **/Flutter/flutter_assets/ 21 | ServiceDefinitions.json 22 | xcuserdata/ 23 | gen/ 24 | 25 | local.properties 26 | keystore.properties 27 | .gradle/ 28 | gradlew 29 | gradlew.bat 30 | gradle-wrapper.jar 31 | *.iml 32 | 33 | GeneratedPluginRegistrant.h 34 | GeneratedPluginRegistrant.m 35 | GeneratedPluginRegistrant.java 36 | build/ 37 | .flutter-plugins -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 8661d8aecd626f7f57ccbcb735553edc05a2e713 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.3.7+8 2 | * docs change pubspec.yaml description 3 | 4 | ## 0.3.7+7 5 | * docs Better English documentation 6 | 7 | ## 0.3.7+6 8 | * docs better example 9 | 10 | ## 0.3.7+5 11 | * docs better example 12 | 13 | ## 0.3.7+4 14 | * docs better example 15 | 16 | ## 0.3.7+3 17 | * docs deleteAllCache api return whether the boolean value is deleted 18 | 19 | ## 0.3.7+2 20 | * fix `ios` path problem 21 | * fix `ios` deleteAllCache functional repair 22 | * docs `document` deleteAllCache api description 23 | * fix `android` ffmpeg keep metadata orientation by -noautorotate command 24 | 25 | ## 0.3.7+1 26 | * feat `both` add api deleteAllCache to delete cache, please do not put other things in the folder of this plugin, it will be cleared 27 | 28 | ## 0.3.7 29 | * **Breaking change** `both` rename startCompress to compressVideo, stopCompress to cancelCompression 30 | * feat `both` compressVideo api add params { int startTime, int duration, bool includeAudio, int frameRate } 31 | * feat `both` getMediaInfo returns add orientation 32 | * fix `android` getMediaInfo returns height and width flipped 33 | * fix `android` different quality compression size 34 | * style `example` fixed some problems with example 35 | 36 | ## 0.3.6 37 | * docs `document` ios swift version documents of how to fix Regift 38 | * refactor `android` reduce compressing time 39 | * refactor `android` remove updateProgress redundant code to improve performance 40 | * refactor `dart` change all print to debugPrint to improve release performance 41 | 42 | ## 0.3.5+4 43 | * Support latest dependencies 44 | 45 | ## 0.3.5+3 46 | * update documents 47 | 48 | ## 0.3.5+2 49 | * increased swift version to 5.0 50 | 51 | ## 0.3.5+1 52 | * reduced frame rate to 15 on android, use milliseconds for duration 53 | 54 | ## 0.3.5 55 | * **Breaking change** fix The old version does not scale the Android compressed video, 56 | That is, the startCompress `quality` properties has no effect. 57 | * fix IOS pod dependency `Regift` 58 | * feat enum `VideoQuality` add DefaultQuality, startCompress `quality` Use the default properties of the old version 59 | * fix IOS convertVideoToGif file Suffix 60 | * docs update example 61 | 62 | ## 0.3.4+1 63 | * fix example lost the IOS pod dependency `Regift` 64 | 65 | ## 0.3.4 66 | * docs update convertVideoToGif associated document 67 | * feat Created API for conversion of video files to gif with start time 68 | and duration parameters. 69 | For Android ffmpeg is used for conversion and for iOS Regift 70 | library is used. 71 | Also, added an example on how to use the API within example project. 72 | 73 | ## 0.3.3+3 74 | * docs update README.md 75 | 76 | ## 0.3.3+2 77 | * docs update README.md 78 | 79 | ## 0.3.3+1 80 | * docs docs folder to doc 81 | 82 | ## 0.3.3 83 | * docs international documentation increased 84 | 85 | ## 0.3.2 86 | * fix unsubscribe excess stream error 87 | 88 | ## 0.3.1 89 | * fix compressed video is null 90 | * fix Bad state: Stream has already been listened to 91 | * fix Video same address cannot be compressed for the second time 92 | 93 | ## 0.3.0 94 | * **Breaking change** refactor ios anderoid lib code 95 | * feat add getThumbnailWithFile and getMediaInfo api 96 | * fix android private folder permissions cause possible problems 97 | * change startCompress api return type 98 | 99 | ## 0.2.0+9 100 | * doc update document 101 | 102 | ## 0.2.0+8 103 | * doc update document 104 | 105 | ## 0.2.0+7 106 | * fix ios getThumbnail path error 107 | * Better tips of Compressing error 108 | * **Breaking change** start Compress api add whether to cancel the parameter 109 | 110 | ## 0.2.0+6 111 | * chore update swift 5 112 | 113 | ## 0.2.0+5 114 | * fix: fix the wrong Usage 115 | 116 | ## 0.2.0+4 117 | * fix: add some suggestions for reduce apk size 118 | * **Breaking change** compressVideo api renamed startCompress 119 | * feat: add stopCompress api 120 | * update README.md 121 | 122 | ## 0.2.0+3 123 | no change 124 | 125 | ## 0.2.0+2 126 | * fix: IOS fix import AVFoundation 127 | 128 | ## 0.2.0+1 129 | * fix: Android video type changed h264 130 | 131 | ## 0.2.0 132 | * **Breaking change** Migrated dependent sdk, Upgraded kotlin and gradle versions 133 | * fix: solved Android transcoding video error code 8 134 | * update example/README.md 135 | 136 | ## 0.1.7 137 | * docs: Better Readme 138 | 139 | ## 0.1.6 140 | * docs: Better documentation 141 | 142 | ## 0.1.5 143 | * CHANGELOG.md 144 | * fix: supported x86 arm64-v8a armeabi armeabi-v7a 145 | 146 | ## 0.1.4 147 | * fix:gradle config 'compile' replaced with 'implementation' 148 | 149 | ## 0.1.3 150 | * fix: fix ios 4.2 version bugs 151 | 152 | ## 0.1.2 153 | * fix: fix ios build swift_version 154 | 155 | ## 0.1.1 156 | * chore: update `AndroidX` 157 | 158 | ## 0.1.0 159 | 160 | * feature: get vedio thumbnail 161 | * feature: compress vedio 162 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Amami Ruri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | # flutter_video_compress 6 | 7 | Generate a new path by compressed video, Choose to keep the source video or delete it by a parameter. Get video thumbnail from a video path and provide video information. Easy to deal with compressed video. Considering reduce application size is not using FFmpeg in IOS. 8 | 9 |

10 | pub version 11 | license 12 | android min Sdk Version 13 | ios min target 14 |

15 | 16 |
17 | flutter compress video 18 |
19 | 20 | ## languages 21 | [English](https://github.com/TenkaiRuri/flutter_video_compress#flutter_video_compress) [简体中文](https://github.com/TenkaiRuri/flutter_video_compress/blob/master/doc/chinese.md#flutter_video_compress) [日本語](https://github.com/TenkaiRuri/flutter_video_compress/blob/master/doc/japanese.md#flutter_video_compress) 22 | 23 | ## Usage 24 | 25 | **Installing** 26 | add [flutter_video_compress](https://pub.dartlang.org/packages/flutter_video_compress) as a dependency in your pubspec.yaml file. 27 | ```yaml 28 | dependencies: 29 | flutter_video_compress: ^0.3.x 30 | ``` 31 | 32 | **Create an instance** 33 | ```dart 34 | final _flutterVideoCompress = FlutterVideoCompress(); 35 | ``` 36 | 37 | **Get thumbnail from video path** 38 | ```dart 39 | final uint8list = await _flutterVideoCompress.getThumbnail( 40 | file.path, 41 | quality: 50, // default(100) 42 | position: -1 // default(-1) 43 | ); 44 | ``` 45 | 46 | **Get thumbnail file from video path** 47 | ```dart 48 | final thumbnailFile = await _flutterVideoCompress.getThumbnailWithFile( 49 | file.path, 50 | quality: 50, // default(100) 51 | position: -1 // default(-1) 52 | ); 53 | ``` 54 | 55 | **Convert video to a gif** 56 | ```dart 57 | final file = await _flutterVideoCompress.convertVideoToGif( 58 | videoFile.path, 59 | startTime: 0, // default(0) 60 | duration: 5, // default(-1) 61 | // endTime: -1 // default(-1) 62 | ); 63 | debugPrint(file.path); 64 | ``` 65 | 66 | **Get media information** 67 | > only support video now. 68 | 69 | ```dart 70 | final info = await _flutterVideoCompress.getMediaInfo(file.path); 71 | debugPrint(info.toJson().toString()); 72 | ``` 73 | 74 | **Compression Video** 75 | > Compatible with IOS, Android and Web after compression. 76 | 77 | ```dart 78 | final info = await _flutterVideoCompress.compressVideo( 79 | file.path, 80 | quality: VideoQuality.DefaultQuality, // default(VideoQuality.DefaultQuality) 81 | deleteOrigin: false, // default(false) 82 | ); 83 | debugPrint(info.toJson().toString()); 84 | ``` 85 | 86 | **Check Compressing state** 87 | ```dart 88 | _flutterVideoCompress.isCompressing 89 | ``` 90 | 91 | **Stop compression** 92 | > Will print InterruptedException in android, but not affect to use. 93 | 94 | ```dart 95 | await _flutterVideoCompress.cancelCompression() 96 | ``` 97 | 98 | **delete all cache files** 99 | > Delete all files generated by this will delete all files located at 'flutter_video_compress', you shoule ought to know what are you doing. 100 | 101 | ```dart 102 | await _flutterVideoCompress.deleteAllCache() 103 | ``` 104 | 105 | **Subscribe the compression progress steam** 106 | ```dart 107 | class ... extends State { 108 | Subscription _subscription; 109 | 110 | @override 111 | void initState() { 112 | super.initState(); 113 | _subscription = 114 | _flutterVideoCompress.compressProgress$.subscribe((progress) { 115 | debugPrint('progress: $progress'); 116 | }); 117 | } 118 | 119 | @override 120 | void dispose() { 121 | super.dispose(); 122 | _subscription.unsubscribe(); 123 | } 124 | } 125 | ``` 126 | 127 | ## Methods 128 | |Functions|Parameters|Description|Returns| 129 | |--|--|--|--| 130 | |getThumbnail|String `path`[video path], int `quality`(1-100)[thumbnail quality], int `position`[Get a thumbnail from video position]|get thumbnail from video `path`|`Future`| 131 | |getThumbnailWithFile|String `path`[video path], int `quality`(1-100)[thumbnail quality], int `position`[Get a thumbnail from video position]|get thumbnail file from video `path`|`Future`| 132 | |convertVideoToGif|String `path`[video path], int `startTime`(from 0 start)[convert video to gif start time], int `endTime`[convert video to gif end time], int `duration`[convert video to gif duration from start time]|convert video to gif from video `path`|`Future`| 133 | |getMediaInfo|String `path`[video path]|get media information from video `path`|`Future`| 134 | |compressVideo|String `path`[video path], VideoQuality `quality`[compressed video quality], bool `deleteOrigin`[delete the origin video], int `startTime`[compression video start time], int `duration`[compression video duration from start time], bool `includeAudio`[is include audio in compressed video], int `frameRate`[compressed video frame rate]|compression video at origin video `path`|`Future`| 135 | |cancelCompression|`none`|cancel compressing|`Future`| 136 | |deleteAllCache|`none`|Delete all files generated by 'flutter_video_compress' will delete all files located at 'flutter_video_compress'|`Future`| 137 | 138 | ## Subscriptions 139 | |Subscriptions|Description|Stream| 140 | |--|--|--| 141 | |compressProgress$|Subscribe the compression progress steam|double `progress`| 142 | 143 | ## Notice 144 | If your application is significantly larger after using the plugin, you can reduce the application size in the following way: 145 | 146 | * exclude `x86` related files (`./assets`) 147 | 148 | * This library not use `ffprobe`, only used `ffmpeg`, but the application still has `ffprobe`, so you will need to exclude (`asssets/arm` or `assets/x86`) 149 | 150 | add this config in `build.gradle`: 151 | * __Don't use__ `ignoreAssetsPattern "!x86"` in debug mode, will crash on the simulator 152 | 153 | ```gradle 154 | android { 155 | ... 156 | // Reduce your application size with this configuration 157 | aaptOptions { 158 | ignoreAssetsPattern "!x86:!*ffprobe" 159 | } 160 | 161 | buildTypes { 162 | ... 163 | } 164 | ``` 165 | [look detail](https://github.com/bravobit/FFmpeg-Android/wiki/Reduce-APK-File-Size#exclude-architecture) 166 | 167 | ## Questions 168 | 169 | If your application is not enabled `AndroidX`, you will need to add the following code to the last line of the `android/build.gradle` file. 170 | ```groovy 171 | rootProject.allprojects { 172 | subprojects { 173 | project.configurations.all { 174 | resolutionStrategy.eachDependency { details -> 175 | if (details.requested.group == 'androidx.core' && !details.requested.name.contains('androidx')) { 176 | details.useVersion "1.0.1" 177 | } 178 | } 179 | } 180 | } 181 | } 182 | ``` 183 | 184 | If your application not support swift, you need to add the following code in `ios/Podfile`. 185 | ```ruby 186 | target 'Runner' do 187 | use_frameworks! # <--- add this 188 | ... 189 | end 190 | ``` 191 | 192 | [look detail](https://github.com/flutter/flutter/issues/16049#issuecomment-382629492) 193 | 194 | 195 | If your application never used a swift plugin before, maybe you would meet the error, you need to add the following code in `ios/Podfile`. 196 | > The 'Pods-Runner' target has transitive dependencies that include static binaries 197 | 198 | ```ruby 199 | pre_install do |installer| 200 | # workaround for https://github.com/CocoaPods/CocoaPods/issues/3289 201 | Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {} 202 | end 203 | ``` 204 | 205 | **If the above method not work, you report bug of the error repository. The reason is「can't support swift」** 206 | 207 | [look detail](https://github.com/flutter/flutter/issues/16049#issue-309580132) 208 | 209 | if show error of `Regift` 210 | 211 | > - `Regift` does not specify a Swift version and none of the targets (`Runner`) integrating it have the `SWIFT_VERSION` attribute set. Please contact the author or set the `SWIFT_VERSION` attribute in at least one of the targets that integrate this pod. 212 | 213 | ```ruby 214 | pre_install do |installer| 215 | installer.analysis_result.specifications.each do |s| 216 | if s.name == 'Regift' 217 | s.swift_version = '4.0' 218 | end 219 | end 220 | end 221 | 222 | post_install do |installer| 223 | installer.pods_project.targets.each do |target| 224 | target.build_configurations.each do |config| 225 | config.build_settings['ENABLE_BITCODE'] = 'NO' 226 | end 227 | end 228 | end 229 | ``` 230 | 231 | ## Contribute 232 | 233 | Contributions are always welcome! 234 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/.idea/.name: -------------------------------------------------------------------------------- 1 | flutter_video_compress -------------------------------------------------------------------------------- /android/.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/android/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /android/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /android/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | -------------------------------------------------------------------------------- /android/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /android/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /android/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.example.flutter_video_compress' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.3.31' 6 | repositories { 7 | google() 8 | jcenter() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:3.4.1' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | rootProject.allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | } 22 | } 23 | 24 | apply plugin: 'com.android.library' 25 | apply plugin: 'kotlin-android' 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | sourceSets { 31 | main.java.srcDirs += 'src/main/kotlin' 32 | } 33 | defaultConfig { 34 | minSdkVersion 16 35 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 36 | } 37 | lintOptions { 38 | disable 'InvalidPackage' 39 | } 40 | } 41 | 42 | dependencies { 43 | implementation 'nl.bravobit:android-ffmpeg:1.1.7' 44 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 45 | } 46 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | android.enableJetifier=true 2 | android.useAndroidX=true 3 | org.gradle.jvmargs=-Xmx1536M 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri May 17 15:24:59 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'flutter_video_compress' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/example/flutter_video_compress/FFmpegCommander.kt: -------------------------------------------------------------------------------- 1 | package com.example.flutter_video_compress 2 | 3 | import android.content.Context 4 | import io.flutter.plugin.common.BinaryMessenger 5 | import io.flutter.plugin.common.MethodChannel 6 | import nl.bravobit.ffmpeg.ExecuteBinaryResponseHandler 7 | import nl.bravobit.ffmpeg.FFmpeg 8 | import nl.bravobit.ffmpeg.FFtask 9 | import java.io.File 10 | 11 | class FFmpegCommander(private val context: Context, private val channelName: String) { 12 | private var stopCommand = false 13 | private var ffTask: FFtask? = null 14 | private val utility = Utility(channelName) 15 | private var totalTime: Long = 0 16 | 17 | 18 | fun compressVideo(path: String, quality: VideoQuality, deleteOrigin: Boolean, 19 | startTime: Int?, duration: Int? = null, includeAudio: Boolean?, 20 | frameRate: Int?, result: MethodChannel.Result, 21 | messenger: BinaryMessenger) { 22 | 23 | val ffmpeg = FFmpeg.getInstance(context) 24 | 25 | if (!ffmpeg.isSupported) { 26 | return result.error(channelName, "FlutterVideoCompress Error", 27 | "ffmpeg isn't supported this platform") 28 | } 29 | 30 | val dir = context.getExternalFilesDir("flutter_video_compress") 31 | 32 | if (dir != null && !dir.exists()) dir.mkdirs() 33 | 34 | val file = File(dir, path.substring(path.lastIndexOf("/"))) 35 | utility.deleteFile(file) 36 | 37 | val scale = quality.getScaleString() 38 | val cmdArray = mutableListOf("-noautorotate", "-i", path, "-vcodec", "h264", "-crf", "28", "-movflags", "+faststart", "-vf", "scale=$scale:-2", "-preset:v", "ultrafast", "-b:v", "1000k") 39 | 40 | // Add high bitrate for the highest quality 41 | // if (quality.isHighQuality()) { 42 | // cmdArray.addAll(listOf("-preset:v", "ultrafast", "-b:v", "1000k")) 43 | // } 44 | 45 | if (startTime != null) { 46 | cmdArray.add("-ss") 47 | cmdArray.add(startTime.toString()) 48 | 49 | if (duration != null) { 50 | cmdArray.add("-t") 51 | cmdArray.add(duration.toString()) 52 | } 53 | } 54 | 55 | if (includeAudio != null && !includeAudio) { 56 | cmdArray.add("-an") 57 | } 58 | 59 | if (frameRate != null) { 60 | cmdArray.add("-r") 61 | cmdArray.add(frameRate.toString()) 62 | } 63 | 64 | cmdArray.add(file.absolutePath) 65 | 66 | this.ffTask = ffmpeg.execute(cmdArray.toTypedArray(), 67 | object : ExecuteBinaryResponseHandler() { 68 | override fun onProgress(message: String) { 69 | notifyProgress(message, messenger) 70 | 71 | if (stopCommand) { 72 | print("FlutterVideoCompress: Video compression has stopped") 73 | stopCommand = false 74 | val json = utility.getMediaInfoJson(context, path) 75 | json.put("isCancel", true) 76 | result.success(json.toString()) 77 | totalTime = 0 78 | ffTask?.killRunningProcess() 79 | } 80 | } 81 | 82 | override fun onFinish() { 83 | val json = utility.getMediaInfoJson(context, file.absolutePath) 84 | json.put("isCancel", false) 85 | result.success(json.toString()) 86 | if (deleteOrigin) { 87 | File(path).delete() 88 | } 89 | totalTime = 0 90 | } 91 | }) 92 | } 93 | 94 | private fun isLandscapeImage(orientation: Int) = orientation != 90 && orientation != 270 95 | 96 | 97 | fun convertVideoToGif(path: String, startTime: Long = 0, endTime: Long, duration: Long, 98 | result: MethodChannel.Result, messenger: BinaryMessenger) { 99 | var gifDuration = 0L 100 | if (endTime > 0) { 101 | if (startTime > endTime) { 102 | result.error(channelName, "FlutterVideoCompress Error", 103 | "startTime should be greater than startTime") 104 | } else { 105 | gifDuration = (endTime - startTime) 106 | } 107 | } else { 108 | gifDuration = duration 109 | } 110 | 111 | val ffmpeg = FFmpeg.getInstance(context) 112 | 113 | if (!ffmpeg.isSupported) { 114 | return result.error(channelName, "FlutterVideoCompress Error", 115 | "ffmpeg is not supported this platform") 116 | } 117 | 118 | val dir = context.getExternalFilesDir("flutter_video_compress") 119 | 120 | if (dir != null && !dir.exists()) dir.mkdirs() 121 | 122 | 123 | val file = File(dir, utility.getFileNameWithGifExtension(path)) 124 | utility.deleteFile(file) 125 | 126 | val cmd = arrayOf("-i", path, "-ss", startTime.toString(), "-t", gifDuration.toString(), 127 | "-vf", "scale=640:-2", "-r", "15", file.absolutePath) 128 | 129 | this.ffTask = ffmpeg.execute(cmd, object : ExecuteBinaryResponseHandler() { 130 | override fun onProgress(message: String) { 131 | notifyProgress(message, messenger) 132 | } 133 | 134 | override fun onFinish() { 135 | result.success(file.absolutePath) 136 | } 137 | }) 138 | } 139 | 140 | private fun notifyProgress(message: String, messenger: BinaryMessenger) { 141 | if ("Duration" in message) { 142 | val reg = Regex("""Duration: ((\d{2}:){2}\d{2}\.\d{2}).*""") 143 | val totalTimeStr = message.replace(reg, "$1") 144 | totalTime = utility.timeStrToTimestamp(totalTimeStr.trim()) 145 | } 146 | 147 | if ("frame=" in message) { 148 | try { 149 | val reg = Regex("""frame.*time=((\d{2}:){2}\d{2}\.\d{2}).*""") 150 | val totalTimeStr = message.replace(reg, "$1") 151 | val time = utility.timeStrToTimestamp(totalTimeStr.trim()) 152 | MethodChannel(messenger, channelName) 153 | .invokeMethod("updateProgress", ((time.toDouble() / totalTime.toDouble()) * 100).toString()) 154 | } catch (e: Exception) { 155 | print(e.stackTrace) 156 | } 157 | } 158 | 159 | MethodChannel(messenger, channelName).invokeMethod("updateProgress", message) 160 | } 161 | 162 | fun cancelCompression() { 163 | if (ffTask != null && !ffTask!!.isProcessCompleted) { 164 | stopCommand = true 165 | } 166 | } 167 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/com/example/flutter_video_compress/FlutterVideoCompressPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.example.flutter_video_compress 2 | 3 | import io.flutter.plugin.common.MethodCall 4 | import io.flutter.plugin.common.MethodChannel 5 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 6 | import io.flutter.plugin.common.MethodChannel.Result 7 | import io.flutter.plugin.common.PluginRegistry.Registrar 8 | 9 | 10 | class FlutterVideoCompressPlugin : MethodCallHandler { 11 | 12 | private val channelName = "flutter_video_compress" 13 | private val utility = Utility(channelName) 14 | private var ffmpegCommander: FFmpegCommander? = null 15 | 16 | companion object { 17 | private lateinit var reg: Registrar 18 | 19 | @JvmStatic 20 | fun registerWith(registrar: Registrar) { 21 | val channel = MethodChannel(registrar.messenger(), "flutter_video_compress") 22 | channel.setMethodCallHandler(FlutterVideoCompressPlugin()) 23 | reg = registrar 24 | } 25 | } 26 | 27 | override fun onMethodCall(call: MethodCall, result: Result) { 28 | initFfmpegCommanderIfNeeded() 29 | when (call.method) { 30 | "getThumbnail" -> { 31 | val path = call.argument("path")!! 32 | val quality = call.argument("quality")!! 33 | val position = call.argument("position")!!.toLong() 34 | ThumbnailUtility(channelName).getThumbnail(path, quality, position, result) 35 | } 36 | "getThumbnailWithFile" -> { 37 | val path = call.argument("path")!! 38 | val quality = call.argument("quality")!! 39 | val position = call.argument("position")!!.toLong() 40 | ThumbnailUtility(channelName).getThumbnailWithFile(reg.context(), path, quality, 41 | position, result) 42 | } 43 | "getMediaInfo" -> { 44 | val path = call.argument("path")!! 45 | result.success(utility.getMediaInfoJson(reg.context(), path).toString()) 46 | } 47 | "compressVideo" -> { 48 | val path = call.argument("path")!! 49 | val quality = call.argument("quality")!! 50 | val deleteOrigin = call.argument("deleteOrigin")!! 51 | val startTime = call.argument("startTime") 52 | val duration = call.argument("duration") 53 | val includeAudio = call.argument("includeAudio") 54 | val frameRate = call.argument("frameRate") 55 | 56 | ffmpegCommander?.compressVideo(path, VideoQuality.from(quality), deleteOrigin, 57 | startTime, duration, includeAudio, frameRate, result, reg.messenger()) 58 | } 59 | "cancelCompression" -> { 60 | ffmpegCommander?.cancelCompression() 61 | result.success("") 62 | } 63 | "convertVideoToGif" -> { 64 | val path = call.argument("path")!! 65 | val startTime = call.argument("startTime")!!.toLong() 66 | val endTime = call.argument("endTime")!!.toLong() 67 | val duration = call.argument("duration")!!.toLong() 68 | 69 | ffmpegCommander?.convertVideoToGif(path, startTime, endTime, duration, result, 70 | reg.messenger()) 71 | } 72 | "deleteAllCache" -> { 73 | utility.deleteAllCache(reg.context(), result) 74 | } 75 | else -> result.notImplemented() 76 | } 77 | } 78 | 79 | private fun initFfmpegCommanderIfNeeded() { 80 | if (ffmpegCommander == null) { 81 | ffmpegCommander = FFmpegCommander(reg.context(), channelName) 82 | } 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/example/flutter_video_compress/ThumbnailUtility.kt: -------------------------------------------------------------------------------- 1 | package com.example.flutter_video_compress 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import io.flutter.plugin.common.MethodChannel 6 | import java.io.ByteArrayOutputStream 7 | import java.io.File 8 | import java.io.IOException 9 | 10 | class ThumbnailUtility(channelName: String) { 11 | private val utility = Utility(channelName) 12 | 13 | fun getThumbnail(path: String, quality: Int, position: Long, result: MethodChannel.Result) { 14 | val bmp = utility.getBitmap(path, position, result) 15 | 16 | val stream = ByteArrayOutputStream() 17 | bmp.compress(Bitmap.CompressFormat.JPEG, quality, stream) 18 | val byteArray = stream.toByteArray() 19 | bmp.recycle() 20 | result.success(byteArray.toList().toByteArray()) 21 | } 22 | 23 | fun getThumbnailWithFile(context: Context, path: String, quality: Int, position: Long, 24 | result: MethodChannel.Result) { 25 | val bmp = utility.getBitmap(path, position, result) 26 | 27 | val dir = context.getExternalFilesDir("flutter_video_compress") 28 | 29 | if (dir != null && !dir.exists()) dir.mkdirs() 30 | 31 | val file = File(dir, path.substring(path.lastIndexOf('/'), 32 | path.lastIndexOf('.')) + ".jpg") 33 | utility.deleteFile(file) 34 | 35 | val stream = ByteArrayOutputStream() 36 | bmp.compress(Bitmap.CompressFormat.JPEG, quality, stream) 37 | val byteArray = stream.toByteArray() 38 | 39 | try { 40 | file.createNewFile() 41 | file.writeBytes(byteArray) 42 | } catch (e: IOException) { 43 | e.printStackTrace() 44 | } 45 | 46 | bmp.recycle() 47 | 48 | result.success(file.absolutePath) 49 | } 50 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/com/example/flutter_video_compress/Utility.kt: -------------------------------------------------------------------------------- 1 | package com.example.flutter_video_compress 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.media.MediaMetadataRetriever 6 | import android.net.Uri 7 | import android.os.Build 8 | import io.flutter.plugin.common.MethodChannel 9 | import org.json.JSONObject 10 | import java.io.File 11 | 12 | class Utility(private val channelName: String) { 13 | 14 | fun isLandscapeImage(orientation: Int) = orientation != 90 && orientation != 270 15 | 16 | fun deleteFile(file: File) { 17 | if (file.exists()) { 18 | file.delete() 19 | } 20 | } 21 | 22 | fun timeStrToTimestamp(time: String): Long { 23 | val timeArr = time.split(":") 24 | val hour = Integer.parseInt(timeArr[0]) 25 | val min = Integer.parseInt(timeArr[1]) 26 | val secArr = timeArr[2].split(".") 27 | val sec = Integer.parseInt(secArr[0]) 28 | val mSec = Integer.parseInt(secArr[1]) 29 | 30 | val timeStamp = (hour * 3600 + min * 60 + sec) * 1000 + mSec 31 | return timeStamp.toLong() 32 | } 33 | 34 | fun getMediaInfoJson(context: Context, path: String): JSONObject { 35 | val file = File(path) 36 | val retriever = MediaMetadataRetriever() 37 | 38 | retriever.setDataSource(context, Uri.fromFile(file)) 39 | 40 | val durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) 41 | val title = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE) ?: "" 42 | val author = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_AUTHOR) ?: "" 43 | val widthStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) 44 | val heightStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT) 45 | val duration = java.lang.Long.parseLong(durationStr) 46 | var width = java.lang.Long.parseLong(widthStr) 47 | var height = java.lang.Long.parseLong(heightStr) 48 | val filesize = file.length() 49 | val orientation = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 50 | retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION) 51 | } else { 52 | null 53 | } 54 | val ori = orientation?.toIntOrNull() 55 | if (ori != null && isLandscapeImage(ori)) { 56 | val tmp = width 57 | width = height 58 | height = tmp 59 | } 60 | 61 | retriever.release() 62 | 63 | val json = JSONObject() 64 | 65 | json.put("path", path) 66 | json.put("title", title) 67 | json.put("author", author) 68 | json.put("width", width) 69 | json.put("height", height) 70 | json.put("duration", duration) 71 | json.put("filesize", filesize) 72 | if (ori != null) { 73 | json.put("orientation", ori) 74 | } 75 | 76 | return json 77 | } 78 | 79 | fun getBitmap(path: String, position: Long, result: MethodChannel.Result): Bitmap { 80 | var bitmap: Bitmap? = null 81 | val retriever = MediaMetadataRetriever() 82 | 83 | try { 84 | retriever.setDataSource(path) 85 | bitmap = retriever.getFrameAtTime(position, MediaMetadataRetriever.OPTION_CLOSEST_SYNC) 86 | } catch (ex: IllegalArgumentException) { 87 | result.error(channelName, "Assume this is a corrupt video file", null) 88 | } catch (ex: RuntimeException) { 89 | result.error(channelName, "Assume this is a corrupt video file", null) 90 | } finally { 91 | try { 92 | retriever.release() 93 | } catch (ex: RuntimeException) { 94 | result.error(channelName, "Ignore failures while cleaning up", null) 95 | } 96 | } 97 | 98 | if (bitmap == null) result.success(emptyArray()) 99 | 100 | val width = bitmap!!.width 101 | val height = bitmap.height 102 | val max = Math.max(width, height) 103 | if (max > 512) { 104 | val scale = 512f / max 105 | val w = Math.round(scale * width) 106 | val h = Math.round(scale * height) 107 | bitmap = Bitmap.createScaledBitmap(bitmap, w, h, true) 108 | } 109 | 110 | return bitmap!! 111 | } 112 | 113 | fun getFileNameWithGifExtension(path: String): String { 114 | val file = File(path) 115 | var fileName = "" 116 | val gifSuffix = "gif" 117 | val dotGifSuffix = ".$gifSuffix" 118 | 119 | if (file.exists()) { 120 | val name = file.name 121 | fileName = name.replaceAfterLast(".", gifSuffix) 122 | 123 | if (!fileName.endsWith(dotGifSuffix)) { 124 | fileName += dotGifSuffix 125 | } 126 | } 127 | return fileName 128 | } 129 | 130 | fun deleteAllCache(context: Context, result: MethodChannel.Result) { 131 | val dir = context.getExternalFilesDir("flutter_video_compress") 132 | result.success(dir?.deleteRecursively()) 133 | } 134 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/com/example/flutter_video_compress/VideoQuality.kt: -------------------------------------------------------------------------------- 1 | package com.example.flutter_video_compress 2 | 3 | enum class VideoQuality(val value : Int) { 4 | DefaultQuality(0), 5 | LowQuality(1), 6 | MediumQuality(2), 7 | HighestQuality(3); 8 | 9 | companion object { 10 | fun from(findValue: Int): VideoQuality = values().first { it.value == findValue } 11 | } 12 | 13 | fun getScaleString(): String = when (this) { 14 | DefaultQuality, LowQuality -> "192" 15 | MediumQuality -> "480" 16 | HighestQuality -> "1280" 17 | } 18 | 19 | fun notDefault(): Boolean = this != DefaultQuality 20 | 21 | fun isHighQuality(): Boolean = this == HighestQuality 22 | } -------------------------------------------------------------------------------- /doc/chinese.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | # flutter_video_compress 6 | 7 | 压缩视频生成新路径,选择保留源视频或删除它。从视频路径获取视频缩略图并提供视频信息。方便的处理压缩视频。考虑减少应用程序大小不在IOS中使用FFmpeg 8 | 9 |

10 | pub version 11 | license 12 | android min Sdk Version 13 | ios min target 14 |

15 | 16 |
17 | flutter compress video 18 |
19 | 20 | ## 语言 21 | [简体中文](https://github.com/TenkaiRuri/flutter_video_compress/blob/master/doc/chinese.md#flutter_video_compress) [日本語](https://github.com/TenkaiRuri/flutter_video_compress/blob/master/doc/japanese.md#flutter_video_compress) [English](https://github.com/TenkaiRuri/flutter_video_compress#flutter_video_compress) 22 | 23 | ## 用户指南 24 | 25 | **安装** 26 | 添加[flutter_video_compress](https://pub.dartlang.org/packages/flutter_video_compress)到你的pubspec.yaml文件中. 27 | ```yaml 28 | dependencies: 29 | flutter_video_compress: ^0.3.x 30 | ``` 31 | 32 | **创建一个实例** 33 | ```dart 34 | final _flutterVideoCompress = FlutterVideoCompress(); 35 | ``` 36 | 37 | **获取缩略图** 38 | ```dart 39 | final uint8list = await _flutterVideoCompress.getThumbnail( 40 | file.path, 41 | quality: 50, // 默认(100) 42 | position: -1 // 默认(-1) 43 | ); 44 | ``` 45 | 46 | **获取缩略图文件** 47 | ```dart 48 | final thumbnailFile = await _flutterVideoCompress.getThumbnailWithFile( 49 | file.path, 50 | quality: 50, // 默认(100) 51 | position: -1 // 默认(-1) 52 | ); 53 | ``` 54 | 55 | **转换视频为gif** 56 | ```dart 57 | final file = await _flutterVideoCompress.convertVideoToGif( 58 | videoFile.path, 59 | startTime: 0, // 默认(0) 60 | duration: 5, // 默认(-1) 61 | // endTime: -1 // 默认(-1) 62 | ); 63 | debugPrint(file.path); 64 | ``` 65 | 66 | **获取媒体信息** 67 | > 现在支持持视频 68 | 69 | ```dart 70 | final info = await _flutterVideoCompress.getMediaInfo(file.path); 71 | debugPrint(info.toJson().toString()); 72 | ``` 73 | 74 | **压缩视频** 75 | > 移动端平台及web平台视频格式是兼容的 76 | 77 | ```dart 78 | final info = await _flutterVideoCompress.compressVideo( 79 | file.path, 80 | quality: VideoQuality.DefaultQuality, // 默认(VideoQuality.DefaultQuality) 81 | deleteOrigin: false, // 默认(false) 82 | ); 83 | debugPrint(info.toJson().toString()); 84 | ``` 85 | 86 | **检查压缩状态** 87 | ```dart 88 | _flutterVideoCompress.isCompressing 89 | ``` 90 | 91 | **停止压缩** 92 | > Android 会打印 InterruptedException 错误, 但是不会影响使用 93 | 94 | ```dart 95 | await _flutterVideoCompress.cancelCompression() 96 | ``` 97 | 98 | **删除所有缓存文件** 99 | > 删除这个插件生成的所有文件,我想你应该知道你在做什么 100 | 101 | ```dart 102 | await _flutterVideoCompress.deleteAllCache() 103 | ``` 104 | 105 | **订阅转换流** 106 | ```dart 107 | class ... extends State { 108 | Subscription _subscription; 109 | 110 | @override 111 | void initState() { 112 | super.initState(); 113 | _subscription = 114 | _flutterVideoCompress.compressProgress$.subscribe((progress) { 115 | debugPrint('progress: $progress'); 116 | }); 117 | } 118 | 119 | @override 120 | void dispose() { 121 | super.dispose(); 122 | _subscription.unsubscribe(); 123 | } 124 | } 125 | ``` 126 | 127 | ## 方法 128 | |Functions|Parameters|Description|Returns| 129 | |--|--|--|--| 130 | |getThumbnail|String `path`[视频路径], int `quality`(1-100)[缩略图质量], int `position`[通过位置(时间)获取是缩略图]|从`path`获取缩略图|`Future`| 131 | |getThumbnailWithFile|String `path`[视频路径], int `quality`(1-100)[缩略图质量], int `position`[通过位置(时间)获取是缩略图]|从`path`获取缩略图文件`path`|`Future`| 132 | |convertVideoToGif|String `path`[视频路径], int `startTime`(from 0 start)[转换视频为gif的开始时间], int `endTime`[转换视频为gif的结束时间], int `duration`[从开始时间转换视频为gif的持续时间]|从`path`将视频转换为gif|`Future`| 133 | |getMediaInfo|String `path`[视频路径]|从`path`获取到视频的媒体信息|`Future`| 134 | |compressVideo|String `path`[视频路径], VideoQuality `quality`[压缩视频质量], bool `deleteOrigin`[是否删除源视频文件], int `startTime`[压缩视频的开始时间], int `duration`[压缩视频的持续时间], bool `includeAudio`[是否包含音频], int `frameRate`[压缩视频帧率]|从`path`压缩视频|`Future`| 135 | |cancelCompression|`none`|取消压缩|`Future`| 136 | |deleteAllCache|`none`|删除位于'flutter_video_compress'的所有文件|`Future`| 137 | 138 | ## 订阅流 139 | |Subscriptions|Description|Stream| 140 | |--|--|--| 141 | |compressProgress$|订阅压缩状态流|double `progress`| 142 | 143 | ## 注意 144 | 如果你的程序在使用这个插件后体积明显增大,可以使用下面的方法优化你的体积 145 | 146 | * 将`x86`相关文件排除 (`./assets`) 147 | 148 | * 这个库不使用`ffprobe`,只使用`ffmpeg`,但你的应用中仍然有`ffprobe`,你需要排除他 (`asssets/arm` or `assets/x86`) 149 | 150 | 将此配置添加到`build.gradle`文件中 151 | **不要在Android模拟器**上使用**`ignoreAssetsPattern'!X86'`,将会崩溃 152 | 153 | ```gradle 154 | android { 155 | ... 156 | 157 | // 减小应用程序大小的配置 158 | aaptOptions { 159 | ignoreAssetsPattern "!x86:!*ffprobe" 160 | } 161 | 162 | buildTypes { 163 | ... 164 | } 165 | ``` 166 | [详情](https://github.com/bravobit/FFmpeg-Android/wiki/Reduce-APK-File-Size#exclude-architecture) 167 | 168 | ## 问题列表 169 | 170 | 如果你的APP未开启`AndroidX`,你需要将下面代码添加到你的`android/build.gradle`文件里。 171 | ```groovy 172 | rootProject.allprojects { 173 | subprojects { 174 | project.configurations.all { 175 | resolutionStrategy.eachDependency { details -> 176 | if (details.requested.group == 'androidx.core' && !details.requested.name.contains('androidx')) { 177 | details.useVersion "1.0.1" 178 | } 179 | } 180 | } 181 | } 182 | } 183 | ``` 184 | 185 | 如果你的程序不支持swift,你需要在`ios/Podfile`文件加入下例代码。 186 | ```ruby 187 | target 'Runner' do 188 | use_frameworks! # <--- add this 189 | ... 190 | end 191 | ``` 192 | 193 | [详情](https://github.com/flutter/flutter/issues/16049#issuecomment-382629492) 194 | 195 | 如果你的程序从未使用过swift的插件,你或许会遇到下面的报错,这时候你需要添加下例代码`ios/Podfile`。 196 | > The 'Pods-Runner' target has transitive dependencies that include static binaries 197 | 198 | ```ruby 199 | pre_install do |installer| 200 | # workaround for https://github.com/CocoaPods/CocoaPods/issues/3289 201 | Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {} 202 | end 203 | ``` 204 | 205 | **如果上述方法不管用,你可以去报错的仓库提issue,理由是 不能支持swift(Can't support swift)** 206 | 207 | [详情](https://github.com/flutter/flutter/issues/16049#issue-309580132) 208 | 209 | 如果遇到`Regift`报错你可能需要将你的配置文件改成这样 210 | 211 | > - `Regift` does not specify a Swift version and none of the targets (`Runner`) integrating it have the `SWIFT_VERSION` attribute set. Please contact the author or set the `SWIFT_VERSION` attribute in at least one of the targets that integrate this pod. 212 | 213 | ```ruby 214 | pre_install do |installer| 215 | installer.analysis_result.specifications.each do |s| 216 | if s.name == 'Regift' 217 | s.swift_version = '4.0' 218 | end 219 | end 220 | end 221 | 222 | post_install do |installer| 223 | installer.pods_project.targets.each do |target| 224 | target.build_configurations.each do |config| 225 | config.build_settings['ENABLE_BITCODE'] = 'NO' 226 | end 227 | end 228 | end 229 | ``` 230 | 231 | ## 贡献指南 232 | 233 | 欢迎每一个贡献 234 | -------------------------------------------------------------------------------- /doc/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 38 | 39 | -------------------------------------------------------------------------------- /doc/images/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/doc/images/preview.gif -------------------------------------------------------------------------------- /doc/japanese.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | # flutter_video_compress 6 | 新しいパスで圧縮ビデオを生成します。ソースビデオを保持するか、パラメータで削除するかを選択します。ビデオのサムネイルを取得し、ビデオ情報を提供します。圧縮ビデオを扱うのが簡単です。アプリケーションサイズを縮小の考えIOSはFFmpegを使用しません。 7 | 8 |

9 | pub version 10 | license 11 | android min Sdk Version 12 | ios min target 13 |

14 | 15 |
16 | flutter compress video 17 |
18 | 19 | ## ランゲージ 20 | [日本語](https://github.com/TenkaiRuri/flutter_video_compress/blob/master/doc/japanese.md#flutter_video_compress) [English](https://github.com/TenkaiRuri/flutter_video_compress#flutter_video_compress) [简体中文](https://github.com/TenkaiRuri/flutter_video_compress/blob/master/doc/chinese.md#flutter_video_compress) 21 | 22 | ## 使用方法 23 | 24 | **インストール** 25 | pubspec.yamlファイルに依存ライブラリーとして[flutter_video_compress](https://pub.dartlang.org/packages/flutter_video_compress)を追加します。 26 | 27 | ```yaml 28 | dependencies: 29 | flutter_video_compress: ^0.3.x 30 | ``` 31 | 32 | **インスタンスを作成** 33 | ```dart 34 | final _flutterVideoCompress = FlutterVideoCompress(); 35 | ``` 36 | 37 | **ビデオファイルでサムネイルを取得する** 38 | ```dart 39 | final uint8list = await _flutterVideoCompress.getThumbnail( 40 | file.path, 41 | quality: 50, // default(100) 42 | position: -1 // default(-1) 43 | ); 44 | ``` 45 | 46 | **ビデオファイルでサムネイルファイルを取得する** 47 | ```dart 48 | final thumbnailFile = await _flutterVideoCompress.getThumbnailWithFile( 49 | file.path, 50 | quality: 50, // default(100) 51 | position: -1 // default(-1) 52 | ); 53 | ``` 54 | 55 | **ビデオをGIFに変換する** 56 | ```dart 57 | final file = await _flutterVideoCompress.convertVideoToGif( 58 | videoFile.path, 59 | startTime: 0, // default(0) 60 | duration: 5, // default(-1) 61 | // endTime: -1 // default(-1) 62 | ); 63 | debugPrint(file.path); 64 | ``` 65 | 66 | **メディア情報を入手する** 67 | > 現在はビデオのみをサポートしています 68 | 69 | ```dart 70 | final info = await _flutterVideoCompress.getMediaInfo(file.path); 71 | debugPrint(info.toJson().toString()); 72 | ``` 73 | 74 | **ビデオを圧縮する** 75 | > 圧縮後のアンドロイドおよびウェブとIOSの互換性ある 76 | 77 | ```dart 78 | final info = await _flutterVideoCompress.compressVideo( 79 | file.path, 80 | quality: VideoQuality.DefaultQuality, // default(VideoQuality.DefaultQuality) 81 | deleteOrigin: false, // default(false) 82 | ); 83 | debugPrint(info.toJson().toString()); 84 | ``` 85 | 86 | **圧縮の状態を確認する** 87 | ```dart 88 | _flutterVideoCompress.isCompressing 89 | ``` 90 | 91 | **圧縮を停止する** 92 | > アンドロイドはInterruptedExceptionを出力します、使用には影響しません 93 | 94 | ```dart 95 | await _flutterVideoCompress.cancelCompression() 96 | ``` 97 | 98 | **すべてのキャッシュファイルを削除する** 99 | > このプラグインによって生成されたすべてのファイルを削除します、何をしているか知っておくべきだと思います。 100 | 101 | ```dart 102 | await _flutterVideoCompress.deleteAllCache() 103 | ``` 104 | 105 | **変換の進行状況をサブスクリプションする** 106 | ```dart 107 | class ... extends State { 108 | Subscription _subscription; 109 | 110 | @override 111 | void initState() { 112 | super.initState(); 113 | _subscription = 114 | _flutterVideoCompress.compressProgress$.subscribe((progress) { 115 | print('progress: $progress'); 116 | }); 117 | } 118 | 119 | @override 120 | void dispose() { 121 | super.dispose(); 122 | _subscription.unsubscribe(); 123 | } 124 | } 125 | ``` 126 | 127 | ## メソッド 128 | |Functions|Parameters|Description|Returns| 129 | |--|--|--|--| 130 | |getThumbnail|String `path`[ビデオのパス], int `quality`(1-100)[サムネイルの品質], int `position`[ビデオ位置からサムネイルを取得します]|動画の`path`からサムネイルを取得します|`Future`| 131 | |getThumbnailWithFile|String `path`[ビデオのパス], int `quality`(1-100)[サムネイルの品質], int `position`[ビデオ位置からサムネイルを取得します]|動画の`path`からサムネイルファイルを取得します|`Future`| 132 | |convertVideoToGif|String `path`[ビデオのパス], int `startTime`(from 0 start)[ビデオをgifに変換するの始まり時間], int `endTime`[動画をgifに変換するの終了時間], int `duration`[始まり時間から動画の期間]|動画を`path`からgifに変換します|`Future`| 133 | |getMediaInfo|String `path`[ビデオのパス]|ビデオの`path`から情報を取得します|`Future`| 134 | |compressVideo|String `path`[ビデオのパス], VideoQuality `quality`[圧縮ビデオ品質], bool `deleteOrigin`[元の動画を削除する], int `startTime`[圧縮ビデオの開始時間], int `duration`[始まり時間から動画の期間], bool `includeAudio`[圧縮ビデオに音声を含める], int `frameRate`[圧縮ビデオフレームレート]|`path`からビデオを圧縮する|`Future`| 135 | |cancelCompression|`none`|圧縮をキャンセル|`Future`| 136 | |deleteAllCache|`none`|'flutter_video_compress'によって生成されたすべてのファイルを削除すると、 'flutter_video_compress'にあるすべてのファイルが削除されます|`Future`| 137 | 138 | ## サブスクリプション 139 | |Subscriptions|Description|Stream| 140 | |--|--|--| 141 | |compressProgress$|変換の進行状況をサブスクリプションする|double `progress`| 142 | 143 | ## お知らせ 144 | プラグインをインポートした後にアプリケーションのサイズが大幅に増加していることがわかった場合は、以下の処理方法が考えられます。 145 | 146 | * `x86`関連ファイルを排除する (`./assets`) 147 | 148 | このライブラリは `ffprobe`を使わず、` ffmpeg`だけを使いますが、あなたのアプリケーションにはまだ `ffprobe`があるので、あなたはそれを除外する必要があります。 149 | 150 | * このライブラリーは `ffprobe`を使用なし、`ffmpeg`だけを使いします、だけどアプリケーションにはまだ `ffprobe`があるので、それを除外する必要があります (`asssets/arm` or `assets/x86`) 151 | 152 | この設定を`build.gradle`ファイルに追加してください 153 | アンドロイドエミュレータで `ignoreAssetsPattern "!x86"`を**使わないでください**、クラッシュします 154 | 155 | ```gradle 156 | android { 157 | ... 158 | // Reduce your application size with this configuration 159 | aaptOptions { 160 | ignoreAssetsPattern "!x86:!*ffprobe" 161 | } 162 | 163 | buildTypes { 164 | ... 165 | } 166 | ``` 167 | 168 | [詳細説明](https://github.com/bravobit/FFmpeg-Android/wiki/Reduce-APK-File-Size#exclude-architecture) 169 | 170 | ## 問題リスト 171 | 172 | アプリケーションが`AndroidX`に対応していない場合は、`android¥build.gradle`ファイルの最後の行に以下のコードを追加する必要があります。 173 | ```gradle 174 | rootProject.allprojects { 175 | subprojects { 176 | project.configurations.all { 177 | resolutionStrategy.eachDependency { details -> 178 | if (details.requested.group == 'androidx.core' && !details.requested.name.contains('androidx')) { 179 | details.useVersion "1.0.1" 180 | } 181 | } 182 | } 183 | } 184 | } 185 | ``` 186 | 187 | アプリケーションがswiftをサポートしていない場合は、`ios¥Podfile`に以下のコードを追加する必要があります 188 | ```ruby 189 | target 'Runner' do 190 | use_frameworks! # <--- add this 191 | ... 192 | end 193 | ``` 194 | 195 | [詳細説明](https://github.com/flutter/flutter/issues/16049#issuecomment-382629492) 196 | 197 | 198 | アプリケーションがswiftをサポートしていない場合は、`ios¥Podfile`に以下のコードを追加する必要があります 199 | > The 'Pods-Runner' target has transitive dependencies that include static binaries 200 | 201 | ```ruby 202 | pre_install do |installer| 203 | # workaround for https://github.com/CocoaPods/CocoaPods/issues/3289 204 | Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {} 205 | end 206 | ``` 207 | 208 | **上記の方法が機能しない場合は、そのエラーリポジトリのバグを報告する。理由は「このリポジトリはswiftをサポートしません」、ありがとうございます** 209 | 210 | [詳細説明](https://github.com/flutter/flutter/issues/16049#issue-309580132) 211 | 212 | `Regift`のエラーを表示するなら 213 | 214 | > - `Regift` does not specify a Swift version and none of the targets (`Runner`) integrating it have the `SWIFT_VERSION` attribute set. Please contact the author or set the `SWIFT_VERSION` attribute in at least one of the targets that integrate this pod. 215 | 216 | ```ruby 217 | pre_install do |installer| 218 | installer.analysis_result.specifications.each do |s| 219 | if s.name == 'Regift' 220 | s.swift_version = '4.0' 221 | end 222 | end 223 | end 224 | 225 | post_install do |installer| 226 | installer.pods_project.targets.each do |target| 227 | target.build_configurations.each do |config| 228 | config.build_settings['ENABLE_BITCODE'] = 'NO' 229 | end 230 | end 231 | end 232 | ``` 233 | 234 | ## 貢献ガイドライン 235 | 236 | 貢献はいつでも歓迎です! 237 | 238 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # Visual Studio Code related 19 | .vscode/ 20 | 21 | # Flutter/Dart/Pub related 22 | **/doc/api/ 23 | .dart_tool/ 24 | .flutter-plugins 25 | .packages 26 | .pub-cache/ 27 | .pub/ 28 | /build/ 29 | 30 | # Android related 31 | **/android/**/gradle-wrapper.jar 32 | **/android/.gradle 33 | **/android/captures/ 34 | **/android/gradlew 35 | **/android/gradlew.bat 36 | **/android/local.properties 37 | **/android/**/GeneratedPluginRegistrant.java 38 | 39 | # iOS/XCode related 40 | **/ios/**/*.mode1v3 41 | **/ios/**/*.mode2v3 42 | **/ios/**/*.moved-aside 43 | **/ios/**/*.pbxuser 44 | **/ios/**/*.perspectivev3 45 | **/ios/**/*sync/ 46 | **/ios/**/.sconsign.dblite 47 | **/ios/**/.tags* 48 | **/ios/**/.vagrant/ 49 | **/ios/**/DerivedData/ 50 | **/ios/**/Icon? 51 | **/ios/**/Pods/ 52 | **/ios/**/.symlinks/ 53 | **/ios/**/profile 54 | **/ios/**/xcuserdata 55 | **/ios/.generated/ 56 | **/ios/Flutter/App.framework 57 | **/ios/Flutter/Flutter.framework 58 | **/ios/Flutter/Generated.xcconfig 59 | **/ios/Flutter/app.flx 60 | **/ios/Flutter/app.zip 61 | **/ios/Flutter/flutter_assets/ 62 | **/ios/ServiceDefinitions.json 63 | **/ios/Runner/GeneratedPluginRegistrant.* 64 | 65 | # Exceptions to above rules. 66 | !**/ios/**/default.mode1v3 67 | !**/ios/**/default.mode2v3 68 | !**/ios/**/default.pbxuser 69 | !**/ios/**/default.perspectivev3 70 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 71 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # flutter_video_compress_example 2 | 3 | ```dart 4 | import 'dart:io'; 5 | import 'dart:async'; 6 | import 'dart:typed_data'; 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_video_compress/flutter_video_compress.dart'; 10 | import 'package:image_picker/image_picker.dart' show ImagePicker, ImageSource; 11 | 12 | void main() => runApp(MyApp()); 13 | 14 | class MyApp extends StatefulWidget { 15 | @override 16 | _MyAppState createState() => _MyAppState(); 17 | } 18 | 19 | class _MyAppState extends State { 20 | final _flutterVideoCompress = FlutterVideoCompress(); 21 | Uint8List _image; 22 | File _imageFile; 23 | Subscription _subscription; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | _subscription = 29 | _flutterVideoCompress.compressProgress$.subscribe((progress) { 30 | debugPrint('progress: $progress'); 31 | }); 32 | } 33 | 34 | @override 35 | void dispose() { 36 | super.dispose(); 37 | _subscription.unsubscribe(); 38 | } 39 | 40 | Future _videoPicker() async { 41 | if (mounted) { 42 | final file = await ImagePicker.pickVideo(source: ImageSource.camera); 43 | if (file?.path != null) { 44 | final thumbnail = await _flutterVideoCompress.getThumbnail( 45 | file.path, 46 | quality: 50, 47 | position: -1, 48 | ); 49 | 50 | setState(() { 51 | _image = thumbnail; 52 | }); 53 | 54 | final resultFile = await _flutterVideoCompress.getThumbnailWithFile( 55 | file.path, 56 | quality: 50, 57 | position: -1, 58 | ); 59 | debugPrint(resultFile.path); 60 | 61 | assert(resultFile.existsSync()); 62 | 63 | debugPrint('file Exists: ${resultFile.existsSync()}'); 64 | 65 | final MediaInfo info = await _flutterVideoCompress.compressVideo( 66 | file.path, 67 | deleteOrigin: true, 68 | quality: VideoQuality.LowQuality, 69 | ); 70 | debugPrint(info.toJson().toString()); 71 | } 72 | } 73 | } 74 | 75 | Future _cancelCompression() async { 76 | await _flutterVideoCompress.cancelCompression(); 77 | } 78 | 79 | Future _getMediaInfo() async { 80 | if (mounted) { 81 | final file = await ImagePicker.pickVideo(source: ImageSource.gallery); 82 | if (file?.path != null) { 83 | final info = await _flutterVideoCompress.getMediaInfo(file.path); 84 | debugPrint(info.toJson().toString()); 85 | } 86 | } 87 | } 88 | 89 | Future _convertVideoToGif() async { 90 | if (mounted) { 91 | final file = await ImagePicker.pickVideo(source: ImageSource.gallery); 92 | if (file?.path != null) { 93 | final info = await _flutterVideoCompress.convertVideoToGif( 94 | file.path, 95 | startTime: 0, 96 | duration: 5, 97 | ); 98 | 99 | debugPrint(info.path); 100 | setState(() { 101 | _imageFile = info; 102 | }); 103 | } 104 | } 105 | } 106 | 107 | List _builColumnChildren() { 108 | // dart 2.3 before 109 | final _list = [ 110 | FlatButton(child: Text('take video'), onPressed: _videoPicker), 111 | FlatButton(child: Text('stop compress'), onPressed: _cancelCompression), 112 | FlatButton(child: Text('get media info'), onPressed: _getMediaInfo), 113 | FlatButton( 114 | child: Text('convert video to gif'), 115 | onPressed: _convertVideoToGif, 116 | ), 117 | ]; 118 | if (_imageFile != null) { 119 | _list.add(Flexible(child: Image.file(_imageFile))); 120 | } else if (_image != null) { 121 | _list.add(Flexible(child: Image.memory(_image))); 122 | } 123 | return _list; 124 | 125 | // dart 2.3 126 | // final _list = [ 127 | // FlatButton(child: Text('take video'), onPressed: _videoPicker), 128 | // FlatButton(child: Text('stop compress'), onPressed: _cancelCompression), 129 | // FlatButton(child: Text('get media info'), onPressed: _getMediaInfo), 130 | // FlatButton( 131 | // child: Text('convert video to gif'), 132 | // onPressed: _convertVideoToGif, 133 | // ), 134 | // if (_imageFile != null) 135 | // Flexible(child: Image.file(_imageFile)) 136 | // else 137 | // if (_image != null) Flexible(child: Image.memory(_image)) 138 | // ]; 139 | // return _list; 140 | } 141 | 142 | @override 143 | Widget build(BuildContext context) { 144 | return MaterialApp( 145 | home: Scaffold( 146 | appBar: AppBar(title: const Text('Plugin example app')), 147 | body: Column( 148 | crossAxisAlignment: CrossAxisAlignment.stretch, 149 | children: _builColumnChildren(), 150 | ), 151 | ), 152 | ); 153 | } 154 | } 155 | ``` -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.example.flutter_video_compress_example" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'androidx.test:runner:1.1.1' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 67 | } 68 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 14 | 21 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/flutter_video_compress_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.flutter_video_compress_example 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | GeneratedPluginRegistrant.registerWith(this) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.31' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.4.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | android.enableJetifier=true 2 | android.useAndroidX=true 3 | org.gradle.jvmargs=-Xmx1536M 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri May 17 10:22:14 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '8.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | pod 'Regift' 8 | 9 | project 'Runner', { 10 | 'Debug' => :debug, 11 | 'Profile' => :release, 12 | 'Release' => :release, 13 | } 14 | 15 | def parse_KV_file(file, separator='=') 16 | file_abs_path = File.expand_path(file) 17 | if !File.exists? file_abs_path 18 | return []; 19 | end 20 | pods_ary = [] 21 | skip_line_start_symbols = ["#", "/"] 22 | File.foreach(file_abs_path) { |line| 23 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 24 | plugin = line.split(pattern=separator) 25 | if plugin.length == 2 26 | podname = plugin[0].strip() 27 | path = plugin[1].strip() 28 | podpath = File.expand_path("#{path}", file_abs_path) 29 | pods_ary.push({:name => podname, :path => podpath}); 30 | else 31 | puts "Invalid plugin specification: #{line}" 32 | end 33 | } 34 | return pods_ary 35 | end 36 | 37 | target 'Runner' do 38 | use_frameworks! 39 | 40 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 41 | # referring to absolute paths on developers' machines. 42 | system('rm -rf .symlinks') 43 | system('mkdir -p .symlinks/plugins') 44 | 45 | # Flutter Pods 46 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 47 | if generated_xcode_build_settings.empty? 48 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 49 | end 50 | generated_xcode_build_settings.map { |p| 51 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 52 | symlink = File.join('.symlinks', 'flutter') 53 | File.symlink(File.dirname(p[:path]), symlink) 54 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 55 | end 56 | } 57 | 58 | # Plugin Pods 59 | plugin_pods = parse_KV_file('../.flutter-plugins') 60 | plugin_pods.map { |p| 61 | symlink = File.join('.symlinks', 'plugins', p[:name]) 62 | File.symlink(p[:path], symlink) 63 | pod p[:name], :path => File.join(symlink, 'ios') 64 | } 65 | end 66 | 67 | pre_install do |installer| 68 | installer.analysis_result.specifications.each do |s| 69 | if s.name == 'Regift' 70 | s.swift_version = '4.0' 71 | end 72 | end 73 | end 74 | 75 | post_install do |installer| 76 | installer.pods_project.targets.each do |target| 77 | target.build_configurations.each do |config| 78 | config.build_settings['ENABLE_BITCODE'] = 'NO' 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_video_compress (0.3.0): 4 | - Flutter 5 | - Regift 6 | - image_picker (0.0.1): 7 | - Flutter 8 | - Regift (1.5.0) 9 | - video_player (0.0.1): 10 | - Flutter 11 | 12 | DEPENDENCIES: 13 | - Flutter (from `.symlinks/flutter/ios-release`) 14 | - flutter_video_compress (from `.symlinks/plugins/flutter_video_compress/ios`) 15 | - image_picker (from `.symlinks/plugins/image_picker/ios`) 16 | - Regift 17 | - video_player (from `.symlinks/plugins/video_player/ios`) 18 | 19 | SPEC REPOS: 20 | https://github.com/cocoapods/specs.git: 21 | - Regift 22 | 23 | EXTERNAL SOURCES: 24 | Flutter: 25 | :path: ".symlinks/flutter/ios-release" 26 | flutter_video_compress: 27 | :path: ".symlinks/plugins/flutter_video_compress/ios" 28 | image_picker: 29 | :path: ".symlinks/plugins/image_picker/ios" 30 | video_player: 31 | :path: ".symlinks/plugins/video_player/ios" 32 | 33 | SPEC CHECKSUMS: 34 | Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a 35 | flutter_video_compress: 3e6c882fbf1be6d16e615c2aa2ac74e9d460749a 36 | image_picker: 16e5fec1fbc87fd3b297c53e4048521eaf17cd06 37 | Regift: d097afd401021f4f42b102ee125ceeb125a38caf 38 | video_player: 3964090a33353060ed7f58aa6427c7b4b208ec21 39 | 40 | PODFILE CHECKSUM: 596239def9fc038f4ca3b58962ce3ad0da67feb5 41 | 42 | COCOAPODS: 1.7.2 43 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0910DC4AAB319CB550545C57 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE7CD6F4DADA18DACEE24635 /* Pods_Runner.framework */; }; 11 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 14 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 15 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 16 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 17 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 18 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 19 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 20 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXCopyFilesBuildPhase section */ 24 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 25 | isa = PBXCopyFilesBuildPhase; 26 | buildActionMask = 2147483647; 27 | dstPath = ""; 28 | dstSubfolderSpec = 10; 29 | files = ( 30 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 31 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 32 | ); 33 | name = "Embed Frameworks"; 34 | runOnlyForDeploymentPostprocessing = 0; 35 | }; 36 | /* End PBXCopyFilesBuildPhase section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 40 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 41 | 2F308078F8D8AEC04C19831E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 42 | 39702881BB56947DF75349A3 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 43 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 44 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 45 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 46 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 47 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 48 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 49 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 50 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 51 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 53 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 54 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 55 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | DD54B8BDABAEF5DA187193CF /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 57 | EE7CD6F4DADA18DACEE24635 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 66 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 67 | 0910DC4AAB319CB550545C57 /* Pods_Runner.framework in Frameworks */, 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | /* End PBXFrameworksBuildPhase section */ 72 | 73 | /* Begin PBXGroup section */ 74 | 149AEF9825D5C8DB20AEF947 /* Pods */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 2F308078F8D8AEC04C19831E /* Pods-Runner.debug.xcconfig */, 78 | DD54B8BDABAEF5DA187193CF /* Pods-Runner.release.xcconfig */, 79 | 39702881BB56947DF75349A3 /* Pods-Runner.profile.xcconfig */, 80 | ); 81 | path = Pods; 82 | sourceTree = ""; 83 | }; 84 | 317CB9D2ED33047C6C9A54A1 /* Frameworks */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | EE7CD6F4DADA18DACEE24635 /* Pods_Runner.framework */, 88 | ); 89 | name = Frameworks; 90 | sourceTree = ""; 91 | }; 92 | 9740EEB11CF90186004384FC /* Flutter */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 3B80C3931E831B6300D905FE /* App.framework */, 96 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 97 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 98 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 99 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 100 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 101 | ); 102 | name = Flutter; 103 | sourceTree = ""; 104 | }; 105 | 97C146E51CF9000F007C117D = { 106 | isa = PBXGroup; 107 | children = ( 108 | 9740EEB11CF90186004384FC /* Flutter */, 109 | 97C146F01CF9000F007C117D /* Runner */, 110 | 97C146EF1CF9000F007C117D /* Products */, 111 | 149AEF9825D5C8DB20AEF947 /* Pods */, 112 | 317CB9D2ED33047C6C9A54A1 /* Frameworks */, 113 | ); 114 | sourceTree = ""; 115 | }; 116 | 97C146EF1CF9000F007C117D /* Products */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 97C146EE1CF9000F007C117D /* Runner.app */, 120 | ); 121 | name = Products; 122 | sourceTree = ""; 123 | }; 124 | 97C146F01CF9000F007C117D /* Runner */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 128 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 129 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 130 | 97C147021CF9000F007C117D /* Info.plist */, 131 | 97C146F11CF9000F007C117D /* Supporting Files */, 132 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 133 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 134 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 135 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 136 | ); 137 | path = Runner; 138 | sourceTree = ""; 139 | }; 140 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | ); 144 | name = "Supporting Files"; 145 | sourceTree = ""; 146 | }; 147 | /* End PBXGroup section */ 148 | 149 | /* Begin PBXNativeTarget section */ 150 | 97C146ED1CF9000F007C117D /* Runner */ = { 151 | isa = PBXNativeTarget; 152 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 153 | buildPhases = ( 154 | 658D95BB1A5175F5E0460993 /* [CP] Check Pods Manifest.lock */, 155 | 9740EEB61CF901F6004384FC /* Run Script */, 156 | 97C146EA1CF9000F007C117D /* Sources */, 157 | 97C146EB1CF9000F007C117D /* Frameworks */, 158 | 97C146EC1CF9000F007C117D /* Resources */, 159 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 160 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 161 | 600D16CF5758BF2E958F6D05 /* [CP] Embed Pods Frameworks */, 162 | ); 163 | buildRules = ( 164 | ); 165 | dependencies = ( 166 | ); 167 | name = Runner; 168 | productName = Runner; 169 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 170 | productType = "com.apple.product-type.application"; 171 | }; 172 | /* End PBXNativeTarget section */ 173 | 174 | /* Begin PBXProject section */ 175 | 97C146E61CF9000F007C117D /* Project object */ = { 176 | isa = PBXProject; 177 | attributes = { 178 | LastUpgradeCheck = 1020; 179 | ORGANIZATIONNAME = "The Chromium Authors"; 180 | TargetAttributes = { 181 | 97C146ED1CF9000F007C117D = { 182 | CreatedOnToolsVersion = 7.3.1; 183 | LastSwiftMigration = 1020; 184 | }; 185 | }; 186 | }; 187 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 188 | compatibilityVersion = "Xcode 3.2"; 189 | developmentRegion = en; 190 | hasScannedForEncodings = 0; 191 | knownRegions = ( 192 | en, 193 | Base, 194 | ); 195 | mainGroup = 97C146E51CF9000F007C117D; 196 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 197 | projectDirPath = ""; 198 | projectRoot = ""; 199 | targets = ( 200 | 97C146ED1CF9000F007C117D /* Runner */, 201 | ); 202 | }; 203 | /* End PBXProject section */ 204 | 205 | /* Begin PBXResourcesBuildPhase section */ 206 | 97C146EC1CF9000F007C117D /* Resources */ = { 207 | isa = PBXResourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 211 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 212 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 213 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 214 | ); 215 | runOnlyForDeploymentPostprocessing = 0; 216 | }; 217 | /* End PBXResourcesBuildPhase section */ 218 | 219 | /* Begin PBXShellScriptBuildPhase section */ 220 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 221 | isa = PBXShellScriptBuildPhase; 222 | buildActionMask = 2147483647; 223 | files = ( 224 | ); 225 | inputPaths = ( 226 | ); 227 | name = "Thin Binary"; 228 | outputPaths = ( 229 | ); 230 | runOnlyForDeploymentPostprocessing = 0; 231 | shellPath = /bin/sh; 232 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 233 | }; 234 | 600D16CF5758BF2E958F6D05 /* [CP] Embed Pods Frameworks */ = { 235 | isa = PBXShellScriptBuildPhase; 236 | buildActionMask = 2147483647; 237 | files = ( 238 | ); 239 | inputPaths = ( 240 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", 241 | "${PODS_ROOT}/../.symlinks/flutter/ios-release/Flutter.framework", 242 | "${BUILT_PRODUCTS_DIR}/Regift/Regift.framework", 243 | "${BUILT_PRODUCTS_DIR}/flutter_video_compress/flutter_video_compress.framework", 244 | "${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework", 245 | "${BUILT_PRODUCTS_DIR}/video_player/video_player.framework", 246 | ); 247 | name = "[CP] Embed Pods Frameworks"; 248 | outputPaths = ( 249 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", 250 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Regift.framework", 251 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_video_compress.framework", 252 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework", 253 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_player.framework", 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | shellPath = /bin/sh; 257 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 258 | showEnvVarsInLog = 0; 259 | }; 260 | 658D95BB1A5175F5E0460993 /* [CP] Check Pods Manifest.lock */ = { 261 | isa = PBXShellScriptBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | ); 265 | inputPaths = ( 266 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 267 | "${PODS_ROOT}/Manifest.lock", 268 | ); 269 | name = "[CP] Check Pods Manifest.lock"; 270 | outputPaths = ( 271 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 272 | ); 273 | runOnlyForDeploymentPostprocessing = 0; 274 | shellPath = /bin/sh; 275 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 276 | showEnvVarsInLog = 0; 277 | }; 278 | 9740EEB61CF901F6004384FC /* Run Script */ = { 279 | isa = PBXShellScriptBuildPhase; 280 | buildActionMask = 2147483647; 281 | files = ( 282 | ); 283 | inputPaths = ( 284 | ); 285 | name = "Run Script"; 286 | outputPaths = ( 287 | ); 288 | runOnlyForDeploymentPostprocessing = 0; 289 | shellPath = /bin/sh; 290 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 291 | }; 292 | /* End PBXShellScriptBuildPhase section */ 293 | 294 | /* Begin PBXSourcesBuildPhase section */ 295 | 97C146EA1CF9000F007C117D /* Sources */ = { 296 | isa = PBXSourcesBuildPhase; 297 | buildActionMask = 2147483647; 298 | files = ( 299 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 300 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 301 | ); 302 | runOnlyForDeploymentPostprocessing = 0; 303 | }; 304 | /* End PBXSourcesBuildPhase section */ 305 | 306 | /* Begin PBXVariantGroup section */ 307 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 308 | isa = PBXVariantGroup; 309 | children = ( 310 | 97C146FB1CF9000F007C117D /* Base */, 311 | ); 312 | name = Main.storyboard; 313 | sourceTree = ""; 314 | }; 315 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 316 | isa = PBXVariantGroup; 317 | children = ( 318 | 97C147001CF9000F007C117D /* Base */, 319 | ); 320 | name = LaunchScreen.storyboard; 321 | sourceTree = ""; 322 | }; 323 | /* End PBXVariantGroup section */ 324 | 325 | /* Begin XCBuildConfiguration section */ 326 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 327 | isa = XCBuildConfiguration; 328 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 329 | buildSettings = { 330 | ALWAYS_SEARCH_USER_PATHS = NO; 331 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 332 | CLANG_ANALYZER_NONNULL = YES; 333 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 334 | CLANG_CXX_LIBRARY = "libc++"; 335 | CLANG_ENABLE_MODULES = YES; 336 | CLANG_ENABLE_OBJC_ARC = YES; 337 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 338 | CLANG_WARN_BOOL_CONVERSION = YES; 339 | CLANG_WARN_COMMA = YES; 340 | CLANG_WARN_CONSTANT_CONVERSION = YES; 341 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 342 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 343 | CLANG_WARN_EMPTY_BODY = YES; 344 | CLANG_WARN_ENUM_CONVERSION = YES; 345 | CLANG_WARN_INFINITE_RECURSION = YES; 346 | CLANG_WARN_INT_CONVERSION = YES; 347 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 348 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 349 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 350 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 351 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 352 | CLANG_WARN_STRICT_PROTOTYPES = YES; 353 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 354 | CLANG_WARN_UNREACHABLE_CODE = YES; 355 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 356 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 357 | COPY_PHASE_STRIP = NO; 358 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 359 | ENABLE_NS_ASSERTIONS = NO; 360 | ENABLE_STRICT_OBJC_MSGSEND = YES; 361 | GCC_C_LANGUAGE_STANDARD = gnu99; 362 | GCC_NO_COMMON_BLOCKS = YES; 363 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 364 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 365 | GCC_WARN_UNDECLARED_SELECTOR = YES; 366 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 367 | GCC_WARN_UNUSED_FUNCTION = YES; 368 | GCC_WARN_UNUSED_VARIABLE = YES; 369 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 370 | MTL_ENABLE_DEBUG_INFO = NO; 371 | SDKROOT = iphoneos; 372 | SWIFT_VERSION = 5.0; 373 | TARGETED_DEVICE_FAMILY = "1,2"; 374 | VALIDATE_PRODUCT = YES; 375 | }; 376 | name = Profile; 377 | }; 378 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 379 | isa = XCBuildConfiguration; 380 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 381 | buildSettings = { 382 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 383 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 384 | ENABLE_BITCODE = NO; 385 | FRAMEWORK_SEARCH_PATHS = ( 386 | "$(inherited)", 387 | "$(PROJECT_DIR)/Flutter", 388 | ); 389 | INFOPLIST_FILE = Runner/Info.plist; 390 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 391 | LIBRARY_SEARCH_PATHS = ( 392 | "$(inherited)", 393 | "$(PROJECT_DIR)/Flutter", 394 | ); 395 | PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterVideoCompressExample; 396 | PRODUCT_NAME = "$(TARGET_NAME)"; 397 | SWIFT_VERSION = 5.0; 398 | VERSIONING_SYSTEM = "apple-generic"; 399 | }; 400 | name = Profile; 401 | }; 402 | 97C147031CF9000F007C117D /* Debug */ = { 403 | isa = XCBuildConfiguration; 404 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 405 | buildSettings = { 406 | ALWAYS_SEARCH_USER_PATHS = NO; 407 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 408 | CLANG_ANALYZER_NONNULL = YES; 409 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 410 | CLANG_CXX_LIBRARY = "libc++"; 411 | CLANG_ENABLE_MODULES = YES; 412 | CLANG_ENABLE_OBJC_ARC = YES; 413 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 414 | CLANG_WARN_BOOL_CONVERSION = YES; 415 | CLANG_WARN_COMMA = YES; 416 | CLANG_WARN_CONSTANT_CONVERSION = YES; 417 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 418 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 419 | CLANG_WARN_EMPTY_BODY = YES; 420 | CLANG_WARN_ENUM_CONVERSION = YES; 421 | CLANG_WARN_INFINITE_RECURSION = YES; 422 | CLANG_WARN_INT_CONVERSION = YES; 423 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 424 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 425 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 426 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 427 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 428 | CLANG_WARN_STRICT_PROTOTYPES = YES; 429 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 430 | CLANG_WARN_UNREACHABLE_CODE = YES; 431 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 432 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 433 | COPY_PHASE_STRIP = NO; 434 | DEBUG_INFORMATION_FORMAT = dwarf; 435 | ENABLE_STRICT_OBJC_MSGSEND = YES; 436 | ENABLE_TESTABILITY = YES; 437 | GCC_C_LANGUAGE_STANDARD = gnu99; 438 | GCC_DYNAMIC_NO_PIC = NO; 439 | GCC_NO_COMMON_BLOCKS = YES; 440 | GCC_OPTIMIZATION_LEVEL = 0; 441 | GCC_PREPROCESSOR_DEFINITIONS = ( 442 | "DEBUG=1", 443 | "$(inherited)", 444 | ); 445 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 446 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 447 | GCC_WARN_UNDECLARED_SELECTOR = YES; 448 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 449 | GCC_WARN_UNUSED_FUNCTION = YES; 450 | GCC_WARN_UNUSED_VARIABLE = YES; 451 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 452 | MTL_ENABLE_DEBUG_INFO = YES; 453 | ONLY_ACTIVE_ARCH = YES; 454 | SDKROOT = iphoneos; 455 | SWIFT_VERSION = 5.0; 456 | TARGETED_DEVICE_FAMILY = "1,2"; 457 | }; 458 | name = Debug; 459 | }; 460 | 97C147041CF9000F007C117D /* Release */ = { 461 | isa = XCBuildConfiguration; 462 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 463 | buildSettings = { 464 | ALWAYS_SEARCH_USER_PATHS = NO; 465 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 466 | CLANG_ANALYZER_NONNULL = YES; 467 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 468 | CLANG_CXX_LIBRARY = "libc++"; 469 | CLANG_ENABLE_MODULES = YES; 470 | CLANG_ENABLE_OBJC_ARC = YES; 471 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 472 | CLANG_WARN_BOOL_CONVERSION = YES; 473 | CLANG_WARN_COMMA = YES; 474 | CLANG_WARN_CONSTANT_CONVERSION = YES; 475 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 476 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 477 | CLANG_WARN_EMPTY_BODY = YES; 478 | CLANG_WARN_ENUM_CONVERSION = YES; 479 | CLANG_WARN_INFINITE_RECURSION = YES; 480 | CLANG_WARN_INT_CONVERSION = YES; 481 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 482 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 483 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 484 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 485 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 486 | CLANG_WARN_STRICT_PROTOTYPES = YES; 487 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 488 | CLANG_WARN_UNREACHABLE_CODE = YES; 489 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 490 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 491 | COPY_PHASE_STRIP = NO; 492 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 493 | ENABLE_NS_ASSERTIONS = NO; 494 | ENABLE_STRICT_OBJC_MSGSEND = YES; 495 | GCC_C_LANGUAGE_STANDARD = gnu99; 496 | GCC_NO_COMMON_BLOCKS = YES; 497 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 498 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 499 | GCC_WARN_UNDECLARED_SELECTOR = YES; 500 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 501 | GCC_WARN_UNUSED_FUNCTION = YES; 502 | GCC_WARN_UNUSED_VARIABLE = YES; 503 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 504 | MTL_ENABLE_DEBUG_INFO = NO; 505 | SDKROOT = iphoneos; 506 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 507 | SWIFT_VERSION = 5.0; 508 | TARGETED_DEVICE_FAMILY = "1,2"; 509 | VALIDATE_PRODUCT = YES; 510 | }; 511 | name = Release; 512 | }; 513 | 97C147061CF9000F007C117D /* Debug */ = { 514 | isa = XCBuildConfiguration; 515 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 516 | buildSettings = { 517 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 518 | CLANG_ENABLE_MODULES = YES; 519 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 520 | DEVELOPMENT_TEAM = BZV32XF67X; 521 | ENABLE_BITCODE = NO; 522 | FRAMEWORK_SEARCH_PATHS = ( 523 | "$(inherited)", 524 | "$(PROJECT_DIR)/Flutter", 525 | ); 526 | INFOPLIST_FILE = Runner/Info.plist; 527 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 528 | LIBRARY_SEARCH_PATHS = ( 529 | "$(inherited)", 530 | "$(PROJECT_DIR)/Flutter", 531 | ); 532 | PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterVideoCompressExample; 533 | PRODUCT_NAME = "$(TARGET_NAME)"; 534 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 535 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 536 | SWIFT_VERSION = 5.0; 537 | VERSIONING_SYSTEM = "apple-generic"; 538 | }; 539 | name = Debug; 540 | }; 541 | 97C147071CF9000F007C117D /* Release */ = { 542 | isa = XCBuildConfiguration; 543 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 544 | buildSettings = { 545 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 546 | CLANG_ENABLE_MODULES = YES; 547 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 548 | DEVELOPMENT_TEAM = BZV32XF67X; 549 | ENABLE_BITCODE = NO; 550 | FRAMEWORK_SEARCH_PATHS = ( 551 | "$(inherited)", 552 | "$(PROJECT_DIR)/Flutter", 553 | ); 554 | INFOPLIST_FILE = Runner/Info.plist; 555 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 556 | LIBRARY_SEARCH_PATHS = ( 557 | "$(inherited)", 558 | "$(PROJECT_DIR)/Flutter", 559 | ); 560 | PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterVideoCompressExample; 561 | PRODUCT_NAME = "$(TARGET_NAME)"; 562 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 563 | SWIFT_VERSION = 5.0; 564 | VERSIONING_SYSTEM = "apple-generic"; 565 | }; 566 | name = Release; 567 | }; 568 | /* End XCBuildConfiguration section */ 569 | 570 | /* Begin XCConfigurationList section */ 571 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 572 | isa = XCConfigurationList; 573 | buildConfigurations = ( 574 | 97C147031CF9000F007C117D /* Debug */, 575 | 97C147041CF9000F007C117D /* Release */, 576 | 249021D3217E4FDB00AE95B9 /* Profile */, 577 | ); 578 | defaultConfigurationIsVisible = 0; 579 | defaultConfigurationName = Release; 580 | }; 581 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 582 | isa = XCConfigurationList; 583 | buildConfigurations = ( 584 | 97C147061CF9000F007C117D /* Debug */, 585 | 97C147071CF9000F007C117D /* Release */, 586 | 249021D4217E4FDB00AE95B9 /* Profile */, 587 | ); 588 | defaultConfigurationIsVisible = 0; 589 | defaultConfigurationName = Release; 590 | }; 591 | /* End XCConfigurationList section */ 592 | }; 593 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 594 | } 595 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_video_compress_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | NSPhotoLibraryUsageDescription 45 | Example usage description 46 | NSCameraUsageDescription 47 | Example usage description 48 | NSMicrophoneUsageDescription 49 | Example usage description 50 | NSAppTransportSecurity 51 | 52 | NSAllowsArbitraryLoads 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_video_compress/flutter_video_compress.dart'; 6 | import 'package:image_picker/image_picker.dart' show ImagePicker, ImageSource; 7 | import 'package:video_player/video_player.dart'; 8 | import 'my-theme.dart'; 9 | 10 | void main() => runApp(MyApp(title: 'Flutter Video Compress Example')); 11 | 12 | class MyApp extends StatefulWidget { 13 | MyApp({this.title}); 14 | 15 | final String title; 16 | 17 | @override 18 | _MyAppState createState() => _MyAppState(); 19 | } 20 | 21 | class _MyAppState extends State { 22 | final _flutterVideoCompress = FlutterVideoCompress(); 23 | Subscription _subscription; 24 | 25 | Image _thumbnailFileImage; 26 | Image _gifFileImage; 27 | 28 | MediaInfo _originalVideoInfo = MediaInfo(path: ''); 29 | MediaInfo _compressedVideoInfo = MediaInfo(path: ''); 30 | String _taskName; 31 | double _progressState = 0; 32 | 33 | final _loadingStreamCtrl = StreamController.broadcast(); 34 | 35 | @override 36 | void initState() { 37 | super.initState(); 38 | _subscription = 39 | _flutterVideoCompress.compressProgress$.subscribe((progress) { 40 | setState(() { 41 | _progressState = progress; 42 | }); 43 | }); 44 | } 45 | 46 | @override 47 | void dispose() { 48 | super.dispose(); 49 | _subscription.unsubscribe(); 50 | _loadingStreamCtrl.close(); 51 | } 52 | 53 | Future runFlutterVideoCompressMethods(File videoFile) async { 54 | _loadingStreamCtrl.sink.add(true); 55 | 56 | var _startDateTime = DateTime.now(); 57 | print('[Compressing Video] start'); 58 | _taskName = '[Compressing Video]'; 59 | final compressedVideoInfo = await _flutterVideoCompress.compressVideo( 60 | videoFile.path, 61 | quality: VideoQuality.DefaultQuality, 62 | deleteOrigin: false, 63 | ); 64 | _taskName = null; 65 | print( 66 | '[Compressing Video] done! ${DateTime.now().difference(_startDateTime).inSeconds}s'); 67 | 68 | _startDateTime = DateTime.now(); 69 | 70 | print('[Getting Thumbnail File] start'); 71 | final thumbnailFile = await _flutterVideoCompress 72 | .getThumbnailWithFile(videoFile.path, quality: 50); 73 | print( 74 | '[Getting Thumbnail File] done! ${DateTime.now().difference(_startDateTime).inSeconds}s'); 75 | 76 | _startDateTime = DateTime.now(); 77 | print('[Getting Gif File] start'); 78 | _taskName = '[Getting Gif File]'; 79 | final gifFile = await _flutterVideoCompress 80 | .convertVideoToGif(videoFile.path, startTime: 0, duration: 5); 81 | print( 82 | '[Getting Gif File] done! ${DateTime.now().difference(_startDateTime).inSeconds}s'); 83 | _taskName = null; 84 | 85 | final videoInfo = await _flutterVideoCompress.getMediaInfo(videoFile.path); 86 | 87 | setState(() { 88 | _thumbnailFileImage = Image.file(thumbnailFile); 89 | _gifFileImage = Image.file(gifFile); 90 | _originalVideoInfo = videoInfo; 91 | _compressedVideoInfo = compressedVideoInfo; 92 | }); 93 | _loadingStreamCtrl.sink.add(false); 94 | } 95 | 96 | Widget _buildMaterialWarp(Widget body) { 97 | return MaterialApp( 98 | title: widget.title, 99 | theme: myTheme, 100 | home: Scaffold( 101 | appBar: AppBar( 102 | title: Text(widget.title), 103 | actions: [ 104 | IconButton( 105 | onPressed: () async { 106 | await _flutterVideoCompress.deleteAllCache(); 107 | }, 108 | icon: Icon(Icons.delete_forever), 109 | ), 110 | ], 111 | ), 112 | body: body), 113 | ); 114 | } 115 | 116 | Widget _buildRoundedRectangleButton(String text, ImageSource source) { 117 | return Padding( 118 | padding: const EdgeInsets.symmetric(horizontal: 32), 119 | child: RaisedButton( 120 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), 121 | child: Text(text, style: TextStyle(color: Colors.white)), 122 | color: Colors.grey[800], 123 | onPressed: () async { 124 | final videoFile = await ImagePicker.pickVideo(source: source); 125 | if (videoFile != null) { 126 | runFlutterVideoCompressMethods(videoFile); 127 | } 128 | }, 129 | ), 130 | ); 131 | } 132 | 133 | String _infoConvert(MediaInfo info) { 134 | return 'path: ${info.path}\n' 135 | 'duration: ${info.duration} microseconds\n' 136 | 'size: ${info.filesize} bytes\n' 137 | 'size: ${info.width} x ${info.height}\n' 138 | 'orientation: ${info.orientation}°\n' 139 | 'compression cancelled: ${info.isCancel}\n' 140 | 'author: ${info.author}'; 141 | } 142 | 143 | List _buildInfoPanel(String title, 144 | {MediaInfo info, Image image, bool isVideoModel = false}) { 145 | if (info?.file == null && image == null && !isVideoModel) return []; 146 | return [ 147 | if (!isVideoModel || info?.file != null) 148 | Card( 149 | child: ListTile( 150 | title: Text(title), 151 | dense: true, 152 | ), 153 | ), 154 | if (info?.file != null) 155 | Padding( 156 | padding: const EdgeInsets.all(8), 157 | child: Text(_infoConvert(info)), 158 | ), 159 | if (image != null) image, 160 | if (isVideoModel && info?.file != null) VideoPlayerView(file: info.file) 161 | ]; 162 | } 163 | 164 | @override 165 | Widget build(context) { 166 | return _buildMaterialWarp( 167 | Stack(children: [ 168 | ListView( 169 | children: [ 170 | const SizedBox(height: 20), 171 | _buildRoundedRectangleButton( 172 | 'Take video from camera with Image Picker', 173 | ImageSource.camera, 174 | ), 175 | _buildRoundedRectangleButton( 176 | 'Take video from gallery with Image Picker', 177 | ImageSource.gallery, 178 | ), 179 | ..._buildInfoPanel( 180 | 'Original video information', 181 | info: _originalVideoInfo, 182 | ), 183 | ..._buildInfoPanel( 184 | 'Original video view', 185 | info: _originalVideoInfo, 186 | isVideoModel: true, 187 | ), 188 | ..._buildInfoPanel( 189 | 'Compressed video information', 190 | info: _compressedVideoInfo, 191 | ), 192 | ..._buildInfoPanel( 193 | 'Compressed video view', 194 | info: _compressedVideoInfo, 195 | isVideoModel: true, 196 | ), 197 | ..._buildInfoPanel( 198 | 'Thumbnail image from file preview', 199 | image: _thumbnailFileImage, 200 | ), 201 | ..._buildInfoPanel( 202 | 'Gif image from original video preview', 203 | image: _gifFileImage, 204 | ), 205 | ].expand((widget) { 206 | if (widget is SizedBox || widget is Card) { 207 | return [widget]; 208 | } 209 | return [widget, const SizedBox(height: 8)]; 210 | }).toList(), 211 | ), 212 | StreamBuilder( 213 | stream: _loadingStreamCtrl.stream, 214 | builder: (context, AsyncSnapshot snapshot) { 215 | if (snapshot.data == true) { 216 | return GestureDetector( 217 | onTap: () { 218 | _flutterVideoCompress.cancelCompression(); 219 | }, 220 | child: Card( 221 | child: Container( 222 | color: Colors.black54, 223 | width: MediaQuery.of(context).size.width, 224 | height: MediaQuery.of(context).size.height, 225 | child: Column( 226 | mainAxisAlignment: MainAxisAlignment.center, 227 | mainAxisSize: MainAxisSize.max, 228 | children: [ 229 | CircularProgressIndicator(), 230 | if (_taskName != null) 231 | Padding( 232 | padding: const EdgeInsets.all(8), 233 | child: Text('[$_taskName] $_progressState%'), 234 | ), 235 | Padding( 236 | padding: const EdgeInsets.all(8), 237 | child: const Text('click cancel...'), 238 | ) 239 | ], 240 | ), 241 | ), 242 | ), 243 | ); 244 | } 245 | return Container(); 246 | }, 247 | ), 248 | ]), 249 | ); 250 | } 251 | } 252 | 253 | class VideoPlayerView extends StatefulWidget { 254 | VideoPlayerView({this.file}); 255 | 256 | final File file; 257 | 258 | @override 259 | _VideoPlayerViewState createState() => _VideoPlayerViewState(); 260 | } 261 | 262 | class _VideoPlayerViewState extends State { 263 | VideoPlayerController _controller; 264 | 265 | @override 266 | void initState() { 267 | super.initState(); 268 | _controller = VideoPlayerController.file(widget.file) 269 | ..initialize().then((_) { 270 | setState(() {}); 271 | }) 272 | ..setVolume(1) 273 | ..play(); 274 | } 275 | 276 | @override 277 | void dispose() { 278 | super.dispose(); 279 | _controller.dispose(); 280 | } 281 | 282 | @override 283 | Widget build(context) { 284 | return GestureDetector( 285 | onTap: () { 286 | setState(() { 287 | _controller.value.isPlaying 288 | ? _controller.pause() 289 | : _controller.play(); 290 | }); 291 | }, 292 | child: SingleChildScrollView( 293 | child: Column( 294 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 295 | mainAxisSize: MainAxisSize.max, 296 | children: [ 297 | Stack( 298 | alignment: Alignment.center, 299 | children: [ 300 | _controller.value.initialized 301 | ? AspectRatio( 302 | aspectRatio: _controller.value.aspectRatio, 303 | child: VideoPlayer(_controller), 304 | ) 305 | : Container(), 306 | Icon( 307 | _controller.value.isPlaying ? null : Icons.play_arrow, 308 | size: 80, 309 | ), 310 | ], 311 | ), 312 | VideoProgressIndicator( 313 | _controller, 314 | allowScrubbing: true, 315 | colors: VideoProgressColors( 316 | playedColor: Color.fromRGBO(255, 255, 255, 0.1), 317 | ), 318 | ), 319 | ], 320 | ), 321 | ), 322 | ); 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /example/lib/my-theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | final ThemeData myTheme = ThemeData( 4 | primarySwatch: MaterialColor( 5 | 4280361249, 6 | { 7 | 50: Color(0xfff2f2f2), 8 | 100: Color(0xffe6e6e6), 9 | 200: Color(0xffcccccc), 10 | 300: Color(0xffb3b3b3), 11 | 400: Color(0xff999999), 12 | 500: Color(0xff808080), 13 | 600: Color(0xff666666), 14 | 700: Color(0xff4d4d4d), 15 | 800: Color(0xff333333), 16 | 900: Color(0xff191919) 17 | }, 18 | ), 19 | brightness: Brightness.dark, 20 | primaryColor: Color(0xff212121), 21 | primaryColorBrightness: Brightness.dark, 22 | primaryColorLight: Color(0xff9e9e9e), 23 | primaryColorDark: Color(0xff000000), 24 | accentColor: Color(0xffd8333f), 25 | accentColorBrightness: Brightness.light, 26 | canvasColor: Color(0xff303030), 27 | scaffoldBackgroundColor: Color(0xff303030), 28 | bottomAppBarColor: Color(0xff424242), 29 | cardColor: Color(0xff424242), 30 | dividerColor: Color(0x1fffffff), 31 | highlightColor: Color(0x40cccccc), 32 | splashColor: Color(0x40cccccc), 33 | selectedRowColor: Color(0xfff5f5f5), 34 | unselectedWidgetColor: Color(0xb3ffffff), 35 | disabledColor: Color(0x4dffffff), 36 | buttonColor: Color(0xff666666), 37 | toggleableActiveColor: Color(0xffd9333e), 38 | secondaryHeaderColor: Color(0xff616161), 39 | textSelectionColor: Color(0xfff44336), 40 | cursorColor: Color(0xff4285f4), 41 | textSelectionHandleColor: Color(0xfff44336), 42 | backgroundColor: Color(0xff616161), 43 | dialogBackgroundColor: Color(0xff424242), 44 | indicatorColor: Color(0xffd9333f), 45 | hintColor: Color(0x80ffffff), 46 | errorColor: Color(0xffd32f2f), 47 | buttonTheme: ButtonThemeData( 48 | textTheme: ButtonTextTheme.normal, 49 | minWidth: 88.0, 50 | height: 36.0, 51 | padding: EdgeInsets.only(top: 0.0, bottom: 0.0, left: 16.0, right: 16.0), 52 | shape: RoundedRectangleBorder( 53 | side: BorderSide( 54 | color: Color(0xff000000), 55 | width: 0.0, 56 | style: BorderStyle.none, 57 | ), 58 | borderRadius: BorderRadius.all( 59 | Radius.circular(2.0), 60 | ), 61 | ), 62 | alignedDropdown: false, 63 | buttonColor: Color(0xff666666), 64 | disabledColor: Color(0x4dffffff), 65 | highlightColor: Color(0x29ffffff), 66 | splashColor: Color(0x1fffffff), 67 | colorScheme: ColorScheme( 68 | primary: Color(0xff999999), 69 | primaryVariant: Color(0xff000000), 70 | secondary: Color(0xff64ffda), 71 | secondaryVariant: Color(0xff00bfa5), 72 | surface: Color(0xff424242), 73 | background: Color(0xff616161), 74 | error: Color(0xffd32f2f), 75 | onPrimary: Color(0xffffffff), 76 | onSecondary: Color(0xff000000), 77 | onSurface: Color(0xffffffff), 78 | onBackground: Color(0xffffffff), 79 | onError: Color(0xff000000), 80 | brightness: Brightness.dark, 81 | ), 82 | ), 83 | textTheme: TextTheme( 84 | display4: TextStyle( 85 | color: Color(0xb3ffffff), 86 | fontSize: 96.0, 87 | fontWeight: FontWeight.w300, 88 | fontStyle: FontStyle.normal, 89 | ), 90 | display3: TextStyle( 91 | color: Color(0xb3ffffff), 92 | fontSize: 60.0, 93 | fontWeight: FontWeight.w300, 94 | fontStyle: FontStyle.normal, 95 | ), 96 | display2: TextStyle( 97 | color: Color(0xb3ffffff), 98 | fontSize: 48.0, 99 | fontWeight: FontWeight.w400, 100 | fontStyle: FontStyle.normal, 101 | ), 102 | display1: TextStyle( 103 | color: Color(0xb3ffffff), 104 | fontSize: 34.0, 105 | fontWeight: FontWeight.w400, 106 | fontStyle: FontStyle.normal, 107 | ), 108 | headline: TextStyle( 109 | color: Color(0xffffffff), 110 | fontSize: 24.0, 111 | fontWeight: FontWeight.w400, 112 | fontStyle: FontStyle.normal, 113 | ), 114 | title: TextStyle( 115 | color: Color(0xffffffff), 116 | fontSize: 20.0, 117 | fontWeight: FontWeight.w500, 118 | fontStyle: FontStyle.normal, 119 | ), 120 | subhead: TextStyle( 121 | color: Color(0xffffffff), 122 | fontSize: 16.0, 123 | fontWeight: FontWeight.w400, 124 | fontStyle: FontStyle.normal, 125 | ), 126 | body2: TextStyle( 127 | color: Color(0xffffffff), 128 | fontSize: 14.0, 129 | fontWeight: FontWeight.w400, 130 | fontStyle: FontStyle.normal, 131 | ), 132 | body1: TextStyle( 133 | color: Color(0xffffffff), 134 | fontSize: 16.0, 135 | fontWeight: FontWeight.w400, 136 | fontStyle: FontStyle.normal, 137 | ), 138 | caption: TextStyle( 139 | color: Color(0xb3ffffff), 140 | fontSize: 12.0, 141 | fontWeight: FontWeight.w400, 142 | fontStyle: FontStyle.normal, 143 | ), 144 | button: TextStyle( 145 | color: Color(0xffffffff), 146 | fontSize: 14.0, 147 | fontWeight: FontWeight.w500, 148 | fontStyle: FontStyle.normal, 149 | ), 150 | subtitle: TextStyle( 151 | color: Color(0xffffffff), 152 | fontSize: 14.0, 153 | fontWeight: FontWeight.w500, 154 | fontStyle: FontStyle.normal, 155 | ), 156 | overline: TextStyle( 157 | color: Color(0xffffffff), 158 | fontSize: 10.0, 159 | fontWeight: FontWeight.w400, 160 | fontStyle: FontStyle.normal, 161 | ), 162 | ), 163 | primaryTextTheme: TextTheme( 164 | display4: TextStyle( 165 | color: Color(0xb3ffffff), 166 | fontSize: 96.0, 167 | fontWeight: FontWeight.w300, 168 | fontStyle: FontStyle.normal, 169 | ), 170 | display3: TextStyle( 171 | color: Color(0xb3ffffff), 172 | fontSize: 60.0, 173 | fontWeight: FontWeight.w300, 174 | fontStyle: FontStyle.normal, 175 | ), 176 | display2: TextStyle( 177 | color: Color(0xb3ffffff), 178 | fontSize: 48.0, 179 | fontWeight: FontWeight.w400, 180 | fontStyle: FontStyle.normal, 181 | ), 182 | display1: TextStyle( 183 | color: Color(0xb3ffffff), 184 | fontSize: 34.0, 185 | fontWeight: FontWeight.w400, 186 | fontStyle: FontStyle.normal, 187 | ), 188 | headline: TextStyle( 189 | color: Color(0xffffffff), 190 | fontSize: 24.0, 191 | fontWeight: FontWeight.w400, 192 | fontStyle: FontStyle.normal, 193 | ), 194 | title: TextStyle( 195 | color: Color(0xffffffff), 196 | fontSize: 20.0, 197 | fontWeight: FontWeight.w500, 198 | fontStyle: FontStyle.normal, 199 | ), 200 | subhead: TextStyle( 201 | color: Color(0xffffffff), 202 | fontSize: 16.0, 203 | fontWeight: FontWeight.w400, 204 | fontStyle: FontStyle.normal, 205 | ), 206 | body2: TextStyle( 207 | color: Color(0xffffffff), 208 | fontSize: 14.0, 209 | fontWeight: FontWeight.w400, 210 | fontStyle: FontStyle.normal, 211 | ), 212 | body1: TextStyle( 213 | color: Color(0xffffffff), 214 | fontSize: 16.0, 215 | fontWeight: FontWeight.w400, 216 | fontStyle: FontStyle.normal, 217 | ), 218 | caption: TextStyle( 219 | color: Color(0xb3ffffff), 220 | fontSize: 12.0, 221 | fontWeight: FontWeight.w400, 222 | fontStyle: FontStyle.normal, 223 | ), 224 | button: TextStyle( 225 | color: Color(0xffffffff), 226 | fontSize: 14.0, 227 | fontWeight: FontWeight.w500, 228 | fontStyle: FontStyle.normal, 229 | ), 230 | subtitle: TextStyle( 231 | color: Color(0xffffffff), 232 | fontSize: 14.0, 233 | fontWeight: FontWeight.w500, 234 | fontStyle: FontStyle.normal, 235 | ), 236 | overline: TextStyle( 237 | color: Color(0xffffffff), 238 | fontSize: 10.0, 239 | fontWeight: FontWeight.w400, 240 | fontStyle: FontStyle.normal, 241 | ), 242 | ), 243 | accentTextTheme: TextTheme( 244 | display4: TextStyle( 245 | color: Color(0x8a000000), 246 | fontSize: 96.0, 247 | fontWeight: FontWeight.w300, 248 | fontStyle: FontStyle.normal, 249 | ), 250 | display3: TextStyle( 251 | color: Color(0x8a000000), 252 | fontSize: 60.0, 253 | fontWeight: FontWeight.w300, 254 | fontStyle: FontStyle.normal, 255 | ), 256 | display2: TextStyle( 257 | color: Color(0x8a000000), 258 | fontSize: 48.0, 259 | fontWeight: FontWeight.w400, 260 | fontStyle: FontStyle.normal, 261 | ), 262 | display1: TextStyle( 263 | color: Color(0x8a000000), 264 | fontSize: 34.0, 265 | fontWeight: FontWeight.w400, 266 | fontStyle: FontStyle.normal, 267 | ), 268 | headline: TextStyle( 269 | color: Color(0xdd000000), 270 | fontSize: 24.0, 271 | fontWeight: FontWeight.w400, 272 | fontStyle: FontStyle.normal, 273 | ), 274 | title: TextStyle( 275 | color: Color(0xdd000000), 276 | fontSize: 20.0, 277 | fontWeight: FontWeight.w500, 278 | fontStyle: FontStyle.normal, 279 | ), 280 | subhead: TextStyle( 281 | color: Color(0xdd000000), 282 | fontSize: 16.0, 283 | fontWeight: FontWeight.w400, 284 | fontStyle: FontStyle.normal, 285 | ), 286 | body2: TextStyle( 287 | color: Color(0xdd000000), 288 | fontSize: 14.0, 289 | fontWeight: FontWeight.w400, 290 | fontStyle: FontStyle.normal, 291 | ), 292 | body1: TextStyle( 293 | color: Color(0xdd000000), 294 | fontSize: 16.0, 295 | fontWeight: FontWeight.w400, 296 | fontStyle: FontStyle.normal, 297 | ), 298 | caption: TextStyle( 299 | color: Color(0x8a000000), 300 | fontSize: 12.0, 301 | fontWeight: FontWeight.w400, 302 | fontStyle: FontStyle.normal, 303 | ), 304 | button: TextStyle( 305 | color: Color(0xdd000000), 306 | fontSize: 14.0, 307 | fontWeight: FontWeight.w500, 308 | fontStyle: FontStyle.normal, 309 | ), 310 | subtitle: TextStyle( 311 | color: Color(0xff000000), 312 | fontSize: 14.0, 313 | fontWeight: FontWeight.w500, 314 | fontStyle: FontStyle.normal, 315 | ), 316 | overline: TextStyle( 317 | color: Color(0xff000000), 318 | fontSize: 10.0, 319 | fontWeight: FontWeight.w400, 320 | fontStyle: FontStyle.normal, 321 | ), 322 | ), 323 | inputDecorationTheme: InputDecorationTheme( 324 | labelStyle: TextStyle( 325 | color: Color(0xffffffff), 326 | fontSize: 16.0, 327 | fontWeight: FontWeight.w400, 328 | fontStyle: FontStyle.normal, 329 | ), 330 | helperStyle: TextStyle( 331 | color: Color(0xffffffff), 332 | fontSize: 16.0, 333 | fontWeight: FontWeight.w400, 334 | fontStyle: FontStyle.normal, 335 | ), 336 | hintStyle: TextStyle( 337 | color: Color(0xffffffff), 338 | fontSize: 16.0, 339 | fontWeight: FontWeight.w400, 340 | fontStyle: FontStyle.normal, 341 | ), 342 | errorStyle: TextStyle( 343 | color: Color(0xffffffff), 344 | fontSize: 16.0, 345 | fontWeight: FontWeight.w400, 346 | fontStyle: FontStyle.normal, 347 | ), 348 | errorMaxLines: null, 349 | hasFloatingPlaceholder: true, 350 | isDense: false, 351 | contentPadding: 352 | EdgeInsets.only(top: 12.0, bottom: 12.0, left: 0.0, right: 0.0), 353 | isCollapsed: false, 354 | prefixStyle: TextStyle( 355 | color: Color(0xffffffff), 356 | fontSize: 16.0, 357 | fontWeight: FontWeight.w400, 358 | fontStyle: FontStyle.normal, 359 | ), 360 | suffixStyle: TextStyle( 361 | color: Color(0xffffffff), 362 | fontSize: 16.0, 363 | fontWeight: FontWeight.w400, 364 | fontStyle: FontStyle.normal, 365 | ), 366 | counterStyle: TextStyle( 367 | color: Color(0xffffffff), 368 | fontSize: 16.0, 369 | fontWeight: FontWeight.w400, 370 | fontStyle: FontStyle.normal, 371 | ), 372 | filled: false, 373 | fillColor: Color(0x00000000), 374 | errorBorder: UnderlineInputBorder( 375 | borderSide: BorderSide( 376 | color: Color(0xff000000), 377 | width: 1.0, 378 | style: BorderStyle.solid, 379 | ), 380 | borderRadius: BorderRadius.all( 381 | Radius.circular(4.0), 382 | ), 383 | ), 384 | focusedBorder: UnderlineInputBorder( 385 | borderSide: BorderSide( 386 | color: Color(0xff000000), 387 | width: 1.0, 388 | style: BorderStyle.solid, 389 | ), 390 | borderRadius: BorderRadius.all( 391 | Radius.circular(4.0), 392 | ), 393 | ), 394 | focusedErrorBorder: UnderlineInputBorder( 395 | borderSide: BorderSide( 396 | color: Color(0xff000000), 397 | width: 1.0, 398 | style: BorderStyle.solid, 399 | ), 400 | borderRadius: BorderRadius.all( 401 | Radius.circular(4.0), 402 | ), 403 | ), 404 | disabledBorder: UnderlineInputBorder( 405 | borderSide: BorderSide( 406 | color: Color(0xff000000), 407 | width: 1.0, 408 | style: BorderStyle.solid, 409 | ), 410 | borderRadius: BorderRadius.all( 411 | Radius.circular(4.0), 412 | ), 413 | ), 414 | enabledBorder: UnderlineInputBorder( 415 | borderSide: BorderSide( 416 | color: Color(0xff000000), 417 | width: 1.0, 418 | style: BorderStyle.solid, 419 | ), 420 | borderRadius: BorderRadius.all( 421 | Radius.circular(4.0), 422 | ), 423 | ), 424 | border: UnderlineInputBorder( 425 | borderSide: BorderSide( 426 | color: Color(0xff000000), 427 | width: 1.0, 428 | style: BorderStyle.solid, 429 | ), 430 | borderRadius: BorderRadius.all( 431 | Radius.circular(4.0), 432 | ), 433 | ), 434 | ), 435 | iconTheme: IconThemeData( 436 | color: Color(0xffffffff), 437 | opacity: 1.0, 438 | size: 24.0, 439 | ), 440 | primaryIconTheme: IconThemeData( 441 | color: Color(0xffffffff), 442 | opacity: 1.0, 443 | size: 24.0, 444 | ), 445 | accentIconTheme: IconThemeData( 446 | color: Color(0xff000000), 447 | opacity: 1.0, 448 | size: 24.0, 449 | ), 450 | sliderTheme: SliderThemeData( 451 | activeTrackColor: Color(0xff212121), 452 | inactiveTrackColor: Color(0x3d212121), 453 | disabledActiveTrackColor: Color(0x52000000), 454 | disabledInactiveTrackColor: Color(0x1f000000), 455 | activeTickMarkColor: Color(0x8a9e9e9e), 456 | inactiveTickMarkColor: Color(0x8a212121), 457 | disabledActiveTickMarkColor: Color(0x1f9e9e9e), 458 | disabledInactiveTickMarkColor: Color(0x1f000000), 459 | thumbColor: Color(0xff212121), 460 | disabledThumbColor: Color(0x52000000), 461 | thumbShape: RoundSliderThumbShape(), 462 | overlayColor: Color(0x29212121), 463 | valueIndicatorColor: Color(0xff212121), 464 | valueIndicatorShape: PaddleSliderValueIndicatorShape(), 465 | showValueIndicator: ShowValueIndicator.onlyForDiscrete, 466 | valueIndicatorTextStyle: TextStyle( 467 | color: Color(0xdd000000), 468 | fontSize: 14.0, 469 | fontWeight: FontWeight.w400, 470 | fontStyle: FontStyle.normal, 471 | ), 472 | overlayShape: SliderComponentShape.noOverlay, 473 | trackHeight: 0, 474 | trackShape: RectangularSliderTrackShape(disabledThumbGapWidth: 0), 475 | tickMarkShape: SliderTickMarkShape.noTickMark, 476 | ), 477 | tabBarTheme: TabBarTheme( 478 | indicatorSize: TabBarIndicatorSize.tab, 479 | labelColor: Color(0xffffffff), 480 | unselectedLabelColor: Color(0xb2ffffff), 481 | ), 482 | chipTheme: ChipThemeData( 483 | backgroundColor: Color(0x1fffffff), 484 | brightness: Brightness.dark, 485 | deleteIconColor: Color(0xdeffffff), 486 | disabledColor: Color(0x0cffffff), 487 | labelPadding: EdgeInsets.only(top: 0.0, bottom: 0.0, left: 8.0, right: 8.0), 488 | labelStyle: TextStyle( 489 | color: Color(0xdeffffff), 490 | fontSize: 14.0, 491 | fontWeight: FontWeight.w400, 492 | fontStyle: FontStyle.normal, 493 | ), 494 | padding: EdgeInsets.only(top: 4.0, bottom: 4.0, left: 4.0, right: 4.0), 495 | secondaryLabelStyle: TextStyle( 496 | color: Color(0x3dffffff), 497 | fontSize: 14.0, 498 | fontWeight: FontWeight.w400, 499 | fontStyle: FontStyle.normal, 500 | ), 501 | secondarySelectedColor: Color(0x3d212121), 502 | selectedColor: Color(0x3dffffff), 503 | shape: StadiumBorder( 504 | side: BorderSide( 505 | color: Color(0xff000000), 506 | width: 0.0, 507 | style: BorderStyle.none, 508 | ), 509 | ), 510 | ), 511 | dialogTheme: DialogTheme( 512 | shape: RoundedRectangleBorder( 513 | side: BorderSide( 514 | color: Color(0xff000000), 515 | width: 0.0, 516 | style: BorderStyle.none, 517 | ), 518 | borderRadius: BorderRadius.all( 519 | Radius.circular(0.0), 520 | ), 521 | ), 522 | ), 523 | ); 524 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.1.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.0.4" 18 | charcode: 19 | dependency: transitive 20 | description: 21 | name: charcode 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.2" 25 | collection: 26 | dependency: transitive 27 | description: 28 | name: collection 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.14.11" 32 | cupertino_icons: 33 | dependency: "direct main" 34 | description: 35 | name: cupertino_icons 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "0.1.2" 39 | flutter: 40 | dependency: "direct main" 41 | description: flutter 42 | source: sdk 43 | version: "0.0.0" 44 | flutter_test: 45 | dependency: "direct dev" 46 | description: flutter 47 | source: sdk 48 | version: "0.0.0" 49 | flutter_video_compress: 50 | dependency: "direct dev" 51 | description: 52 | path: ".." 53 | relative: true 54 | source: path 55 | version: "0.3.7+8" 56 | image_picker: 57 | dependency: "direct main" 58 | description: 59 | name: image_picker 60 | url: "https://pub.dartlang.org" 61 | source: hosted 62 | version: "0.6.0+10" 63 | matcher: 64 | dependency: transitive 65 | description: 66 | name: matcher 67 | url: "https://pub.dartlang.org" 68 | source: hosted 69 | version: "0.12.5" 70 | meta: 71 | dependency: transitive 72 | description: 73 | name: meta 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "1.1.6" 77 | path: 78 | dependency: transitive 79 | description: 80 | name: path 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "1.6.2" 84 | pedantic: 85 | dependency: transitive 86 | description: 87 | name: pedantic 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "1.5.0" 91 | quiver: 92 | dependency: transitive 93 | description: 94 | name: quiver 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "2.0.2" 98 | sky_engine: 99 | dependency: transitive 100 | description: flutter 101 | source: sdk 102 | version: "0.0.99" 103 | source_span: 104 | dependency: transitive 105 | description: 106 | name: source_span 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "1.5.5" 110 | stack_trace: 111 | dependency: transitive 112 | description: 113 | name: stack_trace 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "1.9.3" 117 | stream_channel: 118 | dependency: transitive 119 | description: 120 | name: stream_channel 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "2.0.0" 124 | string_scanner: 125 | dependency: transitive 126 | description: 127 | name: string_scanner 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "1.0.4" 131 | term_glyph: 132 | dependency: transitive 133 | description: 134 | name: term_glyph 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "1.1.0" 138 | test_api: 139 | dependency: transitive 140 | description: 141 | name: test_api 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "0.2.4" 145 | typed_data: 146 | dependency: transitive 147 | description: 148 | name: typed_data 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "1.1.6" 152 | vector_math: 153 | dependency: transitive 154 | description: 155 | name: vector_math 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "2.0.8" 159 | video_player: 160 | dependency: "direct main" 161 | description: 162 | name: video_player 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "0.10.1+3" 166 | sdks: 167 | dart: ">=2.2.2 <3.0.0" 168 | flutter: ">=1.5.0 <2.0.0" 169 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_video_compress_example 2 | description: Demonstrates how to use the flutter_video_compress plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: ">=2.2.2 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | # The following adds the Cupertino Icons font to your application. 13 | # Use with the CupertinoIcons class for iOS style icons. 14 | cupertino_icons: ^0.1.2 15 | image_picker: ^0.6.0+10 16 | video_player: ^0.10.1+3 17 | 18 | dev_dependencies: 19 | flutter_test: 20 | sdk: flutter 21 | 22 | flutter_video_compress: 23 | path: ../ 24 | 25 | # For information on the generic Dart part of this file, see the 26 | # following page: https://www.dartlang.org/tools/pub/pubspec 27 | 28 | # The following section is specific to Flutter. 29 | flutter: 30 | 31 | # The following line ensures that the Material Icons font is 32 | # included with your application, so that you can use the icons in 33 | # the material Icons class. 34 | uses-material-design: true 35 | 36 | # To add assets to your application, add an assets section, like this: 37 | # assets: 38 | # - images/a_dot_burr.jpeg 39 | # - images/a_dot_ham.jpeg 40 | 41 | # An image asset can refer to one or more resolution-specific "variants", see 42 | # https://flutter.dev/assets-and-images/#resolution-aware. 43 | 44 | # For details regarding adding assets from package dependencies, see 45 | # https://flutter.dev/assets-and-images/#from-packages 46 | 47 | # To add custom fonts to your application, add a fonts section here, 48 | # in this "flutter" section. Each entry in this list should have a 49 | # "family" key with the font family name, and a "fonts" key with a 50 | # list giving the asset and other descriptors for the font. For 51 | # example: 52 | # fonts: 53 | # - family: Schyler 54 | # fonts: 55 | # - asset: fonts/Schyler-Regular.ttf 56 | # - asset: fonts/Schyler-Italic.ttf 57 | # style: italic 58 | # - family: Trajan Pro 59 | # fonts: 60 | # - asset: fonts/TrajanPro.ttf 61 | # - asset: fonts/TrajanPro_Bold.ttf 62 | # weight: 700 63 | # 64 | # For details regarding fonts from package dependencies, 65 | # see https://flutter.dev/custom-fonts/#from-packages 66 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | // import 'package:flutter/material.dart'; 9 | // import 'package:flutter_test/flutter_test.dart'; 10 | 11 | // import 'package:flutter_video_compress_example/main.dart'; 12 | 13 | // void main() { 14 | 15 | // } 16 | -------------------------------------------------------------------------------- /flutter_video_compress.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rurico/flutter_video_compress/f2f33ecf5274777e464e137abcdf0ff9d1799ed5/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/AvController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AvController.swift 3 | // flutter_video_compress 4 | // 5 | // Created by ryu on 2019/6/21. 6 | // 7 | 8 | import AVFoundation 9 | import MobileCoreServices 10 | 11 | class AvController: NSObject { 12 | public func getVideoAsset(_ url:URL)->AVURLAsset { 13 | return AVURLAsset(url: url) 14 | } 15 | 16 | public func getTrack(_ asset: AVURLAsset)->AVAssetTrack? { 17 | var track : AVAssetTrack? = nil 18 | let group = DispatchGroup() 19 | group.enter() 20 | asset.loadValuesAsynchronously(forKeys: ["tracks"], completionHandler: { 21 | var error: NSError? = nil; 22 | let status = asset.statusOfValue(forKey: "tracks", error: &error) 23 | if (status == .loaded) { 24 | track = asset.tracks(withMediaType: AVMediaType.video).first 25 | } 26 | group.leave() 27 | }) 28 | group.wait() 29 | return track 30 | } 31 | 32 | public func getVideoOrientation(_ path:String)-> Int? { 33 | let url = Utility.getPathUrl(path) 34 | let asset = getVideoAsset(url) 35 | guard let track = getTrack(asset) else { 36 | return nil 37 | } 38 | let size = track.naturalSize 39 | let txf = track.preferredTransform 40 | if size.width == txf.tx && size.height == txf.ty { 41 | return 0 42 | } else if txf.tx == 0 && txf.ty == 0 { 43 | return 90 44 | } else if txf.tx == 0 && txf.ty == size.width { 45 | return 180 46 | } else { 47 | return 270 48 | } 49 | } 50 | 51 | public func getMetaDataByTag(_ asset:AVAsset,key:String)->String { 52 | for item in asset.commonMetadata { 53 | if item.commonKey?.rawValue == key { 54 | return item.stringValue ?? ""; 55 | } 56 | } 57 | return "" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ios/Classes/FlutterVideoCompressPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface FlutterVideoCompressPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/Classes/FlutterVideoCompressPlugin.m: -------------------------------------------------------------------------------- 1 | #import "FlutterVideoCompressPlugin.h" 2 | #import 3 | 4 | @implementation FlutterVideoCompressPlugin 5 | + (void)registerWithRegistrar:(NSObject*)registrar { 6 | [SwiftFlutterVideoCompressPlugin registerWithRegistrar:registrar]; 7 | } 8 | @end 9 | -------------------------------------------------------------------------------- /ios/Classes/SwiftFlutterVideoCompressPlugin.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import AVFoundation 3 | import Regift 4 | 5 | public class SwiftFlutterVideoCompressPlugin: NSObject, FlutterPlugin { 6 | private let channelName = "flutter_video_compress" 7 | private var exporter: AVAssetExportSession? = nil 8 | private var stopCommand = false 9 | private let channel: FlutterMethodChannel 10 | private let avController = AvController() 11 | 12 | init(channel: FlutterMethodChannel) { 13 | self.channel = channel 14 | } 15 | 16 | public static func register(with registrar: FlutterPluginRegistrar) { 17 | let channel = FlutterMethodChannel(name: "flutter_video_compress", binaryMessenger: registrar.messenger()) 18 | let instance = SwiftFlutterVideoCompressPlugin(channel: channel) 19 | registrar.addMethodCallDelegate(instance, channel: channel) 20 | } 21 | 22 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 23 | let args = call.arguments as? Dictionary 24 | switch call.method { 25 | case "getThumbnail": 26 | let path = args!["path"] as! String 27 | let quality = args!["quality"] as! NSNumber 28 | let position = args!["position"] as! NSNumber 29 | getThumbnail(path, quality, position, result) 30 | case "getThumbnailWithFile": 31 | let path = args!["path"] as! String 32 | let quality = args!["quality"] as! NSNumber 33 | let position = args!["position"] as! NSNumber 34 | getThumbnailWithFile(path, quality, position, result) 35 | case "getMediaInfo": 36 | let path = args!["path"] as! String 37 | getMediaInfo(path, result) 38 | case "compressVideo": 39 | let path = args!["path"] as! String 40 | let quality = args!["quality"] as! NSNumber 41 | let deleteOrigin = args!["deleteOrigin"] as! Bool 42 | let startTime = args!["startTime"] as? Double 43 | let duration = args!["duration"] as? Double 44 | let includeAudio = args!["includeAudio"] as? Bool 45 | let frameRate = args!["frameRate"] as? Int 46 | compressVideo(path, quality, deleteOrigin, startTime, duration, includeAudio, 47 | frameRate, result) 48 | case "cancelCompression": 49 | cancelCompression(result) 50 | case "convertVideoToGif": 51 | let path = args!["path"] as! String 52 | let startTime = args!["startTime"] as! NSNumber 53 | let endTime = args!["endTime"] as! NSNumber 54 | let duration = args!["duration"] as! NSNumber 55 | convertVideoToGif(path, startTime, endTime, duration, result) 56 | case "deleteAllCache": 57 | Utility.deleteFile(Utility.basePath(), clear: true) 58 | result(true) 59 | default: 60 | result(FlutterMethodNotImplemented) 61 | } 62 | } 63 | 64 | private func getBitMap(_ path: String,_ quality: NSNumber,_ position: NSNumber,_ result: FlutterResult)-> Data? { 65 | let url = Utility.getPathUrl(path) 66 | let asset = avController.getVideoAsset(url) 67 | guard let track = avController.getTrack(asset) else { return nil } 68 | 69 | let assetImgGenerate = AVAssetImageGenerator(asset: asset) 70 | assetImgGenerate.appliesPreferredTrackTransform = true 71 | 72 | let timeScale = CMTimeScale(track.nominalFrameRate) 73 | let time = CMTimeMakeWithSeconds(Float64(truncating: position),preferredTimescale: timeScale) 74 | guard let img = try? assetImgGenerate.copyCGImage(at:time, actualTime: nil) else { 75 | return nil 76 | } 77 | let thumbnail = UIImage(cgImage: img) 78 | let compressionQuality = CGFloat(0.01 * Double(truncating: quality)) 79 | return thumbnail.jpegData(compressionQuality: compressionQuality) 80 | } 81 | 82 | private func getThumbnail(_ path: String,_ quality: NSNumber,_ position: NSNumber,_ result: FlutterResult) { 83 | if let bitmap = getBitMap(path,quality,position,result) { 84 | result(bitmap) 85 | } 86 | } 87 | 88 | private func getThumbnailWithFile(_ path: String,_ quality: NSNumber,_ position: NSNumber,_ result: FlutterResult) { 89 | let fileName = Utility.getFileName(path) 90 | let url = Utility.getPathUrl("\(Utility.basePath())/\(fileName).jpg") 91 | Utility.deleteFile(path) 92 | if let bitmap = getBitMap(path,quality,position,result) { 93 | guard (try? bitmap.write(to: url)) != nil else { 94 | return result(FlutterError(code: channelName,message: "getThumbnailWithFile error",details: "getThumbnailWithFile error")) 95 | } 96 | result(Utility.excludeFileProtocol(url.absoluteString)) 97 | } 98 | } 99 | 100 | public func getMediaInfoJson(_ path: String)->[String : Any?] { 101 | let url = Utility.getPathUrl(path) 102 | let asset = avController.getVideoAsset(url) 103 | guard let track = avController.getTrack(asset) else { return [:] } 104 | 105 | let playerItem = AVPlayerItem(url: url) 106 | let metadataAsset = playerItem.asset 107 | 108 | let orientation = avController.getVideoOrientation(path) 109 | 110 | let title = avController.getMetaDataByTag(metadataAsset,key: "title") 111 | let author = avController.getMetaDataByTag(metadataAsset,key: "author") 112 | 113 | let duration = asset.duration.seconds * 1000 114 | let filesize = track.totalSampleDataLength 115 | 116 | let size = track.naturalSize.applying(track.preferredTransform) 117 | 118 | let width = abs(size.width) 119 | let height = abs(size.height) 120 | 121 | let dictionary = [ 122 | "path":Utility.excludeFileProtocol(path), 123 | "title":title, 124 | "author":author, 125 | "width":width, 126 | "height":height, 127 | "duration":duration, 128 | "filesize":filesize, 129 | "orientation":orientation 130 | ] as [String : Any?] 131 | return dictionary 132 | } 133 | 134 | private func getMediaInfo(_ path: String,_ result: FlutterResult) { 135 | let json = getMediaInfoJson(path) 136 | let string = Utility.keyValueToJson(json) 137 | result(string) 138 | } 139 | 140 | 141 | @objc private func updateProgress(timer:Timer) { 142 | let asset = timer.userInfo as! AVAssetExportSession 143 | if(!stopCommand) { 144 | channel.invokeMethod("updateProgress", arguments: "\(String(describing: asset.progress * 100))") 145 | } 146 | } 147 | 148 | private func getExportPreset(_ quality: NSNumber)->String { 149 | switch(quality) { 150 | case 2: 151 | return AVAssetExportPresetMediumQuality 152 | case 3: 153 | return AVAssetExportPresetHighestQuality 154 | default: 155 | return AVAssetExportPresetLowQuality 156 | } 157 | } 158 | 159 | private func getComposition(_ isIncludeAudio: Bool,_ timeRange: CMTimeRange, _ sourceVideoTrack: AVAssetTrack)->AVAsset { 160 | let composition = AVMutableComposition() 161 | if !isIncludeAudio { 162 | let compressionVideoTrack = composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid) 163 | compressionVideoTrack!.preferredTransform = sourceVideoTrack.preferredTransform 164 | try? compressionVideoTrack!.insertTimeRange(timeRange, of: sourceVideoTrack, at: CMTime.zero) 165 | } else { 166 | return sourceVideoTrack.asset! 167 | } 168 | 169 | return composition 170 | } 171 | 172 | private func compressVideo(_ path: String,_ quality: NSNumber,_ deleteOrigin: Bool,_ startTime: Double?, 173 | _ duration: Double?,_ includeAudio: Bool?,_ frameRate: Int?, 174 | _ result: @escaping FlutterResult) { 175 | let sourceVideoUrl = Utility.getPathUrl(path) 176 | let sourceVideoType = sourceVideoUrl.pathExtension 177 | 178 | let sourceVideoAsset = avController.getVideoAsset(sourceVideoUrl) 179 | let sourceVideoTrack = avController.getTrack(sourceVideoAsset) 180 | 181 | let compressionUrl = 182 | Utility.getPathUrl("\(Utility.basePath())/\(Utility.getFileName(path)).\(sourceVideoType)") 183 | 184 | let timescale = sourceVideoAsset.duration.timescale 185 | let minStartTime = Double(startTime ?? 0) 186 | 187 | let videoDuration = sourceVideoAsset.duration.seconds 188 | let minDuration = Double(duration ?? videoDuration) 189 | let maxDurationTime = minStartTime + minDuration < videoDuration ? minDuration : videoDuration 190 | 191 | let cmStartTime = CMTimeMakeWithSeconds(minStartTime, preferredTimescale: timescale) 192 | let cmDurationTime = CMTimeMakeWithSeconds(maxDurationTime, preferredTimescale: timescale) 193 | let timeRange: CMTimeRange = CMTimeRangeMake(start: cmStartTime, duration: cmDurationTime) 194 | 195 | let isIncludeAudio = includeAudio != nil ? includeAudio! : false 196 | 197 | let session = getComposition(isIncludeAudio, timeRange, sourceVideoTrack!) 198 | 199 | let exporter = AVAssetExportSession(asset: session, presetName: getExportPreset(quality))! 200 | 201 | exporter.outputURL = compressionUrl 202 | exporter.outputFileType = AVFileType.mp4 203 | exporter.shouldOptimizeForNetworkUse = true 204 | 205 | if frameRate != nil { 206 | let videoComposition = AVMutableVideoComposition(propertiesOf: sourceVideoAsset) 207 | videoComposition.frameDuration = CMTimeMake(value: 1, timescale: Int32(frameRate!)) 208 | exporter.videoComposition = videoComposition 209 | } 210 | 211 | if !isIncludeAudio { 212 | exporter.timeRange = timeRange 213 | } 214 | 215 | Utility.deleteFile(compressionUrl.absoluteString) 216 | 217 | let timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.updateProgress), 218 | userInfo: exporter, repeats: true) 219 | 220 | exporter.exportAsynchronously(completionHandler: { 221 | if(self.stopCommand) { 222 | timer.invalidate() 223 | self.stopCommand = false 224 | var json = self.getMediaInfoJson(path) 225 | json["isCancel"] = true 226 | let jsonString = Utility.keyValueToJson(json) 227 | return result(jsonString) 228 | } 229 | if deleteOrigin { 230 | timer.invalidate() 231 | let fileManager = FileManager.default 232 | do { 233 | if fileManager.fileExists(atPath: path) { 234 | try fileManager.removeItem(atPath: path) 235 | } 236 | self.exporter = nil 237 | self.stopCommand = false 238 | } 239 | catch let error as NSError { 240 | print(error) 241 | } 242 | } 243 | var json = self.getMediaInfoJson(compressionUrl.absoluteString) 244 | json["isCancel"] = false 245 | let jsonString = Utility.keyValueToJson(json) 246 | result(jsonString) 247 | }) 248 | } 249 | 250 | private func cancelCompression(_ result: FlutterResult) { 251 | exporter?.cancelExport() 252 | stopCommand = true 253 | result("") 254 | } 255 | 256 | private func convertVideoToGif(_ path: String,_ startTime: NSNumber,_ endTime: NSNumber, _ duration:NSNumber, 257 | _ result: FlutterResult) { 258 | let gifStartTime = Float(truncating: startTime) 259 | var gifDuration = Float(truncating: 0) 260 | 261 | if endTime as! Int > 0 { 262 | if startTime.intValue > endTime.intValue { 263 | result(FlutterError(code: channelName, message: "startTime should preceed endTime", 264 | details: nil)) 265 | } else { 266 | gifDuration = Float(Float(truncating: endTime) - gifStartTime) 267 | } 268 | } else { 269 | gifDuration = Float(truncating: duration) 270 | } 271 | 272 | let frameRate = Int(15) 273 | let loopCount = Int(0) 274 | 275 | let sourceFileURL = Utility.getPathUrl(path) 276 | let destinationUrl = Utility.getPathUrl("\(Utility.basePath())/\(Utility.getFileName(path)).gif") 277 | 278 | let trimmedRegift = Regift(sourceFileURL: sourceFileURL, destinationFileURL: destinationUrl, 279 | startTime: gifStartTime, duration: gifDuration, frameRate: frameRate, 280 | loopCount: loopCount, size: nil) 281 | 282 | let destinationPath = trimmedRegift.createGif(); 283 | 284 | result(Utility.excludeFileProtocol(destinationPath!.absoluteString)) 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /ios/Classes/Utility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utility.swift 3 | // flutter_video_compress 4 | // 5 | // Created by ryu on 2019/6/21. 6 | // 7 | 8 | class Utility: NSObject { 9 | static let fileManager = FileManager.default 10 | 11 | static func basePath()->String { 12 | let path = "\(NSTemporaryDirectory())flutter_video_compress" 13 | do { 14 | if !fileManager.fileExists(atPath: path) { 15 | try! fileManager.createDirectory(atPath: path, 16 | withIntermediateDirectories: true, attributes: nil) 17 | } 18 | } 19 | return path 20 | } 21 | 22 | static func stripFileExtension(_ fileName:String)->String { 23 | var components = fileName.components(separatedBy: ".") 24 | if components.count > 1 { 25 | components.removeLast() 26 | return components.joined(separator: ".") 27 | } else { 28 | return fileName 29 | } 30 | } 31 | static func getFileName(_ path: String)->String { 32 | return stripFileExtension((path as NSString).lastPathComponent) 33 | } 34 | 35 | static func getPathUrl(_ path: String)->URL { 36 | return URL(fileURLWithPath: excludeFileProtocol(path)) 37 | } 38 | 39 | static func excludeFileProtocol(_ path: String)->String { 40 | return path.replacingOccurrences(of: "file://", with: "") 41 | } 42 | 43 | static func keyValueToJson(_ keyAndValue: [String : Any?])->String { 44 | let data = try! JSONSerialization.data(withJSONObject: keyAndValue as NSDictionary, options: []) 45 | let jsonString = NSString(data:data as Data,encoding: String.Encoding.utf8.rawValue) 46 | return jsonString! as String 47 | } 48 | 49 | static func deleteFile(_ path: String, clear: Bool = false) { 50 | let url = getPathUrl(path) 51 | if fileManager.fileExists(atPath: url.absoluteString) { 52 | try? fileManager.removeItem(at: url) 53 | } 54 | if clear { 55 | try? fileManager.removeItem(at: url) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ios/flutter_video_compress.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 3 | # 4 | Pod::Spec.new do |s| 5 | s.name = 'flutter_video_compress' 6 | s.version = '0.3.0' 7 | s.swift_version = '5.0' 8 | s.summary = 'A new flutter plugin project.' 9 | s.description = <<-DESC 10 | A new flutter plugin project. 11 | DESC 12 | s.homepage = 'https://github.com/TenkaiRuri/flutter_video_compress' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'FlutterVideoCompress Team' => 'babichan@outlook.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.public_header_files = 'Classes/**/*.h' 18 | s.dependency 'Flutter' 19 | s.dependency 'Regift' 20 | s.preserve_paths = 'Regift.framework' 21 | s.xcconfig = { 'OTHER_LDFLAGS' => '-framework Regift' } 22 | s.vendored_frameworks = 'Regift.framework' 23 | 24 | s.ios.deployment_target = '8.0' 25 | end 26 | 27 | -------------------------------------------------------------------------------- /lib/android/media_metadata_retriever.dart: -------------------------------------------------------------------------------- 1 | abstract class Enum { 2 | final T _value; 3 | 4 | const Enum(this._value); 5 | 6 | T get value => _value; 7 | } 8 | 9 | class MediaMetadataRetriever extends Enum { 10 | /// [Android] API level 10 11 | static const METADATA_KEY_ALBUM = MediaMetadataRetriever(1); 12 | 13 | /// [Android] API level 10 14 | static const METADATA_KEY_ALBUMARTIST = MediaMetadataRetriever(13); 15 | 16 | /// [Android] API level 10 17 | static const METADATA_KEY_ARTIST = MediaMetadataRetriever(2); 18 | 19 | /// [Android] API level 10 20 | static const METADATA_KEY_AUTHOR = MediaMetadataRetriever(3); 21 | 22 | /// [Android] API level 14 23 | static const METADATA_KEY_BITRATE = MediaMetadataRetriever(20); 24 | 25 | /// [Android] API level 23 26 | static const METADATA_KEY_CAPTURE_FRAMERATE = MediaMetadataRetriever(25); 27 | 28 | /// [Android] API level 10 29 | static const METADATA_KEY_CD_TRACK_NUMBER = MediaMetadataRetriever(0); 30 | 31 | /// [Android] API level 10 32 | static const METADATA_KEY_COMPILATION = MediaMetadataRetriever(15); 33 | 34 | /// [Android] API level 10 35 | static const METADATA_KEY_COMPOSER = MediaMetadataRetriever(4); 36 | 37 | /// [Android] API level 10 38 | static const METADATA_KEY_DATE = MediaMetadataRetriever(5); 39 | 40 | /// [Android] API level 10 41 | static const METADATA_KEY_DISC_NUMBER = MediaMetadataRetriever(14); 42 | 43 | /// [Android] API level 10 44 | static const METADATA_KEY_DURATION = MediaMetadataRetriever(9); 45 | 46 | /// [Android] API level Q 47 | static const METADATA_KEY_EXIF_LENGTH = MediaMetadataRetriever(34); 48 | 49 | /// [Android] API level Q 50 | static const METADATA_KEY_EXIF_OFFSET = MediaMetadataRetriever(33); 51 | 52 | /// [Android] API level 10 53 | static const METADATA_KEY_GENRE = MediaMetadataRetriever(6); 54 | 55 | /// [Android] API level 14 56 | static const METADATA_KEY_HAS_AUDIO = MediaMetadataRetriever(16); 57 | 58 | /// [Android] API level 28 59 | static const METADATA_KEY_HAS_IMAGE = MediaMetadataRetriever(26); 60 | 61 | /// [Android] API level 14 62 | static const METADATA_KEY_HAS_VIDEO = MediaMetadataRetriever(17); 63 | 64 | /// [Android] API level 28 65 | static const METADATA_KEY_IMAGE_COUNT = MediaMetadataRetriever(27); 66 | 67 | /// [Android] API level 28 68 | static const METADATA_KEY_IMAGE_HEIGHT = MediaMetadataRetriever(30); 69 | 70 | /// [Android] API level 28 71 | static const METADATA_KEY_IMAGE_PRIMARY = MediaMetadataRetriever(28); 72 | 73 | /// [Android] API level 28 74 | static const METADATA_KEY_IMAGE_ROTATION = MediaMetadataRetriever(31); 75 | 76 | /// [Android] API level 28 77 | static const METADATA_KEY_IMAGE_WIDTH = MediaMetadataRetriever(29); 78 | 79 | /// [Android] API level 15 80 | static const METADATA_KEY_LOCATION = MediaMetadataRetriever(23); 81 | 82 | /// [Android] API level 10 83 | static const METADATA_KEY_MIMETYPE = MediaMetadataRetriever(12); 84 | 85 | /// [Android] API level 10 86 | static const METADATA_KEY_NUM_TRACKS = MediaMetadataRetriever(10); 87 | 88 | /// [Android] API level 10 89 | static const METADATA_KEY_TITLE = MediaMetadataRetriever(7); 90 | 91 | /// [Android] API level 28 92 | static const METADATA_KEY_VIDEO_FRAME_COUNT = MediaMetadataRetriever(32); 93 | 94 | /// [Android] API level 14 95 | static const METADATA_KEY_VIDEO_HEIGHT = MediaMetadataRetriever(19); 96 | 97 | /// [Android] API level 17 98 | static const METADATA_KEY_VIDEO_ROTATION = MediaMetadataRetriever(24); 99 | 100 | /// [Android] API level 14 101 | static const METADATA_KEY_VIDEO_WIDTH = MediaMetadataRetriever(18); 102 | 103 | /// [Android] API level 10 104 | static const METADATA_KEY_WRITER = MediaMetadataRetriever(11); 105 | 106 | /// [Android] API level 10 107 | static const METADATA_KEY_YEAR = MediaMetadataRetriever(8); 108 | 109 | const MediaMetadataRetriever(int value) : super(value); 110 | } 111 | -------------------------------------------------------------------------------- /lib/flutter_video_compress.dart: -------------------------------------------------------------------------------- 1 | /// Compressed video generates a new path, keep the source video or delete it. 2 | /// provide get video information or get thumbnail of the video file. 3 | 4 | library flutter_video_compress; 5 | 6 | import 'dart:async'; 7 | import 'dart:convert'; 8 | import 'dart:io'; 9 | import 'dart:typed_data'; 10 | 11 | import 'dart:ui'; 12 | 13 | import 'package:flutter/foundation.dart'; 14 | import 'package:flutter/services.dart'; 15 | 16 | part 'src/flutter_video_compress.dart'; 17 | part 'src/video_quality.dart'; 18 | part 'src/observable_builder.dart'; 19 | part 'model/media_info.dart'; 20 | -------------------------------------------------------------------------------- /lib/model/media_info.dart: -------------------------------------------------------------------------------- 1 | part of flutter_video_compress; 2 | 3 | class MediaInfo { 4 | String path; 5 | String title; 6 | String author; 7 | int width; 8 | int height; 9 | 10 | /// [Android] API level 17 11 | int orientation; 12 | 13 | /// bytes 14 | int filesize; // filesize 15 | /// microsecond 16 | double duration; 17 | bool isCancel; 18 | File file; 19 | 20 | MediaInfo({ 21 | @required this.path, 22 | this.title, 23 | this.author, 24 | this.width, 25 | this.height, 26 | this.orientation, 27 | this.filesize, 28 | this.duration, 29 | this.isCancel, 30 | this.file, 31 | }); 32 | 33 | MediaInfo.fromJson(Map json) { 34 | path = json['path']; 35 | title = json['title']; 36 | author = json['author']; 37 | width = json['width']; 38 | height = json['height']; 39 | orientation = json['orientation']; 40 | filesize = json['filesize']; 41 | duration = double.tryParse('${json['duration']}'); 42 | isCancel = json['isCancel']; 43 | file = File(path); 44 | } 45 | 46 | Map toJson() { 47 | final Map data = Map(); 48 | data['path'] = this.path; 49 | data['title'] = this.title; 50 | data['author'] = this.author; 51 | data['width'] = this.width; 52 | data['height'] = this.height; 53 | if (this.orientation != null) { 54 | data['orientation'] = this.orientation; 55 | } 56 | data['filesize'] = this.filesize; 57 | data['duration'] = this.duration; 58 | if (this.isCancel != null) { 59 | data['isCancel'] = this.isCancel; 60 | } 61 | data['file'] = File(path).toString(); 62 | return data; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/src/flutter_video_compress.dart: -------------------------------------------------------------------------------- 1 | part of flutter_video_compress; 2 | 3 | class FlutterVideoCompress { 4 | static const _channel = const MethodChannel('flutter_video_compress'); 5 | 6 | factory FlutterVideoCompress() => FlutterVideoCompress._(); 7 | 8 | /// Subscribe the conversion progress 9 | final compressProgress$ = ObservableBuilder(); 10 | 11 | /// get is Compressing state 12 | bool get isCompressing => _isCompressing; 13 | 14 | bool _isCompressing = false; 15 | 16 | FlutterVideoCompress._() { 17 | _channel.setMethodCallHandler(_handleCallback); 18 | } 19 | 20 | Future _handleCallback(MethodCall call) async { 21 | switch (call.method) { 22 | case 'updateProgress': 23 | final progress = double.tryParse(call.arguments); 24 | _updateProgressState(progress); 25 | break; 26 | } 27 | } 28 | 29 | void _updateProgressState(double state) { 30 | if (state != null) { 31 | compressProgress$.next(state); 32 | } 33 | } 34 | 35 | Future _invoke(String name, [Map params]) async { 36 | T result; 37 | try { 38 | result = params != null 39 | ? await _channel.invokeMethod(name, params) 40 | : await _channel.invokeMethod(name); 41 | } on PlatformException catch (e) { 42 | debugPrint('''FlutterVideoCompress Error: 43 | Method: $name 44 | $e'''); 45 | } 46 | return result; 47 | } 48 | 49 | /// get thumbnail from [path] 50 | /// 51 | /// get thumbnail from [path] return [Future], 52 | /// quality can be controlled by [quality] from 1 to 100, 53 | /// select the position unit in the video by [position] is seconds, don't worry about crossing the boundary 54 | /// 55 | /// ## example 56 | /// ```dart 57 | /// final uint8list = await _flutterVideoCompress.getThumbnail( 58 | /// file.path, 59 | /// quality: 50, 60 | /// ); 61 | /// ``` 62 | Future getThumbnail( 63 | String path, { 64 | int quality = 100, 65 | int position = -1, 66 | }) async { 67 | assert(path != null); 68 | assert(quality > 1 || quality < 100); 69 | 70 | return await _invoke('getThumbnail', { 71 | 'path': path, 72 | 'quality': quality, 73 | 'position': position, 74 | }); 75 | } 76 | 77 | /// get thumbnail file from [path] return [Future] 78 | /// 79 | /// quality can be controlled by [quality] from 1 to 100, 80 | /// select the position unit in the video by [position] is seconds, don't worry about crossing the boundary 81 | /// 82 | /// ## example 83 | /// ```dart 84 | /// final file = await _flutterVideoCompress.getThumbnailWithFile( 85 | /// file.path, 86 | /// quality: 50, 87 | /// ); 88 | /// ``` 89 | Future getThumbnailWithFile( 90 | String path, { 91 | int quality = 100, 92 | int position = -1, 93 | }) async { 94 | assert(path != null); 95 | assert(quality > 1 || quality < 100); 96 | 97 | final filePath = await _invoke('getThumbnailWithFile', { 98 | 'path': path, 99 | 'quality': quality, 100 | 'position': position, 101 | }); 102 | 103 | final file = File(filePath); 104 | 105 | return file; 106 | } 107 | 108 | /// converts provided video to a gif. Video is cut starting from [startTime] 109 | /// for the provided [duration] or until [endTime] if set. Either [endTime] or 110 | /// [duration] has to be set where if both set, [endTime] has priority. [endTime] 111 | /// has to be greater than [startTime] in order for this method to work. 112 | /// All time variables should be in seconds. Take care of about [duration] 113 | /// of the video since the plugin doesn't check if [startTime] is within the 114 | /// length of the provided video. 115 | /// 116 | /// ## example 117 | /// ```dart 118 | /// final file = await _flutterVideoCompress.convertVideoToGif( 119 | /// videoFile.path, 120 | /// startTime: 0, 121 | /// duration: 5, 122 | /// ); 123 | /// debugPrint(file.path); 124 | /// ``` 125 | Future convertVideoToGif( 126 | String path, { 127 | int startTime = 0, 128 | int endTime = -1, 129 | int duration = -1, // When you do not know the end time 130 | }) async { 131 | assert(path != null); 132 | if (endTime > 0) { 133 | assert(startTime <= endTime); 134 | } 135 | final filePath = await _invoke('convertVideoToGif', { 136 | 'path': path, 137 | 'startTime': startTime, 138 | 'endTime': endTime, 139 | 'duration': duration 140 | }); 141 | 142 | final file = File(filePath); 143 | 144 | return file; 145 | } 146 | 147 | /// get media information from [path] 148 | /// 149 | /// get media information from [path] return [Future] 150 | /// 151 | /// ## example 152 | /// ```dart 153 | /// final info = await _flutterVideoCompress.getMediaInfo(file.path); 154 | /// debugPrint(info.toJson()); 155 | /// ``` 156 | Future getMediaInfo(String path) async { 157 | assert(path != null); 158 | final jsonStr = await _invoke('getMediaInfo', {'path': path}); 159 | final jsonMap = json.decode(jsonStr); 160 | return MediaInfo.fromJson(jsonMap); 161 | } 162 | 163 | /// compress video from [path] 164 | /// compress video from [path] return [Future] 165 | /// 166 | /// you can choose its quality by [quality], 167 | /// determine whether to delete his source file by [deleteOrigin] 168 | /// optional parameters [startTime] [duration] [includeAudio] [frameRate] 169 | /// 170 | /// ## example 171 | /// ```dart 172 | /// final info = await _flutterVideoCompress.compressVideo( 173 | /// file.path, 174 | /// deleteOrigin: true, 175 | /// ); 176 | /// debugPrint(info.toJson()); 177 | /// ``` 178 | Future compressVideo( 179 | String path, { 180 | VideoQuality quality = VideoQuality.DefaultQuality, 181 | bool deleteOrigin = false, 182 | int startTime, 183 | int duration, 184 | bool includeAudio, 185 | int frameRate, 186 | }) async { 187 | assert(path != null); 188 | if (_isCompressing) { 189 | throw StateError('''FlutterVideoCompress Error: 190 | Method: compressVideo 191 | Already have a compression process, you need to wait for the process to finish or stop it'''); 192 | } 193 | _isCompressing = true; 194 | if (compressProgress$._notSubscribed) { 195 | debugPrint('''FlutterVideoCompress: You can try to subscribe to the 196 | compressProgress\$ stream to know the compressing state.'''); 197 | } 198 | final jsonStr = await _invoke('compressVideo', { 199 | 'path': path, 200 | 'quality': quality.index, 201 | 'deleteOrigin': deleteOrigin, 202 | 'startTime': startTime, 203 | 'duration': duration, 204 | 'includeAudio': includeAudio, 205 | 'frameRate': frameRate, 206 | }); 207 | _isCompressing = false; 208 | final jsonMap = json.decode(jsonStr); 209 | return MediaInfo.fromJson(jsonMap); 210 | } 211 | 212 | /// stop compressing the file that is currently being compressed. 213 | /// If there is no compression process, nothing will happen. 214 | /// 215 | /// ## example 216 | /// ```dart 217 | /// await _flutterVideoCompress.cancelCompression(); 218 | /// ``` 219 | Future cancelCompression() async { 220 | await _invoke('cancelCompression'); 221 | } 222 | 223 | /// delete the cache folder, please do not put other things 224 | /// in the folder of this plugin, it will be cleared 225 | /// 226 | /// ## example 227 | /// ```dart 228 | /// await _flutterVideoCompress.deleteAllCache(); 229 | /// ``` 230 | Future deleteAllCache() async { 231 | return await _invoke('deleteAllCache'); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /lib/src/observable_builder.dart: -------------------------------------------------------------------------------- 1 | /// If you read this source code, you may find this surprise. 2 | /// a small subscription library 3 | 4 | part of flutter_video_compress; 5 | 6 | class ObservableBuilder { 7 | final StreamController _observable = StreamController(); 8 | bool _notSubscribed = true; 9 | 10 | void next(T value) { 11 | _observable.add(value); 12 | } 13 | 14 | Subscription subscribe(void onData(T event), 15 | {Function onError, void onDone(), bool cancelOnError}) { 16 | _notSubscribed = false; 17 | _observable.stream.listen(onData, 18 | onError: onError, onDone: onDone, cancelOnError: cancelOnError); 19 | return Subscription(_observable.close); 20 | } 21 | } 22 | 23 | class Subscription { 24 | final VoidCallback unsubscribe; 25 | const Subscription(this.unsubscribe); 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/video_quality.dart: -------------------------------------------------------------------------------- 1 | part of flutter_video_compress; 2 | 3 | enum VideoQuality { DefaultQuality, LowQuality, MediumQuality, HighestQuality } 4 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_video_compress 2 | description: Generate a new file by compressed video, and provide metadata. Get video thumbnail from a video path, supports JPEG/GIF. To reduce app size not using FFmpeg in IOS. 3 | version: 0.3.7+8 4 | author: Tenkai Ruri 5 | homepage: https://github.com/TenkaiRuri/flutter_video_compress 6 | 7 | environment: 8 | sdk: ">=2.0.0 <3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | image_picker: ^0.6.0+10 18 | video_player: ^0.10.1+3 19 | 20 | 21 | # For information on the generic Dart part of this file, see the 22 | # following page: https://www.dartlang.org/tools/pub/pubspec 23 | 24 | # The following section is specific to Flutter. 25 | flutter: 26 | # This section identifies this Flutter project as a plugin project. 27 | # The androidPackage and pluginClass identifiers should not ordinarily 28 | # be modified. They are used by the tooling to maintain consistency when 29 | # adding or updating assets for this project. 30 | plugin: 31 | androidPackage: com.example.flutter_video_compress 32 | pluginClass: FlutterVideoCompressPlugin 33 | 34 | # To add assets to your plugin package, add an assets section, like this: 35 | # assets: 36 | # - images/a_dot_burr.jpeg 37 | # - images/a_dot_ham.jpeg 38 | # 39 | # For details regarding assets in packages, see 40 | # https://flutter.dev/assets-and-images/#from-packages 41 | # 42 | # An image asset can refer to one or more resolution-specific "variants", see 43 | # https://flutter.dev/assets-and-images/#resolution-aware. 44 | 45 | # To add custom fonts to your plugin package, add a fonts section here, 46 | # in this "flutter" section. Each entry in this list should have a 47 | # "family" key with the font family name, and a "fonts" key with a 48 | # list giving the asset and other descriptors for the font. For 49 | # example: 50 | # fonts: 51 | # - family: Schyler 52 | # fonts: 53 | # - asset: fonts/Schyler-Regular.ttf 54 | # - asset: fonts/Schyler-Italic.ttf 55 | # style: italic 56 | # - family: Trajan Pro 57 | # fonts: 58 | # - asset: fonts/TrajanPro.ttf 59 | # - asset: fonts/TrajanPro_Bold.ttf 60 | # weight: 700 61 | # 62 | # For details regarding fonts in packages, see 63 | # https://flutter.dev/custom-fonts/#from-packages 64 | -------------------------------------------------------------------------------- /test/flutter_video_compress_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | void main() { 5 | const MethodChannel channel = MethodChannel('flutter_video_compress'); 6 | 7 | tearDown(() { 8 | channel.setMockMethodCallHandler(null); 9 | }); 10 | } 11 | --------------------------------------------------------------------------------