react-native-video-trim 2.2.11 → 3.0.1

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.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  ## Features
10
10
  - ✅ Support video and audio
11
- - ✅ Support local and remote files
11
+ - ✅ Support local files
12
12
  - ✅ Save to Photos, Documents and Share to other apps
13
13
  - ✅ Check if file is valid video/audio
14
14
  - ✅ File operations: list, clean up, delete specific file
@@ -42,13 +42,6 @@ npx pod-install ios
42
42
 
43
43
  ## Usage
44
44
 
45
- > [!IMPORTANT]
46
- > Note that for both Android and iOS you have to try on real device
47
-
48
- > [!IMPORTANT]
49
- > If you plan to trim remote file, you must install FFMPEG version from "https" onwards, "min" version won't work. See bottom to know how to install specific FFMPEG version
50
-
51
-
52
45
  ```js
53
46
  import { showEditor } from 'react-native-video-trim';
54
47
 
@@ -233,6 +226,7 @@ Main method to show Video Editor UI.
233
226
  - `alertOnFailTitle` (`default = "Error"`)
234
227
  - `alertOnFailMessage` (`default = "Fail to load media. Possibly invalid file or no network connection"`)
235
228
  - `alertOnFailCloseText` (`default = "Close"`)
229
+ - `progressUpdateInterval` (`default = 0.1`): how fast the trimming progress update interval is, default is emit progress every 100ms (0.1 second)
236
230
 
237
231
  If `saveToPhoto = true`, you must ensure that you have request permission to write to photo/gallery
238
232
  - For Android: you need to have `<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />` in AndroidManifest.xml
@@ -349,25 +343,6 @@ showEditor(url, {
349
343
  })
350
344
  ```
351
345
 
352
- You must install FFMPEG version from "https" onwards, "min" version won't work. Eg.
353
- ```gradle
354
- // Android: android/build.gradle > buildscript > ext
355
-
356
- buildscript {
357
- ext {
358
- ffmpegKitPackage = "full"
359
- }
360
-
361
- ---
362
- // iOS:
363
-
364
- FFMPEGKIT_PACKAGE=https npx pod-install ios
365
-
366
- // or
367
-
368
- FFMPEGKIT_PACKAGE=https pod install
369
- ```
370
-
371
346
  # Cancel trimming
372
347
  <div align="left">
373
348
  <img src="images/progress.jpg" width="200" />
@@ -385,85 +360,6 @@ If there's error while loading media, there'll be a prompt
385
360
 
386
361
  Related props: `alertOnFailToLoad, alertOnFailTitle, alertOnFailMessage, alertOnFailCloseText`
387
362
 
388
- # Customize FFMPEG version
389
- This library uses FFMPEG-Kit Android under the hood, by default FFMPEG `min` is used, which gives smallest bundle size. To customize the package version please see below
390
-
391
- ## Android
392
- If you ever need to use other version of FFMPEG-Kit for Android, you can do the following, in your `android/build.gradle` > `buildscript` > `ext`:
393
-
394
- ```gradle
395
- buildscript {
396
- ext {
397
- ffmpegKitPackage = "full" // default "min"
398
-
399
- ffmpegKitPackageVersion = "5.1.LTS" // default 6.0-2
400
- }
401
- ```
402
-
403
- ## iOS
404
- Same as Android, there're 2 environment variables respectively you can use to specify FFMPEG Kit version you want to use: `FFMPEG_KIT_PACKAGE` and `FFMPEG_KIT_PACKAGE_VERSION`.
405
-
406
- You need to pass the variables when running pod install. Eg:
407
- ```shell
408
- # override package name, default: min
409
- FFMPEGKIT_PACKAGE=full npx pod-install ios
410
-
411
- # override package version, default: '~> 6.0
412
- FFMPEGKIT_PACKAGE_VERSION=5.1 npx pod-install ios
413
-
414
- # or both
415
- FFMPEGKIT_PACKAGE=full FFMPEGKIT_PACKAGE_VERSION=5.1 npx pod-install ios
416
- ```
417
-
418
- ## Packages
419
-
420
- <table>
421
- <thead>
422
- <tr>
423
- <th align="center"></th>
424
- <th align="center"><sup>min</sup></th>
425
- <th align="center"><sup>min-gpl</sup></th>
426
- <th align="center"><sup>https</sup></th>
427
- <th align="center"><sup>https-gpl</sup></th>
428
- <th align="center"><sup>audio</sup></th>
429
- <th align="center"><sup>video</sup></th>
430
- <th align="center"><sup>full</sup></th>
431
- <th align="center"><sup>full-gpl</sup></th>
432
- </tr>
433
- </thead>
434
- <tbody>
435
- <tr>
436
- <td align="center"><sup>external libraries</sup></td>
437
- <td align="center">-</td>
438
- <td align="center"><sup>vid.stab</sup><br><sup>x264</sup><br><sup>x265</sup><br><sup>xvidcore</sup></td>
439
- <td align="center"><sup>gmp</sup><br><sup>gnutls</sup></td>
440
- <td align="center"><sup>gmp</sup><br><sup>gnutls</sup><br><sup>vid.stab</sup><br><sup>x264</sup><br><sup>x265</sup><br><sup>xvidcore</sup></td>
441
- <td align="center"><sup>lame</sup><br><sup>libilbc</sup><br><sup>libvorbis</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>shine</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>twolame</sup><br><sup>vo-amrwbenc</sup></td>
442
- <td align="center"><sup>dav1d</sup><br><sup>fontconfig</sup><br><sup>freetype</sup><br><sup>fribidi</sup><br><sup>kvazaar</sup><br><sup>libass</sup><br><sup>libiconv</sup><br><sup>libtheora</sup><br><sup>libvpx</sup><br><sup>libwebp</sup><br><sup>snappy</sup><br><sup>zimg</sup></td>
443
- <td align="center"><sup>dav1d</sup><br><sup>fontconfig</sup><br><sup>freetype</sup><br><sup>fribidi</sup><br><sup>gmp</sup><br><sup>gnutls</sup><br><sup>kvazaar</sup><br><sup>lame</sup><br><sup>libass</sup><br><sup>libiconv</sup><br><sup>libilbc</sup><br><sup>libtheora</sup><br><sup>libvorbis</sup><br><sup>libvpx</sup><br><sup>libwebp</sup><br><sup>libxml2</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>shine</sup><br><sup>snappy</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>twolame</sup><br><sup>vo-amrwbenc</sup><br><sup>zimg</sup></td>
444
- <td align="center"><sup>dav1d</sup><br><sup>fontconfig</sup><br><sup>freetype</sup><br><sup>fribidi</sup><br><sup>gmp</sup><br><sup>gnutls</sup><br><sup>kvazaar</sup><br><sup>lame</sup><br><sup>libass</sup><br><sup>libiconv</sup><br><sup>libilbc</sup><br><sup>libtheora</sup><br><sup>libvorbis</sup><br><sup>libvpx</sup><br><sup>libwebp</sup><br><sup>libxml2</sup><br><sup>opencore-amr</sup><br><sup>opus</sup><br><sup>shine</sup><br><sup>snappy</sup><br><sup>soxr</sup><br><sup>speex</sup><br><sup>twolame</sup><br><sup>vid.stab</sup><br><sup>vo-amrwbenc</sup><br><sup>x264</sup><br><sup>x265</sup><br><sup>xvidcore</sup><br><sup>zimg</sup></td>
445
- </tr>
446
- <tr>
447
- <td align="center"><sup>android system libraries</sup></td>
448
- <td align="center" colspan=8><sup>zlib</sup><br><sup>MediaCodec</sup></td>
449
- </tr>
450
- <tr>
451
- <td align="center"><sup>ios system libraries</sup></td>
452
- <td align="center" colspan=8><sup>bzip2</sup><br><sup>AudioToolbox</sup><br><sup>AVFoundation</sup><br><sup>iconv</sup><br><sup>VideoToolbox</sup><br><sup>zlib</sup></td>
453
- </tr>
454
- <tr>
455
- <tr>
456
- <td align="center"><sup>macos system libraries</sup></td>
457
- <td align="center" colspan=8><sup>bzip2</sup><br><sup>AudioToolbox</sup><br><sup>AVFoundation</sup><br><sup>Core Image</sup><br><sup>iconv</sup><br><sup>OpenCL</sup><br><sup>OpenGL</sup><br><sup>VideoToolbox</sup><br><sup>zlib</sup></td>
458
- </tr>
459
- <tr>
460
- <td align="center"><sup>tvos system libraries</sup></td>
461
- <td align="center" colspan=8><sup>bzip2</sup><br><sup>AudioToolbox</sup><br><sup>iconv</sup><br><sup>VideoToolbox</sup><br><sup>zlib</sup></td>
462
- </tr>
463
- </tbody>
464
- </table>
465
-
466
-
467
363
  # Android: update SDK version
468
364
  You can override sdk version to use any version in your `android/build.gradle` > `buildscript` > `ext`
469
365
  ```gradle
@@ -476,20 +372,6 @@ buildscript {
476
372
  }
477
373
  ```
478
374
 
479
- # Naming conflict with `ffmpeg-kit-react-native`
480
- This issue is due to this package and `ffmpeg-kit-react-native` have same ffmpegkit class name under the hood:
481
-
482
- <img src="images/error_conflict_name.png"/>
483
-
484
- To fix it we need to synchronize the FFMPEG Kit version of the 2 packages. For example: as shown in the image above, `ffmpeg-kit-react-native` uses `https`, version `6.0`, hence we need to run this:
485
- ```
486
- FFMPEGKIT_PACKAGE=https FFMPEG_KIT_PACKAGE_VERSION=6.0 pod install
487
- ```
488
-
489
- Another way to find out exactly FFMPEG Kit version that `ffmpeg-kit-react-native` using is to ([check here for package name](https://github.com/arthenica/ffmpeg-kit/blob/main/react-native/ffmpeg-kit-react-native.podspec#L19)) and package version is `version` in [package.json](https://github.com/arthenica/ffmpeg-kit/blob/main/react-native/package.json#L3)
490
-
491
- When you know the package name + version, simply run `pod install` with `FFMPEGKIT_PACKAGE` and `FFMPEG_KIT_PACKAGE_VERSION` like above
492
-
493
375
  # Thanks
494
376
  - Android part is created by modified + fix bugs from: https://github.com/iknow4/Android-Video-Trimmer
495
377
  - iOS UI is created from: https://github.com/AndreasVerhoeven/VideoTrimmerControl
@@ -43,16 +43,6 @@ def supportsNamespace() {
43
43
  return major >= 8
44
44
  }
45
45
 
46
- def safePackageName() {
47
- def name = project.properties['ffmpegKit.android.package.name']
48
- name + '-' + safeExtGet('ffmpegKitPackage', 'min')
49
- }
50
-
51
- def safePackageVersion() {
52
- def name = project.properties['ffmpegKit.android.package.version']
53
- safeExtGet('ffmpegKitPackageVersion', name)
54
- }
55
-
56
46
  def safeExtGet(String prop, String fallback) {
57
47
  rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
58
48
  }
@@ -104,7 +94,6 @@ dependencies {
104
94
  //noinspection GradleDynamicVersion
105
95
  implementation "com.facebook.react:react-native:+"
106
96
  implementation 'androidx.recyclerview:recyclerview:1.3.1'
107
- implementation safePackageName() + ':' + safePackageVersion()
108
97
  }
109
98
 
110
99
  if (isNewArchitectureEnabled()) {
@@ -3,5 +3,3 @@ VideoTrim_minSdkVersion=21
3
3
  VideoTrim_targetSdkVersion=31
4
4
  VideoTrim_compileSdkVersion=31
5
5
  VideoTrim_ndkversion=21.4.7075529
6
- ffmpegKit.android.package.name=com.arthenica:ffmpeg-kit
7
- ffmpegKit.android.package.version=6.0-2
@@ -99,6 +99,7 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
99
99
  private boolean openShareSheetOnFinish = false;
100
100
  private boolean isVideoType = true;
101
101
  private boolean closeWhenFinish = true;
102
+ private float progressUpdateInterval = 0.1f;
102
103
 
103
104
  private static final int REQUEST_CODE_SAVE_FILE = 1;
104
105
 
@@ -194,6 +195,7 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
194
195
  isVideoType = !config.hasKey("type") || !Objects.equals(config.getString("type"), "audio");
195
196
 
196
197
  closeWhenFinish = !config.hasKey("closeWhenFinish") || config.getBoolean("closeWhenFinish");
198
+ progressUpdateInterval = config.hasKey("progressUpdateInterval") ? (float) config.getDouble("progressUpdateInterval") : 0.1f;
197
199
 
198
200
  Activity activity = getReactApplicationContext().getCurrentActivity();
199
201
 
@@ -372,11 +374,6 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
372
374
  alertDialog.show();
373
375
  }
374
376
 
375
- @Override
376
- public void onLog(WritableMap log) {
377
- sendEvent(getReactApplicationContext(), "onLog", log);
378
- }
379
-
380
377
  @Override
381
378
  public void onStatistics(WritableMap statistics) {
382
379
  sendEvent(getReactApplicationContext(), "onStatistics", statistics);
@@ -468,7 +465,7 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
468
465
 
469
466
  mProgressDialog.setOnShowListener(dialog -> {
470
467
  sendEvent(getReactApplicationContext(), "onStartTrimming", null);
471
- trimmerView.onSaveClicked();
468
+ trimmerView.onSaveClicked(progressUpdateInterval);
472
469
  });
473
470
 
474
471
  mProgressDialog.show();
@@ -11,6 +11,5 @@ public interface VideoTrimListener {
11
11
  void onError(String errorMessage, ErrorCode errorCode);
12
12
  void onCancel();
13
13
  void onSave();
14
- void onLog(WritableMap log);
15
14
  void onStatistics(WritableMap statistics);
16
15
  }
@@ -1,12 +1,10 @@
1
1
  package com.videotrim.utils;
2
2
 
3
3
  import android.annotation.SuppressLint;
4
+ import android.content.Context;
4
5
  import android.graphics.Bitmap;
6
+ import android.media.MediaCodec;
5
7
  import android.media.MediaMetadataRetriever;
6
- import com.arthenica.ffmpegkit.FFmpegKit;
7
- import com.arthenica.ffmpegkit.FFmpegSession;
8
- import com.arthenica.ffmpegkit.ReturnCode;
9
- import com.arthenica.ffmpegkit.SessionState;
10
8
  import com.facebook.react.bridge.Arguments;
11
9
  import com.facebook.react.bridge.WritableMap;
12
10
  import com.videotrim.enums.ErrorCode;
@@ -21,6 +19,14 @@ import iknow.android.utils.UnitConverter;
21
19
  import iknow.android.utils.callback.SingleCallback;
22
20
  import iknow.android.utils.thread.BackgroundExecutor;
23
21
 
22
+ import android.media.MediaExtractor;
23
+ import android.media.MediaFormat;
24
+ import android.media.MediaMuxer;
25
+ import android.os.Handler;
26
+ import android.os.Looper;
27
+ import java.io.IOException;
28
+ import java.nio.ByteBuffer;
29
+
24
30
  public class VideoTrimmerUtil {
25
31
 
26
32
  private static final String TAG = VideoTrimmerUtil.class.getSimpleName();
@@ -40,77 +46,164 @@ public class VideoTrimmerUtil {
40
46
  public static final int THUMB_WIDTH = UnitConverter.dpToPx(25); // x2 for better resolution
41
47
  private static final int THUMB_RESOLUTION_RES = 2; // double thumb resolution for better quality
42
48
 
43
- public static FFmpegSession trim(String inputFile, String outputFile, int videoDuration, long startMs, long endMs, final VideoTrimListener callback) {
44
- // Get the current date and time
45
- Date currentDate = new Date();
49
+ private static final int BUFFER_SIZE = 1024 * 1024; // 1MB buffer
46
50
 
47
- // Create a SimpleDateFormat object with the desired format
48
- @SuppressLint("SimpleDateFormat") SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
51
+ // Custom session class to manage trimming and cancellation
52
+ public static class TrimSession {
53
+ private final Thread trimThread;
54
+ private volatile boolean isCancelled = false;
49
55
 
50
- // Set the timezone to UTC
51
- dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
56
+ private TrimSession(Thread thread) {
57
+ this.trimThread = thread;
58
+ }
52
59
 
53
- // Format the current date and time
54
- String formattedDateTime = dateFormat.format(currentDate);
55
-
56
- String[] cmds = {
57
- "-ss",
58
- startMs + "ms",
59
- "-to",
60
- endMs + "ms",
61
- "-i",
62
- inputFile,
63
- "-c",
64
- "copy",
65
- "-metadata",
66
- "creation_time=" + formattedDateTime,
67
- outputFile
68
- };
69
- System.out.println("Command: " + String.join(",", cmds));
70
-
71
- return FFmpegKit.executeWithArgumentsAsync(cmds, session -> {
72
- SessionState state = session.getState();
73
- ReturnCode returnCode = session.getReturnCode();
74
- if (ReturnCode.isSuccess(session.getReturnCode())) {
75
- // SUCCESS
76
- callback.onFinishTrim(outputFile, startMs, endMs, videoDuration);
77
- } else if (ReturnCode.isCancel(session.getReturnCode())) {
78
- // CANCEL
79
- callback.onCancelTrim();
80
- } else {
81
- // FAILURE
82
- String errorMessage = String.format("Command failed with state %s and rc %s.%s", state, returnCode, session.getFailStackTrace());
83
- callback.onError(errorMessage, ErrorCode.TRIMMING_FAILED);
84
- }
85
- }, log -> {
86
- System.out.println("FFmpeg process started with log " + log.getMessage());
87
-
88
- WritableMap map = Arguments.createMap();
89
- map.putInt("level", log.getLevel().getValue());
90
- map.putString("message", log.getMessage());
91
- map.putDouble("sessionId", log.getSessionId());
92
- map.putString("logStr", log.toString());
93
- callback.onLog(map);
94
- }, statistics -> {
95
- int timeInMilliseconds = (int) statistics.getTime();
96
- if (timeInMilliseconds > 0) {
97
- int completePercentage =
98
- (timeInMilliseconds * 100) / videoDuration;
99
- callback.onTrimmingProgress(Math.min(Math.max(completePercentage, 0), 100));
60
+ public void cancel() {
61
+ isCancelled = true;
62
+ if (trimThread != null) {
63
+ trimThread.interrupt();
100
64
  }
65
+ }
101
66
 
102
- WritableMap map = Arguments.createMap();
103
- map.putDouble("sessionId", statistics.getSessionId());
104
- map.putInt("videoFrameNumber", statistics.getVideoFrameNumber());
105
- map.putDouble("videoFps", statistics.getVideoFps());
106
- map.putDouble("videoQuality", statistics.getVideoQuality());
107
- map.putDouble("size", statistics.getSize());
108
- map.putDouble("time", statistics.getTime());
109
- map.putDouble("bitrate", statistics.getBitrate());
110
- map.putDouble("speed", statistics.getSpeed());
111
- map.putString("statisticsStr", statistics.toString());
112
- callback.onStatistics(map);
67
+ public boolean isCancelled() {
68
+ return !isCancelled;
69
+ }
70
+ }
71
+
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
+
78
+ // Start trimming in a background thread
79
+ Thread trimThread = new Thread(() -> {
80
+ MediaExtractor extractor = null;
81
+ MediaMuxer muxer = null;
82
+ // Handler mainHandler = new Handler(Looper.getMainLooper());
83
+ TrimSession session = new TrimSession(Thread.currentThread());
84
+
85
+ try {
86
+ // Get rotation metadata from input file
87
+ retriever.setDataSource(inputFile);
88
+ String rotationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
89
+ int rotation = rotationStr != null ? Integer.parseInt(rotationStr) : 0;
90
+ retriever.release();
91
+
92
+ extractor = new MediaExtractor();
93
+ extractor.setDataSource(inputFile);
94
+
95
+ int trackCount = extractor.getTrackCount();
96
+ muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
97
+ int[] trackIndices = new int[trackCount];
98
+ long[] trackStartTimes = new long[trackCount];
99
+ boolean[] tracksAdded = new boolean[trackCount];
100
+
101
+ // Select tracks and add to muxer
102
+ for (int i = 0; i < trackCount; i++) {
103
+ MediaFormat format = extractor.getTrackFormat(i);
104
+ trackIndices[i] = muxer.addTrack(format);
105
+ tracksAdded[i] = false;
106
+ extractor.selectTrack(i);
107
+ }
108
+
109
+ // Set rotation metadata on muxer
110
+ muxer.setOrientationHint(rotation);
111
+
112
+ // Seek to start time
113
+ long startUs = startMs * 1000; // Convert ms to μs
114
+ long endUs = endMs * 1000;
115
+ extractor.seekTo(startUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
116
+
117
+ ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
118
+ MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
119
+ long lastProgressTime = System.currentTimeMillis();
120
+
121
+ muxer.start();
122
+ while (session.isCancelled()) {
123
+ bufferInfo.size = extractor.readSampleData(buffer, 0);
124
+ if (bufferInfo.size < 0) break; // EOS
125
+
126
+ long sampleTime = extractor.getSampleTime();
127
+ if (sampleTime > endUs) break;
128
+
129
+ bufferInfo.presentationTimeUs = sampleTime;
130
+ // Map MediaExtractor flags to MediaCodec flags
131
+ int extractorFlags = extractor.getSampleFlags();
132
+ bufferInfo.flags = (extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0;
133
+ int trackIndex = extractor.getSampleTrackIndex();
134
+
135
+ if (!tracksAdded[trackIndex]) {
136
+ trackStartTimes[trackIndex] = sampleTime;
137
+ tracksAdded[trackIndex] = true;
138
+ }
139
+ bufferInfo.presentationTimeUs -= trackStartTimes[trackIndex]; // Adjust time to start at 0
140
+
141
+ muxer.writeSampleData(trackIndices[trackIndex], buffer, bufferInfo);
142
+ extractor.advance();
143
+
144
+ // Progress update (every progressUpdateInterval * 1000 ms)
145
+ long currentTime = System.currentTimeMillis();
146
+ if (currentTime - lastProgressTime >= progressUpdateInterval * 1000) {
147
+ double progress = (double)(sampleTime - startUs) / (endUs - startUs);
148
+ if (progress > 0 && progress <= 1) {
149
+ WritableMap statsMap = Arguments.createMap();
150
+ statsMap.putDouble("time", progress * videoDuration);
151
+ statsMap.putDouble("progress", progress); // Percentage
152
+
153
+ callback.onStatistics(statsMap);
154
+ callback.onTrimmingProgress((int)(progress * 100));
155
+
156
+ // mainHandler.post(() -> callback.onStatistics(statsMap));
157
+ // mainHandler.post(() -> callback.onTrimmingProgress((int)(progress * 100)));
158
+
159
+ }
160
+ lastProgressTime = currentTime;
161
+ }
162
+ }
163
+
164
+ if (session.isCancelled()) {
165
+ muxer.stop();
166
+
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));
181
+ callback.onFinishTrim(outputFile, startMs, endMs, videoDuration);
182
+ } else {
183
+ // mainHandler.post(callback::onCancelTrim);
184
+ callback.onCancelTrim();
185
+ }
186
+
187
+ } catch (IOException e) {
188
+ e.printStackTrace();
189
+ // mainHandler.post(() -> callback.onError("Trimming failed: " + e.getMessage(), ErrorCode.TRIMMING_FAILED));
190
+ callback.onError("Trimming failed: " + e.getMessage(), ErrorCode.TRIMMING_FAILED);
191
+ } finally {
192
+ try {
193
+ if (muxer != null) {
194
+ if (session.isCancelled()) muxer.release();
195
+ }
196
+ if (extractor != null) extractor.release();
197
+ } catch (Exception e) {
198
+ e.printStackTrace();
199
+ System.err.println("Error releasing resources: " + e.getMessage());
200
+ }
201
+ }
113
202
  });
203
+
204
+ TrimSession session = new TrimSession(trimThread);
205
+ trimThread.start();
206
+ return session;
114
207
  }
115
208
 
116
209
  public static void shootVideoThumbInBackground(final MediaMetadataRetriever mediaMetadataRetriever, final int totalThumbsCount, final long startPosition,
@@ -32,7 +32,6 @@ import android.widget.VideoView;
32
32
 
33
33
  import androidx.appcompat.app.AlertDialog;
34
34
 
35
- import com.arthenica.ffmpegkit.FFmpegSession;
36
35
  import com.facebook.react.bridge.ReactApplicationContext;
37
36
  import com.facebook.react.bridge.ReadableMap;
38
37
  import com.videotrim.R;
@@ -109,7 +108,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
109
108
  private long jumpToPositionOnLoad = 0;
110
109
  private FrameLayout headerView;
111
110
  private TextView headerText;
112
- private FFmpegSession ffmpegSession;
111
+ private VideoTrimmerUtil.TrimSession trimSession;
113
112
  private boolean alertOnFailToLoad = true;
114
113
  private String alertOnFailTitle = "Error";
115
114
  private String alertOnFailMessage = "Fail to load media. Possibly invalid file or no network connection";
@@ -366,20 +365,23 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
366
365
  setHandleTouchListener(trailingHandle, false);
367
366
  }
368
367
 
369
- public void onSaveClicked() {
368
+ public void onSaveClicked(float progressUpdateInterval) {
370
369
  onMediaPause();
371
- ffmpegSession = VideoTrimmerUtil.trim(
370
+ trimSession = VideoTrimmerUtil.trim(
372
371
  mSourceUri.toString(),
373
372
  StorageUtil.getOutputPath(mContext, mOutputExt),
374
373
  mDuration,
375
374
  startTime,
376
375
  endTime,
377
- mOnTrimVideoListener);
376
+ mOnTrimVideoListener,
377
+ progressUpdateInterval,
378
+ mediaMetadataRetriever
379
+ );
378
380
  }
379
381
 
380
382
  public void onCancelTrimClicked() {
381
- if (ffmpegSession != null) {
382
- ffmpegSession.cancel();
383
+ if (trimSession != null) {
384
+ trimSession.cancel();
383
385
  } else {
384
386
  mOnTrimVideoListener.onCancelTrim();
385
387
  }