react-native-video-trim 3.0.1 → 3.0.3
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,26 +54,21 @@ 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
|
-
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
|
-
|
|
62
|
+
public static TrimSession trim(String inputFile, String outputFile, int videoDuration, long startMs, long endMs, final VideoTrimListener callback, float progressUpdateInterval) {
|
|
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
|
|
71
|
+
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
|
87
72
|
retriever.setDataSource(inputFile);
|
|
88
73
|
String rotationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
|
|
89
74
|
int rotation = rotationStr != null ? Integer.parseInt(rotationStr) : 0;
|
|
@@ -97,10 +82,22 @@ public class VideoTrimmerUtil {
|
|
|
97
82
|
int[] trackIndices = new int[trackCount];
|
|
98
83
|
long[] trackStartTimes = new long[trackCount];
|
|
99
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
|
|
100
91
|
|
|
101
|
-
//
|
|
92
|
+
// Add tracks with corrected duration
|
|
102
93
|
for (int i = 0; i < trackCount; i++) {
|
|
103
94
|
MediaFormat format = extractor.getTrackFormat(i);
|
|
95
|
+
String mime = format.getString(MediaFormat.KEY_MIME);
|
|
96
|
+
if (mime.startsWith("video/")) {
|
|
97
|
+
videoTrackIndex = i;
|
|
98
|
+
}
|
|
99
|
+
// Set the duration for each track to the trimmed duration
|
|
100
|
+
format.setLong(MediaFormat.KEY_DURATION, trimmedDurationUs);
|
|
104
101
|
trackIndices[i] = muxer.addTrack(format);
|
|
105
102
|
tracksAdded[i] = false;
|
|
106
103
|
extractor.selectTrack(i);
|
|
@@ -110,16 +107,15 @@ public class VideoTrimmerUtil {
|
|
|
110
107
|
muxer.setOrientationHint(rotation);
|
|
111
108
|
|
|
112
109
|
// Seek to start time
|
|
113
|
-
long startUs = startMs * 1000; // Convert ms to μs
|
|
114
|
-
long endUs = endMs * 1000;
|
|
115
110
|
extractor.seekTo(startUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
|
|
116
111
|
|
|
117
112
|
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
|
|
118
113
|
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
|
119
114
|
long lastProgressTime = System.currentTimeMillis();
|
|
115
|
+
boolean videoSampleWritten = false;
|
|
120
116
|
|
|
121
117
|
muxer.start();
|
|
122
|
-
while (session.
|
|
118
|
+
while (session.isActive()) {
|
|
123
119
|
bufferInfo.size = extractor.readSampleData(buffer, 0);
|
|
124
120
|
if (bufferInfo.size < 0) break; // EOS
|
|
125
121
|
|
|
@@ -127,7 +123,6 @@ public class VideoTrimmerUtil {
|
|
|
127
123
|
if (sampleTime > endUs) break;
|
|
128
124
|
|
|
129
125
|
bufferInfo.presentationTimeUs = sampleTime;
|
|
130
|
-
// Map MediaExtractor flags to MediaCodec flags
|
|
131
126
|
int extractorFlags = extractor.getSampleFlags();
|
|
132
127
|
bufferInfo.flags = (extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0;
|
|
133
128
|
int trackIndex = extractor.getSampleTrackIndex();
|
|
@@ -138,10 +133,18 @@ public class VideoTrimmerUtil {
|
|
|
138
133
|
}
|
|
139
134
|
bufferInfo.presentationTimeUs -= trackStartTimes[trackIndex]; // Adjust time to start at 0
|
|
140
135
|
|
|
136
|
+
// Ensure presentation time doesn't exceed trimmed duration
|
|
137
|
+
if (bufferInfo.presentationTimeUs >= trimmedDurationUs) {
|
|
138
|
+
extractor.advance();
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (trackIndex == videoTrackIndex) {
|
|
143
|
+
videoSampleWritten = true;
|
|
144
|
+
}
|
|
141
145
|
muxer.writeSampleData(trackIndices[trackIndex], buffer, bufferInfo);
|
|
142
146
|
extractor.advance();
|
|
143
147
|
|
|
144
|
-
// Progress update (every progressUpdateInterval * 1000 ms)
|
|
145
148
|
long currentTime = System.currentTimeMillis();
|
|
146
149
|
if (currentTime - lastProgressTime >= progressUpdateInterval * 1000) {
|
|
147
150
|
double progress = (double)(sampleTime - startUs) / (endUs - startUs);
|
|
@@ -152,46 +155,44 @@ public class VideoTrimmerUtil {
|
|
|
152
155
|
|
|
153
156
|
callback.onStatistics(statsMap);
|
|
154
157
|
callback.onTrimmingProgress((int)(progress * 100));
|
|
155
|
-
|
|
156
|
-
// mainHandler.post(() -> callback.onStatistics(statsMap));
|
|
157
|
-
// mainHandler.post(() -> callback.onTrimmingProgress((int)(progress * 100)));
|
|
158
|
-
|
|
159
158
|
}
|
|
160
159
|
lastProgressTime = currentTime;
|
|
161
160
|
}
|
|
162
161
|
}
|
|
163
162
|
|
|
164
|
-
if
|
|
165
|
-
|
|
163
|
+
// For static videos: ensure one video frame if none written
|
|
164
|
+
if (!videoSampleWritten && videoTrackIndex != -1) {
|
|
165
|
+
for (int i = 0; i < trackCount; i++) {
|
|
166
|
+
if (i != videoTrackIndex) {
|
|
167
|
+
extractor.unselectTrack(i);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
extractor.selectTrack(videoTrackIndex);
|
|
171
|
+
extractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
|
|
172
|
+
bufferInfo.size = extractor.readSampleData(buffer, 0);
|
|
173
|
+
if (bufferInfo.size >= 0) {
|
|
174
|
+
bufferInfo.presentationTimeUs = 0;
|
|
175
|
+
// Map MediaExtractor flags to MediaCodec flags
|
|
176
|
+
int extractorFlags = extractor.getSampleFlags();
|
|
177
|
+
bufferInfo.flags = (extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0;
|
|
178
|
+
muxer.writeSampleData(trackIndices[videoTrackIndex], buffer, bufferInfo);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
166
181
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
// android.net.Uri uri = android.provider.MediaStore.Files.getContentUri("external");
|
|
170
|
-
// android.content.ContentValues values = new android.content.ContentValues();
|
|
171
|
-
// values.put(android.provider.MediaStore.MediaColumns.DATA, outputFile);
|
|
172
|
-
// values.put(android.provider.MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis() / 1000);
|
|
173
|
-
// values.put(android.provider.MediaStore.MediaColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
|
|
174
|
-
// values.put(android.provider.MediaStore.MediaColumns.DATE_TAKEN, System.currentTimeMillis());
|
|
175
|
-
// contentResolver.insert(uri, values);
|
|
176
|
-
// android.content.ContentValues updateValues = new android.content.ContentValues();
|
|
177
|
-
// updateValues.put(android.provider.MediaStore.MediaColumns.DATE_TAKEN, System.currentTimeMillis());
|
|
178
|
-
// contentResolver.update(uri, updateValues, android.provider.MediaStore.MediaColumns.DATA + "=?", new String[]{outputFile});
|
|
179
|
-
|
|
180
|
-
// mainHandler.post(() -> callback.onFinishTrim(outputFile, startMs, endMs, videoDuration));
|
|
182
|
+
if (session.isActive()) {
|
|
183
|
+
muxer.stop();
|
|
181
184
|
callback.onFinishTrim(outputFile, startMs, endMs, videoDuration);
|
|
182
185
|
} else {
|
|
183
|
-
// mainHandler.post(callback::onCancelTrim);
|
|
184
186
|
callback.onCancelTrim();
|
|
185
187
|
}
|
|
186
188
|
|
|
187
189
|
} catch (IOException e) {
|
|
188
190
|
e.printStackTrace();
|
|
189
|
-
// mainHandler.post(() -> callback.onError("Trimming failed: " + e.getMessage(), ErrorCode.TRIMMING_FAILED));
|
|
190
191
|
callback.onError("Trimming failed: " + e.getMessage(), ErrorCode.TRIMMING_FAILED);
|
|
191
192
|
} finally {
|
|
192
193
|
try {
|
|
193
|
-
if (muxer != null) {
|
|
194
|
-
|
|
194
|
+
if (muxer != null && session.isActive()) {
|
|
195
|
+
muxer.release();
|
|
195
196
|
}
|
|
196
197
|
if (extractor != null) extractor.release();
|
|
197
198
|
} catch (Exception e) {
|