react-native-video-trim 2.2.11 → 3.0.0
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 +2 -120
- package/android/build.gradle +0 -11
- package/android/gradle.properties +0 -2
- package/android/src/main/java/com/videotrim/VideoTrimModule.java +3 -6
- package/android/src/main/java/com/videotrim/interfaces/VideoTrimListener.java +0 -1
- package/android/src/main/java/com/videotrim/utils/VideoTrimmerUtil.java +153 -69
- package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.java +8 -7
- package/ios/VideoTrim.swift +656 -633
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/index.d.ts +5 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/react-native-video-trim.podspec +0 -1
- package/src/index.tsx +5 -0
- package/ios/VideoTrim.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -4
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
- ✅ Support video and audio
|
|
11
|
-
- ✅ Support local
|
|
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
|
package/android/build.gradle
CHANGED
|
@@ -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()) {
|
|
@@ -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();
|
|
@@ -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,155 @@ 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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
51
|
-
|
|
56
|
+
private TrimSession(Thread thread) {
|
|
57
|
+
this.trimThread = thread;
|
|
58
|
+
}
|
|
52
59
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
+
}
|
|
66
|
+
|
|
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) {
|
|
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
|
+
extractor = new MediaExtractor();
|
|
87
|
+
extractor.setDataSource(inputFile);
|
|
88
|
+
|
|
89
|
+
int trackCount = extractor.getTrackCount();
|
|
90
|
+
muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
|
|
91
|
+
int[] trackIndices = new int[trackCount];
|
|
92
|
+
long[] trackStartTimes = new long[trackCount];
|
|
93
|
+
boolean[] tracksAdded = new boolean[trackCount];
|
|
94
|
+
|
|
95
|
+
// Select tracks and add to muxer
|
|
96
|
+
for (int i = 0; i < trackCount; i++) {
|
|
97
|
+
MediaFormat format = extractor.getTrackFormat(i);
|
|
98
|
+
trackIndices[i] = muxer.addTrack(format);
|
|
99
|
+
tracksAdded[i] = false;
|
|
100
|
+
extractor.selectTrack(i);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Seek to start time
|
|
104
|
+
long startUs = startMs * 1000; // Convert ms to μs
|
|
105
|
+
long endUs = endMs * 1000;
|
|
106
|
+
extractor.seekTo(startUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
|
|
107
|
+
|
|
108
|
+
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
|
|
109
|
+
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
|
110
|
+
long lastProgressTime = System.currentTimeMillis();
|
|
111
|
+
|
|
112
|
+
muxer.start();
|
|
113
|
+
while (session.isCancelled()) {
|
|
114
|
+
bufferInfo.size = extractor.readSampleData(buffer, 0);
|
|
115
|
+
if (bufferInfo.size < 0) break; // EOS
|
|
116
|
+
|
|
117
|
+
long sampleTime = extractor.getSampleTime();
|
|
118
|
+
if (sampleTime > endUs) break;
|
|
119
|
+
|
|
120
|
+
bufferInfo.presentationTimeUs = sampleTime;
|
|
121
|
+
// Map MediaExtractor flags to MediaCodec flags
|
|
122
|
+
int extractorFlags = extractor.getSampleFlags();
|
|
123
|
+
bufferInfo.flags = (extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0;
|
|
124
|
+
int trackIndex = extractor.getSampleTrackIndex();
|
|
125
|
+
|
|
126
|
+
if (!tracksAdded[trackIndex]) {
|
|
127
|
+
trackStartTimes[trackIndex] = sampleTime;
|
|
128
|
+
tracksAdded[trackIndex] = true;
|
|
129
|
+
}
|
|
130
|
+
bufferInfo.presentationTimeUs -= trackStartTimes[trackIndex]; // Adjust time to start at 0
|
|
131
|
+
|
|
132
|
+
muxer.writeSampleData(trackIndices[trackIndex], buffer, bufferInfo);
|
|
133
|
+
extractor.advance();
|
|
134
|
+
|
|
135
|
+
// Progress update (every progressUpdateInterval * 1000 ms)
|
|
136
|
+
long currentTime = System.currentTimeMillis();
|
|
137
|
+
if (currentTime - lastProgressTime >= progressUpdateInterval * 1000) {
|
|
138
|
+
double progress = (double)(sampleTime - startUs) / (endUs - startUs);
|
|
139
|
+
if (progress > 0 && progress <= 1) {
|
|
140
|
+
WritableMap statsMap = Arguments.createMap();
|
|
141
|
+
statsMap.putDouble("time", progress * videoDuration);
|
|
142
|
+
statsMap.putDouble("progress", progress); // Percentage
|
|
143
|
+
|
|
144
|
+
callback.onStatistics(statsMap);
|
|
145
|
+
callback.onTrimmingProgress((int)(progress * 100));
|
|
146
|
+
|
|
147
|
+
// mainHandler.post(() -> callback.onStatistics(statsMap));
|
|
148
|
+
// mainHandler.post(() -> callback.onTrimmingProgress((int)(progress * 100)));
|
|
149
|
+
|
|
150
|
+
}
|
|
151
|
+
lastProgressTime = currentTime;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (session.isCancelled()) {
|
|
156
|
+
muxer.stop();
|
|
157
|
+
|
|
158
|
+
// Update creation_time via MediaStore
|
|
159
|
+
// android.content.ContentResolver contentResolver = context.getContentResolver();
|
|
160
|
+
// android.net.Uri uri = android.provider.MediaStore.Files.getContentUri("external");
|
|
161
|
+
// android.content.ContentValues values = new android.content.ContentValues();
|
|
162
|
+
// values.put(android.provider.MediaStore.MediaColumns.DATA, outputFile);
|
|
163
|
+
// values.put(android.provider.MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis() / 1000);
|
|
164
|
+
// values.put(android.provider.MediaStore.MediaColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
|
|
165
|
+
// values.put(android.provider.MediaStore.MediaColumns.DATE_TAKEN, System.currentTimeMillis());
|
|
166
|
+
// contentResolver.insert(uri, values);
|
|
167
|
+
// android.content.ContentValues updateValues = new android.content.ContentValues();
|
|
168
|
+
// updateValues.put(android.provider.MediaStore.MediaColumns.DATE_TAKEN, System.currentTimeMillis());
|
|
169
|
+
// contentResolver.update(uri, updateValues, android.provider.MediaStore.MediaColumns.DATA + "=?", new String[]{outputFile});
|
|
170
|
+
|
|
171
|
+
// mainHandler.post(() -> callback.onFinishTrim(outputFile, startMs, endMs, videoDuration));
|
|
172
|
+
callback.onFinishTrim(outputFile, startMs, endMs, videoDuration);
|
|
173
|
+
} else {
|
|
174
|
+
// mainHandler.post(callback::onCancelTrim);
|
|
175
|
+
callback.onCancelTrim();
|
|
176
|
+
}
|
|
101
177
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
178
|
+
} catch (IOException e) {
|
|
179
|
+
e.printStackTrace();
|
|
180
|
+
// mainHandler.post(() -> callback.onError("Trimming failed: " + e.getMessage(), ErrorCode.TRIMMING_FAILED));
|
|
181
|
+
callback.onError("Trimming failed: " + e.getMessage(), ErrorCode.TRIMMING_FAILED);
|
|
182
|
+
} finally {
|
|
183
|
+
try {
|
|
184
|
+
if (muxer != null) {
|
|
185
|
+
if (session.isCancelled()) muxer.release();
|
|
186
|
+
}
|
|
187
|
+
if (extractor != null) extractor.release();
|
|
188
|
+
} catch (Exception e) {
|
|
189
|
+
e.printStackTrace();
|
|
190
|
+
System.err.println("Error releasing resources: " + e.getMessage());
|
|
191
|
+
}
|
|
192
|
+
}
|
|
113
193
|
});
|
|
194
|
+
|
|
195
|
+
TrimSession session = new TrimSession(trimThread);
|
|
196
|
+
trimThread.start();
|
|
197
|
+
return session;
|
|
114
198
|
}
|
|
115
199
|
|
|
116
200
|
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
|
|
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,22 @@ 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
|
-
|
|
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
|
+
);
|
|
378
379
|
}
|
|
379
380
|
|
|
380
381
|
public void onCancelTrimClicked() {
|
|
381
|
-
if (
|
|
382
|
-
|
|
382
|
+
if (trimSession != null) {
|
|
383
|
+
trimSession.cancel();
|
|
383
384
|
} else {
|
|
384
385
|
mOnTrimVideoListener.onCancelTrim();
|
|
385
386
|
}
|