react-native-video-trim 7.0.1 → 7.1.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 +67 -9
- package/android/src/main/java/com/videotrim/utils/VideoTrimmerUtil.kt +1 -1
- package/android/src/main/java/com/videotrim/widgets/AudioWaveformView.kt +92 -0
- package/android/src/main/java/com/videotrim/widgets/CropOverlayView.kt +10 -24
- package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.kt +606 -32
- package/ios/AudioWaveformView.swift +75 -0
- package/ios/CropOverlayView.swift +7 -11
- package/ios/VideoTrim.mm +30 -0
- package/ios/VideoTrim.swift +7 -4
- package/ios/VideoTrimmer.swift +322 -12
- package/ios/VideoTrimmerViewController.swift +114 -44
- package/lib/module/NativeVideoTrim.js.map +1 -1
- package/lib/module/index.js +16 -4
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/NativeVideoTrim.d.ts +15 -0
- package/lib/typescript/src/NativeVideoTrim.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeVideoTrim.ts +15 -0
- package/src/index.tsx +35 -4
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* [Behavior Options](#behavior-options)
|
|
13
13
|
- [Platform Setup](#platform-setup)
|
|
14
14
|
- [Advanced Features](#advanced-features)
|
|
15
|
+
* [Theming](#theming)
|
|
15
16
|
* [Audio Trimming](#audio-trimming)
|
|
16
17
|
* [Remote Files (HTTPS)](#remote-files-https)
|
|
17
18
|
* [Video Transforms (Flip, Rotate, Crop)](#video-transforms-flip-rotate-crop)
|
|
@@ -24,8 +25,7 @@
|
|
|
24
25
|
<div align="center">
|
|
25
26
|
<h2>📱 Professional video trimmer for React Native apps</h2>
|
|
26
27
|
|
|
27
|
-
<img src="images/
|
|
28
|
-
<img src="images/ios.gif" width="300" />
|
|
28
|
+
<img src="images/ios.png" width="300" />
|
|
29
29
|
|
|
30
30
|
<p>
|
|
31
31
|
<strong>✅ iOS & Android</strong> •
|
|
@@ -40,7 +40,7 @@ A powerful, easy-to-use video and audio trimming library for React Native applic
|
|
|
40
40
|
|
|
41
41
|
### ✨ Key Features
|
|
42
42
|
|
|
43
|
-
- **📹 Video & Audio Support** - Trim both video and audio files
|
|
43
|
+
- **📹 Video & Audio Support** - Trim both video and audio files with waveform visualization
|
|
44
44
|
- **🔄 Flip, Rotate & Crop** - Built-in video transforms with undo/redo support
|
|
45
45
|
- **🎯 Precise Trimming** - Optional frame-accurate cuts via hardware-accelerated re-encoding
|
|
46
46
|
- **🌐 Local & Remote Files** - Support for local storage and HTTPS URLs
|
|
@@ -48,18 +48,20 @@ A powerful, easy-to-use video and audio trimming library for React Native applic
|
|
|
48
48
|
- **✅ File Validation** - Built-in validation for media files
|
|
49
49
|
- **🗂️ File Management** - List, clean up, and delete specific files
|
|
50
50
|
- **🔄 Universal Architecture** - Works with both New and Old React Native architectures
|
|
51
|
+
- **🎨 Dark & Light Theme** - Built-in dark and light theme support
|
|
51
52
|
|
|
52
53
|
### 🎛️ Core Capabilities
|
|
53
54
|
|
|
54
55
|
| Feature | Description |
|
|
55
56
|
|---------|-------------|
|
|
56
|
-
| **Trimming** | Video/audio trimming with visual timeline controls |
|
|
57
|
+
| **Trimming** | Video/audio trimming with visual timeline controls and audio waveform |
|
|
57
58
|
| **Transforms** | Horizontal flip, 90° rotation, and freeform crop with undo/redo |
|
|
58
59
|
| **Precise Trimming** | Frame-accurate cuts using hardware re-encoding (opt-in) |
|
|
59
60
|
| **Validation** | Check if files are valid video/audio before processing |
|
|
60
61
|
| **Save Options** | Photos, Documents, Share sheet integration |
|
|
61
62
|
| **File Management** | Complete file lifecycle management |
|
|
62
63
|
| **Customization** | Extensive UI and behavior customization |
|
|
64
|
+
| **Theming** | Dark and light theme with automatic color adaptation |
|
|
63
65
|
|
|
64
66
|
<div align="center">
|
|
65
67
|
<img src="images/document_picker.png" width="250" />
|
|
@@ -282,16 +284,29 @@ All configuration options are optional. Here are the most commonly used ones:
|
|
|
282
284
|
|
|
283
285
|
| Option | Type | Default | Description |
|
|
284
286
|
|--------|------|---------|-------------|
|
|
287
|
+
| `theme` | `'dark' \| 'light'` | `'dark'` | Editor color theme (see [Theming](#theming)) |
|
|
285
288
|
| `cancelButtonText` | `string` | `"Cancel"` | Cancel button text |
|
|
286
289
|
| `saveButtonText` | `string` | `"Save"` | Save button text |
|
|
287
290
|
| `trimmingText` | `string` | `"Trimming video..."` | Progress dialog text |
|
|
288
291
|
| `headerText` | `string` | - | Header text |
|
|
289
292
|
| `headerTextSize` | `number` | `16` | Header text size |
|
|
290
|
-
| `headerTextColor` | `string` | - | Header text color |
|
|
293
|
+
| `headerTextColor` | `string` | - | Header text color (defaults to black in light theme, white in dark theme) |
|
|
291
294
|
| `trimmerColor` | `string` | - | Trimmer bar color |
|
|
292
|
-
| `handleIconColor` | `string` | - | Trimmer left/right handles color |
|
|
295
|
+
| `handleIconColor` | `string` | - | Trimmer left/right handles color (defaults to black in light theme, white in dark theme) |
|
|
293
296
|
| `fullScreenModalIOS` | `boolean` | `false` | Use fullscreen modal on iOS |
|
|
294
297
|
|
|
298
|
+
### Audio Waveform Options
|
|
299
|
+
|
|
300
|
+
These options only apply when `type: 'audio'`. The waveform replaces the thumbnail track with a bar visualization of the audio amplitude.
|
|
301
|
+
|
|
302
|
+
| Option | Type | Default | Description |
|
|
303
|
+
|--------|------|---------|-------------|
|
|
304
|
+
| `waveformColor` | `string` | `"white"` | Fill color of the waveform bars |
|
|
305
|
+
| `waveformBackgroundColor` | `string` | `"#3478F6"` | Background color behind the bars |
|
|
306
|
+
| `waveformBarWidth` | `number` | `3` | Width of each bar in dp/pt |
|
|
307
|
+
| `waveformBarGap` | `number` | `2` | Gap between bars in dp/pt |
|
|
308
|
+
| `waveformBarCornerRadius` | `number` | `1.5` | Corner radius of each bar in dp/pt |
|
|
309
|
+
|
|
295
310
|
### Dialog Options
|
|
296
311
|
|
|
297
312
|
<details>
|
|
@@ -367,6 +382,7 @@ showEditor(videoPath, {
|
|
|
367
382
|
removeAfterSavedToPhoto: true,
|
|
368
383
|
|
|
369
384
|
// UI customization
|
|
385
|
+
theme: 'light',
|
|
370
386
|
headerText: "Trim Your Video",
|
|
371
387
|
cancelButtonText: "Back",
|
|
372
388
|
saveButtonText: "Done",
|
|
@@ -398,14 +414,42 @@ buildscript {
|
|
|
398
414
|
|
|
399
415
|
## Advanced Features
|
|
400
416
|
|
|
417
|
+
### Theming
|
|
418
|
+
|
|
419
|
+
The editor supports dark and light themes. Set the `theme` option to switch between them:
|
|
420
|
+
|
|
421
|
+
```javascript
|
|
422
|
+
// Light theme
|
|
423
|
+
showEditor(videoUrl, {
|
|
424
|
+
theme: 'light',
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Dark theme (default)
|
|
428
|
+
showEditor(videoUrl, {
|
|
429
|
+
theme: 'dark',
|
|
430
|
+
});
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
| | Dark (default) | Light |
|
|
434
|
+
|---|---|---|
|
|
435
|
+
| **Background** | Black | White |
|
|
436
|
+
| **Icons & text** | White | Black |
|
|
437
|
+
| **Cancel/Save text** | White | Black |
|
|
438
|
+
| **Crop brackets & grid** | White | Black |
|
|
439
|
+
| **Progress indicator** | White | White |
|
|
440
|
+
| **Trimmer handle chevrons** | White | White |
|
|
441
|
+
| **Dialogs** | Dark style | Light style |
|
|
442
|
+
|
|
443
|
+
The `headerTextColor` and `handleIconColor` options automatically adapt to the active theme but can still be overridden explicitly.
|
|
444
|
+
|
|
401
445
|
### Audio Trimming
|
|
402
446
|
|
|
403
447
|
<div align="center">
|
|
404
|
-
<img src="images/audio_android.
|
|
405
|
-
<img src="images/audio_ios.
|
|
448
|
+
<img src="images/audio_android.png" width="200" />
|
|
449
|
+
<img src="images/audio_ios.png" width="200" />
|
|
406
450
|
</div>
|
|
407
451
|
|
|
408
|
-
For audio-only trimming, specify the media type and output format
|
|
452
|
+
For audio-only trimming, specify the media type and output format. The editor automatically displays an audio waveform visualization in place of the thumbnail track. The waveform updates on zoom for higher resolution.
|
|
409
453
|
|
|
410
454
|
```javascript
|
|
411
455
|
showEditor(audioUrl, {
|
|
@@ -415,6 +459,20 @@ showEditor(audioUrl, {
|
|
|
415
459
|
});
|
|
416
460
|
```
|
|
417
461
|
|
|
462
|
+
Customize the waveform appearance:
|
|
463
|
+
|
|
464
|
+
```javascript
|
|
465
|
+
showEditor(audioUrl, {
|
|
466
|
+
type: 'audio',
|
|
467
|
+
outputExt: 'mp3',
|
|
468
|
+
waveformColor: '#FFFFFF',
|
|
469
|
+
waveformBackgroundColor: '#3478F6',
|
|
470
|
+
waveformBarWidth: 3,
|
|
471
|
+
waveformBarGap: 2,
|
|
472
|
+
waveformBarCornerRadius: 1.5,
|
|
473
|
+
});
|
|
474
|
+
```
|
|
475
|
+
|
|
418
476
|
### Remote Files (HTTPS)
|
|
419
477
|
|
|
420
478
|
To trim remote files, you need the HTTPS-enabled version of FFmpeg:
|
|
@@ -204,7 +204,7 @@ object VideoTrimmerUtil {
|
|
|
204
204
|
endPosition: Long,
|
|
205
205
|
callback: SingleCallback<Bitmap, Int>
|
|
206
206
|
) {
|
|
207
|
-
BackgroundExecutor.execute(object : BackgroundExecutor.Task("", 0L, "") {
|
|
207
|
+
BackgroundExecutor.execute(object : BackgroundExecutor.Task("initial_thumbs", 0L, "") {
|
|
208
208
|
override fun execute() {
|
|
209
209
|
try {
|
|
210
210
|
val interval = (endPosition - startPosition) / (totalThumbsCount - 1)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
package com.videotrim.widgets
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Canvas
|
|
5
|
+
import android.graphics.Color
|
|
6
|
+
import android.graphics.Paint
|
|
7
|
+
import android.graphics.RectF
|
|
8
|
+
import android.util.AttributeSet
|
|
9
|
+
import android.view.View
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Custom View that draws an audio waveform as a row of vertical rounded-rect bars.
|
|
13
|
+
*
|
|
14
|
+
* Each bar's height is driven by a normalized amplitude value in [0, 1].
|
|
15
|
+
* The view recalculates bar count from its own width and maps the amplitudes
|
|
16
|
+
* array proportionally, so it works correctly regardless of whether the
|
|
17
|
+
* amplitudes array has more or fewer entries than the visible bar count.
|
|
18
|
+
*
|
|
19
|
+
* The background color (set via [View.setBackgroundColor]) provides the
|
|
20
|
+
* waveform track color; the bars are drawn on top with [barColor].
|
|
21
|
+
*/
|
|
22
|
+
class AudioWaveformView @JvmOverloads constructor(
|
|
23
|
+
context: Context,
|
|
24
|
+
attrs: AttributeSet? = null,
|
|
25
|
+
defStyleAttr: Int = 0
|
|
26
|
+
) : View(context, attrs, defStyleAttr) {
|
|
27
|
+
|
|
28
|
+
var amplitudes: FloatArray = FloatArray(0)
|
|
29
|
+
private set
|
|
30
|
+
private val barPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
31
|
+
color = Color.WHITE
|
|
32
|
+
style = Paint.Style.FILL
|
|
33
|
+
}
|
|
34
|
+
private val barRect = RectF()
|
|
35
|
+
|
|
36
|
+
var barColor: Int
|
|
37
|
+
get() = barPaint.color
|
|
38
|
+
set(value) {
|
|
39
|
+
barPaint.color = value
|
|
40
|
+
invalidate()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
var barWidthPx: Float = 0f
|
|
44
|
+
set(value) {
|
|
45
|
+
field = value
|
|
46
|
+
invalidate()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
var barGapPx: Float = 0f
|
|
50
|
+
set(value) {
|
|
51
|
+
field = value
|
|
52
|
+
invalidate()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
var barCornerRadiusPx: Float = 0f
|
|
56
|
+
set(value) {
|
|
57
|
+
field = value
|
|
58
|
+
invalidate()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fun setAmplitudes(data: FloatArray) {
|
|
62
|
+
amplitudes = data
|
|
63
|
+
invalidate()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun onDraw(canvas: Canvas) {
|
|
67
|
+
super.onDraw(canvas)
|
|
68
|
+
if (amplitudes.isEmpty()) return
|
|
69
|
+
|
|
70
|
+
val totalHeight = height.toFloat()
|
|
71
|
+
val step = barWidthPx + barGapPx
|
|
72
|
+
if (step <= 0f) return
|
|
73
|
+
val barCount = (width.toFloat() / step).toInt()
|
|
74
|
+
if (barCount <= 0) return
|
|
75
|
+
|
|
76
|
+
// Keep bars from touching the container edges
|
|
77
|
+
val verticalPadding = barWidthPx * 1.5f
|
|
78
|
+
val drawableHeight = totalHeight - verticalPadding * 2f
|
|
79
|
+
if (drawableHeight <= 0f) return
|
|
80
|
+
val minBarHeight = barWidthPx
|
|
81
|
+
|
|
82
|
+
for (i in 0 until barCount) {
|
|
83
|
+
val ampIndex = (i * amplitudes.size / barCount).coerceIn(0, amplitudes.size - 1)
|
|
84
|
+
val amp = amplitudes[ampIndex]
|
|
85
|
+
val barHeight = (amp * drawableHeight).coerceAtLeast(minBarHeight)
|
|
86
|
+
val x = i * step
|
|
87
|
+
val y = verticalPadding + (drawableHeight - barHeight) / 2f
|
|
88
|
+
barRect.set(x, y, x + barWidthPx, y + barHeight)
|
|
89
|
+
canvas.drawRoundRect(barRect, barCornerRadiusPx, barCornerRadiusPx, barPaint)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -6,8 +6,6 @@ import android.graphics.Color
|
|
|
6
6
|
import android.graphics.Paint
|
|
7
7
|
import android.graphics.Path
|
|
8
8
|
import android.graphics.RectF
|
|
9
|
-
import android.graphics.Region
|
|
10
|
-
import android.os.Build
|
|
11
9
|
import android.util.AttributeSet
|
|
12
10
|
import android.util.TypedValue
|
|
13
11
|
import android.view.MotionEvent
|
|
@@ -46,6 +44,16 @@ class CropOverlayView @JvmOverloads constructor(
|
|
|
46
44
|
var onCropBegan: (() -> Unit)? = null
|
|
47
45
|
var onCropEnded: (() -> Unit)? = null
|
|
48
46
|
|
|
47
|
+
var isLightTheme = false
|
|
48
|
+
set(value) {
|
|
49
|
+
field = value
|
|
50
|
+
val c = if (value) Color.BLACK else Color.WHITE
|
|
51
|
+
borderPaint.color = c
|
|
52
|
+
gridPaint.color = c
|
|
53
|
+
cornerPaint.color = c
|
|
54
|
+
invalidate()
|
|
55
|
+
}
|
|
56
|
+
|
|
49
57
|
private val minCropSize = dpToPx(60f)
|
|
50
58
|
private val borderWidth = dpToPx(1f)
|
|
51
59
|
private val cornerLength = dpToPx(20f)
|
|
@@ -71,10 +79,6 @@ class CropOverlayView @JvmOverloads constructor(
|
|
|
71
79
|
strokeCap = Paint.Cap.ROUND
|
|
72
80
|
strokeJoin = Paint.Join.ROUND
|
|
73
81
|
}
|
|
74
|
-
private val dimmingPaint = Paint().apply {
|
|
75
|
-
color = Color.argb(140, 0, 0, 0)
|
|
76
|
-
style = Paint.Style.FILL
|
|
77
|
-
}
|
|
78
82
|
|
|
79
83
|
private enum class DragEdge {
|
|
80
84
|
TOP, BOTTOM, LEFT, RIGHT,
|
|
@@ -128,8 +132,6 @@ class CropOverlayView @JvmOverloads constructor(
|
|
|
128
132
|
if (cropRect.isEmpty) return
|
|
129
133
|
val cr = cropRect
|
|
130
134
|
|
|
131
|
-
drawDimming(canvas, cr)
|
|
132
|
-
|
|
133
135
|
canvas.drawRect(cr, borderPaint)
|
|
134
136
|
|
|
135
137
|
for (i in 1..2) {
|
|
@@ -163,22 +165,6 @@ class CropOverlayView @JvmOverloads constructor(
|
|
|
163
165
|
canvas.drawPath(path, cornerPaint)
|
|
164
166
|
}
|
|
165
167
|
|
|
166
|
-
private fun drawDimming(canvas: Canvas, cr: RectF) {
|
|
167
|
-
canvas.save()
|
|
168
|
-
val fullPath = Path()
|
|
169
|
-
fullPath.addRect(0f, 0f, width.toFloat(), height.toFloat(), Path.Direction.CW)
|
|
170
|
-
val cropPath = Path()
|
|
171
|
-
cropPath.addRect(cr, Path.Direction.CW)
|
|
172
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
173
|
-
fullPath.op(cropPath, Path.Op.DIFFERENCE)
|
|
174
|
-
canvas.drawPath(fullPath, dimmingPaint)
|
|
175
|
-
} else {
|
|
176
|
-
@Suppress("DEPRECATION")
|
|
177
|
-
canvas.clipPath(cropPath, Region.Op.DIFFERENCE)
|
|
178
|
-
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), dimmingPaint)
|
|
179
|
-
}
|
|
180
|
-
canvas.restore()
|
|
181
|
-
}
|
|
182
168
|
|
|
183
169
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
184
170
|
if (event.pointerCount > 1) {
|