react-native-video-trim 0.0.1 → 1.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/LICENSE +20 -0
- package/README.md +206 -0
- package/android/build.gradle +105 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/AndroidManifestDeprecated.xml +3 -0
- package/android/src/main/java/com/videotrim/VideoTrimModule.java +223 -0
- package/android/src/main/java/com/videotrim/VideoTrimPackage.java +28 -0
- package/android/src/main/java/com/videotrim/adapters/VideoTrimmerAdapter.java +60 -0
- package/android/src/main/java/com/videotrim/interfaces/IVideoTrimmerView.java +5 -0
- package/android/src/main/java/com/videotrim/interfaces/VideoTrimListener.java +7 -0
- package/android/src/main/java/com/videotrim/utils/StorageUtil.java +285 -0
- package/android/src/main/java/com/videotrim/utils/VideoTrimmerUtil.java +93 -0
- package/android/src/main/java/com/videotrim/widgets/RangeSeekBarView.java +534 -0
- package/android/src/main/java/com/videotrim/widgets/SpacesItemDecoration2.java +33 -0
- package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.java +443 -0
- package/android/src/main/java/com/videotrim/widgets/ZVideoView.java +48 -0
- package/android/src/main/res/drawable/ic_video_pause_black.png +0 -0
- package/android/src/main/res/drawable/ic_video_play_black.png +0 -0
- package/android/src/main/res/drawable/ic_video_thumb_handle.png +0 -0
- package/android/src/main/res/drawable/icon_seek_bar.png +0 -0
- package/android/src/main/res/layout/video_thumb_item_layout.xml +16 -0
- package/android/src/main/res/layout/video_trimmer_view.xml +148 -0
- package/android/src/main/res/values/colors.xml +17 -0
- package/android/src/main/res/values/strings.xml +14 -0
- package/ios/VideoTrim-Bridging-Header.h +2 -0
- package/ios/VideoTrim.mm +10 -0
- package/ios/VideoTrim.swift +170 -0
- package/ios/VideoTrim.xcodeproj/project.pbxproj +283 -0
- package/ios/VideoTrim.xcodeproj/project.xcworkspace/contents.xcworkspacedata +4 -0
- package/lib/commonjs/index.js +32 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/module/index.js +25 -0
- package/lib/module/index.js.map +1 -0
- package/lib/typescript/index.d.ts +7 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/package.json +158 -7
- package/react-native-video-trim.podspec +41 -0
- package/src/index.tsx +35 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Mai Trung Duc
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# react-native-video-trim
|
|
2
|
+
<div align="center">
|
|
3
|
+
<h2>Video trimmer for your React Native app</h2>
|
|
4
|
+
|
|
5
|
+
<img src="images/android.gif" width="300" />
|
|
6
|
+
<img src="images/ios.gif" width="300" />
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install react-native-video-trim
|
|
13
|
+
|
|
14
|
+
# or with yarn
|
|
15
|
+
|
|
16
|
+
yarn add react-native-video-trim
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Next install CocoaPods deps:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
cd ios & pod install
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import { showEditor } from 'react-native-video-trim';
|
|
29
|
+
|
|
30
|
+
// ...
|
|
31
|
+
|
|
32
|
+
showEditor(videoUrl);
|
|
33
|
+
|
|
34
|
+
// or with output length limit
|
|
35
|
+
|
|
36
|
+
showEditor(videoUrl, {
|
|
37
|
+
maxDuration: 20,
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
Usually this library will be used along with other library to select video file, Eg. [react-native-image-picker](https://github.com/react-native-image-picker/react-native-image-picker). Below is real world example:
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import * as React from 'react';
|
|
44
|
+
|
|
45
|
+
import {
|
|
46
|
+
StyleSheet,
|
|
47
|
+
View,
|
|
48
|
+
Text,
|
|
49
|
+
TouchableOpacity,
|
|
50
|
+
NativeEventEmitter,
|
|
51
|
+
NativeModules,
|
|
52
|
+
} from 'react-native';
|
|
53
|
+
import { isValidVideo, showEditor } from 'react-native-video-trim';
|
|
54
|
+
import { launchImageLibrary } from 'react-native-image-picker';
|
|
55
|
+
import { useEffect } from 'react';
|
|
56
|
+
|
|
57
|
+
export default function App() {
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const eventEmitter = new NativeEventEmitter(NativeModules.VideoTrim);
|
|
60
|
+
const subscription = eventEmitter.addListener('VideoTrim', (event) => {
|
|
61
|
+
switch (event.name) {
|
|
62
|
+
case 'onShow': {
|
|
63
|
+
console.log('onShowListener', event);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case 'onHide': {
|
|
67
|
+
console.log('onHide', event);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case 'onStartTrimming': {
|
|
71
|
+
console.log('onStartTrimming', event);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case 'onFinishTrimming': {
|
|
75
|
+
console.log('onFinishTrimming', event);
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case 'onCancelTrimming': {
|
|
79
|
+
console.log('onCancelTrimming', event);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case 'onError': {
|
|
83
|
+
console.log('onError', event);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return () => {
|
|
90
|
+
subscription.remove();
|
|
91
|
+
};
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<View style={styles.container}>
|
|
96
|
+
<TouchableOpacity
|
|
97
|
+
onPress={async () => {
|
|
98
|
+
const result = await launchImageLibrary({
|
|
99
|
+
mediaType: 'video',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
isValidVideo(result.assets![0]?.uri || '').then((res) =>
|
|
103
|
+
console.log(res)
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
showEditor(result.assets![0]?.uri || '', {
|
|
107
|
+
maxDuration: 20,
|
|
108
|
+
});
|
|
109
|
+
}}
|
|
110
|
+
style={{ padding: 10, backgroundColor: 'red' }}
|
|
111
|
+
>
|
|
112
|
+
<Text>Launch Library</Text>
|
|
113
|
+
</TouchableOpacity>
|
|
114
|
+
<TouchableOpacity
|
|
115
|
+
onPress={() => {
|
|
116
|
+
isValidVideo('invalid file path').then((res) => console.log(res));
|
|
117
|
+
}}
|
|
118
|
+
style={{
|
|
119
|
+
padding: 10,
|
|
120
|
+
backgroundColor: 'blue',
|
|
121
|
+
marginTop: 20,
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
124
|
+
<Text>Check Video Valid</Text>
|
|
125
|
+
</TouchableOpacity>
|
|
126
|
+
</View>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const styles = StyleSheet.create({
|
|
131
|
+
container: {
|
|
132
|
+
flex: 1,
|
|
133
|
+
alignItems: 'center',
|
|
134
|
+
justifyContent: 'center',
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
# Methods
|
|
140
|
+
|
|
141
|
+
## showEditor(videoPath: string, config?: EditorConfig)
|
|
142
|
+
Main method to show Video Editor UI.
|
|
143
|
+
|
|
144
|
+
*Params*:
|
|
145
|
+
- `videoPath`: Path to video file, if this is an invalid path, `onError` event will be fired
|
|
146
|
+
- `config` (optional):
|
|
147
|
+
|
|
148
|
+
- `saveToPhoto` (optional, `default = true`): whether to save video to photo/gallery after editing
|
|
149
|
+
- `maxDuration` (optional): maximum duration for the trimmed video
|
|
150
|
+
|
|
151
|
+
If `saveToPhoto = true`, you must ensure that you have request permission to write to photo/gallery
|
|
152
|
+
- For Android: you need to have `<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />` in AndroidManifest.xml
|
|
153
|
+
- For iOS: you need `NSPhotoLibraryUsageDescription` in Info.plist
|
|
154
|
+
|
|
155
|
+
## isValidVideo (videoPath: string)
|
|
156
|
+
|
|
157
|
+
This method is to check if a path is a actual video. It returns `Promise<boolean>`
|
|
158
|
+
|
|
159
|
+
# Events
|
|
160
|
+
To listen for events you interest, do the following:
|
|
161
|
+
```js
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
const eventEmitter = new NativeEventEmitter(NativeModules.VideoTrim);
|
|
164
|
+
const subscription = eventEmitter.addListener('VideoTrim', (event) => {
|
|
165
|
+
switch (event.name) {
|
|
166
|
+
case 'onShow': {
|
|
167
|
+
// on Dialog show
|
|
168
|
+
console.log('onShowListener', event);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case 'onHide': {
|
|
172
|
+
// on Dialog hide
|
|
173
|
+
console.log('onHide', event);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case 'onStartTrimming': {
|
|
177
|
+
// Android only: on start trimming
|
|
178
|
+
console.log('onStartTrimming', event);
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
case 'onFinishTrimming': {
|
|
182
|
+
// on trimming is done
|
|
183
|
+
console.log('onFinishTrimming', event);
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case 'onCancelTrimming': {
|
|
187
|
+
// when user clicks Cancel button
|
|
188
|
+
console.log('onCancelTrimming', event);
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case 'onError': {
|
|
192
|
+
// any error occured: invalid file, lack of permissions to write to photo/gallery, unexpected error...
|
|
193
|
+
console.log('onError', event);
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return () => {
|
|
200
|
+
subscription.remove();
|
|
201
|
+
};
|
|
202
|
+
}, []);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
# Thanks
|
|
206
|
+
Android part is created by modified + fix bugs from [original project](Android-Video-Trimmer)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
repositories {
|
|
3
|
+
google()
|
|
4
|
+
mavenCentral()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
dependencies {
|
|
8
|
+
classpath "com.android.tools.build:gradle:7.2.1"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
def isNewArchitectureEnabled() {
|
|
13
|
+
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
apply plugin: "com.android.library"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
|
|
20
|
+
|
|
21
|
+
if (isNewArchitectureEnabled()) {
|
|
22
|
+
apply plugin: "com.facebook.react"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
def getExtOrDefault(name) {
|
|
26
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["VideoTrim_" + name]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
def getExtOrIntegerDefault(name) {
|
|
30
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["VideoTrim_" + name]).toInteger()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
def supportsNamespace() {
|
|
34
|
+
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
|
|
35
|
+
def major = parsed[0].toInteger()
|
|
36
|
+
def minor = parsed[1].toInteger()
|
|
37
|
+
|
|
38
|
+
// Namespace support was added in 7.3.0
|
|
39
|
+
if (major == 7 && minor >= 3) {
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return major >= 8
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
android {
|
|
47
|
+
if (supportsNamespace()) {
|
|
48
|
+
namespace "com.videotrim"
|
|
49
|
+
} else {
|
|
50
|
+
sourceSets {
|
|
51
|
+
main {
|
|
52
|
+
manifest.srcFile "src/main/AndroidManifestDeprecated.xml"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
58
|
+
|
|
59
|
+
defaultConfig {
|
|
60
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
61
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
62
|
+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
63
|
+
}
|
|
64
|
+
buildTypes {
|
|
65
|
+
release {
|
|
66
|
+
minifyEnabled false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
lintOptions {
|
|
71
|
+
disable "GradleCompatible"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
compileOptions {
|
|
75
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
76
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
repositories {
|
|
82
|
+
mavenCentral()
|
|
83
|
+
google()
|
|
84
|
+
jcenter()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
dependencies {
|
|
89
|
+
// For < 0.71, this will be from the local maven repo
|
|
90
|
+
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
|
|
91
|
+
//noinspection GradleDynamicVersion
|
|
92
|
+
implementation "com.facebook.react:react-native:+"
|
|
93
|
+
implementation 'com.github.iknow4:android-utils-sdk:1.1.2'
|
|
94
|
+
implementation 'nl.bravobit:android-ffmpeg:1.1.7'
|
|
95
|
+
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
|
96
|
+
implementation 'com.guolindev.permissionx:permissionx:1.7.1'
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (isNewArchitectureEnabled()) {
|
|
100
|
+
react {
|
|
101
|
+
jsRootDir = file("../src/")
|
|
102
|
+
libraryName = "VideoTrim"
|
|
103
|
+
codegenJavaPackageName = "com.videotrim"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
package com.videotrim;
|
|
2
|
+
|
|
3
|
+
import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread;
|
|
4
|
+
import android.app.Activity;
|
|
5
|
+
import android.app.ProgressDialog;
|
|
6
|
+
import android.media.MediaMetadataRetriever;
|
|
7
|
+
import android.net.Uri;
|
|
8
|
+
|
|
9
|
+
import androidx.annotation.NonNull;
|
|
10
|
+
import androidx.annotation.Nullable;
|
|
11
|
+
import androidx.appcompat.app.AlertDialog;
|
|
12
|
+
import com.facebook.react.bridge.Arguments;
|
|
13
|
+
import com.facebook.react.bridge.LifecycleEventListener;
|
|
14
|
+
import com.facebook.react.bridge.Promise;
|
|
15
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
16
|
+
import com.facebook.react.bridge.ReactContext;
|
|
17
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
18
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
19
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
20
|
+
import com.facebook.react.bridge.WritableMap;
|
|
21
|
+
import com.facebook.react.module.annotations.ReactModule;
|
|
22
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
23
|
+
import com.videotrim.interfaces.VideoTrimListener;
|
|
24
|
+
import com.videotrim.utils.StorageUtil;
|
|
25
|
+
import com.videotrim.widgets.VideoTrimmerView;
|
|
26
|
+
import java.io.IOException;
|
|
27
|
+
import iknow.android.utils.BaseUtils;
|
|
28
|
+
import nl.bravobit.ffmpeg.FFmpeg;
|
|
29
|
+
|
|
30
|
+
@ReactModule(name = VideoTrimModule.NAME)
|
|
31
|
+
public class VideoTrimModule extends ReactContextBaseJavaModule implements VideoTrimListener, LifecycleEventListener {
|
|
32
|
+
public static final String NAME = "VideoTrim";
|
|
33
|
+
private static Boolean isInit = false;
|
|
34
|
+
private VideoTrimmerView trimmerView;
|
|
35
|
+
private AlertDialog alertDialog;
|
|
36
|
+
private ProgressDialog mProgressDialog;
|
|
37
|
+
private Boolean mSaveToPhoto = true;
|
|
38
|
+
private int mMaxDuration = 0;
|
|
39
|
+
private int listenerCount = 0;
|
|
40
|
+
|
|
41
|
+
public VideoTrimModule(ReactApplicationContext reactContext) {
|
|
42
|
+
super(reactContext);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@Override
|
|
46
|
+
@NonNull
|
|
47
|
+
public String getName() {
|
|
48
|
+
return NAME;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@ReactMethod
|
|
53
|
+
public void showEditor(String videoPath, ReadableMap config) {
|
|
54
|
+
if (trimmerView != null || alertDialog != null) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (config.hasKey("saveToPhoto")) {
|
|
59
|
+
this.mSaveToPhoto = config.getBoolean("saveToPhoto");
|
|
60
|
+
}
|
|
61
|
+
if (config.hasKey("maxDuration")) {
|
|
62
|
+
this.mMaxDuration = config.getInt("maxDuration");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!_isValidVideo(videoPath)) {
|
|
66
|
+
WritableMap map = Arguments.createMap();
|
|
67
|
+
map.putString("message", "File is not a valid video");
|
|
68
|
+
sendEvent(getReactApplicationContext(), "onError", map);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
Activity activity = getReactApplicationContext().getCurrentActivity();
|
|
73
|
+
|
|
74
|
+
if (!isInit) {
|
|
75
|
+
init(activity);
|
|
76
|
+
isInit = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// here is NOT main thread, we need to create VideoTrimmerView on UI thread, so that later we can update it using same thread
|
|
80
|
+
|
|
81
|
+
runOnUiThread(() -> {
|
|
82
|
+
trimmerView = new VideoTrimmerView(getReactApplicationContext(), mMaxDuration, null);
|
|
83
|
+
trimmerView.setOnTrimVideoListener(this);
|
|
84
|
+
trimmerView.initVideoByURI(Uri.parse(videoPath));
|
|
85
|
+
|
|
86
|
+
AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
|
|
87
|
+
builder.setCancelable(false);
|
|
88
|
+
alertDialog = builder.create();
|
|
89
|
+
alertDialog.setView(trimmerView);
|
|
90
|
+
alertDialog.show();
|
|
91
|
+
|
|
92
|
+
// this is to ensure to release resource if dialog is dismissed in unexpected way (Eg. open control/notification center by dragging from top of screen)
|
|
93
|
+
alertDialog.setOnDismissListener(dialog -> {
|
|
94
|
+
// This is called in same thread as the trimmer view -> UI thread
|
|
95
|
+
if (trimmerView != null) {
|
|
96
|
+
trimmerView.onDestroy();
|
|
97
|
+
trimmerView = null;
|
|
98
|
+
}
|
|
99
|
+
hideDialog();
|
|
100
|
+
sendEvent(getReactApplicationContext(), "onHide", null);
|
|
101
|
+
});
|
|
102
|
+
sendEvent(getReactApplicationContext(), "onShow", null);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private void init(Activity activity) {
|
|
107
|
+
isInit = true;
|
|
108
|
+
// we have to init this before create videoTrimmerView
|
|
109
|
+
BaseUtils.init(getReactApplicationContext());
|
|
110
|
+
if (!FFmpeg.getInstance(getReactApplicationContext()).isSupported()) {
|
|
111
|
+
// we have to call this for FFMPEG to initialize, otherwise it'll throw can't open ffmpeg (no such file or dir)
|
|
112
|
+
WritableMap mapE = Arguments.createMap();
|
|
113
|
+
mapE.putString("message", "Android CPU arch not supported");
|
|
114
|
+
sendEvent(getReactApplicationContext(), "onError", mapE);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@Override
|
|
119
|
+
public void onHostResume() {
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@Override
|
|
124
|
+
public void onHostPause() {
|
|
125
|
+
if (trimmerView != null) {
|
|
126
|
+
trimmerView.onVideoPause();
|
|
127
|
+
trimmerView.setRestoreState(true);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@Override
|
|
132
|
+
public void onHostDestroy() {
|
|
133
|
+
hideDialog();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@Override public void onStartTrim() {
|
|
137
|
+
sendEvent(getReactApplicationContext(), "onStartTrimming", null);
|
|
138
|
+
runOnUiThread(() -> {
|
|
139
|
+
buildDialog(getReactApplicationContext().getResources().getString(R.string.trimming)).show();
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@Override public void onFinishTrim(String in) {
|
|
144
|
+
if (mProgressDialog.isShowing()) mProgressDialog.dismiss();
|
|
145
|
+
WritableMap map = Arguments.createMap();
|
|
146
|
+
map.putString("outputPath", in);
|
|
147
|
+
sendEvent(getReactApplicationContext(), "onFinishTrimming", map);
|
|
148
|
+
if (mSaveToPhoto) {
|
|
149
|
+
try {
|
|
150
|
+
StorageUtil.saveVideoToGallery(getReactApplicationContext(), in);
|
|
151
|
+
} catch (IOException e) {
|
|
152
|
+
e.printStackTrace();
|
|
153
|
+
WritableMap mapE = Arguments.createMap();
|
|
154
|
+
mapE.putString("message", "Fail to save to Gallery. Please check if you have correct permission");
|
|
155
|
+
sendEvent(getReactApplicationContext(), "onError", mapE);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
this.hideDialog();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@Override public void onCancel() {
|
|
162
|
+
sendEvent(getReactApplicationContext(), "onCancelTrimming", null);
|
|
163
|
+
this.hideDialog();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private void hideDialog() {
|
|
167
|
+
if (alertDialog != null) {
|
|
168
|
+
if(alertDialog.isShowing()) {
|
|
169
|
+
alertDialog.dismiss();
|
|
170
|
+
}
|
|
171
|
+
alertDialog = null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private ProgressDialog buildDialog(String msg) {
|
|
176
|
+
if (mProgressDialog == null) {
|
|
177
|
+
mProgressDialog = ProgressDialog.show(getReactApplicationContext().getCurrentActivity(), "", msg);
|
|
178
|
+
}
|
|
179
|
+
mProgressDialog.setMessage(msg);
|
|
180
|
+
return mProgressDialog;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@ReactMethod
|
|
184
|
+
public void addListener(String eventName) {
|
|
185
|
+
// This method is required by React
|
|
186
|
+
listenerCount += 1;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@ReactMethod
|
|
190
|
+
public void removeListeners(Integer count) {
|
|
191
|
+
// This method is required by React
|
|
192
|
+
listenerCount -= count;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
|
|
196
|
+
if (listenerCount > 0) {
|
|
197
|
+
WritableMap map = params != null ? params : Arguments.createMap();
|
|
198
|
+
map.putString("name", eventName);
|
|
199
|
+
reactContext
|
|
200
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
201
|
+
.emit("VideoTrim", map);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public boolean _isValidVideo(String filePath) {
|
|
206
|
+
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
retriever.setDataSource(getReactApplicationContext(), Uri.parse(filePath));
|
|
210
|
+
} catch (Exception e){
|
|
211
|
+
e.printStackTrace();
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
String hasVideo = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
|
|
216
|
+
return "yes".equals(hasVideo);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@ReactMethod
|
|
220
|
+
private void isValidVideo(String filePath, Promise promise) {
|
|
221
|
+
promise.resolve(_isValidVideo(filePath));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package com.videotrim;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
|
|
5
|
+
import com.facebook.react.ReactPackage;
|
|
6
|
+
import com.facebook.react.bridge.NativeModule;
|
|
7
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
8
|
+
import com.facebook.react.uimanager.ViewManager;
|
|
9
|
+
|
|
10
|
+
import java.util.ArrayList;
|
|
11
|
+
import java.util.Collections;
|
|
12
|
+
import java.util.List;
|
|
13
|
+
|
|
14
|
+
public class VideoTrimPackage implements ReactPackage {
|
|
15
|
+
@NonNull
|
|
16
|
+
@Override
|
|
17
|
+
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
|
|
18
|
+
List<NativeModule> modules = new ArrayList<>();
|
|
19
|
+
modules.add(new VideoTrimModule(reactContext));
|
|
20
|
+
return modules;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@NonNull
|
|
24
|
+
@Override
|
|
25
|
+
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
|
|
26
|
+
return Collections.emptyList();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
package com.videotrim.adapters;
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import android.content.Context;
|
|
5
|
+
import android.graphics.Bitmap;
|
|
6
|
+
import android.view.LayoutInflater;
|
|
7
|
+
import android.view.View;
|
|
8
|
+
import android.view.ViewGroup;
|
|
9
|
+
import android.widget.ImageView;
|
|
10
|
+
import android.widget.LinearLayout;
|
|
11
|
+
|
|
12
|
+
import androidx.annotation.NonNull;
|
|
13
|
+
import androidx.recyclerview.widget.RecyclerView;
|
|
14
|
+
|
|
15
|
+
import com.videotrim.R;
|
|
16
|
+
import com.videotrim.utils.VideoTrimmerUtil;
|
|
17
|
+
|
|
18
|
+
import java.util.ArrayList;
|
|
19
|
+
import java.util.List;
|
|
20
|
+
|
|
21
|
+
public class VideoTrimmerAdapter extends RecyclerView.Adapter {
|
|
22
|
+
private List<Bitmap> mBitmaps = new ArrayList<>();
|
|
23
|
+
private LayoutInflater mInflater;
|
|
24
|
+
private Context context;
|
|
25
|
+
|
|
26
|
+
public VideoTrimmerAdapter(Context context) {
|
|
27
|
+
this.context = context;
|
|
28
|
+
this.mInflater = LayoutInflater.from(context);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
32
|
+
return new TrimmerViewHolder(mInflater.inflate(R.layout.video_thumb_item_layout, parent, false));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
|
36
|
+
((TrimmerViewHolder) holder).thumbImageView.setImageBitmap(mBitmaps.get(position));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@Override public int getItemCount() {
|
|
40
|
+
return mBitmaps.size();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public void addBitmaps(Bitmap bitmap) {
|
|
44
|
+
mBitmaps.add(bitmap);
|
|
45
|
+
notifyDataSetChanged();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private final class TrimmerViewHolder extends RecyclerView.ViewHolder {
|
|
49
|
+
ImageView thumbImageView;
|
|
50
|
+
|
|
51
|
+
TrimmerViewHolder(View itemView) {
|
|
52
|
+
super(itemView);
|
|
53
|
+
thumbImageView = itemView.findViewById(R.id.thumb);
|
|
54
|
+
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) thumbImageView.getLayoutParams();
|
|
55
|
+
layoutParams.width = VideoTrimmerUtil.VIDEO_FRAMES_WIDTH / VideoTrimmerUtil.MAX_COUNT_RANGE;
|
|
56
|
+
thumbImageView.setLayoutParams(layoutParams);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|