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 isCancelled() {
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, MediaMetadataRetriever retriever) {
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
- // Select tracks and add to muxer
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.isCancelled()) {
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 (session.isCancelled()) {
165
- muxer.stop();
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
- // Update creation_time via MediaStore
168
- // android.content.ContentResolver contentResolver = context.getContentResolver();
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
- if (session.isCancelled()) muxer.release();
194
+ if (muxer != null && session.isActive()) {
195
+ muxer.release();
195
196
  }
196
197
  if (extractor != null) extractor.release();
197
198
  } catch (Exception e) {
@@ -374,8 +374,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
374
374
  startTime,
375
375
  endTime,
376
376
  mOnTrimVideoListener,
377
- progressUpdateInterval,
378
- mediaMetadataRetriever
377
+ progressUpdateInterval
379
378
  );
380
379
  }
381
380
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-video-trim",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "Video trimmer for your React Native app",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",