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.
Files changed (39) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +206 -0
  3. package/android/build.gradle +105 -0
  4. package/android/gradle.properties +5 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/AndroidManifestDeprecated.xml +3 -0
  7. package/android/src/main/java/com/videotrim/VideoTrimModule.java +223 -0
  8. package/android/src/main/java/com/videotrim/VideoTrimPackage.java +28 -0
  9. package/android/src/main/java/com/videotrim/adapters/VideoTrimmerAdapter.java +60 -0
  10. package/android/src/main/java/com/videotrim/interfaces/IVideoTrimmerView.java +5 -0
  11. package/android/src/main/java/com/videotrim/interfaces/VideoTrimListener.java +7 -0
  12. package/android/src/main/java/com/videotrim/utils/StorageUtil.java +285 -0
  13. package/android/src/main/java/com/videotrim/utils/VideoTrimmerUtil.java +93 -0
  14. package/android/src/main/java/com/videotrim/widgets/RangeSeekBarView.java +534 -0
  15. package/android/src/main/java/com/videotrim/widgets/SpacesItemDecoration2.java +33 -0
  16. package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.java +443 -0
  17. package/android/src/main/java/com/videotrim/widgets/ZVideoView.java +48 -0
  18. package/android/src/main/res/drawable/ic_video_pause_black.png +0 -0
  19. package/android/src/main/res/drawable/ic_video_play_black.png +0 -0
  20. package/android/src/main/res/drawable/ic_video_thumb_handle.png +0 -0
  21. package/android/src/main/res/drawable/icon_seek_bar.png +0 -0
  22. package/android/src/main/res/layout/video_thumb_item_layout.xml +16 -0
  23. package/android/src/main/res/layout/video_trimmer_view.xml +148 -0
  24. package/android/src/main/res/values/colors.xml +17 -0
  25. package/android/src/main/res/values/strings.xml +14 -0
  26. package/ios/VideoTrim-Bridging-Header.h +2 -0
  27. package/ios/VideoTrim.mm +10 -0
  28. package/ios/VideoTrim.swift +170 -0
  29. package/ios/VideoTrim.xcodeproj/project.pbxproj +283 -0
  30. package/ios/VideoTrim.xcodeproj/project.xcworkspace/contents.xcworkspacedata +4 -0
  31. package/lib/commonjs/index.js +32 -0
  32. package/lib/commonjs/index.js.map +1 -0
  33. package/lib/module/index.js +25 -0
  34. package/lib/module/index.js.map +1 -0
  35. package/lib/typescript/index.d.ts +7 -0
  36. package/lib/typescript/index.d.ts.map +1 -0
  37. package/package.json +158 -7
  38. package/react-native-video-trim.podspec +41 -0
  39. 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,5 @@
1
+ VideoTrim_kotlinVersion=1.7.0
2
+ VideoTrim_minSdkVersion=21
3
+ VideoTrim_targetSdkVersion=31
4
+ VideoTrim_compileSdkVersion=31
5
+ VideoTrim_ndkversion=21.4.7075529
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,3 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ package="com.videotrim">
3
+ </manifest>
@@ -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
+
@@ -0,0 +1,5 @@
1
+ package com.videotrim.interfaces;
2
+
3
+ public interface IVideoTrimmerView {
4
+ void onDestroy();
5
+ }
@@ -0,0 +1,7 @@
1
+ package com.videotrim.interfaces;
2
+
3
+ public interface VideoTrimListener {
4
+ void onStartTrim();
5
+ void onFinishTrim(String url);
6
+ void onCancel();
7
+ }