react-native-video-trim 3.0.8 → 3.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -4
- package/android/build.gradle +1 -0
- package/android/src/main/java/com/videotrim/VideoTrimModule.java +17 -7
- package/android/src/main/java/com/videotrim/interfaces/VideoTrimListener.java +1 -0
- package/android/src/main/java/com/videotrim/utils/VideoTrimmerUtil.java +83 -198
- package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.java +7 -7
- package/ios/VideoTrim.swift +483 -394
- package/package.json +1 -1
- package/react-native-video-trim.podspec +2 -0
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
- ✅ Save to Photos, Documents and Share to other apps
|
|
13
13
|
- ✅ Check if file is valid video/audio
|
|
14
14
|
- ✅ File operations: list, clean up, delete specific file
|
|
15
|
+
- ✅ Support React Native New + Old Arch
|
|
15
16
|
|
|
16
17
|
<div align="left">
|
|
17
18
|
<img src="images/document_picker.png" width="300" />
|
|
@@ -21,11 +22,19 @@
|
|
|
21
22
|
## Installation
|
|
22
23
|
|
|
23
24
|
```sh
|
|
24
|
-
|
|
25
|
+
# new arch
|
|
26
|
+
npm install react-native-video-trim react-native-nitro-modules
|
|
27
|
+
|
|
28
|
+
# old arch
|
|
29
|
+
npm install react-native-video-trim@^3.0.0
|
|
25
30
|
|
|
26
31
|
# or with yarn
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
# new arch
|
|
34
|
+
yarn add react-native-video-trim react-native-nitro-modules
|
|
35
|
+
|
|
36
|
+
# old arch
|
|
37
|
+
yarn add react-native-video-trim@^3.0.0
|
|
29
38
|
```
|
|
30
39
|
## For iOS (React Native CLI project)
|
|
31
40
|
Run the following command to setup for iOS:
|
|
@@ -226,7 +235,6 @@ Main method to show Video Editor UI.
|
|
|
226
235
|
- `alertOnFailTitle` (`default = "Error"`)
|
|
227
236
|
- `alertOnFailMessage` (`default = "Fail to load media. Possibly invalid file or no network connection"`)
|
|
228
237
|
- `alertOnFailCloseText` (`default = "Close"`)
|
|
229
|
-
- `progressUpdateInterval` (`default = 0.1`): how fast the trimming progress update interval is, default is emit progress every 100ms (0.1 second)
|
|
230
238
|
|
|
231
239
|
If `saveToPhoto = true`, you must ensure that you have request permission to write to photo/gallery
|
|
232
240
|
- For Android: you need to have `<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />` in AndroidManifest.xml
|
|
@@ -274,7 +282,13 @@ Clean all generated output files in app storage. Return number of successfully d
|
|
|
274
282
|
## deleteFile()
|
|
275
283
|
Delete a file in app storage. Return `true` if success
|
|
276
284
|
|
|
277
|
-
#
|
|
285
|
+
# Callbacks (New arch)
|
|
286
|
+
|
|
287
|
+
## showEditor
|
|
288
|
+
|
|
289
|
+
## closeEditor
|
|
290
|
+
|
|
291
|
+
# Events (Old arch)
|
|
278
292
|
To listen for events you interest, do the following:
|
|
279
293
|
```js
|
|
280
294
|
useEffect(() => {
|
package/android/build.gradle
CHANGED
|
@@ -94,6 +94,7 @@ dependencies {
|
|
|
94
94
|
//noinspection GradleDynamicVersion
|
|
95
95
|
implementation "com.facebook.react:react-native:+"
|
|
96
96
|
implementation 'androidx.recyclerview:recyclerview:1.3.1'
|
|
97
|
+
implementation 'io.github.maitrungduc1410:ffmpeg-kit-min:6.0.0'
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
if (isNewArchitectureEnabled()) {
|
|
@@ -99,7 +99,6 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
99
99
|
private boolean openShareSheetOnFinish = false;
|
|
100
100
|
private boolean isVideoType = true;
|
|
101
101
|
private boolean closeWhenFinish = true;
|
|
102
|
-
private float progressUpdateInterval = 0.1f;
|
|
103
102
|
|
|
104
103
|
private static final int REQUEST_CODE_SAVE_FILE = 1;
|
|
105
104
|
|
|
@@ -195,7 +194,6 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
195
194
|
isVideoType = !config.hasKey("type") || !Objects.equals(config.getString("type"), "audio");
|
|
196
195
|
|
|
197
196
|
closeWhenFinish = !config.hasKey("closeWhenFinish") || config.getBoolean("closeWhenFinish");
|
|
198
|
-
progressUpdateInterval = config.hasKey("progressUpdateInterval") ? (float) config.getDouble("progressUpdateInterval") : 0.1f;
|
|
199
197
|
|
|
200
198
|
Activity activity = getReactApplicationContext().getCurrentActivity();
|
|
201
199
|
|
|
@@ -374,6 +372,11 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
374
372
|
alertDialog.show();
|
|
375
373
|
}
|
|
376
374
|
|
|
375
|
+
@Override
|
|
376
|
+
public void onLog(WritableMap log) {
|
|
377
|
+
sendEvent(getReactApplicationContext(), "onLog", log);
|
|
378
|
+
}
|
|
379
|
+
|
|
377
380
|
@Override
|
|
378
381
|
public void onStatistics(WritableMap statistics) {
|
|
379
382
|
sendEvent(getReactApplicationContext(), "onStatistics", statistics);
|
|
@@ -433,7 +436,9 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
433
436
|
builder.setTitle(cancelTrimmingDialogTitle);
|
|
434
437
|
builder.setCancelable(false);
|
|
435
438
|
builder.setPositiveButton(cancelTrimmingDialogConfirmText, (dialog, which) -> {
|
|
436
|
-
trimmerView
|
|
439
|
+
if (trimmerView != null) {
|
|
440
|
+
trimmerView.onCancelTrimClicked();
|
|
441
|
+
}
|
|
437
442
|
|
|
438
443
|
if (mProgressDialog != null && mProgressDialog.isShowing()) {
|
|
439
444
|
mProgressDialog.dismiss();
|
|
@@ -445,7 +450,9 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
445
450
|
cancelTrimmingConfirmDialog = builder.create();
|
|
446
451
|
cancelTrimmingConfirmDialog.show();
|
|
447
452
|
} else {
|
|
448
|
-
trimmerView
|
|
453
|
+
if (trimmerView != null) {
|
|
454
|
+
trimmerView.onCancelTrimClicked();
|
|
455
|
+
}
|
|
449
456
|
|
|
450
457
|
if (mProgressDialog != null && mProgressDialog.isShowing()) {
|
|
451
458
|
mProgressDialog.dismiss();
|
|
@@ -465,7 +472,10 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
465
472
|
|
|
466
473
|
mProgressDialog.setOnShowListener(dialog -> {
|
|
467
474
|
sendEvent(getReactApplicationContext(), "onStartTrimming", null);
|
|
468
|
-
|
|
475
|
+
|
|
476
|
+
if (trimmerView != null) {
|
|
477
|
+
trimmerView.onSaveClicked();
|
|
478
|
+
}
|
|
469
479
|
});
|
|
470
480
|
|
|
471
481
|
mProgressDialog.show();
|
|
@@ -552,9 +562,9 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
552
562
|
private void isValidFile(String filePath, Promise promise) {
|
|
553
563
|
MediaMetadataUtil.checkFileValidity(filePath, (isValid, fileType, duration) -> {
|
|
554
564
|
if (isValid) {
|
|
555
|
-
|
|
565
|
+
Log.d(TAG, "Valid " + fileType + " file with duration: " + duration + " milliseconds");
|
|
556
566
|
} else {
|
|
557
|
-
|
|
567
|
+
Log.d(TAG, "Invalid file");
|
|
558
568
|
}
|
|
559
569
|
|
|
560
570
|
WritableMap map = Arguments.createMap();
|
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
package com.videotrim.utils;
|
|
2
2
|
|
|
3
|
+
import android.annotation.SuppressLint;
|
|
3
4
|
import android.graphics.Bitmap;
|
|
4
|
-
import android.media.MediaCodec;
|
|
5
5
|
import android.media.MediaMetadataRetriever;
|
|
6
|
+
import android.util.Log;
|
|
7
|
+
|
|
8
|
+
import com.arthenica.ffmpegkit.FFmpegKit;
|
|
9
|
+
import com.arthenica.ffmpegkit.FFmpegSession;
|
|
10
|
+
import com.arthenica.ffmpegkit.ReturnCode;
|
|
11
|
+
import com.arthenica.ffmpegkit.SessionState;
|
|
6
12
|
import com.facebook.react.bridge.Arguments;
|
|
7
13
|
import com.facebook.react.bridge.WritableMap;
|
|
8
14
|
import com.videotrim.enums.ErrorCode;
|
|
9
15
|
import com.videotrim.interfaces.VideoTrimListener;
|
|
16
|
+
|
|
17
|
+
import java.text.SimpleDateFormat;
|
|
18
|
+
import java.util.Date;
|
|
19
|
+
import java.util.TimeZone;
|
|
20
|
+
|
|
10
21
|
import iknow.android.utils.DeviceUtil;
|
|
11
22
|
import iknow.android.utils.UnitConverter;
|
|
12
23
|
import iknow.android.utils.callback.SingleCallback;
|
|
13
24
|
import iknow.android.utils.thread.BackgroundExecutor;
|
|
14
|
-
import android.media.MediaExtractor;
|
|
15
|
-
import android.media.MediaFormat;
|
|
16
|
-
import android.media.MediaMuxer;
|
|
17
|
-
import java.io.IOException;
|
|
18
|
-
import java.nio.ByteBuffer;
|
|
19
25
|
|
|
20
26
|
public class VideoTrimmerUtil {
|
|
21
27
|
|
|
@@ -36,201 +42,80 @@ public class VideoTrimmerUtil {
|
|
|
36
42
|
public static final int THUMB_WIDTH = UnitConverter.dpToPx(25); // x2 for better resolution
|
|
37
43
|
private static final int THUMB_RESOLUTION_RES = 2; // double thumb resolution for better quality
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
public static FFmpegSession trim(String inputFile, String outputFile, int videoDuration, long startMs, long endMs, final VideoTrimListener callback) {
|
|
46
|
+
// Get the current date and time
|
|
47
|
+
Date currentDate = new Date();
|
|
48
|
+
|
|
49
|
+
// Create a SimpleDateFormat object with the desired format
|
|
50
|
+
@SuppressLint("SimpleDateFormat") SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
|
|
51
|
+
|
|
52
|
+
// Set the timezone to UTC
|
|
53
|
+
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
|
54
|
+
// Format the current date and time
|
|
55
|
+
String formattedDateTime = dateFormat.format(currentDate);
|
|
56
|
+
|
|
57
|
+
String[] cmds = {
|
|
58
|
+
"-ss",
|
|
59
|
+
startMs + "ms",
|
|
60
|
+
"-to",
|
|
61
|
+
endMs + "ms",
|
|
62
|
+
"-i",
|
|
63
|
+
inputFile,
|
|
64
|
+
"-c",
|
|
65
|
+
"copy",
|
|
66
|
+
"-metadata",
|
|
67
|
+
"creation_time=" + formattedDateTime,
|
|
68
|
+
outputFile
|
|
69
|
+
};
|
|
70
|
+
Log.d(TAG,"Command111: " + String.join(",", cmds));
|
|
71
|
+
|
|
72
|
+
FFmpegSession s = FFmpegKit.execute("-protocols");
|
|
73
|
+
Log.d(TAG, "1111getOutput: " + s.getOutput());
|
|
74
|
+
Log.d(TAG, "1111getAllLogs: " + s.getAllLogs());
|
|
75
|
+
|
|
76
|
+
return FFmpegKit.executeWithArgumentsAsync(cmds, session -> {
|
|
77
|
+
SessionState state = session.getState();
|
|
78
|
+
ReturnCode returnCode = session.getReturnCode();
|
|
79
|
+
if (ReturnCode.isSuccess(session.getReturnCode())) {
|
|
80
|
+
// SUCCESS
|
|
81
|
+
callback.onFinishTrim(outputFile, startMs, endMs, videoDuration);
|
|
82
|
+
} else if (ReturnCode.isCancel(session.getReturnCode())) {
|
|
83
|
+
// CANCEL
|
|
84
|
+
callback.onCancelTrim();
|
|
85
|
+
} else {
|
|
86
|
+
// FAILURE
|
|
87
|
+
String errorMessage = String.format("Command failed with state %s and rc %s.%s", state, returnCode, session.getFailStackTrace());
|
|
88
|
+
callback.onError(errorMessage, ErrorCode.TRIMMING_FAILED);
|
|
54
89
|
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// Get rotation metadata from input file
|
|
71
|
-
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
|
72
|
-
retriever.setDataSource(inputFile);
|
|
73
|
-
String rotationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
|
|
74
|
-
int rotation = rotationStr != null ? Integer.parseInt(rotationStr) : 0;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
extractor = new MediaExtractor();
|
|
78
|
-
extractor.setDataSource(inputFile);
|
|
79
|
-
|
|
80
|
-
int trackCount = extractor.getTrackCount();
|
|
81
|
-
muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
|
|
82
|
-
int[] trackIndices = new int[trackCount];
|
|
83
|
-
long[] trackStartTimes = new long[trackCount];
|
|
84
|
-
boolean[] tracksAdded = new boolean[trackCount];
|
|
85
|
-
int videoTrackIndex = -1;
|
|
86
|
-
|
|
87
|
-
// Calculate trimmed duration
|
|
88
|
-
long startUs = startMs * 1000; // e.g., 5s = 5000000us
|
|
89
|
-
long endUs = endMs * 1000; // e.g., 9s = 9000000us
|
|
90
|
-
long trimmedDurationUs = endUs - startUs; // e.g., 4s = 4000000us
|
|
91
|
-
|
|
92
|
-
// Determine max buffer size from video format and resolution
|
|
93
|
-
int maxBufferSize = BUFFER_SIZE; // Default 1MB
|
|
94
|
-
String widthStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
|
|
95
|
-
String heightStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
|
|
96
|
-
int width = widthStr != null ? Integer.parseInt(widthStr) : 0; // Default to unknown
|
|
97
|
-
int height = heightStr != null ? Integer.parseInt(heightStr) : 0;
|
|
98
|
-
|
|
99
|
-
// IMPORTANT: after this line, the retriever is no longer needed
|
|
100
|
-
retriever.release();
|
|
101
|
-
|
|
102
|
-
// Adjust buffer size based on resolution
|
|
103
|
-
if (width > 3840 || height > 2160) { // 8K+
|
|
104
|
-
maxBufferSize = 16 * 1024 * 1024; // 16MB for 8K
|
|
105
|
-
} else if (width > 1920 || height > 1080) { // 4K
|
|
106
|
-
maxBufferSize = 8 * 1024 * 1024; // 8MB for 4K
|
|
107
|
-
} else if (width > 1280 || height > 720) { // 1080p
|
|
108
|
-
maxBufferSize = 4 * 1024 * 1024; // 4MB for 1080p
|
|
109
|
-
} // 720p or lower (or unknown resolution) sticks with BUFFER_SIZE (1MB)
|
|
110
|
-
|
|
111
|
-
// Add tracks with corrected duration
|
|
112
|
-
for (int i = 0; i < trackCount; i++) {
|
|
113
|
-
MediaFormat format = extractor.getTrackFormat(i);
|
|
114
|
-
|
|
115
|
-
// Override with KEY_MAX_INPUT_SIZE if available
|
|
116
|
-
if (format.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
|
|
117
|
-
int maxInputSize = format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
|
|
118
|
-
maxBufferSize = Math.max(maxBufferSize, maxInputSize);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
String mime = format.getString(MediaFormat.KEY_MIME);
|
|
122
|
-
if (mime != null && mime.startsWith("video/")) {
|
|
123
|
-
videoTrackIndex = i;
|
|
124
|
-
}
|
|
125
|
-
// Set the duration for each track to the trimmed duration
|
|
126
|
-
format.setLong(MediaFormat.KEY_DURATION, trimmedDurationUs);
|
|
127
|
-
trackIndices[i] = muxer.addTrack(format);
|
|
128
|
-
tracksAdded[i] = false;
|
|
129
|
-
extractor.selectTrack(i);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Set rotation metadata on muxer
|
|
133
|
-
muxer.setOrientationHint(rotation);
|
|
134
|
-
|
|
135
|
-
// Seek to start time
|
|
136
|
-
extractor.seekTo(startUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
|
|
137
|
-
|
|
138
|
-
ByteBuffer buffer = ByteBuffer.allocate(maxBufferSize);
|
|
139
|
-
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
|
140
|
-
long lastProgressTime = System.currentTimeMillis();
|
|
141
|
-
boolean videoSampleWritten = false;
|
|
142
|
-
|
|
143
|
-
muxer.start();
|
|
144
|
-
while (session.isActive()) {
|
|
145
|
-
bufferInfo.size = extractor.readSampleData(buffer, 0);
|
|
146
|
-
if (bufferInfo.size < 0) break; // EOS
|
|
147
|
-
|
|
148
|
-
long sampleTime = extractor.getSampleTime();
|
|
149
|
-
if (sampleTime > endUs) break;
|
|
150
|
-
|
|
151
|
-
bufferInfo.presentationTimeUs = sampleTime;
|
|
152
|
-
int extractorFlags = extractor.getSampleFlags();
|
|
153
|
-
bufferInfo.flags = (extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0;
|
|
154
|
-
int trackIndex = extractor.getSampleTrackIndex();
|
|
155
|
-
|
|
156
|
-
if (!tracksAdded[trackIndex]) {
|
|
157
|
-
trackStartTimes[trackIndex] = sampleTime;
|
|
158
|
-
tracksAdded[trackIndex] = true;
|
|
159
|
-
}
|
|
160
|
-
bufferInfo.presentationTimeUs -= trackStartTimes[trackIndex]; // Adjust time to start at 0
|
|
161
|
-
|
|
162
|
-
// Ensure presentation time doesn't exceed trimmed duration
|
|
163
|
-
if (bufferInfo.presentationTimeUs >= trimmedDurationUs) {
|
|
164
|
-
extractor.advance();
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (trackIndex == videoTrackIndex) {
|
|
169
|
-
videoSampleWritten = true;
|
|
170
|
-
}
|
|
171
|
-
muxer.writeSampleData(trackIndices[trackIndex], buffer, bufferInfo);
|
|
172
|
-
extractor.advance();
|
|
173
|
-
|
|
174
|
-
long currentTime = System.currentTimeMillis();
|
|
175
|
-
if (currentTime - lastProgressTime >= progressUpdateInterval * 1000) {
|
|
176
|
-
double progress = (double)(sampleTime - startUs) / (endUs - startUs);
|
|
177
|
-
if (progress > 0 && progress <= 1) {
|
|
178
|
-
WritableMap statsMap = Arguments.createMap();
|
|
179
|
-
statsMap.putDouble("time", progress * videoDuration);
|
|
180
|
-
statsMap.putDouble("progress", progress); // Percentage
|
|
181
|
-
|
|
182
|
-
callback.onStatistics(statsMap);
|
|
183
|
-
callback.onTrimmingProgress((int)(progress * 100));
|
|
184
|
-
}
|
|
185
|
-
lastProgressTime = currentTime;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// For static videos: ensure one video frame if none written
|
|
190
|
-
if (!videoSampleWritten && videoTrackIndex != -1) {
|
|
191
|
-
for (int i = 0; i < trackCount; i++) {
|
|
192
|
-
if (i != videoTrackIndex) {
|
|
193
|
-
extractor.unselectTrack(i);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
extractor.selectTrack(videoTrackIndex);
|
|
197
|
-
extractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
|
|
198
|
-
bufferInfo.size = extractor.readSampleData(buffer, 0);
|
|
199
|
-
if (bufferInfo.size >= 0) {
|
|
200
|
-
bufferInfo.presentationTimeUs = 0;
|
|
201
|
-
// Map MediaExtractor flags to MediaCodec flags
|
|
202
|
-
int extractorFlags = extractor.getSampleFlags();
|
|
203
|
-
bufferInfo.flags = (extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0;
|
|
204
|
-
muxer.writeSampleData(trackIndices[videoTrackIndex], buffer, bufferInfo);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (session.isActive()) {
|
|
209
|
-
muxer.stop();
|
|
210
|
-
callback.onFinishTrim(outputFile, startMs, endMs, videoDuration);
|
|
211
|
-
} else {
|
|
212
|
-
callback.onCancelTrim();
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
} catch (IOException e) {
|
|
216
|
-
e.printStackTrace();
|
|
217
|
-
callback.onError("Trimming failed: " + e.getMessage(), ErrorCode.TRIMMING_FAILED);
|
|
218
|
-
} finally {
|
|
219
|
-
try {
|
|
220
|
-
if (muxer != null && session.isActive()) {
|
|
221
|
-
muxer.release();
|
|
222
|
-
}
|
|
223
|
-
if (extractor != null) extractor.release();
|
|
224
|
-
} catch (Exception e) {
|
|
225
|
-
e.printStackTrace();
|
|
226
|
-
System.err.println("Error releasing resources: " + e.getMessage());
|
|
227
|
-
}
|
|
90
|
+
}, log -> {
|
|
91
|
+
Log.d(TAG, "FFmpeg process started with log " + log.getMessage());
|
|
92
|
+
|
|
93
|
+
WritableMap map = Arguments.createMap();
|
|
94
|
+
map.putInt("level", log.getLevel().getValue());
|
|
95
|
+
map.putString("message", log.getMessage());
|
|
96
|
+
map.putDouble("sessionId", log.getSessionId());
|
|
97
|
+
map.putString("logStr", log.toString());
|
|
98
|
+
callback.onLog(map);
|
|
99
|
+
}, statistics -> {
|
|
100
|
+
int timeInMilliseconds = (int) statistics.getTime();
|
|
101
|
+
if (timeInMilliseconds > 0) {
|
|
102
|
+
int completePercentage =
|
|
103
|
+
(timeInMilliseconds * 100) / videoDuration;
|
|
104
|
+
callback.onTrimmingProgress(Math.min(Math.max(completePercentage, 0), 100));
|
|
228
105
|
}
|
|
229
|
-
});
|
|
230
106
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
107
|
+
WritableMap map = Arguments.createMap();
|
|
108
|
+
map.putDouble("sessionId", statistics.getSessionId());
|
|
109
|
+
map.putInt("videoFrameNumber", statistics.getVideoFrameNumber());
|
|
110
|
+
map.putDouble("videoFps", statistics.getVideoFps());
|
|
111
|
+
map.putDouble("videoQuality", statistics.getVideoQuality());
|
|
112
|
+
map.putDouble("size", statistics.getSize());
|
|
113
|
+
map.putDouble("time", statistics.getTime());
|
|
114
|
+
map.putDouble("bitrate", statistics.getBitrate());
|
|
115
|
+
map.putDouble("speed", statistics.getSpeed());
|
|
116
|
+
map.putString("statisticsStr", statistics.toString());
|
|
117
|
+
callback.onStatistics(map);
|
|
118
|
+
});
|
|
234
119
|
}
|
|
235
120
|
|
|
236
121
|
public static void shootVideoThumbInBackground(final MediaMetadataRetriever mediaMetadataRetriever, final int totalThumbsCount, final long startPosition,
|
|
@@ -48,6 +48,7 @@ import java.util.Locale;
|
|
|
48
48
|
import iknow.android.utils.DeviceUtil;
|
|
49
49
|
import iknow.android.utils.thread.BackgroundExecutor;
|
|
50
50
|
import iknow.android.utils.thread.UiThreadExecutor;
|
|
51
|
+
import com.arthenica.ffmpegkit.FFmpegSession;
|
|
51
52
|
|
|
52
53
|
public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
53
54
|
|
|
@@ -108,7 +109,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
108
109
|
private long jumpToPositionOnLoad = 0;
|
|
109
110
|
private FrameLayout headerView;
|
|
110
111
|
private TextView headerText;
|
|
111
|
-
private
|
|
112
|
+
private FFmpegSession ffmpegSession;
|
|
112
113
|
private boolean alertOnFailToLoad = true;
|
|
113
114
|
private String alertOnFailTitle = "Error";
|
|
114
115
|
private String alertOnFailMessage = "Fail to load media. Possibly invalid file or no network connection";
|
|
@@ -365,22 +366,21 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
365
366
|
setHandleTouchListener(trailingHandle, false);
|
|
366
367
|
}
|
|
367
368
|
|
|
368
|
-
public void onSaveClicked(
|
|
369
|
+
public void onSaveClicked() {
|
|
369
370
|
onMediaPause();
|
|
370
|
-
|
|
371
|
+
ffmpegSession = VideoTrimmerUtil.trim(
|
|
371
372
|
mSourceUri.toString(),
|
|
372
373
|
StorageUtil.getOutputPath(mContext, mOutputExt),
|
|
373
374
|
mDuration,
|
|
374
375
|
startTime,
|
|
375
376
|
endTime,
|
|
376
|
-
mOnTrimVideoListener
|
|
377
|
-
progressUpdateInterval
|
|
377
|
+
mOnTrimVideoListener
|
|
378
378
|
);
|
|
379
379
|
}
|
|
380
380
|
|
|
381
381
|
public void onCancelTrimClicked() {
|
|
382
|
-
if (
|
|
383
|
-
|
|
382
|
+
if (ffmpegSession != null) {
|
|
383
|
+
ffmpegSession.cancel();
|
|
384
384
|
} else {
|
|
385
385
|
mOnTrimVideoListener.onCancelTrim();
|
|
386
386
|
}
|