react-native-video-trim 3.0.2 → 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,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,22 @@ public class VideoTrimmerUtil {
103
82
  int[] trackIndices = new int[trackCount];
104
83
  long[] trackStartTimes = new long[trackCount];
105
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
106
91
 
107
- // Select tracks and add to muxer
92
+ // Add tracks with corrected duration
108
93
  for (int i = 0; i < trackCount; i++) {
109
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);
110
101
  trackIndices[i] = muxer.addTrack(format);
111
102
  tracksAdded[i] = false;
112
103
  extractor.selectTrack(i);
@@ -116,16 +107,15 @@ public class VideoTrimmerUtil {
116
107
  muxer.setOrientationHint(rotation);
117
108
 
118
109
  // Seek to start time
119
- long startUs = startMs * 1000; // Convert ms to μs
120
- long endUs = endMs * 1000;
121
110
  extractor.seekTo(startUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
122
111
 
123
112
  ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
124
113
  MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
125
114
  long lastProgressTime = System.currentTimeMillis();
115
+ boolean videoSampleWritten = false;
126
116
 
127
117
  muxer.start();
128
- while (session.isCancelled()) {
118
+ while (session.isActive()) {
129
119
  bufferInfo.size = extractor.readSampleData(buffer, 0);
130
120
  if (bufferInfo.size < 0) break; // EOS
131
121
 
@@ -133,7 +123,6 @@ public class VideoTrimmerUtil {
133
123
  if (sampleTime > endUs) break;
134
124
 
135
125
  bufferInfo.presentationTimeUs = sampleTime;
136
- // Map MediaExtractor flags to MediaCodec flags
137
126
  int extractorFlags = extractor.getSampleFlags();
138
127
  bufferInfo.flags = (extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0;
139
128
  int trackIndex = extractor.getSampleTrackIndex();
@@ -144,10 +133,18 @@ public class VideoTrimmerUtil {
144
133
  }
145
134
  bufferInfo.presentationTimeUs -= trackStartTimes[trackIndex]; // Adjust time to start at 0
146
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
+ }
147
145
  muxer.writeSampleData(trackIndices[trackIndex], buffer, bufferInfo);
148
146
  extractor.advance();
149
147
 
150
- // Progress update (every progressUpdateInterval * 1000 ms)
151
148
  long currentTime = System.currentTimeMillis();
152
149
  if (currentTime - lastProgressTime >= progressUpdateInterval * 1000) {
153
150
  double progress = (double)(sampleTime - startUs) / (endUs - startUs);
@@ -158,46 +155,44 @@ public class VideoTrimmerUtil {
158
155
 
159
156
  callback.onStatistics(statsMap);
160
157
  callback.onTrimmingProgress((int)(progress * 100));
161
-
162
- // mainHandler.post(() -> callback.onStatistics(statsMap));
163
- // mainHandler.post(() -> callback.onTrimmingProgress((int)(progress * 100)));
164
-
165
158
  }
166
159
  lastProgressTime = currentTime;
167
160
  }
168
161
  }
169
162
 
170
- if (session.isCancelled()) {
171
- 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
+ }
172
181
 
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));
182
+ if (session.isActive()) {
183
+ muxer.stop();
187
184
  callback.onFinishTrim(outputFile, startMs, endMs, videoDuration);
188
185
  } else {
189
- // mainHandler.post(callback::onCancelTrim);
190
186
  callback.onCancelTrim();
191
187
  }
192
188
 
193
189
  } catch (IOException e) {
194
190
  e.printStackTrace();
195
- // mainHandler.post(() -> callback.onError("Trimming failed: " + e.getMessage(), ErrorCode.TRIMMING_FAILED));
196
191
  callback.onError("Trimming failed: " + e.getMessage(), ErrorCode.TRIMMING_FAILED);
197
192
  } finally {
198
193
  try {
199
- if (muxer != null) {
200
- if (session.isCancelled()) muxer.release();
194
+ if (muxer != null && session.isActive()) {
195
+ muxer.release();
201
196
  }
202
197
  if (extractor != null) extractor.release();
203
198
  } 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.3",
4
4
  "description": "Video trimmer for your React Native app",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",