Skip to content

Commit 6f6fb2d

Browse files
committed
feat: add video compression like whatsapp by compressionMethod for android
1 parent 84058b8 commit 6f6fb2d

File tree

7 files changed

+288
-79
lines changed

7 files changed

+288
-79
lines changed

README.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ const result = await Image.compress('file://path_of_file/image.jpg', {
6868
});
6969
```
7070

71-
[Here is this package comparison of compression with WhatsApp](https://docs.google.com/spreadsheets/d/13TsnC1c7NOC9aCjzN6wkKurJQPeGRNwDhWsQOkXQskU/edit?usp=sharing)
71+
[Here is this package comparison of images compression with WhatsApp](https://docs.google.com/spreadsheets/d/13TsnC1c7NOC9aCjzN6wkKurJQPeGRNwDhWsQOkXQskU/edit?usp=sharing)
7272

7373
##### For manual Compression
7474

@@ -94,6 +94,30 @@ const result = await Audio.compress(
9494

9595
### Video
9696

97+
##### For Like Whatsapp Video Compression
98+
99+
```js
100+
import { Video } from 'react-native-compressor';
101+
102+
const result = await Video.compress(
103+
'file://path_of_file/BigBuckBunny.mp4',
104+
{
105+
compressionMethod: 'auto',
106+
},
107+
(progress) => {
108+
if (backgroundMode) {
109+
console.log('Compression Progress: ', progress);
110+
} else {
111+
setCompressingProgress(progress);
112+
}
113+
}
114+
);
115+
```
116+
117+
[Here is this package comparison of video compression with WhatsApp](https://docs.google.com/spreadsheets/d/13TsnC1c7NOC9aCjzN6wkKurJQPeGRNwDhWsQOkXQskU/edit#gid=1055406534)
118+
119+
##### For manual Compression
120+
97121
```js
98122
import { Video } from 'react-native-compressor';
99123

@@ -164,8 +188,16 @@ const result = await Video.compress(
164188

165189
### videoCompresssionType
166190

191+
- ###### `compressionMethod: compressionMethod` (default: "manual")
192+
193+
if you want to compress videos like **whatsapp** then make this prop `auto`. Can be either `manual` or `auto`, defines the Compression Method.
194+
195+
- ###### `maxSize: number` (default: 640)
196+
197+
The maximum size can be height in case of portrait video or can be width in case of landscape video.
198+
167199
- ###### `bitrate: string`
168-
bitrate of video which reduce or increase video size.
200+
bitrate of video which reduce or increase video size. if compressionMethod will auto then this prop will not work
169201

170202
## Contributing
171203

android/src/main/java/com/reactnativecompressor/Image/ImageCompressor.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
public class ImageCompressor {
2727

28-
2928
public static String getRNFileUrl(String filePath) {
3029
File returnAbleFile= new File(filePath);
3130
try {
@@ -134,7 +133,7 @@ public static String autoCompressImage(String imagePath,ImageCompressorOptions c
134133
String outputExtension=compressorOptions.output.toString();
135134
int quality= (int) (compressorOptions.quality*100);
136135
float autoCompressMaxHeight = compressorOptions.maxHeight;
137-
float audoCompressMaxWidth = compressorOptions.maxWidth;
136+
float autoCompressMaxWidth = compressorOptions.maxWidth;
138137

139138
Uri uri= Uri.parse(imagePath);
140139
imagePath = uri.getPath();
@@ -148,20 +147,20 @@ public static String autoCompressImage(String imagePath,ImageCompressorOptions c
148147
int actualWidth = options.outWidth;
149148

150149
float imgRatio = (float) actualWidth / (float) actualHeight;
151-
float maxRatio = audoCompressMaxWidth / autoCompressMaxHeight;
150+
float maxRatio = autoCompressMaxWidth / autoCompressMaxHeight;
152151

153-
if (actualHeight > autoCompressMaxHeight || actualWidth > audoCompressMaxWidth) {
152+
if (actualHeight > autoCompressMaxHeight || actualWidth > autoCompressMaxWidth) {
154153
if (imgRatio < maxRatio) {
155154
imgRatio = autoCompressMaxHeight / actualHeight;
156155
actualWidth = (int) (imgRatio * actualWidth);
157156
actualHeight = (int) autoCompressMaxHeight;
158157
} else if (imgRatio > maxRatio) {
159-
imgRatio = audoCompressMaxWidth / actualWidth;
158+
imgRatio = autoCompressMaxWidth / actualWidth;
160159
actualHeight = (int) (imgRatio * actualHeight);
161-
actualWidth = (int) audoCompressMaxWidth;
160+
actualWidth = (int) autoCompressMaxWidth;
162161
} else {
163162
actualHeight = (int) autoCompressMaxHeight;
164-
actualWidth = (int) audoCompressMaxWidth;
163+
actualWidth = (int) autoCompressMaxWidth;
165164

166165
}
167166
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package com.reactnativecompressor.Video.AutoVideoCompression;
2+
3+
import android.media.MediaCodecInfo;
4+
import android.media.MediaMetadataRetriever;
5+
import android.media.session.MediaController;
6+
import android.net.Uri;
7+
import android.os.Build;
8+
9+
import androidx.annotation.Nullable;
10+
11+
import com.facebook.react.bridge.Arguments;
12+
import com.facebook.react.bridge.Promise;
13+
import com.facebook.react.bridge.ReactApplicationContext;
14+
import com.facebook.react.bridge.ReactContext;
15+
import com.facebook.react.bridge.WritableMap;
16+
import com.facebook.react.modules.core.DeviceEventManagerModule;
17+
import com.reactnativecompressor.Video.VideoCompressorHelper;
18+
import com.zolad.videoslimmer.VideoSlimmer;
19+
20+
import java.io.File;
21+
22+
import static com.reactnativecompressor.Utils.Utils.generateCacheFilePath;
23+
24+
public class AutoVideoCompression {
25+
static int videoCompressionThreshold=10;
26+
static int currentVideoCompression=0;
27+
28+
public static void createCompressionSettings(String fileUrl,VideoCompressorHelper options,Promise promise, ReactApplicationContext reactContext) {
29+
float maxSize = options.maxSize;;
30+
try{
31+
Uri uri= Uri.parse(fileUrl);
32+
String srcPath = uri.getPath();
33+
MediaMetadataRetriever metaRetriever = new MediaMetadataRetriever();
34+
metaRetriever.setDataSource(srcPath);
35+
File file=new File(srcPath);
36+
float sizeInBytes = file.length();
37+
float sizeInMb = sizeInBytes / (1024 * 1024);
38+
if(sizeInMb>16)
39+
{
40+
String destinationPath = generateCacheFilePath("mp4", reactContext);
41+
int actualHeight =Integer.parseInt(metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
42+
int actualWidth = Integer.parseInt(metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
43+
int bitrate = Integer.parseInt(metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE));
44+
45+
float scale = actualWidth > actualHeight ? maxSize / actualWidth : maxSize / actualHeight;
46+
int resultWidth = Math.round(actualWidth * scale / 2) * 2;
47+
int resultHeight = Math.round(actualHeight * scale / 2) * 2;
48+
49+
float videoBitRate = makeVideoBitrate(
50+
actualHeight, actualWidth,
51+
bitrate,
52+
resultHeight, resultWidth
53+
);
54+
55+
VideoSlimmer.convertVideo(srcPath, destinationPath, resultWidth, resultHeight, (int) videoBitRate, new VideoSlimmer.ProgressListener() {
56+
@Override
57+
public void onStart() {
58+
//convert start
59+
}
60+
@Override
61+
public void onFinish(boolean result) {
62+
//convert finish,result(true is success,false is fail)
63+
promise.resolve("file:/"+destinationPath);
64+
}
65+
@Override
66+
public void onProgress(float percent) {
67+
int roundProgress=Math.round(percent);
68+
if(roundProgress%videoCompressionThreshold==0&&roundProgress>currentVideoCompression) {
69+
WritableMap params = Arguments.createMap();
70+
WritableMap data = Arguments.createMap();
71+
params.putString("uuid", options.uuid);
72+
data.putDouble("progress", percent / 100);
73+
params.putMap("data", data);
74+
sendEvent(reactContext, "videoCompressProgress", params);
75+
currentVideoCompression=roundProgress;
76+
}
77+
}
78+
});
79+
80+
}
81+
else
82+
{
83+
promise.resolve(fileUrl);
84+
}
85+
} catch (Exception ex) {
86+
promise.reject(ex);
87+
}
88+
finally {
89+
currentVideoCompression=0;
90+
}
91+
}
92+
93+
public static int makeVideoBitrate(int originalHeight, int originalWidth, int originalBitrate, int height, int width) {
94+
float compressFactor = 0.8f;
95+
float minCompressFactor = 0.8f;
96+
int maxBitrate = 1669_000;
97+
98+
int remeasuredBitrate = (int) (originalBitrate / (Math.min(originalHeight / (float) (height), originalWidth / (float) (width))));
99+
remeasuredBitrate *= compressFactor;
100+
int minBitrate = (int) (getVideoBitrateWithFactor(minCompressFactor) / (1280f * 720f / (width * height)));
101+
if (originalBitrate < minBitrate) {
102+
return remeasuredBitrate;
103+
}
104+
if (remeasuredBitrate > maxBitrate) {
105+
return maxBitrate;
106+
}
107+
return Math.max(remeasuredBitrate, minBitrate);
108+
}
109+
private static int getVideoBitrateWithFactor(float f) {
110+
return (int) (f * 2000f * 1000f * 1.13f);
111+
}
112+
113+
private static void sendEvent(ReactContext reactContext,
114+
String eventName,
115+
@Nullable WritableMap params) {
116+
reactContext
117+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
118+
.emit(eventName, params);
119+
}
120+
}

android/src/main/java/com/reactnativecompressor/Video/VideoCompressorHelper.java

Lines changed: 106 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,31 @@
11
package com.reactnativecompressor.Video;
22

33
import android.annotation.SuppressLint;
4+
import android.media.MediaMetadataRetriever;
5+
import android.net.Uri;
46
import android.os.Handler;
57
import android.os.PowerManager;
68

79
import androidx.annotation.Nullable;
810

11+
import com.facebook.react.bridge.Arguments;
912
import com.facebook.react.bridge.LifecycleEventListener;
1013
import com.facebook.react.bridge.Promise;
1114
import com.facebook.react.bridge.ReactApplicationContext;
1215
import com.facebook.react.bridge.ReactContext;
1316
import com.facebook.react.bridge.ReadableMap;
1417
import com.facebook.react.bridge.ReadableMapKeySetIterator;
18+
import com.facebook.react.bridge.WritableMap;
1519
import com.facebook.react.modules.core.DeviceEventManagerModule;
20+
import com.reactnativecompressor.Image.utils.ImageCompressorOptions;
1621
import com.reactnativecompressor.Utils.FileUplaoder.FileUploader;
22+
import com.reactnativecompressor.Video.AutoVideoCompression.AutoVideoCompression;
23+
import com.zolad.videoslimmer.VideoSlimmer;
1724

1825
import java.util.UUID;
1926

27+
import static com.reactnativecompressor.Utils.Utils.generateCacheFilePath;
28+
2029
public class VideoCompressorHelper {
2130
private static ReactApplicationContext _reactContext;
2231
private static String backgroundId;
@@ -85,6 +94,14 @@ public static String video_deactivateBackgroundTask_helper(ReadableMap options,R
8594

8695
return "";
8796
}
97+
public enum CompressionMethod {
98+
auto, manual
99+
}
100+
101+
public VideoCompressorHelper.CompressionMethod compressionMethod = VideoCompressorHelper.CompressionMethod.manual;
102+
public float bitrate = 0;
103+
public String uuid = "";
104+
public float maxSize = 640.0f;
88105

89106
public static VideoCompressorHelper fromMap(ReadableMap map) {
90107
final VideoCompressorHelper options = new VideoCompressorHelper();
@@ -94,10 +111,12 @@ public static VideoCompressorHelper fromMap(ReadableMap map) {
94111
final String key = iterator.nextKey();
95112

96113
switch (key) {
97-
case "bitrate":
98-
options.bitrate = (float) map.getDouble(key);
114+
case "compressionMethod":
115+
options.compressionMethod = VideoCompressorHelper.CompressionMethod.valueOf(map.getString(key));
116+
break;
117+
case "maxSize":
118+
options.maxSize = (float) map.getDouble(key);
99119
break;
100-
101120
case "uuid":
102121
options.uuid = map.getString(key);
103122
break;
@@ -106,6 +125,87 @@ public static VideoCompressorHelper fromMap(ReadableMap map) {
106125
}
107126
return options;
108127
}
109-
public float bitrate = 0;
110-
public String uuid = "";
111-
}
128+
129+
private static void sendEvent(ReactContext reactContext,
130+
String eventName,
131+
@Nullable WritableMap params) {
132+
reactContext
133+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
134+
.emit(eventName, params);
135+
}
136+
static int videoCompressionThreshold=10;
137+
static int currentVideoCompression=0;
138+
public static void VideoCompressManual(String fileUrl,VideoCompressorHelper options,Promise promise, ReactApplicationContext reactContext) {
139+
try{
140+
Uri uri= Uri.parse(fileUrl);
141+
String srcPath = uri.getPath();
142+
String destinationPath = generateCacheFilePath("mp4", reactContext);
143+
MediaMetadataRetriever metaRetriever = new MediaMetadataRetriever();
144+
metaRetriever.setDataSource(srcPath);
145+
int height =Integer.parseInt(metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
146+
int width = Integer.parseInt(metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
147+
int bitrate=Integer.parseInt(metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE));
148+
149+
boolean isPortrait = height > width;
150+
int maxSize = 1920;
151+
if(isPortrait && height > maxSize){
152+
width = (int) (((float)maxSize/height)*width);
153+
height = maxSize;
154+
}else if(width > maxSize){
155+
height = (int) (((float)maxSize/width)*height);
156+
width = maxSize;
157+
}
158+
else
159+
{
160+
if(options.bitrate==0) {
161+
options.bitrate = (int) (bitrate * 0.8);
162+
}
163+
}
164+
float videoBitRate = (options.bitrate>0)?options.bitrate: (float) (height * width * 1.5);
165+
166+
VideoSlimmer.convertVideo(srcPath, destinationPath, width, height, (int) videoBitRate, new VideoSlimmer.ProgressListener() {
167+
168+
169+
@Override
170+
public void onStart() {
171+
//convert start
172+
173+
}
174+
175+
@Override
176+
public void onFinish(boolean result) {
177+
//convert finish,result(true is success,false is fail)
178+
promise.resolve("file:/"+destinationPath);
179+
}
180+
181+
182+
@Override
183+
public void onProgress(float percent) {
184+
int roundProgress=Math.round(percent);
185+
if(roundProgress%videoCompressionThreshold==0&&roundProgress>currentVideoCompression) {
186+
WritableMap params = Arguments.createMap();
187+
WritableMap data = Arguments.createMap();
188+
params.putString("uuid", options.uuid);
189+
data.putDouble("progress", percent / 100);
190+
params.putMap("data", data);
191+
sendEvent(reactContext, "videoCompressProgress", params);
192+
currentVideoCompression=roundProgress;
193+
}
194+
}
195+
});
196+
197+
} catch (Exception ex) {
198+
promise.reject(ex);
199+
}
200+
finally {
201+
currentVideoCompression=0;
202+
}
203+
}
204+
205+
206+
public static void VideoCompressAuto(String fileUrl,VideoCompressorHelper options,Promise promise, ReactApplicationContext reactContext) {
207+
AutoVideoCompression.createCompressionSettings(fileUrl,options,promise,reactContext);
208+
}
209+
210+
211+
}

0 commit comments

Comments
 (0)