react-native-video-trim 8.1.0 → 8.1.2
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 +16 -0
- package/android/src/main/java/com/videotrim/BaseVideoTrimModule.kt +97 -91
- package/android/src/main/java/com/videotrim/enums/ErrorCode.kt +1 -0
- package/android/src/main/java/com/videotrim/utils/VideoTrimmerUtil.kt +275 -116
- package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.kt +28 -13
- package/ios/ErrorCode.swift +1 -0
- package/ios/VideoTrim.mm +1 -0
- package/ios/VideoTrim.swift +28 -1
- package/ios/VideoTrimmerViewController.swift +3 -1
- package/lib/module/NativeVideoTrim.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/NativeVideoTrim.d.ts +6 -0
- package/lib/typescript/src/NativeVideoTrim.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeVideoTrim.ts +6 -0
- package/src/index.tsx +1 -0
package/README.md
CHANGED
|
@@ -567,6 +567,7 @@ These options only apply when `type: 'audio'`. The waveform replaces the thumbna
|
|
|
567
567
|
| Option | Type | Default | Description |
|
|
568
568
|
|--------|------|---------|-------------|
|
|
569
569
|
| `enableHapticFeedback` | `boolean` | `true` | Enable haptic feedback |
|
|
570
|
+
| `enableEditTools` | `boolean` | `true` | Show the top toolbar of edit tools (flip, rotate, crop, mute, speed, undo, redo). Set to `false` to hide the entire toolbar. Video only |
|
|
570
571
|
| `closeWhenFinish` | `boolean` | `true` | Close editor when done |
|
|
571
572
|
| `enablePreciseTrimming` | `boolean` | `false` | Re-encode for frame-accurate cuts (slower, see [Precise Frame Trimming](#precise-frame-trimming)) |
|
|
572
573
|
| `changeStatusBarColorOnOpen` | `boolean` | `false` | Change status bar color (Android only) |
|
|
@@ -760,6 +761,21 @@ The editor includes built-in transform controls — horizontal flip, 90° left r
|
|
|
760
761
|
|
|
761
762
|
When any transform is applied, FFmpeg automatically re-encodes the video using the platform's hardware encoder (`h264_videotoolbox` on iOS, `h264_mediacodec` on Android) at the source bitrate to preserve quality. No additional configuration is needed.
|
|
762
763
|
|
|
764
|
+
### Android encoder compatibility (auto fallback)
|
|
765
|
+
|
|
766
|
+
On Android, a small number of devices ship a hardware H.264 encoder (`h264_mediacodec`) that refuses to configure for valid H.264 inputs — typically with `MediaCodec configure failed, Generic error in an external library` (e.g. LG G8 ThinQ on Snapdragon 855, and other older Qualcomm/MediaTek chipsets). This affects only the re-encode path (transform / crop / `enablePreciseTrimming` / non-`1.0` speed); plain trims use stream copy and never touch an encoder.
|
|
767
|
+
|
|
768
|
+
The library handles this automatically with a two-step encoder fallback chain:
|
|
769
|
+
|
|
770
|
+
1. `h264_mediacodec` — hardware H.264, fast, default. Used on every device first.
|
|
771
|
+
2. `mpeg4 -q:v 3` — software MPEG-4 Part 2, always available. Only used if attempt 1 fails at `configure()` with a hardware-encoder signature. Lower visual quality and larger files than H.264, but guaranteed to work.
|
|
772
|
+
|
|
773
|
+
No configuration is needed — the fallback is transparent. When a retry happens, a notice is emitted via the existing `onLog` event (`Hardware encoder failed; retrying with software encoder fallback`) so you can observe quality degradation if you want to log it.
|
|
774
|
+
|
|
775
|
+
If every attempt in the chain fails, `onError` is emitted with `errorCode: "HARDWARE_ENCODER_FAILED"` (instead of the generic `"TRIMMING_FAILED"`) so apps can present a more specific message or telemetry.
|
|
776
|
+
|
|
777
|
+
> iOS does not need this fallback — `h264_videotoolbox` runs against Apple's single-vendor stack and has no known reproducible configure-time failures on supported devices. The `HARDWARE_ENCODER_FAILED` `errorCode` is still emitted on iOS as a defensive measure if the rare VideoToolbox failure does occur.
|
|
778
|
+
|
|
763
779
|
### Precise Frame Trimming
|
|
764
780
|
|
|
765
781
|
By default, trimming uses FFmpeg's stream copy (`-c copy`), which is very fast but can only cut at keyframes. The actual start/end points may drift by several seconds from what the user selected.
|
|
@@ -634,111 +634,117 @@ open class BaseVideoTrimModule internal constructor(
|
|
|
634
634
|
options.getBoolean("enablePreciseTrimming")
|
|
635
635
|
val needsReEncode = enablePrecise || speed != 1.0
|
|
636
636
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
637
|
+
// Headless trim still benefits from the encoder fallback chain whenever it
|
|
638
|
+
// re-encodes (hardware encoder failures are device-specific, not path-specific).
|
|
639
|
+
// No-op log/stats/progress callbacks because headless trim returns via Promise
|
|
640
|
+
// rather than emitting events.
|
|
641
|
+
val onSuccess: () -> Unit = {
|
|
642
|
+
val duration = endTime - startTime
|
|
643
|
+
val result = Arguments.createMap()
|
|
644
|
+
result.putString("outputPath", resolvedOutputFile)
|
|
645
|
+
result.putDouble("startTime", startTime)
|
|
646
|
+
result.putDouble("endTime", endTime)
|
|
647
|
+
result.putDouble("duration", duration)
|
|
648
|
+
result.putBoolean("success", true)
|
|
649
|
+
|
|
650
|
+
if (options?.getBoolean("saveToPhoto") == true && options.getString("type") == "video") {
|
|
651
|
+
Log.d(TAG, "Android trim: saveToPhoto is true, attempting to save to gallery")
|
|
652
|
+
try {
|
|
653
|
+
StorageUtil.saveToGallery(reactApplicationContext, resolvedOutputFile)
|
|
654
|
+
Log.d(TAG, "Edited video saved to Photo Library successfully.")
|
|
655
|
+
if (options.getBoolean("removeAfterSavedToPhoto")) {
|
|
656
|
+
Log.d(TAG, "Removing file after successful save to photo")
|
|
657
|
+
StorageUtil.deleteFile(resolvedOutputFile)
|
|
658
|
+
}
|
|
659
|
+
promise.resolve(result)
|
|
660
|
+
} catch (e: IOException) {
|
|
661
|
+
e.printStackTrace()
|
|
662
|
+
if (options.getBoolean("removeAfterFailedToSavePhoto")) {
|
|
663
|
+
Log.d(TAG, "Removing file after failed save to photo")
|
|
664
|
+
StorageUtil.deleteFile(resolvedOutputFile)
|
|
665
|
+
}
|
|
666
|
+
promise.reject(
|
|
667
|
+
Exception("Failed to save edited video to Photo Library: " + e.localizedMessage)
|
|
668
|
+
)
|
|
669
|
+
}
|
|
670
|
+
} else {
|
|
671
|
+
Log.d(TAG, "Android trim: saveToPhoto is false or not video type, resolving with structured result")
|
|
672
|
+
promise.resolve(result)
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
val callbacks = VideoTrimmerUtil.TrimCallbacks(
|
|
677
|
+
onLog = { msg -> msg.getString("message")?.let { Log.d(TAG, it) } },
|
|
678
|
+
onStatistics = { /* headless trim does not surface statistics */ },
|
|
679
|
+
onProgress = { /* headless trim does not surface progress */ },
|
|
680
|
+
onSuccess = onSuccess,
|
|
681
|
+
onCancel = {
|
|
682
|
+
println("FFmpeg command was cancelled")
|
|
683
|
+
promise.reject(Exception("FFmpeg command was cancelled"))
|
|
684
|
+
},
|
|
685
|
+
onError = { errorMessage, _ ->
|
|
686
|
+
Log.d(TAG, errorMessage)
|
|
687
|
+
promise.reject(Exception(errorMessage))
|
|
688
|
+
},
|
|
642
689
|
)
|
|
643
690
|
|
|
644
|
-
if (needsReEncode) {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
}
|
|
691
|
+
if (!needsReEncode) {
|
|
692
|
+
// Stream copy: no encoder is opened so the fallback chain doesn't apply.
|
|
693
|
+
val cmds = mutableListOf(
|
|
694
|
+
"-ss", "${startTime}ms",
|
|
695
|
+
"-to", "${endTime}ms",
|
|
696
|
+
"-i", url,
|
|
697
|
+
)
|
|
698
|
+
if (removeAudio) {
|
|
699
|
+
cmds.addAll(listOf("-c:v", "copy", "-an"))
|
|
700
|
+
} else {
|
|
701
|
+
cmds.addAll(listOf("-c", "copy"))
|
|
702
|
+
}
|
|
703
|
+
cmds.addAll(listOf("-metadata", "creation_time=$formattedDateTime", resolvedOutputFile))
|
|
704
|
+
VideoTrimmerUtil.executeWithEncoderFallback(
|
|
705
|
+
encoderConfigs = listOf(emptyList()),
|
|
706
|
+
buildCommand = { cmds.toTypedArray() },
|
|
707
|
+
videoDurationMs = 0,
|
|
708
|
+
callbacks = callbacks,
|
|
709
|
+
)
|
|
710
|
+
return
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
var bitrateStr = "10M"
|
|
714
|
+
try {
|
|
715
|
+
val retriever = MediaMetadataRetriever()
|
|
716
|
+
retriever.setDataSource(reactApplicationContext, Uri.parse(url))
|
|
717
|
+
val bitrate = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE)
|
|
718
|
+
?.toLongOrNull() ?: 0L
|
|
719
|
+
if (bitrate > 0) bitrateStr = "$bitrate"
|
|
720
|
+
retriever.release()
|
|
721
|
+
} catch (_: Exception) {}
|
|
654
722
|
|
|
655
|
-
|
|
723
|
+
val buildCommand: (List<String>) -> Array<String> = { encoderArgs ->
|
|
724
|
+
val cmds = mutableListOf(
|
|
725
|
+
"-ss", "${startTime}ms",
|
|
726
|
+
"-to", "${endTime}ms",
|
|
727
|
+
"-i", url,
|
|
728
|
+
)
|
|
656
729
|
if (speed != 1.0) {
|
|
657
730
|
cmds.addAll(listOf("-vf", "setpts=${1.0 / speed}*PTS"))
|
|
658
731
|
}
|
|
659
|
-
cmds.addAll(
|
|
732
|
+
cmds.addAll(encoderArgs)
|
|
660
733
|
when {
|
|
661
734
|
removeAudio -> cmds.add("-an")
|
|
662
735
|
speed != 1.0 -> cmds.addAll(listOf("-af", VideoTrimmerUtil.buildAtempoChain(speed)))
|
|
663
736
|
else -> cmds.addAll(listOf("-c:a", "copy"))
|
|
664
737
|
}
|
|
665
738
|
cmds.addAll(listOf("-metadata", "creation_time=$formattedDateTime", resolvedOutputFile))
|
|
666
|
-
|
|
667
|
-
cmds.addAll(listOf("-i", url))
|
|
668
|
-
if (removeAudio) {
|
|
669
|
-
cmds.addAll(listOf("-c:v", "copy", "-an"))
|
|
670
|
-
} else {
|
|
671
|
-
cmds.addAll(listOf("-c", "copy"))
|
|
672
|
-
}
|
|
673
|
-
cmds.addAll(listOf("-metadata", "creation_time=$formattedDateTime", resolvedOutputFile))
|
|
739
|
+
cmds.toTypedArray()
|
|
674
740
|
}
|
|
675
741
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
when {
|
|
683
|
-
ReturnCode.isSuccess(returnCode) -> {
|
|
684
|
-
val duration = endTime - startTime
|
|
685
|
-
val result = Arguments.createMap()
|
|
686
|
-
|
|
687
|
-
result.putString("outputPath", resolvedOutputFile)
|
|
688
|
-
result.putDouble("startTime", startTime)
|
|
689
|
-
result.putDouble("endTime", endTime)
|
|
690
|
-
result.putDouble("duration", duration)
|
|
691
|
-
result.putBoolean("success", true)
|
|
692
|
-
|
|
693
|
-
if (options?.getBoolean("saveToPhoto") == true && options.getString("type") == "video") {
|
|
694
|
-
Log.d(TAG, "Android trim: saveToPhoto is true, attempting to save to gallery")
|
|
695
|
-
try {
|
|
696
|
-
StorageUtil.saveToGallery(reactApplicationContext, resolvedOutputFile)
|
|
697
|
-
Log.d(TAG, "Edited video saved to Photo Library successfully.")
|
|
698
|
-
if (options.getBoolean("removeAfterSavedToPhoto")) {
|
|
699
|
-
Log.d(TAG, "Removing file after successful save to photo")
|
|
700
|
-
StorageUtil.deleteFile(resolvedOutputFile)
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
promise.resolve(result)
|
|
704
|
-
} catch (e: IOException) {
|
|
705
|
-
e.printStackTrace()
|
|
706
|
-
|
|
707
|
-
if (options.getBoolean("removeAfterFailedToSavePhoto")) {
|
|
708
|
-
Log.d(TAG, "Removing file after failed save to photo")
|
|
709
|
-
StorageUtil.deleteFile(resolvedOutputFile)
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
promise.reject(
|
|
713
|
-
Exception("Failed to save edited video to Photo Library: " + e.localizedMessage)
|
|
714
|
-
)
|
|
715
|
-
}
|
|
716
|
-
} else {
|
|
717
|
-
Log.d(TAG, "Android trim: saveToPhoto is false or not video type, resolving with structured result")
|
|
718
|
-
|
|
719
|
-
promise.resolve(result)
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
ReturnCode.isCancel(returnCode) -> {
|
|
723
|
-
println("FFmpeg command was cancelled")
|
|
724
|
-
promise.reject(
|
|
725
|
-
Exception("FFmpeg command was cancelled with code $returnCode")
|
|
726
|
-
)
|
|
727
|
-
}
|
|
728
|
-
else -> {
|
|
729
|
-
val errorMessage = String.format("Command failed with state %s and rc %s.%s", state, returnCode, session.getFailStackTrace())
|
|
730
|
-
Log.d(TAG, errorMessage)
|
|
731
|
-
promise.reject(
|
|
732
|
-
Exception(errorMessage)
|
|
733
|
-
)
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
}, { log ->
|
|
738
|
-
Log.d(TAG, "FFmpeg process started with log ${log.message}")
|
|
739
|
-
}, { statistics ->
|
|
740
|
-
// Handle statistics if needed
|
|
741
|
-
})
|
|
742
|
+
VideoTrimmerUtil.executeWithEncoderFallback(
|
|
743
|
+
encoderConfigs = VideoTrimmerUtil.reEncodeEncoderConfigs(bitrateStr),
|
|
744
|
+
buildCommand = buildCommand,
|
|
745
|
+
videoDurationMs = 0,
|
|
746
|
+
callbacks = callbacks,
|
|
747
|
+
)
|
|
742
748
|
}
|
|
743
749
|
|
|
744
750
|
// Extracts a single video frame as JPEG/PNG. Output goes to the cache directory.
|
|
@@ -10,6 +10,7 @@ import com.arthenica.ffmpegkit.FFmpegSession
|
|
|
10
10
|
import com.arthenica.ffmpegkit.ReturnCode
|
|
11
11
|
import com.facebook.react.bridge.Arguments
|
|
12
12
|
import com.facebook.react.bridge.UiThreadUtil
|
|
13
|
+
import com.facebook.react.bridge.WritableMap
|
|
13
14
|
import com.videotrim.enums.ErrorCode
|
|
14
15
|
import com.videotrim.interfaces.VideoTrimListener
|
|
15
16
|
import iknow.android.utils.DeviceUtil
|
|
@@ -21,6 +22,28 @@ import java.util.Date
|
|
|
21
22
|
import java.util.TimeZone
|
|
22
23
|
import kotlin.math.roundToInt
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Handle returned from FFmpeg trim invocations that own an encoder-fallback chain.
|
|
27
|
+
*
|
|
28
|
+
* A single trim request may span multiple FFmpeg sessions when the first encoder
|
|
29
|
+
* (typically [h264_mediacodec]) fails at configure-time on a quirky hardware
|
|
30
|
+
* encoder and we retry with a software fallback. The handle tracks the currently
|
|
31
|
+
* running session so callers can cancel without caring which attempt is active.
|
|
32
|
+
*/
|
|
33
|
+
class TrimSession {
|
|
34
|
+
@Volatile private var currentSession: FFmpegSession? = null
|
|
35
|
+
@Volatile internal var cancelled = false
|
|
36
|
+
|
|
37
|
+
internal fun setCurrentSession(session: FFmpegSession?) {
|
|
38
|
+
currentSession = session
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fun cancel() {
|
|
42
|
+
cancelled = true
|
|
43
|
+
currentSession?.cancel()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
24
47
|
object VideoTrimmerUtil {
|
|
25
48
|
|
|
26
49
|
private val TAG: String = VideoTrimmerUtil::class.java.simpleName
|
|
@@ -53,6 +76,186 @@ object VideoTrimmerUtil {
|
|
|
53
76
|
return filters.joinToString(",")
|
|
54
77
|
}
|
|
55
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Encoder fallback chain used when a re-encode is required. Tried in order
|
|
81
|
+
* until one succeeds at FFmpeg's configure() step.
|
|
82
|
+
*
|
|
83
|
+
* 1. `h264_mediacodec` — Android's hardware H.264 encoder. Fastest and the
|
|
84
|
+
* default. Fails on some quirky Qualcomm/MediaTek implementations with
|
|
85
|
+
* `MediaCodec configure failed` (see "Android encoder compatibility" in
|
|
86
|
+
* README).
|
|
87
|
+
* 2. `mpeg4` — software MPEG-4 Part 2 (Simple Profile). Always available in
|
|
88
|
+
* every FFmpegKit build, succeeds on every device. Produces larger files
|
|
89
|
+
* at lower visual quality than H.264, used only as a last resort when
|
|
90
|
+
* the hardware encoder rejects the configuration.
|
|
91
|
+
*
|
|
92
|
+
* The chain only matters for the re-encode branch (transform/crop/precise/
|
|
93
|
+
* speed); the default stream-copy path doesn't open an encoder at all.
|
|
94
|
+
*/
|
|
95
|
+
internal fun reEncodeEncoderConfigs(bitrateStr: String): List<List<String>> = listOf(
|
|
96
|
+
listOf("-c:v", "h264_mediacodec", "-b:v", bitrateStr),
|
|
97
|
+
listOf("-c:v", "mpeg4", "-q:v", "3")
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Scan an FFmpeg session's log for signatures that indicate the hardware
|
|
102
|
+
* H.264 encoder (MediaCodec) refused to configure on this device. These
|
|
103
|
+
* lines come from FFmpeg's mediacodec wrapper:
|
|
104
|
+
*
|
|
105
|
+
* `[amediacodec @ ...] android.media.MediaCodec$CodecException: Error 0xffffffc3`
|
|
106
|
+
* `[h264_mediacodec @ ...] MediaCodec configure failed, Generic error in an external library`
|
|
107
|
+
*
|
|
108
|
+
* When this happens we re-issue the command with the next encoder in the
|
|
109
|
+
* fallback chain instead of surfacing the failure to the user.
|
|
110
|
+
*/
|
|
111
|
+
internal fun classifyFFmpegError(session: FFmpegSession): ErrorCode {
|
|
112
|
+
val logs = try { session.allLogsAsString ?: "" } catch (_: Exception) { "" }
|
|
113
|
+
val hardwareEncoderSignals = listOf(
|
|
114
|
+
"MediaCodec configure failed",
|
|
115
|
+
"Error initializing output stream",
|
|
116
|
+
)
|
|
117
|
+
val matchedHardwareSignal = hardwareEncoderSignals.any { logs.contains(it) }
|
|
118
|
+
if (matchedHardwareSignal && logs.contains("mediacodec")) {
|
|
119
|
+
return ErrorCode.HARDWARE_ENCODER_FAILED
|
|
120
|
+
}
|
|
121
|
+
return ErrorCode.TRIMMING_FAILED
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Callbacks used by [executeWithEncoderFallback]. Per-attempt log and
|
|
126
|
+
* statistics are forwarded as-is; the success/cancel/error callbacks fire
|
|
127
|
+
* exactly once for the overall trim request after the fallback chain
|
|
128
|
+
* settles.
|
|
129
|
+
*/
|
|
130
|
+
internal class TrimCallbacks(
|
|
131
|
+
val onLog: (WritableMap) -> Unit,
|
|
132
|
+
val onStatistics: (WritableMap) -> Unit,
|
|
133
|
+
val onProgress: (Int) -> Unit,
|
|
134
|
+
val onSuccess: () -> Unit,
|
|
135
|
+
val onCancel: () -> Unit,
|
|
136
|
+
val onError: (String, ErrorCode) -> Unit,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Execute an FFmpeg trim command with an encoder-fallback chain.
|
|
141
|
+
*
|
|
142
|
+
* The command is built lazily per attempt by [buildCommand], which receives
|
|
143
|
+
* the current encoder args (e.g. `-c:v h264_mediacodec -b:v 8000000`) and
|
|
144
|
+
* returns the full argv. This lets each attempt swap only the encoder
|
|
145
|
+
* portion while reusing the same input/filter/output args.
|
|
146
|
+
*
|
|
147
|
+
* On non-success non-cancel completion, the session log is scanned via
|
|
148
|
+
* [classifyFFmpegError]. If it looks like a hardware encoder rejection and
|
|
149
|
+
* there are more encoders in the chain, we silently retry. Otherwise the
|
|
150
|
+
* error is surfaced via [TrimCallbacks.onError] with the classified code.
|
|
151
|
+
*
|
|
152
|
+
* Returns a [TrimSession] handle whose [TrimSession.cancel] cancels the
|
|
153
|
+
* currently running attempt and prevents any further attempts.
|
|
154
|
+
*/
|
|
155
|
+
internal fun executeWithEncoderFallback(
|
|
156
|
+
encoderConfigs: List<List<String>>,
|
|
157
|
+
buildCommand: (encoderArgs: List<String>) -> Array<String>,
|
|
158
|
+
videoDurationMs: Int,
|
|
159
|
+
callbacks: TrimCallbacks,
|
|
160
|
+
): TrimSession {
|
|
161
|
+
val trimSession = TrimSession()
|
|
162
|
+
runAttempt(trimSession, encoderConfigs.iterator(), buildCommand, videoDurationMs, callbacks)
|
|
163
|
+
return trimSession
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private fun runAttempt(
|
|
167
|
+
trimSession: TrimSession,
|
|
168
|
+
encoderIterator: Iterator<List<String>>,
|
|
169
|
+
buildCommand: (encoderArgs: List<String>) -> Array<String>,
|
|
170
|
+
videoDurationMs: Int,
|
|
171
|
+
callbacks: TrimCallbacks,
|
|
172
|
+
) {
|
|
173
|
+
if (trimSession.cancelled) {
|
|
174
|
+
UiThreadUtil.runOnUiThread { callbacks.onCancel() }
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
if (!encoderIterator.hasNext()) {
|
|
178
|
+
// Should be unreachable because we only retry on HARDWARE_ENCODER_FAILED;
|
|
179
|
+
// any non-retryable error is surfaced before exhausting the iterator.
|
|
180
|
+
UiThreadUtil.runOnUiThread {
|
|
181
|
+
callbacks.onError("Encoder fallback chain exhausted", ErrorCode.TRIMMING_FAILED)
|
|
182
|
+
}
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
val encoderArgs = encoderIterator.next()
|
|
187
|
+
val command = buildCommand(encoderArgs)
|
|
188
|
+
val cmdStr = "Command: ${command.joinToString(" ")}"
|
|
189
|
+
Log.d(TAG, cmdStr)
|
|
190
|
+
val cmdMap = Arguments.createMap()
|
|
191
|
+
cmdMap.putString("message", cmdStr)
|
|
192
|
+
callbacks.onLog(cmdMap)
|
|
193
|
+
|
|
194
|
+
val session = FFmpegKit.executeWithArgumentsAsync(command, { session ->
|
|
195
|
+
val state = session.state
|
|
196
|
+
val returnCode = session.returnCode
|
|
197
|
+
UiThreadUtil.runOnUiThread {
|
|
198
|
+
when {
|
|
199
|
+
ReturnCode.isSuccess(returnCode) -> callbacks.onSuccess()
|
|
200
|
+
ReturnCode.isCancel(returnCode) -> callbacks.onCancel()
|
|
201
|
+
else -> {
|
|
202
|
+
val classified = classifyFFmpegError(session)
|
|
203
|
+
if (classified == ErrorCode.HARDWARE_ENCODER_FAILED && encoderIterator.hasNext()) {
|
|
204
|
+
// Log fallback decision via the existing onLog channel so consumers
|
|
205
|
+
// can observe quality degradation without a new dedicated event.
|
|
206
|
+
val notice = Arguments.createMap()
|
|
207
|
+
notice.putString(
|
|
208
|
+
"message",
|
|
209
|
+
"Hardware encoder failed; retrying with software encoder fallback"
|
|
210
|
+
)
|
|
211
|
+
callbacks.onLog(notice)
|
|
212
|
+
runAttempt(trimSession, encoderIterator, buildCommand, videoDurationMs, callbacks)
|
|
213
|
+
} else {
|
|
214
|
+
val errorMessage =
|
|
215
|
+
"Command failed with state $state and rc $returnCode.${session.failStackTrace}"
|
|
216
|
+
callbacks.onError(errorMessage, classified)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}, { log ->
|
|
222
|
+
Log.d(TAG, "FFmpeg process started with log ${log.message}")
|
|
223
|
+
val map = Arguments.createMap()
|
|
224
|
+
map.putInt("level", log.level.value)
|
|
225
|
+
map.putString("message", log.message)
|
|
226
|
+
map.putDouble("sessionId", log.sessionId.toDouble())
|
|
227
|
+
map.putString("logStr", log.toString())
|
|
228
|
+
UiThreadUtil.runOnUiThread { callbacks.onLog(map) }
|
|
229
|
+
}, { statistics ->
|
|
230
|
+
val timeInMilliseconds = statistics.time.toInt()
|
|
231
|
+
if (timeInMilliseconds > 0 && videoDurationMs > 0) {
|
|
232
|
+
val completePercentage = (timeInMilliseconds * 100) / videoDurationMs
|
|
233
|
+
UiThreadUtil.runOnUiThread {
|
|
234
|
+
callbacks.onProgress(completePercentage.coerceIn(0, 100))
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
val map = Arguments.createMap()
|
|
238
|
+
map.putDouble("sessionId", statistics.sessionId.toDouble())
|
|
239
|
+
map.putInt("videoFrameNumber", statistics.videoFrameNumber)
|
|
240
|
+
map.putDouble("videoFps", statistics.videoFps.toDouble())
|
|
241
|
+
map.putDouble("videoQuality", statistics.videoQuality.toDouble())
|
|
242
|
+
map.putDouble("size", statistics.size.toDouble())
|
|
243
|
+
map.putDouble("time", statistics.time)
|
|
244
|
+
map.putDouble("bitrate", statistics.bitrate.toDouble())
|
|
245
|
+
map.putDouble("speed", statistics.speed.toDouble())
|
|
246
|
+
map.putString("statisticsStr", statistics.toString())
|
|
247
|
+
UiThreadUtil.runOnUiThread { callbacks.onStatistics(map) }
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
trimSession.setCurrentSession(session)
|
|
251
|
+
|
|
252
|
+
// If cancel arrived between the iterator check and session start, cancel
|
|
253
|
+
// immediately so the FFmpeg session won't run to completion.
|
|
254
|
+
if (trimSession.cancelled) {
|
|
255
|
+
session.cancel()
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
56
259
|
fun trim(
|
|
57
260
|
inputFile: String,
|
|
58
261
|
outputFile: String,
|
|
@@ -69,154 +272,110 @@ object VideoTrimmerUtil {
|
|
|
69
272
|
removeAudio: Boolean,
|
|
70
273
|
speed: Double,
|
|
71
274
|
callback: VideoTrimListener
|
|
72
|
-
):
|
|
275
|
+
): TrimSession {
|
|
73
276
|
val currentDate = Date()
|
|
74
277
|
@SuppressLint("SimpleDateFormat")
|
|
75
278
|
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
|
|
76
279
|
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
|
77
280
|
val formattedDateTime = dateFormat.format(currentDate)
|
|
78
281
|
|
|
79
|
-
val cmds = mutableListOf<String>()
|
|
80
|
-
cmds.add("-ss")
|
|
81
|
-
cmds.add("${startMs}ms")
|
|
82
|
-
cmds.add("-to")
|
|
83
|
-
cmds.add("${endMs}ms")
|
|
84
|
-
|
|
85
282
|
val hasUserTransform = userRotationCount != 0 || userIsFlipped
|
|
86
283
|
// Re-encode is required when: (1) user applied flip/rotate, (2) user cropped, or
|
|
87
284
|
// (3) enablePreciseTrimming is on, or (4) speed != 1.0. In those cases, -c copy
|
|
88
285
|
// won't work because either we need video filters or we need frame-accurate cut points.
|
|
89
286
|
val needsReEncode = hasUserTransform || cropNormalized != null || enablePreciseTrimming || speed != 1.0
|
|
90
287
|
|
|
91
|
-
|
|
92
|
-
|
|
288
|
+
val callbacks = TrimCallbacks(
|
|
289
|
+
onLog = { callback.onLog(it) },
|
|
290
|
+
onStatistics = { callback.onStatistics(it) },
|
|
291
|
+
onProgress = { callback.onTrimmingProgress(it) },
|
|
292
|
+
onSuccess = { callback.onFinishTrim(outputFile, startMs, endMs, videoDuration) },
|
|
293
|
+
onCancel = { callback.onCancelTrim() },
|
|
294
|
+
onError = { msg, code -> callback.onError(msg, code) },
|
|
295
|
+
)
|
|
93
296
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
297
|
+
if (!needsReEncode) {
|
|
298
|
+
// Stream copy: no re-encoding, extremely fast but only cuts at keyframes.
|
|
299
|
+
// No encoder is opened so the fallback chain doesn't apply — single attempt.
|
|
300
|
+
val cmds = mutableListOf("-ss", "${startMs}ms", "-to", "${endMs}ms", "-i", inputFile)
|
|
301
|
+
if (removeAudio) {
|
|
302
|
+
cmds.addAll(listOf("-c:v", "copy", "-an"))
|
|
303
|
+
} else {
|
|
304
|
+
cmds.addAll(listOf("-c", "copy"))
|
|
101
305
|
}
|
|
306
|
+
cmds.addAll(listOf("-metadata", "creation_time=$formattedDateTime", outputFile))
|
|
307
|
+
return executeWithEncoderFallback(
|
|
308
|
+
encoderConfigs = listOf(emptyList()),
|
|
309
|
+
buildCommand = { cmds.toTypedArray() },
|
|
310
|
+
videoDurationMs = videoDuration,
|
|
311
|
+
callbacks = callbacks,
|
|
312
|
+
)
|
|
313
|
+
}
|
|
102
314
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
315
|
+
// Build the video filters once. They're encoder-independent.
|
|
316
|
+
val videoFilters = mutableListOf<String>()
|
|
317
|
+
when (userRotationCount) {
|
|
318
|
+
1 -> videoFilters.add("transpose=2")
|
|
319
|
+
2 -> { videoFilters.add("transpose=2"); videoFilters.add("transpose=2") }
|
|
320
|
+
3 -> videoFilters.add("transpose=1")
|
|
321
|
+
}
|
|
322
|
+
if (userIsFlipped) {
|
|
323
|
+
videoFilters.add("hflip")
|
|
324
|
+
}
|
|
325
|
+
// Convert normalized crop rect [0..1] to pixel coordinates in the post-rotation frame.
|
|
326
|
+
if (cropNormalized != null && videoWidth > 0 && videoHeight > 0) {
|
|
327
|
+
val postW: Int
|
|
328
|
+
val postH: Int
|
|
329
|
+
// After 90°/270° rotation the width and height are swapped.
|
|
330
|
+
if (userRotationCount % 2 != 0) {
|
|
331
|
+
postW = videoHeight; postH = videoWidth
|
|
332
|
+
} else {
|
|
333
|
+
postW = videoWidth; postH = videoHeight
|
|
123
334
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
335
|
+
val cx = (cropNormalized.left * postW).roundToInt()
|
|
336
|
+
val cy = (cropNormalized.top * postH).roundToInt()
|
|
337
|
+
var cw = (cropNormalized.width() * postW).roundToInt()
|
|
338
|
+
var ch = (cropNormalized.height() * postH).roundToInt()
|
|
339
|
+
// H.264 requires even dimensions; round down to nearest even number.
|
|
340
|
+
cw = cw and 1.inv()
|
|
341
|
+
ch = ch and 1.inv()
|
|
342
|
+
if (cw > 0 && ch > 0) {
|
|
343
|
+
videoFilters.add("crop=$cw:$ch:$cx:$cy")
|
|
127
344
|
}
|
|
345
|
+
}
|
|
346
|
+
if (speed != 1.0) {
|
|
347
|
+
videoFilters.add("setpts=${1.0 / speed}*PTS")
|
|
348
|
+
}
|
|
349
|
+
val filterString = videoFilters.joinToString(",")
|
|
128
350
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
val bitrateStr = if (videoBitrate > 0) "$videoBitrate" else "10M"
|
|
351
|
+
// Preserve source quality by matching the original bitrate. Falls back to 10 Mbps.
|
|
352
|
+
val bitrateStr = if (videoBitrate > 0) "$videoBitrate" else "10M"
|
|
132
353
|
|
|
133
|
-
|
|
354
|
+
// Note: Android FFmpegKit auto-rotates by default, so no -noautorotate is needed.
|
|
355
|
+
// The transpose filters above only handle user-initiated rotation, not source metadata.
|
|
356
|
+
val buildCommand: (List<String>) -> Array<String> = { encoderArgs ->
|
|
357
|
+
val cmds = mutableListOf("-ss", "${startMs}ms", "-to", "${endMs}ms", "-i", inputFile)
|
|
134
358
|
// When enablePreciseTrimming is the only reason for re-encode (no transforms),
|
|
135
359
|
// videoFilters is empty — skip -vf entirely to avoid FFmpeg error on empty filter.
|
|
136
360
|
if (filterString.isNotEmpty()) {
|
|
137
361
|
cmds.addAll(listOf("-vf", filterString))
|
|
138
362
|
}
|
|
139
|
-
|
|
140
|
-
// Note: Android FFmpegKit auto-rotates by default, so no -noautorotate is needed.
|
|
141
|
-
// The transpose filters above only handle user-initiated rotation, not source metadata.
|
|
142
|
-
cmds.addAll(listOf("-c:v", "h264_mediacodec", "-b:v", bitrateStr))
|
|
363
|
+
cmds.addAll(encoderArgs)
|
|
143
364
|
when {
|
|
144
365
|
removeAudio -> cmds.add("-an")
|
|
145
366
|
speed != 1.0 -> cmds.addAll(listOf("-af", buildAtempoChain(speed)))
|
|
146
367
|
else -> cmds.addAll(listOf("-c:a", "copy"))
|
|
147
368
|
}
|
|
148
369
|
cmds.addAll(listOf("-metadata", "creation_time=$formattedDateTime", outputFile))
|
|
149
|
-
|
|
150
|
-
// Stream copy: no re-encoding, extremely fast but only cuts at keyframes.
|
|
151
|
-
cmds.addAll(listOf("-i", inputFile))
|
|
152
|
-
if (removeAudio) {
|
|
153
|
-
cmds.addAll(listOf("-c:v", "copy", "-an"))
|
|
154
|
-
} else {
|
|
155
|
-
cmds.addAll(listOf("-c", "copy"))
|
|
156
|
-
}
|
|
157
|
-
cmds.addAll(listOf("-metadata", "creation_time=$formattedDateTime", outputFile))
|
|
370
|
+
cmds.toTypedArray()
|
|
158
371
|
}
|
|
159
372
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
m.putString("message", cmdStr)
|
|
167
|
-
callback.onLog(m)
|
|
168
|
-
|
|
169
|
-
return FFmpegKit.executeWithArgumentsAsync(command, { session ->
|
|
170
|
-
val state = session.state
|
|
171
|
-
val returnCode = session.returnCode
|
|
172
|
-
UiThreadUtil.runOnUiThread {
|
|
173
|
-
when {
|
|
174
|
-
ReturnCode.isSuccess(returnCode) -> {
|
|
175
|
-
callback.onFinishTrim(outputFile, startMs, endMs, videoDuration)
|
|
176
|
-
}
|
|
177
|
-
ReturnCode.isCancel(returnCode) -> {
|
|
178
|
-
callback.onCancelTrim()
|
|
179
|
-
}
|
|
180
|
-
else -> {
|
|
181
|
-
val errorMessage = "Command failed with state $state and rc $returnCode.${session.failStackTrace}"
|
|
182
|
-
callback.onError(errorMessage, ErrorCode.TRIMMING_FAILED)
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}, { log ->
|
|
187
|
-
Log.d(TAG, "FFmpeg process started with log ${log.message}")
|
|
188
|
-
|
|
189
|
-
val map = Arguments.createMap()
|
|
190
|
-
map.putInt("level", log.level.value)
|
|
191
|
-
map.putString("message", log.message)
|
|
192
|
-
map.putDouble("sessionId", log.sessionId.toDouble())
|
|
193
|
-
map.putString("logStr", log.toString())
|
|
194
|
-
UiThreadUtil.runOnUiThread {
|
|
195
|
-
callback.onLog(map)
|
|
196
|
-
}
|
|
197
|
-
}, { statistics ->
|
|
198
|
-
val timeInMilliseconds = statistics.time.toInt()
|
|
199
|
-
if (timeInMilliseconds > 0) {
|
|
200
|
-
val completePercentage = (timeInMilliseconds * 100) / videoDuration
|
|
201
|
-
UiThreadUtil.runOnUiThread {
|
|
202
|
-
callback.onTrimmingProgress(completePercentage.coerceIn(0, 100))
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
val map = Arguments.createMap()
|
|
207
|
-
map.putDouble("sessionId", statistics.sessionId.toDouble())
|
|
208
|
-
map.putInt("videoFrameNumber", statistics.videoFrameNumber)
|
|
209
|
-
map.putDouble("videoFps", statistics.videoFps.toDouble())
|
|
210
|
-
map.putDouble("videoQuality", statistics.videoQuality.toDouble())
|
|
211
|
-
map.putDouble("size", statistics.size.toDouble())
|
|
212
|
-
map.putDouble("time", statistics.time)
|
|
213
|
-
map.putDouble("bitrate", statistics.bitrate.toDouble())
|
|
214
|
-
map.putDouble("speed", statistics.speed.toDouble())
|
|
215
|
-
map.putString("statisticsStr", statistics.toString())
|
|
216
|
-
UiThreadUtil.runOnUiThread {
|
|
217
|
-
callback.onStatistics(map)
|
|
218
|
-
}
|
|
219
|
-
})
|
|
373
|
+
return executeWithEncoderFallback(
|
|
374
|
+
encoderConfigs = reEncodeEncoderConfigs(bitrateStr),
|
|
375
|
+
buildCommand = buildCommand,
|
|
376
|
+
videoDurationMs = videoDuration,
|
|
377
|
+
callbacks = callbacks,
|
|
378
|
+
)
|
|
220
379
|
}
|
|
221
380
|
|
|
222
381
|
fun shootVideoThumbInBackground(
|
|
@@ -39,7 +39,6 @@ import android.widget.TextView
|
|
|
39
39
|
|
|
40
40
|
import androidx.appcompat.app.AlertDialog
|
|
41
41
|
|
|
42
|
-
import com.arthenica.ffmpegkit.FFmpegSession
|
|
43
42
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
44
43
|
import com.facebook.react.bridge.ReadableMap
|
|
45
44
|
import com.facebook.react.bridge.UiThreadUtil.runOnUiThread
|
|
@@ -167,11 +166,12 @@ class VideoTrimmerView(
|
|
|
167
166
|
private var mOutputExt = "mp4"
|
|
168
167
|
private var enableHapticFeedback = true
|
|
169
168
|
private var enablePreciseTrimming = false
|
|
169
|
+
private var enableEditTools = true
|
|
170
170
|
private var autoplay = false
|
|
171
171
|
private var jumpToPositionOnLoad = 0L
|
|
172
172
|
private lateinit var headerView: FrameLayout
|
|
173
173
|
private lateinit var headerText: TextView
|
|
174
|
-
private var
|
|
174
|
+
private var trimSession: com.videotrim.utils.TrimSession? = null
|
|
175
175
|
private var alertOnFailToLoad = true
|
|
176
176
|
private var alertOnFailTitle = "Error"
|
|
177
177
|
private var alertOnFailMessage = "Fail to load media. Possibly invalid file or no network connection"
|
|
@@ -523,13 +523,23 @@ class VideoTrimmerView(
|
|
|
523
523
|
}
|
|
524
524
|
|
|
525
525
|
mediaPlayer?.setVolume(if (isMuted) 0f else 1f, if (isMuted) 0f else 1f)
|
|
526
|
-
applyPlaybackSpeed()
|
|
526
|
+
// Do NOT call applyPlaybackSpeed() here. On Android, MediaPlayer.setPlaybackParams()
|
|
527
|
+
// with non-zero speed on a prepared player is equivalent to start(), which would
|
|
528
|
+
// silently begin playback even when autoplay=false. The configured speed is applied
|
|
529
|
+
// by playOrPause() after the explicit start() call instead.
|
|
527
530
|
|
|
528
531
|
if (isVideoType) {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
532
|
+
if (enableEditTools) {
|
|
533
|
+
transformRow.alpha = 0f
|
|
534
|
+
transformRow.visibility = View.VISIBLE
|
|
535
|
+
transformRow.animate().alpha(1f).setDuration(250).start()
|
|
536
|
+
updateUndoRedoButtons()
|
|
537
|
+
} else {
|
|
538
|
+
// Hide the entire top toolbar (flip/rotate/crop/mute/speed/undo/redo).
|
|
539
|
+
// The layout already defaults transformRow to gone, but set it explicitly
|
|
540
|
+
// in case the value changed across editor sessions.
|
|
541
|
+
transformRow.visibility = View.GONE
|
|
542
|
+
}
|
|
533
543
|
} else {
|
|
534
544
|
// Fade the waveform in to match the trimmer container animation.
|
|
535
545
|
waveformView?.animate()?.alpha(1f)?.setDuration(250)?.start()
|
|
@@ -624,9 +634,13 @@ class VideoTrimmerView(
|
|
|
624
634
|
private fun setSpeed(newSpeed: Double) {
|
|
625
635
|
speed = newSpeed
|
|
626
636
|
speedBtn.text = if (newSpeed == 1.0) "1x" else "${newSpeed}x"
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
637
|
+
// Only apply playbackParams when actively playing. Calling setPlaybackParams() on a
|
|
638
|
+
// prepared-but-paused MediaPlayer implicitly starts playback (Android docs), which
|
|
639
|
+
// would silently resume the video when the user just wanted to pick a future speed.
|
|
640
|
+
// When paused, the new speed is picked up by playOrPause() on the next play.
|
|
641
|
+
val player = mediaPlayer
|
|
642
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && player != null && player.isPlaying) {
|
|
643
|
+
player.playbackParams = player.playbackParams.setSpeed(newSpeed.toFloat())
|
|
630
644
|
}
|
|
631
645
|
if (enableHapticFeedback) {
|
|
632
646
|
performHapticFeedback(android.view.HapticFeedbackConstants.VIRTUAL_KEY)
|
|
@@ -677,7 +691,7 @@ class VideoTrimmerView(
|
|
|
677
691
|
?.toLongOrNull() ?: 0L
|
|
678
692
|
}
|
|
679
693
|
val effectiveRemoveAudio = isMuted || configRemoveAudio
|
|
680
|
-
|
|
694
|
+
trimSession = VideoTrimmerUtil.trim(
|
|
681
695
|
mSourceUri.toString(),
|
|
682
696
|
StorageUtil.getOutputPath(mContext, mOutputExt),
|
|
683
697
|
mDuration,
|
|
@@ -697,8 +711,8 @@ class VideoTrimmerView(
|
|
|
697
711
|
}
|
|
698
712
|
|
|
699
713
|
fun onCancelTrimClicked() {
|
|
700
|
-
if (
|
|
701
|
-
|
|
714
|
+
if (trimSession != null) {
|
|
715
|
+
trimSession!!.cancel()
|
|
702
716
|
} else {
|
|
703
717
|
mOnTrimVideoListener.onCancelTrim()
|
|
704
718
|
}
|
|
@@ -819,6 +833,7 @@ class VideoTrimmerView(
|
|
|
819
833
|
}
|
|
820
834
|
enableHapticFeedback = config.hasKey("enableHapticFeedback") && config.getBoolean("enableHapticFeedback")
|
|
821
835
|
enablePreciseTrimming = config.hasKey("enablePreciseTrimming") && config.getBoolean("enablePreciseTrimming")
|
|
836
|
+
enableEditTools = if (config.hasKey("enableEditTools")) config.getBoolean("enableEditTools") else true
|
|
822
837
|
autoplay = config.hasKey("autoplay") && config.getBoolean("autoplay")
|
|
823
838
|
|
|
824
839
|
if (config.hasKey("removeAudio")) {
|
package/ios/ErrorCode.swift
CHANGED
|
@@ -9,6 +9,7 @@ import Foundation
|
|
|
9
9
|
|
|
10
10
|
enum ErrorCode: String {
|
|
11
11
|
case trimmingFailed = "TRIMMING_FAILED"
|
|
12
|
+
case hardwareEncoderFailed = "HARDWARE_ENCODER_FAILED"
|
|
12
13
|
case failToLoadMedia = "FAIL_TO_LOAD_MEDIA"
|
|
13
14
|
case failToSaveToPhoto = "FAIL_TO_SAVE_TO_PHOTO"
|
|
14
15
|
case failToShare = "FAIL_TO_SHARE"
|
package/ios/VideoTrim.mm
CHANGED
|
@@ -111,6 +111,7 @@ RCT_EXPORT_MODULE()
|
|
|
111
111
|
dict[@"removeAfterShared"] = @(config.removeAfterShared());
|
|
112
112
|
dict[@"removeAfterFailedToShare"] = @(config.removeAfterFailedToShare());
|
|
113
113
|
dict[@"enableHapticFeedback"] = @(config.enableHapticFeedback());
|
|
114
|
+
dict[@"enableEditTools"] = @(config.enableEditTools());
|
|
114
115
|
dict[@"maxDuration"] = @(config.maxDuration());
|
|
115
116
|
dict[@"minDuration"] = @(config.minDuration());
|
|
116
117
|
dict[@"cancelButtonText"] = config.cancelButtonText();
|
package/ios/VideoTrim.swift
CHANGED
|
@@ -571,7 +571,8 @@ public class VideoTrim: RCTEventEmitter, AssetLoaderDelegate, UIDocumentPickerDe
|
|
|
571
571
|
self.emitEventToJS("onCancelTrimming", eventData: nil)
|
|
572
572
|
} else {
|
|
573
573
|
// FAILURE
|
|
574
|
-
|
|
574
|
+
let classified = VideoTrim.classifyFFmpegError(session: session)
|
|
575
|
+
self.onError(message: "Command failed with state \(String(describing: FFmpegKitConfig.sessionState(toString: state ?? .failed))) and rc \(String(describing: returnCode)).\(String(describing: session?.getFailStackTrace()))", code: classified)
|
|
575
576
|
shouldCloseEditor = self.closeWhenFinish
|
|
576
577
|
}
|
|
577
578
|
|
|
@@ -904,6 +905,32 @@ public class VideoTrim: RCTEventEmitter, AssetLoaderDelegate, UIDocumentPickerDe
|
|
|
904
905
|
]
|
|
905
906
|
self.emitEventToJS("onError", eventData: eventPayload)
|
|
906
907
|
}
|
|
908
|
+
|
|
909
|
+
/// Scan an FFmpeg session's log for signatures that indicate the hardware
|
|
910
|
+
/// video encoder (`h264_videotoolbox`) refused to configure on this device.
|
|
911
|
+
/// VideoToolbox failures are extremely rare on supported iOS hardware — Apple
|
|
912
|
+
/// controls the entire stack and regression-tests it — but the classifier
|
|
913
|
+
/// exists for API parity with Android (where the same hardware-encoder bug is
|
|
914
|
+
/// common and reproducible) and to give consumers a more actionable
|
|
915
|
+
/// `errorCode` than the generic `TRIMMING_FAILED` if it ever happens.
|
|
916
|
+
///
|
|
917
|
+
/// Detected signals:
|
|
918
|
+
/// - `VTCompressionSession` errors
|
|
919
|
+
/// - `Error initializing output stream` / `Error while opening encoder`
|
|
920
|
+
/// combined with a `videotoolbox` mention in the same session log
|
|
921
|
+
static func classifyFFmpegError(session: FFmpegSession?) -> ErrorCode {
|
|
922
|
+
let logs = session?.getAllLogsAsString() ?? ""
|
|
923
|
+
let hardwareEncoderSignals = [
|
|
924
|
+
"VTCompressionSession",
|
|
925
|
+
"Error initializing output stream",
|
|
926
|
+
"Error while opening encoder",
|
|
927
|
+
]
|
|
928
|
+
let matchedHardwareSignal = hardwareEncoderSignals.contains { logs.contains($0) }
|
|
929
|
+
if matchedHardwareSignal && logs.localizedCaseInsensitiveContains("videotoolbox") {
|
|
930
|
+
return .hardwareEncoderFailed
|
|
931
|
+
}
|
|
932
|
+
return .trimmingFailed
|
|
933
|
+
}
|
|
907
934
|
}
|
|
908
935
|
|
|
909
936
|
// MARK: @objc instance methods
|
|
@@ -51,6 +51,7 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
51
51
|
var cancelBtnClicked: (() -> Void)?
|
|
52
52
|
var saveBtnClicked: ((CMTimeRange) -> Void)?
|
|
53
53
|
private var enableHapticFeedback = true
|
|
54
|
+
private var enableEditTools = true
|
|
54
55
|
private var zoomOnWaitingDuration: Double = 5.0 // Default: 5 seconds
|
|
55
56
|
|
|
56
57
|
private var trimmerColor: UIColor = UIColor.systemYellow
|
|
@@ -533,7 +534,7 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
533
534
|
|
|
534
535
|
// MARK: - Transform (Rotation/Flip/Crop) + Undo/Redo
|
|
535
536
|
private func setupTransformButtons() {
|
|
536
|
-
guard isVideoType else { return }
|
|
537
|
+
guard isVideoType, enableEditTools else { return }
|
|
537
538
|
|
|
538
539
|
let flipBtn = UIButton(type: .system)
|
|
539
540
|
flipBtn.setImage(UIImage(systemName: "arrow.trianglehead.left.and.right.righttriangle.left.righttriangle.right", withConfiguration: symbolConfig), for: .normal)
|
|
@@ -1071,6 +1072,7 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
1071
1072
|
saveButtonText = config["saveButtonText"] as? String ?? "Save"
|
|
1072
1073
|
jumpToPositionOnLoad = config["jumpToPositionOnLoad"] as? Double ?? 0
|
|
1073
1074
|
enableHapticFeedback = config["enableHapticFeedback"] as? Bool ?? true
|
|
1075
|
+
enableEditTools = config["enableEditTools"] as? Bool ?? true
|
|
1074
1076
|
zoomOnWaitingDuration = (config["zoomOnWaitingDuration"] as? Double ?? 5.0) / 1000.0 // convert ms to s
|
|
1075
1077
|
autoplay = config["autoplay"] as? Bool ?? false
|
|
1076
1078
|
isVideoType = (config["type"] as? String ?? "video") == "video"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeVideoTrim.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;;AAGlD;AACA;AACA;;AAgCA;AACA;AACA;;
|
|
1
|
+
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeVideoTrim.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;;AAGlD;AACA;AACA;;AAgCA;AACA;AACA;;AAkIA;AACA;AACA;;AAQA;AACA;AACA;;AAUA;AACA;AACA;;AAcA;AACA;AACA;;AAcA;AACA;AACA;;AAMA;AACA;AACA;;AAMA;AACA;AACA;;AAQA;AACA;AACA;;AAqBA;AACA;AACA;;AAMA;AACA;AACA;;AAYA;AACA;AACA;;AAMA;AACA;AACA;;AAMA;AACA;AACA;;AAQA;AACA;AACA;;AAMA;AACA;AACA;;AAMA;AACA;AACA;;AAMA;AACA;AACA;;AA4FA,eAAeA,mBAAmB,CAACC,YAAY,CAAO,WAAW,CAAC","ignoreList":[]}
|
package/lib/module/index.js
CHANGED
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["VideoTrimNewArch","VideoTrimOldArch","processColor","isFabric","global","nativeFabricUIManager","VideoTrim","createBaseOptions","overrides","saveToPhoto","type","outputExt","removeAfterSavedToPhoto","removeAfterFailedToSavePhoto","enablePreciseTrimming","removeAudio","speed","createCompressOptions","quality","bitrate","width","height","frameRate","createFrameExtractionOptions","time","format","maxWidth","maxHeight","createExtractAudioOptions","createGifOptions","startTime","endTime","fps","createMergeOptions","createEditorConfig","enableHapticFeedback","maxDuration","minDuration","openDocumentsOnFinish","openShareSheetOnFinish","removeAfterSavedToDocuments","removeAfterFailedToSaveDocuments","removeAfterShared","removeAfterFailedToShare","cancelButtonText","saveButtonText","enableCancelDialog","cancelDialogTitle","cancelDialogMessage","cancelDialogCancelText","cancelDialogConfirmText","enableSaveDialog","saveDialogTitle","saveDialogMessage","saveDialogCancelText","saveDialogConfirmText","trimmingText","fullScreenModalIOS","autoplay","jumpToPositionOnLoad","closeWhenFinish","enableCancelTrimming","cancelTrimmingButtonText","enableCancelTrimmingDialog","cancelTrimmingDialogTitle","cancelTrimmingDialogMessage","cancelTrimmingDialogCancelText","cancelTrimmingDialogConfirmText","headerText","headerTextSize","headerTextColor","trimmerColor","handleIconColor","zoomOnWaitingDuration","alertOnFailToLoad","alertOnFailTitle","alertOnFailMessage","alertOnFailCloseText","waveformColor","waveformBackgroundColor","waveformBarWidth","waveformBarGap","waveformBarCornerRadius","durationFormat","createTrimOptions","showEditor","filePath","config","isLight","theme","_headerTextColor","_trimmerColor","_handleIconColor","_waveformColor","_waveformBackgroundColor","listFiles","cleanFiles","deleteFile","trim","length","Error","closeEditor","isValidFile","url","options","getFrameAt","extractAudio","compress","toGif","merge","urls","saveToDocuments","share"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,OAAOA,gBAAgB,MAAM,sBAAmB;AAChD,OAAOC,gBAAgB,MAAM,cAAW;AAqBxC,SAASC,YAAY,QAAQ,cAAc;;AAE3C;AACA,MAAMC,QAAQ,GAAG,CAAC,CAAEC,MAAM,CAASC,qBAAqB;AACxD,MAAMC,SAAS,GAAGH,QAAQ,GAAGH,gBAAgB,GAAGC,gBAAgB;AAEhE,SAASM,iBAAiBA,CAACC,SAA+B,GAAG,CAAC,CAAC,EAAe;EAC5E,OAAO;IACLC,WAAW,EAAE,KAAK;IAClBC,IAAI,EAAE,OAAO;IACbC,SAAS,EAAE,KAAK;IAChBC,uBAAuB,EAAE,KAAK;IAC9BC,4BAA4B,EAAE,KAAK;IACnCC,qBAAqB,EAAE,KAAK;IAC5BC,WAAW,EAAE,KAAK;IAClBC,KAAK,EAAE,GAAG;IACV,GAAGR;EACL,CAAC;AACH;AAEA,SAASS,qBAAqBA,CAC5BT,SAAmC,GAAG,CAAC,CAAC,EACvB;EACjB,OAAO;IACLU,OAAO,EAAE,QAAQ;IACjBC,OAAO,EAAE,CAAC,CAAC;IACXC,KAAK,EAAE,CAAC,CAAC;IACTC,MAAM,EAAE,CAAC,CAAC;IACVC,SAAS,EAAE,CAAC,CAAC;IACbX,SAAS,EAAE,KAAK;IAChBI,WAAW,EAAE,KAAK;IAClB,GAAGP;EACL,CAAC;AACH;AAEA,SAASe,4BAA4BA,CACnCf,SAA0C,GAAG,CAAC,CAAC,EACvB;EACxB,OAAO;IACLgB,IAAI,EAAE,CAAC;IACPC,MAAM,EAAE,MAAM;IACdP,OAAO,EAAE,EAAE;IACXQ,QAAQ,EAAE,CAAC,CAAC;IACZC,SAAS,EAAE,CAAC,CAAC;IACb,GAAGnB;EACL,CAAC;AACH;AAEA,SAASoB,yBAAyBA,CAChCpB,SAAuC,GAAG,CAAC,CAAC,EACvB;EACrB,OAAO;IACLG,SAAS,EAAE,KAAK;IAChB,GAAGH;EACL,CAAC;AACH;AAEA,SAASqB,gBAAgBA,CAACrB,SAA8B,GAAG,CAAC,CAAC,EAAc;EACzE,OAAO;IACLsB,SAAS,EAAE,CAAC;IACZC,OAAO,EAAE,CAAC,CAAC;IACXC,GAAG,EAAE,EAAE;IACPZ,KAAK,EAAE,CAAC,CAAC;IACT,GAAGZ;EACL,CAAC;AACH;AAEA,SAASyB,kBAAkBA,CACzBzB,SAAgC,GAAG,CAAC,CAAC,EACvB;EACd,OAAO;IACLG,SAAS,EAAE,KAAK;IAChB,GAAGH;EACL,CAAC;AACH;AAEA,SAAS0B,kBAAkBA,CACzB1B,SAAgC,GAAG,CAAC,CAAC,EACvB;EACd,OAAO;IACL2B,oBAAoB,EAAE,IAAI;IAC1BC,WAAW,EAAE,CAAC,CAAC;IACfC,WAAW,EAAE,CAAC,CAAC;IACfC,qBAAqB,EAAE,KAAK;IAC5BC,sBAAsB,EAAE,KAAK;IAC7BC,2BAA2B,EAAE,KAAK;IAClCC,gCAAgC,EAAE,KAAK;IACvCC,iBAAiB,EAAE,KAAK;IACxBC,wBAAwB,EAAE,KAAK;IAC/BC,gBAAgB,EAAE,QAAQ;IAC1BC,cAAc,EAAE,MAAM;IACtBC,kBAAkB,EAAE,IAAI;IACxBC,iBAAiB,EAAE,UAAU;IAC7BC,mBAAmB,EAAE,8BAA8B;IACnDC,sBAAsB,EAAE,OAAO;IAC/BC,uBAAuB,EAAE,SAAS;IAClCC,gBAAgB,EAAE,IAAI;IACtBC,eAAe,EAAE,eAAe;IAChCC,iBAAiB,EAAE,4BAA4B;IAC/CC,oBAAoB,EAAE,OAAO;IAC7BC,qBAAqB,EAAE,SAAS;IAChCC,YAAY,EAAE,mBAAmB;IACjCC,kBAAkB,EAAE,KAAK;IACzBC,QAAQ,EAAE,KAAK;IACfC,oBAAoB,EAAE,CAAC,CAAC;IACxBC,eAAe,EAAE,IAAI;IACrBC,oBAAoB,EAAE,IAAI;IAC1BC,wBAAwB,EAAE,QAAQ;IAClCC,0BAA0B,EAAE,IAAI;IAChCC,yBAAyB,EAAE,UAAU;IACrCC,2BAA2B,EAAE,uCAAuC;IACpEC,8BAA8B,EAAE,OAAO;IACvCC,+BAA+B,EAAE,SAAS;IAC1CC,UAAU,EAAE,EAAE;IACdC,cAAc,EAAE,EAAE;IAClBC,eAAe,
|
|
1
|
+
{"version":3,"names":["VideoTrimNewArch","VideoTrimOldArch","processColor","isFabric","global","nativeFabricUIManager","VideoTrim","createBaseOptions","overrides","saveToPhoto","type","outputExt","removeAfterSavedToPhoto","removeAfterFailedToSavePhoto","enablePreciseTrimming","removeAudio","speed","createCompressOptions","quality","bitrate","width","height","frameRate","createFrameExtractionOptions","time","format","maxWidth","maxHeight","createExtractAudioOptions","createGifOptions","startTime","endTime","fps","createMergeOptions","createEditorConfig","enableHapticFeedback","enableEditTools","maxDuration","minDuration","openDocumentsOnFinish","openShareSheetOnFinish","removeAfterSavedToDocuments","removeAfterFailedToSaveDocuments","removeAfterShared","removeAfterFailedToShare","cancelButtonText","saveButtonText","enableCancelDialog","cancelDialogTitle","cancelDialogMessage","cancelDialogCancelText","cancelDialogConfirmText","enableSaveDialog","saveDialogTitle","saveDialogMessage","saveDialogCancelText","saveDialogConfirmText","trimmingText","fullScreenModalIOS","autoplay","jumpToPositionOnLoad","closeWhenFinish","enableCancelTrimming","cancelTrimmingButtonText","enableCancelTrimmingDialog","cancelTrimmingDialogTitle","cancelTrimmingDialogMessage","cancelTrimmingDialogCancelText","cancelTrimmingDialogConfirmText","headerText","headerTextSize","headerTextColor","trimmerColor","handleIconColor","zoomOnWaitingDuration","alertOnFailToLoad","alertOnFailTitle","alertOnFailMessage","alertOnFailCloseText","waveformColor","waveformBackgroundColor","waveformBarWidth","waveformBarGap","waveformBarCornerRadius","durationFormat","createTrimOptions","showEditor","filePath","config","isLight","theme","_headerTextColor","_trimmerColor","_handleIconColor","_waveformColor","_waveformBackgroundColor","listFiles","cleanFiles","deleteFile","trim","length","Error","closeEditor","isValidFile","url","options","getFrameAt","extractAudio","compress","toGif","merge","urls","saveToDocuments","share"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -39,6 +39,12 @@ export interface BaseOptions {
|
|
|
39
39
|
export interface EditorConfig extends BaseOptions {
|
|
40
40
|
/** Whether to enable haptic feedback when interacting with the trimmer. */
|
|
41
41
|
enableHapticFeedback: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Whether to show the top toolbar of edit tools (flip, rotate, crop, mute,
|
|
44
|
+
* speed, undo, redo). Set to `false` to hide the entire toolbar. Default `true`.
|
|
45
|
+
* Video only — the toolbar never appears for audio files.
|
|
46
|
+
*/
|
|
47
|
+
enableEditTools: boolean;
|
|
42
48
|
/** Maximum allowed duration for the trimmed clip in milliseconds. Set to `-1` for no limit. */
|
|
43
49
|
maxDuration: number;
|
|
44
50
|
/** Minimum allowed duration for the trimmed clip in milliseconds. Set to `-1` for no limit. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NativeVideoTrim.d.ts","sourceRoot":"","sources":["../../../src/NativeVideoTrim.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NativeVideoTrim.d.ts","sourceRoot":"","sources":["../../../src/NativeVideoTrim.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,eAAe,EACf,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,EACtB,WAAW,EACX,UAAU,EACV,SAAS,EACT,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,WAAW,EACX,WAAW,EACX,UAAU,EACX,MAAM,mBAAmB,CAAC;AAK3B,QAAA,MAAM,SAAS,KAAiD,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,eAAe,EACf,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,EACtB,WAAW,EACX,UAAU,EACV,SAAS,EACT,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,WAAW,EACX,WAAW,EACX,UAAU,EACX,MAAM,mBAAmB,CAAC;AAK3B,QAAA,MAAM,SAAS,KAAiD,CAAC;AA6IjE;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,OAAO,CACb,IAAI,CACF,YAAY,EACV,iBAAiB,GACjB,cAAc,GACd,iBAAiB,GACjB,eAAe,GACf,yBAAyB,CAC5B,CACF,GAAG;IACF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC,GACA,IAAI,CAgCN;AAED;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAE7C;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAE5C;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAK7D;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAElC;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAEtE;AAED;;;;;;GAMG;AACH,wBAAgB,IAAI,CAClB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,GAC5B,OAAO,CAAC,UAAU,CAAC,CAErB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,OAAO,CAAC,sBAAsB,CAAM,GAC5C,OAAO,CAAC,WAAW,CAAC,CAEtB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,OAAO,CAAC,mBAAmB,CAAM,GACzC,OAAO,CAAC,kBAAkB,CAAC,CAE7B;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,OAAO,CAAC,eAAe,CAAM,GACrC,OAAO,CAAC,cAAc,CAAC,CAEzB;AAED;;;;;;GAMG;AACH,wBAAgB,KAAK,CACnB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,OAAO,CAAC,UAAU,CAAM,GAChC,OAAO,CAAC,SAAS,CAAC,CAEpB;AAED;;;;;;GAMG;AACH,wBAAgB,KAAK,CACnB,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,OAAO,CAAC,YAAY,CAAM,GAClC,OAAO,CAAC,WAAW,CAAC,CAKtB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAKxE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,qBAAqB,CAAC,CAKhC;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAK5D;AAED,cAAc,mBAAmB,CAAC;AAClC,eAAe,SAAS,CAAC"}
|
package/package.json
CHANGED
package/src/NativeVideoTrim.ts
CHANGED
|
@@ -42,6 +42,12 @@ export interface BaseOptions {
|
|
|
42
42
|
export interface EditorConfig extends BaseOptions {
|
|
43
43
|
/** Whether to enable haptic feedback when interacting with the trimmer. */
|
|
44
44
|
enableHapticFeedback: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Whether to show the top toolbar of edit tools (flip, rotate, crop, mute,
|
|
47
|
+
* speed, undo, redo). Set to `false` to hide the entire toolbar. Default `true`.
|
|
48
|
+
* Video only — the toolbar never appears for audio files.
|
|
49
|
+
*/
|
|
50
|
+
enableEditTools: boolean;
|
|
45
51
|
/** Maximum allowed duration for the trimmed clip in milliseconds. Set to `-1` for no limit. */
|
|
46
52
|
maxDuration: number;
|
|
47
53
|
/** Minimum allowed duration for the trimmed clip in milliseconds. Set to `-1` for no limit. */
|