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 isCancelled() {
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 = MediaMetadataUtil.getMediaMetadataRetriever(inputFile);
88
-
89
- int rotation = 0;
90
- if (retriever != null) {
91
- retriever.setDataSource(inputFile);
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
- // Select tracks and add to muxer
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(BUFFER_SIZE);
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.isCancelled()) {
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 (session.isCancelled()) {
171
- muxer.stop();
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
- // Update creation_time via MediaStore
174
- // android.content.ContentResolver contentResolver = context.getContentResolver();
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
- if (session.isCancelled()) muxer.release();
217
+ if (muxer != null && session.isActive()) {
218
+ muxer.release();
201
219
  }
202
220
  if (extractor != null) extractor.release();
203
221
  } catch (Exception e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-video-trim",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
4
4
  "description": "Video trimmer for your React Native app",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",