react-native-video-trim 3.0.2 → 3.0.4
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.
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
package com.videotrim.utils;
|
|
2
2
|
|
|
3
|
-
import android.annotation.SuppressLint;
|
|
4
|
-
import android.content.Context;
|
|
5
3
|
import android.graphics.Bitmap;
|
|
6
4
|
import android.media.MediaCodec;
|
|
7
5
|
import android.media.MediaMetadataRetriever;
|
|
@@ -9,21 +7,13 @@ import com.facebook.react.bridge.Arguments;
|
|
|
9
7
|
import com.facebook.react.bridge.WritableMap;
|
|
10
8
|
import com.videotrim.enums.ErrorCode;
|
|
11
9
|
import com.videotrim.interfaces.VideoTrimListener;
|
|
12
|
-
|
|
13
|
-
import java.text.SimpleDateFormat;
|
|
14
|
-
import java.util.Date;
|
|
15
|
-
import java.util.TimeZone;
|
|
16
|
-
|
|
17
10
|
import iknow.android.utils.DeviceUtil;
|
|
18
11
|
import iknow.android.utils.UnitConverter;
|
|
19
12
|
import iknow.android.utils.callback.SingleCallback;
|
|
20
13
|
import iknow.android.utils.thread.BackgroundExecutor;
|
|
21
|
-
|
|
22
14
|
import android.media.MediaExtractor;
|
|
23
15
|
import android.media.MediaFormat;
|
|
24
16
|
import android.media.MediaMuxer;
|
|
25
|
-
import android.os.Handler;
|
|
26
|
-
import android.os.Looper;
|
|
27
17
|
import java.io.IOException;
|
|
28
18
|
import java.nio.ByteBuffer;
|
|
29
19
|
|
|
@@ -64,36 +54,25 @@ public class VideoTrimmerUtil {
|
|
|
64
54
|
}
|
|
65
55
|
}
|
|
66
56
|
|
|
67
|
-
public boolean
|
|
57
|
+
public boolean isActive() {
|
|
68
58
|
return !isCancelled;
|
|
69
59
|
}
|
|
70
60
|
}
|
|
71
61
|
|
|
72
62
|
public static TrimSession trim(String inputFile, String outputFile, int videoDuration, long startMs, long endMs, final VideoTrimListener callback, float progressUpdateInterval) {
|
|
73
|
-
// Format creation time
|
|
74
|
-
@SuppressLint("SimpleDateFormat") SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
|
|
75
|
-
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
|
76
|
-
String formattedDateTime = dateFormat.format(new Date());
|
|
77
|
-
|
|
78
63
|
// Start trimming in a background thread
|
|
79
64
|
Thread trimThread = new Thread(() -> {
|
|
80
65
|
MediaExtractor extractor = null;
|
|
81
66
|
MediaMuxer muxer = null;
|
|
82
|
-
// Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
83
67
|
TrimSession session = new TrimSession(Thread.currentThread());
|
|
84
68
|
|
|
85
69
|
try {
|
|
86
70
|
// Get rotation metadata from input file
|
|
87
|
-
MediaMetadataRetriever retriever =
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
String rotationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
|
|
93
|
-
rotation = rotationStr != null ? Integer.parseInt(rotationStr) : 0;
|
|
94
|
-
retriever.release();
|
|
95
|
-
}
|
|
96
|
-
|
|
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
|
+
retriever.release();
|
|
97
76
|
|
|
98
77
|
extractor = new MediaExtractor();
|
|
99
78
|
extractor.setDataSource(inputFile);
|
|
@@ -103,10 +82,45 @@ public class VideoTrimmerUtil {
|
|
|
103
82
|
int[] trackIndices = new int[trackCount];
|
|
104
83
|
long[] trackStartTimes = new long[trackCount];
|
|
105
84
|
boolean[] tracksAdded = new boolean[trackCount];
|
|
106
|
-
|
|
107
|
-
|
|
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
|
+
// Adjust buffer size based on resolution
|
|
100
|
+
if (width > 3840 || height > 2160) { // 8K+
|
|
101
|
+
maxBufferSize = 16 * 1024 * 1024; // 16MB for 8K
|
|
102
|
+
} else if (width > 1920 || height > 1080) { // 4K
|
|
103
|
+
maxBufferSize = 8 * 1024 * 1024; // 8MB for 4K
|
|
104
|
+
} else if (width > 1280 || height > 720) { // 1080p
|
|
105
|
+
maxBufferSize = 4 * 1024 * 1024; // 4MB for 1080p
|
|
106
|
+
} // 720p or lower (or unknown resolution) sticks with BUFFER_SIZE (1MB)
|
|
107
|
+
|
|
108
|
+
// Add tracks with corrected duration
|
|
108
109
|
for (int i = 0; i < trackCount; i++) {
|
|
109
110
|
MediaFormat format = extractor.getTrackFormat(i);
|
|
111
|
+
|
|
112
|
+
// Override with KEY_MAX_INPUT_SIZE if available
|
|
113
|
+
if (format.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
|
|
114
|
+
int maxInputSize = format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
|
|
115
|
+
maxBufferSize = Math.max(maxBufferSize, maxInputSize);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
String mime = format.getString(MediaFormat.KEY_MIME);
|
|
119
|
+
if (mime != null && mime.startsWith("video/")) {
|
|
120
|
+
videoTrackIndex = i;
|
|
121
|
+
}
|
|
122
|
+
// Set the duration for each track to the trimmed duration
|
|
123
|
+
format.setLong(MediaFormat.KEY_DURATION, trimmedDurationUs);
|
|
110
124
|
trackIndices[i] = muxer.addTrack(format);
|
|
111
125
|
tracksAdded[i] = false;
|
|
112
126
|
extractor.selectTrack(i);
|
|
@@ -116,16 +130,15 @@ public class VideoTrimmerUtil {
|
|
|
116
130
|
muxer.setOrientationHint(rotation);
|
|
117
131
|
|
|
118
132
|
// Seek to start time
|
|
119
|
-
long startUs = startMs * 1000; // Convert ms to μs
|
|
120
|
-
long endUs = endMs * 1000;
|
|
121
133
|
extractor.seekTo(startUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
|
|
122
134
|
|
|
123
|
-
ByteBuffer buffer = ByteBuffer.allocate(
|
|
135
|
+
ByteBuffer buffer = ByteBuffer.allocate(maxBufferSize);
|
|
124
136
|
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
|
125
137
|
long lastProgressTime = System.currentTimeMillis();
|
|
138
|
+
boolean videoSampleWritten = false;
|
|
126
139
|
|
|
127
140
|
muxer.start();
|
|
128
|
-
while (session.
|
|
141
|
+
while (session.isActive()) {
|
|
129
142
|
bufferInfo.size = extractor.readSampleData(buffer, 0);
|
|
130
143
|
if (bufferInfo.size < 0) break; // EOS
|
|
131
144
|
|
|
@@ -133,7 +146,6 @@ public class VideoTrimmerUtil {
|
|
|
133
146
|
if (sampleTime > endUs) break;
|
|
134
147
|
|
|
135
148
|
bufferInfo.presentationTimeUs = sampleTime;
|
|
136
|
-
// Map MediaExtractor flags to MediaCodec flags
|
|
137
149
|
int extractorFlags = extractor.getSampleFlags();
|
|
138
150
|
bufferInfo.flags = (extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0;
|
|
139
151
|
int trackIndex = extractor.getSampleTrackIndex();
|
|
@@ -144,10 +156,18 @@ public class VideoTrimmerUtil {
|
|
|
144
156
|
}
|
|
145
157
|
bufferInfo.presentationTimeUs -= trackStartTimes[trackIndex]; // Adjust time to start at 0
|
|
146
158
|
|
|
159
|
+
// Ensure presentation time doesn't exceed trimmed duration
|
|
160
|
+
if (bufferInfo.presentationTimeUs >= trimmedDurationUs) {
|
|
161
|
+
extractor.advance();
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (trackIndex == videoTrackIndex) {
|
|
166
|
+
videoSampleWritten = true;
|
|
167
|
+
}
|
|
147
168
|
muxer.writeSampleData(trackIndices[trackIndex], buffer, bufferInfo);
|
|
148
169
|
extractor.advance();
|
|
149
170
|
|
|
150
|
-
// Progress update (every progressUpdateInterval * 1000 ms)
|
|
151
171
|
long currentTime = System.currentTimeMillis();
|
|
152
172
|
if (currentTime - lastProgressTime >= progressUpdateInterval * 1000) {
|
|
153
173
|
double progress = (double)(sampleTime - startUs) / (endUs - startUs);
|
|
@@ -158,46 +178,44 @@ public class VideoTrimmerUtil {
|
|
|
158
178
|
|
|
159
179
|
callback.onStatistics(statsMap);
|
|
160
180
|
callback.onTrimmingProgress((int)(progress * 100));
|
|
161
|
-
|
|
162
|
-
// mainHandler.post(() -> callback.onStatistics(statsMap));
|
|
163
|
-
// mainHandler.post(() -> callback.onTrimmingProgress((int)(progress * 100)));
|
|
164
|
-
|
|
165
181
|
}
|
|
166
182
|
lastProgressTime = currentTime;
|
|
167
183
|
}
|
|
168
184
|
}
|
|
169
185
|
|
|
170
|
-
if
|
|
171
|
-
|
|
186
|
+
// For static videos: ensure one video frame if none written
|
|
187
|
+
if (!videoSampleWritten && videoTrackIndex != -1) {
|
|
188
|
+
for (int i = 0; i < trackCount; i++) {
|
|
189
|
+
if (i != videoTrackIndex) {
|
|
190
|
+
extractor.unselectTrack(i);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
extractor.selectTrack(videoTrackIndex);
|
|
194
|
+
extractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
|
|
195
|
+
bufferInfo.size = extractor.readSampleData(buffer, 0);
|
|
196
|
+
if (bufferInfo.size >= 0) {
|
|
197
|
+
bufferInfo.presentationTimeUs = 0;
|
|
198
|
+
// Map MediaExtractor flags to MediaCodec flags
|
|
199
|
+
int extractorFlags = extractor.getSampleFlags();
|
|
200
|
+
bufferInfo.flags = (extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0;
|
|
201
|
+
muxer.writeSampleData(trackIndices[videoTrackIndex], buffer, bufferInfo);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
172
204
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
// android.net.Uri uri = android.provider.MediaStore.Files.getContentUri("external");
|
|
176
|
-
// android.content.ContentValues values = new android.content.ContentValues();
|
|
177
|
-
// values.put(android.provider.MediaStore.MediaColumns.DATA, outputFile);
|
|
178
|
-
// values.put(android.provider.MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis() / 1000);
|
|
179
|
-
// values.put(android.provider.MediaStore.MediaColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
|
|
180
|
-
// values.put(android.provider.MediaStore.MediaColumns.DATE_TAKEN, System.currentTimeMillis());
|
|
181
|
-
// contentResolver.insert(uri, values);
|
|
182
|
-
// android.content.ContentValues updateValues = new android.content.ContentValues();
|
|
183
|
-
// updateValues.put(android.provider.MediaStore.MediaColumns.DATE_TAKEN, System.currentTimeMillis());
|
|
184
|
-
// contentResolver.update(uri, updateValues, android.provider.MediaStore.MediaColumns.DATA + "=?", new String[]{outputFile});
|
|
185
|
-
|
|
186
|
-
// mainHandler.post(() -> callback.onFinishTrim(outputFile, startMs, endMs, videoDuration));
|
|
205
|
+
if (session.isActive()) {
|
|
206
|
+
muxer.stop();
|
|
187
207
|
callback.onFinishTrim(outputFile, startMs, endMs, videoDuration);
|
|
188
208
|
} else {
|
|
189
|
-
// mainHandler.post(callback::onCancelTrim);
|
|
190
209
|
callback.onCancelTrim();
|
|
191
210
|
}
|
|
192
211
|
|
|
193
212
|
} catch (IOException e) {
|
|
194
213
|
e.printStackTrace();
|
|
195
|
-
// mainHandler.post(() -> callback.onError("Trimming failed: " + e.getMessage(), ErrorCode.TRIMMING_FAILED));
|
|
196
214
|
callback.onError("Trimming failed: " + e.getMessage(), ErrorCode.TRIMMING_FAILED);
|
|
197
215
|
} finally {
|
|
198
216
|
try {
|
|
199
|
-
if (muxer != null) {
|
|
200
|
-
|
|
217
|
+
if (muxer != null && session.isActive()) {
|
|
218
|
+
muxer.release();
|
|
201
219
|
}
|
|
202
220
|
if (extractor != null) extractor.release();
|
|
203
221
|
} catch (Exception e) {
|